Coverage for pydantic/functional_serializers.py: 95.65%

65 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-13 19:35 +0000

1"""This module contains related classes and functions for serialization.""" 

2 

3from __future__ import annotations 1opqrstabcdmnuvwxyzefghABCDEFijkl

4 

5import dataclasses 1opqrstabcdmnuvwxyzefghABCDEFijkl

6from functools import partial, partialmethod 1opqrstabcdmnuvwxyzefghABCDEFijkl

7from typing import TYPE_CHECKING, Annotated, Any, Callable, Literal, TypeVar, overload 1opqrstabcdmnuvwxyzefghABCDEFijkl

8 

9from pydantic_core import PydanticUndefined, core_schema 1opqrstabcdmnuvwxyzefghABCDEFijkl

10from pydantic_core.core_schema import SerializationInfo, SerializerFunctionWrapHandler, WhenUsed 1opqrstabcdmnuvwxyzefghABCDEFijkl

11from typing_extensions import TypeAlias 1opqrstabcdmnuvwxyzefghABCDEFijkl

12 

13from . import PydanticUndefinedAnnotation 1opqrstabcdmnuvwxyzefghABCDEFijkl

14from ._internal import _decorators, _internal_dataclass 1opqrstabcdmnuvwxyzefghABCDEFijkl

15from .annotated_handlers import GetCoreSchemaHandler 1opqrstabcdmnuvwxyzefghABCDEFijkl

16 

17 

18@dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True) 1opqrstabcdmnuvwxyzefghABCDEFijkl

19class PlainSerializer: 1opqrstabcdmnuvwxyzefghABCDEFijkl

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 1opqrstabcdmnuvwxyzefghABCDEFijkl

50 return_type: Any = PydanticUndefined 1opqrstabcdmnuvwxyzefghABCDEFijkl

51 when_used: WhenUsed = 'always' 1opqrstabcdmnuvwxyzefghABCDEFijkl

52 

53 def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: 1opqrstabcdmnuvwxyzefghABCDEFijkl

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) 1opqrstabcdmnuvwxyzefghABCDEFijkl

64 try: 1opqrstabcdmnuvwxyzefghABCDEFijkl

65 # Do not pass in globals as the function could be defined in a different module. 

66 # Instead, let `get_function_return_type` infer the globals to use, but still pass 

67 # in locals that may contain a parent/rebuild namespace: 

68 return_type = _decorators.get_function_return_type( 1opqrstabcdmnuvwxyzefghABCDEFijkl

69 self.func, 

70 self.return_type, 

71 localns=handler._get_types_namespace().locals, 

72 ) 

73 except NameError as e: 1opqrstabcdmnuvwxyzefghABCDEFijkl

74 raise PydanticUndefinedAnnotation.from_name_error(e) from e 1opqrstabcdmnuvwxyzefghABCDEFijkl

75 return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type) 1opqrstabcdmnuvwxyzefghABCDEFijkl

76 schema['serialization'] = core_schema.plain_serializer_function_ser_schema( 1opqrstabcdmnuvwxyzefghABCDEFijkl

77 function=self.func, 

78 info_arg=_decorators.inspect_annotated_serializer(self.func, 'plain'), 

79 return_schema=return_schema, 

80 when_used=self.when_used, 

81 ) 

82 return schema 1opqrstabcdmnuvwxyzefghABCDEFijkl

83 

84 

85@dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True) 1opqrstabcdmnuvwxyzefghABCDEFijkl

86class WrapSerializer: 1opqrstabcdmnuvwxyzefghABCDEFijkl

87 """Wrap serializers receive the raw inputs along with a handler function that applies the standard serialization 

88 logic, and can modify the resulting value before returning it as the final output of serialization. 

89 

90 For example, here's a scenario in which a wrap serializer transforms timezones to UTC **and** utilizes the existing `datetime` serialization logic. 

91 

92 ```python 

93 from datetime import datetime, timezone 

94 from typing import Annotated, Any 

95 

96 from pydantic import BaseModel, WrapSerializer 

97 

98 class EventDatetime(BaseModel): 

99 start: datetime 

100 end: datetime 

101 

102 def convert_to_utc(value: Any, handler, info) -> dict[str, datetime]: 

103 # Note that `handler` can actually help serialize the `value` for 

104 # further custom serialization in case it's a subclass. 

105 partial_result = handler(value, info) 

106 if info.mode == 'json': 

107 return { 

108 k: datetime.fromisoformat(v).astimezone(timezone.utc) 

109 for k, v in partial_result.items() 

110 } 

111 return {k: v.astimezone(timezone.utc) for k, v in partial_result.items()} 

112 

113 UTCEventDatetime = Annotated[EventDatetime, WrapSerializer(convert_to_utc)] 

114 

115 class EventModel(BaseModel): 

116 event_datetime: UTCEventDatetime 

117 

118 dt = EventDatetime( 

119 start='2024-01-01T07:00:00-08:00', end='2024-01-03T20:00:00+06:00' 

120 ) 

121 event = EventModel(event_datetime=dt) 

122 print(event.model_dump()) 

123 ''' 

124 { 

125 'event_datetime': { 

126 'start': datetime.datetime( 

127 2024, 1, 1, 15, 0, tzinfo=datetime.timezone.utc 

128 ), 

129 'end': datetime.datetime( 

130 2024, 1, 3, 14, 0, tzinfo=datetime.timezone.utc 

131 ), 

132 } 

133 } 

134 ''' 

135 

136 print(event.model_dump_json()) 

137 ''' 

138 {"event_datetime":{"start":"2024-01-01T15:00:00Z","end":"2024-01-03T14:00:00Z"}} 

139 ''' 

140 ``` 

141 

142 Attributes: 

143 func: The serializer function to be wrapped. 

144 return_type: The return type for the function. If omitted it will be inferred from the type annotation. 

145 when_used: Determines when this serializer should be used. Accepts a string with values `'always'`, 

146 `'unless-none'`, `'json'`, and `'json-unless-none'`. Defaults to 'always'. 

147 """ 

148 

149 func: core_schema.WrapSerializerFunction 1opqrstabcdmnuvwxyzefghABCDEFijkl

150 return_type: Any = PydanticUndefined 1opqrstabcdmnuvwxyzefghABCDEFijkl

151 when_used: WhenUsed = 'always' 1opqrstabcdmnuvwxyzefghABCDEFijkl

152 

153 def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: 1opqrstabcdmnuvwxyzefghABCDEFijkl

154 """This method is used to get the Pydantic core schema of the class. 

155 

156 Args: 

157 source_type: Source type. 

158 handler: Core schema handler. 

159 

160 Returns: 

161 The generated core schema of the class. 

162 """ 

163 schema = handler(source_type) 1opqrstabcdmnuvwxyzefghABCDEFijkl

164 globalns, localns = handler._get_types_namespace() 1opqrstabcdmnuvwxyzefghABCDEFijkl

165 try: 1opqrstabcdmnuvwxyzefghABCDEFijkl

166 # Do not pass in globals as the function could be defined in a different module. 

167 # Instead, let `get_function_return_type` infer the globals to use, but still pass 

168 # in locals that may contain a parent/rebuild namespace: 

169 return_type = _decorators.get_function_return_type( 1opqrstabcdmnuvwxyzefghABCDEFijkl

170 self.func, 

171 self.return_type, 

172 localns=handler._get_types_namespace().locals, 

173 ) 

174 except NameError as e: 1opqrstabcdmnuvwxyzefghABCDEFijkl

175 raise PydanticUndefinedAnnotation.from_name_error(e) from e 1opqrstabcdmnuvwxyzefghABCDEFijkl

176 return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type) 1opqrstabcdmnuvwxyzefghABCDEFijkl

177 schema['serialization'] = core_schema.wrap_serializer_function_ser_schema( 1opqrstabcdmnuvwxyzefghABCDEFijkl

178 function=self.func, 

179 info_arg=_decorators.inspect_annotated_serializer(self.func, 'wrap'), 

180 return_schema=return_schema, 

181 when_used=self.when_used, 

182 ) 

183 return schema 1opqrstabcdmnuvwxyzefghABCDEFijkl

184 

185 

186if TYPE_CHECKING: 1opqrstabcdmnuvwxyzefghABCDEFijkl

187 _Partial: TypeAlias = 'partial[Any] | partialmethod[Any]' 

188 

189 FieldPlainSerializer: TypeAlias = 'core_schema.SerializerFunction | _Partial' 

190 """A field serializer method or function in `plain` mode.""" 

191 

192 FieldWrapSerializer: TypeAlias = 'core_schema.WrapSerializerFunction | _Partial' 

193 """A field serializer method or function in `wrap` mode.""" 

194 

195 FieldSerializer: TypeAlias = 'FieldPlainSerializer | FieldWrapSerializer' 

196 """A field serializer method or function.""" 

197 

198 _FieldPlainSerializerT = TypeVar('_FieldPlainSerializerT', bound=FieldPlainSerializer) 

199 _FieldWrapSerializerT = TypeVar('_FieldWrapSerializerT', bound=FieldWrapSerializer) 

200 

201 

202@overload 1opqrstabcdmnuvwxyzefghABCDEFijkl

203def field_serializer( 1opqrstabcdmnuvwxyzefghABCDEFijkl

204 field: str, 1abcdmnefghijkl

205 /, 

206 *fields: str, 1abcdmnefghijkl

207 mode: Literal['wrap'], 1abcdmnefghijkl

208 return_type: Any = ..., 1opqrstabcdmnuvwxyzefghABCDEFijkl

209 when_used: WhenUsed = ..., 1opqrstabcdmnuvwxyzefghABCDEFijkl

210 check_fields: bool | None = ..., 1opqrstabcdmnuvwxyzefghABCDEFijkl

211) -> Callable[[_FieldWrapSerializerT], _FieldWrapSerializerT]: ... 1abcdmnefghijkl

212 

213 

214@overload 1opqrstabcdmnuvwxyzefghABCDEFijkl

215def field_serializer( 1opqrstabcdmnuvwxyzefghABCDEFijkl

216 field: str, 1abcdmnefghijkl

217 /, 

218 *fields: str, 1abcdmnefghijkl

219 mode: Literal['plain'] = ..., 1opqrstabcdmnuvwxyzefghABCDEFijkl

220 return_type: Any = ..., 1opqrstabcdmnuvwxyzefghABCDEFijkl

221 when_used: WhenUsed = ..., 1opqrstabcdmnuvwxyzefghABCDEFijkl

222 check_fields: bool | None = ..., 1opqrstabcdmnuvwxyzefghABCDEFijkl

223) -> Callable[[_FieldPlainSerializerT], _FieldPlainSerializerT]: ... 1abcdmnefghijkl

224 

225 

226def field_serializer( 1opqrstabcdmnuvwxyzefghABCDEFijkl

227 *fields: str, 

228 mode: Literal['plain', 'wrap'] = 'plain', 

229 return_type: Any = PydanticUndefined, 

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 typing import Set 

242 

243 from pydantic import BaseModel, field_serializer 

244 

245 class StudentModel(BaseModel): 

246 name: str = 'Jane' 

247 courses: Set[str] 

248 

249 @field_serializer('courses', when_used='json') 

250 def serialize_courses_in_order(self, courses: Set[str]): 

251 return sorted(courses) 

252 

253 student = StudentModel(courses={'Math', 'Chemistry', 'English'}) 

254 print(student.model_dump_json()) 

255 #> {"name":"Jane","courses":["Chemistry","English","Math"]} 

256 ``` 

257 

258 See [Custom serializers](../concepts/serialization.md#custom-serializers) for more information. 

259 

260 Four signatures are supported: 

261 

262 - `(self, value: Any, info: FieldSerializationInfo)` 

263 - `(self, value: Any, nxt: SerializerFunctionWrapHandler, info: FieldSerializationInfo)` 

264 - `(value: Any, info: SerializationInfo)` 

265 - `(value: Any, nxt: SerializerFunctionWrapHandler, info: SerializationInfo)` 

266 

267 Args: 

268 fields: Which field(s) the method should be called on. 

269 mode: The serialization mode. 

270 

271 - `plain` means the function will be called instead of the default serialization logic, 

272 - `wrap` means the function will be called with an argument to optionally call the 

273 default serialization logic. 

274 return_type: Optional return type for the function, if omitted it will be inferred from the type annotation. 

275 when_used: Determines the serializer will be used for serialization. 

276 check_fields: Whether to check that the fields actually exist on the model. 

277 

278 Returns: 

279 The decorator function. 

280 """ 

281 

282 def dec(f: FieldSerializer) -> _decorators.PydanticDescriptorProxy[Any]: 1opqrstabcdmnuvwxyzefghABCDEFijkl

283 dec_info = _decorators.FieldSerializerDecoratorInfo( 1opqrstabcdmnuvwxyzefghABCDEFijkl

284 fields=fields, 

285 mode=mode, 

286 return_type=return_type, 

287 when_used=when_used, 

288 check_fields=check_fields, 

289 ) 

290 return _decorators.PydanticDescriptorProxy(f, dec_info) # pyright: ignore[reportArgumentType] 1opqrstabcdmnuvwxyzefghABCDEFijkl

291 

292 return dec # pyright: ignore[reportReturnType] 1opqrstabcdmnuvwxyzefghABCDEFijkl

293 

294 

295if TYPE_CHECKING: 1opqrstabcdmnuvwxyzefghABCDEFijkl

296 # The first argument in the following callables represent the `self` type: 

297 

298 ModelPlainSerializerWithInfo: TypeAlias = Callable[[Any, SerializationInfo], Any] 

299 """A model serializer method with the `info` argument, in `plain` mode.""" 

300 

301 ModelPlainSerializerWithoutInfo: TypeAlias = Callable[[Any], Any] 

302 """A model serializer method without the `info` argument, in `plain` mode.""" 

303 

304 ModelPlainSerializer: TypeAlias = 'ModelPlainSerializerWithInfo | ModelPlainSerializerWithoutInfo' 

305 """A model serializer method in `plain` mode.""" 

306 

307 ModelWrapSerializerWithInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler, SerializationInfo], Any] 

308 """A model serializer method with the `info` argument, in `wrap` mode.""" 

309 

310 ModelWrapSerializerWithoutInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler], Any] 

311 """A model serializer method without the `info` argument, in `wrap` mode.""" 

312 

313 ModelWrapSerializer: TypeAlias = 'ModelWrapSerializerWithInfo | ModelWrapSerializerWithoutInfo' 

314 """A model serializer method in `wrap` mode.""" 

315 

316 ModelSerializer: TypeAlias = 'ModelPlainSerializer | ModelWrapSerializer' 

317 

318 _ModelPlainSerializerT = TypeVar('_ModelPlainSerializerT', bound=ModelPlainSerializer) 

319 _ModelWrapSerializerT = TypeVar('_ModelWrapSerializerT', bound=ModelWrapSerializer) 

320 

321 

322@overload 1opqrstabcdmnuvwxyzefghABCDEFijkl

323def model_serializer(f: _ModelPlainSerializerT, /) -> _ModelPlainSerializerT: ... 1opqrstabcdmnuvwxyzefghABCDEFijkl

324 

325 

326@overload 1opqrstabcdmnuvwxyzefghABCDEFijkl

327def model_serializer( 1opqrstabcdmnuvwxyzefghABCDEFijkl

328 *, mode: Literal['wrap'], when_used: WhenUsed = 'always', return_type: Any = ... 1opqrstabcdmnuvwxyzefghABCDEFijkl

329) -> Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT]: ... 1abcdmnefghijkl

330 

331 

332@overload 1opqrstabcdmnuvwxyzefghABCDEFijkl

333def model_serializer( 1opqrstabcdmnuvwxyzefghABCDEFijkl

334 *, 

335 mode: Literal['plain'] = ..., 1opqrstabcdmnuvwxyzefghABCDEFijkl

336 when_used: WhenUsed = 'always', 1opqrstabcdmnuvwxyzefghABCDEFijkl

337 return_type: Any = ..., 1opqrstabcdmnuvwxyzefghABCDEFijkl

338) -> Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT]: ... 1abcdmnefghijkl

339 

340 

341def model_serializer( 1opqrstabcduvwxyzefghABCDEFijkl

342 f: _ModelPlainSerializerT | _ModelWrapSerializerT | None = None, 

343 /, 

344 *, 

345 mode: Literal['plain', 'wrap'] = 'plain', 

346 when_used: WhenUsed = 'always', 

347 return_type: Any = PydanticUndefined, 

348) -> ( 

349 _ModelPlainSerializerT 

350 | Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT] 

351 | Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT] 

352): 

353 """Decorator that enables custom model serialization. 

354 

355 This is useful when a model need to be serialized in a customized manner, allowing for flexibility beyond just specific fields. 

356 

357 An example would be to serialize temperature to the same temperature scale, such as degrees Celsius. 

358 

359 ```python 

360 from typing import Literal 

361 

362 from pydantic import BaseModel, model_serializer 

363 

364 class TemperatureModel(BaseModel): 

365 unit: Literal['C', 'F'] 

366 value: int 

367 

368 @model_serializer() 

369 def serialize_model(self): 

370 if self.unit == 'F': 

371 return {'unit': 'C', 'value': int((self.value - 32) / 1.8)} 

372 return {'unit': self.unit, 'value': self.value} 

373 

374 temperature = TemperatureModel(unit='F', value=212) 

375 print(temperature.model_dump()) 

376 #> {'unit': 'C', 'value': 100} 

377 ``` 

378 

379 Two signatures are supported for `mode='plain'`, which is the default: 

380 

381 - `(self)` 

382 - `(self, info: SerializationInfo)` 

383 

384 And two other signatures for `mode='wrap'`: 

385 

386 - `(self, nxt: SerializerFunctionWrapHandler)` 

387 - `(self, nxt: SerializerFunctionWrapHandler, info: SerializationInfo)` 

388 

389 See [Custom serializers](../concepts/serialization.md#custom-serializers) for more information. 

390 

391 Args: 

392 f: The function to be decorated. 

393 mode: The serialization mode. 

394 

395 - `'plain'` means the function will be called instead of the default serialization logic 

396 - `'wrap'` means the function will be called with an argument to optionally call the default 

397 serialization logic. 

398 when_used: Determines when this serializer should be used. 

399 return_type: The return type for the function. If omitted it will be inferred from the type annotation. 

400 

401 Returns: 

402 The decorator function. 

403 """ 

404 

405 def dec(f: ModelSerializer) -> _decorators.PydanticDescriptorProxy[Any]: 1opqrstabcdmnuvwxyzefghABCDEFijkl

406 dec_info = _decorators.ModelSerializerDecoratorInfo(mode=mode, return_type=return_type, when_used=when_used) 1opqrstabcdmnuvwxyzefghABCDEFijkl

407 return _decorators.PydanticDescriptorProxy(f, dec_info) 1opqrstabcdmnuvwxyzefghABCDEFijkl

408 

409 if f is None: 1opqrstabcdmnuvwxyzefghABCDEFijkl

410 return dec # pyright: ignore[reportReturnType] 1opqrstabcdmnuvwxyzefghABCDEFijkl

411 else: 

412 return dec(f) # pyright: ignore[reportReturnType] 1opqrstabcdmnuvwxyzefghABCDEFijkl

413 

414 

415AnyType = TypeVar('AnyType') 1opqrstabcdmnuvwxyzefghABCDEFijkl

416 

417 

418if TYPE_CHECKING: 1opqrstabcdmnuvwxyzefghABCDEFijkl

419 SerializeAsAny = Annotated[AnyType, ...] # SerializeAsAny[list[str]] will be treated by type checkers as list[str] 

420 """Force serialization to ignore whatever is defined in the schema and instead ask the object 

421 itself how it should be serialized. 

422 In particular, this means that when model subclasses are serialized, fields present in the subclass 

423 but not in the original schema will be included. 

424 """ 

425else: 

426 

427 @dataclasses.dataclass(**_internal_dataclass.slots_true) 1opqrstabcdmnuvwxyzefghABCDEFijkl

428 class SerializeAsAny: # noqa: D101 1opqrstabcdmnuvwxyzefghABCDEFijkl

429 def __class_getitem__(cls, item: Any) -> Any: 1opqrstabcdmnuvwxyzefghABCDEFijkl

430 return Annotated[item, SerializeAsAny()] 1opqrstabcdmnuvwxyzefghABCDEFijkl

431 

432 def __get_pydantic_core_schema__( 1opqrstabcdmnuvwxyzefghABCDEFijkl

433 self, source_type: Any, handler: GetCoreSchemaHandler 

434 ) -> core_schema.CoreSchema: 

435 schema = handler(source_type) 1opqrstabcdmnuvwxyzefghABCDEFijkl

436 schema_to_update = schema 1opqrstabcdmnuvwxyzefghABCDEFijkl

437 while schema_to_update['type'] == 'definitions': 437 ↛ 438line 437 didn't jump to line 438 because the condition on line 437 was never true1opqrstabcdmnuvwxyzefghABCDEFijkl

438 schema_to_update = schema_to_update.copy() 

439 schema_to_update = schema_to_update['schema'] 

440 schema_to_update['serialization'] = core_schema.wrap_serializer_function_ser_schema( 1opqrstabcdmnuvwxyzefghABCDEFijkl

441 lambda x, h: h(x), schema=core_schema.any_schema() 

442 ) 

443 return schema 1opqrstabcdmnuvwxyzefghABCDEFijkl

444 

445 __hash__ = object.__hash__ 1opqrstabcdmnuvwxyzefghABCDEFijkl