136 lines
5.0 KiB
Markdown
136 lines
5.0 KiB
Markdown
# Dependencies
|
|
|
|
Use FastAPI dependencies for request-scoped wiring: sessions, auth, permission checks, and other collaborators that should be assembled by the framework before the handler runs.
|
|
|
|
## Why this convention exists
|
|
|
|
Dependencies are FastAPI's main request-time composition tool. They let you:
|
|
|
|
- keep wiring visible in the handler signature
|
|
- reuse auth and resource access across routes
|
|
- give tests a clean override seam
|
|
- manage `yield`-based resources with framework-controlled lifetime
|
|
|
|
That is better than opening sessions, parsing tokens, or repeating permission logic inside every route body.
|
|
|
|
## The convention
|
|
|
|
1. Put reusable request wiring in dependency callables.
|
|
2. Use `Annotated[..., Depends(...)]` aliases when they make signatures easier to read.
|
|
3. Keep handlers consuming already-shaped collaborators.
|
|
4. Raise semantic HTTP errors inside dependencies when the failure is inherently about the request or caller.
|
|
5. Use lifespan for app-wide startup resources; use dependencies for per-request access to them.
|
|
|
|
## Follow it when the concern is request-scoped
|
|
|
|
Typical fits:
|
|
|
|
- DB/session access
|
|
- current-user lookup
|
|
- permission gates
|
|
- token/header parsing
|
|
- reusable request validation
|
|
|
|
## Do not over-abstract it
|
|
|
|
Do *not* create a dependency just because a helper exists.
|
|
|
|
Skip dependency injection when:
|
|
|
|
- the logic is local to one route and trivial
|
|
- the helper is plain business logic with no FastAPI concerns
|
|
- the abstraction makes the signature harder to understand than the inline code would
|
|
|
|
Also avoid turning the dependency graph into a maze. If a reader has to chase six `Depends(...)` calls just to find the session, the abstraction is working against you.
|
|
|
|
## Preferred shape
|
|
|
|
```python
|
|
from typing import Annotated
|
|
from fastapi import Depends, HTTPException
|
|
|
|
|
|
def get_db() -> Generator[Session, None, None]:
|
|
with Session(engine) as session:
|
|
yield session
|
|
|
|
|
|
SessionDep = Annotated[Session, Depends(get_db)]
|
|
|
|
|
|
def get_current_user(session: SessionDep, token: TokenDep) -> User:
|
|
user = load_user_from_token(session, token)
|
|
if not user:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
return user
|
|
|
|
|
|
CurrentUser = Annotated[User, Depends(get_current_user)]
|
|
|
|
|
|
@router.get("/items/{id}")
|
|
def read_item(session: SessionDep, current_user: CurrentUser, id: UUID) -> ItemPublic:
|
|
...
|
|
```
|
|
|
|
Why this works:
|
|
|
|
- the route tells you exactly what it needs
|
|
- auth and session logic are reusable
|
|
- tests can swap dependencies without patching route internals
|
|
|
|
## Counterexamples
|
|
|
|
### Hidden wiring inside handlers
|
|
|
|
```python
|
|
@router.get("/items/{id}")
|
|
def read_item(id: UUID):
|
|
session = Session(engine)
|
|
token = parse_auth_header_somehow()
|
|
user = load_user(session, token)
|
|
...
|
|
```
|
|
|
|
Bad because resource and auth wiring are duplicated and easy to get inconsistently wrong.
|
|
|
|
### Business logic buried in dependencies
|
|
|
|
```python
|
|
def get_processed_report(...) -> Report:
|
|
# loads DB, runs domain rules, emails people, mutates state, etc.
|
|
```
|
|
|
|
Bad because dependencies should shape request collaborators, not become a second invisible service layer.
|
|
|
|
### Startup work disguised as a dependency
|
|
|
|
If initialization is app-wide and long-lived, it belongs in lifespan, not in a request dependency that quietly performs setup.
|
|
|
|
## Source signals
|
|
|
|
### FastAPI
|
|
|
|
- `docs_src/bigger_applications/app_an_py310/dependencies.py:6-13` uses a dedicated dependency callable for request validation and raises an HTTP error there.
|
|
- `fastapi/routing.py:95-136` wraps request handling in `AsyncExitStack`, which is the mechanism FastAPI uses to manage dependency lifetime, including `yield`-based dependencies.
|
|
- `tests/test_dependency_security_overrides.py:24-29` defines a route whose collaborators come from `Security(...)` and `Depends(...)` rather than inline setup.
|
|
- `tests/test_dependency_security_overrides.py:44-63` replaces those collaborators through `app.dependency_overrides`, which is the testing seam FastAPI expects you to use.
|
|
|
|
### Full-stack FastAPI Template
|
|
|
|
- `backend/app/api/deps.py:21-27` defines a yielded DB session dependency and exposes it as `SessionDep`.
|
|
- `backend/app/api/deps.py:30-57` decodes auth, loads the user, and raises semantic HTTP errors in a reusable current-user dependency.
|
|
- `backend/app/api/routes/items.py:13-16` shows the result in practice: the route signature receives `SessionDep` and `CurrentUser` directly.
|
|
|
|
### Starlette and FastAPI Users
|
|
|
|
- `starlette/applications.py:46-48` says lifespan is the preferred replacement for `on_startup` and `on_shutdown`, reinforcing the split between app-wide resource setup and request-time dependencies.
|
|
- `examples/beanie-oauth/app/app.py:17-28` initializes Beanie in lifespan.
|
|
- `examples/beanie-oauth/app/app.py:60-62` protects a route with `Depends(current_active_user)` instead of doing auth lookup inline.
|
|
|
|
## Bottom line
|
|
|
|
Dependencies should make request wiring more obvious, more reusable, and easier to test.
|
|
|
|
If a dependency hides core business behavior, it is probably the wrong abstraction.
|