Skip to content

Custom Handler

In the event that you are using a third party library with a custom error class, a handler specific to a common base class can be provided.

Providing a custom handler allows for conversion from the custom error class into a Problem, when the base exception handler catches it, rather than converting each raised instance into a Problem at the time it is raised.

This does not replace the base exception handler, but provides it with additional capability to handle custom exceptions during the handling of any given exception.

Usage

Given a third_party library with a error.py module.

class CustomBaseError(Exception):
    def __init__(reason: str, debug: str):
        self.reason = reason
        self.debug = debug

A custom handler can then be defined in your application.

import fastapi
from rfc9457 import error_class_to_type
from fastapi_problem.error import Problem
from fastapi_problem.handler import ExceptionHandler, add_exception_handler, new_exception_handler
from starlette.requests import Request

from third_party.error import CustomBaseError

def my_custom_handler(eh: ExceptionHandler, request: Request, exc: CustomBaseError) -> Problem:
    return Problem(
        title=exc.reason,
        detail=exc.debug,
        type_=error_class_to_type(exc),
        status=500,
        headers={"x-custom-header": "value"},
    )

app = fastapi.FastAPI()
eh = new_exception_handler(
    handlers={
        CustomBaseError: my_custom_handler,
    },
)
add_exception_handler(app, eh)

Any instance of CustomBaseError, or any subclasses, that reach the exception handler will then be converted into a Problem response, as opposed to an unhandled error response. Once the exception has been processed by the custom handler, the exception handler continues as before including any existing logging logic.

Builtin Handlers

Starlette HTTPException and fastapi RequestValidationError instances are handled by default, to customise how these errors are processed, provide a handler for starlette.exceptions.HTTPException or fastapi.exceptions.RequestValidationError, similar to the custom handlers previously defined, but rather than passing it to handlers, use the http_exception_handler and request_validation_handler parameters respectively.

import fastapi
from fastapi_problem.error import Problem
from fastapi_problem.handler import ExceptionHandler, add_exception_handler, new_exception_handler
from starlette.exceptions import HTTPException
from starlette.requests import Request


def my_custom_handler(eh: ExceptionHandler, request: Request, exc: HTTPException) -> Problem:
    return Problem(...)


app = fastapi.FastAPI()
eh = new_excep, new_exception_handler(
    http_exception_handler=my_custom_handler,
)
add_exception_handler(app, eh)

Optional handling

In some cases you may want to handle specific cases for a type of exception, but let others defer to another handler. In these scenarios, a custom handler can return None rather than a Problem. If a handler returns None the exception will be pass to the next defined handler.

import fastapi
from rfc9457 import error_class_to_type
from fastapi_problem.error import Problem
from fastapi_problem.handler import ExceptionHandler, add_exception_handler, new_exception_handler
from starlette.requests import Request

def no_response_handler(eh: ExceptionHandler, request: Request, exc: RuntimeError) -> Problem | None:
    if str(exc) == "No response returned.":
        return Problem(
            title="No response returned.",
            detail="starlette bug",
            type_="no-response",
            status=409,
        )
    return None

def base_handler(eh: ExceptionHandler, request: Request, exc: Exception) -> Problem:
    return Problem(
        title=exc.reason,
        detail=exc.debug,
        type_=error_class_to_type(exc),
        status=500,
    )

app = fastapi.FastAPI()
eh = new_exception_handler(
    handlers={
        RuntimeError: no_response_handler,
        Exception: base_handler,
    },
)
add_exception_handler(app, eh)

At the time of writing there was (is?) a bug in starlette that would cause middlewares to error. To prevent these from reaching Sentry, a deferred handler was implemented in the impacted project.