mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-07 13:54:47 +03:00
Apply error codes to all exception types. Add documentation.
This commit is contained in:
parent
2a3d07d9d3
commit
e4b961bd1b
|
@ -98,7 +98,7 @@ Note that the exception handler will only be called for responses generated by r
|
||||||
|
|
||||||
The **base class** for all exceptions raised inside an `APIView` class or `@api_view`.
|
The **base class** for all exceptions raised inside an `APIView` class or `@api_view`.
|
||||||
|
|
||||||
To provide a custom exception, subclass `APIException` and set the `.status_code` and `.default_detail` properties on the class.
|
To provide a custom exception, subclass `APIException` and set the `.status_code`, `.default_detail`, and `default_code` attributes on the class.
|
||||||
|
|
||||||
For example, if your API relies on a third party service that may sometimes be unreachable, you might want to implement an exception for the "503 Service Unavailable" HTTP response code. You could do this like so:
|
For example, if your API relies on a third party service that may sometimes be unreachable, you might want to implement an exception for the "503 Service Unavailable" HTTP response code. You could do this like so:
|
||||||
|
|
||||||
|
@ -107,10 +107,42 @@ For example, if your API relies on a third party service that may sometimes be u
|
||||||
class ServiceUnavailable(APIException):
|
class ServiceUnavailable(APIException):
|
||||||
status_code = 503
|
status_code = 503
|
||||||
default_detail = 'Service temporarily unavailable, try again later.'
|
default_detail = 'Service temporarily unavailable, try again later.'
|
||||||
|
default_code = 'service_unavailable'
|
||||||
|
|
||||||
|
#### Inspecting API exceptions
|
||||||
|
|
||||||
|
There are a number of different properties available for inspecting the status
|
||||||
|
of an API exception. You can use these to build custom exception handling
|
||||||
|
for your project.
|
||||||
|
|
||||||
|
The available attributes and methods are:
|
||||||
|
|
||||||
|
* `.detail` - Return the textual description of the error.
|
||||||
|
* `.get_codes()` - Return the code identifier of the error.
|
||||||
|
* `.full_details()` - Retrun both the textual description and the code identifier.
|
||||||
|
|
||||||
|
In most cases the error detail will be a simple item:
|
||||||
|
|
||||||
|
>>> print(exc.detail)
|
||||||
|
You do not have permission to perform this action.
|
||||||
|
>>> print(exc.get_codes())
|
||||||
|
permission_denied
|
||||||
|
>>> print(exc.full_details())
|
||||||
|
{'message':'You do not have permission to perform this action.','code':'permission_denied'}
|
||||||
|
|
||||||
|
In the case of validation errors the error detail will be either a list or
|
||||||
|
dictionary of items:
|
||||||
|
|
||||||
|
>>> print(exc.detail)
|
||||||
|
{"name":"This field is required.","age":"A valid integer is required."}
|
||||||
|
>>> print(exc.get_codes())
|
||||||
|
{"name":"required","age":"invalid"}
|
||||||
|
>>> print(exc.get_full_details())
|
||||||
|
{"name":{"message":"This field is required.","code":"required"},"age":{"message":"A valid integer is required.","code":"invalid"}}
|
||||||
|
|
||||||
## ParseError
|
## ParseError
|
||||||
|
|
||||||
**Signature:** `ParseError(detail=None)`
|
**Signature:** `ParseError(detail=None, code=None)`
|
||||||
|
|
||||||
Raised if the request contains malformed data when accessing `request.data`.
|
Raised if the request contains malformed data when accessing `request.data`.
|
||||||
|
|
||||||
|
@ -118,7 +150,7 @@ By default this exception results in a response with the HTTP status code "400 B
|
||||||
|
|
||||||
## AuthenticationFailed
|
## AuthenticationFailed
|
||||||
|
|
||||||
**Signature:** `AuthenticationFailed(detail=None)`
|
**Signature:** `AuthenticationFailed(detail=None, code=None)`
|
||||||
|
|
||||||
Raised when an incoming request includes incorrect authentication.
|
Raised when an incoming request includes incorrect authentication.
|
||||||
|
|
||||||
|
@ -126,7 +158,7 @@ By default this exception results in a response with the HTTP status code "401 U
|
||||||
|
|
||||||
## NotAuthenticated
|
## NotAuthenticated
|
||||||
|
|
||||||
**Signature:** `NotAuthenticated(detail=None)`
|
**Signature:** `NotAuthenticated(detail=None, code=None)`
|
||||||
|
|
||||||
Raised when an unauthenticated request fails the permission checks.
|
Raised when an unauthenticated request fails the permission checks.
|
||||||
|
|
||||||
|
@ -134,7 +166,7 @@ By default this exception results in a response with the HTTP status code "401 U
|
||||||
|
|
||||||
## PermissionDenied
|
## PermissionDenied
|
||||||
|
|
||||||
**Signature:** `PermissionDenied(detail=None)`
|
**Signature:** `PermissionDenied(detail=None, code=None)`
|
||||||
|
|
||||||
Raised when an authenticated request fails the permission checks.
|
Raised when an authenticated request fails the permission checks.
|
||||||
|
|
||||||
|
@ -142,7 +174,7 @@ By default this exception results in a response with the HTTP status code "403 F
|
||||||
|
|
||||||
## NotFound
|
## NotFound
|
||||||
|
|
||||||
**Signature:** `NotFound(detail=None)`
|
**Signature:** `NotFound(detail=None, code=None)`
|
||||||
|
|
||||||
Raised when a resource does not exists at the given URL. This exception is equivalent to the standard `Http404` Django exception.
|
Raised when a resource does not exists at the given URL. This exception is equivalent to the standard `Http404` Django exception.
|
||||||
|
|
||||||
|
@ -150,7 +182,7 @@ By default this exception results in a response with the HTTP status code "404 N
|
||||||
|
|
||||||
## MethodNotAllowed
|
## MethodNotAllowed
|
||||||
|
|
||||||
**Signature:** `MethodNotAllowed(method, detail=None)`
|
**Signature:** `MethodNotAllowed(method, detail=None, code=None)`
|
||||||
|
|
||||||
Raised when an incoming request occurs that does not map to a handler method on the view.
|
Raised when an incoming request occurs that does not map to a handler method on the view.
|
||||||
|
|
||||||
|
@ -158,7 +190,7 @@ By default this exception results in a response with the HTTP status code "405 M
|
||||||
|
|
||||||
## NotAcceptable
|
## NotAcceptable
|
||||||
|
|
||||||
**Signature:** `NotAcceptable(detail=None)`
|
**Signature:** `NotAcceptable(detail=None, code=None)`
|
||||||
|
|
||||||
Raised when an incoming request occurs with an `Accept` header that cannot be satisfied by any of the available renderers.
|
Raised when an incoming request occurs with an `Accept` header that cannot be satisfied by any of the available renderers.
|
||||||
|
|
||||||
|
@ -166,7 +198,7 @@ By default this exception results in a response with the HTTP status code "406 N
|
||||||
|
|
||||||
## UnsupportedMediaType
|
## UnsupportedMediaType
|
||||||
|
|
||||||
**Signature:** `UnsupportedMediaType(media_type, detail=None)`
|
**Signature:** `UnsupportedMediaType(media_type, detail=None, code=None)`
|
||||||
|
|
||||||
Raised if there are no parsers that can handle the content type of the request data when accessing `request.data`.
|
Raised if there are no parsers that can handle the content type of the request data when accessing `request.data`.
|
||||||
|
|
||||||
|
@ -174,7 +206,7 @@ By default this exception results in a response with the HTTP status code "415 U
|
||||||
|
|
||||||
## Throttled
|
## Throttled
|
||||||
|
|
||||||
**Signature:** `Throttled(wait=None, detail=None)`
|
**Signature:** `Throttled(wait=None, detail=None, code=None)`
|
||||||
|
|
||||||
Raised when an incoming request fails the throttling checks.
|
Raised when an incoming request fails the throttling checks.
|
||||||
|
|
||||||
|
|
|
@ -81,43 +81,19 @@ class APIException(Exception):
|
||||||
"""
|
"""
|
||||||
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
|
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
default_detail = _('A server error occurred.')
|
default_detail = _('A server error occurred.')
|
||||||
|
default_code = 'error'
|
||||||
|
|
||||||
def __init__(self, detail=None):
|
def __init__(self, detail=None, code=None):
|
||||||
if detail is not None:
|
if detail is None:
|
||||||
self.detail = force_text(detail)
|
detail = self.default_detail
|
||||||
else:
|
if code is None:
|
||||||
self.detail = force_text(self.default_detail)
|
code = self.default_code
|
||||||
|
|
||||||
|
self.detail = _get_error_details(detail, code)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.detail
|
return self.detail
|
||||||
|
|
||||||
|
|
||||||
# The recommended style for using `ValidationError` is to keep it namespaced
|
|
||||||
# under `serializers`, in order to minimize potential confusion with Django's
|
|
||||||
# built in `ValidationError`. For example:
|
|
||||||
#
|
|
||||||
# from rest_framework import serializers
|
|
||||||
# raise serializers.ValidationError('Value was invalid')
|
|
||||||
|
|
||||||
class ValidationError(APIException):
|
|
||||||
status_code = status.HTTP_400_BAD_REQUEST
|
|
||||||
|
|
||||||
def __init__(self, detail, code=None):
|
|
||||||
# For validation errors the 'detail' key is always required.
|
|
||||||
# The details should always be coerced to a list if not already.
|
|
||||||
if not isinstance(detail, dict) and not isinstance(detail, list):
|
|
||||||
detail = [detail]
|
|
||||||
|
|
||||||
if code is None:
|
|
||||||
default_code = 'invalid'
|
|
||||||
else:
|
|
||||||
default_code = code
|
|
||||||
|
|
||||||
self.detail = _get_error_details(detail, default_code)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return six.text_type(self.detail)
|
|
||||||
|
|
||||||
def get_codes(self):
|
def get_codes(self):
|
||||||
"""
|
"""
|
||||||
Return only the code part of the error details.
|
Return only the code part of the error details.
|
||||||
|
@ -135,65 +111,95 @@ class ValidationError(APIException):
|
||||||
return _get_full_details(self.detail)
|
return _get_full_details(self.detail)
|
||||||
|
|
||||||
|
|
||||||
|
# The recommended style for using `ValidationError` is to keep it namespaced
|
||||||
|
# under `serializers`, in order to minimize potential confusion with Django's
|
||||||
|
# built in `ValidationError`. For example:
|
||||||
|
#
|
||||||
|
# from rest_framework import serializers
|
||||||
|
# raise serializers.ValidationError('Value was invalid')
|
||||||
|
|
||||||
|
class ValidationError(APIException):
|
||||||
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
|
default_detail = _('Invalid input.')
|
||||||
|
default_code = 'invalid'
|
||||||
|
|
||||||
|
def __init__(self, detail, code=None):
|
||||||
|
if detail is None:
|
||||||
|
detail = self.default_detail
|
||||||
|
if code is None:
|
||||||
|
code = self.default_code
|
||||||
|
|
||||||
|
# For validation failures, we may collect may errors together, so the
|
||||||
|
# details should always be coerced to a list if not already.
|
||||||
|
if not isinstance(detail, dict) and not isinstance(detail, list):
|
||||||
|
detail = [detail]
|
||||||
|
|
||||||
|
self.detail = _get_error_details(detail, code)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return six.text_type(self.detail)
|
||||||
|
|
||||||
|
|
||||||
class ParseError(APIException):
|
class ParseError(APIException):
|
||||||
status_code = status.HTTP_400_BAD_REQUEST
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
default_detail = _('Malformed request.')
|
default_detail = _('Malformed request.')
|
||||||
|
default_code = 'parse_error'
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationFailed(APIException):
|
class AuthenticationFailed(APIException):
|
||||||
status_code = status.HTTP_401_UNAUTHORIZED
|
status_code = status.HTTP_401_UNAUTHORIZED
|
||||||
default_detail = _('Incorrect authentication credentials.')
|
default_detail = _('Incorrect authentication credentials.')
|
||||||
|
default_code = 'authentication_failed'
|
||||||
|
|
||||||
|
|
||||||
class NotAuthenticated(APIException):
|
class NotAuthenticated(APIException):
|
||||||
status_code = status.HTTP_401_UNAUTHORIZED
|
status_code = status.HTTP_401_UNAUTHORIZED
|
||||||
default_detail = _('Authentication credentials were not provided.')
|
default_detail = _('Authentication credentials were not provided.')
|
||||||
|
default_code = 'not_authenticated'
|
||||||
|
|
||||||
|
|
||||||
class PermissionDenied(APIException):
|
class PermissionDenied(APIException):
|
||||||
status_code = status.HTTP_403_FORBIDDEN
|
status_code = status.HTTP_403_FORBIDDEN
|
||||||
default_detail = _('You do not have permission to perform this action.')
|
default_detail = _('You do not have permission to perform this action.')
|
||||||
|
default_code = 'permission_denied'
|
||||||
|
|
||||||
|
|
||||||
class NotFound(APIException):
|
class NotFound(APIException):
|
||||||
status_code = status.HTTP_404_NOT_FOUND
|
status_code = status.HTTP_404_NOT_FOUND
|
||||||
default_detail = _('Not found.')
|
default_detail = _('Not found.')
|
||||||
|
default_code = 'not_found'
|
||||||
|
|
||||||
|
|
||||||
class MethodNotAllowed(APIException):
|
class MethodNotAllowed(APIException):
|
||||||
status_code = status.HTTP_405_METHOD_NOT_ALLOWED
|
status_code = status.HTTP_405_METHOD_NOT_ALLOWED
|
||||||
default_detail = _('Method "{method}" not allowed.')
|
default_detail = _('Method "{method}" not allowed.')
|
||||||
|
default_code = 'method_not_allowed'
|
||||||
|
|
||||||
def __init__(self, method, detail=None):
|
def __init__(self, method, detail=None, code=None):
|
||||||
if detail is not None:
|
if detail is None:
|
||||||
self.detail = force_text(detail)
|
detail = force_text(self.default_detail).format(method=method)
|
||||||
else:
|
super(MethodNotAllowed, self).__init__(detail, code)
|
||||||
self.detail = force_text(self.default_detail).format(method=method)
|
|
||||||
|
|
||||||
|
|
||||||
class NotAcceptable(APIException):
|
class NotAcceptable(APIException):
|
||||||
status_code = status.HTTP_406_NOT_ACCEPTABLE
|
status_code = status.HTTP_406_NOT_ACCEPTABLE
|
||||||
default_detail = _('Could not satisfy the request Accept header.')
|
default_detail = _('Could not satisfy the request Accept header.')
|
||||||
|
default_code = 'not_acceptable'
|
||||||
|
|
||||||
def __init__(self, detail=None, available_renderers=None):
|
def __init__(self, detail=None, code=None, available_renderers=None):
|
||||||
if detail is not None:
|
|
||||||
self.detail = force_text(detail)
|
|
||||||
else:
|
|
||||||
self.detail = force_text(self.default_detail)
|
|
||||||
self.available_renderers = available_renderers
|
self.available_renderers = available_renderers
|
||||||
|
super(NotAcceptable, self).__init__(detail, code)
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedMediaType(APIException):
|
class UnsupportedMediaType(APIException):
|
||||||
status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
|
status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
|
||||||
default_detail = _('Unsupported media type "{media_type}" in request.')
|
default_detail = _('Unsupported media type "{media_type}" in request.')
|
||||||
|
default_code = 'unsupported_media_type'
|
||||||
|
|
||||||
def __init__(self, media_type, detail=None):
|
def __init__(self, media_type, detail=None, code=None):
|
||||||
if detail is not None:
|
if detail is None:
|
||||||
self.detail = force_text(detail)
|
detail = force_text(self.default_detail).format(media_type=media_type)
|
||||||
else:
|
super(UnsupportedMediaType, self).__init__(detail, code)
|
||||||
self.detail = force_text(self.default_detail).format(
|
|
||||||
media_type=media_type
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Throttled(APIException):
|
class Throttled(APIException):
|
||||||
|
@ -201,12 +207,10 @@ class Throttled(APIException):
|
||||||
default_detail = _('Request was throttled.')
|
default_detail = _('Request was throttled.')
|
||||||
extra_detail_singular = 'Expected available in {wait} second.'
|
extra_detail_singular = 'Expected available in {wait} second.'
|
||||||
extra_detail_plural = 'Expected available in {wait} seconds.'
|
extra_detail_plural = 'Expected available in {wait} seconds.'
|
||||||
|
default_code = 'throttled'
|
||||||
|
|
||||||
def __init__(self, wait=None, detail=None):
|
def __init__(self, wait=None, detail=None, code=None):
|
||||||
if detail is not None:
|
super(Throttled, self).__init__(detail, code)
|
||||||
self.detail = force_text(detail)
|
|
||||||
else:
|
|
||||||
self.detail = force_text(self.default_detail)
|
|
||||||
|
|
||||||
if wait is None:
|
if wait is None:
|
||||||
self.wait = None
|
self.wait = None
|
||||||
|
|
Loading…
Reference in New Issue
Block a user