fastapi/fastapi
Dependency injection
The defining feature of FastAPI. The framework reads a function's signature, builds a Dependant tree, and resolves it on every request. Each piece below is implemented in fastapi/dependencies/utils.py (resolver) and fastapi/dependencies/models.py (the Dependant dataclass).
What gets injected
A path-operation function can declare any combination of:
| Annotation | What it becomes |
| ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | --------------- |
| name: int (in path) | Path parameter |
| name: int or name: int | None = ... | Query parameter |
| Annotated[T, Path/Query/Header/Cookie/Body/Form/File(...)] | Explicit parameter (the marker carries metadata; see Parameters) |
| Pydantic model annotation | Body parameter (or query if the model is configured for queries) |
| Annotated[T, Depends(callable)] or name: T = Depends(callable) | Sub-dependency |
| Annotated[T, Security(callable, scopes=[...])] | Security sub-dependency that contributes OAuth2 scopes |
| Request, Response, BackgroundTasks, WebSocket, HTTPConnection | Special direct-injection parameters |
| SecurityScopes | The OAuth2 scopes the operation declares |
The Dependant dataclass
fastapi/dependencies/models.py:Dependant holds everything inferred from a callable's signature:
@dataclass
class Dependant:
path_params: list[ModelField] = ...
query_params: list[ModelField] = ...
header_params: list[ModelField] = ...
cookie_params: list[ModelField] = ...
body_params: list[ModelField] = ...
dependencies: list["Dependant"] = ...
name: str | None = None
call: Callable[..., Any] | None = None
request_param_name: str | None = None
websocket_param_name: str | None = None
http_connection_param_name: str | None = None
response_param_name: str | None = None
background_tasks_param_name: str | None = None
security_scopes_param_name: str | None = None
own_oauth_scopes: list[str] | None = None
parent_oauth_scopes: list[str] | None = None
use_cache: bool = True
path: str | None = None
scope: Literal["function", "request"] | None = NonePlus several @cached_property accessors that the runtime resolver hits hot:
is_coroutine_callable,is_gen_callable,is_async_gen_callable— kind of the callable.oauth_scopes— merged parent + own scopes for security purposes.computed_scope— defaults to"request"if the callable is a generator, otherwise inherits fromscopeorNone. Drives whichAsyncExitStackcleanup goes on.cache_key—(callable, scopes_for_cache, computed_scope_str). TwoDepends(...)of the same callable share results across a request when this matches anduse_cache=True._is_security_scheme—Trueif the callable is an instance ofSecurityBase. Drives OpenAPI security generation.
Building the tree (registration time)
get_dependant(call, name, path, security_scopes) walks the callable's signature and produces a Dependant. For each parameter:
- Look at
Annotated[...]metadata first. - If a special parameter type (
Request,Response, etc.), record the name on the dependant and skip. - If a
Depends/Securitymarker, recurse viaget_param_sub_dependantand append todependencies. - Otherwise classify as path/query/header/cookie/body via
analyze_param.
This walk happens once per route at registration time. The route stashes the dependant on APIRoute.dependant and reuses it forever. OpenAPI generation reads it; the resolver reads it.
Resolving (request time)
solve_dependencies(*, request, dependant, body, dependency_overrides_provider, async_exit_stack, embed_body_fields) is the resolver. It:
- Iterates
dependant.dependenciesand resolves each sub-dependency recursively. - Calls each sub-dependency with the values it produced. For
defcallables it usesrun_in_threadpool. For generator callables, it enters their context manager via the appropriateAsyncExitStack. - Caches results on a per-request cache keyed by
Dependant.cache_key. - After sub-dependencies are resolved, parses the request to populate this level's
path_params,query_params,header_params,cookie_params, andbody_params. Validation errors are accumulated, never raised inline. - Populates special parameters (
request_param_name, etc.) on the values dict. - Applies
dependency_overrides(set in tests viaapp.dependency_overrides[real] = stub). - Returns the values dict, errors list, dependency cache, and accumulated background tasks.
The cache is what makes a single dependency resolve once per request even when many sub-dependencies request it. Setting Depends(callable, use_cache=False) opts out — useful for "fresh per call" dependencies like a request-scoped UUID.
Yield dependencies and scope
A dependency callable that uses yield is a context manager:
def get_db():
db = Session(...)
try:
yield db
finally:
db.close()Dependant.computed_scope defaults to "request" for generator callables — the cleanup runs on scope["fastapi_inner_astack"], after the response is fully sent. Setting scope="function" runs cleanup on scope["fastapi_function_astack"], immediately after the endpoint returns.
DependencyScopeError is raised if a function-scoped dependency depends on a request-scoped dependency — the lifetimes would not nest.
dependency_overrides
FastAPI.dependency_overrides is a dict[Callable, Callable]. When a dependant is resolved, the resolver checks the dict and substitutes the override before calling. This is the standard pattern for unit-testing endpoints that depend on a database or external API:
app.dependency_overrides[get_db] = get_test_dbImplementation: see solve_dependencies and dependency_overrides_provider parameter.
Integration points
- OpenAPI —
fastapi/openapi/utils.pyreads each route'sDependantto build the parameters/body/security sections. - Security — every
SecurityBaseinstance is a callable inserted viaSecurity(...); its presence flipsDependant._is_security_schemeand causes the OpenAPI generator to emitsecuritySchemes. - Routing —
APIRoutebuilds itsDependantonce and uses it on every request.
Common pitfalls
- A dependency uses
yieldbut the route never sees the cleanup run. Cause: the user manuallytry-suppressed the exception inside the generator. The framework now raises a verboseFastAPIError(seerequest_responseinfastapi/routing.py) explaining what happened. - Two dependencies share state when they should not. Cause: same callable +
use_cache=True(default). Passuse_cache=Falseor split the callable. - Security scopes do not show up in OpenAPI. Cause: scope was passed via
Depends(...)instead ofSecurity(...). OnlySecuritycarriesscopes.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.
Previous
Request lifecycle
Next
OpenAPI generation