codekingpro/portable-devtools
114k
1import datetime2import warnings3from collections import deque4from decimal import Decimal5from enum import Enum6from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network7from pathlib import Path8from re import Pattern9from types import GeneratorType10from typing import TYPE_CHECKING, Any, Callable, Union11from uuid import UUID12 13from typing_extensions import deprecated14 15from .._internal._import_utils import import_cached_base_model16from ..color import Color17from ..networks import NameEmail18from ..types import SecretBytes, SecretStr19from ..warnings import PydanticDeprecatedSince2020 21if not TYPE_CHECKING:22 # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-2191523 # and https://youtrack.jetbrains.com/issue/PY-5142824 DeprecationWarning = PydanticDeprecatedSince2025 26__all__ = 'pydantic_encoder', 'custom_pydantic_encoder', 'timedelta_isoformat'27 28 29def isoformat(o: Union[datetime.date, datetime.time]) -> str:30 return o.isoformat()31 32 33def decimal_encoder(dec_value: Decimal) -> Union[int, float]:34 """Encodes a Decimal as int of there's no exponent, otherwise float.35 36 This is useful when we use ConstrainedDecimal to represent Numeric(x,0)37 where a integer (but not int typed) is used. Encoding this as a float38 results in failed round-tripping between encode and parse.39 Our Id type is a prime example of this.40 41 >>> decimal_encoder(Decimal("1.0"))42 1.043 44 >>> decimal_encoder(Decimal("1"))45 146 """47 exponent = dec_value.as_tuple().exponent48 if isinstance(exponent, int) and exponent >= 0:49 return int(dec_value)50 else:51 return float(dec_value)52 53 54ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = {55 bytes: lambda o: o.decode(),56 Color: str,57 datetime.date: isoformat,58 datetime.datetime: isoformat,59 datetime.time: isoformat,60 datetime.timedelta: lambda td: td.total_seconds(),61 Decimal: decimal_encoder,62 Enum: lambda o: o.value,63 frozenset: list,64 deque: list,65 GeneratorType: list,66 IPv4Address: str,67 IPv4Interface: str,68 IPv4Network: str,69 IPv6Address: str,70 IPv6Interface: str,71 IPv6Network: str,72 NameEmail: str,73 Path: str,74 Pattern: lambda o: o.pattern,75 SecretBytes: str,76 SecretStr: str,77 set: list,78 UUID: str,79}80 81 82@deprecated(83 '`pydantic_encoder` is deprecated, use `pydantic_core.to_jsonable_python` instead.',84 category=None,85)86def pydantic_encoder(obj: Any) -> Any:87 warnings.warn(88 '`pydantic_encoder` is deprecated, use `pydantic_core.to_jsonable_python` instead.',89 category=PydanticDeprecatedSince20,90 stacklevel=2,91 )92 from dataclasses import asdict, is_dataclass93 94 BaseModel = import_cached_base_model()95 96 if isinstance(obj, BaseModel):97 return obj.model_dump()98 elif is_dataclass(obj):99 return asdict(obj) # type: ignore100 101 # Check the class type and its superclasses for a matching encoder102 for base in obj.__class__.__mro__[:-1]:103 try:104 encoder = ENCODERS_BY_TYPE[base]105 except KeyError:106 continue107 return encoder(obj)108 else: # We have exited the for loop without finding a suitable encoder109 raise TypeError(f"Object of type '{obj.__class__.__name__}' is not JSON serializable")110 111 112# TODO: Add a suggested migration path once there is a way to use custom encoders113@deprecated(114 '`custom_pydantic_encoder` is deprecated, use `BaseModel.model_dump` instead.',115 category=None,116)117def custom_pydantic_encoder(type_encoders: dict[Any, Callable[[type[Any]], Any]], obj: Any) -> Any:118 warnings.warn(119 '`custom_pydantic_encoder` is deprecated, use `BaseModel.model_dump` instead.',120 category=PydanticDeprecatedSince20,121 stacklevel=2,122 )123 # Check the class type and its superclasses for a matching encoder124 for base in obj.__class__.__mro__[:-1]:125 try:126 encoder = type_encoders[base]127 except KeyError:128 continue129 130 return encoder(obj)131 else: # We have exited the for loop without finding a suitable encoder132 return pydantic_encoder(obj)133 134 135@deprecated('`timedelta_isoformat` is deprecated.', category=None)136def timedelta_isoformat(td: datetime.timedelta) -> str:137 """ISO 8601 encoding for Python timedelta object."""138 warnings.warn('`timedelta_isoformat` is deprecated.', category=PydanticDeprecatedSince20, stacklevel=2)139 minutes, seconds = divmod(td.seconds, 60)140 hours, minutes = divmod(minutes, 60)141 return f'{"-" if td.days < 0 else ""}P{abs(td.days)}DT{hours:d}H{minutes:d}M{seconds:d}.{td.microseconds:06d}S'142 