Complete worked example
python
from typing import Annotated
from fastapi import FastAPI, Form
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
class Preferences(BaseModel):
model_config = ConfigDict(extra="forbid")
notifications: bool = True
nonce: str = "demo"
def inspect_example(request):
app = FastAPI()
@app.post("/preferences")
def save(preferences: Annotated[Preferences, Form()],
nonce: Annotated[str, Form()] = "demo"):
return {"effective": preferences.model_dump(),
"supplied": preferences.model_dump(exclude_unset=True), "nonce": nonce}
with TestClient(app) as client:
response = client.post("/preferences", data=request)
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 form model forbids extras and declares the nonce field used by a sibling Form parameter. Since FastAPI 0.160, the model extra policy considers all fields at that request location, including sibling parameters and dependencies. Declaring a shared form field explicitly prevents an intended sibling value from becoming an accidental extra. The endpoint returns normalized settings, the supplied-field projection and the sibling nonce. The fixture sends local form data with python-multipart installed from the pinned lock. It never uploads files or connects to a real account. Tests compare omission, an explicit false and an unexpected admin field.