FastAPI Dependency Injection DAG & Lifespan Context

FastAPI features a powerful Dependency Injection (DI) system. Unlike spring-like XML DI frameworks, FastAPI’s DI engine resolves dependencies dynamically per request, constructing a Directed Acyclic Graph (DAG) of sub-dependencies, resolving database connections, and managing resource cleanups using generator yield patterns.

This chapter details FastAPI’s Dependency DAG resolution engine, yield context cleanups, sub-dependencies, and modern ASGI lifespan event context management.


1. Dependency Injection DAG Engine (Depends())

FastAPI’s dependency engine (fastapi.dependencies.utils.solve_dependencies) analyzes path operation functions during request handling:

  1. DAG Tree Traversal: Constructs a tree of required dependencies.
  2. Sub-Dependency Resolution: If Dependency A depends on Dependency B (Depends(get_db)), FastAPI resolves Dependency B first.
  3. Dependency Caching (use_cache=True): By default, if multiple dependencies in the same request tree request Depends(get_db), FastAPI executes get_db once and caches the result for the lifetime of that specific HTTP request.
FastAPI Dependency Injection DAG:

HTTP Request Arrives
       |
       v
[ Path Operation: get_user_orders(user=Depends(get_current_user), db=Depends(get_db)) ]
       |
       +---> Resolve 1: get_db() -> Opens DB Session (Cached for request)
       |
       +---> Resolve 2: get_current_user(token=Depends(oauth2_scheme), db=Depends(get_db))
                 |           |
                 |           └---> Reuses Cached db Session! (use_cache=True)
                 v
           Decodes JWT & Queries DB
       |
       v
[ Execute Path Operation Handler ]
       |
       v
[ Cleanup Phase: Execute post-yield code in reverse DAG order ]

2. Context Cleanup via yield Dependencies

Dependencies can manage stateful resources (database sessions, HTTP client connections, locks) using a generator function with yield:

from typing import Generator
from sqlalchemy.orm import Session
from myapp.database import SessionLocal

def get_db() -> Generator[Session, None, None]:
    db = SessionLocal()
    try:
        yield db  # Provide resource to route handler
    finally:
        db.close() # GUARANTEED CLEANUP after request finishes!

Execution Flow:

  1. Code before yield executes prior to running the view handler.
  2. The yielded value is injected into the view parameter.
  3. Code after yield (or inside finally:) executes after the response has been sent to the client.

3. ASGI Lifespan Events (asynccontextmanager)

Legacy FastAPI used @app.on_event("startup") and @app.on_event("shutdown"). Modern FastAPI uses the ASGI Lifespan interface (asynccontextmanager), which encapsulates application-wide setup and teardown inside a single context manager:

from contextlib import asynccontextmanager
from fastapi import FastAPI
import httpx

# Shared application state
state = {}

@asynccontextmanager
async def lifespan(app: FastAPI):
    # STARTUP: Initialize database connection pools & HTTP clients
    state["http_client"] = httpx.AsyncClient()
    yield
    # SHUTDOWN: Gracefully close pools & clients
    await state["http_client"].aclose()

app = FastAPI(lifespan=lifespan)

4. Production Trade-offs & Dependency Scope

  • use_cache=True (Default): Shares the dependency result within a single HTTP request tree.
  • use_cache=False: Forces FastAPI to re-evaluate the dependency every time it is referenced in the dependency tree. Use use_cache=False when distinct sub-components require independent, un-shared resource instances.
Display Options
Appearance
Text Size
100%