134 lines
3.6 KiB
Python
134 lines
3.6 KiB
Python
import logging
|
|
import traceback
|
|
from functools import partial
|
|
from typing import TYPE_CHECKING, Generic, List, Optional, TypeVar
|
|
|
|
import pydantic
|
|
from django.conf import settings
|
|
from django.http import Http404, HttpRequest, HttpResponse
|
|
|
|
from ninja.types import DictStrAny
|
|
|
|
if TYPE_CHECKING:
|
|
from ninja import NinjaAPI # pragma: no cover
|
|
from ninja.params.models import ParamModel # pragma: no cover
|
|
|
|
__all__ = [
|
|
"ConfigError",
|
|
"AuthenticationError",
|
|
"AuthorizationError",
|
|
"ValidationError",
|
|
"HttpError",
|
|
"set_default_exc_handlers",
|
|
]
|
|
|
|
|
|
logger = logging.getLogger("django")
|
|
|
|
|
|
class ConfigError(Exception):
|
|
pass
|
|
|
|
|
|
TModel = TypeVar("TModel", bound="ParamModel")
|
|
|
|
|
|
class ValidationErrorContext(Generic[TModel]):
|
|
"""
|
|
The full context of a `pydantic.ValidationError`, including all information
|
|
needed to produce a `ninja.errors.ValidationError`.
|
|
"""
|
|
|
|
def __init__(
|
|
self, pydantic_validation_error: pydantic.ValidationError, model: TModel
|
|
):
|
|
self.pydantic_validation_error = pydantic_validation_error
|
|
self.model = model
|
|
|
|
|
|
class ValidationError(Exception):
|
|
"""
|
|
This exception raised when operation params do not validate
|
|
Note: this is not the same as pydantic.ValidationError
|
|
the errors attribute as well holds the location of the error(body, form, query, etc.)
|
|
"""
|
|
|
|
def __init__(self, errors: List[DictStrAny]) -> None:
|
|
self.errors = errors
|
|
super().__init__(errors)
|
|
|
|
|
|
class HttpError(Exception):
|
|
def __init__(self, status_code: int, message: str) -> None:
|
|
self.status_code = status_code
|
|
self.message = message
|
|
super().__init__(status_code, message)
|
|
|
|
def __str__(self) -> str:
|
|
return self.message
|
|
|
|
|
|
class AuthenticationError(HttpError):
|
|
def __init__(self, status_code: int = 401, message: str = "Unauthorized") -> None:
|
|
super().__init__(status_code=status_code, message=message)
|
|
|
|
|
|
class AuthorizationError(HttpError):
|
|
def __init__(self, status_code: int = 403, message: str = "Forbidden") -> None:
|
|
super().__init__(status_code=status_code, message=message)
|
|
|
|
|
|
class Throttled(HttpError):
|
|
def __init__(self, wait: Optional[int]) -> None:
|
|
self.wait = wait
|
|
super().__init__(status_code=429, message="Too many requests.")
|
|
|
|
|
|
def set_default_exc_handlers(api: "NinjaAPI") -> None:
|
|
api.add_exception_handler(
|
|
Exception,
|
|
partial(_default_exception, api=api),
|
|
)
|
|
api.add_exception_handler(
|
|
Http404,
|
|
partial(_default_404, api=api),
|
|
)
|
|
api.add_exception_handler(
|
|
HttpError,
|
|
partial(_default_http_error, api=api),
|
|
)
|
|
api.add_exception_handler(
|
|
ValidationError,
|
|
partial(_default_validation_error, api=api),
|
|
)
|
|
|
|
|
|
def _default_404(request: HttpRequest, exc: Exception, api: "NinjaAPI") -> HttpResponse:
|
|
msg = "Not Found"
|
|
if settings.DEBUG:
|
|
msg += f": {exc}"
|
|
return api.create_response(request, {"detail": msg}, status=404)
|
|
|
|
|
|
def _default_http_error(
|
|
request: HttpRequest, exc: HttpError, api: "NinjaAPI"
|
|
) -> HttpResponse:
|
|
return api.create_response(request, {"detail": str(exc)}, status=exc.status_code)
|
|
|
|
|
|
def _default_validation_error(
|
|
request: HttpRequest, exc: ValidationError, api: "NinjaAPI"
|
|
) -> HttpResponse:
|
|
return api.create_response(request, {"detail": exc.errors}, status=422)
|
|
|
|
|
|
def _default_exception(
|
|
request: HttpRequest, exc: Exception, api: "NinjaAPI"
|
|
) -> HttpResponse:
|
|
if not settings.DEBUG:
|
|
raise exc # let django deal with it
|
|
|
|
logger.exception(exc)
|
|
tb = traceback.format_exc()
|
|
return HttpResponse(tb, status=500, content_type="text/plain")
|