Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

120 строки
3.3 KiB

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