Problem Details¶
New in version 2.9.0.
Problem details are a standardized way of providing machine-readable details of errors in HTTP responses as specified in RFC 9457, the latest RFC at the time of writing.
Usage¶
To send a problem details response, the ProblemDetailsPlugin
should be registered and then
a ProblemDetailsException
can be raised anywhere which will automatically be converted
into a problem details response.
from dataclasses import dataclass
from litestar import Litestar, post
from litestar.plugins.problem_details import ProblemDetailsConfig, ProblemDetailsException, ProblemDetailsPlugin
@dataclass
class PurchaseItem:
item_id: int
quantity: int
@post("/purchase")
async def purchase(data: PurchaseItem) -> None:
# Logic to check if the user has enough credit to buy the item.
# We assume the user does not have enough credit.
raise ProblemDetailsException(
type_="https://example.com/probs/out-of-credit",
title="You do not have enough credit.",
detail="Your current balance is 30, but that costs 50.",
instance="/account/12345/msgs/abc",
extra={"balance": 30},
)
problem_details_plugin = ProblemDetailsPlugin(ProblemDetailsConfig())
app = Litestar(route_handlers=[purchase], plugins=[problem_details_plugin])
Run it
> curl http://127.0.0.1:8000/purchase --header Content-Type: application/json --request POST --data {"item_id": 1234, "quantity": 2}
{"status":500,"type":"https://example.com/probs/out-of-credit","title":"You do not have enough credit.","instance":"/account/12345/msgs/abc","detail":"Your current balance is 30, but that costs 50.","balance":30}
You can convert all HTTPExceptions
into problem details response by enabling the flag in the ProblemDetailsConfig.
HTTPException
into problem details response.¶from dataclasses import dataclass
from litestar import Litestar, post
from litestar.exceptions.http_exceptions import NotFoundException
from litestar.plugins.problem_details import ProblemDetailsConfig, ProblemDetailsPlugin
@dataclass
class PurchaseItem:
item_id: int
quantity: int
@post("/purchase")
async def purchase(data: PurchaseItem) -> None:
# Logic to check if the user has enough credit to buy the item.
# We assume the user does not have enough credit.
raise NotFoundException(detail="No item with the given ID was found", extra={"item_id": data.item_id})
problem_details_plugin = ProblemDetailsPlugin(ProblemDetailsConfig(enable_for_all_http_exceptions=True))
app = Litestar(route_handlers=[purchase], plugins=[problem_details_plugin])
Run it
> curl http://127.0.0.1:8000/purchase --header Content-Type: application/json --request POST --data {"item_id": 1234, "quantity": 2}
{"status":404,"title":"No item with the given ID was found","detail":"Not Found","item_id":1234}
You can also convert any exception that is not a HTTPException
into a problem details response
by providing a mapping of the exception type to a callable that converts the exception into a
ProblemDetailsException.
Tip
This can used to override how the HTTPException
is converted into a problem details response as well.
from __future__ import annotations
from dataclasses import dataclass
from litestar import Litestar, post
from litestar.plugins.problem_details import ProblemDetailsConfig, ProblemDetailsException, ProblemDetailsPlugin
@dataclass
class PurchaseItem:
item_id: int
quantity: int
class PurchaseNotAllowedError(Exception):
def __init__(self, account_id: int, balance: int, detail: str) -> None:
self.account_id = account_id
self.balance = balance
self.detail = detail
@post("/purchase")
async def purchase(data: PurchaseItem) -> None:
raise PurchaseNotAllowedError(
account_id=12345,
balance=30,
detail="Your current balance is 30, but that costs 50.",
)
def convert_purchase_not_allowed_to_problem_details(exc: PurchaseNotAllowedError) -> ProblemDetailsException:
return ProblemDetailsException(
type_="https://example.com/probs/out-of-credit",
title="You do not have enough credit.",
detail=exc.detail,
instance=f"/account/{exc.account_id}/msgs/abc",
extra={"balance": exc.balance},
)
problem_details_plugin = ProblemDetailsPlugin(
ProblemDetailsConfig(
enable_for_all_http_exceptions=True,
exception_to_problem_detail_map={PurchaseNotAllowedError: convert_purchase_not_allowed_to_problem_details},
)
)
app = Litestar(route_handlers=[purchase], plugins=[problem_details_plugin])
Run it
> curl http://127.0.0.1:8000/purchase --header Content-Type: application/json --request POST --data {"item_id": 1234, "quantity": 2}
{"status":500,"type":"https://example.com/probs/out-of-credit","title":"You do not have enough credit.","instance":"/account/12345/msgs/abc","detail":"Your current balance is 30, but that costs 50.","balance":30}
Warning
If the extra
field is a Mapping
, then it’s merged into the problem details response, otherwise it’s included in the response with the key extra.