Skip to main content

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 ModelField objects for path_params, query_params, header_params, cookie_params, and body_params.
  • Sub-dependencies: A list of other Dependant objects in the dependencies attribute.
  • Special Injections: Names of parameters that should receive the Request, Response, BackgroundTasks, or SecurityScopes objects.
  • 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:

  1. Explicit Dependencies: Parameters using Depends() or Security().
  2. Annotated Types: Metadata extracted from Annotated[type, DependencyOrField].
  3. Special Types: Parameters type-hinted as Request, Response, WebSocket, etc.
  4. Inferred Fields: If no explicit FastAPI annotation is found, the engine infers the type (e.g., a scalar type becomes a Query parameter, while a Pydantic model becomes a Body parameter).

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:

  1. Checks for Dependency Overrides: If the app has dependency_overrides, the engine replaces the original callable with the override.
  2. Recursively calls solve_dependencies: This ensures that nested dependencies are resolved first.
  3. 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 checks dependency_cache before 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.