From afa7461f287fb5709b1bad701587e4af9c3eaacd Mon Sep 17 00:00:00 2001 From: ProfiAnton Date: Mon, 13 Oct 2025 19:16:01 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Support=20Annotated=20dependencies?= =?UTF-8?q?=20in=20APIRouter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/routing.py | 29 ++++++++++++-- .../test_apirouter_annotated_dependencies.py | 38 +++++++++++++++++++ 2 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 tests/test_apirouter_annotated_dependencies.py diff --git a/fastapi/routing.py b/fastapi/routing.py index fe25d7dec..907511081 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -37,6 +37,7 @@ from fastapi.datastructures import Default, DefaultPlaceholder from fastapi.dependencies.models import Dependant from fastapi.dependencies.utils import ( _should_embed_body_fields, + analyze_param, get_body_field, get_dependant, get_flat_dependant, @@ -670,6 +671,22 @@ class APIRoute(routing.Route): return match, child_scope +def get_api_router_dep(dep: params.Depends | Any) -> params.Depends: + if isinstance(dep, params.Depends): + return dep + d = analyze_param( + param_name="APIRouter Dependency", + annotation=dep, + value=inspect.Signature.empty, + is_path_param=False, + ).depends + + assert d is not None, ( + "APIRouter dependency must be a Depends or be Annotated with Depends" + ) + return d + + class APIRouter(routing.Router): """ `APIRouter` class, used to group *path operations*, for example to structure @@ -716,11 +733,15 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Optional[Sequence[params.Depends | Any]], Doc( """ - A list of dependencies (using `Depends()`) to be applied to all the - *path operations* in this router. + A list of dependencies to be applied to all the *path operations* in + this router. + + Dependencies can be provided as `Depends(...)` instances, or using + `Annotated[..., Depends(...)]`. FastAPI will analyze and convert + these automatically when the router is included. Read more about it in the [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). @@ -927,7 +948,7 @@ class APIRouter(routing.Router): ) self.prefix = prefix self.tags: List[Union[str, Enum]] = tags or [] - self.dependencies = list(dependencies or []) + self.dependencies = [get_api_router_dep(dep) for dep in dependencies or []] self.deprecated = deprecated self.include_in_schema = include_in_schema self.responses = responses or {} diff --git a/tests/test_apirouter_annotated_dependencies.py b/tests/test_apirouter_annotated_dependencies.py new file mode 100644 index 000000000..bcf63e231 --- /dev/null +++ b/tests/test_apirouter_annotated_dependencies.py @@ -0,0 +1,38 @@ +from fastapi import APIRouter, Depends, FastAPI +from fastapi.testclient import TestClient +from typing_extensions import Annotated + + +def get_value() -> int: + return 1 + + +ValueDep = Annotated[int, Depends(get_value)] + + +router = APIRouter(dependencies=[ValueDep, Depends(lambda: "sdfgh")]) + + +@router.get("/") +def read_root(dep: ValueDep): + return {"dep": dep} + + +@router.get("/no_dep") +def no_dep(): + return {"status": 200} + + +app = FastAPI() +app.include_router(router) + + +def test_apirouter_annotated_dependencies(): + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"dep": 1} + + response = client.get("/no_dep") + assert response.status_code == 200 + assert response.json() == {"status": 200}