Unit 04.03: Combining all three in one endpoint
One handler signature can draw from four different parts of the request.
Path, query, body and header together
An endpoint taking two path segments, a query flag, a body and a header.
The code calls it with all four.
from fastapi import FastAPI, Header, Query
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
class Update(BaseModel):
status: str
@app.patch("/accounts/{account_id}/invoices/{invoice_id}")
def update(account_id: str, invoice_id: str, body: Update,
notify: bool = Query(default=False),
x_request_id: str | None = Header(default=None)) -> dict:
return {"account": account_id, "invoice": invoice_id,
"status": body.status, "notify": notify, "request_id": x_request_id}
client = TestClient(app)
r = client.patch("/accounts/ACC-1/invoices/INV-9?notify=true",
json={"status": "paid"}, headers={"X-Request-ID": "r-8841"})
print(r.json())
print("\nFour sources, one signature: two path parts, a query flag, a body,")
print("and a header. FastAPI decides which is which from the declaration.")
FastAPI decides which is which from the declaration: names matching the path template are path parameters, Pydantic models are the body, and Header marks a header. Nothing is positional or guessed.
The header name conversion is worth knowing - x_request_id in Python corresponds to X-Request-ID on the wire, because hyphens are not valid in an identifier.
The mistake this prevents
The mistake is reading the raw request object to get at a header or a query value. It works and it removes the value from the schema, from the validation and from the documentation all at once.
Takeaway
Declare every input in the signature. FastAPI resolves the source from the declaration, and anything read from the raw request is invisible to the contract.
