관리-도구
편집 파일: json.py
import datetime import warnings from collections import deque from decimal import Decimal from enum import Enum from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network from pathlib import Path from re import Pattern from types import GeneratorType from typing import TYPE_CHECKING, Any, Callable, Dict, Type, Union from uuid import UUID from typing_extensions import deprecated from ..color import Color from ..networks import NameEmail from ..types import SecretBytes, SecretStr from ..warnings import PydanticDeprecatedSince20 if not TYPE_CHECKING: # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915 # and https://youtrack.jetbrains.com/issue/PY-51428 DeprecationWarning = PydanticDeprecatedSince20 __all__ = 'pydantic_encoder', 'custom_pydantic_encoder', 'timedelta_isoformat' def isoformat(o: Union[datetime.date, datetime.time]) -> str: return o.isoformat() def decimal_encoder(dec_value: Decimal) -> Union[int, float]: """Encodes a Decimal as int of there's no exponent, otherwise float. This is useful when we use ConstrainedDecimal to represent Numeric(x,0) where a integer (but not int typed) is used. Encoding this as a float results in failed round-tripping between encode and parse. Our Id type is a prime example of this. >>> decimal_encoder(Decimal("1.0")) 1.0 >>> decimal_encoder(Decimal("1")) 1 """ exponent = dec_value.as_tuple().exponent if isinstance(exponent, int) and exponent >= 0: return int(dec_value) else: return float(dec_value) ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = { bytes: lambda o: o.decode(), Color: str, datetime.date: isoformat, datetime.datetime: isoformat, datetime.time: isoformat, datetime.timedelta: lambda td: td.total_seconds(), Decimal: decimal_encoder, Enum: lambda o: o.value, frozenset: list, deque: list, GeneratorType: list, IPv4Address: str, IPv4Interface: str, IPv4Network: str, IPv6Address: str, IPv6Interface: str, IPv6Network: str, NameEmail: str, Path: str, Pattern: lambda o: o.pattern, SecretBytes: str, SecretStr: str, set: list, UUID: str, } @deprecated( 'pydantic_encoder is deprecated, use pydantic_core.to_jsonable_python instead.', category=PydanticDeprecatedSince20 ) def pydantic_encoder(obj: Any) -> Any: from dataclasses import asdict, is_dataclass from ..main import BaseModel warnings.warn('pydantic_encoder is deprecated, use BaseModel.model_dump instead.', DeprecationWarning, stacklevel=2) if isinstance(obj, BaseModel): return obj.model_dump() elif is_dataclass(obj): return asdict(obj) # Check the class type and its superclasses for a matching encoder for base in obj.__class__.__mro__[:-1]: try: encoder = ENCODERS_BY_TYPE[base] except KeyError: continue return encoder(obj) else: # We have exited the for loop without finding a suitable encoder raise TypeError(f"Object of type '{obj.__class__.__name__}' is not JSON serializable") # TODO: Add a suggested migration path once there is a way to use custom encoders @deprecated('custom_pydantic_encoder is deprecated.', category=PydanticDeprecatedSince20) def custom_pydantic_encoder(type_encoders: Dict[Any, Callable[[Type[Any]], Any]], obj: Any) -> Any: # Check the class type and its superclasses for a matching encoder warnings.warn( 'custom_pydantic_encoder is deprecated, use BaseModel.model_dump instead.', DeprecationWarning, stacklevel=2 ) for base in obj.__class__.__mro__[:-1]: try: encoder = type_encoders[base] except KeyError: continue return encoder(obj) else: # We have exited the for loop without finding a suitable encoder return pydantic_encoder(obj) @deprecated('timedelta_isoformat is deprecated.', category=PydanticDeprecatedSince20) def timedelta_isoformat(td: datetime.timedelta) -> str: """ISO 8601 encoding for Python timedelta object.""" warnings.warn('timedelta_isoformat is deprecated.', DeprecationWarning, stacklevel=2) minutes, seconds = divmod(td.seconds, 60) hours, minutes = divmod(minutes, 60) return f'{"-" if td.days < 0 else ""}P{abs(td.days)}DT{hours:d}H{minutes:d}M{seconds:d}.{td.microseconds:06d}S'