Coverage for pydantic/functional_serializers.py: 96.05%
68 statements
« prev ^ index » next coverage.py v7.10.0, created at 2025-07-26 11:49 +0000
« prev ^ index » next coverage.py v7.10.0, created at 2025-07-26 11:49 +0000
1"""This module contains related classes and functions for serialization."""
3from __future__ import annotations 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
5import dataclasses 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
6from functools import partial, partialmethod 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
7from typing import TYPE_CHECKING, Annotated, Any, Callable, Literal, TypeVar, overload 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
9from pydantic_core import PydanticUndefined, core_schema 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
10from pydantic_core.core_schema import SerializationInfo, SerializerFunctionWrapHandler, WhenUsed 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
11from typing_extensions import TypeAlias 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
13from . import PydanticUndefinedAnnotation 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
14from ._internal import _decorators, _internal_dataclass 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
15from .annotated_handlers import GetCoreSchemaHandler 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
18@dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True) 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
19class PlainSerializer: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
20 """Plain serializers use a function to modify the output of serialization.
22 This is particularly helpful when you want to customize the serialization for annotated types.
23 Consider an input of `list`, which will be serialized into a space-delimited string.
25 ```python
26 from typing import Annotated
28 from pydantic import BaseModel, PlainSerializer
30 CustomStr = Annotated[
31 list, PlainSerializer(lambda x: ' '.join(x), return_type=str)
32 ]
34 class StudentModel(BaseModel):
35 courses: CustomStr
37 student = StudentModel(courses=['Math', 'Chemistry', 'English'])
38 print(student.model_dump())
39 #> {'courses': 'Math Chemistry English'}
40 ```
42 Attributes:
43 func: The serializer function.
44 return_type: The return type for the function. If omitted it will be inferred from the type annotation.
45 when_used: Determines when this serializer should be used. Accepts a string with values `'always'`,
46 `'unless-none'`, `'json'`, and `'json-unless-none'`. Defaults to 'always'.
47 """
49 func: core_schema.SerializerFunction 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
50 return_type: Any = PydanticUndefined 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
51 when_used: WhenUsed = 'always' 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
53 def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
54 """Gets the Pydantic core schema.
56 Args:
57 source_type: The source type.
58 handler: The `GetCoreSchemaHandler` instance.
60 Returns:
61 The Pydantic core schema.
62 """
63 schema = handler(source_type) 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
64 if self.return_type is not PydanticUndefined: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
65 return_type = self.return_type 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
66 else:
67 try: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
68 # Do not pass in globals as the function could be defined in a different module.
69 # Instead, let `get_callable_return_type` infer the globals to use, but still pass
70 # in locals that may contain a parent/rebuild namespace:
71 return_type = _decorators.get_callable_return_type( 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
72 self.func,
73 localns=handler._get_types_namespace().locals,
74 )
75 except NameError as e: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
76 raise PydanticUndefinedAnnotation.from_name_error(e) from e 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
78 return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type) 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
79 schema['serialization'] = core_schema.plain_serializer_function_ser_schema( 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
80 function=self.func,
81 info_arg=_decorators.inspect_annotated_serializer(self.func, 'plain'),
82 return_schema=return_schema,
83 when_used=self.when_used,
84 )
85 return schema 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
88@dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True) 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
89class WrapSerializer: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
90 """Wrap serializers receive the raw inputs along with a handler function that applies the standard serialization
91 logic, and can modify the resulting value before returning it as the final output of serialization.
93 For example, here's a scenario in which a wrap serializer transforms timezones to UTC **and** utilizes the existing `datetime` serialization logic.
95 ```python
96 from datetime import datetime, timezone
97 from typing import Annotated, Any
99 from pydantic import BaseModel, WrapSerializer
101 class EventDatetime(BaseModel):
102 start: datetime
103 end: datetime
105 def convert_to_utc(value: Any, handler, info) -> dict[str, datetime]:
106 # Note that `handler` can actually help serialize the `value` for
107 # further custom serialization in case it's a subclass.
108 partial_result = handler(value, info)
109 if info.mode == 'json':
110 return {
111 k: datetime.fromisoformat(v).astimezone(timezone.utc)
112 for k, v in partial_result.items()
113 }
114 return {k: v.astimezone(timezone.utc) for k, v in partial_result.items()}
116 UTCEventDatetime = Annotated[EventDatetime, WrapSerializer(convert_to_utc)]
118 class EventModel(BaseModel):
119 event_datetime: UTCEventDatetime
121 dt = EventDatetime(
122 start='2024-01-01T07:00:00-08:00', end='2024-01-03T20:00:00+06:00'
123 )
124 event = EventModel(event_datetime=dt)
125 print(event.model_dump())
126 '''
127 {
128 'event_datetime': {
129 'start': datetime.datetime(
130 2024, 1, 1, 15, 0, tzinfo=datetime.timezone.utc
131 ),
132 'end': datetime.datetime(
133 2024, 1, 3, 14, 0, tzinfo=datetime.timezone.utc
134 ),
135 }
136 }
137 '''
139 print(event.model_dump_json())
140 '''
141 {"event_datetime":{"start":"2024-01-01T15:00:00Z","end":"2024-01-03T14:00:00Z"}}
142 '''
143 ```
145 Attributes:
146 func: The serializer function to be wrapped.
147 return_type: The return type for the function. If omitted it will be inferred from the type annotation.
148 when_used: Determines when this serializer should be used. Accepts a string with values `'always'`,
149 `'unless-none'`, `'json'`, and `'json-unless-none'`. Defaults to 'always'.
150 """
152 func: core_schema.WrapSerializerFunction 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
153 return_type: Any = PydanticUndefined 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
154 when_used: WhenUsed = 'always' 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
156 def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
157 """This method is used to get the Pydantic core schema of the class.
159 Args:
160 source_type: Source type.
161 handler: Core schema handler.
163 Returns:
164 The generated core schema of the class.
165 """
166 schema = handler(source_type) 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
167 if self.return_type is not PydanticUndefined: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
168 return_type = self.return_type 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
169 else:
170 try: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
171 # Do not pass in globals as the function could be defined in a different module.
172 # Instead, let `get_callable_return_type` infer the globals to use, but still pass
173 # in locals that may contain a parent/rebuild namespace:
174 return_type = _decorators.get_callable_return_type( 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
175 self.func,
176 localns=handler._get_types_namespace().locals,
177 )
178 except NameError as e: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
179 raise PydanticUndefinedAnnotation.from_name_error(e) from e 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
181 return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type) 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
182 schema['serialization'] = core_schema.wrap_serializer_function_ser_schema( 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
183 function=self.func,
184 info_arg=_decorators.inspect_annotated_serializer(self.func, 'wrap'),
185 return_schema=return_schema,
186 when_used=self.when_used,
187 )
188 return schema 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
191if TYPE_CHECKING: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
192 _Partial: TypeAlias = 'partial[Any] | partialmethod[Any]'
194 FieldPlainSerializer: TypeAlias = 'core_schema.SerializerFunction | _Partial'
195 """A field serializer method or function in `plain` mode."""
197 FieldWrapSerializer: TypeAlias = 'core_schema.WrapSerializerFunction | _Partial'
198 """A field serializer method or function in `wrap` mode."""
200 FieldSerializer: TypeAlias = 'FieldPlainSerializer | FieldWrapSerializer'
201 """A field serializer method or function."""
203 _FieldPlainSerializerT = TypeVar('_FieldPlainSerializerT', bound=FieldPlainSerializer)
204 _FieldWrapSerializerT = TypeVar('_FieldWrapSerializerT', bound=FieldWrapSerializer)
207@overload 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
208def field_serializer( 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
209 field: str, 1abcdmnefghijkl
210 /,
211 *fields: str, 1abcdmnefghijkl
212 mode: Literal['wrap'], 1abcdmnefghijkl
213 return_type: Any = ..., 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
214 when_used: WhenUsed = ..., 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
215 check_fields: bool | None = ..., 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
216) -> Callable[[_FieldWrapSerializerT], _FieldWrapSerializerT]: ... 1abcdmnefghijkl
219@overload 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
220def field_serializer( 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
221 field: str, 1abcdmnefghijkl
222 /,
223 *fields: str, 1abcdmnefghijkl
224 mode: Literal['plain'] = ..., 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
225 return_type: Any = ..., 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
226 when_used: WhenUsed = ..., 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
227 check_fields: bool | None = ..., 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
228) -> Callable[[_FieldPlainSerializerT], _FieldPlainSerializerT]: ... 1abcdmnefghijkl
231def field_serializer( 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
232 *fields: str,
233 mode: Literal['plain', 'wrap'] = 'plain',
234 # TODO PEP 747 (grep for 'return_type' on the whole code base):
235 return_type: Any = PydanticUndefined,
236 when_used: WhenUsed = 'always',
237 check_fields: bool | None = None,
238) -> (
239 Callable[[_FieldWrapSerializerT], _FieldWrapSerializerT]
240 | Callable[[_FieldPlainSerializerT], _FieldPlainSerializerT]
241):
242 """Decorator that enables custom field serialization.
244 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.
246 ```python
247 from typing import Set
249 from pydantic import BaseModel, field_serializer
251 class StudentModel(BaseModel):
252 name: str = 'Jane'
253 courses: Set[str]
255 @field_serializer('courses', when_used='json')
256 def serialize_courses_in_order(self, courses: Set[str]):
257 return sorted(courses)
259 student = StudentModel(courses={'Math', 'Chemistry', 'English'})
260 print(student.model_dump_json())
261 #> {"name":"Jane","courses":["Chemistry","English","Math"]}
262 ```
264 See [the usage documentation](../concepts/serialization.md#serializers) for more information.
266 Four signatures are supported:
268 - `(self, value: Any, info: FieldSerializationInfo)`
269 - `(self, value: Any, nxt: SerializerFunctionWrapHandler, info: FieldSerializationInfo)`
270 - `(value: Any, info: SerializationInfo)`
271 - `(value: Any, nxt: SerializerFunctionWrapHandler, info: SerializationInfo)`
273 Args:
274 fields: Which field(s) the method should be called on.
275 mode: The serialization mode.
277 - `plain` means the function will be called instead of the default serialization logic,
278 - `wrap` means the function will be called with an argument to optionally call the
279 default serialization logic.
280 return_type: Optional return type for the function, if omitted it will be inferred from the type annotation.
281 when_used: Determines the serializer will be used for serialization.
282 check_fields: Whether to check that the fields actually exist on the model.
284 Returns:
285 The decorator function.
286 """
288 def dec(f: FieldSerializer) -> _decorators.PydanticDescriptorProxy[Any]: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
289 dec_info = _decorators.FieldSerializerDecoratorInfo( 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
290 fields=fields,
291 mode=mode,
292 return_type=return_type,
293 when_used=when_used,
294 check_fields=check_fields,
295 )
296 return _decorators.PydanticDescriptorProxy(f, dec_info) # pyright: ignore[reportArgumentType] 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
298 return dec # pyright: ignore[reportReturnType] 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
301if TYPE_CHECKING: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
302 # The first argument in the following callables represent the `self` type:
304 ModelPlainSerializerWithInfo: TypeAlias = Callable[[Any, SerializationInfo[Any]], Any]
305 """A model serializer method with the `info` argument, in `plain` mode."""
307 ModelPlainSerializerWithoutInfo: TypeAlias = Callable[[Any], Any]
308 """A model serializer method without the `info` argument, in `plain` mode."""
310 ModelPlainSerializer: TypeAlias = 'ModelPlainSerializerWithInfo | ModelPlainSerializerWithoutInfo'
311 """A model serializer method in `plain` mode."""
313 ModelWrapSerializerWithInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler, SerializationInfo[Any]], Any]
314 """A model serializer method with the `info` argument, in `wrap` mode."""
316 ModelWrapSerializerWithoutInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler], Any]
317 """A model serializer method without the `info` argument, in `wrap` mode."""
319 ModelWrapSerializer: TypeAlias = 'ModelWrapSerializerWithInfo | ModelWrapSerializerWithoutInfo'
320 """A model serializer method in `wrap` mode."""
322 ModelSerializer: TypeAlias = 'ModelPlainSerializer | ModelWrapSerializer'
324 _ModelPlainSerializerT = TypeVar('_ModelPlainSerializerT', bound=ModelPlainSerializer)
325 _ModelWrapSerializerT = TypeVar('_ModelWrapSerializerT', bound=ModelWrapSerializer)
328@overload 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
329def model_serializer(f: _ModelPlainSerializerT, /) -> _ModelPlainSerializerT: ... 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
332@overload 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
333def model_serializer( 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
334 *, mode: Literal['wrap'], when_used: WhenUsed = 'always', return_type: Any = ... 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
335) -> Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT]: ... 1abcdmnefghijkl
338@overload 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
339def model_serializer( 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
340 *,
341 mode: Literal['plain'] = ..., 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
342 when_used: WhenUsed = 'always', 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
343 return_type: Any = ..., 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
344) -> Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT]: ... 1abcdmnefghijkl
347def model_serializer( 1opqrstabcduvwxyzABCefghDEFGHIJKLijklMNO
348 f: _ModelPlainSerializerT | _ModelWrapSerializerT | None = None,
349 /,
350 *,
351 mode: Literal['plain', 'wrap'] = 'plain',
352 when_used: WhenUsed = 'always',
353 return_type: Any = PydanticUndefined,
354) -> (
355 _ModelPlainSerializerT
356 | Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT]
357 | Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT]
358):
359 """Decorator that enables custom model serialization.
361 This is useful when a model need to be serialized in a customized manner, allowing for flexibility beyond just specific fields.
363 An example would be to serialize temperature to the same temperature scale, such as degrees Celsius.
365 ```python
366 from typing import Literal
368 from pydantic import BaseModel, model_serializer
370 class TemperatureModel(BaseModel):
371 unit: Literal['C', 'F']
372 value: int
374 @model_serializer()
375 def serialize_model(self):
376 if self.unit == 'F':
377 return {'unit': 'C', 'value': int((self.value - 32) / 1.8)}
378 return {'unit': self.unit, 'value': self.value}
380 temperature = TemperatureModel(unit='F', value=212)
381 print(temperature.model_dump())
382 #> {'unit': 'C', 'value': 100}
383 ```
385 Two signatures are supported for `mode='plain'`, which is the default:
387 - `(self)`
388 - `(self, info: SerializationInfo)`
390 And two other signatures for `mode='wrap'`:
392 - `(self, nxt: SerializerFunctionWrapHandler)`
393 - `(self, nxt: SerializerFunctionWrapHandler, info: SerializationInfo)`
395 See [the usage documentation](../concepts/serialization.md#serializers) for more information.
397 Args:
398 f: The function to be decorated.
399 mode: The serialization mode.
401 - `'plain'` means the function will be called instead of the default serialization logic
402 - `'wrap'` means the function will be called with an argument to optionally call the default
403 serialization logic.
404 when_used: Determines when this serializer should be used.
405 return_type: The return type for the function. If omitted it will be inferred from the type annotation.
407 Returns:
408 The decorator function.
409 """
411 def dec(f: ModelSerializer) -> _decorators.PydanticDescriptorProxy[Any]: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
412 dec_info = _decorators.ModelSerializerDecoratorInfo(mode=mode, return_type=return_type, when_used=when_used) 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
413 return _decorators.PydanticDescriptorProxy(f, dec_info) 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
415 if f is None: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
416 return dec # pyright: ignore[reportReturnType] 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
417 else:
418 return dec(f) # pyright: ignore[reportReturnType] 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
421AnyType = TypeVar('AnyType') 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
424if TYPE_CHECKING: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
425 SerializeAsAny = Annotated[AnyType, ...] # SerializeAsAny[list[str]] will be treated by type checkers as list[str]
426 """Annotation used to mark a type as having duck-typing serialization behavior.
428 See [usage documentation](../concepts/serialization.md#serializing-with-duck-typing) for more details.
429 """
430else:
432 @dataclasses.dataclass(**_internal_dataclass.slots_true) 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
433 class SerializeAsAny: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
434 """Annotation used to mark a type as having duck-typing serialization behavior.
436 See [usage documentation](../concepts/serialization.md#serializing-with-duck-typing) for more details.
437 """
439 def __class_getitem__(cls, item: Any) -> Any: 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
440 return Annotated[item, SerializeAsAny()] 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
442 def __get_pydantic_core_schema__( 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
443 self, source_type: Any, handler: GetCoreSchemaHandler
444 ) -> core_schema.CoreSchema:
445 schema = handler(source_type) 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
446 schema_to_update = schema 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
447 while schema_to_update['type'] == 'definitions': 447 ↛ 448line 447 didn't jump to line 448 because the condition on line 447 was never true1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
448 schema_to_update = schema_to_update.copy()
449 schema_to_update = schema_to_update['schema']
450 schema_to_update['serialization'] = core_schema.wrap_serializer_function_ser_schema( 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
451 lambda x, h: h(x), schema=core_schema.any_schema()
452 )
453 return schema 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO
455 __hash__ = object.__hash__ 1opqrstabcduvwmnxyzABCefghDEFGHIJKLijklMNO