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.