Coverage for pydantic/functional_serializers.py: 96.05%
68 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-11 13:08 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-11 13:08 +0000
1"""This module contains related classes and functions for serialization."""
3from __future__ import annotations 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
5import dataclasses 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
6from functools import partial, partialmethod 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
7from typing import TYPE_CHECKING, Annotated, Any, Callable, Literal, TypeVar, overload 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
9from pydantic_core import PydanticUndefined, core_schema 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
10from pydantic_core.core_schema import SerializationInfo, SerializerFunctionWrapHandler, WhenUsed 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
11from typing_extensions import TypeAlias 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
13from . import PydanticUndefinedAnnotation 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
14from ._internal import _decorators, _internal_dataclass 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
15from .annotated_handlers import GetCoreSchemaHandler 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
18@dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True) 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
19class PlainSerializer: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
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 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
50 return_type: Any = PydanticUndefined 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
51 when_used: WhenUsed = 'always' 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
53 def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
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) 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
64 if self.return_type is not PydanticUndefined: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
65 return_type = self.return_type 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
66 else:
67 try: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
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( 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
72 self.func,
73 localns=handler._get_types_namespace().locals,
74 )
75 except NameError as e: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
76 raise PydanticUndefinedAnnotation.from_name_error(e) from e 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
78 return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type) 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
79 schema['serialization'] = core_schema.plain_serializer_function_ser_schema( 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
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 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
88@dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True) 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
89class WrapSerializer: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
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 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
153 return_type: Any = PydanticUndefined 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
154 when_used: WhenUsed = 'always' 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
156 def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
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) 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
167 if self.return_type is not PydanticUndefined: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
168 return_type = self.return_type 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
169 else:
170 try: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
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( 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
175 self.func,
176 localns=handler._get_types_namespace().locals,
177 )
178 except NameError as e: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
179 raise PydanticUndefinedAnnotation.from_name_error(e) from e 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
181 return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type) 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
182 schema['serialization'] = core_schema.wrap_serializer_function_ser_schema( 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
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 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
191if TYPE_CHECKING: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
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 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
208def field_serializer( 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
209 field: str, 1abcdepqfghijklmno
210 /,
211 *fields: str, 1abcdepqfghijklmno
212 mode: Literal['wrap'], 1abcdepqfghijklmno
213 return_type: Any = ..., 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
214 when_used: WhenUsed = ..., 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
215 check_fields: bool | None = ..., 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
216) -> Callable[[_FieldWrapSerializerT], _FieldWrapSerializerT]: ... 1abcdepqfghijklmno
219@overload 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
220def field_serializer( 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
221 field: str, 1abcdepqfghijklmno
222 /,
223 *fields: str, 1abcdepqfghijklmno
224 mode: Literal['plain'] = ..., 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
225 return_type: Any = ..., 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
226 when_used: WhenUsed = ..., 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
227 check_fields: bool | None = ..., 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
228) -> Callable[[_FieldPlainSerializerT], _FieldPlainSerializerT]: ... 1abcdepqfghijklmno
231def field_serializer( 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
232 *fields: str,
233 mode: Literal['plain', 'wrap'] = 'plain',
234 return_type: Any = PydanticUndefined,
235 when_used: WhenUsed = 'always',
236 check_fields: bool | None = None,
237) -> (
238 Callable[[_FieldWrapSerializerT], _FieldWrapSerializerT]
239 | Callable[[_FieldPlainSerializerT], _FieldPlainSerializerT]
240):
241 """Decorator that enables custom field serialization.
243 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.
245 ```python
246 from typing import Set
248 from pydantic import BaseModel, field_serializer
250 class StudentModel(BaseModel):
251 name: str = 'Jane'
252 courses: Set[str]
254 @field_serializer('courses', when_used='json')
255 def serialize_courses_in_order(self, courses: Set[str]):
256 return sorted(courses)
258 student = StudentModel(courses={'Math', 'Chemistry', 'English'})
259 print(student.model_dump_json())
260 #> {"name":"Jane","courses":["Chemistry","English","Math"]}
261 ```
263 See [Custom serializers](../concepts/serialization.md#custom-serializers) for more information.
265 Four signatures are supported:
267 - `(self, value: Any, info: FieldSerializationInfo)`
268 - `(self, value: Any, nxt: SerializerFunctionWrapHandler, info: FieldSerializationInfo)`
269 - `(value: Any, info: SerializationInfo)`
270 - `(value: Any, nxt: SerializerFunctionWrapHandler, info: SerializationInfo)`
272 Args:
273 fields: Which field(s) the method should be called on.
274 mode: The serialization mode.
276 - `plain` means the function will be called instead of the default serialization logic,
277 - `wrap` means the function will be called with an argument to optionally call the
278 default serialization logic.
279 return_type: Optional return type for the function, if omitted it will be inferred from the type annotation.
280 when_used: Determines the serializer will be used for serialization.
281 check_fields: Whether to check that the fields actually exist on the model.
283 Returns:
284 The decorator function.
285 """
287 def dec(f: FieldSerializer) -> _decorators.PydanticDescriptorProxy[Any]: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
288 dec_info = _decorators.FieldSerializerDecoratorInfo( 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
289 fields=fields,
290 mode=mode,
291 return_type=return_type,
292 when_used=when_used,
293 check_fields=check_fields,
294 )
295 return _decorators.PydanticDescriptorProxy(f, dec_info) # pyright: ignore[reportArgumentType] 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
297 return dec # pyright: ignore[reportReturnType] 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
300if TYPE_CHECKING: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
301 # The first argument in the following callables represent the `self` type:
303 ModelPlainSerializerWithInfo: TypeAlias = Callable[[Any, SerializationInfo[Any]], Any]
304 """A model serializer method with the `info` argument, in `plain` mode."""
306 ModelPlainSerializerWithoutInfo: TypeAlias = Callable[[Any], Any]
307 """A model serializer method without the `info` argument, in `plain` mode."""
309 ModelPlainSerializer: TypeAlias = 'ModelPlainSerializerWithInfo | ModelPlainSerializerWithoutInfo'
310 """A model serializer method in `plain` mode."""
312 ModelWrapSerializerWithInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler, SerializationInfo[Any]], Any]
313 """A model serializer method with the `info` argument, in `wrap` mode."""
315 ModelWrapSerializerWithoutInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler], Any]
316 """A model serializer method without the `info` argument, in `wrap` mode."""
318 ModelWrapSerializer: TypeAlias = 'ModelWrapSerializerWithInfo | ModelWrapSerializerWithoutInfo'
319 """A model serializer method in `wrap` mode."""
321 ModelSerializer: TypeAlias = 'ModelPlainSerializer | ModelWrapSerializer'
323 _ModelPlainSerializerT = TypeVar('_ModelPlainSerializerT', bound=ModelPlainSerializer)
324 _ModelWrapSerializerT = TypeVar('_ModelWrapSerializerT', bound=ModelWrapSerializer)
327@overload 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
328def model_serializer(f: _ModelPlainSerializerT, /) -> _ModelPlainSerializerT: ... 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
331@overload 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
332def model_serializer( 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
333 *, mode: Literal['wrap'], when_used: WhenUsed = 'always', return_type: Any = ... 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
334) -> Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT]: ... 1abcdepqfghijklmno
337@overload 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
338def model_serializer( 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
339 *,
340 mode: Literal['plain'] = ..., 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
341 when_used: WhenUsed = 'always', 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
342 return_type: Any = ..., 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
343) -> Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT]: ... 1abcdepqfghijklmno
346def model_serializer( 1rstuvwabcdexyzABCfghijDEFGHIklmno
347 f: _ModelPlainSerializerT | _ModelWrapSerializerT | None = None,
348 /,
349 *,
350 mode: Literal['plain', 'wrap'] = 'plain',
351 when_used: WhenUsed = 'always',
352 return_type: Any = PydanticUndefined,
353) -> (
354 _ModelPlainSerializerT
355 | Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT]
356 | Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT]
357):
358 """Decorator that enables custom model serialization.
360 This is useful when a model need to be serialized in a customized manner, allowing for flexibility beyond just specific fields.
362 An example would be to serialize temperature to the same temperature scale, such as degrees Celsius.
364 ```python
365 from typing import Literal
367 from pydantic import BaseModel, model_serializer
369 class TemperatureModel(BaseModel):
370 unit: Literal['C', 'F']
371 value: int
373 @model_serializer()
374 def serialize_model(self):
375 if self.unit == 'F':
376 return {'unit': 'C', 'value': int((self.value - 32) / 1.8)}
377 return {'unit': self.unit, 'value': self.value}
379 temperature = TemperatureModel(unit='F', value=212)
380 print(temperature.model_dump())
381 #> {'unit': 'C', 'value': 100}
382 ```
384 Two signatures are supported for `mode='plain'`, which is the default:
386 - `(self)`
387 - `(self, info: SerializationInfo)`
389 And two other signatures for `mode='wrap'`:
391 - `(self, nxt: SerializerFunctionWrapHandler)`
392 - `(self, nxt: SerializerFunctionWrapHandler, info: SerializationInfo)`
394 See [Custom serializers](../concepts/serialization.md#custom-serializers) for more information.
396 Args:
397 f: The function to be decorated.
398 mode: The serialization mode.
400 - `'plain'` means the function will be called instead of the default serialization logic
401 - `'wrap'` means the function will be called with an argument to optionally call the default
402 serialization logic.
403 when_used: Determines when this serializer should be used.
404 return_type: The return type for the function. If omitted it will be inferred from the type annotation.
406 Returns:
407 The decorator function.
408 """
410 def dec(f: ModelSerializer) -> _decorators.PydanticDescriptorProxy[Any]: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
411 dec_info = _decorators.ModelSerializerDecoratorInfo(mode=mode, return_type=return_type, when_used=when_used) 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
412 return _decorators.PydanticDescriptorProxy(f, dec_info) 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
414 if f is None: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
415 return dec # pyright: ignore[reportReturnType] 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
416 else:
417 return dec(f) # pyright: ignore[reportReturnType] 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
420AnyType = TypeVar('AnyType') 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
423if TYPE_CHECKING: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
424 SerializeAsAny = Annotated[AnyType, ...] # SerializeAsAny[list[str]] will be treated by type checkers as list[str]
425 """Force serialization to ignore whatever is defined in the schema and instead ask the object
426 itself how it should be serialized.
427 In particular, this means that when model subclasses are serialized, fields present in the subclass
428 but not in the original schema will be included.
429 """
430else:
432 @dataclasses.dataclass(**_internal_dataclass.slots_true) 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
433 class SerializeAsAny: # noqa: D101 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
434 def __class_getitem__(cls, item: Any) -> Any: 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
435 return Annotated[item, SerializeAsAny()] 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
437 def __get_pydantic_core_schema__( 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
438 self, source_type: Any, handler: GetCoreSchemaHandler
439 ) -> core_schema.CoreSchema:
440 schema = handler(source_type) 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
441 schema_to_update = schema 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
442 while schema_to_update['type'] == 'definitions': 442 ↛ 443line 442 didn't jump to line 443 because the condition on line 442 was never true1rstuvwabcdepqxyzABCfghijDEFGHIklmno
443 schema_to_update = schema_to_update.copy()
444 schema_to_update = schema_to_update['schema']
445 schema_to_update['serialization'] = core_schema.wrap_serializer_function_ser_schema( 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
446 lambda x, h: h(x), schema=core_schema.any_schema()
447 )
448 return schema 1rstuvwabcdepqxyzABCfghijDEFGHIklmno
450 __hash__ = object.__hash__ 1rstuvwabcdepqxyzABCfghijDEFGHIklmno