Initial extracted documentation set
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user