fastapi-typed-errors¶
Typed HTTP errors for FastAPI. Exact Literal codes in OpenAPI, discriminated oneOf unions on the code field, and a single source of truth — the error class itself.
Why¶
In plain FastAPI an error is HTTPException(404, "..."). The error code lives in a string, OpenAPI has neither a code type nor a response-body model, and the list of errors a route can raise has to be duplicated by hand into responses={}. Clients can't switch on the code, and the docs drift away from the code fast.
fastapi-typed-errors makes an error a class: the HTTP status, machine-readable code and response model are declared once and derived automatically.
@app.get("/items/{item_id}")
def get_item(item_id: int):
if item_id == 0:
raise HTTPException(404, "No item") # (1)!
return {"item_id": item_id}
- The error code is just a string in
detail; in OpenAPI the 404 has neither an exact code nor a body schema.
class NotFoundError(BaseError[Literal[ErrorCode.NOT_FOUND]]):
http_status = HTTPStatus.NOT_FOUND
@router.get("/items/{item_id}")
def get_item(item_id: int) -> Annotated[Item, Raises[NotFoundError]]: # (1)!
if item_id == 0:
raise NotFoundError("No item")
return Item(item_id=item_id)
- The declared error lands in
responsesautomatically: a 404 with the exactLiteral["NOT_FOUND"]and a{code, detail}body.
router = with_errors(APIRouter(), auto=True) # (1)!
@router.get("/items/{item_id}")
def get_item(item_id: int) -> Item: # (2)!
if item_id == 0:
raise NotFoundError("No item")
return Item(item_id=item_id)
- Turn on auto-fill once, on the router.
- Not a single marker —
responsesbuild themselves: the walker findsraise NotFoundError(and errors raised by dependencies) statically. See auto=True.
The error response body is always predictable:
And in OpenAPI the route gets one entry per status — with an exact code and a body model. Here is how it renders in Swagger UI:
Features¶
-
Single source of truth
Status, code and response model are declared once in the error class. The metaclass derives the rest.
-
Exact types in OpenAPI
Every status gets an exact
Literalcode; several errors on one status become a discriminatedoneOfunion oncode. -
Declared right in the annotation
-> Annotated[Item, Raises[NotFoundError, ForbiddenError]]— andresponsesfill themselves. -
CI contract checking
check_raisescompares what's declared against what's actually raised — in the endpoint and its dependencies. -
Auto-fill
with_errors(router, auto=True)finds errors statically and fillsresponseswithout a single marker. -
Extensible
Your own envelope response model, your own codes (
StrEnumor a bareLiteral), compatibility withABC.
Install¶
Requirements
- Python 3.12+ (PEP 695 generics)
- FastAPI ≥ 0.115 (the
0.137–0.138versions are excluded — see Limitations) - Pydantic ≥ 2.9
Next¶
- Quickstart — a working app in a minute.
- Guide — concepts, decorator, checker, extension.
- API Reference — signatures from the docstrings.