Files
fastapi-toolsets/docs/module/exceptions.md
d3vyce 05b5a2c876 feat: rework Exception/ApiError (#107)
* feat: rework Exception/ApiError

* docs: update exceptions module

* fix: docstring
2026-03-02 16:34:29 +01:00

5.1 KiB

Exceptions

Structured API exceptions with consistent error responses and automatic OpenAPI documentation.

Overview

The exceptions module provides a set of pre-built HTTP exceptions and a FastAPI exception handler that formats all errors — including validation errors — into a uniform ErrorResponse.

Setup

Register the exception handlers on your FastAPI app at startup:

from fastapi import FastAPI
from fastapi_toolsets.exceptions import init_exceptions_handlers

app = FastAPI()
init_exceptions_handlers(app=app)

This registers handlers for:

  • ApiException — all custom exceptions below
  • HTTPException — Starlette/FastAPI HTTP errors
  • RequestValidationError — Pydantic request validation (422)
  • ResponseValidationError — Pydantic response validation (422)
  • Exception — unhandled errors (500)

It also patches app.openapi() to replace the default Pydantic 422 schema with a structured example matching the ErrorResponse format.

Built-in exceptions

Exception Status Default message
UnauthorizedError 401 Unauthorized
ForbiddenError 403 Forbidden
NotFoundError 404 Not Found
ConflictError 409 Conflict
NoSearchableFieldsError 400 No Searchable Fields
InvalidFacetFilterError 400 Invalid Facet Filter
InvalidOrderFieldError 422 Invalid Order Field

Per-instance overrides

All built-in exceptions accept optional keyword arguments to customise the response for a specific raise site without changing the class defaults:

Argument Effect
detail Overrides both str(exc) (log output) and the message field in the response body
desc Overrides the description field
data Overrides the data field
raise NotFoundError(detail="User 42 not found", desc="No user with that ID exists in the database.")

Custom exceptions

Subclass ApiException and define an api_error class variable:

from fastapi_toolsets.exceptions import ApiException
from fastapi_toolsets.schemas import ApiError

class PaymentRequiredError(ApiException):
    api_error = ApiError(
        code=402,
        msg="Payment Required",
        desc="Your subscription has expired.",
        err_code="BILLING-402",
    )

!!! warning Subclasses that do not define api_error raise a TypeError at class creation time, not at raise time.

Custom __init__

Override __init__ to compute detail, desc, or data dynamically, then delegate to super().__init__():

class OrderValidationError(ApiException):
    api_error = ApiError(
        code=422,
        msg="Order Validation Failed",
        desc="One or more order fields are invalid.",
        err_code="ORDER-422",
    )

    def __init__(self, *field_errors: str) -> None:
        super().__init__(
            f"{len(field_errors)} validation error(s)",
            desc=", ".join(field_errors),
            data={"errors": [{"message": e} for e in field_errors]},
        )

Intermediate base classes

Use abstract=True when creating a shared base that is not meant to be raised directly:

class BillingError(ApiException, abstract=True):
    """Base for all billing-related errors."""

class PaymentRequiredError(BillingError):
    api_error = ApiError(code=402, msg="Payment Required", desc="...", err_code="BILLING-402")

class SubscriptionExpiredError(BillingError):
    api_error = ApiError(code=402, msg="Subscription Expired", desc="...", err_code="BILLING-402-EXP")

OpenAPI response documentation

Use generate_error_responses to add error schemas to your endpoint's OpenAPI spec:

from fastapi_toolsets.exceptions import generate_error_responses, NotFoundError, ForbiddenError

@router.get(
    "/users/{id}",
    responses=generate_error_responses(NotFoundError, ForbiddenError),
)
async def get_user(...): ...

Multiple exceptions sharing the same HTTP status code are grouped under one entry, each appearing as a named example keyed by its err_code. This keeps the OpenAPI UI readable when several error variants map to the same status.


:material-api: API Reference