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.