Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/rest_framework/exceptions.py: 66%
144 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1"""
2Handled exceptions raised by REST framework.
4In addition Django's built in 403 and 404 exceptions are handled.
5(`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
6"""
7import math
9from django.http import JsonResponse
10from django.utils.encoding import force_str
11from django.utils.translation import gettext_lazy as _
12from django.utils.translation import ngettext
14from rest_framework import status
15from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList
18def _get_error_details(data, default_code=None):
19 """
20 Descend into a nested data structure, forcing any
21 lazy translation strings or strings into `ErrorDetail`.
22 """
23 if isinstance(data, (list, tuple)):
24 ret = [
25 _get_error_details(item, default_code) for item in data
26 ]
27 if isinstance(data, ReturnList): 27 ↛ 28line 27 didn't jump to line 28, because the condition on line 27 was never true
28 return ReturnList(ret, serializer=data.serializer)
29 return ret
30 elif isinstance(data, dict):
31 ret = {
32 key: _get_error_details(value, default_code)
33 for key, value in data.items()
34 }
35 if isinstance(data, ReturnDict):
36 return ReturnDict(ret, serializer=data.serializer)
37 return ret
39 text = force_str(data)
40 code = getattr(data, 'code', default_code)
41 return ErrorDetail(text, code)
44def _get_codes(detail):
45 if isinstance(detail, list):
46 return [_get_codes(item) for item in detail]
47 elif isinstance(detail, dict):
48 return {key: _get_codes(value) for key, value in detail.items()}
49 return detail.code
52def _get_full_details(detail):
53 if isinstance(detail, list):
54 return [_get_full_details(item) for item in detail]
55 elif isinstance(detail, dict):
56 return {key: _get_full_details(value) for key, value in detail.items()}
57 return {
58 'message': detail,
59 'code': detail.code
60 }
63class ErrorDetail(str):
64 """
65 A string-like object that can additionally have a code.
66 """
67 code = None
69 def __new__(cls, string, code=None):
70 self = super().__new__(cls, string)
71 self.code = code
72 return self
74 def __eq__(self, other):
75 r = super().__eq__(other)
76 if r is NotImplemented:
77 return NotImplemented
78 try:
79 return r and self.code == other.code
80 except AttributeError:
81 return r
83 def __ne__(self, other):
84 return not self.__eq__(other)
86 def __repr__(self):
87 return 'ErrorDetail(string=%r, code=%r)' % (
88 str(self),
89 self.code,
90 )
92 def __hash__(self):
93 return hash(str(self))
96class APIException(Exception):
97 """
98 Base class for REST framework exceptions.
99 Subclasses should provide `.status_code` and `.default_detail` properties.
100 """
101 status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
102 default_detail = _('A server error occurred.')
103 default_code = 'error'
105 def __init__(self, detail=None, code=None):
106 if detail is None: 106 ↛ 108line 106 didn't jump to line 108, because the condition on line 106 was never false
107 detail = self.default_detail
108 if code is None: 108 ↛ 111line 108 didn't jump to line 111, because the condition on line 108 was never false
109 code = self.default_code
111 self.detail = _get_error_details(detail, code)
113 def __str__(self):
114 return str(self.detail)
116 def get_codes(self):
117 """
118 Return only the code part of the error details.
120 Eg. {"name": ["required"]}
121 """
122 return _get_codes(self.detail)
124 def get_full_details(self):
125 """
126 Return both the message & code parts of the error details.
128 Eg. {"name": [{"message": "This field is required.", "code": "required"}]}
129 """
130 return _get_full_details(self.detail)
133# The recommended style for using `ValidationError` is to keep it namespaced
134# under `serializers`, in order to minimize potential confusion with Django's
135# built in `ValidationError`. For example:
136#
137# from rest_framework import serializers
138# raise serializers.ValidationError('Value was invalid')
140class ValidationError(APIException):
141 status_code = status.HTTP_400_BAD_REQUEST
142 default_detail = _('Invalid input.')
143 default_code = 'invalid'
145 def __init__(self, detail=None, code=None):
146 if detail is None: 146 ↛ 147line 146 didn't jump to line 147, because the condition on line 146 was never true
147 detail = self.default_detail
148 if code is None:
149 code = self.default_code
151 # For validation failures, we may collect many errors together,
152 # so the details should always be coerced to a list if not already.
153 if isinstance(detail, tuple): 153 ↛ 154line 153 didn't jump to line 154, because the condition on line 153 was never true
154 detail = list(detail)
155 elif not isinstance(detail, dict) and not isinstance(detail, list):
156 detail = [detail]
158 self.detail = _get_error_details(detail, code)
161class ParseError(APIException):
162 status_code = status.HTTP_400_BAD_REQUEST
163 default_detail = _('Malformed request.')
164 default_code = 'parse_error'
167class AuthenticationFailed(APIException):
168 status_code = status.HTTP_401_UNAUTHORIZED
169 default_detail = _('Incorrect authentication credentials.')
170 default_code = 'authentication_failed'
173class NotAuthenticated(APIException):
174 status_code = status.HTTP_401_UNAUTHORIZED
175 default_detail = _('Authentication credentials were not provided.')
176 default_code = 'not_authenticated'
179class PermissionDenied(APIException):
180 status_code = status.HTTP_403_FORBIDDEN
181 default_detail = _('You do not have permission to perform this action.')
182 default_code = 'permission_denied'
185class NotFound(APIException):
186 status_code = status.HTTP_404_NOT_FOUND
187 default_detail = _('Not found.')
188 default_code = 'not_found'
191class MethodNotAllowed(APIException):
192 status_code = status.HTTP_405_METHOD_NOT_ALLOWED
193 default_detail = _('Method "{method}" not allowed.')
194 default_code = 'method_not_allowed'
196 def __init__(self, method, detail=None, code=None):
197 if detail is None:
198 detail = force_str(self.default_detail).format(method=method)
199 super().__init__(detail, code)
202class NotAcceptable(APIException):
203 status_code = status.HTTP_406_NOT_ACCEPTABLE
204 default_detail = _('Could not satisfy the request Accept header.')
205 default_code = 'not_acceptable'
207 def __init__(self, detail=None, code=None, available_renderers=None):
208 self.available_renderers = available_renderers
209 super().__init__(detail, code)
212class UnsupportedMediaType(APIException):
213 status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
214 default_detail = _('Unsupported media type "{media_type}" in request.')
215 default_code = 'unsupported_media_type'
217 def __init__(self, media_type, detail=None, code=None):
218 if detail is None:
219 detail = force_str(self.default_detail).format(media_type=media_type)
220 super().__init__(detail, code)
223class Throttled(APIException):
224 status_code = status.HTTP_429_TOO_MANY_REQUESTS
225 default_detail = _('Request was throttled.')
226 extra_detail_singular = _('Expected available in {wait} second.')
227 extra_detail_plural = _('Expected available in {wait} seconds.')
228 default_code = 'throttled'
230 def __init__(self, wait=None, detail=None, code=None):
231 if detail is None:
232 detail = force_str(self.default_detail)
233 if wait is not None:
234 wait = math.ceil(wait)
235 detail = ' '.join((
236 detail,
237 force_str(ngettext(self.extra_detail_singular.format(wait=wait),
238 self.extra_detail_plural.format(wait=wait),
239 wait))))
240 self.wait = wait
241 super().__init__(detail, code)
244def server_error(request, *args, **kwargs):
245 """
246 Generic 500 error handler.
247 """
248 data = {
249 'error': 'Server Error (500)'
250 }
251 return JsonResponse(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
254def bad_request(request, exception, *args, **kwargs):
255 """
256 Generic 400 error handler.
257 """
258 data = {
259 'error': 'Bad Request (400)'
260 }
261 return JsonResponse(data, status=status.HTTP_400_BAD_REQUEST)