FastAPI Dependency Injection: A Complete Guide to `Depends` and `yield`
Dependency Injection (DI) is a core principle in FastAPI, allowing you to seamlessly integrate reusable logic, external resources (like database connections), configuration settings, and security checks into your API routes. FastAPI's DI system is built on standard Python features: type hinting and function calls, using the special Depends callable.
1. Basic Dependencies (Injecting Configuration or Services)โ
The simplest use case is injecting a resource or configuration variable that doesn't change during the request. FastAPI resolves the dependency function first and passes its return value to the route function.
from fastapi import FastAPI, Depends, HTTPException, Header
from typing import Annotated
app = FastAPI()
# 1. Dependency Function: Retrieves a fixed setting (e.g., from environment variables)
def get_settings():
"""Returns a dictionary containing application settings."""
return {
"api_version": "v1.5",
"debug_mode": True
}
# Type hint the dependency for clarity and reuse
Settings = Annotated[dict, Depends(get_settings)]
@app.get("/status")
def get_app_status(settings: Settings):
"""
The return value of get_settings() is injected into the 'settings' parameter.
"""
return {
"status": "online",
"version": settings["api_version"]
}
# Annotation: When FastAPI sees Depends(get_settings) in the function signature,
# it calls get_settings() before executing the route.
2. Dependencies that Integrate Security (Header Checks)โ
Dependencies can interact with the current request object and perform validation, such as checking for authentication headers. Dependencies can also raise HTTPException to abort the request early.
async def verify_api_key(api_key: Annotated[str | None, Header()] = None):
"""
Checks for a required header (X-API-Key) and validates it.
"""
REQUIRED_KEY = "super-secret-token"
if api_key is None:
raise HTTPException(
status_code=400,
detail="X-API-Key header required"
)
if api_key != REQUIRED_KEY:
raise HTTPException(
status_code=401,
detail="Invalid API Key"
)
# If successful, return a value that can be used by the route (e.g., the user ID)
return True
# Annotate the dependency for injection
AuthRequired = Annotated[bool, Depends(verify_api_key)]
@app.post("/secure/data")
def create_secure_data(auth_passed: AuthRequired):
"""
This route only runs if verify_api_key returns a truthy value (or True).
If verify_api_key raises an HTTPException, the request stops immediately.
"""
return {"message": "Data created successfully after auth check."}
# Annotation: Dependencies can be async, taking advantage of FastAPI's async nature.
3. Dependencies with yield (Context Management)โ
Using yield in a dependency function allows you to implement context managers, executing code before the request and after the request. This is the standard pattern for managing database connections, temporary resources, or complex transaction logic.
import time
from typing import Generator
# Mock Database Session (replace with SQLAlchemy, etc.)
class DB_Session:
def __init__(self, start_time):
self.start_time = start_time
def close(self):
print(f"\n[DB] Closing session. Total time: {time.time() - self.start_time:.4f}s")
def get_db() -> Generator[DB_Session, None, None]:
"""
Dependency that manages a resource using yield.
"""
# CODE BEFORE YIELD (Startup/Setup Logic)
start_time = time.time()
db_session = DB_Session(start_time)
print(f"[DB] Opening new session...")
try:
# The value yielded here is injected into the route function
yield db_session
finally:
# CODE AFTER YIELD (Teardown/Cleanup Logic)
db_session.close()
# Annotate the dependency
DBSession = Annotated[DB_Session, Depends(get_db)]
@app.get("/items/{item_id}")
def read_item(item_id: int, db: DBSession):
"""
The database session is opened, used in the route, and automatically closed
by the 'finally' block after the response is sent.
"""
# Simulate DB operation
time.sleep(0.05)
return {"item_id": item_id, "status": "retrieved via managed session"}
# Annotation: The code after the 'yield' is executed as a cleanup task,
# even if the request hits an exception (due to the 'finally' block).
4. Class-Based Dependenciesโ
For reusable service layers or classes that need dependency injection themselves, you can define the dependency as a class with a __call__ method.
class ItemService:
def __init__(self, api_key: str):
# The constructor can receive other dependencies (nested dependencies)
self.api_key = api_key
print(f"Service initialized with key ending in: {api_key[-4:]}")
def __call__(self, item_id: int):
"""
The __call__ method makes the class instance callable, allowing it
to be used directly by Depends().
"""
if item_id > 100:
raise HTTPException(status_code=404, detail="Item not found.")
return {"id": item_id, "verified_by": self.api_key}
def get_key_string() -> str:
return "SECRET-4321"
# ItemService itself is the dependency function/class.
# FastAPI will call ItemService() and then ItemService.__init__ is executed.
@app.get("/service/{item_id}")
def get_item_with_service(
item_id: int,
item_svc: Annotated[ItemService, Depends(ItemService)]
):
"""
The ItemService class is instantiated, and its __init__ method is
automatically provided with the dependencies it needs (get_key_string()).
"""
return item_svc(item_id)
# Annotation: Class dependencies enable complex nested dependency graphs
# and are ideal for business logic layers (Services/Repositories).
