123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- # Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
- # Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
- # Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU Affero General Public License as
- # published by the Free Software Foundation, either version 3 of the
- # License, or (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU Affero General Public License for more details.
- #
- # You should have received a copy of the GNU Affero General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- # This code is partially taken from django-rest-framework:
- # Copyright (c) 2011-2015, Tom Christie
- """
- Handled exceptions raised by REST framework.
- In addition Django's built in 403 and 404 exceptions are handled.
- (`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
- """
- from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
- from django.utils.encoding import force_text
- from django.utils.translation import ugettext_lazy as _
- from django.http import Http404
- from . import response
- from . import status
- import math
- class APIException(Exception):
- """
- Base class for REST framework exceptions.
- Subclasses should provide `.status_code` and `.default_detail` properties.
- """
- status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
- default_detail = ""
- def __init__(self, detail=None):
- self.detail = detail or self.default_detail
- class ParseError(APIException):
- status_code = status.HTTP_400_BAD_REQUEST
- default_detail = _("Malformed request.")
- class AuthenticationFailed(APIException):
- status_code = status.HTTP_401_UNAUTHORIZED
- default_detail = _("Incorrect authentication credentials.")
- class NotAuthenticated(APIException):
- status_code = status.HTTP_401_UNAUTHORIZED
- default_detail = _("Authentication credentials were not provided.")
- class PermissionDenied(APIException):
- status_code = status.HTTP_403_FORBIDDEN
- default_detail = _("You do not have permission to perform this action.")
- class MethodNotAllowed(APIException):
- status_code = status.HTTP_405_METHOD_NOT_ALLOWED
- default_detail = _("Method '%s' not allowed.")
- def __init__(self, method, detail=None):
- self.detail = (detail or self.default_detail) % method
- class NotAcceptable(APIException):
- status_code = status.HTTP_406_NOT_ACCEPTABLE
- default_detail = _("Could not satisfy the request's Accept header")
- def __init__(self, detail=None, available_renderers=None):
- self.detail = detail or self.default_detail
- self.available_renderers = available_renderers
- class UnsupportedMediaType(APIException):
- status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
- default_detail = _("Unsupported media type '%s' in request.")
- def __init__(self, media_type, detail=None):
- self.detail = (detail or self.default_detail) % media_type
- class Throttled(APIException):
- status_code = status.HTTP_429_TOO_MANY_REQUESTS
- default_detail = _("Request was throttled.")
- extra_detail = _("Expected available in %d second%s.")
- def __init__(self, wait=None, detail=None):
- if wait is None:
- self.detail = detail or self.default_detail
- self.wait = None
- else:
- format = "%s%s" % ((detail or self.default_detail), self.extra_detail)
- self.detail = format % (wait, wait != 1 and "s" or "")
- self.wait = math.ceil(wait)
- class BaseException(APIException):
- status_code = status.HTTP_400_BAD_REQUEST
- default_detail = _("Unexpected error")
- def __init__(self, detail=None):
- self.detail = detail or self.default_detail
- class NotFound(BaseException, Http404):
- """
- Exception used for not found objects.
- """
- status_code = status.HTTP_404_NOT_FOUND
- default_detail = _("Not found.")
- class NotSupported(BaseException):
- status_code = status.HTTP_405_METHOD_NOT_ALLOWED
- default_detail = _("Method not supported for this endpoint.")
- class BadRequest(BaseException):
- """
- Exception used on bad arguments detected
- on api view.
- """
- default_detail = _("Wrong arguments.")
- class WrongArguments(BadRequest):
- """
- Exception used on bad arguments detected
- on service. This is same as `BadRequest`.
- """
- default_detail = _("Wrong arguments.")
- class RequestValidationError(BadRequest):
- default_detail = _("Data validation error")
- class PermissionDenied(PermissionDenied):
- """
- Compatibility subclass of restframework `PermissionDenied`
- exception.
- """
- pass
- class IntegrityError(BadRequest):
- default_detail = _("Integrity Error for wrong or invalid arguments")
- class PreconditionError(BadRequest):
- """
- Error raised on precondition method on viewset.
- """
- default_detail = _("Precondition error")
- class NotAuthenticated(NotAuthenticated):
- """
- Compatibility subclass of restframework `NotAuthenticated`
- exception.
- """
- pass
- def format_exception(exc):
- if isinstance(exc.detail, (dict, list, tuple,)):
- detail = exc.detail
- else:
- class_name = exc.__class__.__name__
- class_module = exc.__class__.__module__
- detail = {
- "_error_message": force_text(exc.detail),
- "_error_type": "{0}.{1}".format(class_module, class_name)
- }
- return detail
- def exception_handler(exc):
- """
- Returns the response that should be used for any given exception.
- By default we handle the REST framework `APIException`, and also
- Django's builtin `Http404` and `PermissionDenied` exceptions.
- Any unhandled exceptions may return `None`, which will cause a 500 error
- to be raised.
- """
- if isinstance(exc, APIException):
- headers = {}
- if getattr(exc, "auth_header", None):
- headers["WWW-Authenticate"] = exc.auth_header
- if getattr(exc, "wait", None):
- headers["X-Throttle-Wait-Seconds"] = "%d" % exc.wait
- detail = format_exception(exc)
- return response.Response(detail, status=exc.status_code, headers=headers)
- elif isinstance(exc, Http404):
- return response.NotFound({'_error_message': str(exc)})
- elif isinstance(exc, DjangoPermissionDenied):
- return response.Forbidden({"_error_message": str(exc)})
- # Note: Unhandled exceptions will raise a 500 error.
- return None
|