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

1""" 

2Handled exceptions raised by REST framework. 

3 

4In addition Django's built in 403 and 404 exceptions are handled. 

5(`django.http.Http404` and `django.core.exceptions.PermissionDenied`) 

6""" 

7import math 

8 

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 

13 

14from rest_framework import status 

15from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList 

16 

17 

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 

38 

39 text = force_str(data) 

40 code = getattr(data, 'code', default_code) 

41 return ErrorDetail(text, code) 

42 

43 

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 

50 

51 

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 } 

61 

62 

63class ErrorDetail(str): 

64 """ 

65 A string-like object that can additionally have a code. 

66 """ 

67 code = None 

68 

69 def __new__(cls, string, code=None): 

70 self = super().__new__(cls, string) 

71 self.code = code 

72 return self 

73 

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 

82 

83 def __ne__(self, other): 

84 return not self.__eq__(other) 

85 

86 def __repr__(self): 

87 return 'ErrorDetail(string=%r, code=%r)' % ( 

88 str(self), 

89 self.code, 

90 ) 

91 

92 def __hash__(self): 

93 return hash(str(self)) 

94 

95 

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' 

104 

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 

110 

111 self.detail = _get_error_details(detail, code) 

112 

113 def __str__(self): 

114 return str(self.detail) 

115 

116 def get_codes(self): 

117 """ 

118 Return only the code part of the error details. 

119 

120 Eg. {"name": ["required"]} 

121 """ 

122 return _get_codes(self.detail) 

123 

124 def get_full_details(self): 

125 """ 

126 Return both the message & code parts of the error details. 

127 

128 Eg. {"name": [{"message": "This field is required.", "code": "required"}]} 

129 """ 

130 return _get_full_details(self.detail) 

131 

132 

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

139 

140class ValidationError(APIException): 

141 status_code = status.HTTP_400_BAD_REQUEST 

142 default_detail = _('Invalid input.') 

143 default_code = 'invalid' 

144 

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 

150 

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] 

157 

158 self.detail = _get_error_details(detail, code) 

159 

160 

161class ParseError(APIException): 

162 status_code = status.HTTP_400_BAD_REQUEST 

163 default_detail = _('Malformed request.') 

164 default_code = 'parse_error' 

165 

166 

167class AuthenticationFailed(APIException): 

168 status_code = status.HTTP_401_UNAUTHORIZED 

169 default_detail = _('Incorrect authentication credentials.') 

170 default_code = 'authentication_failed' 

171 

172 

173class NotAuthenticated(APIException): 

174 status_code = status.HTTP_401_UNAUTHORIZED 

175 default_detail = _('Authentication credentials were not provided.') 

176 default_code = 'not_authenticated' 

177 

178 

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' 

183 

184 

185class NotFound(APIException): 

186 status_code = status.HTTP_404_NOT_FOUND 

187 default_detail = _('Not found.') 

188 default_code = 'not_found' 

189 

190 

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' 

195 

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) 

200 

201 

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' 

206 

207 def __init__(self, detail=None, code=None, available_renderers=None): 

208 self.available_renderers = available_renderers 

209 super().__init__(detail, code) 

210 

211 

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' 

216 

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) 

221 

222 

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' 

229 

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) 

242 

243 

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) 

252 

253 

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)