From 0253ac7273c2fc9c28b32bd8791942f24531b1ab Mon Sep 17 00:00:00 2001 From: ProfiAnton Date: Thu, 16 Oct 2025 02:57:36 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Support=20Annotated=20dependencies?= =?UTF-8?q?=20in=20APIRoute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/applications.py | 28 ++++----- fastapi/routing.py | 63 ++++++++++++------- .../test_apirouter_annotated_dependencies.py | 4 +- 3 files changed, 57 insertions(+), 38 deletions(-) diff --git a/fastapi/applications.py b/fastapi/applications.py index 7379d92ebb..38150eb287 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -333,7 +333,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends | Any]], + Optional[Sequence[Depends | routing.AnnotatedType]], Doc( """ A list of global dependencies, they will be applied to each @@ -1141,7 +1141,7 @@ class FastAPI(Starlette): response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[Depends]] = None, + dependencies: Optional[Sequence[Depends | routing.AnnotatedType]] = None, summary: Optional[str] = None, description: Optional[str] = None, response_description: str = "Successful Response", @@ -1199,7 +1199,7 @@ class FastAPI(Starlette): response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[Depends]] = None, + dependencies: Optional[Sequence[Depends | routing.AnnotatedType]] = None, summary: Optional[str] = None, description: Optional[str] = None, response_description: str = "Successful Response", @@ -1258,7 +1258,7 @@ class FastAPI(Starlette): endpoint: Callable[..., Any], name: Optional[str] = None, *, - dependencies: Optional[Sequence[Depends]] = None, + dependencies: Optional[Sequence[Depends | routing.AnnotatedType]] = None, ) -> None: self.router.add_api_websocket_route( path, @@ -1287,7 +1287,7 @@ class FastAPI(Starlette): ] = None, *, dependencies: Annotated[ - Optional[Sequence[Depends]], + Optional[Sequence[Depends | routing.AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be used for this @@ -1352,7 +1352,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Optional[Sequence[Depends | routing.AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to all the @@ -1611,7 +1611,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Optional[Sequence[Depends | routing.AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -1984,7 +1984,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Optional[Sequence[Depends | routing.AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -2362,7 +2362,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Optional[Sequence[Depends | routing.AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -2740,7 +2740,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Optional[Sequence[Depends | routing.AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3113,7 +3113,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Optional[Sequence[Depends | routing.AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3486,7 +3486,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Optional[Sequence[Depends | routing.AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3859,7 +3859,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Optional[Sequence[Depends | routing.AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -4237,7 +4237,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Optional[Sequence[Depends | routing.AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the diff --git a/fastapi/routing.py b/fastapi/routing.py index 47b9240f63..5f1e2dcc01 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -17,6 +17,7 @@ from typing import ( List, Mapping, Optional, + Protocol, Sequence, Set, Tuple, @@ -464,6 +465,10 @@ def get_websocket_app( return app +class AnnotatedType(Protocol): + """this forces mypy to check if Any is handled correctly""" + + class APIWebSocketRoute(routing.WebSocketRoute): def __init__( self, @@ -471,13 +476,15 @@ class APIWebSocketRoute(routing.WebSocketRoute): endpoint: Callable[..., Any], *, name: Optional[str] = None, - dependencies: Optional[Sequence[params.Depends]] = None, + dependencies: Optional[Sequence[params.Depends | AnnotatedType]] = None, dependency_overrides_provider: Optional[Any] = None, ) -> None: self.path = path self.endpoint = endpoint self.name = get_name(endpoint) if name is None else name - self.dependencies = list(dependencies or []) + self.dependencies = [ + get_depends_from_annotated(dep) for dep in dependencies or [] + ] self.path_regex, self.path_format, self.param_convertors = compile_path(path) self.dependant = get_dependant(path=self.path_format, call=self.endpoint) for depends in self.dependencies[::-1]: @@ -513,7 +520,7 @@ class APIRoute(routing.Route): response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[params.Depends]] = None, + dependencies: Optional[Sequence[params.Depends | AnnotatedType]] = None, summary: Optional[str] = None, description: Optional[str] = None, response_description: str = "Successful Response", @@ -606,7 +613,9 @@ class APIRoute(routing.Route): else: self.response_field = None # type: ignore self.secure_cloned_response_field = None - self.dependencies = list(dependencies or []) + self.dependencies = [ + get_depends_from_annotated(dep) for dep in dependencies or [] + ] self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "") # if a "form feed" character (page break) is found in the description text, # truncate description text to the content preceding the first "form feed" @@ -671,7 +680,7 @@ class APIRoute(routing.Route): return match, child_scope -def get_depends_from_annotated(dep: params.Depends | Any) -> params.Depends: +def get_depends_from_annotated(dep: params.Depends | AnnotatedType) -> params.Depends: if isinstance(dep, params.Depends): return dep d = analyze_param( @@ -733,7 +742,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends | Any]], + Optional[Sequence[params.Depends | AnnotatedType]], Doc( """ A list of dependencies to be applied to all the *path operations* in @@ -987,7 +996,7 @@ class APIRouter(routing.Router): response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[params.Depends]] = None, + dependencies: Optional[Sequence[params.Depends | AnnotatedType]] = None, summary: Optional[str] = None, description: Optional[str] = None, response_description: str = "Successful Response", @@ -1024,7 +1033,9 @@ class APIRouter(routing.Router): current_tags.extend(tags) current_dependencies = self.dependencies.copy() if dependencies: - current_dependencies.extend(dependencies) + current_dependencies.extend( + [get_depends_from_annotated(dep) for dep in dependencies] + ) current_callbacks = self.callbacks.copy() if callbacks: current_callbacks.extend(callbacks) @@ -1068,7 +1079,7 @@ class APIRouter(routing.Router): response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[params.Depends]] = None, + dependencies: Optional[Sequence[params.Depends | AnnotatedType]] = None, summary: Optional[str] = None, description: Optional[str] = None, response_description: str = "Successful Response", @@ -1129,11 +1140,13 @@ class APIRouter(routing.Router): endpoint: Callable[..., Any], name: Optional[str] = None, *, - dependencies: Optional[Sequence[params.Depends]] = None, + dependencies: Optional[Sequence[params.Depends | AnnotatedType]] = None, ) -> None: current_dependencies = self.dependencies.copy() if dependencies: - current_dependencies.extend(dependencies) + current_dependencies.extend( + [get_depends_from_annotated(dep) for dep in dependencies] + ) route = APIWebSocketRoute( self.prefix + path, @@ -1164,7 +1177,7 @@ class APIRouter(routing.Router): ] = None, *, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Optional[Sequence[params.Depends | AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be used for this @@ -1240,7 +1253,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Optional[Sequence[params.Depends | AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to all the @@ -1386,7 +1399,9 @@ class APIRouter(routing.Router): current_tags.extend(route.tags) current_dependencies: List[params.Depends] = [] if dependencies: - current_dependencies.extend(dependencies) + current_dependencies.extend( + [get_depends_from_annotated(dep) for dep in dependencies] + ) if route.dependencies: current_dependencies.extend(route.dependencies) current_callbacks = [] @@ -1442,7 +1457,9 @@ class APIRouter(routing.Router): elif isinstance(route, APIWebSocketRoute): current_dependencies = [] if dependencies: - current_dependencies.extend(dependencies) + current_dependencies.extend( + [get_depends_from_annotated(dep) for dep in dependencies] + ) if route.dependencies: current_dependencies.extend(route.dependencies) self.add_api_websocket_route( @@ -1538,7 +1555,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Optional[Sequence[params.Depends | AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -1915,7 +1932,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Optional[Sequence[params.Depends | AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -2297,7 +2314,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Optional[Sequence[params.Depends | AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -2679,7 +2696,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Optional[Sequence[params.Depends | AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3056,7 +3073,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Optional[Sequence[params.Depends | AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3433,7 +3450,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Optional[Sequence[params.Depends | AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3815,7 +3832,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Optional[Sequence[params.Depends | AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -4197,7 +4214,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Optional[Sequence[params.Depends | AnnotatedType]], Doc( """ A list of dependencies (using `Depends()`) to be applied to the diff --git a/tests/test_apirouter_annotated_dependencies.py b/tests/test_apirouter_annotated_dependencies.py index bcf63e2316..5fbe0a7221 100644 --- a/tests/test_apirouter_annotated_dependencies.py +++ b/tests/test_apirouter_annotated_dependencies.py @@ -10,7 +10,7 @@ def get_value() -> int: ValueDep = Annotated[int, Depends(get_value)] -router = APIRouter(dependencies=[ValueDep, Depends(lambda: "sdfgh")]) +router = APIRouter(dependencies=[Depends(lambda: "sdfgh"), ValueDep]) @router.get("/") @@ -26,6 +26,8 @@ def no_dep(): app = FastAPI() app.include_router(router) +router.post("/", dependencies=[Depends(lambda: None)]) + def test_apirouter_annotated_dependencies(): client = TestClient(app)