Skip to main content

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 of StarletteBackgroundTasks that collects tasks added by dependencies.
  • response: A "dummy" Response object used by dependencies to set headers or cookies.
  • dependency_cache: A dictionary used to store and retrieve results of dependencies that have use_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:

  1. Generators (yield): Handled by _solve_generator, which uses an AsyncExitStack to manage the context. FastAPI maintains two stacks in the request scope: fastapi_inner_astack and fastapi_function_astack.
  2. Coroutines (async def): Awaited directly: solved = await call(**solved_result.values).
  3. 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.