Coverage for pydantic/json.py: 100.00%

47 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-15 13:26 +0000

1import datetime 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

2from collections import deque 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

3from decimal import Decimal 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

4from enum import Enum 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

5from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

6from pathlib import Path 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

7from re import Pattern 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

8from types import GeneratorType 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

9from typing import Any, Callable, Dict, Type, Union 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

10from uuid import UUID 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

11 

12from pydantic.color import Color 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

13from pydantic.networks import NameEmail 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

14from pydantic.types import SecretBytes, SecretStr 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

15 

16__all__ = 'pydantic_encoder', 'custom_pydantic_encoder', 'timedelta_isoformat' 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

17 

18 

19def isoformat(o: Union[datetime.date, datetime.time]) -> str: 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

20 return o.isoformat() 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

21 

22 

23def decimal_encoder(dec_value: Decimal) -> Union[int, float]: 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

24 """ 

25 Encodes a Decimal as int of there's no exponent, otherwise float 

26 

27 This is useful when we use ConstrainedDecimal to represent Numeric(x,0) 

28 where a integer (but not int typed) is used. Encoding this as a float 

29 results in failed round-tripping between encode and parse. 

30 Our Id type is a prime example of this. 

31 

32 >>> decimal_encoder(Decimal("1.0")) 

33 1.0 

34 

35 >>> decimal_encoder(Decimal("1")) 

36 1 

37 """ 

38 if dec_value.as_tuple().exponent >= 0: 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

39 return int(dec_value) 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

40 else: 

41 return float(dec_value) 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

42 

43 

44ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = { 1abcdefghijklmnopqrstPQRSTUuvwxyzABCD

45 bytes: lambda o: o.decode(), 

46 Color: str, 

47 datetime.date: isoformat, 

48 datetime.datetime: isoformat, 

49 datetime.time: isoformat, 

50 datetime.timedelta: lambda td: td.total_seconds(), 

51 Decimal: decimal_encoder, 

52 Enum: lambda o: o.value, 

53 frozenset: list, 

54 deque: list, 

55 GeneratorType: list, 

56 IPv4Address: str, 

57 IPv4Interface: str, 

58 IPv4Network: str, 

59 IPv6Address: str, 

60 IPv6Interface: str, 

61 IPv6Network: str, 

62 NameEmail: str, 

63 Path: str, 

64 Pattern: lambda o: o.pattern, 

65 SecretBytes: str, 

66 SecretStr: str, 

67 set: list, 

68 UUID: str, 

69} 

70 

71 

72def pydantic_encoder(obj: Any) -> Any: 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

73 from dataclasses import asdict, is_dataclass 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

74 

75 from pydantic.main import BaseModel 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

76 

77 if isinstance(obj, BaseModel): 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

78 return obj.dict() 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

79 elif is_dataclass(obj): 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

80 return asdict(obj) 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

81 

82 # Check the class type and its superclasses for a matching encoder 

83 for base in obj.__class__.__mro__[:-1]: 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

84 try: 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

85 encoder = ENCODERS_BY_TYPE[base] 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

86 except KeyError: 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

87 continue 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

88 return encoder(obj) 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

89 else: # We have exited the for loop without finding a suitable encoder 

90 raise TypeError(f"Object of type '{obj.__class__.__name__}' is not JSON serializable") 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

91 

92 

93def custom_pydantic_encoder(type_encoders: Dict[Any, Callable[[Type[Any]], Any]], obj: Any) -> Any: 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

94 # Check the class type and its superclasses for a matching encoder 

95 for base in obj.__class__.__mro__[:-1]: 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

96 try: 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

97 encoder = type_encoders[base] 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

98 except KeyError: 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

99 continue 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

100 

101 return encoder(obj) 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

102 else: # We have exited the for loop without finding a suitable encoder 

103 return pydantic_encoder(obj) 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

104 

105 

106def timedelta_isoformat(td: datetime.timedelta) -> str: 1EFabcdefghijGHklmnopqrstIJKLPQRSTUMNOuvwxyzABCD

107 """ 

108 ISO 8601 encoding for Python timedelta object. 

109 """ 

110 minutes, seconds = divmod(td.seconds, 60) 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

111 hours, minutes = divmod(minutes, 60) 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD

112 return f'{"-" if td.days < 0 else ""}P{abs(td.days)}DT{hours:d}H{minutes:d}M{seconds:d}.{td.microseconds:06d}S' 1EFabcdefghijGHklmnopqrstIJKLMNOuvwxyzABCD