mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-27 08:29:59 +03:00
Raise PEP 3134 chained exceptions
Use PEP 3134 (https://peps.python.org/pep-3134/) exception chaining to provide enhanced reporting and extra context when errors are encountered.
This commit is contained in:
parent
df92e57ad6
commit
f0ff449232
|
@ -79,9 +79,9 @@ class BasicAuthentication(BaseAuthentication):
|
|||
except UnicodeDecodeError:
|
||||
auth_decoded = base64.b64decode(auth[1]).decode('latin-1')
|
||||
auth_parts = auth_decoded.partition(':')
|
||||
except (TypeError, UnicodeDecodeError, binascii.Error):
|
||||
except (TypeError, UnicodeDecodeError, binascii.Error) as exc:
|
||||
msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
|
||||
raise exceptions.AuthenticationFailed(msg)
|
||||
raise exceptions.AuthenticationFailed(msg) from exc
|
||||
|
||||
userid, password = auth_parts[0], auth_parts[2]
|
||||
return self.authenticate_credentials(userid, password, request)
|
||||
|
@ -189,9 +189,9 @@ class TokenAuthentication(BaseAuthentication):
|
|||
|
||||
try:
|
||||
token = auth[1].decode()
|
||||
except UnicodeError:
|
||||
except UnicodeError as exc:
|
||||
msg = _('Invalid token header. Token string should not contain invalid characters.')
|
||||
raise exceptions.AuthenticationFailed(msg)
|
||||
raise exceptions.AuthenticationFailed(msg) from exc
|
||||
|
||||
return self.authenticate_credentials(token)
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import decimal
|
|||
import functools
|
||||
import inspect
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
@ -104,7 +105,7 @@ def get_attribute(instance, attrs):
|
|||
# If we raised an Attribute or KeyError here it'd get treated
|
||||
# as an omitted field in `Field.get_attribute()`. Instead we
|
||||
# raise a ValueError to ensure the exception is not masked.
|
||||
raise ValueError('Exception raised in callable attribute "{}"; original exception was: {}'.format(attr, exc))
|
||||
raise ValueError('Exception raised in callable attribute "{}"; original exception was: {}'.format(attr, exc)) from exc
|
||||
|
||||
return instance
|
||||
|
||||
|
@ -466,14 +467,14 @@ class Field:
|
|||
instance=instance.__class__.__name__,
|
||||
)
|
||||
)
|
||||
raise type(exc)(msg)
|
||||
raise type(exc)(msg) from exc
|
||||
except (KeyError, AttributeError) as exc:
|
||||
if self.default is not empty:
|
||||
return self.get_default()
|
||||
if self.allow_null:
|
||||
return None
|
||||
if not self.required:
|
||||
raise SkipField()
|
||||
raise SkipField() from exc
|
||||
msg = (
|
||||
'Got {exc_type} when attempting to get a value for field '
|
||||
'`{field}` on serializer `{serializer}`.\nThe serializer '
|
||||
|
@ -487,7 +488,7 @@ class Field:
|
|||
exc=exc
|
||||
)
|
||||
)
|
||||
raise type(exc)(msg)
|
||||
raise type(exc)(msg) from exc
|
||||
|
||||
def get_default(self):
|
||||
"""
|
||||
|
@ -633,12 +634,17 @@ class Field:
|
|||
"""
|
||||
try:
|
||||
msg = self.error_messages[key]
|
||||
except KeyError:
|
||||
except KeyError as exc:
|
||||
class_name = self.__class__.__name__
|
||||
msg = MISSING_ERROR_MESSAGE.format(class_name=class_name, key=key)
|
||||
raise AssertionError(msg)
|
||||
raise AssertionError(msg) from exc
|
||||
message_string = msg.format(**kwargs)
|
||||
raise ValidationError(message_string, code=key)
|
||||
err = ValidationError(message_string, code=key)
|
||||
(_, exc, _) = sys.exc_info()
|
||||
if exc:
|
||||
raise err from exc
|
||||
else:
|
||||
raise err
|
||||
|
||||
@property
|
||||
def root(self):
|
||||
|
|
|
@ -17,8 +17,8 @@ def get_object_or_404(queryset, *filter_args, **filter_kwargs):
|
|||
"""
|
||||
try:
|
||||
return _get_object_or_404(queryset, *filter_args, **filter_kwargs)
|
||||
except (TypeError, ValueError, ValidationError):
|
||||
raise Http404
|
||||
except (TypeError, ValueError, ValidationError) as exc:
|
||||
raise Http404 from exc
|
||||
|
||||
|
||||
class GenericAPIView(views.APIView):
|
||||
|
|
|
@ -206,7 +206,7 @@ class PageNumberPagination(BasePagination):
|
|||
msg = self.invalid_page_message.format(
|
||||
page_number=page_number, message=str(exc)
|
||||
)
|
||||
raise NotFound(msg)
|
||||
raise NotFound(msg) from exc
|
||||
|
||||
if paginator.num_pages > 1 and self.template is not None:
|
||||
# The browsable API should display pagination controls.
|
||||
|
@ -862,8 +862,8 @@ class CursorPagination(BasePagination):
|
|||
reverse = bool(int(reverse))
|
||||
|
||||
position = tokens.get('p', [None])[0]
|
||||
except (TypeError, ValueError):
|
||||
raise NotFound(self.invalid_cursor_message)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise NotFound(self.invalid_cursor_message) from exc
|
||||
|
||||
return Cursor(offset=offset, reverse=reverse, position=position)
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ class JSONParser(BaseParser):
|
|||
parse_constant = json.strict_constant if self.strict else None
|
||||
return json.load(decoded_stream, parse_constant=parse_constant)
|
||||
except ValueError as exc:
|
||||
raise ParseError('JSON parse error - %s' % str(exc))
|
||||
raise ParseError('JSON parse error - %s' % str(exc)) from exc
|
||||
|
||||
|
||||
class FormParser(BaseParser):
|
||||
|
@ -109,7 +109,7 @@ class MultiPartParser(BaseParser):
|
|||
data, files = parser.parse()
|
||||
return DataAndFiles(data, files)
|
||||
except MultiPartParserError as exc:
|
||||
raise ParseError('Multipart form parse error - %s' % str(exc))
|
||||
raise ParseError('Multipart form parse error - %s' % str(exc)) from exc
|
||||
|
||||
|
||||
class FileUploadParser(BaseParser):
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import sys
|
||||
from collections import OrderedDict
|
||||
from urllib import parse
|
||||
|
||||
|
@ -316,12 +315,10 @@ class HyperlinkedRelatedField(RelatedField):
|
|||
|
||||
try:
|
||||
return queryset.get(**lookup_kwargs)
|
||||
except ValueError:
|
||||
exc = ObjectValueError(str(sys.exc_info()[1]))
|
||||
raise exc.with_traceback(sys.exc_info()[2])
|
||||
except TypeError:
|
||||
exc = ObjectTypeError(str(sys.exc_info()[1]))
|
||||
raise exc.with_traceback(sys.exc_info()[2])
|
||||
except ValueError as exc:
|
||||
raise ObjectValueError(str(exc)) from exc
|
||||
except TypeError as exc:
|
||||
raise ObjectTypeError(str(exc)) from exc
|
||||
|
||||
def get_url(self, obj, view_name, request, format):
|
||||
"""
|
||||
|
@ -399,7 +396,7 @@ class HyperlinkedRelatedField(RelatedField):
|
|||
# Return the hyperlink, or error if incorrectly configured.
|
||||
try:
|
||||
url = self.get_url(value, self.view_name, request, format)
|
||||
except NoReverseMatch:
|
||||
except NoReverseMatch as exc:
|
||||
msg = (
|
||||
'Could not resolve URL for hyperlinked relationship using '
|
||||
'view name "%s". You may have failed to include the related '
|
||||
|
@ -413,7 +410,7 @@ class HyperlinkedRelatedField(RelatedField):
|
|||
"was %s, which may be why it didn't match any "
|
||||
"entries in your URL conf." % value_string
|
||||
)
|
||||
raise ImproperlyConfigured(msg % self.view_name)
|
||||
raise ImproperlyConfigured(msg % self.view_name) from exc
|
||||
|
||||
if url is None:
|
||||
return None
|
||||
|
|
|
@ -9,7 +9,6 @@ The wrapped request then offers a richer API, in particular :
|
|||
- form overloading of HTTP method, content type and content
|
||||
"""
|
||||
import io
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -72,10 +71,8 @@ def wrap_attributeerrors():
|
|||
"""
|
||||
try:
|
||||
yield
|
||||
except AttributeError:
|
||||
info = sys.exc_info()
|
||||
exc = WrappedAttributeError(str(info[1]))
|
||||
raise exc.with_traceback(info[2])
|
||||
except AttributeError as exc:
|
||||
raise WrappedAttributeError(str(exc)) from exc
|
||||
|
||||
|
||||
class Empty:
|
||||
|
|
|
@ -89,13 +89,13 @@ def insert_into(target, keys, value):
|
|||
|
||||
try:
|
||||
target.links.append((keys[-1], value))
|
||||
except TypeError:
|
||||
except TypeError as exc:
|
||||
msg = INSERT_INTO_COLLISION_FMT.format(
|
||||
value_url=value.url,
|
||||
target_url=target.url,
|
||||
keys=keys
|
||||
)
|
||||
raise ValueError(msg)
|
||||
raise ValueError(msg) from exc
|
||||
|
||||
|
||||
class SchemaGenerator(BaseSchemaGenerator):
|
||||
|
|
|
@ -12,7 +12,6 @@ response content is handled by parsers and renderers.
|
|||
"""
|
||||
import copy
|
||||
import inspect
|
||||
import traceback
|
||||
from collections import OrderedDict, defaultdict
|
||||
from collections.abc import Mapping
|
||||
|
||||
|
@ -227,12 +226,14 @@ class BaseSerializer(Field):
|
|||
self._validated_data = self.run_validation(self.initial_data)
|
||||
except ValidationError as exc:
|
||||
self._validated_data = {}
|
||||
self._exc = exc
|
||||
self._errors = exc.detail
|
||||
else:
|
||||
self._exc = None
|
||||
self._errors = {}
|
||||
|
||||
if self._errors and raise_exception:
|
||||
raise ValidationError(self.errors)
|
||||
if raise_exception and self._exc is not None:
|
||||
raise ValidationError(self.errors) from self._exc
|
||||
|
||||
return not bool(self._errors)
|
||||
|
||||
|
@ -429,7 +430,7 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
|
|||
value = self.validate(value)
|
||||
assert value is not None, '.validate() should return the validated data'
|
||||
except (ValidationError, DjangoValidationError) as exc:
|
||||
raise ValidationError(detail=as_serializer_error(exc))
|
||||
raise ValidationError(detail=as_serializer_error(exc)) from exc
|
||||
|
||||
return value
|
||||
|
||||
|
@ -621,7 +622,7 @@ class ListSerializer(BaseSerializer):
|
|||
value = self.validate(value)
|
||||
assert value is not None, '.validate() should return the validated data'
|
||||
except (ValidationError, DjangoValidationError) as exc:
|
||||
raise ValidationError(detail=as_serializer_error(exc))
|
||||
raise ValidationError(detail=as_serializer_error(exc)) from exc
|
||||
|
||||
return value
|
||||
|
||||
|
@ -748,12 +749,14 @@ class ListSerializer(BaseSerializer):
|
|||
self._validated_data = self.run_validation(self.initial_data)
|
||||
except ValidationError as exc:
|
||||
self._validated_data = []
|
||||
self._exc = exc
|
||||
self._errors = exc.detail
|
||||
else:
|
||||
self._exc = None
|
||||
self._errors = []
|
||||
|
||||
if self._errors and raise_exception:
|
||||
raise ValidationError(self.errors)
|
||||
if raise_exception and self._exc is not None:
|
||||
raise ValidationError(self.errors) from self._exc
|
||||
|
||||
return not bool(self._errors)
|
||||
|
||||
|
@ -960,25 +963,23 @@ class ModelSerializer(Serializer):
|
|||
|
||||
try:
|
||||
instance = ModelClass._default_manager.create(**validated_data)
|
||||
except TypeError:
|
||||
tb = traceback.format_exc()
|
||||
except TypeError as exc:
|
||||
msg = (
|
||||
'Got a `TypeError` when calling `%s.%s.create()`. '
|
||||
'This may be because you have a writable field on the '
|
||||
'serializer class that is not a valid argument to '
|
||||
'`%s.%s.create()`. You may need to make the field '
|
||||
'read-only, or override the %s.create() method to handle '
|
||||
'this correctly.\nOriginal exception was:\n %s' %
|
||||
'this correctly.' %
|
||||
(
|
||||
ModelClass.__name__,
|
||||
ModelClass._default_manager.name,
|
||||
ModelClass.__name__,
|
||||
ModelClass._default_manager.name,
|
||||
self.__class__.__name__,
|
||||
tb
|
||||
)
|
||||
)
|
||||
raise TypeError(msg)
|
||||
raise TypeError(msg) from exc
|
||||
|
||||
# Save many-to-many relationships after the instance is created.
|
||||
if many_to_many:
|
||||
|
|
|
@ -177,7 +177,7 @@ def import_from_string(val, setting_name):
|
|||
return import_string(val)
|
||||
except ImportError as e:
|
||||
msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e)
|
||||
raise ImportError(msg)
|
||||
raise ImportError(msg) from e
|
||||
|
||||
|
||||
class APISettings:
|
||||
|
|
|
@ -90,9 +90,9 @@ class SimpleRateThrottle(BaseThrottle):
|
|||
|
||||
try:
|
||||
return self.THROTTLE_RATES[self.scope]
|
||||
except KeyError:
|
||||
except KeyError as exc:
|
||||
msg = "No default throttle rate set for '%s' scope" % self.scope
|
||||
raise ImproperlyConfigured(msg)
|
||||
raise ImproperlyConfigured(msg) from exc
|
||||
|
||||
def parse_rate(self, rate):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue
Block a user