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