exceptions.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. # Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
  2. # Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
  3. # Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as
  6. # published by the Free Software Foundation, either version 3 of the
  7. # License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. # This code is partially taken from django-rest-framework:
  17. # Copyright (c) 2011-2015, Tom Christie
  18. """
  19. Handled exceptions raised by REST framework.
  20. In addition Django's built in 403 and 404 exceptions are handled.
  21. (`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
  22. """
  23. from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
  24. from django.utils.encoding import force_text
  25. from django.utils.translation import ugettext_lazy as _
  26. from django.http import Http404
  27. from . import response
  28. from . import status
  29. import math
  30. class APIException(Exception):
  31. """
  32. Base class for REST framework exceptions.
  33. Subclasses should provide `.status_code` and `.default_detail` properties.
  34. """
  35. status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
  36. default_detail = ""
  37. def __init__(self, detail=None):
  38. self.detail = detail or self.default_detail
  39. class ParseError(APIException):
  40. status_code = status.HTTP_400_BAD_REQUEST
  41. default_detail = _("Malformed request.")
  42. class AuthenticationFailed(APIException):
  43. status_code = status.HTTP_401_UNAUTHORIZED
  44. default_detail = _("Incorrect authentication credentials.")
  45. class NotAuthenticated(APIException):
  46. status_code = status.HTTP_401_UNAUTHORIZED
  47. default_detail = _("Authentication credentials were not provided.")
  48. class PermissionDenied(APIException):
  49. status_code = status.HTTP_403_FORBIDDEN
  50. default_detail = _("You do not have permission to perform this action.")
  51. class MethodNotAllowed(APIException):
  52. status_code = status.HTTP_405_METHOD_NOT_ALLOWED
  53. default_detail = _("Method '%s' not allowed.")
  54. def __init__(self, method, detail=None):
  55. self.detail = (detail or self.default_detail) % method
  56. class NotAcceptable(APIException):
  57. status_code = status.HTTP_406_NOT_ACCEPTABLE
  58. default_detail = _("Could not satisfy the request's Accept header")
  59. def __init__(self, detail=None, available_renderers=None):
  60. self.detail = detail or self.default_detail
  61. self.available_renderers = available_renderers
  62. class UnsupportedMediaType(APIException):
  63. status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
  64. default_detail = _("Unsupported media type '%s' in request.")
  65. def __init__(self, media_type, detail=None):
  66. self.detail = (detail or self.default_detail) % media_type
  67. class Throttled(APIException):
  68. status_code = status.HTTP_429_TOO_MANY_REQUESTS
  69. default_detail = _("Request was throttled.")
  70. extra_detail = _("Expected available in %d second%s.")
  71. def __init__(self, wait=None, detail=None):
  72. if wait is None:
  73. self.detail = detail or self.default_detail
  74. self.wait = None
  75. else:
  76. format = "%s%s" % ((detail or self.default_detail), self.extra_detail)
  77. self.detail = format % (wait, wait != 1 and "s" or "")
  78. self.wait = math.ceil(wait)
  79. class BaseException(APIException):
  80. status_code = status.HTTP_400_BAD_REQUEST
  81. default_detail = _("Unexpected error")
  82. def __init__(self, detail=None):
  83. self.detail = detail or self.default_detail
  84. class NotFound(BaseException, Http404):
  85. """
  86. Exception used for not found objects.
  87. """
  88. status_code = status.HTTP_404_NOT_FOUND
  89. default_detail = _("Not found.")
  90. class NotSupported(BaseException):
  91. status_code = status.HTTP_405_METHOD_NOT_ALLOWED
  92. default_detail = _("Method not supported for this endpoint.")
  93. class BadRequest(BaseException):
  94. """
  95. Exception used on bad arguments detected
  96. on api view.
  97. """
  98. default_detail = _("Wrong arguments.")
  99. class WrongArguments(BadRequest):
  100. """
  101. Exception used on bad arguments detected
  102. on service. This is same as `BadRequest`.
  103. """
  104. default_detail = _("Wrong arguments.")
  105. class RequestValidationError(BadRequest):
  106. default_detail = _("Data validation error")
  107. class PermissionDenied(PermissionDenied):
  108. """
  109. Compatibility subclass of restframework `PermissionDenied`
  110. exception.
  111. """
  112. pass
  113. class IntegrityError(BadRequest):
  114. default_detail = _("Integrity Error for wrong or invalid arguments")
  115. class PreconditionError(BadRequest):
  116. """
  117. Error raised on precondition method on viewset.
  118. """
  119. default_detail = _("Precondition error")
  120. class NotAuthenticated(NotAuthenticated):
  121. """
  122. Compatibility subclass of restframework `NotAuthenticated`
  123. exception.
  124. """
  125. pass
  126. def format_exception(exc):
  127. if isinstance(exc.detail, (dict, list, tuple,)):
  128. detail = exc.detail
  129. else:
  130. class_name = exc.__class__.__name__
  131. class_module = exc.__class__.__module__
  132. detail = {
  133. "_error_message": force_text(exc.detail),
  134. "_error_type": "{0}.{1}".format(class_module, class_name)
  135. }
  136. return detail
  137. def exception_handler(exc):
  138. """
  139. Returns the response that should be used for any given exception.
  140. By default we handle the REST framework `APIException`, and also
  141. Django's builtin `Http404` and `PermissionDenied` exceptions.
  142. Any unhandled exceptions may return `None`, which will cause a 500 error
  143. to be raised.
  144. """
  145. if isinstance(exc, APIException):
  146. headers = {}
  147. if getattr(exc, "auth_header", None):
  148. headers["WWW-Authenticate"] = exc.auth_header
  149. if getattr(exc, "wait", None):
  150. headers["X-Throttle-Wait-Seconds"] = "%d" % exc.wait
  151. detail = format_exception(exc)
  152. return response.Response(detail, status=exc.status_code, headers=headers)
  153. elif isinstance(exc, Http404):
  154. return response.NotFound({'_error_message': str(exc)})
  155. elif isinstance(exc, DjangoPermissionDenied):
  156. return response.Forbidden({"_error_message": str(exc)})
  157. # Note: Unhandled exceptions will raise a 500 error.
  158. return None