from __future__ import annotations
from http import HTTPStatus
from typing import Any
from litestar.exceptions.base_exceptions import LitestarException
from litestar.status_codes import (
HTTP_400_BAD_REQUEST,
HTTP_401_UNAUTHORIZED,
HTTP_403_FORBIDDEN,
HTTP_404_NOT_FOUND,
HTTP_405_METHOD_NOT_ALLOWED,
HTTP_413_REQUEST_ENTITY_TOO_LARGE,
HTTP_429_TOO_MANY_REQUESTS,
HTTP_500_INTERNAL_SERVER_ERROR,
HTTP_503_SERVICE_UNAVAILABLE,
)
from litestar.types.empty import Empty, EmptyType
__all__ = (
"ClientException",
"HTTPException",
"ImproperlyConfiguredException",
"InternalServerException",
"MethodNotAllowedException",
"NoRouteMatchFoundException",
"NotAuthorizedException",
"NotFoundException",
"PermissionDeniedException",
"ServiceUnavailableException",
"TemplateNotFoundException",
"TooManyRequestsException",
"ValidationException",
)
[docs]
class HTTPException(LitestarException):
"""Base exception for HTTP error responses.
These exceptions carry information to construct an HTTP response.
"""
status_code: int = HTTP_500_INTERNAL_SERVER_ERROR
"""Exception status code."""
detail: str
"""Exception details or message."""
headers: dict[str, str] | None = None
"""Headers to attach to the response."""
extra: dict[str, Any] | list[Any] | None = None
"""An extra mapping to attach to the exception."""
[docs]
def __init__(
self,
*args: Any,
detail: str = "",
status_code: int | None = None,
headers: dict[str, str] | None | EmptyType = Empty,
extra: dict[str, Any] | list[Any] | None | EmptyType = Empty,
) -> None:
"""Initialize ``HTTPException``.
Set ``detail`` and ``args`` if not provided.
Args:
*args: if ``detail`` kwarg not provided, first arg should be error detail.
detail: Exception details or message. Will default to args[0] if not provided.
status_code: Exception HTTP status code.
headers: Headers to set on the response.
Defaults to the class's ``headers`` if not provided.
Set to ``None`` explicitly to unset the default.
extra: An extra mapping to attach to the exception.
Defaults to the class's ``extra`` if not provided.
Set to ``None`` explicitly to unset the default.
"""
super().__init__(*args, detail=detail)
self.status_code = status_code or self.status_code
self.extra = extra if extra is not Empty else self.extra
self.headers = headers if headers is not Empty else self.headers
if not self.detail:
self.detail = HTTPStatus(self.status_code).phrase
self.args = (f"{self.status_code}: {self.detail}", *self.args)
def __repr__(self) -> str:
return f"{self.status_code} - {self.__class__.__name__} - {self.detail}"
def __str__(self) -> str:
return " ".join(self.args).strip()
[docs]
class ClientException(HTTPException):
"""Client error."""
status_code: int = HTTP_400_BAD_REQUEST
[docs]
class ValidationException(ClientException, ValueError):
"""Client data validation error."""
[docs]
class NotAuthorizedException(ClientException):
"""Request lacks valid authentication credentials for the requested resource."""
status_code = HTTP_401_UNAUTHORIZED
[docs]
class PermissionDeniedException(ClientException):
"""Request understood, but not authorized."""
status_code = HTTP_403_FORBIDDEN
[docs]
class NotFoundException(ClientException, ValueError):
"""Cannot find the requested resource."""
status_code = HTTP_404_NOT_FOUND
[docs]
class MethodNotAllowedException(ClientException):
"""Server knows the request method, but the target resource doesn't support this method."""
status_code = HTTP_405_METHOD_NOT_ALLOWED
class RequestEntityTooLarge(ClientException):
status_code = HTTP_413_REQUEST_ENTITY_TOO_LARGE
detail = "Request Entity Too Large"
[docs]
class TooManyRequestsException(ClientException):
"""Request limits have been exceeded."""
status_code = HTTP_429_TOO_MANY_REQUESTS
[docs]
class InternalServerException(HTTPException):
"""Server encountered an unexpected condition that prevented it from fulfilling the request."""
status_code: int = HTTP_500_INTERNAL_SERVER_ERROR
[docs]
class ServiceUnavailableException(InternalServerException):
"""Server is not ready to handle the request."""
status_code = HTTP_503_SERVICE_UNAVAILABLE
[docs]
class NoRouteMatchFoundException(InternalServerException):
"""A route with the given name could not be found."""
[docs]
class TemplateNotFoundException(InternalServerException):
"""Referenced template could not be found."""
[docs]
def __init__(self, *args: Any, template_name: str) -> None:
"""Initialize ``TemplateNotFoundException``.
Args:
*args (Any): Passed through to ``super().__init__()`` - should not include ``detail``.
template_name (str): Name of template that could not be found.
"""
super().__init__(*args, detail=f"Template {template_name} not found.")