Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pydantic/functional_serializers.py: 45%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

114 statements  

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 

16from .errors import PydanticUserError 

17 

18 

19@dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True) 

20class PlainSerializer: 

21 """Plain serializers use a function to modify the output of serialization. 

22 

23 This is particularly helpful when you want to customize the serialization for annotated types. 

24 Consider an input of `list`, which will be serialized into a space-delimited string. 

25 

26 ```python 

27 from typing import Annotated 

28 

29 from pydantic import BaseModel, PlainSerializer 

30 

31 CustomStr = Annotated[ 

32 list, PlainSerializer(lambda x: ' '.join(x), return_type=str) 

33 ] 

34 

35 class StudentModel(BaseModel): 

36 courses: CustomStr 

37 

38 student = StudentModel(courses=['Math', 'Chemistry', 'English']) 

39 print(student.model_dump()) 

40 #> {'courses': 'Math Chemistry English'} 

41 ``` 

42 

43 Attributes: 

44 func: The serializer function. 

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

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

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

48 """ 

49 

50 func: core_schema.SerializerFunction 

51 return_type: Any = PydanticUndefined 

52 when_used: WhenUsed = 'always' 

53 

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

55 """Gets the Pydantic core schema. 

56 

57 Args: 

58 source_type: The source type. 

59 handler: The `GetCoreSchemaHandler` instance. 

60 

61 Returns: 

62 The Pydantic core schema. 

63 """ 

64 schema = handler(source_type) 

65 if self.return_type is not PydanticUndefined: 

66 return_type = self.return_type 

67 else: 

68 try: 

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

70 # Instead, let `get_callable_return_type` infer the globals to use, but still pass 

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

72 return_type = _decorators.get_callable_return_type( 

73 self.func, 

74 localns=handler._get_types_namespace().locals, 

75 ) 

76 except NameError as e: 

77 raise PydanticUndefinedAnnotation.from_name_error(e) from e 

78 

79 return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type) 

80 schema['serialization'] = core_schema.plain_serializer_function_ser_schema( 

81 function=self.func, 

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

83 return_schema=return_schema, 

84 when_used=self.when_used, 

85 ) 

86 return schema 

87 

88 

89@dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True) 

90class WrapSerializer: 

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

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

93 

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

95 

96 ```python 

97 from datetime import datetime, timezone 

98 from typing import Annotated, Any 

99 

100 from pydantic import BaseModel, WrapSerializer 

101 

102 class EventDatetime(BaseModel): 

103 start: datetime 

104 end: datetime 

105 

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

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

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

109 partial_result = handler(value, info) 

110 if info.mode == 'json': 

111 return { 

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

113 for k, v in partial_result.items() 

114 } 

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

116 

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

118 

119 class EventModel(BaseModel): 

120 event_datetime: UTCEventDatetime 

121 

122 dt = EventDatetime( 

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

124 ) 

125 event = EventModel(event_datetime=dt) 

126 print(event.model_dump()) 

127 ''' 

128 { 

129 'event_datetime': { 

130 'start': datetime.datetime( 

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

132 ), 

133 'end': datetime.datetime( 

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

135 ), 

136 } 

137 } 

138 ''' 

139 

140 print(event.model_dump_json()) 

141 ''' 

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

143 ''' 

144 ``` 

145 

146 Attributes: 

147 func: The serializer function to be wrapped. 

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

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

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

151 """ 

152 

153 func: core_schema.WrapSerializerFunction 

154 return_type: Any = PydanticUndefined 

155 when_used: WhenUsed = 'always' 

156 

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

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

159 

160 Args: 

161 source_type: Source type. 

162 handler: Core schema handler. 

163 

164 Returns: 

165 The generated core schema of the class. 

166 """ 

167 schema = handler(source_type) 

168 if self.return_type is not PydanticUndefined: 

169 return_type = self.return_type 

170 else: 

171 try: 

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

173 # Instead, let `get_callable_return_type` infer the globals to use, but still pass 

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

175 return_type = _decorators.get_callable_return_type( 

176 self.func, 

177 localns=handler._get_types_namespace().locals, 

178 ) 

179 except NameError as e: 

180 raise PydanticUndefinedAnnotation.from_name_error(e) from e 

181 

182 return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type) 

183 schema['serialization'] = core_schema.wrap_serializer_function_ser_schema( 

184 function=self.func, 

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

186 return_schema=return_schema, 

187 when_used=self.when_used, 

188 ) 

189 return schema 

190 

191 

192if TYPE_CHECKING: 

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

194 

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

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

197 

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

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

200 

201 FieldSerializer: TypeAlias = 'FieldPlainSerializer | FieldWrapSerializer' 

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

203 

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

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

206 

207 

208@overload 

209def field_serializer( 

210 field: str, 

211 /, 

212 *fields: str, 

213 mode: Literal['wrap'], 

214 return_type: Any = ..., 

215 when_used: WhenUsed = ..., 

216 check_fields: bool | None = ..., 

217) -> Callable[[_FieldWrapSerializerT], _FieldWrapSerializerT]: ... 

218 

219 

220@overload 

221def field_serializer( 

222 field: str, 

223 /, 

224 *fields: str, 

225 mode: Literal['plain'] = ..., 

226 return_type: Any = ..., 

227 when_used: WhenUsed = ..., 

228 check_fields: bool | None = ..., 

229) -> Callable[[_FieldPlainSerializerT], _FieldPlainSerializerT]: ... 

230 

231 

232def field_serializer( # noqa: D417 

233 field: str, 

234 /, 

235 *fields: str, 

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

237 # TODO PEP 747 (grep for 'return_type' on the whole code base): 

238 return_type: Any = PydanticUndefined, 

239 when_used: WhenUsed = 'always', 

240 check_fields: bool | None = None, 

241) -> ( 

242 Callable[[_FieldWrapSerializerT], _FieldWrapSerializerT] 

243 | Callable[[_FieldPlainSerializerT], _FieldPlainSerializerT] 

244): 

245 """Decorator that enables custom field serialization. 

246 

247 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. 

248 

249 ```python 

250 from pydantic import BaseModel, field_serializer 

251 

252 class StudentModel(BaseModel): 

253 name: str = 'Jane' 

254 courses: set[str] 

255 

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

257 def serialize_courses_in_order(self, courses: set[str]): 

258 return sorted(courses) 

259 

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

261 print(student.model_dump_json()) 

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

263 ``` 

264 

265 See [the usage documentation](../concepts/serialization.md#serializers) for more information. 

266 

267 Four signatures are supported for the decorated serializer: 

268 

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

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

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

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

273 

274 Args: 

275 *fields: The field names the serializer should apply to. 

276 mode: The serialization mode. 

277 

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

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

280 default serialization logic. 

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

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

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

284 

285 Raises: 

286 PydanticUserError: 

287 - If the decorator is used without any arguments (at least one field name must be provided). 

288 - If the provided field names are not strings. 

289 """ 

290 if callable(field) or isinstance(field, classmethod): 

291 raise PydanticUserError( 

292 'The `@field_serializer` decorator cannot be used without arguments, at least one field must be provided. ' 

293 "For example: `@field_serializer('<field_name>', ...)`.", 

294 code='decorator-missing-arguments', 

295 ) 

296 

297 fields = field, *fields 

298 if not all(isinstance(field, str) for field in fields): 

299 raise PydanticUserError( 

300 'The provided field names to the `@field_serializer` decorator should be strings. ' 

301 "For example: `@field_serializer('<field_name_1>', '<field_name_2>', ...).`", 

302 code='decorator-invalid-fields', 

303 ) 

304 

305 def dec(f: FieldSerializer) -> _decorators.PydanticDescriptorProxy[Any]: 

306 dec_info = _decorators.FieldSerializerDecoratorInfo( 

307 fields=fields, 

308 mode=mode, 

309 return_type=return_type, 

310 when_used=when_used, 

311 check_fields=check_fields, 

312 ) 

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

314 

315 return dec # pyright: ignore[reportReturnType] 

316 

317 

318if TYPE_CHECKING: 

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

320 

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

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

323 

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

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

326 

327 ModelPlainSerializer: TypeAlias = 'ModelPlainSerializerWithInfo | ModelPlainSerializerWithoutInfo' 

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

329 

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

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

332 

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

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

335 

336 ModelWrapSerializer: TypeAlias = 'ModelWrapSerializerWithInfo | ModelWrapSerializerWithoutInfo' 

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

338 

339 ModelSerializer: TypeAlias = 'ModelPlainSerializer | ModelWrapSerializer' 

340 

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

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

343 

344 

345@overload 

346def model_serializer(f: _ModelPlainSerializerT, /) -> _ModelPlainSerializerT: ... 

347 

348 

349@overload 

350def model_serializer( 

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

352) -> Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT]: ... 

353 

354 

355@overload 

356def model_serializer( 

357 *, 

358 mode: Literal['plain'] = ..., 

359 when_used: WhenUsed = 'always', 

360 return_type: Any = ..., 

361) -> Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT]: ... 

362 

363 

364def model_serializer( 

365 f: _ModelPlainSerializerT | _ModelWrapSerializerT | None = None, 

366 /, 

367 *, 

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

369 when_used: WhenUsed = 'always', 

370 return_type: Any = PydanticUndefined, 

371) -> ( 

372 _ModelPlainSerializerT 

373 | Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT] 

374 | Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT] 

375): 

376 """Decorator that enables custom model serialization. 

377 

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

379 

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

381 

382 ```python 

383 from typing import Literal 

384 

385 from pydantic import BaseModel, model_serializer 

386 

387 class TemperatureModel(BaseModel): 

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

389 value: int 

390 

391 @model_serializer() 

392 def serialize_model(self): 

393 if self.unit == 'F': 

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

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

396 

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

398 print(temperature.model_dump()) 

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

400 ``` 

401 

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

403 

404 - `(self)` 

405 - `(self, info: SerializationInfo)` 

406 

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

408 

409 - `(self, nxt: SerializerFunctionWrapHandler)` 

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

411 

412 See [the usage documentation](../concepts/serialization.md#serializers) for more information. 

413 

414 Args: 

415 f: The function to be decorated. 

416 mode: The serialization mode. 

417 

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

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

420 serialization logic. 

421 when_used: Determines when this serializer should be used. 

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

423 

424 Returns: 

425 The decorator function. 

426 """ 

427 

428 def dec(f: ModelSerializer) -> _decorators.PydanticDescriptorProxy[Any]: 

429 dec_info = _decorators.ModelSerializerDecoratorInfo(mode=mode, return_type=return_type, when_used=when_used) 

430 return _decorators.PydanticDescriptorProxy(f, dec_info) 

431 

432 if f is None: 

433 return dec # pyright: ignore[reportReturnType] 

434 else: 

435 return dec(f) # pyright: ignore[reportReturnType] 

436 

437 

438AnyType = TypeVar('AnyType') 

439 

440 

441if TYPE_CHECKING: 

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

443 """Annotation used to mark a type as having duck-typing serialization behavior. 

444 

445 See [usage documentation](../concepts/serialization.md#serializing-with-duck-typing) for more details. 

446 """ 

447else: 

448 

449 @dataclasses.dataclass(**_internal_dataclass.slots_true) 

450 class SerializeAsAny: 

451 """Annotation used to mark a type as having duck-typing serialization behavior. 

452 

453 See [usage documentation](../concepts/serialization.md#serializing-with-duck-typing) for more details. 

454 """ 

455 

456 def __class_getitem__(cls, item: Any) -> Any: 

457 return Annotated[item, SerializeAsAny()] 

458 

459 def __get_pydantic_core_schema__( 

460 self, source_type: Any, handler: GetCoreSchemaHandler 

461 ) -> core_schema.CoreSchema: 

462 schema = handler(source_type) 

463 schema_to_update = schema 

464 while schema_to_update['type'] == 'definitions': 

465 schema_to_update = schema_to_update.copy() 

466 schema_to_update = schema_to_update['schema'] 

467 schema_to_update['serialization'] = core_schema.simple_ser_schema('any') 

468 return schema 

469 

470 __hash__ = object.__hash__