Customization and extension¶
Your own response model¶
By default the error body is {code, detail}. To add your own fields (an envelope with a status, say), subclass ErrorResponse generically and point your base error class's response_base at it.
from typing import Any, ClassVar, Literal
from fastapi_typed_errors import BaseError, ErrorResponse
class MyErrorResponse[T: str](ErrorResponse[T]):
status: Literal["error"] = "error" # (1)!
class AppError[T: str](BaseError[T]):
response_base: ClassVar[type[ErrorResponse[Any]]] = MyErrorResponse
class NotFoundError(AppError[Literal["NOT_FOUND"]]):
http_status = HTTPStatus.NOT_FOUND
- The extra field sits next to
code/detail.
The response body becomes:
Flat extensions only
The shape must stay flat. Nested envelopes like {"status": ..., "data": {"code": ...}} are not supported: pydantic discriminated unions require the code discriminator at the top level of the model, otherwise error_models() breaks for several errors on one status.
Bare string codes¶
You bring your own enum — but it isn't required. A code can be a bare string in a Literal:
A StrEnum is nicer when there are many codes and you want a single registry; a bare Literal — for one-offs.
description and headers¶
description: ClassVar[str | None]— the defaultdetailand the status description in OpenAPI.- Headers pass through to
HTTPException:
class RequiredTokenError(BaseError[Literal["REQUIRED_TOKEN"]]):
http_status = HTTPStatus.UNAUTHORIZED
description = "Token required"
raise RequiredTokenError(headers={"WWW-Authenticate": "Bearer"})
Intermediate bases¶
Factor shared configuration (your own response_base, a common code prefix) into an intermediate generic base — it is parametrized with a TypeVar and is not required to declare a code:
class AppError[T: str](BaseError[T]):
response_base = MyErrorResponse
# no http_status here — this is not a concrete error
class NotFoundError(AppError[Literal["NOT_FOUND"]]):
http_status = HTTPStatus.NOT_FOUND
The metaclass skips bases parametrized with a TypeVar and derives error_code/model only for concrete subclasses.
ABC compatibility¶
BaseError subclasses HTTPException and uses the BaseErrorMeta metaclass. Mixing it with ABC (which has its own ABCMeta) triggers a metaclass conflict. The fix is a combined metaclass:
from abc import ABCMeta, abstractmethod
from fastapi_typed_errors.core.base import BaseErrorMeta # (1)!
class ABCErrorMeta(BaseErrorMeta, ABCMeta): ...
class AbstractError[T: str](BaseError[T], metaclass=ABCErrorMeta):
@abstractmethod
def audit(self) -> None: ...
BaseErrorMetais deliberately not re-exported from the package root — import it fromfastapi_typed_errors.core.base.
Explicit registration is a principle¶
The package hides no magic behind install(app) helpers. The handler is wired by hand:
This is the same style as app.add_middleware(...): configuring the app stays explicit and under your control.
Next: Recipes — common practical patterns.