Skip to content

# 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.

API Reference