Skip to content

Exceptions

Esmerald comes with some built-in exceptions but also allows you to install custom exception handlers to deal with how you return responses when exceptions happen.

HTTPException

The HTTPException object serves as base that can be used for any handled exception from Esmerald.

from esmerald.exceptions import HTTPException

ImproperlyConfigured

The name might be familiar for some of the developers out there and it is intentional as it is also self explanatory. Inherits from the base HTTPException and it is raised when a misconfiguration occurs.

from esmerald.exceptions import ImproperlyConfigured

Status code: 500

NotAuthenticated

Exception raised when an a resources that depends of an authenticated user does not exist.

from esmerald.exceptions import NotAuthenticated

Status code: 401

PermissionDenied

Exception raised when a permission fails. It can be used in any context also outside of the permissions context and it should be raised any time the access to a resource should be blocked.

from esmerald.exceptions import PermissionDenied

Status code: 403

ValidationErrorException

ValidationErrorException is part of the Esmerald default exception_handlers by design and it is part of its core when a validation, for example, from pydantic models, occurs.

from esmerald.exceptions import ValidationErrorException

Status code: 400

NotAuthorized

Exception raised when an authentication fails. It is very useful for any authentication middleware process and it is encouraged to be applied in any custom middleware handling with similar processes.

from esmerald.exceptions import NotAuthorized

Status code: 401

NotFound

Classic and self explanatory exception. Useful when a resource is not found or a simple 404 needs to be raised.

from esmerald.exceptions import NotFound

Status code: 404

MethodNotAllowed

Very useful exception to be used, as already is, to raise exceptions when an HTTP method is not allows on a given Gateway.

from esmerald.exceptions import MethodNotAllowed

Status code: 405

InternalServerError

Used internally for internal server error and it raises a descriptive message in the browser if debug=True.

from esmerald.exceptions import InternalServerError

Status code: 500

ServiceUnavailable

It should be used to be raised when a resource is not available.

from esmerald.exceptions import ServiceUnavailable

Status code: 503

ValidatorError

This is a special exception that can be applied to anything in Esmerald. This allows to not only throw a normal exception but filter it out the "noise" that can be caused by using an external library and provide a simple response.

from esmerald.exceptions import ValidationError

Status code: 400

Example

Imagine you are using a library such as Pydantic (to simplify the example) and you want to throw a simple exception that does not rely on a ValueError but you also want to provide some details.

from esmerald import Esmerald, post
from esmerald.exceptions import ValidationError

from pydantic import BaseModel, model_validator


class PasswordIn(BaseModel):
    password: str
    retype_password: str

    @model_validator(mode="after")
    def validate_value(self):
        if self.password != self.retype_password
            raise ValidationError({'password': "Passwords do not match"})
        return self


@post('/password')
async def check(data: PasswordIn) -> None:
    ...

When a response fails, it should throw an error similar to this:

{"detail": {"password": "Passwords do not match"}}

This can be particularly useful mostly if you want to create custom error messages and to be easy to parse a response back to a client.

You can use the ValidationError in different way. As a list, tuple, str and dict and you can also override the default status_code.

from esmerald.exceptions import ValidationError

ValidationError("An error") # as string
ValidationError({"field": "an error" }) # as dict
ValidationError(["An error", "another error"]) # as list
ValidationError(("An error", "another error")) # as tuple
ValidationError("Not Authorized", status_code=401) # override the status_code

Custom exceptions

Every application has different needs, errors, operations and everything else. Although the default Esmerald exceptions work for generic and internal processing you might face an issue when it comes to handle some specifc, more narrow and unique to your application type of exception. This is very simple and also very possible.

from typing import Optional

from pydantic import BaseModel

from esmerald import HTTPException, JSONResponse, Request, post
from lilya import status


class PartialContentException(HTTPException):
    status_code = status.HTTP_206_PARTIAL_CONTENT
    detail = "Incomplete data."


async def validation_error_exception_handler(
    request: Request, exc: PartialContentException
) -> 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,
        )


class User(BaseModel):
    name: Optional[str]
    email: Optional[str]


@post(
    "/create",
    exception_handlers={PartialContentException: validation_error_exception_handler},
)
async def create_user(data: User):
    if not data.user:
        raise PartialContentException()
    else:
        ...

The example above of course is forced to be like that for illustration purposes to raise the custom exception as the default if no Optional fields were declared would be handled by Esmerald ValidationErrorException exception handler but this serves as an example how to do your own.

Overriding the current Esmerald exception handlers

Currently by default, every Esmerald application starts with ImproperlyConfigured and ValidationErrorException to make sure everything is covered accordingly but this does not necessarily mean that this can't be changed.

from pydantic.error_wrappers import ValidationError

from esmerald import (
    HTTPException,
    ImproperlyConfigured,
    JSONResponse,
    Request,
    Response,
    ValidationErrorException,
)
from esmerald.applications import Esmerald
from esmerald.enums import MediaType
from lilya import status
from lilya.exceptions import HTTPException as LilyaHTTPException
from lilya.responses import Response as LilyaResponse


async def improperly_configured_exception_handler(
    request: Request, exc: ImproperlyConfigured
) -> LilyaResponse:
    status_code = (
        exc.status_code
        if isinstance(exc, LilyaHTTPException)
        else status.HTTP_500_INTERNAL_SERVER_ERROR
    )
    if not status_code:
        status_code = status.HTTP_500_INTERNAL_SERVER_ERROR

    content = {"detail": exc.detail}
    if exc.extra:
        content.update({"extra": exc.extra})
    headers = exc.headers if isinstance(exc, (HTTPException, LilyaHTTPException)) else None

    return Response(
        media_type=MediaType.JSON,
        content=content,
        status_code=status_code,
        headers=headers,
    )


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,
        )


app = Esmerald(
    routes=[...],
    exception_handlers={
        ImproperlyConfigured: improperly_configured_exception_handler,
        ValidationErrorException: validation_error_exception_handler,
    },
)

This will make sure that the application defaults will have a your exception_handler instead of the main application.

API Reference

Check out the API Reference for HTTPException for more details.