Managing Response Models, Status Codes, File Handling, and Error Processing in FastAPI
Selecting and Filtering Output with Response Models
When an endpoint uses a Pydantic schema as its output model, you can control wich fields appear in the JSON response. The parameter response_model_exclude_unset skips fields that retain their default values. Only explicit provided data is sent back, even if the provided value happens to match the default.
from typing import Optional
from fastapi import APIRouter
from pydantic import BaseModel, EmailStr
router = APIRouter()
class UserCreate(BaseModel):
name: str
secret: str
contact: EmailStr
phone: str = "10086"
location: str = None
alias: Optional[str] = None
class UserView(BaseModel):
name: str
contact: EmailStr
phone: str = "10086"
location: str = None
alias: Optional[str] = None
fake_users = {
"a1": {"name": "a1", "secret": "abc", "contact": "a1@mail.com"},
}
@router.post(
"/users",
response_model=UserView,
response_model_exclude_unset=True
)
def add_user(payload: UserCreate):
"""Only supplied fields and unchanged defaults show up when `exclude_unset` is enabled."""
print(payload.secret) # secret is excluded from response
return fake_users["a1"]
For targeted control, response_model_include specifies the exact set of output fields, while response_model_exclude removes particular fields regardless of thier values.
@router.post(
"/users/custom",
response_model=UserView,
response_model_include=["name", "contact", "phone"],
response_model_exclude=["phone"]
)
def custom_output(payload: UserCreate):
"""Whitelist fields with include, then blacklist with exclude."""
return payload
Status Codes and Route Metadata
Assign a status code to a route directly with status_code or use FastAPI’s status constants for clarity.
from fastapi import status
@router.post("/echo-status", status_code=204)
def echo_nothing():
return None
@router.get("/code-detail", status_code=status.HTTP_202_ACCEPTED)
def detail_status():
return {"accepted": True}
Enhance OpenAPI documentation and mark a deprecated endpoint using Path Operation Configuration arguments.
@router.patch(
"/legacy",
response_model=UserView,
summary="Update user legacy",
description="This path is replaced by a newer version.",
response_description="Updated user data",
deprecated=True,
status_code=status.HTTP_200_OK
)
def legacy_update(payload: UserCreate):
return payload.dict()
Processing Form Data
Parse submitted HTML forms by declaring Form fields in the route signature.
from fastapi import Form
@router.post("/session/login")
def handle_login(account: str = Form(...), credential: str = Form(...)):
return {"account": account}
Uploading Files
Small files can be read entirely as bytes, while larger or stream-oriented uploads use UploadFile, which offers asynchronous methods and access to metadata.
from fastapi import File, UploadFile
from typing import List
@router.post("/upload/bytes")
def upload_bytes(raw: bytes = File(...)):
return {"size_received": len(raw)}
@router.post("/upload/multiple")
async def upload_many(documents: List[UploadFile] = File(...)):
for doc in documents:
data = await doc.read()
# process data
first = documents[0]
return {"name": first.filename, "mime": first.content_type}
Customizing Error Responses
Raise an HTTPException with a dedicated status code and optional headers to signal problems.
from fastapi import HTTPException
@router.get("/cities/{name}")
def lookup_city(name: str):
if name != "Tokyo":
raise HTTPException(status_code=404, detail="City unknown", headers={"X-Reason": "Not found"})
return {"city": name}
Override the built-in handlers for HTTPException and RequestValidationError to return custom response formats.
from fastapi import Request
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as BaseHTTPException
from fastapi.exceptions import RequestValidationError
from main import app
@app.exception_handler(BaseHTTPException)
async def custom_http_handler(request: Request, exc: BaseHTTPException):
return PlainTextResponse(f"Error: {exc.detail}", status_code=exc.status_code)
@app.exception_handler(RequestValidationError)
async def custom_validation_handler(request: Request, exc: RequestValidationError):
return PlainTextResponse(f"Invalid input: {exc.errors()}", status_code=400)