Skip to content

Exception Handlers

This section is a complement to exceptions and more focused on the exception handler themselves.

Exception handlers are, as the name suggests, the handlers in case an exception of type X occurs in any level of an Esmerald application.

Exception handlers and the application levels

In every level the exception_handler parameter (among others) are available to be used and handle specific exeptions raised on each level.

The exception handlers are read from top-down in a python dictionary format, which means if you have the same exception being raised on different levels but different exception handlers handling them, the last one takes the priority.

from pydantic.error_wrappers import ValidationError

from esmerald import (
    Esmerald,
    Gateway,
    Include,
    JSONResponse,
    Request,
    ValidationErrorException,
    get,
)
from lilya import status


async def validation_error_exception_handler(
    request: Request, exc: ValidationError
) -> JSONResponse:
    extra = getattr(exc, "extra", None)
    if extra:
        return JSONResponse(
            {"detail": exc.detail, "errors": exc.extra.get("extra", {})},
            status_code=status.HTTP_400_BAD_REQUEST,
        )
    else:
        return JSONResponse(
            {"detail": exc.detail},
            status_code=status.HTTP_400_BAD_REQUEST,
        )


async def validation_error_gateway(request: Request, exc: ValidationError) -> JSONResponse:
    extra = getattr(exc, "extra", None)
    status_code = status.HTTP_400_BAD_REQUEST

    if extra:
        return JSONResponse(
            {"detail": exc.detail, "errors": exc.extra.get("extra", {})},
            status_code=status_code,
        )
    else:
        return JSONResponse(
            {"detail": exc.detail},
            status_code=status_code,
        )


@get("/me")
async def me(request: Request) -> str:
    return "Hello, world!"


app = Esmerald(
    routes=[
        Include(
            "/",
            routes=[
                Gateway(
                    handler=me,
                    exception_handlers={
                        ValidationErrorException: validation_error_gateway,
                    },
                )
            ],
        )
    ],
    exception_handlers={
        ValidationErrorException: validation_error_exception_handler,
    },
)

What is happening

The application level contains an exception handler validation_error_exception_handler and that means that for every ValidationErrorException being raised in the application it will be handled by that function except the Gateway that has its own handler validation_error_gateway.

The Gateway having its own exception handler to manage the ValidationErrorException takes precedent when the endpoint is called and the exception is raised.

Exceptions

All the levels are managed in a simple top-down approach where one takes priority over another as previously mentioned but.

Pior to version 1.0.0, a ChildEsmerald was an independent instance that is plugged into a main Esmerald application but since it is like another Esmerald instance that also means the ChildEsmerald didn't take priority over the top-level application.

In other words, a ChildEsmerald did not take priority over the main instance but the rules of prioritization of the levels inside a ChildEsmerald prevailed the same as for a normal Esmerald instance.

Some exceptions are still applied. For example, for dependencies and exception handlers, the rule of isolation and priority is still applied.

The same is applied also to dependencies.

Custom exception handlers

We all know that Esmerald handles really well the exceptions by design but sometimes we might also want to throw an error while doing some code logic that is not directly related with a data of an handler.

For example.

from pydantic import BaseModel

from esmerald import Esmerald, Gateway, JSONResponse, post


class DataIn(BaseModel):
    id: int
    name: str


@post("/create")
async def create(data: DataIn) -> JSONResponse:
    # Simple validation to raise ValueError
    if data.id > 20:
        raise ValueError("The ID must be less than 20.")


app = Esmerald(routes=[Gateway(handler=create)])

This example is a not usual at all but it serves to show where an exception is raised.

Esmerald offers one out of the box custom exception handlers:

  • value_error_handler - When you want the ValueError exception to be automatically parsed into a JSON.
from esmerald.exception_handlers import value_error_handler

How it would look like the previous example using this custom exception handler?

from pydantic import BaseModel, ValidationError

from esmerald import Esmerald, Gateway, JSONResponse, post
from esmerald.exception_handlers import pydantic_validation_error_handler, value_error_handler


class DataIn(BaseModel):
    id: int
    name: str


@post("/create")
async def create(data: DataIn) -> JSONResponse:
    # Simple validation to raise ValueError
    if data.id > 20:
        raise ValueError("The ID must be less than 20.")


app = Esmerald(
    routes=[Gateway(handler=create)],
    exception_handlers={
        ValueError: value_error_handler,
    },
)

Or if you prefer to place it on a Gateway level.

from pydantic import BaseModel, ValidationError

from esmerald import Esmerald, Gateway, JSONResponse, post
from esmerald.exception_handlers import pydantic_validation_error_handler, value_error_handler


class DataIn(BaseModel):
    id: int
    name: str


@post("/create")
async def create(data: DataIn) -> JSONResponse:
    # Simple validation to raise ValueError
    if data.id > 20:
        raise ValueError("The ID must be less than 20.")


app = Esmerald(
    routes=[
        Gateway(
            handler=create,
            exception_handlers={
                ValueError: value_error_handler,
            },
        )
    ],
)

Or even specific only to the handler itself.

from pydantic import BaseModel, ValidationError

from esmerald import Esmerald, Gateway, JSONResponse, post
from esmerald.exception_handlers import pydantic_validation_error_handler, value_error_handler


class DataIn(BaseModel):
    id: int
    name: str


@post(
    "/create",
    exception_handlers={
        ValueError: value_error_handler,
    },
)
async def create(data: DataIn) -> JSONResponse:
    # Simple validation to raise ValueError
    if data.id > 20:
        raise ValueError("The ID must be less than 20.")


app = Esmerald(
    routes=[Gateway(handler=create)],
)

As you can see, you can use this exception handler directly or as usual, you can create one of your own and apply on every application level.