The Dependency Resolution Engine
FastAPI uses a sophisticated dependency resolution engine to manage the lifecycle, validation, and injection of dependencies into path operation functions. This engine operates in two distinct phases: a startup Analysis Phase that builds a static graph of requirements, and a per-request Resolution Phase that solves this graph to produce the final values.
The Blueprint: Dependant Model
The core of the resolution engine is the Dependant class (defined in fastapi.dependencies.models). It acts as a node in a dependency graph, representing either a path operation function or a dependency callable.
A Dependant object stores everything FastAPI needs to know about a function's requirements:
- Parameters: Lists of
ModelFieldobjects forpath_params,query_params,header_params,cookie_params, andbody_params. - Sub-dependencies: A list of other
Dependantobjects in thedependenciesattribute. - Special Injections: Names of parameters that should receive the
Request,Response,BackgroundTasks, orSecurityScopesobjects. - Metadata: Information about whether the callable is a generator (
is_gen_callable), an async generator (is_async_gen_callable), or a coroutine (is_coroutine_callable).
Analysis Phase: Building the Graph
When FastAPI starts up, it inspects path operation functions using get_dependant in fastapi/dependencies/utils.py. This function recursively traverses the function signature to build the Dependant graph.
Parameter Analysis
The engine uses analyze_param to determine how to handle each parameter in a function signature. It checks for:
- Explicit Dependencies: Parameters using
Depends()orSecurity(). - Annotated Types: Metadata extracted from
Annotated[type, DependencyOrField]. - Special Types: Parameters type-hinted as
Request,Response,WebSocket, etc. - Inferred Fields: If no explicit FastAPI annotation is found, the engine infers the type (e.g., a scalar type becomes a
Queryparameter, while a Pydantic model becomes aBodyparameter).
The intermediate state of this analysis is captured in the ParamDetails class:
@dataclass
class ParamDetails:
type_annotation: Any
depends: params.Depends | None
field: ModelField | None
Resolution Phase: solve_dependencies
For every incoming request, FastAPI calls solve_dependencies in fastapi/dependencies/utils.py. This is an asynchronous, recursive function that transforms a Dependant graph into a SolvedDependency object.
Recursive Traversal
The engine first solves all sub-dependencies listed in dependant.dependencies. For each sub-dependency, it:
- Checks for Dependency Overrides: If the app has
dependency_overrides, the engine replaces the original callable with the override. - Recursively calls
solve_dependencies: This ensures that nested dependencies are resolved first. - Executes the Callable: Depending on the type, it calls the dependency using
run_in_threadpool(for sync),await(for async), or handles it as a context manager (for generators).
Caching Mechanism
To prevent redundant execution of the same dependency within a single request (e.g., multiple dependencies requiring the same database session), the engine uses a cache.
- The
Dependant.cache_key(a tuple of the callable, scopes, and scope type) identifies the dependency. - If
use_cache=True(the default), the engine checksdependency_cachebefore executing a dependency and stores the result after execution.
Context Management and Generators
FastAPI supports dependencies that use yield. These are handled using AsyncExitStack. When a generator dependency is encountered, the engine wraps it in a context manager and enters it using the stack:
# From fastapi/dependencies/utils.py
async def _solve_generator(
*, dependant: Dependant, stack: AsyncExitStack, sub_values: dict[str, Any]
) -> Any:
assert dependant.call
if dependant.is_async_gen_callable:
cm = asynccontextmanager(dependant.call)(**sub_values)
elif dependant.is_gen_callable:
cm = contextmanager_in_threadpool(contextmanager(dependant.call)(**sub_values))
return await stack.enter_async_context(cm)
This ensures that the code after the yield is executed after the response is sent, allowing for clean-up tasks like closing database connections.
The Result: SolvedDependency
The final output of the resolution process is a SolvedDependency object. This container holds everything needed to execute the path operation function:
@dataclass
class SolvedDependency:
values: dict[str, Any] # Map of parameter names to resolved values
errors: list[Any] # Validation errors encountered during resolution
background_tasks: StarletteBackgroundTasks | None
response: Response
dependency_cache: dict[DependencyCacheKey, Any]
If errors is populated (e.g., a required query parameter is missing or a body fails validation), FastAPI will skip calling the path operation and instead return a 422 Unprocessable Entity response containing these errors. Otherwise, the values dictionary is unpacked into the path operation function.