fastapi 0.97.0__py3-none-any.whl → 0.100.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fastapi might be problematic. Click here for more details.

fastapi/applications.py CHANGED
@@ -15,7 +15,6 @@ from typing import (
15
15
 
16
16
  from fastapi import routing
17
17
  from fastapi.datastructures import Default, DefaultPlaceholder
18
- from fastapi.encoders import DictIntStrAny, SetIntStr
19
18
  from fastapi.exception_handlers import (
20
19
  http_exception_handler,
21
20
  request_validation_exception_handler,
@@ -31,7 +30,7 @@ from fastapi.openapi.docs import (
31
30
  )
32
31
  from fastapi.openapi.utils import get_openapi
33
32
  from fastapi.params import Depends
34
- from fastapi.types import DecoratedCallable
33
+ from fastapi.types import DecoratedCallable, IncEx
35
34
  from fastapi.utils import generate_unique_id
36
35
  from starlette.applications import Starlette
37
36
  from starlette.datastructures import State
@@ -297,8 +296,8 @@ class FastAPI(Starlette):
297
296
  deprecated: Optional[bool] = None,
298
297
  methods: Optional[List[str]] = None,
299
298
  operation_id: Optional[str] = None,
300
- response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
301
- response_model_exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
299
+ response_model_include: Optional[IncEx] = None,
300
+ response_model_exclude: Optional[IncEx] = None,
302
301
  response_model_by_alias: bool = True,
303
302
  response_model_exclude_unset: bool = False,
304
303
  response_model_exclude_defaults: bool = False,
@@ -355,8 +354,8 @@ class FastAPI(Starlette):
355
354
  deprecated: Optional[bool] = None,
356
355
  methods: Optional[List[str]] = None,
357
356
  operation_id: Optional[str] = None,
358
- response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
359
- response_model_exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
357
+ response_model_include: Optional[IncEx] = None,
358
+ response_model_exclude: Optional[IncEx] = None,
360
359
  response_model_by_alias: bool = True,
361
360
  response_model_exclude_unset: bool = False,
362
361
  response_model_exclude_defaults: bool = False,
@@ -476,8 +475,8 @@ class FastAPI(Starlette):
476
475
  responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
477
476
  deprecated: Optional[bool] = None,
478
477
  operation_id: Optional[str] = None,
479
- response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
480
- response_model_exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
478
+ response_model_include: Optional[IncEx] = None,
479
+ response_model_exclude: Optional[IncEx] = None,
481
480
  response_model_by_alias: bool = True,
482
481
  response_model_exclude_unset: bool = False,
483
482
  response_model_exclude_defaults: bool = False,
@@ -531,8 +530,8 @@ class FastAPI(Starlette):
531
530
  responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
532
531
  deprecated: Optional[bool] = None,
533
532
  operation_id: Optional[str] = None,
534
- response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
535
- response_model_exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
533
+ response_model_include: Optional[IncEx] = None,
534
+ response_model_exclude: Optional[IncEx] = None,
536
535
  response_model_by_alias: bool = True,
537
536
  response_model_exclude_unset: bool = False,
538
537
  response_model_exclude_defaults: bool = False,
@@ -586,8 +585,8 @@ class FastAPI(Starlette):
586
585
  responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
587
586
  deprecated: Optional[bool] = None,
588
587
  operation_id: Optional[str] = None,
589
- response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
590
- response_model_exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
588
+ response_model_include: Optional[IncEx] = None,
589
+ response_model_exclude: Optional[IncEx] = None,
591
590
  response_model_by_alias: bool = True,
592
591
  response_model_exclude_unset: bool = False,
593
592
  response_model_exclude_defaults: bool = False,
@@ -641,8 +640,8 @@ class FastAPI(Starlette):
641
640
  responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
642
641
  deprecated: Optional[bool] = None,
643
642
  operation_id: Optional[str] = None,
644
- response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
645
- response_model_exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
643
+ response_model_include: Optional[IncEx] = None,
644
+ response_model_exclude: Optional[IncEx] = None,
646
645
  response_model_by_alias: bool = True,
647
646
  response_model_exclude_unset: bool = False,
648
647
  response_model_exclude_defaults: bool = False,
@@ -696,8 +695,8 @@ class FastAPI(Starlette):
696
695
  responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
697
696
  deprecated: Optional[bool] = None,
698
697
  operation_id: Optional[str] = None,
699
- response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
700
- response_model_exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
698
+ response_model_include: Optional[IncEx] = None,
699
+ response_model_exclude: Optional[IncEx] = None,
701
700
  response_model_by_alias: bool = True,
702
701
  response_model_exclude_unset: bool = False,
703
702
  response_model_exclude_defaults: bool = False,
@@ -751,8 +750,8 @@ class FastAPI(Starlette):
751
750
  responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
752
751
  deprecated: Optional[bool] = None,
753
752
  operation_id: Optional[str] = None,
754
- response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
755
- response_model_exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
753
+ response_model_include: Optional[IncEx] = None,
754
+ response_model_exclude: Optional[IncEx] = None,
756
755
  response_model_by_alias: bool = True,
757
756
  response_model_exclude_unset: bool = False,
758
757
  response_model_exclude_defaults: bool = False,
@@ -806,8 +805,8 @@ class FastAPI(Starlette):
806
805
  responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
807
806
  deprecated: Optional[bool] = None,
808
807
  operation_id: Optional[str] = None,
809
- response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
810
- response_model_exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
808
+ response_model_include: Optional[IncEx] = None,
809
+ response_model_exclude: Optional[IncEx] = None,
811
810
  response_model_by_alias: bool = True,
812
811
  response_model_exclude_unset: bool = False,
813
812
  response_model_exclude_defaults: bool = False,
@@ -861,8 +860,8 @@ class FastAPI(Starlette):
861
860
  responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
862
861
  deprecated: Optional[bool] = None,
863
862
  operation_id: Optional[str] = None,
864
- response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
865
- response_model_exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
863
+ response_model_include: Optional[IncEx] = None,
864
+ response_model_exclude: Optional[IncEx] = None,
866
865
  response_model_by_alias: bool = True,
867
866
  response_model_exclude_unset: bool = False,
868
867
  response_model_exclude_defaults: bool = False,
fastapi/datastructures.py CHANGED
@@ -1,5 +1,12 @@
1
- from typing import Any, Callable, Dict, Iterable, Type, TypeVar
2
-
1
+ from typing import Any, Callable, Dict, Iterable, Type, TypeVar, cast
2
+
3
+ from fastapi._compat import (
4
+ PYDANTIC_V2,
5
+ CoreSchema,
6
+ GetJsonSchemaHandler,
7
+ JsonSchemaValue,
8
+ general_plain_validator_function,
9
+ )
3
10
  from starlette.datastructures import URL as URL # noqa: F401
4
11
  from starlette.datastructures import Address as Address # noqa: F401
5
12
  from starlette.datastructures import FormData as FormData # noqa: F401
@@ -21,8 +28,28 @@ class UploadFile(StarletteUploadFile):
21
28
  return v
22
29
 
23
30
  @classmethod
24
- def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
25
- field_schema.update({"type": "string", "format": "binary"})
31
+ def _validate(cls, __input_value: Any, _: Any) -> "UploadFile":
32
+ if not isinstance(__input_value, StarletteUploadFile):
33
+ raise ValueError(f"Expected UploadFile, received: {type(__input_value)}")
34
+ return cast(UploadFile, __input_value)
35
+
36
+ if not PYDANTIC_V2:
37
+
38
+ @classmethod
39
+ def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
40
+ field_schema.update({"type": "string", "format": "binary"})
41
+
42
+ @classmethod
43
+ def __get_pydantic_json_schema__(
44
+ cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler
45
+ ) -> JsonSchemaValue:
46
+ return {"type": "string", "format": "binary"}
47
+
48
+ @classmethod
49
+ def __get_pydantic_core_schema__(
50
+ cls, source: Type[Any], handler: Callable[[Any], CoreSchema]
51
+ ) -> CoreSchema:
52
+ return general_plain_validator_function(cls._validate)
26
53
 
27
54
 
28
55
  class DefaultPlaceholder:
@@ -1,7 +1,7 @@
1
1
  from typing import Any, Callable, List, Optional, Sequence
2
2
 
3
+ from fastapi._compat import ModelField
3
4
  from fastapi.security.base import SecurityBase
4
- from pydantic.fields import ModelField
5
5
 
6
6
 
7
7
  class SecurityRequirement:
@@ -1,7 +1,6 @@
1
- import dataclasses
2
1
  import inspect
3
2
  from contextlib import contextmanager
4
- from copy import copy, deepcopy
3
+ from copy import deepcopy
5
4
  from typing import (
6
5
  Any,
7
6
  Callable,
@@ -20,6 +19,31 @@ from typing import (
20
19
 
21
20
  import anyio
22
21
  from fastapi import params
22
+ from fastapi._compat import (
23
+ PYDANTIC_V2,
24
+ ErrorWrapper,
25
+ ModelField,
26
+ Required,
27
+ Undefined,
28
+ _regenerate_error_with_loc,
29
+ copy_field_info,
30
+ create_body_model,
31
+ evaluate_forwardref,
32
+ field_annotation_is_scalar,
33
+ get_annotation_from_field_info,
34
+ get_missing_field_error,
35
+ is_bytes_field,
36
+ is_bytes_sequence_field,
37
+ is_scalar_field,
38
+ is_scalar_sequence_field,
39
+ is_sequence_field,
40
+ is_uploadfile_or_nonable_uploadfile_annotation,
41
+ is_uploadfile_sequence_annotation,
42
+ lenient_issubclass,
43
+ sequence_types,
44
+ serialize_sequence_value,
45
+ value_is_sequence,
46
+ )
23
47
  from fastapi.concurrency import (
24
48
  AsyncExitStack,
25
49
  asynccontextmanager,
@@ -31,50 +55,14 @@ from fastapi.security.base import SecurityBase
31
55
  from fastapi.security.oauth2 import OAuth2, SecurityScopes
32
56
  from fastapi.security.open_id_connect_url import OpenIdConnect
33
57
  from fastapi.utils import create_response_field, get_path_param_names
34
- from pydantic import BaseModel, create_model
35
- from pydantic.error_wrappers import ErrorWrapper
36
- from pydantic.errors import MissingError
37
- from pydantic.fields import (
38
- SHAPE_FROZENSET,
39
- SHAPE_LIST,
40
- SHAPE_SEQUENCE,
41
- SHAPE_SET,
42
- SHAPE_SINGLETON,
43
- SHAPE_TUPLE,
44
- SHAPE_TUPLE_ELLIPSIS,
45
- FieldInfo,
46
- ModelField,
47
- Required,
48
- Undefined,
49
- )
50
- from pydantic.schema import get_annotation_from_field_info
51
- from pydantic.typing import evaluate_forwardref, get_args, get_origin
52
- from pydantic.utils import lenient_issubclass
58
+ from pydantic.fields import FieldInfo
53
59
  from starlette.background import BackgroundTasks
54
60
  from starlette.concurrency import run_in_threadpool
55
61
  from starlette.datastructures import FormData, Headers, QueryParams, UploadFile
56
62
  from starlette.requests import HTTPConnection, Request
57
63
  from starlette.responses import Response
58
64
  from starlette.websockets import WebSocket
59
- from typing_extensions import Annotated
60
-
61
- sequence_shapes = {
62
- SHAPE_LIST,
63
- SHAPE_SET,
64
- SHAPE_FROZENSET,
65
- SHAPE_TUPLE,
66
- SHAPE_SEQUENCE,
67
- SHAPE_TUPLE_ELLIPSIS,
68
- }
69
- sequence_types = (list, set, tuple)
70
- sequence_shape_to_type = {
71
- SHAPE_LIST: list,
72
- SHAPE_SET: set,
73
- SHAPE_TUPLE: tuple,
74
- SHAPE_SEQUENCE: list,
75
- SHAPE_TUPLE_ELLIPSIS: list,
76
- }
77
-
65
+ from typing_extensions import Annotated, get_args, get_origin
78
66
 
79
67
  multipart_not_installed_error = (
80
68
  'Form data requires "python-multipart" to be installed. \n'
@@ -216,36 +204,6 @@ def get_flat_params(dependant: Dependant) -> List[ModelField]:
216
204
  )
217
205
 
218
206
 
219
- def is_scalar_field(field: ModelField) -> bool:
220
- field_info = field.field_info
221
- if not (
222
- field.shape == SHAPE_SINGLETON
223
- and not lenient_issubclass(field.type_, BaseModel)
224
- and not lenient_issubclass(field.type_, sequence_types + (dict,))
225
- and not dataclasses.is_dataclass(field.type_)
226
- and not isinstance(field_info, params.Body)
227
- ):
228
- return False
229
- if field.sub_fields:
230
- if not all(is_scalar_field(f) for f in field.sub_fields):
231
- return False
232
- return True
233
-
234
-
235
- def is_scalar_sequence_field(field: ModelField) -> bool:
236
- if (field.shape in sequence_shapes) and not lenient_issubclass(
237
- field.type_, BaseModel
238
- ):
239
- if field.sub_fields is not None:
240
- for sub_field in field.sub_fields:
241
- if not is_scalar_field(sub_field):
242
- return False
243
- return True
244
- if lenient_issubclass(field.type_, sequence_types):
245
- return True
246
- return False
247
-
248
-
249
207
  def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
250
208
  signature = inspect.signature(call)
251
209
  globalns = getattr(call, "__globals__", {})
@@ -364,12 +322,11 @@ def analyze_param(
364
322
  is_path_param: bool,
365
323
  ) -> Tuple[Any, Optional[params.Depends], Optional[ModelField]]:
366
324
  field_info = None
367
- used_default_field_info = False
368
325
  depends = None
369
326
  type_annotation: Any = Any
370
327
  if (
371
328
  annotation is not inspect.Signature.empty
372
- and get_origin(annotation) is Annotated # type: ignore[comparison-overlap]
329
+ and get_origin(annotation) is Annotated
373
330
  ):
374
331
  annotated_args = get_args(annotation)
375
332
  type_annotation = annotated_args[0]
@@ -384,7 +341,9 @@ def analyze_param(
384
341
  fastapi_annotation = next(iter(fastapi_annotations), None)
385
342
  if isinstance(fastapi_annotation, FieldInfo):
386
343
  # Copy `field_info` because we mutate `field_info.default` below.
387
- field_info = copy(fastapi_annotation)
344
+ field_info = copy_field_info(
345
+ field_info=fastapi_annotation, annotation=annotation
346
+ )
388
347
  assert field_info.default is Undefined or field_info.default is Required, (
389
348
  f"`{field_info.__class__.__name__}` default value cannot be set in"
390
349
  f" `Annotated` for {param_name!r}. Set the default value with `=` instead."
@@ -415,6 +374,8 @@ def analyze_param(
415
374
  f" together for {param_name!r}"
416
375
  )
417
376
  field_info = value
377
+ if PYDANTIC_V2:
378
+ field_info.annotation = type_annotation
418
379
 
419
380
  if depends is not None and depends.dependency is None:
420
381
  depends.dependency = type_annotation
@@ -433,10 +394,15 @@ def analyze_param(
433
394
  # We might check here that `default_value is Required`, but the fact is that the same
434
395
  # parameter might sometimes be a path parameter and sometimes not. See
435
396
  # `tests/test_infer_param_optionality.py` for an example.
436
- field_info = params.Path()
397
+ field_info = params.Path(annotation=type_annotation)
398
+ elif is_uploadfile_or_nonable_uploadfile_annotation(
399
+ type_annotation
400
+ ) or is_uploadfile_sequence_annotation(type_annotation):
401
+ field_info = params.File(annotation=type_annotation, default=default_value)
402
+ elif not field_annotation_is_scalar(annotation=type_annotation):
403
+ field_info = params.Body(annotation=type_annotation, default=default_value)
437
404
  else:
438
- field_info = params.Query(default=default_value)
439
- used_default_field_info = True
405
+ field_info = params.Query(annotation=type_annotation, default=default_value)
440
406
 
441
407
  field = None
442
408
  if field_info is not None:
@@ -450,8 +416,8 @@ def analyze_param(
450
416
  and getattr(field_info, "in_", None) is None
451
417
  ):
452
418
  field_info.in_ = params.ParamTypes.query
453
- annotation = get_annotation_from_field_info(
454
- annotation if annotation is not inspect.Signature.empty else Any,
419
+ use_annotation = get_annotation_from_field_info(
420
+ type_annotation,
455
421
  field_info,
456
422
  param_name,
457
423
  )
@@ -459,19 +425,15 @@ def analyze_param(
459
425
  alias = param_name.replace("_", "-")
460
426
  else:
461
427
  alias = field_info.alias or param_name
428
+ field_info.alias = alias
462
429
  field = create_response_field(
463
430
  name=param_name,
464
- type_=annotation,
431
+ type_=use_annotation,
465
432
  default=field_info.default,
466
433
  alias=alias,
467
434
  required=field_info.default in (Required, Undefined),
468
435
  field_info=field_info,
469
436
  )
470
- if used_default_field_info:
471
- if lenient_issubclass(field.type_, UploadFile):
472
- field.field_info = params.File(field_info.default)
473
- elif not is_scalar_field(field=field):
474
- field.field_info = params.Body(field_info.default)
475
437
 
476
438
  return type_annotation, depends, field
477
439
 
@@ -554,13 +516,13 @@ async def solve_dependencies(
554
516
  dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], Any]] = None,
555
517
  ) -> Tuple[
556
518
  Dict[str, Any],
557
- List[ErrorWrapper],
519
+ List[Any],
558
520
  Optional[BackgroundTasks],
559
521
  Response,
560
522
  Dict[Tuple[Callable[..., Any], Tuple[str]], Any],
561
523
  ]:
562
524
  values: Dict[str, Any] = {}
563
- errors: List[ErrorWrapper] = []
525
+ errors: List[Any] = []
564
526
  if response is None:
565
527
  response = Response()
566
528
  del response.headers["content-length"]
@@ -674,7 +636,7 @@ async def solve_dependencies(
674
636
  def request_params_to_args(
675
637
  required_params: Sequence[ModelField],
676
638
  received_params: Union[Mapping[str, Any], QueryParams, Headers],
677
- ) -> Tuple[Dict[str, Any], List[ErrorWrapper]]:
639
+ ) -> Tuple[Dict[str, Any], List[Any]]:
678
640
  values = {}
679
641
  errors = []
680
642
  for field in required_params:
@@ -688,23 +650,19 @@ def request_params_to_args(
688
650
  assert isinstance(
689
651
  field_info, params.Param
690
652
  ), "Params must be subclasses of Param"
653
+ loc = (field_info.in_.value, field.alias)
691
654
  if value is None:
692
655
  if field.required:
693
- errors.append(
694
- ErrorWrapper(
695
- MissingError(), loc=(field_info.in_.value, field.alias)
696
- )
697
- )
656
+ errors.append(get_missing_field_error(loc=loc))
698
657
  else:
699
658
  values[field.name] = deepcopy(field.default)
700
659
  continue
701
- v_, errors_ = field.validate(
702
- value, values, loc=(field_info.in_.value, field.alias)
703
- )
660
+ v_, errors_ = field.validate(value, values, loc=loc)
704
661
  if isinstance(errors_, ErrorWrapper):
705
662
  errors.append(errors_)
706
663
  elif isinstance(errors_, list):
707
- errors.extend(errors_)
664
+ new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=())
665
+ errors.extend(new_errors)
708
666
  else:
709
667
  values[field.name] = v_
710
668
  return values, errors
@@ -713,9 +671,9 @@ def request_params_to_args(
713
671
  async def request_body_to_args(
714
672
  required_params: List[ModelField],
715
673
  received_body: Optional[Union[Dict[str, Any], FormData]],
716
- ) -> Tuple[Dict[str, Any], List[ErrorWrapper]]:
674
+ ) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
717
675
  values = {}
718
- errors = []
676
+ errors: List[Dict[str, Any]] = []
719
677
  if required_params:
720
678
  field = required_params[0]
721
679
  field_info = field.field_info
@@ -733,9 +691,7 @@ async def request_body_to_args(
733
691
 
734
692
  value: Optional[Any] = None
735
693
  if received_body is not None:
736
- if (
737
- field.shape in sequence_shapes or field.type_ in sequence_types
738
- ) and isinstance(received_body, FormData):
694
+ if (is_sequence_field(field)) and isinstance(received_body, FormData):
739
695
  value = received_body.getlist(field.alias)
740
696
  else:
741
697
  try:
@@ -748,7 +704,7 @@ async def request_body_to_args(
748
704
  or (isinstance(field_info, params.Form) and value == "")
749
705
  or (
750
706
  isinstance(field_info, params.Form)
751
- and field.shape in sequence_shapes
707
+ and is_sequence_field(field)
752
708
  and len(value) == 0
753
709
  )
754
710
  ):
@@ -759,16 +715,17 @@ async def request_body_to_args(
759
715
  continue
760
716
  if (
761
717
  isinstance(field_info, params.File)
762
- and lenient_issubclass(field.type_, bytes)
718
+ and is_bytes_field(field)
763
719
  and isinstance(value, UploadFile)
764
720
  ):
765
721
  value = await value.read()
766
722
  elif (
767
- field.shape in sequence_shapes
723
+ is_bytes_sequence_field(field)
768
724
  and isinstance(field_info, params.File)
769
- and lenient_issubclass(field.type_, bytes)
770
- and isinstance(value, sequence_types)
725
+ and value_is_sequence(value)
771
726
  ):
727
+ # For types
728
+ assert isinstance(value, sequence_types) # type: ignore[arg-type]
772
729
  results: List[Union[bytes, str]] = []
773
730
 
774
731
  async def process_fn(
@@ -780,24 +737,19 @@ async def request_body_to_args(
780
737
  async with anyio.create_task_group() as tg:
781
738
  for sub_value in value:
782
739
  tg.start_soon(process_fn, sub_value.read)
783
- value = sequence_shape_to_type[field.shape](results)
740
+ value = serialize_sequence_value(field=field, value=results)
784
741
 
785
742
  v_, errors_ = field.validate(value, values, loc=loc)
786
743
 
787
- if isinstance(errors_, ErrorWrapper):
788
- errors.append(errors_)
789
- elif isinstance(errors_, list):
744
+ if isinstance(errors_, list):
790
745
  errors.extend(errors_)
746
+ elif errors_:
747
+ errors.append(errors_)
791
748
  else:
792
749
  values[field.name] = v_
793
750
  return values, errors
794
751
 
795
752
 
796
- def get_missing_field_error(loc: Tuple[str, ...]) -> ErrorWrapper:
797
- missing_field_error = ErrorWrapper(MissingError(), loc=loc)
798
- return missing_field_error
799
-
800
-
801
753
  def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
802
754
  flat_dependant = get_flat_dependant(dependant)
803
755
  if not flat_dependant.body_params:
@@ -815,12 +767,16 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
815
767
  for param in flat_dependant.body_params:
816
768
  setattr(param.field_info, "embed", True) # noqa: B010
817
769
  model_name = "Body_" + name
818
- BodyModel: Type[BaseModel] = create_model(model_name)
819
- for f in flat_dependant.body_params:
820
- BodyModel.__fields__[f.name] = f
770
+ BodyModel = create_body_model(
771
+ fields=flat_dependant.body_params, model_name=model_name
772
+ )
821
773
  required = any(True for f in flat_dependant.body_params if f.required)
822
-
823
- BodyFieldInfo_kwargs: Dict[str, Any] = {"default": None}
774
+ BodyFieldInfo_kwargs: Dict[str, Any] = {
775
+ "annotation": BodyModel,
776
+ "alias": "body",
777
+ }
778
+ if not required:
779
+ BodyFieldInfo_kwargs["default"] = None
824
780
  if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params):
825
781
  BodyFieldInfo: Type[params.Body] = params.File
826
782
  elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params):
fastapi/encoders.py CHANGED
@@ -1,15 +1,87 @@
1
1
  import dataclasses
2
- from collections import defaultdict
2
+ import datetime
3
+ from collections import defaultdict, deque
4
+ from decimal import Decimal
3
5
  from enum import Enum
4
- from pathlib import PurePath
6
+ from ipaddress import (
7
+ IPv4Address,
8
+ IPv4Interface,
9
+ IPv4Network,
10
+ IPv6Address,
11
+ IPv6Interface,
12
+ IPv6Network,
13
+ )
14
+ from pathlib import Path, PurePath
15
+ from re import Pattern
5
16
  from types import GeneratorType
6
- from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
17
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union
18
+ from uuid import UUID
7
19
 
20
+ from fastapi.types import IncEx
8
21
  from pydantic import BaseModel
9
- from pydantic.json import ENCODERS_BY_TYPE
22
+ from pydantic.color import Color
23
+ from pydantic.networks import NameEmail
24
+ from pydantic.types import SecretBytes, SecretStr
10
25
 
11
- SetIntStr = Set[Union[int, str]]
12
- DictIntStrAny = Dict[Union[int, str], Any]
26
+ from ._compat import PYDANTIC_V2, MultiHostUrl, Url, _model_dump
27
+
28
+
29
+ # Taken from Pydantic v1 as is
30
+ def isoformat(o: Union[datetime.date, datetime.time]) -> str:
31
+ return o.isoformat()
32
+
33
+
34
+ # Taken from Pydantic v1 as is
35
+ # TODO: pv2 should this return strings instead?
36
+ def decimal_encoder(dec_value: Decimal) -> Union[int, float]:
37
+ """
38
+ Encodes a Decimal as int of there's no exponent, otherwise float
39
+
40
+ This is useful when we use ConstrainedDecimal to represent Numeric(x,0)
41
+ where a integer (but not int typed) is used. Encoding this as a float
42
+ results in failed round-tripping between encode and parse.
43
+ Our Id type is a prime example of this.
44
+
45
+ >>> decimal_encoder(Decimal("1.0"))
46
+ 1.0
47
+
48
+ >>> decimal_encoder(Decimal("1"))
49
+ 1
50
+ """
51
+ if dec_value.as_tuple().exponent >= 0: # type: ignore[operator]
52
+ return int(dec_value)
53
+ else:
54
+ return float(dec_value)
55
+
56
+
57
+ ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = {
58
+ bytes: lambda o: o.decode(),
59
+ Color: str,
60
+ datetime.date: isoformat,
61
+ datetime.datetime: isoformat,
62
+ datetime.time: isoformat,
63
+ datetime.timedelta: lambda td: td.total_seconds(),
64
+ Decimal: decimal_encoder,
65
+ Enum: lambda o: o.value,
66
+ frozenset: list,
67
+ deque: list,
68
+ GeneratorType: list,
69
+ IPv4Address: str,
70
+ IPv4Interface: str,
71
+ IPv4Network: str,
72
+ IPv6Address: str,
73
+ IPv6Interface: str,
74
+ IPv6Network: str,
75
+ NameEmail: str,
76
+ Path: str,
77
+ Pattern: lambda o: o.pattern,
78
+ SecretBytes: str,
79
+ SecretStr: str,
80
+ set: list,
81
+ UUID: str,
82
+ Url: str,
83
+ MultiHostUrl: str,
84
+ }
13
85
 
14
86
 
15
87
  def generate_encoders_by_class_tuples(
@@ -28,8 +100,8 @@ encoders_by_class_tuples = generate_encoders_by_class_tuples(ENCODERS_BY_TYPE)
28
100
 
29
101
  def jsonable_encoder(
30
102
  obj: Any,
31
- include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
32
- exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
103
+ include: Optional[IncEx] = None,
104
+ exclude: Optional[IncEx] = None,
33
105
  by_alias: bool = True,
34
106
  exclude_unset: bool = False,
35
107
  exclude_defaults: bool = False,
@@ -50,10 +122,15 @@ def jsonable_encoder(
50
122
  if exclude is not None and not isinstance(exclude, (set, dict)):
51
123
  exclude = set(exclude)
52
124
  if isinstance(obj, BaseModel):
53
- encoder = getattr(obj.__config__, "json_encoders", {})
54
- if custom_encoder:
55
- encoder.update(custom_encoder)
56
- obj_dict = obj.dict(
125
+ # TODO: remove when deprecating Pydantic v1
126
+ encoders: Dict[Any, Any] = {}
127
+ if not PYDANTIC_V2:
128
+ encoders = getattr(obj.__config__, "json_encoders", {}) # type: ignore[attr-defined]
129
+ if custom_encoder:
130
+ encoders.update(custom_encoder)
131
+ obj_dict = _model_dump(
132
+ obj,
133
+ mode="json",
57
134
  include=include,
58
135
  exclude=exclude,
59
136
  by_alias=by_alias,
@@ -67,7 +144,8 @@ def jsonable_encoder(
67
144
  obj_dict,
68
145
  exclude_none=exclude_none,
69
146
  exclude_defaults=exclude_defaults,
70
- custom_encoder=encoder,
147
+ # TODO: remove when deprecating Pydantic v1
148
+ custom_encoder=encoders,
71
149
  sqlalchemy_safe=sqlalchemy_safe,
72
150
  )
73
151
  if dataclasses.is_dataclass(obj):