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.