← Python Code FastAPI
Browse Python Concepts

lifespan — Shared Startup State Resolution

Mental Model

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.

Rule: When retrieving state resources initialized during startup, always retrieve them directly from the request.state object.

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?

Broken code
Python
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}
Predict what occurs when a client triggers a GET request to the /status endpoint.

The Output

What actually happens
TypeError: __init__() missing 1 required positional argument: 'target'

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

Corrected pattern
Python
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

When retrieving state resources initialized during startup, always retrieve them directly from the request.state object.
Common mistake: Attempting to use 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.