lifespan — Shared Startup State Resolution
FastAPI's lifespan context manager establishes an application-wide state dictionary (app.state) that lives for the entire duration of the application. Depends() is designed for request-scoped dependencies, not for directly retrieving these globally managed startup resources. Think of app.state as a persistent shelf where startup items are placed.
The Setup
You want to configure a heavy, thread-safe cache engine using the modern lifespan pattern. You construct a startup generator, yield the cache client, and attempt to inject the client into a route handler using standard dependency annotations.
What Does This Print?
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends
class DBEngine:
def __init__(self, target: str):
self.target = target
self.connected = True
@asynccontextmanager
async def lifespan(app: FastAPI):
# Initialize resource at app startup
engine = DBEngine("postgresql://")
yield {"engine": engine}
engine.connected = False
app = FastAPI(lifespan=lifespan)
@app.get("/status")
async def status(engine: DBEngine = Depends()):
# Attempt to consume context-managed startup object
return {"connected": engine.connected, "target": engine.target}
/status endpoint.
The Output
The request returns a 422 Unprocessable Entity or crashes with a runtime type exception:
Instead of retrieving the initialized database connection, FastAPI's dependency injection engine attempted to instantiate a brand new DBEngine instance, failing because it had no target argument.
Why Python Does This
FastAPI's dependency resolution system inspects the signatures of functions inside Depends(). If a dependency object signature contains no defaults or relies on runtime variables, FastAPI attempts to construct it. The dictionary yielded inside lifespan is not magically bound to dependency injection arguments. The values are added to the application instance state dictionary (app.state). To retrieve lifespan resources in a route handler, you must access them through the Request object's state attribute.
The Fix
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
class DBEngine:
def __init__(self, target: str):
self.target = target
self.connected = True
@asynccontextmanager
async def lifespan(app: FastAPI):
engine = DBEngine("postgresql://")
# Yielding dictionary registers keys inside app.state
yield {"engine": engine}
engine.connected = False
app = FastAPI(lifespan=lifespan)
@app.get("/status")
async def status(request: Request):
# Access the context-managed database engine via the request state scope
engine = request.state.engine
return {"connected": engine.connected, "target": engine.target}
When lifespan yields a dictionary, FastAPI/Starlette places each key as an attribute on the request state object. Accessing request.state.engine retrieves the pre-initialized DBEngine instance that was stored during application startup. This bypasses Depends()'s constructor-based instantiation logic entirely — the handler never calls DBEngine(...) at all.
How This Fails in Real Systems
An API used lifespan startup blocks to configure a Redis cluster connection. Route handlers attempted to use a dependency injection signature, causing each API request to instantiate new Redis connection pools, eventually exhausting the cache server's socket descriptor limits within minutes.
Key Takeaway
Depends() to retrieve objects initialized in the lifespan context manager, rather than accessing them via request.state.<key> where <key> is the name used in the yielded dictionary.