Skip to main content
Advertisement

Dependency Injection

FastAPI's Depends separates common logic (authentication, DB connections, pagination) into dependency functions that can be easily swapped out during testing.


Basic Depends

from fastapi import FastAPI, Depends, HTTPException
from typing import Annotated

app = FastAPI()


# Dependency function: handle common query parameters
def common_parameters(
skip: int = 0,
limit: int = 10,
q: str | None = None,
):
return {"skip": skip, "limit": limit, "q": q}


# Concise with type hints
CommonParams = Annotated[dict, Depends(common_parameters)]


@app.get("/items/")
def list_items(commons: CommonParams):
return {"params": commons, "items": []}


@app.get("/users/")
def list_users(commons: CommonParams):
return {"params": commons, "users": []}


# Class-based dependency
class Pagination:
def __init__(self, page: int = 1, size: int = 20):
self.skip = (page - 1) * size
self.limit = size
self.page = page


@app.get("/products/")
def list_products(pagination: Annotated[Pagination, Depends()]):
return {
"page": pagination.page,
"skip": pagination.skip,
"limit": pagination.limit,
}

Nested Dependencies

from fastapi import FastAPI, Depends, Header, HTTPException
from typing import Annotated

app = FastAPI()

fake_tokens = {"alice-token": "Alice", "bob-token": "Bob"}
fake_users = {"Alice": {"id": 1, "role": "admin"}, "Bob": {"id": 2, "role": "user"}}


# Step 1: extract token
def get_token(x_token: Annotated[str, Header()]) -> str:
if x_token not in fake_tokens:
raise HTTPException(status_code=401, detail="Invalid token")
return x_token


# Step 2: look up user from token (depends on step 1)
def get_current_user(token: Annotated[str, Depends(get_token)]) -> dict:
username = fake_tokens[token]
return fake_users[username]


# Step 3: check admin (depends on step 2)
def require_admin(user: Annotated[dict, Depends(get_current_user)]) -> dict:
if user["role"] != "admin":
raise HTTPException(status_code=403, detail="Admin only")
return user


CurrentUser = Annotated[dict, Depends(get_current_user)]
AdminUser = Annotated[dict, Depends(require_admin)]


@app.get("/me")
def read_me(user: CurrentUser):
return user


@app.get("/admin/dashboard")
def admin_dashboard(admin: AdminUser):
return {"message": "Welcome Admin", "user": admin}

Resource Management — yield Dependencies

from fastapi import FastAPI, Depends
from typing import Annotated, Generator

app = FastAPI()


class DBSession:
def __init__(self, db_url: str):
self.url = db_url
self.queries: list[str] = []
print(f"[DB] Connected: {db_url}")

def execute(self, query: str) -> list:
self.queries.append(query)
return [{"id": 1, "data": query}]

def close(self):
print(f"[DB] Disconnected ({len(self.queries)} queries executed)")


DATABASE_URL = "postgresql://localhost/mydb"


# yield dependency: setup before yield, teardown after
def get_db() -> Generator[DBSession, None, None]:
db = DBSession(DATABASE_URL)
try:
yield db
finally:
db.close() # always runs (even on exception)


DB = Annotated[DBSession, Depends(get_db)]


@app.get("/users/")
def list_users(db: DB):
return db.execute("SELECT * FROM users")


@app.get("/users/{user_id}")
def get_user(user_id: int, db: DB):
return db.execute(f"SELECT * FROM users WHERE id={user_id}")

Global Dependencies

from fastapi import FastAPI, Depends, Header, HTTPException
from typing import Annotated

app = FastAPI()


async def verify_api_key(x_api_key: Annotated[str, Header()]):
if x_api_key != "secret-key":
raise HTTPException(status_code=403, detail="Invalid API Key")


# Apply dependency to all routes in a router
from fastapi import APIRouter

router = APIRouter(
prefix="/api",
dependencies=[Depends(verify_api_key)],
)


@router.get("/data")
def get_data():
return {"data": "protected"}


@router.get("/more-data")
def get_more_data():
return {"data": "also protected"}


# App-wide global dependency
app_with_global = FastAPI(
dependencies=[Depends(verify_api_key)]
)

Dependency Overrides (Testing)

from fastapi import FastAPI, Depends
from fastapi.testclient import TestClient
from typing import Annotated

app = FastAPI()


# Real dependency
def get_db():
return {"type": "production_db"}


DB = Annotated[dict, Depends(get_db)]


@app.get("/items/")
def list_items(db: DB):
return {"db_type": db["type"]}


# Replace dependency in tests
def get_test_db():
return {"type": "test_db"}


def test_list_items():
app.dependency_overrides[get_db] = get_test_db # swap

client = TestClient(app)
response = client.get("/items/")

assert response.status_code == 200
assert response.json()["db_type"] == "test_db"

app.dependency_overrides.clear() # restore

Summary

ConceptDescription
Depends(fn)Inject a dependency function
Nested dependenciesDependencies that use other dependencies
yield dependencysetup/teardown pattern
Global dependenciesrouter(dependencies=[...])
dependency_overridesSwap dependencies in tests
AnnotatedEmbed dependency in type hint

Dependency injection makes it easy to reuse authentication, DB sessions, and common parameters while keeping tests simple.

Advertisement