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")