Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

113 linhas
3.3 KiB

  1. import datetime
  2. from collections import deque
  3. from decimal import Decimal
  4. from enum import Enum
  5. from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network
  6. from pathlib import Path
  7. from re import Pattern
  8. from types import GeneratorType
  9. from typing import Any, Callable, Dict, Type, Union
  10. from uuid import UUID
  11. from pydantic.v1.color import Color
  12. from pydantic.v1.networks import NameEmail
  13. from pydantic.v1.types import SecretBytes, SecretStr
  14. __all__ = 'pydantic_encoder', 'custom_pydantic_encoder', 'timedelta_isoformat'
  15. def isoformat(o: Union[datetime.date, datetime.time]) -> str:
  16. return o.isoformat()
  17. def decimal_encoder(dec_value: Decimal) -> Union[int, float]:
  18. """
  19. Encodes a Decimal as int of there's no exponent, otherwise float
  20. This is useful when we use ConstrainedDecimal to represent Numeric(x,0)
  21. where a integer (but not int typed) is used. Encoding this as a float
  22. results in failed round-tripping between encode and parse.
  23. Our Id type is a prime example of this.
  24. >>> decimal_encoder(Decimal("1.0"))
  25. 1.0
  26. >>> decimal_encoder(Decimal("1"))
  27. 1
  28. """
  29. if dec_value.as_tuple().exponent >= 0:
  30. return int(dec_value)
  31. else:
  32. return float(dec_value)
  33. ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = {
  34. bytes: lambda o: o.decode(),
  35. Color: str,
  36. datetime.date: isoformat,
  37. datetime.datetime: isoformat,
  38. datetime.time: isoformat,
  39. datetime.timedelta: lambda td: td.total_seconds(),
  40. Decimal: decimal_encoder,
  41. Enum: lambda o: o.value,
  42. frozenset: list,
  43. deque: list,
  44. GeneratorType: list,
  45. IPv4Address: str,
  46. IPv4Interface: str,
  47. IPv4Network: str,
  48. IPv6Address: str,
  49. IPv6Interface: str,
  50. IPv6Network: str,
  51. NameEmail: str,
  52. Path: str,
  53. Pattern: lambda o: o.pattern,
  54. SecretBytes: str,
  55. SecretStr: str,
  56. set: list,
  57. UUID: str,
  58. }
  59. def pydantic_encoder(obj: Any) -> Any:
  60. from dataclasses import asdict, is_dataclass
  61. from pydantic.v1.main import BaseModel
  62. if isinstance(obj, BaseModel):
  63. return obj.dict()
  64. elif is_dataclass(obj):
  65. return asdict(obj)
  66. # Check the class type and its superclasses for a matching encoder
  67. for base in obj.__class__.__mro__[:-1]:
  68. try:
  69. encoder = ENCODERS_BY_TYPE[base]
  70. except KeyError:
  71. continue
  72. return encoder(obj)
  73. else: # We have exited the for loop without finding a suitable encoder
  74. raise TypeError(f"Object of type '{obj.__class__.__name__}' is not JSON serializable")
  75. def custom_pydantic_encoder(type_encoders: Dict[Any, Callable[[Type[Any]], Any]], obj: Any) -> Any:
  76. # Check the class type and its superclasses for a matching encoder
  77. for base in obj.__class__.__mro__[:-1]:
  78. try:
  79. encoder = type_encoders[base]
  80. except KeyError:
  81. continue
  82. return encoder(obj)
  83. else: # We have exited the for loop without finding a suitable encoder
  84. return pydantic_encoder(obj)
  85. def timedelta_isoformat(td: datetime.timedelta) -> str:
  86. """
  87. ISO 8601 encoding for Python timedelta object.
  88. """
  89. minutes, seconds = divmod(td.seconds, 60)
  90. hours, minutes = divmod(minutes, 60)
  91. return f'{"-" if td.days < 0 else ""}P{abs(td.days)}DT{hours:d}H{minutes:d}M{seconds:d}.{td.microseconds:06d}S'