Exceptions and exception handling#
Litestar define a base exception called LitestarException
which serves
as a basis to all other exceptions.
In general, Litestar will raise two types of exceptions:
Exceptions that arise during application init, which fall
Exceptions that are raised as part of the normal application flow, i.e. exceptions in route handlers, dependencies, and middleware, that should be serialized in some fashion.
Configuration Exceptions#
For missing extra dependencies, Litestar will raise either
MissingDependencyException
. For example, if you try to use the
SQLAlchemyPlugin without having SQLAlchemy installed, this will be raised when you
start the application.
For other configuration issues, Litestar will raise
ImproperlyConfiguredException
with a message explaining the
issue.
Application Exceptions#
For application exceptions, Litestar uses the class HTTPException
,
which inherits from LitestarException
. This exception will be serialized
into a JSON response of the following schema:
{
"status_code": 500,
"detail": "Internal Server Error",
"extra": {}
}
Litestar also offers several pre-configured exception subclasses with pre-set error codes that you can use, such as:
Exception |
Status code |
Description |
---|---|---|
500 |
Used internally for configuration errors |
|
400 |
Raised when validation or parsing failed |
|
404 |
HTTP status code 404 |
|
401 |
HTTP status code 401 |
|
403 |
HTTP status code 403 |
|
500 |
HTTP status code 500 |
|
503 |
HTTP status code 503 |
When a value fails pydantic
validation, the result will be a ValidationException
with the extra
key set to the
pydantic validation errors. Thus, this data will be made available for the API consumers by default.
Exception handling#
Litestar handles all errors by default by transforming them into JSON responses. If the errors are instances of
HTTPException
, the responses will include the appropriate status_code
.
Otherwise, the responses will default to 500 - "Internal Server Error"
.
You can customize exception handling by passing a dictionary, mapping either status codes or exception classes to callables. For example, if you would like to replace the default exception handler with a handler that returns plain-text responses you could do this:
from litestar import Litestar, MediaType, Request, Response, get
from litestar.exceptions import HTTPException
from litestar.status_codes import HTTP_500_INTERNAL_SERVER_ERROR
def plain_text_exception_handler(_: Request, exc: Exception) -> Response:
"""Default handler for exceptions subclassed from HTTPException."""
status_code = getattr(exc, "status_code", HTTP_500_INTERNAL_SERVER_ERROR)
detail = getattr(exc, "detail", "")
return Response(
media_type=MediaType.TEXT,
content=detail,
status_code=status_code,
)
@get("/")
async def index() -> None:
raise HTTPException(detail="an error occurred", status_code=400)
app = Litestar(
route_handlers=[index],
exception_handlers={HTTPException: plain_text_exception_handler},
)
Run it
> curl http://127.0.0.1:8000/
an error occurred
The above will define a top level exception handler that will apply the plain_text_exception_handler
function to all
exceptions that inherit from HTTPException
. You could of course be more granular:
from litestar import Litestar, MediaType, Request, Response, get
from litestar.exceptions import HTTPException, ValidationException
from litestar.status_codes import HTTP_500_INTERNAL_SERVER_ERROR
def validation_exception_handler(request: Request, exc: ValidationException) -> Response:
return Response(
media_type=MediaType.TEXT,
content=f"validation error: {exc.detail}",
status_code=400,
)
def internal_server_error_handler(request: Request, exc: Exception) -> Response:
return Response(
media_type=MediaType.TEXT,
content=f"server error: {exc}",
status_code=500,
)
def value_error_handler(request: Request, exc: ValueError) -> Response:
return Response(
media_type=MediaType.TEXT,
content=f"value error: {exc}",
status_code=400,
)
@get("/validation-error")
async def validation_error(some_query_param: str) -> str:
return some_query_param
@get("/server-error")
async def server_error() -> None:
raise HTTPException()
@get("/value-error")
async def value_error() -> None:
raise ValueError("this is wrong")
app = Litestar(
route_handlers=[validation_error, server_error, value_error],
exception_handlers={
ValidationException: validation_exception_handler,
HTTP_500_INTERNAL_SERVER_ERROR: internal_server_error_handler,
ValueError: value_error_handler,
},
)
Run it
> curl http://127.0.0.1:8000/validation-error
validation error: Missing required query parameter 'some_query_param' for path /validation-error
> curl http://127.0.0.1:8000/server-error
server error: 500: Internal Server Error
> curl http://127.0.0.1:8000/value-error
value error: this is wrong
The choice whether to use a single function that has switching logic inside it, or multiple functions depends on your specific needs.
While it does not make much sense to have different functions with a top-level exception handling,
Litestar supports defining exception handlers on all layers of the app, with the lower layers overriding layer above
them. In the following example, the exception handler for the route handler function will only handle
the ValidationException
occurring within that route handler:
from litestar import Litestar, Request, Response, get
from litestar.exceptions import HTTPException, ValidationException
def app_exception_handler(request: Request, exc: HTTPException) -> Response:
return Response(
content={
"error": "server error",
"path": request.url.path,
"detail": exc.detail,
"status_code": exc.status_code,
},
status_code=500,
)
def router_handler_exception_handler(request: Request, exc: ValidationException) -> Response:
return Response(
content={"error": "validation error", "path": request.url.path},
status_code=400,
)
@get("/")
async def index() -> None:
raise HTTPException("something's gone wrong")
@get(
"/greet",
exception_handlers={ValidationException: router_handler_exception_handler},
)
async def greet(name: str) -> str:
return f"hello {name}"
app = Litestar(
route_handlers=[index, greet],
exception_handlers={HTTPException: app_exception_handler},
)
Run it
> curl http://127.0.0.1:8000/
{"error":"server error","path":"/","detail":"something's gone wrong","status_code":500}
> curl http://127.0.0.1:8000/greet
{"error":"validation error","path":"/greet"}
Exception handling layers#
Since Litestar allows users to define both exception handlers and middlewares in a layered fashion, i.e. on individual route handlers, controllers, routers, or the app layer, multiple layers of exception handlers are required to ensure that exceptions are handled correctly:
As a result of the above structure, the exceptions raised by the ASGI Router itself, namely 404 Not Found
and 405 Method Not Allowed
are handled only by exception handlers defined on the app layer. Thus, if you want to affect
these exceptions, you will need to pass the exception handlers for them to the Litestar constructor and cannot use other
layers for this purpose.