1"""This module contains related classes and functions for serialization."""
2
3from __future__ import annotations
4
5import dataclasses
6from functools import partial, partialmethod
7from typing import TYPE_CHECKING, Annotated, Any, Callable, Literal, TypeVar, overload
8
9from pydantic_core import PydanticUndefined, core_schema
10from pydantic_core.core_schema import SerializationInfo, SerializerFunctionWrapHandler, WhenUsed
11from typing_extensions import TypeAlias
12
13from . import PydanticUndefinedAnnotation
14from ._internal import _decorators, _internal_dataclass
15from .annotated_handlers import GetCoreSchemaHandler
16
17
18@dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True)
19class PlainSerializer:
20 """Plain serializers use a function to modify the output of serialization.
21
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.
24
25 ```python
26 from typing import Annotated
27
28 from pydantic import BaseModel, PlainSerializer
29
30 CustomStr = Annotated[
31 list, PlainSerializer(lambda x: ' '.join(x), return_type=str)
32 ]
33
34 class StudentModel(BaseModel):
35 courses: CustomStr
36
37 student = StudentModel(courses=['Math', 'Chemistry', 'English'])
38 print(student.model_dump())
39 #> {'courses': 'Math Chemistry English'}
40 ```
41
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 """
48
49 func: core_schema.SerializerFunction
50 return_type: Any = PydanticUndefined
51 when_used: WhenUsed = 'always'
52
53 def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
54 """Gets the Pydantic core schema.
55
56 Args:
57 source_type: The source type.
58 handler: The `GetCoreSchemaHandler` instance.
59
60 Returns:
61 The Pydantic core schema.
62 """
63 schema = handler(source_type)
64 if self.return_type is not PydanticUndefined:
65 return_type = self.return_type
66 else:
67 try:
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(
72 self.func,
73 localns=handler._get_types_namespace().locals,
74 )
75 except NameError as e:
76 raise PydanticUndefinedAnnotation.from_name_error(e) from e
77
78 return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type)
79 schema['serialization'] = core_schema.plain_serializer_function_ser_schema(
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
86
87
88@dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True)
89class WrapSerializer:
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.
92
93 For example, here's a scenario in which a wrap serializer transforms timezones to UTC **and** utilizes the existing `datetime` serialization logic.
94
95 ```python
96 from datetime import datetime, timezone
97 from typing import Annotated, Any
98
99 from pydantic import BaseModel, WrapSerializer
100
101 class EventDatetime(BaseModel):
102 start: datetime
103 end: datetime
104
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()}
115
116 UTCEventDatetime = Annotated[EventDatetime, WrapSerializer(convert_to_utc)]
117
118 class EventModel(BaseModel):
119 event_datetime: UTCEventDatetime
120
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 '''
138
139 print(event.model_dump_json())
140 '''
141 {"event_datetime":{"start":"2024-01-01T15:00:00Z","end":"2024-01-03T14:00:00Z"}}
142 '''
143 ```
144
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 """
151
152 func: core_schema.WrapSerializerFunction
153 return_type: Any = PydanticUndefined
154 when_used: WhenUsed = 'always'
155
156 def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
157 """This method is used to get the Pydantic core schema of the class.
158
159 Args:
160 source_type: Source type.
161 handler: Core schema handler.
162
163 Returns:
164 The generated core schema of the class.
165 """
166 schema = handler(source_type)
167 if self.return_type is not PydanticUndefined:
168 return_type = self.return_type
169 else:
170 try:
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(
175 self.func,
176 localns=handler._get_types_namespace().locals,
177 )
178 except NameError as e:
179 raise PydanticUndefinedAnnotation.from_name_error(e) from e
180
181 return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type)
182 schema['serialization'] = core_schema.wrap_serializer_function_ser_schema(
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
189
190
191if TYPE_CHECKING:
192 _Partial: TypeAlias = 'partial[Any] | partialmethod[Any]'
193
194 FieldPlainSerializer: TypeAlias = 'core_schema.SerializerFunction | _Partial'
195 """A field serializer method or function in `plain` mode."""
196
197 FieldWrapSerializer: TypeAlias = 'core_schema.WrapSerializerFunction | _Partial'
198 """A field serializer method or function in `wrap` mode."""
199
200 FieldSerializer: TypeAlias = 'FieldPlainSerializer | FieldWrapSerializer'
201 """A field serializer method or function."""
202
203 _FieldPlainSerializerT = TypeVar('_FieldPlainSerializerT', bound=FieldPlainSerializer)
204 _FieldWrapSerializerT = TypeVar('_FieldWrapSerializerT', bound=FieldWrapSerializer)
205
206
207@overload
208def field_serializer(
209 field: str,
210 /,
211 *fields: str,
212 mode: Literal['wrap'],
213 return_type: Any = ...,
214 when_used: WhenUsed = ...,
215 check_fields: bool | None = ...,
216) -> Callable[[_FieldWrapSerializerT], _FieldWrapSerializerT]: ...
217
218
219@overload
220def field_serializer(
221 field: str,
222 /,
223 *fields: str,
224 mode: Literal['plain'] = ...,
225 return_type: Any = ...,
226 when_used: WhenUsed = ...,
227 check_fields: bool | None = ...,
228) -> Callable[[_FieldPlainSerializerT], _FieldPlainSerializerT]: ...
229
230
231def field_serializer(
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.
243
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.
245
246 ```python
247 from pydantic import BaseModel, field_serializer
248
249 class StudentModel(BaseModel):
250 name: str = 'Jane'
251 courses: set[str]
252
253 @field_serializer('courses', when_used='json')
254 def serialize_courses_in_order(self, courses: set[str]):
255 return sorted(courses)
256
257 student = StudentModel(courses={'Math', 'Chemistry', 'English'})
258 print(student.model_dump_json())
259 #> {"name":"Jane","courses":["Chemistry","English","Math"]}
260 ```
261
262 See [the usage documentation](../concepts/serialization.md#serializers) for more information.
263
264 Four signatures are supported:
265
266 - `(self, value: Any, info: FieldSerializationInfo)`
267 - `(self, value: Any, nxt: SerializerFunctionWrapHandler, info: FieldSerializationInfo)`
268 - `(value: Any, info: SerializationInfo)`
269 - `(value: Any, nxt: SerializerFunctionWrapHandler, info: SerializationInfo)`
270
271 Args:
272 fields: Which field(s) the method should be called on.
273 mode: The serialization mode.
274
275 - `plain` means the function will be called instead of the default serialization logic,
276 - `wrap` means the function will be called with an argument to optionally call the
277 default serialization logic.
278 return_type: Optional return type for the function, if omitted it will be inferred from the type annotation.
279 when_used: Determines the serializer will be used for serialization.
280 check_fields: Whether to check that the fields actually exist on the model.
281
282 Returns:
283 The decorator function.
284 """
285
286 def dec(f: FieldSerializer) -> _decorators.PydanticDescriptorProxy[Any]:
287 dec_info = _decorators.FieldSerializerDecoratorInfo(
288 fields=fields,
289 mode=mode,
290 return_type=return_type,
291 when_used=when_used,
292 check_fields=check_fields,
293 )
294 return _decorators.PydanticDescriptorProxy(f, dec_info) # pyright: ignore[reportArgumentType]
295
296 return dec # pyright: ignore[reportReturnType]
297
298
299if TYPE_CHECKING:
300 # The first argument in the following callables represent the `self` type:
301
302 ModelPlainSerializerWithInfo: TypeAlias = Callable[[Any, SerializationInfo[Any]], Any]
303 """A model serializer method with the `info` argument, in `plain` mode."""
304
305 ModelPlainSerializerWithoutInfo: TypeAlias = Callable[[Any], Any]
306 """A model serializer method without the `info` argument, in `plain` mode."""
307
308 ModelPlainSerializer: TypeAlias = 'ModelPlainSerializerWithInfo | ModelPlainSerializerWithoutInfo'
309 """A model serializer method in `plain` mode."""
310
311 ModelWrapSerializerWithInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler, SerializationInfo[Any]], Any]
312 """A model serializer method with the `info` argument, in `wrap` mode."""
313
314 ModelWrapSerializerWithoutInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler], Any]
315 """A model serializer method without the `info` argument, in `wrap` mode."""
316
317 ModelWrapSerializer: TypeAlias = 'ModelWrapSerializerWithInfo | ModelWrapSerializerWithoutInfo'
318 """A model serializer method in `wrap` mode."""
319
320 ModelSerializer: TypeAlias = 'ModelPlainSerializer | ModelWrapSerializer'
321
322 _ModelPlainSerializerT = TypeVar('_ModelPlainSerializerT', bound=ModelPlainSerializer)
323 _ModelWrapSerializerT = TypeVar('_ModelWrapSerializerT', bound=ModelWrapSerializer)
324
325
326@overload
327def model_serializer(f: _ModelPlainSerializerT, /) -> _ModelPlainSerializerT: ...
328
329
330@overload
331def model_serializer(
332 *, mode: Literal['wrap'], when_used: WhenUsed = 'always', return_type: Any = ...
333) -> Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT]: ...
334
335
336@overload
337def model_serializer(
338 *,
339 mode: Literal['plain'] = ...,
340 when_used: WhenUsed = 'always',
341 return_type: Any = ...,
342) -> Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT]: ...
343
344
345def model_serializer(
346 f: _ModelPlainSerializerT | _ModelWrapSerializerT | None = None,
347 /,
348 *,
349 mode: Literal['plain', 'wrap'] = 'plain',
350 when_used: WhenUsed = 'always',
351 return_type: Any = PydanticUndefined,
352) -> (
353 _ModelPlainSerializerT
354 | Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT]
355 | Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT]
356):
357 """Decorator that enables custom model serialization.
358
359 This is useful when a model need to be serialized in a customized manner, allowing for flexibility beyond just specific fields.
360
361 An example would be to serialize temperature to the same temperature scale, such as degrees Celsius.
362
363 ```python
364 from typing import Literal
365
366 from pydantic import BaseModel, model_serializer
367
368 class TemperatureModel(BaseModel):
369 unit: Literal['C', 'F']
370 value: int
371
372 @model_serializer()
373 def serialize_model(self):
374 if self.unit == 'F':
375 return {'unit': 'C', 'value': int((self.value - 32) / 1.8)}
376 return {'unit': self.unit, 'value': self.value}
377
378 temperature = TemperatureModel(unit='F', value=212)
379 print(temperature.model_dump())
380 #> {'unit': 'C', 'value': 100}
381 ```
382
383 Two signatures are supported for `mode='plain'`, which is the default:
384
385 - `(self)`
386 - `(self, info: SerializationInfo)`
387
388 And two other signatures for `mode='wrap'`:
389
390 - `(self, nxt: SerializerFunctionWrapHandler)`
391 - `(self, nxt: SerializerFunctionWrapHandler, info: SerializationInfo)`
392
393 See [the usage documentation](../concepts/serialization.md#serializers) for more information.
394
395 Args:
396 f: The function to be decorated.
397 mode: The serialization mode.
398
399 - `'plain'` means the function will be called instead of the default serialization logic
400 - `'wrap'` means the function will be called with an argument to optionally call the default
401 serialization logic.
402 when_used: Determines when this serializer should be used.
403 return_type: The return type for the function. If omitted it will be inferred from the type annotation.
404
405 Returns:
406 The decorator function.
407 """
408
409 def dec(f: ModelSerializer) -> _decorators.PydanticDescriptorProxy[Any]:
410 dec_info = _decorators.ModelSerializerDecoratorInfo(mode=mode, return_type=return_type, when_used=when_used)
411 return _decorators.PydanticDescriptorProxy(f, dec_info)
412
413 if f is None:
414 return dec # pyright: ignore[reportReturnType]
415 else:
416 return dec(f) # pyright: ignore[reportReturnType]
417
418
419AnyType = TypeVar('AnyType')
420
421
422if TYPE_CHECKING:
423 SerializeAsAny = Annotated[AnyType, ...] # SerializeAsAny[list[str]] will be treated by type checkers as list[str]
424 """Annotation used to mark a type as having duck-typing serialization behavior.
425
426 See [usage documentation](../concepts/serialization.md#serializing-with-duck-typing) for more details.
427 """
428else:
429
430 @dataclasses.dataclass(**_internal_dataclass.slots_true)
431 class SerializeAsAny:
432 """Annotation used to mark a type as having duck-typing serialization behavior.
433
434 See [usage documentation](../concepts/serialization.md#serializing-with-duck-typing) for more details.
435 """
436
437 def __class_getitem__(cls, item: Any) -> Any:
438 return Annotated[item, SerializeAsAny()]
439
440 def __get_pydantic_core_schema__(
441 self, source_type: Any, handler: GetCoreSchemaHandler
442 ) -> core_schema.CoreSchema:
443 schema = handler(source_type)
444 schema_to_update = schema
445 while schema_to_update['type'] == 'definitions':
446 schema_to_update = schema_to_update.copy()
447 schema_to_update = schema_to_update['schema']
448 schema_to_update['serialization'] = core_schema.simple_ser_schema('any')
449 return schema
450
451 __hash__ = object.__hash__