Learn / Python and FastAPI Backend Engineering
Advanced Dependency Injection
Design FastAPI dependency boundaries for request-scoped sessions, authenticated users, framework-free services, and test overrides that cannot leak between cases.
Course: Python and FastAPI Backend Engineering. Level: Intermediate. Topic: Backend craft.
Stage: basic - Basic dependency boundaries - Dependency injection as an API boundary. Use FastAPI dependencies to expose request-scoped resources, auth, settings, and test seams without global state.
Outcomes
- Thread request-scoped session and user providers through routes without hidden globals.
- Build dependency overrides for auth and repositories that are isolated per test.
- Keep business services framework-free while still using FastAPI dependencies at the HTTP edge.
Concepts
- request-scoped provider
- dependency override
- auth boundary
- framework-free service
Concept flow
Show how request providers feed a framework-free service and where tests can replace auth or persistence safely.
- Request
- Auth provider
- Session provider
- Repository provider
- Service function
- Override test
Session flow
- Separate provider boundaries from service work (concept, 11 min) — Use dependency functions to translate request state into plain service inputs.
- Providers own framework details like Depends, tokens, sessions, and app settings.
- Service functions should accept plain objects, protocols, and values.
- Build request-scoped auth and persistence (walkthrough, 23 min) — Compose current_user, get_session, and get_order_repository into a route that exposes every dependency clearly.
- Async sessions belong to the request and should be yielded by a provider that owns cleanup.
- A route signature is a reviewable dependency graph when providers are named well.
- Prove override isolation (exercise, 14 min) — Write an override-based route test and prove overrides are cleared after the case.
- Override tests should exercise real routing, validation, and response behavior.
- Fixture cleanup is part of the dependency contract, not test hygiene trivia.
Code example
Python in app/dependencies.py.
from collections.abc import AsyncIterator
from typing import Annotated, Protocol
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
router = APIRouter(prefix="/orders", tags=["orders"])
class User:
def __init__(self, id: UUID, email: str) -> None:
self.id = id
self.email = email
class OrderRepository(Protocol):
async def list_for_user(self, user_id: UUID) -> list[str]: ...
class SqlOrderRepository:
def __init__(self, session: AsyncSession) -> None:
self.session = session
async def list_for_user(self, user_id: UUID) -> list[str]:
rows = await self.session.execute(
text("""
select order_number
from orders
where user_id = :user_id
order by created_at desc
"""),
{"user_id": str(user_id)},
)
return [str(row.order_number) for row in rows]
async_session = async_sessionmaker(expire_on_commit=False)
async def get_session() -> AsyncIterator[AsyncSession]:
async with async_session() as session:
yield session
async def current_user(token: Annotated[str, Depends(oauth2_scheme)]) -> User:
user = await users.find_by_token(token)
if user is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
return user
async def get_order_repository(
session: Annotated[AsyncSession, Depends(get_session)],
) -> OrderRepository:
return SqlOrderRepository(session)
async def list_visible_orders(user: User, orders: OrderRepository) -> list[str]:
return await orders.list_for_user(user.id)
@router.get("", response_model=list[str])
async def list_orders(
user: Annotated[User, Depends(current_user)],
orders: Annotated[OrderRepository, Depends(get_order_repository)],
) -> list[str]:
return await list_visible_orders(user, orders)
Walkthrough examples
- Override pack for authenticated order listing — A team needs route-level confidence without calling real auth, real databases, or leaking fake providers between tests.
- File: app/dependencies.py
- File: app/orders/routes.py
- File: app/orders/service.py
- File: tests/test_order_dependencies.py
- Extract current_user, get_session, and get_order_repository as named providers with one lifetime each.
- Move list_visible_orders into a plain service function that accepts User and OrderRepository.
- In a route test, override current_user and get_order_repository with fake providers.
- Clear app.dependency_overrides in fixture teardown and add a test proving unauthorized behavior returns after cleanup.
Practice
- Name every provider in the route signature and write down its lifetime: request, app, or test-only.
- Override current_user and get_order_repository in a route test, then clear overrides in teardown.
- Move the business decision into a framework-free service function and call it from the route.
Checklist
- Route functions depend on providers, not module-level sessions or users.
- Application services accept plain objects or protocols instead of Depends.
- Tests override auth and persistence providers without touching production code.
- Overrides are reset after each test so one case cannot change another.
- Override keys use the original provider callable, not a wrapper or duplicate import.
Quiz prompts
- Where should Depends usually appear in a layered FastAPI service? — FastAPI dependencies are excellent at the edge, but core services stay easier to test and reuse when they accept plain parameters.
- A test overrides current_user but never clears app.dependency_overrides. What is the risk? — Overrides are app-level state. Treat them like test fixtures that must be scoped and cleaned up.
- Why model repositories as protocols or small interfaces in this lesson? — A small repository protocol keeps the service framework-free and makes override tests focused on route behavior.
- Which dependency should normally be request-scoped? — Database sessions and authenticated users are tied to one request; settings and pure helpers usually have different lifetimes.
- A dependency override silently stops working after a refactor. What should you inspect first? — FastAPI overrides are keyed by the dependency callable object. Wrapping or importing a different provider can miss the intended seam.
Flashcards
- Where should FastAPI Depends live? Keep Depends at the HTTP edge: route signatures and provider functions. Services should accept plain objects or protocols.
- Why yield an AsyncSession from a provider? The provider owns request-scoped setup and cleanup, so each request gets a bounded session lifecycle.
- What must every dependency override fixture do? Install fakes only for the test scope and clear app.dependency_overrides afterward.
- Why depend on an OrderRepository protocol? It names the behavior the service needs while letting production use SQL and tests use a fake.
- What is the auth provider boundary? It turns token/request details into a User object or raises a client-safe auth error before service code runs.
- How is current_user different from require_admin? current_user authenticates identity; require_admin or require_scope authorizes what that identity may do.
Labs
- Build isolated auth and repository overrides — Refactor one authenticated route so tests can replace auth and persistence without changing production route code.
- Create current_user, get_session, and get_order_repository providers with documented lifetimes.
- Move route business logic into a framework-free service that accepts a User and repository port.
- Write one successful route test using fake auth and fake repository overrides.
- Write one cleanup proof: after override teardown, the same route requires real auth again.
- Add a require_scope provider and override only current_user to prove authorization still runs.
- Tests prove a fake user can read only the fake repository data assigned to that user.
- Override cleanup is verified by a failing/unauthorized request after teardown.
- Review notes include the provider graph and the command output from the override tests.
Challenge
- Review-ready FastAPI dependency graph (Stretch) — Turn a route with hidden globals into an explicit dependency graph: request-scoped session, authenticated user, repository provider, framework-free service, and isolated override tests.
- The route signature makes auth and persistence dependencies visible.
- The service function can be called without importing FastAPI.
- Override tests cover success, auth failure, fake repository behavior, and cleanup.
- The review note names provider lifetimes and one risk if an override leaks.
Canonical lesson URL