Allow Response type hint as dependency annotation (#14794)

Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
This commit is contained in:
Jonathan Fulton
2026-02-05 13:23:16 -05:00
committed by GitHub
parent 464c359bb0
commit b49435becd
2 changed files with 176 additions and 2 deletions

View File

@@ -449,7 +449,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,
@@ -460,7 +462,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}"
)

View File

@@ -0,0 +1,173 @@
"""Test using special types (Response, Request, BackgroundTasks) as dependency annotations.
These tests verify that special FastAPI types can be used with Depends() annotations
and that the dependency injection system properly handles them.
"""
from typing import Annotated
from fastapi import BackgroundTasks, Depends, FastAPI, Request, Response
from fastapi.responses import JSONResponse
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"
def test_response_dependency_returns_different_response_instance():
"""Dependency that returns a different Response instance should work.
When a dependency returns a new Response object (e.g., JSONResponse) instead
of modifying the injected one, the returned response should be used and any
modifications to it in the endpoint should be preserved.
"""
app = FastAPI()
def default_response() -> Response:
response = JSONResponse(content={"status": "ok"})
response.headers["X-Custom"] = "initial"
return response
@app.get("/")
def endpoint(response: Annotated[Response, Depends(default_response)]):
response.headers["X-Custom"] = "modified"
return response
client = TestClient(app)
resp = client.get("/")
assert resp.status_code == 200
assert resp.json() == {"status": "ok"}
assert resp.headers.get("X-Custom") == "modified"
# Tests for Request type hint with Depends
def test_request_with_depends_annotated():
"""Request type hint should work in dependency chain."""
app = FastAPI()
def extract_request_info(request: Request) -> dict:
return {
"path": request.url.path,
"user_agent": request.headers.get("user-agent", "unknown"),
}
@app.get("/")
def endpoint(
info: Annotated[dict, Depends(extract_request_info)],
):
return info
client = TestClient(app)
resp = client.get("/", headers={"user-agent": "test-agent"})
assert resp.status_code == 200
assert resp.json() == {"path": "/", "user_agent": "test-agent"}
# Tests for BackgroundTasks type hint with Depends
def test_background_tasks_with_depends_annotated():
"""BackgroundTasks type hint should work with Annotated[BackgroundTasks, Depends(...)]."""
app = FastAPI()
task_results = []
def background_task(message: str):
task_results.append(message)
def add_background_task(background_tasks: BackgroundTasks) -> BackgroundTasks:
background_tasks.add_task(background_task, "from dependency")
return background_tasks
@app.get("/")
def endpoint(
background_tasks: Annotated[BackgroundTasks, Depends(add_background_task)],
):
background_tasks.add_task(background_task, "from endpoint")
return {"status": "ok"}
client = TestClient(app)
resp = client.get("/")
assert resp.status_code == 200
assert "from dependency" in task_results
assert "from endpoint" in task_results