Complete worked example
python
from typing import Annotated, Literal
from fastapi import FastAPI, Query
from fastapi.testclient import TestClient
from pydantic import BaseModel
class Role(BaseModel):
id: int
country: str
class Page(BaseModel):
country: str
observed_total: int
items: list[Role]
def inspect_example(query):
records = [{"id": 1, "country": "US"}, {"id": 2, "country": "IL"},
{"id": 3, "country": "UA"}, {"id": 4, "country": "UA"}]
app = FastAPI()
@app.get("/roles", response_model=Page)
def read(country: Literal["US", "IL", "UA"],
limit: Annotated[int, Query(ge=1, le=2)] = 2,
offset: Annotated[int, Query(ge=0)] = 0):
ordered = sorted(records, key=lambda record: record["id"])
selected = [record for record in ordered if record["country"] == country]
return {"country": country, "observed_total": len(selected),
"items": selected[offset:offset + limit]}
with TestClient(app) as client:
response = client.get("/roles", params=query)
body = response.json()
if response.status_code == 422:
body = sorted({e["type"] for e in body["detail"]})
return {"status": response.status_code, "body": body}- Reasoning
- The route validates the country vocabulary, sorts by stable ID, selects matching records, then applies offset and limit. The observed total is computed from the filtered collection before pagination. The returned items are therefore a page of that declared scope. A small Pydantic response envelope records country, observed_total and items. Tests exercise a full page, a later offset and an invalid country. The local data intentionally starts with other countries so an incorrectly early limit cannot accidentally appear correct for every test.