Unit 07.00: Dependencies as shared logic, declared
A dependency is shared logic declared in the signature rather than called at the top of every handler.
One rule, two endpoints, enforced before either
A dependency requiring a request id, used by two endpoints.
The code calls both, with and without the header.
from fastapi import Depends, FastAPI, Header, HTTPException
from fastapi.testclient import TestClient
app = FastAPI()
def require_request_id(x_request_id: str | None = Header(default=None)) -> str:
if not x_request_id:
raise HTTPException(status_code=400,
detail={"error": "missing_request_id"})
return x_request_id
@app.get("/a")
def a(request_id: str = Depends(require_request_id)) -> dict:
return {"endpoint": "a", "request_id": request_id}
@app.get("/b")
def b(request_id: str = Depends(require_request_id)) -> dict:
return {"endpoint": "b", "request_id": request_id}
client = TestClient(app)
print(client.get("/a", headers={"X-Request-ID": "r-1"}).json())
print(f"missing header -> {client.get('/b').status_code}")
print("\nOne rule, declared in two signatures, enforced before either handler.")
The rule runs before either handler and neither endpoint contains the check. Adding a third endpoint means adding the parameter, not remembering the logic.
It also appears in the generated documentation, so a caller sees the header requirement rather than discovering it through a 400.
The mistake this prevents
The mistake is a helper function called as the first line of each handler. It works, and it is one an author can forget on the endpoint they add under time pressure - which will be the one that matters.
Takeaway
Declare shared request-time rules as dependencies. They run before the handler, appear in the docs, and cannot be forgotten by a new endpoint.
