Browse Source

️ Avoid flattening dependencies for body fields (#16071)

pull/16072/head
Sebastián Ramírez 1 week ago
committed by GitHub
parent
commit
23e4a10434
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 32
      fastapi/dependencies/utils.py
  2. 18
      fastapi/routing.py

32
fastapi/dependencies/utils.py

@ -219,6 +219,16 @@ def get_flat_dependant(
return flat_dependant return flat_dependant
def _get_flat_body_params(dependant: Dependant) -> list[ModelField]:
body_params: list[ModelField] = []
dependants = [dependant]
while dependants:
current_dependant = dependants.pop()
body_params.extend(current_dependant.body_params)
dependants.extend(reversed(current_dependant.dependencies))
return body_params
def _get_flat_fields_from_params(fields: list[ModelField]) -> list[ModelField]: def _get_flat_fields_from_params(fields: list[ModelField]) -> list[ModelField]:
if not fields: if not fields:
return fields return fields
@ -1043,8 +1053,8 @@ async def request_body_to_args(
return values, errors return values, errors
def get_body_field( def _get_body_field(
*, flat_dependant: Dependant, name: str, embed_body_fields: bool *, body_params: list[ModelField], name: str, embed_body_fields: bool
) -> ModelField | None: ) -> ModelField | None:
""" """
Get a ModelField representing the request body for a path operation, combining Get a ModelField representing the request body for a path operation, combining
@ -1056,34 +1066,30 @@ def get_body_field(
This is **not** used to validate/parse the request body, that's done with each This is **not** used to validate/parse the request body, that's done with each
individual body parameter. individual body parameter.
""" """
if not flat_dependant.body_params: if not body_params:
return None return None
first_param = flat_dependant.body_params[0] first_param = body_params[0]
if not embed_body_fields: if not embed_body_fields:
return first_param return first_param
model_name = "Body_" + name model_name = "Body_" + name
BodyModel = create_body_model( BodyModel = create_body_model(fields=body_params, model_name=model_name)
fields=flat_dependant.body_params, model_name=model_name required = any(True for f in body_params if f.field_info.is_required())
)
required = any(
True for f in flat_dependant.body_params if f.field_info.is_required()
)
BodyFieldInfo_kwargs: dict[str, Any] = { BodyFieldInfo_kwargs: dict[str, Any] = {
"annotation": BodyModel, "annotation": BodyModel,
"alias": "body", "alias": "body",
} }
if not required: if not required:
BodyFieldInfo_kwargs["default"] = None BodyFieldInfo_kwargs["default"] = None
if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params): if any(isinstance(f.field_info, params.File) for f in body_params):
BodyFieldInfo: type[params.Body] = params.File BodyFieldInfo: type[params.Body] = params.File
elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params): elif any(isinstance(f.field_info, params.Form) for f in body_params):
BodyFieldInfo = params.Form BodyFieldInfo = params.Form
else: else:
BodyFieldInfo = params.Body BodyFieldInfo = params.Body
body_param_media_types = [ body_param_media_types = [
f.field_info.media_type f.field_info.media_type
for f in flat_dependant.body_params for f in body_params
if isinstance(f.field_info, params.Body) if isinstance(f.field_info, params.Body)
] ]
if len(set(body_param_media_types)) == 1: if len(set(body_param_media_types)) == 1:

18
fastapi/routing.py

@ -55,10 +55,10 @@ from fastapi.dependencies.models import (
_is_gen_callable, _is_gen_callable,
) )
from fastapi.dependencies.utils import ( from fastapi.dependencies.utils import (
_get_body_field,
_get_flat_body_params,
_should_embed_body_fields, _should_embed_body_fields,
get_body_field,
get_dependant, get_dependant,
get_flat_dependant,
get_parameterless_sub_dependant, get_parameterless_sub_dependant,
get_stream_item_type, get_stream_item_type,
get_typed_return_annotation, get_typed_return_annotation,
@ -849,16 +849,16 @@ def _build_dependant_with_parameterless_dependencies(
path: str, path: str,
call: Callable[..., Any], call: Callable[..., Any],
dependencies: Sequence[params.Depends], dependencies: Sequence[params.Depends],
) -> tuple[Dependant, Dependant, bool]: ) -> tuple[Dependant, list[ModelField], bool]:
dependant = get_dependant(path=path, call=call, scope="function") dependant = get_dependant(path=path, call=call, scope="function")
for depends in dependencies[::-1]: for depends in dependencies[::-1]:
dependant.dependencies.insert( dependant.dependencies.insert(
0, 0,
get_parameterless_sub_dependant(depends=depends, path=path), get_parameterless_sub_dependant(depends=depends, path=path),
) )
flat_dependant = get_flat_dependant(dependant) body_params = _get_flat_body_params(dependant)
embed_body_fields = _should_embed_body_fields(flat_dependant.body_params) embed_body_fields = _should_embed_body_fields(body_params)
return dependant, flat_dependant, embed_body_fields return dependant, body_params, embed_body_fields
class _RouteWithPath(Protocol): class _RouteWithPath(Protocol):
@ -1090,15 +1090,15 @@ def _populate_api_route_state(
assert callable(endpoint), "An endpoint must be a callable" assert callable(endpoint), "An endpoint must be a callable"
( (
route.dependant, route.dependant,
flat_dependant, body_params,
route._embed_body_fields, route._embed_body_fields,
) = _build_dependant_with_parameterless_dependencies( ) = _build_dependant_with_parameterless_dependencies(
path=route.path_format, path=route.path_format,
call=route.endpoint, call=route.endpoint,
dependencies=route.dependencies, dependencies=route.dependencies,
) )
route.body_field = get_body_field( route.body_field = _get_body_field(
flat_dependant=flat_dependant, body_params=body_params,
name=route.unique_id, name=route.unique_id,
embed_body_fields=route._embed_body_fields, embed_body_fields=route._embed_body_fields,
) )

Loading…
Cancel
Save