How a modern_di.Container is wired into a FastAPI app and how scoped child
containers are opened and closed around each connection. Terms in italics are
defined in the glossary.
setup_di attaches a caller-built root container to the app and is the single
entry point an application calls at startup. It does three things:
- Stores the container on
app.state.di_container(read back byfetch_di_container(app)). - Registers the two context providers
(
fastapi_request_provider,fastapi_websocket_provider) on the container'sproviders_registry, so the liveRequest/WebSocketcan be resolved. - Chains an internal lifespan manager onto the app's existing
lifespan_contextviafastapi.routing._merge_lifespan_context, preserving any lifespan the app already had.
It returns the same container for convenience. The application owns container
construction (groups, overrides); setup_di only wires it in.
The chained _lifespan_manager runs async with fetch_di_container(app): — the
root container's __aenter__ opens it on startup and __aexit__ closes it on
shutdown. Using async with (rather than a one-shot open) means a second
lifespan cycle against the same container reopens it instead of raising
ContainerClosedError. This is what lets an app be started, stopped, and
started again (e.g. repeated TestClient contexts in tests) against one
container instance.
build_di_container is an async FastAPI dependency that yields a child
container scoped to the current connection, then closes it:
- It applies the scope mapping: a
fastapi.Request→Scope.REQUESTwith the request placed incontext[fastapi.Request]; afastapi.WebSocket→Scope.SESSIONwith the socket incontext[fastapi.WebSocket]. Any otherHTTPConnectionyields a child withscope=None. - The child is built from the root container via
build_child_container(context=..., scope=...). - After the endpoint returns, the
finallyblock callscontainer.close_async(), tearing down anything opened in that scope.
Finer scopes are reached by building further children from this one: an HTTP
endpoint can build_child_container() again for ACTION scope, and a WebSocket
handler (whose injected container is SESSION-scoped) builds a child for
REQUEST scope.
Returns the root container off app.state (cast to Container). Used
internally by the lifespan and build_di_container, and available to
application code that needs the root container directly.