functional_serializers.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. """This module contains related classes and functions for serialization."""
  2. from __future__ import annotations
  3. import dataclasses
  4. from functools import partial, partialmethod
  5. from typing import TYPE_CHECKING, Annotated, Any, Callable, Literal, TypeVar, overload
  6. from pydantic_core import PydanticUndefined, core_schema
  7. from pydantic_core.core_schema import SerializationInfo, SerializerFunctionWrapHandler, WhenUsed
  8. from typing_extensions import TypeAlias
  9. from . import PydanticUndefinedAnnotation
  10. from ._internal import _decorators, _internal_dataclass
  11. from .annotated_handlers import GetCoreSchemaHandler
  12. @dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True)
  13. class PlainSerializer:
  14. """Plain serializers use a function to modify the output of serialization.
  15. This is particularly helpful when you want to customize the serialization for annotated types.
  16. Consider an input of `list`, which will be serialized into a space-delimited string.
  17. ```python
  18. from typing import Annotated
  19. from pydantic import BaseModel, PlainSerializer
  20. CustomStr = Annotated[
  21. list, PlainSerializer(lambda x: ' '.join(x), return_type=str)
  22. ]
  23. class StudentModel(BaseModel):
  24. courses: CustomStr
  25. student = StudentModel(courses=['Math', 'Chemistry', 'English'])
  26. print(student.model_dump())
  27. #> {'courses': 'Math Chemistry English'}
  28. ```
  29. Attributes:
  30. func: The serializer function.
  31. return_type: The return type for the function. If omitted it will be inferred from the type annotation.
  32. when_used: Determines when this serializer should be used. Accepts a string with values `'always'`,
  33. `'unless-none'`, `'json'`, and `'json-unless-none'`. Defaults to 'always'.
  34. """
  35. func: core_schema.SerializerFunction
  36. return_type: Any = PydanticUndefined
  37. when_used: WhenUsed = 'always'
  38. def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
  39. """Gets the Pydantic core schema.
  40. Args:
  41. source_type: The source type.
  42. handler: The `GetCoreSchemaHandler` instance.
  43. Returns:
  44. The Pydantic core schema.
  45. """
  46. schema = handler(source_type)
  47. if self.return_type is not PydanticUndefined:
  48. return_type = self.return_type
  49. else:
  50. try:
  51. # Do not pass in globals as the function could be defined in a different module.
  52. # Instead, let `get_callable_return_type` infer the globals to use, but still pass
  53. # in locals that may contain a parent/rebuild namespace:
  54. return_type = _decorators.get_callable_return_type(
  55. self.func,
  56. localns=handler._get_types_namespace().locals,
  57. )
  58. except NameError as e:
  59. raise PydanticUndefinedAnnotation.from_name_error(e) from e
  60. return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type)
  61. schema['serialization'] = core_schema.plain_serializer_function_ser_schema(
  62. function=self.func,
  63. info_arg=_decorators.inspect_annotated_serializer(self.func, 'plain'),
  64. return_schema=return_schema,
  65. when_used=self.when_used,
  66. )
  67. return schema
  68. @dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True)
  69. class WrapSerializer:
  70. """Wrap serializers receive the raw inputs along with a handler function that applies the standard serialization
  71. logic, and can modify the resulting value before returning it as the final output of serialization.
  72. For example, here's a scenario in which a wrap serializer transforms timezones to UTC **and** utilizes the existing `datetime` serialization logic.
  73. ```python
  74. from datetime import datetime, timezone
  75. from typing import Annotated, Any
  76. from pydantic import BaseModel, WrapSerializer
  77. class EventDatetime(BaseModel):
  78. start: datetime
  79. end: datetime
  80. def convert_to_utc(value: Any, handler, info) -> dict[str, datetime]:
  81. # Note that `handler` can actually help serialize the `value` for
  82. # further custom serialization in case it's a subclass.
  83. partial_result = handler(value, info)
  84. if info.mode == 'json':
  85. return {
  86. k: datetime.fromisoformat(v).astimezone(timezone.utc)
  87. for k, v in partial_result.items()
  88. }
  89. return {k: v.astimezone(timezone.utc) for k, v in partial_result.items()}
  90. UTCEventDatetime = Annotated[EventDatetime, WrapSerializer(convert_to_utc)]
  91. class EventModel(BaseModel):
  92. event_datetime: UTCEventDatetime
  93. dt = EventDatetime(
  94. start='2024-01-01T07:00:00-08:00', end='2024-01-03T20:00:00+06:00'
  95. )
  96. event = EventModel(event_datetime=dt)
  97. print(event.model_dump())
  98. '''
  99. {
  100. 'event_datetime': {
  101. 'start': datetime.datetime(
  102. 2024, 1, 1, 15, 0, tzinfo=datetime.timezone.utc
  103. ),
  104. 'end': datetime.datetime(
  105. 2024, 1, 3, 14, 0, tzinfo=datetime.timezone.utc
  106. ),
  107. }
  108. }
  109. '''
  110. print(event.model_dump_json())
  111. '''
  112. {"event_datetime":{"start":"2024-01-01T15:00:00Z","end":"2024-01-03T14:00:00Z"}}
  113. '''
  114. ```
  115. Attributes:
  116. func: The serializer function to be wrapped.
  117. return_type: The return type for the function. If omitted it will be inferred from the type annotation.
  118. when_used: Determines when this serializer should be used. Accepts a string with values `'always'`,
  119. `'unless-none'`, `'json'`, and `'json-unless-none'`. Defaults to 'always'.
  120. """
  121. func: core_schema.WrapSerializerFunction
  122. return_type: Any = PydanticUndefined
  123. when_used: WhenUsed = 'always'
  124. def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
  125. """This method is used to get the Pydantic core schema of the class.
  126. Args:
  127. source_type: Source type.
  128. handler: Core schema handler.
  129. Returns:
  130. The generated core schema of the class.
  131. """
  132. schema = handler(source_type)
  133. if self.return_type is not PydanticUndefined:
  134. return_type = self.return_type
  135. else:
  136. try:
  137. # Do not pass in globals as the function could be defined in a different module.
  138. # Instead, let `get_callable_return_type` infer the globals to use, but still pass
  139. # in locals that may contain a parent/rebuild namespace:
  140. return_type = _decorators.get_callable_return_type(
  141. self.func,
  142. localns=handler._get_types_namespace().locals,
  143. )
  144. except NameError as e:
  145. raise PydanticUndefinedAnnotation.from_name_error(e) from e
  146. return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type)
  147. schema['serialization'] = core_schema.wrap_serializer_function_ser_schema(
  148. function=self.func,
  149. info_arg=_decorators.inspect_annotated_serializer(self.func, 'wrap'),
  150. return_schema=return_schema,
  151. when_used=self.when_used,
  152. )
  153. return schema
  154. if TYPE_CHECKING:
  155. _Partial: TypeAlias = 'partial[Any] | partialmethod[Any]'
  156. FieldPlainSerializer: TypeAlias = 'core_schema.SerializerFunction | _Partial'
  157. """A field serializer method or function in `plain` mode."""
  158. FieldWrapSerializer: TypeAlias = 'core_schema.WrapSerializerFunction | _Partial'
  159. """A field serializer method or function in `wrap` mode."""
  160. FieldSerializer: TypeAlias = 'FieldPlainSerializer | FieldWrapSerializer'
  161. """A field serializer method or function."""
  162. _FieldPlainSerializerT = TypeVar('_FieldPlainSerializerT', bound=FieldPlainSerializer)
  163. _FieldWrapSerializerT = TypeVar('_FieldWrapSerializerT', bound=FieldWrapSerializer)
  164. @overload
  165. def field_serializer(
  166. field: str,
  167. /,
  168. *fields: str,
  169. mode: Literal['wrap'],
  170. return_type: Any = ...,
  171. when_used: WhenUsed = ...,
  172. check_fields: bool | None = ...,
  173. ) -> Callable[[_FieldWrapSerializerT], _FieldWrapSerializerT]: ...
  174. @overload
  175. def field_serializer(
  176. field: str,
  177. /,
  178. *fields: str,
  179. mode: Literal['plain'] = ...,
  180. return_type: Any = ...,
  181. when_used: WhenUsed = ...,
  182. check_fields: bool | None = ...,
  183. ) -> Callable[[_FieldPlainSerializerT], _FieldPlainSerializerT]: ...
  184. def field_serializer(
  185. *fields: str,
  186. mode: Literal['plain', 'wrap'] = 'plain',
  187. # TODO PEP 747 (grep for 'return_type' on the whole code base):
  188. return_type: Any = PydanticUndefined,
  189. when_used: WhenUsed = 'always',
  190. check_fields: bool | None = None,
  191. ) -> (
  192. Callable[[_FieldWrapSerializerT], _FieldWrapSerializerT]
  193. | Callable[[_FieldPlainSerializerT], _FieldPlainSerializerT]
  194. ):
  195. """Decorator that enables custom field serialization.
  196. In the below example, a field of type `set` is used to mitigate duplication. A `field_serializer` is used to serialize the data as a sorted list.
  197. ```python
  198. from pydantic import BaseModel, field_serializer
  199. class StudentModel(BaseModel):
  200. name: str = 'Jane'
  201. courses: set[str]
  202. @field_serializer('courses', when_used='json')
  203. def serialize_courses_in_order(self, courses: set[str]):
  204. return sorted(courses)
  205. student = StudentModel(courses={'Math', 'Chemistry', 'English'})
  206. print(student.model_dump_json())
  207. #> {"name":"Jane","courses":["Chemistry","English","Math"]}
  208. ```
  209. See [the usage documentation](../concepts/serialization.md#serializers) for more information.
  210. Four signatures are supported:
  211. - `(self, value: Any, info: FieldSerializationInfo)`
  212. - `(self, value: Any, nxt: SerializerFunctionWrapHandler, info: FieldSerializationInfo)`
  213. - `(value: Any, info: SerializationInfo)`
  214. - `(value: Any, nxt: SerializerFunctionWrapHandler, info: SerializationInfo)`
  215. Args:
  216. fields: Which field(s) the method should be called on.
  217. mode: The serialization mode.
  218. - `plain` means the function will be called instead of the default serialization logic,
  219. - `wrap` means the function will be called with an argument to optionally call the
  220. default serialization logic.
  221. return_type: Optional return type for the function, if omitted it will be inferred from the type annotation.
  222. when_used: Determines the serializer will be used for serialization.
  223. check_fields: Whether to check that the fields actually exist on the model.
  224. Returns:
  225. The decorator function.
  226. """
  227. def dec(f: FieldSerializer) -> _decorators.PydanticDescriptorProxy[Any]:
  228. dec_info = _decorators.FieldSerializerDecoratorInfo(
  229. fields=fields,
  230. mode=mode,
  231. return_type=return_type,
  232. when_used=when_used,
  233. check_fields=check_fields,
  234. )
  235. return _decorators.PydanticDescriptorProxy(f, dec_info) # pyright: ignore[reportArgumentType]
  236. return dec # pyright: ignore[reportReturnType]
  237. if TYPE_CHECKING:
  238. # The first argument in the following callables represent the `self` type:
  239. ModelPlainSerializerWithInfo: TypeAlias = Callable[[Any, SerializationInfo[Any]], Any]
  240. """A model serializer method with the `info` argument, in `plain` mode."""
  241. ModelPlainSerializerWithoutInfo: TypeAlias = Callable[[Any], Any]
  242. """A model serializer method without the `info` argument, in `plain` mode."""
  243. ModelPlainSerializer: TypeAlias = 'ModelPlainSerializerWithInfo | ModelPlainSerializerWithoutInfo'
  244. """A model serializer method in `plain` mode."""
  245. ModelWrapSerializerWithInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler, SerializationInfo[Any]], Any]
  246. """A model serializer method with the `info` argument, in `wrap` mode."""
  247. ModelWrapSerializerWithoutInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler], Any]
  248. """A model serializer method without the `info` argument, in `wrap` mode."""
  249. ModelWrapSerializer: TypeAlias = 'ModelWrapSerializerWithInfo | ModelWrapSerializerWithoutInfo'
  250. """A model serializer method in `wrap` mode."""
  251. ModelSerializer: TypeAlias = 'ModelPlainSerializer | ModelWrapSerializer'
  252. _ModelPlainSerializerT = TypeVar('_ModelPlainSerializerT', bound=ModelPlainSerializer)
  253. _ModelWrapSerializerT = TypeVar('_ModelWrapSerializerT', bound=ModelWrapSerializer)
  254. @overload
  255. def model_serializer(f: _ModelPlainSerializerT, /) -> _ModelPlainSerializerT: ...
  256. @overload
  257. def model_serializer(
  258. *, mode: Literal['wrap'], when_used: WhenUsed = 'always', return_type: Any = ...
  259. ) -> Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT]: ...
  260. @overload
  261. def model_serializer(
  262. *,
  263. mode: Literal['plain'] = ...,
  264. when_used: WhenUsed = 'always',
  265. return_type: Any = ...,
  266. ) -> Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT]: ...
  267. def model_serializer(
  268. f: _ModelPlainSerializerT | _ModelWrapSerializerT | None = None,
  269. /,
  270. *,
  271. mode: Literal['plain', 'wrap'] = 'plain',
  272. when_used: WhenUsed = 'always',
  273. return_type: Any = PydanticUndefined,
  274. ) -> (
  275. _ModelPlainSerializerT
  276. | Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT]
  277. | Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT]
  278. ):
  279. """Decorator that enables custom model serialization.
  280. This is useful when a model need to be serialized in a customized manner, allowing for flexibility beyond just specific fields.
  281. An example would be to serialize temperature to the same temperature scale, such as degrees Celsius.
  282. ```python
  283. from typing import Literal
  284. from pydantic import BaseModel, model_serializer
  285. class TemperatureModel(BaseModel):
  286. unit: Literal['C', 'F']
  287. value: int
  288. @model_serializer()
  289. def serialize_model(self):
  290. if self.unit == 'F':
  291. return {'unit': 'C', 'value': int((self.value - 32) / 1.8)}
  292. return {'unit': self.unit, 'value': self.value}
  293. temperature = TemperatureModel(unit='F', value=212)
  294. print(temperature.model_dump())
  295. #> {'unit': 'C', 'value': 100}
  296. ```
  297. Two signatures are supported for `mode='plain'`, which is the default:
  298. - `(self)`
  299. - `(self, info: SerializationInfo)`
  300. And two other signatures for `mode='wrap'`:
  301. - `(self, nxt: SerializerFunctionWrapHandler)`
  302. - `(self, nxt: SerializerFunctionWrapHandler, info: SerializationInfo)`
  303. See [the usage documentation](../concepts/serialization.md#serializers) for more information.
  304. Args:
  305. f: The function to be decorated.
  306. mode: The serialization mode.
  307. - `'plain'` means the function will be called instead of the default serialization logic
  308. - `'wrap'` means the function will be called with an argument to optionally call the default
  309. serialization logic.
  310. when_used: Determines when this serializer should be used.
  311. return_type: The return type for the function. If omitted it will be inferred from the type annotation.
  312. Returns:
  313. The decorator function.
  314. """
  315. def dec(f: ModelSerializer) -> _decorators.PydanticDescriptorProxy[Any]:
  316. dec_info = _decorators.ModelSerializerDecoratorInfo(mode=mode, return_type=return_type, when_used=when_used)
  317. return _decorators.PydanticDescriptorProxy(f, dec_info)
  318. if f is None:
  319. return dec # pyright: ignore[reportReturnType]
  320. else:
  321. return dec(f) # pyright: ignore[reportReturnType]
  322. AnyType = TypeVar('AnyType')
  323. if TYPE_CHECKING:
  324. SerializeAsAny = Annotated[AnyType, ...] # SerializeAsAny[list[str]] will be treated by type checkers as list[str]
  325. """Annotation used to mark a type as having duck-typing serialization behavior.
  326. See [usage documentation](../concepts/serialization.md#serializing-with-duck-typing) for more details.
  327. """
  328. else:
  329. @dataclasses.dataclass(**_internal_dataclass.slots_true)
  330. class SerializeAsAny:
  331. """Annotation used to mark a type as having duck-typing serialization behavior.
  332. See [usage documentation](../concepts/serialization.md#serializing-with-duck-typing) for more details.
  333. """
  334. def __class_getitem__(cls, item: Any) -> Any:
  335. return Annotated[item, SerializeAsAny()]
  336. def __get_pydantic_core_schema__(
  337. self, source_type: Any, handler: GetCoreSchemaHandler
  338. ) -> core_schema.CoreSchema:
  339. schema = handler(source_type)
  340. schema_to_update = schema
  341. while schema_to_update['type'] == 'definitions':
  342. schema_to_update = schema_to_update.copy()
  343. schema_to_update = schema_to_update['schema']
  344. schema_to_update['serialization'] = core_schema.simple_ser_schema('any')
  345. return schema
  346. __hash__ = object.__hash__