Skip to main content

Dependency Injection System

The Dependency Injection (DI) system in this project is a core architectural component designed to handle shared logic, security requirements, and resource management across path operations. Unlike traditional DI frameworks that rely on complex configuration, this implementation uses a declarative approach integrated with Python's type hinting system.

The Declarative Dependency Model

The system revolves around the Depends class (defined in fastapi/params.py), which acts as a marker for the framework to resolve a requirement. Dependencies are typically declared using the Annotated type hint, which allows developers to keep the function signature clean while providing metadata to the framework.

In docs_src/dependencies/tutorial001_an_py310.py, a basic dependency is implemented as follows:

from typing import Annotated
from fastapi import Depends, FastAPI

app = FastAPI()

async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}

@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons

When a request hits the /items/ endpoint, the framework's routing logic (specifically APIRoute in fastapi/routing.py) calls solve_dependencies to resolve common_parameters before executing the path operation.

Recursive Resolution and the Dependency Graph

The implementation in fastapi/dependencies/utils.py handles dependencies as a directed acyclic graph (DAG). The solve_dependencies function is recursive; if a dependency itself has dependencies, the system resolves the entire tree before proceeding.

This allows for modular logic where small, focused dependencies can be composed into complex requirements. For example, a dependency that requires a database session might itself depend on a dependency that validates a user's authentication token.

Resource Lifecycle Management with yield

One of the most powerful features of the DI system is its ability to manage the lifecycle of resources like database connections or file handles. This is achieved using Python's yield keyword within a dependency function.

The framework uses contextlib.asynccontextmanager and AsyncExitStack to ensure that the code following the yield statement is executed after the response has been sent. This pattern is visible in docs_src/dependencies/tutorial008_an_py310.py:

async def get_db():
db = DBSession()
try:
yield db
finally:
db.close()

In fastapi/dependencies/utils.py, the _solve_generator function handles these cases by entering the generator into an AsyncExitStack associated with the request scope. This guarantees that db.close() is called even if an exception occurs during the path operation.

Caching and Performance Optimization

By default, the DI system caches the result of a dependency within the scope of a single request. If multiple dependencies (or the path operation itself) require the same dependency, it is executed only once.

This behavior is controlled by the use_cache parameter in the Depends class:

@dataclass(frozen=True)
class Depends:
dependency: Callable[..., Any] | None = None
use_cache: bool = True
scope: Literal["function", "request"] | None = None

If use_cache is set to False, the dependency will be re-executed every time it is encountered in the dependency tree for that request.

Security Integration

The Security class is a specialized subclass of Depends that adds support for OAuth2 scopes. It allows the DI system to not only inject security-related data (like a user object) but also to integrate with OpenAPI documentation by declaring required scopes.

@dataclass(frozen=True)
class Security(Depends):
scopes: Sequence[str] | None = None

When Security is used, the framework extracts the requested scopes and includes them in the generated OpenAPI schema, enabling automatic documentation of security requirements.

Testing and Dependency Overrides

For testing purposes, the FastAPI application class provides a dependency_overrides attribute. This is a dictionary that allows developers to swap out real dependencies for mocks or fakes without modifying the production code.

The solve_dependencies function checks this provider (usually the FastAPI app instance) before executing a dependency:

# From fastapi/dependencies/utils.py
if (
dependency_overrides_provider
and dependency_overrides_provider.dependency_overrides
):
original_call = sub_dependant.call
call = getattr(
dependency_overrides_provider, "dependency_overrides", {}
).get(original_call, original_call)

This mechanism is extensively used in the project's test suite (e.g., tests/test_dependency_overrides.py) to inject test databases or bypass authentication during integration tests.