Skip to content

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.