Skip to content

Commit

Permalink
➕ Custom exception handler docs
Browse files Browse the repository at this point in the history
* Add a new default application exception handler
  • Loading branch information
tarsil committed Jul 20, 2023
1 parent c1cc955 commit b8b4131
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 25 deletions.
45 changes: 45 additions & 0 deletions docs/exception-handlers.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,48 @@ endpoint is called and the exception is raised.
{! ../docs_src/_shared/exceptions.md !}

The same is applied also to [dependencies](./dependencies.md).


### 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.

```python
{!> ../docs_src/exception_handlers/example.py !}
```

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.

```python
from esmerald.exception_handlers import value_error_handler
```

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

```python hl_lines="21-23"
{!> ../docs_src/exception_handlers/example_use.py !}
```

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

```python hl_lines="22-25"
{!> ../docs_src/exception_handlers/example_use_gateway.py !}
```

Or even specific only to the handler itself.

```python hl_lines="14-16"
{!> ../docs_src/exception_handlers/example_use_handler.py !}
```

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](./application/levels.md).
18 changes: 18 additions & 0 deletions docs_src/exception_handlers/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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)])
24 changes: 24 additions & 0 deletions docs_src/exception_handlers/example_use.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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,
},
)
28 changes: 28 additions & 0 deletions docs_src/exception_handlers/example_use_gateway.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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,
},
)
],
)
26 changes: 26 additions & 0 deletions docs_src/exception_handlers/example_use_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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)],
)
5 changes: 4 additions & 1 deletion esmerald/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from openapi_schemas_pydantic.v3_1_0 import Contact, License, SecurityScheme, Tag
from openapi_schemas_pydantic.v3_1_0.open_api import OpenAPI
from pydantic import AnyUrl
from pydantic import AnyUrl, ValidationError
from starlette.applications import Starlette
from starlette.middleware import Middleware as StarletteMiddleware # noqa
from starlette.types import Lifespan, Receive, Scope, Send
Expand All @@ -28,6 +28,7 @@
from esmerald.datastructures import State
from esmerald.exception_handlers import (
improperly_configured_exception_handler,
pydantic_validation_error_handler,
validation_error_exception_handler,
)
from esmerald.exceptions import ImproperlyConfigured, ValidationErrorException
Expand Down Expand Up @@ -775,6 +776,8 @@ def get_default_exception_handlers(self) -> None:
ValidationErrorException, validation_error_exception_handler
)

self.exception_handlers.setdefault(ValidationError, pydantic_validation_error_handler)

def build_routes_middleware(
self, route: "RouteParent", middlewares: Optional[List["Middleware"]] = None
) -> List["Middleware"]:
Expand Down
27 changes: 3 additions & 24 deletions tests/exception_handlers/test_custom_exception_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ async def update() -> JSON:
DataIn(name="Esmerald", email="[email protected]")


def test_pydantic_validation_error_handler_return_500(test_client_factory):
def test_pydantic_validation_error_handler_return_422(test_client_factory):
with create_client(
routes=[Gateway(handler=create)],
) as client:
response = client.post("/create")
assert response.status_code == 500
assert response.status_code == 422


def test_pydantic_validation_error_handler(test_client_factory):
Expand All @@ -64,33 +64,12 @@ def test_pydantic_validation_error_handler(test_client_factory):
assert "email" in locs


def test_value_error_handler_return_500(test_client_factory):
with create_client(
routes=[Gateway(handler=update)],
) as client:
response = client.put("/update")
assert response.status_code == 500


def test_value_error_handler(test_client_factory):
with create_client(
routes=[Gateway(handler=update)],
exception_handlers={ValueError: value_error_handler},
) as client:
response = client.put("/update")
assert response.status_code == 400

assert (
response.json()["detail"][0]["msg"]
== "Value error, The name: Esmerald was successfully passed"
)


def test_value_error_handler_return_500_on_function(test_client_factory):
with create_client(
routes=[Gateway(handler=raised)],
) as client:
response = client.post("/raise-error")

assert response.status_code == 500


Expand Down

0 comments on commit b8b4131

Please sign in to comment.