FastAPI Dependency Injection: Internal Requests, Request Object, and Parameterized Depends
FastAPI's Dependency Injection (DI) system is one of its most powerful architectural features. It allows developers to encapsulate authentication, parameter validation, database sessions, and low-level HTTP inspection into reusable, composable units.
This guide provides a comprehensive manual on advanced FastAPI dependency injection patterns: executing in-process internal route requests with TestClient, parameterizing dependencies with factories, accessing the Starlette Request object, nesting dependency resolution chains, and leveraging class-based dependencies.
In-Process Internal Route Requests with TestClientโ
When one route needs to call another route within the same application (e.g. aggregating data from a POST calculation endpoint into a GET reporting handler), calling the handler function directly bypasses middleware, dependency resolution, and Pydantic request parsing.
The idiomatic approach is using TestClient (from starlette.testclient or httpx), which executes requests in-process without network overhead.
Implementation: Target POST Route and Internal Clientโ
from fastapi import FastAPI, HTTPException, Body, Depends
from fastapi.testclient import TestClient
from typing import Annotated
app = FastAPI()
# 1. Target POST endpoint with body validation
@app.post("/api/file-lookup")
async def file_lookup_handler(id_code: Annotated[str, Body(embed=True)]):
database = {
"A001": {"path": "/data/reports/a001.pdf", "status": "active"},
"B002": {"path": "/data/exports/b002.csv", "status": "archived"}
}
if id_code in database:
return database[id_code]
raise HTTPException(status_code=404, detail=f"File identifier '{id_code}' not found.")
# 2. In-process internal client instance
internal_client = TestClient(app)
# 3. Initiator GET endpoint making internal call
@app.get("/system/reports/{report_id}")
async def fetch_report(report_id: str):
response = internal_client.post(
"/api/file-lookup",
json={"id_code": report_id}
)
if response.status_code == 200:
return {"report_id": report_id, "meta": response.json()}
if response.status_code == 404:
raise HTTPException(status_code=404, detail="Requested report does not exist.")
raise HTTPException(status_code=response.status_code, detail="Internal routing failure.")
Accessing the Raw Starlette Request in Dependenciesโ
When dependencies need raw HTTP connection data (client IP, security headers, cookies, or SSL metadata), declaring request: Request automatically injects the Starlette request instance:
from fastapi import FastAPI, Depends, Request, HTTPException
app = FastAPI()
def verify_internal_subnet(request: Request) -> str:
client_ip = request.client.host if request.client else "unknown"
forwarded_for = request.headers.get("x-forwarded-for")
effective_ip = forwarded_for.split(",")[0].strip() if forwarded_for else client_ip
# Validate IP against private address spaces
if not (effective_ip.startswith("10.") or effective_ip.startswith("192.168.") or effective_ip == "127.0.0.1"):
raise HTTPException(status_code=403, detail="Endpoint restricted to internal network.")
return effective_ip
@app.get("/internal/metrics", dependencies=[Depends(verify_internal_subnet)])
def get_system_metrics():
return {"status": "ok", "cpu_utilization": 24.5}
Parameterized Dependency Injection Patternsโ
1. Request Input Parameters (Path, Query, Header)โ
FastAPI maps parameter names declared in dependency functions to the request context:
from fastapi import Path, HTTPException, Depends
from typing import Annotated
def validate_positive_id(item_id: Annotated[int, Path(ge=1, le=10000)]) -> int:
if item_id % 2 != 0:
raise HTTPException(status_code=400, detail="Item ID must be an even integer.")
return item_id
ValidItemID = Annotated[int, Depends(validate_positive_id)]
@app.get("/items/{item_id}")
def read_item(item_id: ValidItemID):
return {"item_id": item_id, "status": "validated"}
2. Nested Dependency Chains (Sub-Dependencies)โ
Dependencies can depend on other dependencies, forming an execution DAG (Directed Acyclic Graph):
class User:
def __init__(self, user_id: int, roles: list[str]):
self.user_id = user_id
self.roles = roles
# Sub-dependency: Authentication
def get_current_user() -> User:
return User(user_id=42, roles=["admin", "editor"])
# Main dependency: Authorization consuming sub-dependency
def require_admin(user: Annotated[User, Depends(get_current_user)]) -> User:
if "admin" not in user.roles:
raise HTTPException(status_code=403, detail="Admin role required.")
return user
@app.delete("/admin/resource/{resource_id}")
def delete_resource(resource_id: int, user: Annotated[User, Depends(require_admin)]):
return {"deleted": resource_id, "by_user": user.user_id}
3. Dependency Factories (Parameterized Closures)โ
Use higher-order functions to configure reusable dependency validators dynamically:
from typing import Callable
def require_role(target_role: str) -> Callable:
def role_checker(user: Annotated[User, Depends(get_current_user)]) -> User:
if target_role not in user.roles:
raise HTTPException(status_code=403, detail=f"Permission denied: requires {target_role}")
return user
return role_checker
@app.post("/articles", dependencies=[Depends(require_role("editor"))])
def create_article():
return {"status": "created"}
@app.post("/billing", dependencies=[Depends(require_role("billing_admin"))])
def update_billing():
return {"status": "billing updated"}
4. Class-Based Dependencies with __init__ and __call__โ
Classes can serve as dependencies, consuming sub-dependencies in __init__ or holding state across route invocations:
class PaginationParams:
def __init__(self, skip: int = 0, limit: int = 100):
self.skip = skip
self.limit = min(limit, 100)
@app.get("/products")
def list_products(pagination: Annotated[PaginationParams, Depends(PaginationParams)]):
return {"offset": pagination.skip, "limit": pagination.limit}
Sources & Technical Referencesโ
- [1] FastAPI Documentation: Testing with the TestClient
- [2] Starlette Documentation: TestClient Architecture and Lifecycle
- [3] FastAPI Documentation: Sub-dependencies and Resolution Chains
- [4] FastAPI Documentation: Classes as Dependencies
- [5] FastAPI Documentation: Advanced Dependencies & Factories
- [6] FastAPI Documentation: Using the Starlette Request Object Directly
