MiddleCodeOccasionalNot answered yet
Reusable pagination dependency with validated bounds
Several list endpoints need the same limit and offset query parameters. Write one reusable dependency that supplies both, so each endpoint declares it with a single argument instead of repeating the parameters.
Requirements:
do not validate or clamp inside the handler
limitdefaults to20and must stay within1..100offsetdefaults to0and must never be negative- out-of-range values are rejected by FastAPI with a
422before the endpoint runs —
from fastapi import Depends, FastAPI, Query
app = FastAPI()
def pagination(...):
# your code here
...
@app.get("/items")
def list_items(page=Depends(pagination)):
return {"page": page}Write the implementation.
Declare limit and offset as ordinary parameters of a plain function with Query(ge=..., le=...) bounds and return them. FastAPI resolves a dependency's own parameters exactly as it resolves an endpoint's, so the bounds are enforced before the handler runs and a bad value yields 422.
- ✗Reading the query string manually instead of declaring parameters on the dependency
- ✗Clamping out-of-range values silently instead of letting
422reject them - ✗Believing
geandleonQuery()are documentation only
- →How would you share this dependency across every route in a router?
- →What changes if two endpoints need different
limitceilings?
Contents
Task
Move limit and offset into a single dependency so FastAPI itself enforces the bounds and an endpoint declares pagination with one argument.
Solution
from typing import Annotated
from fastapi import Depends, FastAPI, Query
app = FastAPI()
def pagination(
limit: Annotated[int, Query(ge=1, le=100)] = 20,
offset: Annotated[int, Query(ge=0)] = 0,
) -> dict[str, int]:
return {"limit": limit, "offset": offset}
# type alias: declaring pagination collapses to a single annotation
PageParams = Annotated[dict[str, int], Depends(pagination)]
@app.get("/items")
def list_items(page: PageParams):
return {"page": page}
@app.get("/orders")
def list_orders(page: PageParams):
return {"page": page}Key points
- A dependency's parameters are resolved by the same machinery as an endpoint's, so
Query(ge=..., le=...)behaves identically here. - ⚠️
?limit=500produces a422with a per-field error list before the handler is entered — clamping is unnecessary and harmful: the client never learns its parameter was ignored. - The
Annotated[..., Depends(...)]alias removes= Depends(pagination)from every signature and leaves one annotation per endpoint. - The dependency's result is cached for the request, so two sub-dependencies asking for pagination receive the same object.
Contents