From 6b782a8f9f1a36a7f867ba51ce2179a5ae306855 Mon Sep 17 00:00:00 2001 From: Jonathan Fulton Date: Sat, 31 Jan 2026 19:04:51 -0500 Subject: [PATCH] fix: allow Response type hint as dependency annotation (#10127) Previously, using Response as a type hint with Depends would fail: def endpoint(response: Annotated[Response, Depends(modify_response)]): This was due to an overly strict assertion that prevented Depends from being used with Response type annotations. The fix moves the Depends check before the special type handling, so when a Depends is specified, the dependency is called normally and its return value used, rather than applying the special Response injection logic. This allows patterns like: - response: Annotated[Response, Depends(modify_response)] - response: Response = Depends(modify_response) While still supporting regular Response injection: - response: Response Fixes #10127 --- fastapi/dependencies/utils.py | 5 +- tests/test_response_dependency.py | 89 +++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 tests/test_response_dependency.py diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 45e1ff3ed..ef8549b92 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -444,7 +444,9 @@ def analyze_param( depends = dataclasses.replace(depends, dependency=type_annotation) # Handle non-param type annotations like Request - if lenient_issubclass( + # Only apply special handling when there's no explicit Depends - if there's a Depends, + # the dependency will be called and its return value used instead of the special injection + if depends is None and lenient_issubclass( type_annotation, ( Request, @@ -455,7 +457,6 @@ def analyze_param( SecurityScopes, ), ): - assert depends is None, f"Cannot specify `Depends` for type {type_annotation!r}" assert field_info is None, ( f"Cannot specify FastAPI annotation for type {type_annotation!r}" ) diff --git a/tests/test_response_dependency.py b/tests/test_response_dependency.py new file mode 100644 index 000000000..1c600e484 --- /dev/null +++ b/tests/test_response_dependency.py @@ -0,0 +1,89 @@ +"""Test using Response type hint as dependency annotation.""" + +from typing import Annotated + +from fastapi import Depends, FastAPI, Response +from fastapi.testclient import TestClient + + +def test_response_with_depends_annotated(): + """Response type hint should work with Annotated[Response, Depends(...)].""" + app = FastAPI() + + def modify_response(response: Response) -> Response: + response.headers["X-Custom"] = "modified" + return response + + @app.get("/") + def endpoint(response: Annotated[Response, Depends(modify_response)]): + return {"status": "ok"} + + client = TestClient(app) + resp = client.get("/") + + assert resp.status_code == 200 + assert resp.json() == {"status": "ok"} + assert resp.headers.get("X-Custom") == "modified" + + +def test_response_with_depends_default(): + """Response type hint should work with Response = Depends(...).""" + app = FastAPI() + + def modify_response(response: Response) -> Response: + response.headers["X-Custom"] = "modified" + return response + + @app.get("/") + def endpoint(response: Response = Depends(modify_response)): + return {"status": "ok"} + + client = TestClient(app) + resp = client.get("/") + + assert resp.status_code == 200 + assert resp.json() == {"status": "ok"} + assert resp.headers.get("X-Custom") == "modified" + + +def test_response_without_depends(): + """Regular Response injection should still work.""" + app = FastAPI() + + @app.get("/") + def endpoint(response: Response): + response.headers["X-Direct"] = "set" + return {"status": "ok"} + + client = TestClient(app) + resp = client.get("/") + + assert resp.status_code == 200 + assert resp.json() == {"status": "ok"} + assert resp.headers.get("X-Direct") == "set" + + +def test_response_dependency_chain(): + """Response dependency should work in a chain of dependencies.""" + app = FastAPI() + + def first_modifier(response: Response) -> Response: + response.headers["X-First"] = "1" + return response + + def second_modifier( + response: Annotated[Response, Depends(first_modifier)], + ) -> Response: + response.headers["X-Second"] = "2" + return response + + @app.get("/") + def endpoint(response: Annotated[Response, Depends(second_modifier)]): + return {"status": "ok"} + + client = TestClient(app) + resp = client.get("/") + + assert resp.status_code == 200 + assert resp.headers.get("X-First") == "1" + assert resp.headers.get("X-Second") == "2"