]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
⚡️ Avoid flattening dependencies for body fields (#16071)
authorSebastián Ramírez <tiangolo@gmail.com>
Mon, 27 Jul 2026 15:52:55 +0000 (17:52 +0200)
committerGitHub <noreply@github.com>
Mon, 27 Jul 2026 15:52:55 +0000 (15:52 +0000)
fastapi/dependencies/utils.py
fastapi/routing.py

index 88f68958b487a6bd7d63f3690cc115fcf9063ada..279926939781693d0e2de9bd2091100056d36a1a 100644 (file)
@@ -219,6 +219,16 @@ def get_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]:
     if not fields:
         return fields
@@ -1043,8 +1053,8 @@ async def request_body_to_args(
     return values, errors
 
 
-def get_body_field(
-    *, flat_dependant: Dependant, name: str, embed_body_fields: bool
+def _get_body_field(
+    *, body_params: list[ModelField], name: str, embed_body_fields: bool
 ) -> ModelField | None:
     """
     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
     individual body parameter.
     """
-    if not flat_dependant.body_params:
+    if not body_params:
         return None
-    first_param = flat_dependant.body_params[0]
+    first_param = body_params[0]
     if not embed_body_fields:
         return first_param
     model_name = "Body_" + name
-    BodyModel = create_body_model(
-        fields=flat_dependant.body_params, model_name=model_name
-    )
-    required = any(
-        True for f in flat_dependant.body_params if f.field_info.is_required()
-    )
+    BodyModel = create_body_model(fields=body_params, model_name=model_name)
+    required = any(True for f in body_params if f.field_info.is_required())
     BodyFieldInfo_kwargs: dict[str, Any] = {
         "annotation": BodyModel,
         "alias": "body",
     }
     if not required:
         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
-    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
     else:
         BodyFieldInfo = params.Body
 
         body_param_media_types = [
             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 len(set(body_param_media_types)) == 1:
index 125ab51ae7be2aca2629e04011ae2291c85e620c..2cecfce7c99d4ab411463dea86931620cc5865e7 100644 (file)
@@ -55,10 +55,10 @@ from fastapi.dependencies.models import (
     _is_gen_callable,
 )
 from fastapi.dependencies.utils import (
+    _get_body_field,
+    _get_flat_body_params,
     _should_embed_body_fields,
-    get_body_field,
     get_dependant,
-    get_flat_dependant,
     get_parameterless_sub_dependant,
     get_stream_item_type,
     get_typed_return_annotation,
@@ -849,16 +849,16 @@ def _build_dependant_with_parameterless_dependencies(
     path: str,
     call: Callable[..., Any],
     dependencies: Sequence[params.Depends],
-) -> tuple[Dependant, Dependant, bool]:
+) -> tuple[Dependant, list[ModelField], bool]:
     dependant = get_dependant(path=path, call=call, scope="function")
     for depends in dependencies[::-1]:
         dependant.dependencies.insert(
             0,
             get_parameterless_sub_dependant(depends=depends, path=path),
         )
-    flat_dependant = get_flat_dependant(dependant)
-    embed_body_fields = _should_embed_body_fields(flat_dependant.body_params)
-    return dependant, flat_dependant, embed_body_fields
+    body_params = _get_flat_body_params(dependant)
+    embed_body_fields = _should_embed_body_fields(body_params)
+    return dependant, body_params, embed_body_fields
 
 
 class _RouteWithPath(Protocol):
@@ -1090,15 +1090,15 @@ def _populate_api_route_state(
     assert callable(endpoint), "An endpoint must be a callable"
     (
         route.dependant,
-        flat_dependant,
+        body_params,
         route._embed_body_fields,
     ) = _build_dependant_with_parameterless_dependencies(
         path=route.path_format,
         call=route.endpoint,
         dependencies=route.dependencies,
     )
-    route.body_field = get_body_field(
-        flat_dependant=flat_dependant,
+    route.body_field = _get_body_field(
+        body_params=body_params,
         name=route.unique_id,
         embed_body_fields=route._embed_body_fields,
     )