Examples¶
Every example is a complete, runnable app, imported and checked by the test suite. Run one with uvicorn examples.<name>:app --reload from a checkout of the repository, then open http://127.0.0.1:8000/docs.
basic.py¶
The default setup: the validation error at 400.
Show the code
"""
Minimal setup: override the default 422 validation error with 400.
Run:
uvicorn examples.basic:app --reload
"""
from typing import Any
from fastapi import FastAPI
from pydantic import BaseModel
from fastapi_validation_override import override_validation_error
app = FastAPI(
title="Basic Example",
description="Replace the default 422 validation error with 400.",
)
class Item(BaseModel):
name: str
price: float
@app.post("/items")
async def create_item(item: Item) -> dict[str, Any]:
return item.model_dump()
override_validation_error(app)
custom_status_code.py¶
The validation error at 409.
Show the code
"""
Use a custom status code (409) instead of the default 400.
Run:
uvicorn examples.custom_status_code:app --reload
"""
from typing import Any
from fastapi import FastAPI
from pydantic import BaseModel
from fastapi_validation_override import override_validation_error
app = FastAPI(
title="Custom Status Code Example",
description="Use a custom status code (409) instead of the default 400.",
)
class Item(BaseModel):
name: str
price: float
@app.post("/items")
async def create_item(item: Item) -> dict[str, Any]:
return item.model_dump()
override_validation_error(app, status_code=409)
error_builder.py¶
The error body and its schema from a Pydantic model, with error_builder.
Show the code
"""
Replace the validation error body and its schema with your own Pydantic model.
Run:
uvicorn examples.error_builder:app --reload
"""
from typing import Any
from fastapi import FastAPI
from fastapi.exceptions import RequestValidationError
from pydantic import BaseModel
from fastapi_validation_override import override_validation_error
app = FastAPI(
title="Error Builder Example",
description="Validation errors are returned and documented as ErrorEnvelope at 400.",
)
class FieldError(BaseModel):
field: str
message: str
class ErrorEnvelope(BaseModel):
code: str = "VALIDATION_ERROR"
errors: list[FieldError]
def build_error(exc: RequestValidationError) -> ErrorEnvelope:
return ErrorEnvelope(
errors=[FieldError(field=".".join(map(str, error["loc"])), message=error["msg"]) for error in exc.errors()]
)
class Item(BaseModel):
name: str
price: float
@app.post("/items")
async def create_item(item: Item) -> dict[str, Any]:
return item.model_dump()
@app.get("/health")
async def health() -> dict[str, str]:
return {"status": "ok"}
override_validation_error(app, error_builder=build_error)
problem_details.py¶
RFC 9457 Problem Details as application/problem+json, next to an application/json error at the same code.
Show the code
"""
Return validation errors as RFC 9457 Problem Details, with the application/problem+json content type.
The 400 of POST /orders documents two errors under two content types: OutOfStock under
application/json, declared by the route, and the validation error under application/problem+json.
Run:
uvicorn examples.problem_details:app --reload
"""
from typing import Any
from fastapi import FastAPI
from fastapi.exceptions import RequestValidationError
from pydantic import BaseModel
from fastapi_validation_override import override_validation_error
app = FastAPI(
title="Problem Details Example",
description="Validation errors are returned and documented as application/problem+json.",
)
class Problem(BaseModel):
type: str = "about:blank"
title: str
status: int
detail: str
def build_problem(exc: RequestValidationError) -> Problem:
return Problem(title="Validation failed", status=400, detail=f"{len(exc.errors())} invalid field(s)")
class OutOfStock(BaseModel):
item_name: str
available: int
class Item(BaseModel):
name: str
quantity: int
@app.post("/items")
async def create_item(item: Item) -> dict[str, Any]:
return item.model_dump()
@app.post("/orders", responses={400: {"model": OutOfStock, "description": "Out of stock"}})
async def create_order(item: Item) -> dict[str, Any]:
return item.model_dump()
override_validation_error(app, error_builder=build_problem, media_type="application/problem+json")
existing_response_at_target_code.py¶
The validation error merged with anyOf into a response the route already declares at 400.
Show the code
"""
Merge the validation error schema into a response already declared at the target status code.
Run:
uvicorn examples.existing_response_at_target_code:app --reload
"""
from typing import Any
from fastapi import FastAPI
from pydantic import BaseModel
from fastapi_validation_override import override_validation_error
app = FastAPI(
title="Existing Response at Target Code Example",
description="When a route already declares a response at the target status code, "
"the validation error schema is merged into it using anyOf.",
)
class Item(BaseModel):
name: str
price: float
class OutOfStockError(BaseModel):
message: str
item_name: str
@app.post("/items", responses={400: {"model": OutOfStockError, "description": "Out of stock"}})
async def create_item(item: Item) -> dict[str, Any]:
return item.model_dump()
override_validation_error(app)
custom_422_response.py¶
A route's own 422 kept next to the validation error at 400.
Show the code
"""
Keep a route's own, non-validation 422 response; the validation error is still documented
at the target status code independently of what occupies 422.
Run:
uvicorn examples.custom_422_response:app --reload
"""
from typing import Any
from fastapi import FastAPI
from pydantic import BaseModel
from fastapi_validation_override import override_validation_error
app = FastAPI(
title="Custom 422 Response Example",
description="A route can keep its own domain-specific 422 response; "
"the validation error is still documented at the target status code.",
)
class Item(BaseModel):
name: str
price: float
class OutOfStockError(BaseModel):
error_code: str
item_name: str
@app.post("/items", responses={422: {"model": OutOfStockError, "description": "Item is out of stock"}})
async def create_item(item: Item) -> dict[str, Any]:
return item.model_dump()
override_validation_error(app)
# The 422 response above is left untouched: it still documents OutOfStockError.
# The 400 response is added automatically, documenting the validation error schema,
# because this route still validates its request body regardless of what occupies 422.
handle_exceptions_false.py¶
Your own exception handler, with only the schema patched.
Show the code
"""
Patch only the OpenAPI schema and handle the validation exception yourself.
Run:
uvicorn examples.handle_exceptions_false:app --reload
"""
from typing import Any
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from fastapi_validation_override import override_validation_error
app = FastAPI(
title="Custom Exception Handler Example",
description="Patch only the OpenAPI schema and handle the exception with custom logic.",
)
class Item(BaseModel):
name: str
price: float
@app.exception_handler(RequestValidationError)
async def custom_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
return JSONResponse(
status_code=400,
content={
"message": "Validation failed",
"errors": exc.errors(),
},
)
@app.post("/items")
async def create_item(item: Item) -> dict[str, Any]:
return item.model_dump()
override_validation_error(app, status_code=400, handle_exceptions=False)
custom_validation_schema.py¶
FastAPI's own validation error definitions reassigned to a custom error envelope, with a handler that sends it.
Show the code
"""
Replace the validation error schema with a custom error envelope.
Run:
uvicorn examples.custom_validation_schema:app --reload
"""
from typing import Any
import fastapi.openapi.utils as fastapi_openapi_utils
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from fastapi_validation_override import override_validation_error
# Match an existing company-wide error envelope instead of FastAPI's default {"detail": [...]}.
# Must be reassigned before override_validation_error runs, since it reads these at call time.
fastapi_openapi_utils.validation_error_definition = { # ty: ignore[invalid-assignment]
"title": "ValidationError",
"type": "object",
"properties": {
"field": {"type": "array", "items": {"type": "string"}},
"message": {"type": "string"},
"issue_type": {"type": "string"},
},
}
fastapi_openapi_utils.validation_error_response_definition = {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"error_code": {"type": "string", "default": "VALIDATION_ERROR"},
"message": {"type": "string", "default": "One or more fields failed validation"},
"fields": {"type": "array", "items": {"$ref": "#/components/schemas/ValidationError"}},
},
}
app = FastAPI(
title="Custom Validation Schema Example",
description="Documents and returns validation errors using a custom error envelope "
"instead of FastAPI's default shape.",
)
class Item(BaseModel):
name: str
price: float
@app.exception_handler(RequestValidationError)
async def validation_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
return JSONResponse(
status_code=400,
content={
"error_code": "VALIDATION_ERROR",
"message": "One or more fields failed validation",
"fields": [
{"field": [str(part) for part in error["loc"]], "message": error["msg"], "issue_type": error["type"]}
for error in exc.errors()
],
},
)
@app.post("/items")
async def create_item(item: Item) -> dict[str, Any]:
return item.model_dump()
override_validation_error(app, status_code=400, handle_exceptions=False)
# handle_exceptions=False: the handler above is already responsible for matching the schema above.
custom_openapi.py¶
A custom app.openapi function, kept and patched.
Show the code
"""
Preserve a custom app.openapi function: the library wraps it and patches its output on top.
Run:
uvicorn examples.custom_openapi:app --reload
"""
from typing import Any
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
from pydantic import BaseModel
from fastapi_validation_override import override_validation_error
app = FastAPI(
title="Custom OpenAPI Example",
description="The developer's custom app.openapi function is preserved and patched on top.",
)
class Item(BaseModel):
name: str
price: float
@app.post("/items")
async def create_item(item: Item) -> dict[str, Any]:
return item.model_dump()
def custom_openapi() -> dict[str, Any]:
if app.openapi_schema:
return app.openapi_schema
schema = get_openapi(title="My API", version="1.0.0", routes=app.routes)
schema["info"]["x-logo"] = {"url": "https://example.com/logo.png"}
app.openapi_schema = schema
return schema
app.openapi = custom_openapi # type: ignore[method-assign] # ty: ignore[invalid-assignment]
override_validation_error(app)
with_apirouter.py¶
Routes split across several APIRouters.
Show the code
"""
The override applies to every route regardless of how it's organized across APIRouters.
Run:
uvicorn examples.with_apirouter:app --reload
"""
from typing import Any
from fastapi import APIRouter, FastAPI
from pydantic import BaseModel
from fastapi_validation_override import override_validation_error
app = FastAPI(
title="APIRouter Example",
description="Override applies to all routes regardless of how they are organized.",
)
items_router = APIRouter(prefix="/items", tags=["items"])
users_router = APIRouter(prefix="/users", tags=["users"])
class Item(BaseModel):
name: str
price: float
class User(BaseModel):
username: str
age: int
@items_router.post("")
async def create_item(item: Item) -> dict[str, Any]:
return item.model_dump()
@users_router.post("")
async def create_user(user: User) -> dict[str, Any]:
return user.model_dump()
app.include_router(items_router)
app.include_router(users_router)
override_validation_error(app)
webhooks_and_callbacks.py¶
The validation error in webhooks and callbacks.
Show the code
"""
The override covers webhooks and callbacks the same way it covers regular paths.
Run:
uvicorn examples.webhooks_and_callbacks:app --reload
"""
from typing import Any
from fastapi import APIRouter, FastAPI
from pydantic import BaseModel
from fastapi_validation_override import override_validation_error
app = FastAPI(
title="Webhooks and Callbacks Example",
description="The override covers webhooks and callbacks the same way it covers regular paths.",
)
class Item(BaseModel):
name: str
price: float
class ItemUpdate(BaseModel):
status: str
@app.webhooks.post("item-created")
async def item_created_webhook(item: Item) -> None:
"""Sent to subscribers when an item is created."""
callback_router = APIRouter()
@callback_router.post("{$callback_url}/updated")
async def item_updated_callback(update: ItemUpdate) -> None: ...
@app.post("/items", callbacks=callback_router.routes)
async def create_item(item: Item) -> dict[str, Any]:
return item.model_dump()
override_validation_error(app)
# The webhook and the callback each get their own 400 response documented, independently of
# /items and of each other.