Dependency Injection
FastAPI implements a hierarchical Dependency Injection (DI) system that allows developers to share logic across different path operations, manage resource lifecycles, and enforce security requirements. This system is built around the Depends class and its specialized subclass, Security, both defined in fastapi.params.
The DI system is designed to solve the problem of code duplication for common tasks like database session management, authentication, and request parameter validation. By defining these tasks as dependencies, FastAPI can automatically resolve them, inject the results into path operation functions, and handle cleanup.
The Dependency Injection Mechanism
At its core, dependency injection in FastAPI involves defining a callable (a function or a class) and telling FastAPI to execute it before the path operation. This is done using the Depends class, typically within a type hint using Annotated.
When a path operation is called, FastAPI analyzes the parameters. If it finds a Depends instance, it resolves that dependency (and any sub-dependencies) before calling the operation function.
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
In this example from docs_src/dependencies/tutorial001_an_py310.py, common_parameters is a dependency. FastAPI extracts the query parameters q, skip, and limit from the request, passes them to common_parameters, and then injects the returned dictionary into the read_items function.
Class-Based Dependencies
FastAPI also supports using classes as dependencies. When a class is passed to Depends, FastAPI instantiates the class. The parameters of the class's __init__ method are treated as sub-dependencies, allowing for complex state management and grouping of related logic.
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
return commons
As seen in docs_src/dependencies/tutorial002_an_py310.py, this approach allows the dependency to maintain state within an object instance, which can be cleaner than passing around dictionaries.
Resource Management with Yield
For dependencies that require setup and teardown—such as database sessions or network connections—FastAPI supports yield dependencies. This pattern ensures that resources are properly released even if an exception occurs during the request processing.
async def get_db():
db = DBSession()
try:
yield db
finally:
db.close()
In docs_src/dependencies/tutorial008_an_py310.py, the code before the yield is executed before the path operation, and the code in the finally block is executed after the response has been sent. This guarantees that db.close() is called regardless of whether the request succeeded or failed.
Security and OAuth2 Scopes
The Security class is a specialized version of Depends designed for authentication and authorization. While it functions similarly to Depends, it adds a scopes parameter used for OAuth2 scope requirements.
class Security(Depends):
scopes: Sequence[str] | None = None
The Security class serves two primary purposes in FastAPI:
- OpenAPI Documentation: It allows FastAPI to document the specific security scopes required for a path operation in the generated OpenAPI schema.
- Scope Enforcement: It provides a mechanism to pass required scopes to a security dependency.
When using Security, the dependency function can request SecurityScopes (from fastapi.security) to access the list of scopes required by the current operation and all its parent dependencies.
async def get_current_user(
security_scopes: SecurityScopes,
token: Annotated[str, Depends(oauth2_scheme)]
):
# Logic to verify token and check security_scopes.scopes
...
@app.get("/users/me/items/")
async def read_own_items(
current_user: Annotated[User, Security(get_current_user, scopes=["items"])],
):
return [{"item_id": "Foo", "owner": current_user.username}]
In this pattern from docs_src/security/tutorial005_an_py310.py, the get_current_user function receives the "items" scope via the security_scopes parameter, allowing it to perform fine-grained authorization.
Dependency Resolution and Caching
FastAPI builds a dependency graph for every request. If multiple dependencies (or the path operation itself) require the same dependency, FastAPI caches the result by default to avoid redundant executions.
The Depends class includes a use_cache boolean (defaulting to True). If a dependency is used multiple times in the same request—for example, a get_current_user dependency used by both the path operation and another sub-dependency—FastAPI will execute it once and reuse the result. Setting use_cache=False forces FastAPI to re-execute the dependency every time it is encountered in the graph.
Decoupling with Dependency Overrides
A significant advantage of this DI system is the ability to override dependencies during testing. The FastAPI application class provides a dependency_overrides attribute, which is a dictionary mapping original dependencies to their replacements.
This is frequently used in tests to replace real database sessions with test databases or to mock external API calls, as demonstrated in tests/test_dependency_overrides.py:
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient
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
client = TestClient(app)
def test_override():
app.dependency_overrides[common_parameters] = lambda: {"q": "override", "skip": 5, "limit": 10}
response = client.get("/items/")
assert response.json() == {"q": "override", "skip": 5, "limit": 10}
app.dependency_overrides = {}
This mechanism allows for high testability without modifying the production path operation logic.