Dependency Resolution and Solved Results
In this codebase, dependency resolution is the process of traversing a tree of dependencies, executing them in the correct order, and collecting their results into a single object. This process is managed primarily by the solve_dependencies function, which produces a SolvedDependency result.
The SolvedDependency Container
The SolvedDependency class (defined in fastapi/dependencies/utils.py) is a dataclass that acts as a container for all information gathered during the resolution of a dependency tree.
@dataclass
class SolvedDependency:
values: dict[str, Any]
errors: list[Any]
background_tasks: StarletteBackgroundTasks | None
response: Response
dependency_cache: dict[DependencyCacheKey, Any]
values: A dictionary mapping parameter names to their resolved values. These values are eventually passed as keyword arguments to the path operation function.errors: A list of validation errors encountered during resolution (e.g., missing query parameters or failed Pydantic validation).background_tasks: An instance ofStarletteBackgroundTasksthat collects tasks added by dependencies.response: A "dummy"Responseobject used by dependencies to set headers or cookies.dependency_cache: A dictionary used to store and retrieve results of dependencies that haveuse_cache=True(the default), ensuring a dependency is only executed once per request.
The Resolution Engine: solve_dependencies
The solve_dependencies function is the core engine that processes a Dependant object. It is an asynchronous function that recursively resolves sub-dependencies before resolving the parameters of the current dependency.
Recursive Resolution and Overrides
When solve_dependencies encounters sub-dependencies in dependant.dependencies, it first checks for overrides provided by the dependency_overrides_provider (typically the FastAPI app instance). If an override exists, it creates a new Dependant for the override callable using get_dependant.
# fastapi/dependencies/utils.py
for sub_dependant in dependant.dependencies:
# ... (logic for overrides)
solved_result = await solve_dependencies(
request=request,
dependant=use_sub_dependant,
# ...
dependency_cache=dependency_cache,
)
Execution of Dependencies
Once sub-dependencies are resolved, the engine executes the actual dependency callable. The execution method depends on the type of the callable:
- Generators (yield): Handled by
_solve_generator, which uses anAsyncExitStackto manage the context. FastAPI maintains two stacks in the request scope:fastapi_inner_astackandfastapi_function_astack. - Coroutines (async def): Awaited directly:
solved = await call(**solved_result.values). - Standard Functions (def): Run in a threadpool to avoid blocking the event loop:
solved = await run_in_threadpool(call, **solved_result.values).
Parameter Extraction and Validation
After resolving sub-dependencies, solve_dependencies extracts values from the incoming request (Path, Query, Header, Cookie, and Body) using helper functions like request_params_to_args and request_body_to_args.
# fastapi/dependencies/utils.py
path_values, path_errors = request_params_to_args(
dependant.path_params, request.path_params
)
# ... (similar calls for query, header, cookie)
values.update(path_values)
errors += path_errors + ...
These helpers use Pydantic's ModelField (wrapped in fastapi._compat) to validate the incoming data against the expected types defined in the dependency signatures.
Managing Lifecycle with AsyncExitStack
For dependencies that use yield, the lifecycle is managed via AsyncExitStack. This ensures that the code after the yield (the cleanup code) is executed after the request is finished.
In solve_dependencies, the stack is retrieved from the request scope:
request_astack = request.scope.get("fastapi_inner_astack")
function_astack = request.scope.get("fastapi_function_astack")
The _solve_generator function then enters the context manager created from the dependency:
async def _solve_generator(
*, dependant: Dependant, stack: AsyncExitStack, sub_values: dict[str, Any]
) -> Any:
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)
Integration with Routing
The routing layer in fastapi/routing.py consumes the SolvedDependency to determine if it should proceed with the endpoint execution.
For a standard HTTP request, the get_request_handler (or the APIRoute logic) calls solve_dependencies. If solved_result.errors is populated, FastAPI raises a RequestValidationError. If successful, it merges the headers and cookies from the solved_result.response into the final response.
# fastapi/routing.py (simplified example of usage)
solved_result = await solve_dependencies(
request=request,
dependant=dependant,
# ...
)
if solved_result.errors:
raise RequestValidationError(solved_result.errors, body=body)
# Merge headers from dependencies
response.headers.raw.extend(solved_result.response.headers.raw)
# Execute the actual endpoint with resolved values
raw_response = await run_endpoint_function(
dependant=dependant, values=solved_result.values, is_coroutine=is_coroutine
)
This architecture allows dependencies to influence the final response (via the response parameter) and manage background tasks, while keeping the core endpoint logic clean and focused on its primary responsibility.