fastapi 0.112.3__py3-none-any.whl → 0.113.0__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/__init__.py +1 -1
- fastapi/_compat.py +15 -0
- fastapi/dependencies/utils.py +183 -128
- fastapi/param_functions.py +2 -2
- fastapi/params.py +1 -2
- fastapi/routing.py +23 -3
- {fastapi-0.112.3.dist-info → fastapi-0.113.0.dist-info}/METADATA +2 -2
- {fastapi-0.112.3.dist-info → fastapi-0.113.0.dist-info}/RECORD +11 -11
- {fastapi-0.112.3.dist-info → fastapi-0.113.0.dist-info}/WHEEL +0 -0
- {fastapi-0.112.3.dist-info → fastapi-0.113.0.dist-info}/entry_points.txt +0 -0
- {fastapi-0.112.3.dist-info → fastapi-0.113.0.dist-info}/licenses/LICENSE +0 -0
fastapi/__init__.py
CHANGED
fastapi/_compat.py
CHANGED
|
@@ -279,6 +279,12 @@ if PYDANTIC_V2:
|
|
|
279
279
|
BodyModel: Type[BaseModel] = create_model(model_name, **field_params) # type: ignore[call-overload]
|
|
280
280
|
return BodyModel
|
|
281
281
|
|
|
282
|
+
def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:
|
|
283
|
+
return [
|
|
284
|
+
ModelField(field_info=field_info, name=name)
|
|
285
|
+
for name, field_info in model.model_fields.items()
|
|
286
|
+
]
|
|
287
|
+
|
|
282
288
|
else:
|
|
283
289
|
from fastapi.openapi.constants import REF_PREFIX as REF_PREFIX
|
|
284
290
|
from pydantic import AnyUrl as Url # noqa: F401
|
|
@@ -513,6 +519,9 @@ else:
|
|
|
513
519
|
BodyModel.__fields__[f.name] = f # type: ignore[index]
|
|
514
520
|
return BodyModel
|
|
515
521
|
|
|
522
|
+
def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:
|
|
523
|
+
return list(model.__fields__.values()) # type: ignore[attr-defined]
|
|
524
|
+
|
|
516
525
|
|
|
517
526
|
def _regenerate_error_with_loc(
|
|
518
527
|
*, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...]
|
|
@@ -532,6 +541,12 @@ def _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
|
|
|
532
541
|
|
|
533
542
|
|
|
534
543
|
def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
|
|
544
|
+
origin = get_origin(annotation)
|
|
545
|
+
if origin is Union or origin is UnionType:
|
|
546
|
+
for arg in get_args(annotation):
|
|
547
|
+
if field_annotation_is_sequence(arg):
|
|
548
|
+
return True
|
|
549
|
+
return False
|
|
535
550
|
return _annotation_is_sequence(annotation) or _annotation_is_sequence(
|
|
536
551
|
get_origin(annotation)
|
|
537
552
|
)
|
fastapi/dependencies/utils.py
CHANGED
|
@@ -33,6 +33,7 @@ from fastapi._compat import (
|
|
|
33
33
|
field_annotation_is_scalar,
|
|
34
34
|
get_annotation_from_field_info,
|
|
35
35
|
get_missing_field_error,
|
|
36
|
+
get_model_fields,
|
|
36
37
|
is_bytes_field,
|
|
37
38
|
is_bytes_sequence_field,
|
|
38
39
|
is_scalar_field,
|
|
@@ -56,10 +57,17 @@ from fastapi.security.base import SecurityBase
|
|
|
56
57
|
from fastapi.security.oauth2 import OAuth2, SecurityScopes
|
|
57
58
|
from fastapi.security.open_id_connect_url import OpenIdConnect
|
|
58
59
|
from fastapi.utils import create_model_field, get_path_param_names
|
|
60
|
+
from pydantic import BaseModel
|
|
59
61
|
from pydantic.fields import FieldInfo
|
|
60
62
|
from starlette.background import BackgroundTasks as StarletteBackgroundTasks
|
|
61
63
|
from starlette.concurrency import run_in_threadpool
|
|
62
|
-
from starlette.datastructures import
|
|
64
|
+
from starlette.datastructures import (
|
|
65
|
+
FormData,
|
|
66
|
+
Headers,
|
|
67
|
+
ImmutableMultiDict,
|
|
68
|
+
QueryParams,
|
|
69
|
+
UploadFile,
|
|
70
|
+
)
|
|
63
71
|
from starlette.requests import HTTPConnection, Request
|
|
64
72
|
from starlette.responses import Response
|
|
65
73
|
from starlette.websockets import WebSocket
|
|
@@ -282,7 +290,7 @@ def get_dependant(
|
|
|
282
290
|
), f"Cannot specify multiple FastAPI annotations for {param_name!r}"
|
|
283
291
|
continue
|
|
284
292
|
assert param_details.field is not None
|
|
285
|
-
if
|
|
293
|
+
if isinstance(param_details.field.field_info, params.Body):
|
|
286
294
|
dependant.body_params.append(param_details.field)
|
|
287
295
|
else:
|
|
288
296
|
add_param_to_fields(field=param_details.field, dependant=dependant)
|
|
@@ -466,29 +474,16 @@ def analyze_param(
|
|
|
466
474
|
required=field_info.default in (Required, Undefined),
|
|
467
475
|
field_info=field_info,
|
|
468
476
|
)
|
|
477
|
+
if is_path_param:
|
|
478
|
+
assert is_scalar_field(
|
|
479
|
+
field=field
|
|
480
|
+
), "Path params must be of one of the supported types"
|
|
481
|
+
elif isinstance(field_info, params.Query):
|
|
482
|
+
assert is_scalar_field(field) or is_scalar_sequence_field(field)
|
|
469
483
|
|
|
470
484
|
return ParamDetails(type_annotation=type_annotation, depends=depends, field=field)
|
|
471
485
|
|
|
472
486
|
|
|
473
|
-
def is_body_param(*, param_field: ModelField, is_path_param: bool) -> bool:
|
|
474
|
-
if is_path_param:
|
|
475
|
-
assert is_scalar_field(
|
|
476
|
-
field=param_field
|
|
477
|
-
), "Path params must be of one of the supported types"
|
|
478
|
-
return False
|
|
479
|
-
elif is_scalar_field(field=param_field):
|
|
480
|
-
return False
|
|
481
|
-
elif isinstance(
|
|
482
|
-
param_field.field_info, (params.Query, params.Header)
|
|
483
|
-
) and is_scalar_sequence_field(param_field):
|
|
484
|
-
return False
|
|
485
|
-
else:
|
|
486
|
-
assert isinstance(
|
|
487
|
-
param_field.field_info, params.Body
|
|
488
|
-
), f"Param: {param_field.name} can only be a request body, using Body()"
|
|
489
|
-
return True
|
|
490
|
-
|
|
491
|
-
|
|
492
487
|
def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:
|
|
493
488
|
field_info = field.field_info
|
|
494
489
|
field_info_in = getattr(field_info, "in_", None)
|
|
@@ -557,6 +552,7 @@ async def solve_dependencies(
|
|
|
557
552
|
dependency_overrides_provider: Optional[Any] = None,
|
|
558
553
|
dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], Any]] = None,
|
|
559
554
|
async_exit_stack: AsyncExitStack,
|
|
555
|
+
embed_body_fields: bool,
|
|
560
556
|
) -> SolvedDependency:
|
|
561
557
|
values: Dict[str, Any] = {}
|
|
562
558
|
errors: List[Any] = []
|
|
@@ -598,6 +594,7 @@ async def solve_dependencies(
|
|
|
598
594
|
dependency_overrides_provider=dependency_overrides_provider,
|
|
599
595
|
dependency_cache=dependency_cache,
|
|
600
596
|
async_exit_stack=async_exit_stack,
|
|
597
|
+
embed_body_fields=embed_body_fields,
|
|
601
598
|
)
|
|
602
599
|
background_tasks = solved_result.background_tasks
|
|
603
600
|
dependency_cache.update(solved_result.dependency_cache)
|
|
@@ -640,7 +637,9 @@ async def solve_dependencies(
|
|
|
640
637
|
body_values,
|
|
641
638
|
body_errors,
|
|
642
639
|
) = await request_body_to_args( # body_params checked above
|
|
643
|
-
|
|
640
|
+
body_fields=dependant.body_params,
|
|
641
|
+
received_body=body,
|
|
642
|
+
embed_body_fields=embed_body_fields,
|
|
644
643
|
)
|
|
645
644
|
values.update(body_values)
|
|
646
645
|
errors.extend(body_errors)
|
|
@@ -669,138 +668,194 @@ async def solve_dependencies(
|
|
|
669
668
|
)
|
|
670
669
|
|
|
671
670
|
|
|
671
|
+
def _validate_value_with_model_field(
|
|
672
|
+
*, field: ModelField, value: Any, values: Dict[str, Any], loc: Tuple[str, ...]
|
|
673
|
+
) -> Tuple[Any, List[Any]]:
|
|
674
|
+
if value is None:
|
|
675
|
+
if field.required:
|
|
676
|
+
return None, [get_missing_field_error(loc=loc)]
|
|
677
|
+
else:
|
|
678
|
+
return deepcopy(field.default), []
|
|
679
|
+
v_, errors_ = field.validate(value, values, loc=loc)
|
|
680
|
+
if isinstance(errors_, ErrorWrapper):
|
|
681
|
+
return None, [errors_]
|
|
682
|
+
elif isinstance(errors_, list):
|
|
683
|
+
new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=())
|
|
684
|
+
return None, new_errors
|
|
685
|
+
else:
|
|
686
|
+
return v_, []
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
def _get_multidict_value(field: ModelField, values: Mapping[str, Any]) -> Any:
|
|
690
|
+
if is_sequence_field(field) and isinstance(values, (ImmutableMultiDict, Headers)):
|
|
691
|
+
value = values.getlist(field.alias)
|
|
692
|
+
else:
|
|
693
|
+
value = values.get(field.alias, None)
|
|
694
|
+
if (
|
|
695
|
+
value is None
|
|
696
|
+
or (
|
|
697
|
+
isinstance(field.field_info, params.Form)
|
|
698
|
+
and isinstance(value, str) # For type checks
|
|
699
|
+
and value == ""
|
|
700
|
+
)
|
|
701
|
+
or (is_sequence_field(field) and len(value) == 0)
|
|
702
|
+
):
|
|
703
|
+
if field.required:
|
|
704
|
+
return
|
|
705
|
+
else:
|
|
706
|
+
return deepcopy(field.default)
|
|
707
|
+
return value
|
|
708
|
+
|
|
709
|
+
|
|
672
710
|
def request_params_to_args(
|
|
673
|
-
|
|
711
|
+
fields: Sequence[ModelField],
|
|
674
712
|
received_params: Union[Mapping[str, Any], QueryParams, Headers],
|
|
675
713
|
) -> Tuple[Dict[str, Any], List[Any]]:
|
|
676
|
-
values = {}
|
|
714
|
+
values: Dict[str, Any] = {}
|
|
677
715
|
errors = []
|
|
678
|
-
for field in
|
|
679
|
-
|
|
680
|
-
received_params, (QueryParams, Headers)
|
|
681
|
-
):
|
|
682
|
-
value = received_params.getlist(field.alias) or field.default
|
|
683
|
-
else:
|
|
684
|
-
value = received_params.get(field.alias)
|
|
716
|
+
for field in fields:
|
|
717
|
+
value = _get_multidict_value(field, received_params)
|
|
685
718
|
field_info = field.field_info
|
|
686
719
|
assert isinstance(
|
|
687
720
|
field_info, params.Param
|
|
688
721
|
), "Params must be subclasses of Param"
|
|
689
722
|
loc = (field_info.in_.value, field.alias)
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
continue
|
|
696
|
-
v_, errors_ = field.validate(value, values, loc=loc)
|
|
697
|
-
if isinstance(errors_, ErrorWrapper):
|
|
698
|
-
errors.append(errors_)
|
|
699
|
-
elif isinstance(errors_, list):
|
|
700
|
-
new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=())
|
|
701
|
-
errors.extend(new_errors)
|
|
723
|
+
v_, errors_ = _validate_value_with_model_field(
|
|
724
|
+
field=field, value=value, values=values, loc=loc
|
|
725
|
+
)
|
|
726
|
+
if errors_:
|
|
727
|
+
errors.extend(errors_)
|
|
702
728
|
else:
|
|
703
729
|
values[field.name] = v_
|
|
704
730
|
return values, errors
|
|
705
731
|
|
|
706
732
|
|
|
733
|
+
def _should_embed_body_fields(fields: List[ModelField]) -> bool:
|
|
734
|
+
if not fields:
|
|
735
|
+
return False
|
|
736
|
+
# More than one dependency could have the same field, it would show up as multiple
|
|
737
|
+
# fields but it's the same one, so count them by name
|
|
738
|
+
body_param_names_set = {field.name for field in fields}
|
|
739
|
+
# A top level field has to be a single field, not multiple
|
|
740
|
+
if len(body_param_names_set) > 1:
|
|
741
|
+
return True
|
|
742
|
+
first_field = fields[0]
|
|
743
|
+
# If it explicitly specifies it is embedded, it has to be embedded
|
|
744
|
+
if getattr(first_field.field_info, "embed", None):
|
|
745
|
+
return True
|
|
746
|
+
# If it's a Form (or File) field, it has to be a BaseModel to be top level
|
|
747
|
+
# otherwise it has to be embedded, so that the key value pair can be extracted
|
|
748
|
+
if isinstance(first_field.field_info, params.Form) and not lenient_issubclass(
|
|
749
|
+
first_field.type_, BaseModel
|
|
750
|
+
):
|
|
751
|
+
return True
|
|
752
|
+
return False
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
async def _extract_form_body(
|
|
756
|
+
body_fields: List[ModelField],
|
|
757
|
+
received_body: FormData,
|
|
758
|
+
) -> Dict[str, Any]:
|
|
759
|
+
values = {}
|
|
760
|
+
first_field = body_fields[0]
|
|
761
|
+
first_field_info = first_field.field_info
|
|
762
|
+
|
|
763
|
+
for field in body_fields:
|
|
764
|
+
value = _get_multidict_value(field, received_body)
|
|
765
|
+
if (
|
|
766
|
+
isinstance(first_field_info, params.File)
|
|
767
|
+
and is_bytes_field(field)
|
|
768
|
+
and isinstance(value, UploadFile)
|
|
769
|
+
):
|
|
770
|
+
value = await value.read()
|
|
771
|
+
elif (
|
|
772
|
+
is_bytes_sequence_field(field)
|
|
773
|
+
and isinstance(first_field_info, params.File)
|
|
774
|
+
and value_is_sequence(value)
|
|
775
|
+
):
|
|
776
|
+
# For types
|
|
777
|
+
assert isinstance(value, sequence_types) # type: ignore[arg-type]
|
|
778
|
+
results: List[Union[bytes, str]] = []
|
|
779
|
+
|
|
780
|
+
async def process_fn(
|
|
781
|
+
fn: Callable[[], Coroutine[Any, Any, Any]],
|
|
782
|
+
) -> None:
|
|
783
|
+
result = await fn()
|
|
784
|
+
results.append(result) # noqa: B023
|
|
785
|
+
|
|
786
|
+
async with anyio.create_task_group() as tg:
|
|
787
|
+
for sub_value in value:
|
|
788
|
+
tg.start_soon(process_fn, sub_value.read)
|
|
789
|
+
value = serialize_sequence_value(field=field, value=results)
|
|
790
|
+
if value is not None:
|
|
791
|
+
values[field.name] = value
|
|
792
|
+
return values
|
|
793
|
+
|
|
794
|
+
|
|
707
795
|
async def request_body_to_args(
|
|
708
|
-
|
|
796
|
+
body_fields: List[ModelField],
|
|
709
797
|
received_body: Optional[Union[Dict[str, Any], FormData]],
|
|
798
|
+
embed_body_fields: bool,
|
|
710
799
|
) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
|
|
711
|
-
values = {}
|
|
800
|
+
values: Dict[str, Any] = {}
|
|
712
801
|
errors: List[Dict[str, Any]] = []
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
or (
|
|
742
|
-
isinstance(field_info, params.Form)
|
|
743
|
-
and is_sequence_field(field)
|
|
744
|
-
and len(value) == 0
|
|
745
|
-
)
|
|
746
|
-
):
|
|
747
|
-
if field.required:
|
|
748
|
-
errors.append(get_missing_field_error(loc))
|
|
749
|
-
else:
|
|
750
|
-
values[field.name] = deepcopy(field.default)
|
|
802
|
+
assert body_fields, "request_body_to_args() should be called with fields"
|
|
803
|
+
single_not_embedded_field = len(body_fields) == 1 and not embed_body_fields
|
|
804
|
+
first_field = body_fields[0]
|
|
805
|
+
body_to_process = received_body
|
|
806
|
+
|
|
807
|
+
fields_to_extract: List[ModelField] = body_fields
|
|
808
|
+
|
|
809
|
+
if single_not_embedded_field and lenient_issubclass(first_field.type_, BaseModel):
|
|
810
|
+
fields_to_extract = get_model_fields(first_field.type_)
|
|
811
|
+
|
|
812
|
+
if isinstance(received_body, FormData):
|
|
813
|
+
body_to_process = await _extract_form_body(fields_to_extract, received_body)
|
|
814
|
+
|
|
815
|
+
if single_not_embedded_field:
|
|
816
|
+
loc: Tuple[str, ...] = ("body",)
|
|
817
|
+
v_, errors_ = _validate_value_with_model_field(
|
|
818
|
+
field=first_field, value=body_to_process, values=values, loc=loc
|
|
819
|
+
)
|
|
820
|
+
return {first_field.name: v_}, errors_
|
|
821
|
+
for field in body_fields:
|
|
822
|
+
loc = ("body", field.alias)
|
|
823
|
+
value: Optional[Any] = None
|
|
824
|
+
if body_to_process is not None:
|
|
825
|
+
try:
|
|
826
|
+
value = body_to_process.get(field.alias)
|
|
827
|
+
# If the received body is a list, not a dict
|
|
828
|
+
except AttributeError:
|
|
829
|
+
errors.append(get_missing_field_error(loc))
|
|
751
830
|
continue
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
)
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
is_bytes_sequence_field(field)
|
|
760
|
-
and isinstance(field_info, params.File)
|
|
761
|
-
and value_is_sequence(value)
|
|
762
|
-
):
|
|
763
|
-
# For types
|
|
764
|
-
assert isinstance(value, sequence_types) # type: ignore[arg-type]
|
|
765
|
-
results: List[Union[bytes, str]] = []
|
|
766
|
-
|
|
767
|
-
async def process_fn(
|
|
768
|
-
fn: Callable[[], Coroutine[Any, Any, Any]],
|
|
769
|
-
) -> None:
|
|
770
|
-
result = await fn()
|
|
771
|
-
results.append(result) # noqa: B023
|
|
772
|
-
|
|
773
|
-
async with anyio.create_task_group() as tg:
|
|
774
|
-
for sub_value in value:
|
|
775
|
-
tg.start_soon(process_fn, sub_value.read)
|
|
776
|
-
value = serialize_sequence_value(field=field, value=results)
|
|
777
|
-
|
|
778
|
-
v_, errors_ = field.validate(value, values, loc=loc)
|
|
779
|
-
|
|
780
|
-
if isinstance(errors_, list):
|
|
781
|
-
errors.extend(errors_)
|
|
782
|
-
elif errors_:
|
|
783
|
-
errors.append(errors_)
|
|
784
|
-
else:
|
|
785
|
-
values[field.name] = v_
|
|
831
|
+
v_, errors_ = _validate_value_with_model_field(
|
|
832
|
+
field=field, value=value, values=values, loc=loc
|
|
833
|
+
)
|
|
834
|
+
if errors_:
|
|
835
|
+
errors.extend(errors_)
|
|
836
|
+
else:
|
|
837
|
+
values[field.name] = v_
|
|
786
838
|
return values, errors
|
|
787
839
|
|
|
788
840
|
|
|
789
|
-
def get_body_field(
|
|
790
|
-
flat_dependant
|
|
841
|
+
def get_body_field(
|
|
842
|
+
*, flat_dependant: Dependant, name: str, embed_body_fields: bool
|
|
843
|
+
) -> Optional[ModelField]:
|
|
844
|
+
"""
|
|
845
|
+
Get a ModelField representing the request body for a path operation, combining
|
|
846
|
+
all body parameters into a single field if necessary.
|
|
847
|
+
|
|
848
|
+
Used to check if it's form data (with `isinstance(body_field, params.Form)`)
|
|
849
|
+
or JSON and to generate the JSON Schema for a request body.
|
|
850
|
+
|
|
851
|
+
This is **not** used to validate/parse the request body, that's done with each
|
|
852
|
+
individual body parameter.
|
|
853
|
+
"""
|
|
791
854
|
if not flat_dependant.body_params:
|
|
792
855
|
return None
|
|
793
856
|
first_param = flat_dependant.body_params[0]
|
|
794
|
-
|
|
795
|
-
embed = getattr(field_info, "embed", None)
|
|
796
|
-
body_param_names_set = {param.name for param in flat_dependant.body_params}
|
|
797
|
-
if len(body_param_names_set) == 1 and not embed:
|
|
857
|
+
if not embed_body_fields:
|
|
798
858
|
return first_param
|
|
799
|
-
# If one field requires to embed, all have to be embedded
|
|
800
|
-
# in case a sub-dependency is evaluated with a single unique body field
|
|
801
|
-
# That is combined (embedded) with other body fields
|
|
802
|
-
for param in flat_dependant.body_params:
|
|
803
|
-
setattr(param.field_info, "embed", True) # noqa: B010
|
|
804
859
|
model_name = "Body_" + name
|
|
805
860
|
BodyModel = create_body_model(
|
|
806
861
|
fields=flat_dependant.body_params, model_name=model_name
|
fastapi/param_functions.py
CHANGED
|
@@ -1282,7 +1282,7 @@ def Body( # noqa: N802
|
|
|
1282
1282
|
),
|
|
1283
1283
|
] = _Unset,
|
|
1284
1284
|
embed: Annotated[
|
|
1285
|
-
bool,
|
|
1285
|
+
Union[bool, None],
|
|
1286
1286
|
Doc(
|
|
1287
1287
|
"""
|
|
1288
1288
|
When `embed` is `True`, the parameter will be expected in a JSON body as a
|
|
@@ -1294,7 +1294,7 @@ def Body( # noqa: N802
|
|
|
1294
1294
|
[FastAPI docs for Body - Multiple Parameters](https://fastapi.tiangolo.com/tutorial/body-multiple-params/#embed-a-single-body-parameter).
|
|
1295
1295
|
"""
|
|
1296
1296
|
),
|
|
1297
|
-
] =
|
|
1297
|
+
] = None,
|
|
1298
1298
|
media_type: Annotated[
|
|
1299
1299
|
str,
|
|
1300
1300
|
Doc(
|
fastapi/params.py
CHANGED
|
@@ -479,7 +479,7 @@ class Body(FieldInfo):
|
|
|
479
479
|
*,
|
|
480
480
|
default_factory: Union[Callable[[], Any], None] = _Unset,
|
|
481
481
|
annotation: Optional[Any] = None,
|
|
482
|
-
embed: bool =
|
|
482
|
+
embed: Union[bool, None] = None,
|
|
483
483
|
media_type: str = "application/json",
|
|
484
484
|
alias: Optional[str] = None,
|
|
485
485
|
alias_priority: Union[int, None] = _Unset,
|
|
@@ -642,7 +642,6 @@ class Form(Body):
|
|
|
642
642
|
default=default,
|
|
643
643
|
default_factory=default_factory,
|
|
644
644
|
annotation=annotation,
|
|
645
|
-
embed=True,
|
|
646
645
|
media_type=media_type,
|
|
647
646
|
alias=alias,
|
|
648
647
|
alias_priority=alias_priority,
|
fastapi/routing.py
CHANGED
|
@@ -33,8 +33,10 @@ from fastapi._compat import (
|
|
|
33
33
|
from fastapi.datastructures import Default, DefaultPlaceholder
|
|
34
34
|
from fastapi.dependencies.models import Dependant
|
|
35
35
|
from fastapi.dependencies.utils import (
|
|
36
|
+
_should_embed_body_fields,
|
|
36
37
|
get_body_field,
|
|
37
38
|
get_dependant,
|
|
39
|
+
get_flat_dependant,
|
|
38
40
|
get_parameterless_sub_dependant,
|
|
39
41
|
get_typed_return_annotation,
|
|
40
42
|
solve_dependencies,
|
|
@@ -225,6 +227,7 @@ def get_request_handler(
|
|
|
225
227
|
response_model_exclude_defaults: bool = False,
|
|
226
228
|
response_model_exclude_none: bool = False,
|
|
227
229
|
dependency_overrides_provider: Optional[Any] = None,
|
|
230
|
+
embed_body_fields: bool = False,
|
|
228
231
|
) -> Callable[[Request], Coroutine[Any, Any, Response]]:
|
|
229
232
|
assert dependant.call is not None, "dependant.call must be a function"
|
|
230
233
|
is_coroutine = asyncio.iscoroutinefunction(dependant.call)
|
|
@@ -291,6 +294,7 @@ def get_request_handler(
|
|
|
291
294
|
body=body,
|
|
292
295
|
dependency_overrides_provider=dependency_overrides_provider,
|
|
293
296
|
async_exit_stack=async_exit_stack,
|
|
297
|
+
embed_body_fields=embed_body_fields,
|
|
294
298
|
)
|
|
295
299
|
errors = solved_result.errors
|
|
296
300
|
if not errors:
|
|
@@ -354,7 +358,9 @@ def get_request_handler(
|
|
|
354
358
|
|
|
355
359
|
|
|
356
360
|
def get_websocket_app(
|
|
357
|
-
dependant: Dependant,
|
|
361
|
+
dependant: Dependant,
|
|
362
|
+
dependency_overrides_provider: Optional[Any] = None,
|
|
363
|
+
embed_body_fields: bool = False,
|
|
358
364
|
) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]:
|
|
359
365
|
async def app(websocket: WebSocket) -> None:
|
|
360
366
|
async with AsyncExitStack() as async_exit_stack:
|
|
@@ -367,6 +373,7 @@ def get_websocket_app(
|
|
|
367
373
|
dependant=dependant,
|
|
368
374
|
dependency_overrides_provider=dependency_overrides_provider,
|
|
369
375
|
async_exit_stack=async_exit_stack,
|
|
376
|
+
embed_body_fields=embed_body_fields,
|
|
370
377
|
)
|
|
371
378
|
if solved_result.errors:
|
|
372
379
|
raise WebSocketRequestValidationError(
|
|
@@ -399,11 +406,15 @@ class APIWebSocketRoute(routing.WebSocketRoute):
|
|
|
399
406
|
0,
|
|
400
407
|
get_parameterless_sub_dependant(depends=depends, path=self.path_format),
|
|
401
408
|
)
|
|
402
|
-
|
|
409
|
+
self._flat_dependant = get_flat_dependant(self.dependant)
|
|
410
|
+
self._embed_body_fields = _should_embed_body_fields(
|
|
411
|
+
self._flat_dependant.body_params
|
|
412
|
+
)
|
|
403
413
|
self.app = websocket_session(
|
|
404
414
|
get_websocket_app(
|
|
405
415
|
dependant=self.dependant,
|
|
406
416
|
dependency_overrides_provider=dependency_overrides_provider,
|
|
417
|
+
embed_body_fields=self._embed_body_fields,
|
|
407
418
|
)
|
|
408
419
|
)
|
|
409
420
|
|
|
@@ -544,7 +555,15 @@ class APIRoute(routing.Route):
|
|
|
544
555
|
0,
|
|
545
556
|
get_parameterless_sub_dependant(depends=depends, path=self.path_format),
|
|
546
557
|
)
|
|
547
|
-
self.
|
|
558
|
+
self._flat_dependant = get_flat_dependant(self.dependant)
|
|
559
|
+
self._embed_body_fields = _should_embed_body_fields(
|
|
560
|
+
self._flat_dependant.body_params
|
|
561
|
+
)
|
|
562
|
+
self.body_field = get_body_field(
|
|
563
|
+
flat_dependant=self._flat_dependant,
|
|
564
|
+
name=self.unique_id,
|
|
565
|
+
embed_body_fields=self._embed_body_fields,
|
|
566
|
+
)
|
|
548
567
|
self.app = request_response(self.get_route_handler())
|
|
549
568
|
|
|
550
569
|
def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:
|
|
@@ -561,6 +580,7 @@ class APIRoute(routing.Route):
|
|
|
561
580
|
response_model_exclude_defaults=self.response_model_exclude_defaults,
|
|
562
581
|
response_model_exclude_none=self.response_model_exclude_none,
|
|
563
582
|
dependency_overrides_provider=self.dependency_overrides_provider,
|
|
583
|
+
embed_body_fields=self._embed_body_fields,
|
|
564
584
|
)
|
|
565
585
|
|
|
566
586
|
def matches(self, scope: Scope) -> Tuple[Match, Scope]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fastapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.113.0
|
|
4
4
|
Summary: FastAPI framework, high performance, easy to learn, fast to code, ready for production
|
|
5
5
|
Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= <tiangolo@gmail.com>
|
|
6
6
|
Classifier: Intended Audience :: Information Technology
|
|
@@ -115,7 +115,7 @@ The key features are:
|
|
|
115
115
|
<a href="https://bump.sh/fastapi?utm_source=fastapi&utm_medium=referral&utm_campaign=sponsor" target="_blank" title="Automate FastAPI documentation generation with Bump.sh"><img src="https://fastapi.tiangolo.com/img/sponsors/bump-sh.svg"></a>
|
|
116
116
|
<a href="https://github.com/scalar/scalar/?utm_source=fastapi&utm_medium=website&utm_campaign=main-badge" target="_blank" title="Scalar: Beautiful Open-Source API References from Swagger/OpenAPI files"><img src="https://fastapi.tiangolo.com/img/sponsors/scalar.svg"></a>
|
|
117
117
|
<a href="https://www.propelauth.com/?utm_source=fastapi&utm_campaign=1223&utm_medium=mainbadge" target="_blank" title="Auth, user management and more for your B2B product"><img src="https://fastapi.tiangolo.com/img/sponsors/propelauth.png"></a>
|
|
118
|
-
<a href="https://
|
|
118
|
+
<a href="https://www.withcoherence.com/?utm_medium=advertising&utm_source=fastapi&utm_campaign=website" target="_blank" title="Coherence"><img src="https://fastapi.tiangolo.com/img/sponsors/coherence.png"></a>
|
|
119
119
|
<a href="https://www.mongodb.com/developer/languages/python/python-quickstart-fastapi/?utm_campaign=fastapi_framework&utm_source=fastapi_sponsorship&utm_medium=web_referral" target="_blank" title="Simplify Full Stack Development with FastAPI & MongoDB"><img src="https://fastapi.tiangolo.com/img/sponsors/mongodb.png"></a>
|
|
120
120
|
<a href="https://zuplo.link/fastapi-gh" target="_blank" title="Zuplo: Scale, Protect, Document, and Monetize your FastAPI"><img src="https://fastapi.tiangolo.com/img/sponsors/zuplo.png"></a>
|
|
121
121
|
<a href="https://fine.dev?ref=fastapibadge" target="_blank" title="Fine's AI FastAPI Workflow: Effortlessly Deploy and Integrate FastAPI into Your Project"><img src="https://fastapi.tiangolo.com/img/sponsors/fine.png"></a>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
fastapi-0.
|
|
2
|
-
fastapi-0.
|
|
3
|
-
fastapi-0.
|
|
4
|
-
fastapi-0.
|
|
5
|
-
fastapi/__init__.py,sha256=
|
|
1
|
+
fastapi-0.113.0.dist-info/METADATA,sha256=ii9Gxjj0MqtirULJzkuNcZ6ur3w-NRAvUA1Z0dv_EOY,27227
|
|
2
|
+
fastapi-0.113.0.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
|
|
3
|
+
fastapi-0.113.0.dist-info/entry_points.txt,sha256=Nn2-rs4A5_lQZko2b9QqCKQx9Irx0agGbxq3QLgjBxQ,46
|
|
4
|
+
fastapi-0.113.0.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086
|
|
5
|
+
fastapi/__init__.py,sha256=d4A3W-W3uWnnSbn4daxNgwAvbz-XdCxNGIoAU2Xh68w,1081
|
|
6
6
|
fastapi/__main__.py,sha256=bKePXLdO4SsVSM6r9SVoLickJDcR2c0cTOxZRKq26YQ,37
|
|
7
|
-
fastapi/_compat.py,sha256=
|
|
7
|
+
fastapi/_compat.py,sha256=9AipVKxefipp4ymECk3VhrryBi-Hbh8A7zsljaWdGAk,23723
|
|
8
8
|
fastapi/applications.py,sha256=Ix-o9pQAWhEDf9J0Q1hZ0nBB1uP72c-Y3oiYzvrwqiM,176316
|
|
9
9
|
fastapi/background.py,sha256=rouLirxUANrcYC824MSMypXL_Qb2HYg2YZqaiEqbEKI,1768
|
|
10
10
|
fastapi/cli.py,sha256=OYhZb0NR_deuT5ofyPF2NoNBzZDNOP8Salef2nk-HqA,418
|
|
@@ -12,7 +12,7 @@ fastapi/concurrency.py,sha256=AYLnS4judDUmXsNRICtoKSP0prfYDcS8ehBtYW9JhQQ,1403
|
|
|
12
12
|
fastapi/datastructures.py,sha256=b2PEz77XGq-u3Ur1Inwk0AGjOsQZO49yF9C7IPJ15cY,5766
|
|
13
13
|
fastapi/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
fastapi/dependencies/models.py,sha256=Pjl6vx-4nZ5Tta9kJa3-RfQKkXtCpS09-FhMgs9eWNs,1507
|
|
15
|
-
fastapi/dependencies/utils.py,sha256=
|
|
15
|
+
fastapi/dependencies/utils.py,sha256=EGlTiaoExyqTVLYEuQnHa8NDfuf37YNRs-gU2hiwlxo,32352
|
|
16
16
|
fastapi/encoders.py,sha256=LvwYmFeOz4tVwvgBoC5rvZnbr7hZr73KGrU8O7zSptU,11068
|
|
17
17
|
fastapi/exception_handlers.py,sha256=MBrIOA-ugjJDivIi4rSsUJBdTsjuzN76q4yh0q1COKw,1332
|
|
18
18
|
fastapi/exceptions.py,sha256=taNixuFEXb67lI1bnX1ubq8y8TseJ4yoPlWjyP0fTzk,4969
|
|
@@ -28,12 +28,12 @@ fastapi/openapi/constants.py,sha256=adGzmis1L1HJRTE3kJ5fmHS_Noq6tIY6pWv_SFzoFDU,
|
|
|
28
28
|
fastapi/openapi/docs.py,sha256=XcQq-ZbQdC5sI0gIGu5MoHK1q-OFaqws7-ORTo6sjY4,10348
|
|
29
29
|
fastapi/openapi/models.py,sha256=PqkxQiqcEgjKuhfUIWPZPQcyTcubtUCB3vcObLsB7VE,15397
|
|
30
30
|
fastapi/openapi/utils.py,sha256=asSbOKDuagDfpByNQvPy7OM0sqOBdUmqh64BH-n-5f0,22286
|
|
31
|
-
fastapi/param_functions.py,sha256=
|
|
32
|
-
fastapi/params.py,sha256=
|
|
31
|
+
fastapi/param_functions.py,sha256=uQzNlihlhM80u4Xbstz__D3L3yxpTqLmsC-Hra1WfqE,64018
|
|
32
|
+
fastapi/params.py,sha256=e_ZHoUohqWWbquiBL5AHqdsKJmTMLZzAPii6eCYR3tk,28187
|
|
33
33
|
fastapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
34
|
fastapi/requests.py,sha256=zayepKFcienBllv3snmWI20Gk0oHNVLU4DDhqXBb4LU,142
|
|
35
35
|
fastapi/responses.py,sha256=QNQQlwpKhQoIPZTTWkpc9d_QGeGZ_aVQPaDV3nQ8m7c,1761
|
|
36
|
-
fastapi/routing.py,sha256=
|
|
36
|
+
fastapi/routing.py,sha256=LnYhRsafzHporfz4aDUmEk8qDU2tarLZNjHaKy3RB7w,176148
|
|
37
37
|
fastapi/security/__init__.py,sha256=bO8pNmxqVRXUjfl2mOKiVZLn0FpBQ61VUYVjmppnbJw,881
|
|
38
38
|
fastapi/security/api_key.py,sha256=_OqUUjEHG5_MT1IPAhXIGJRCPldTBdSww_DegFy_W8Y,9368
|
|
39
39
|
fastapi/security/base.py,sha256=dl4pvbC-RxjfbWgPtCWd8MVU-7CB2SZ22rJDXVCXO6c,141
|
|
@@ -47,4 +47,4 @@ fastapi/testclient.py,sha256=nBvaAmX66YldReJNZXPOk1sfuo2Q6hs8bOvIaCep6LQ,66
|
|
|
47
47
|
fastapi/types.py,sha256=nFb36sK3DSoqoyo7Miwy3meKK5UdFBgkAgLSzQlUVyI,383
|
|
48
48
|
fastapi/utils.py,sha256=y8Bj5ttMaI9tS4D60OUgXqKnktBr99NdYUnHHV9LgoY,7948
|
|
49
49
|
fastapi/websockets.py,sha256=419uncYObEKZG0YcrXscfQQYLSWoE10jqxVMetGdR98,222
|
|
50
|
-
fastapi-0.
|
|
50
|
+
fastapi-0.113.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|