Web APIs for Django, made easy.
—
Complete HTTP status code constants and structured exception classes for consistent error handling in Django REST Framework.
Complete set of HTTP status code constants for consistent API responses.
# Informational responses (1xx)
HTTP_100_CONTINUE = 100
HTTP_101_SWITCHING_PROTOCOLS = 101
# Successful responses (2xx)
HTTP_200_OK = 200
HTTP_201_CREATED = 201
HTTP_202_ACCEPTED = 202
HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203
HTTP_204_NO_CONTENT = 204
HTTP_205_RESET_CONTENT = 205
HTTP_206_PARTIAL_CONTENT = 206
HTTP_207_MULTI_STATUS = 207
HTTP_208_ALREADY_REPORTED = 208
HTTP_226_IM_USED = 226
# Redirection messages (3xx)
HTTP_300_MULTIPLE_CHOICES = 300
HTTP_301_MOVED_PERMANENTLY = 301
HTTP_302_FOUND = 302
HTTP_303_SEE_OTHER = 303
HTTP_304_NOT_MODIFIED = 304
HTTP_305_USE_PROXY = 305
HTTP_306_RESERVED = 306
HTTP_307_TEMPORARY_REDIRECT = 307
HTTP_308_PERMANENT_REDIRECT = 308
# Client error responses (4xx)
HTTP_400_BAD_REQUEST = 400
HTTP_401_UNAUTHORIZED = 401
HTTP_402_PAYMENT_REQUIRED = 402
HTTP_403_FORBIDDEN = 403
HTTP_404_NOT_FOUND = 404
HTTP_405_METHOD_NOT_ALLOWED = 405
HTTP_406_NOT_ACCEPTABLE = 406
HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
HTTP_408_REQUEST_TIMEOUT = 408
HTTP_409_CONFLICT = 409
HTTP_410_GONE = 410
HTTP_411_LENGTH_REQUIRED = 411
HTTP_412_PRECONDITION_FAILED = 412
HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413
HTTP_414_REQUEST_URI_TOO_LONG = 414
HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416
HTTP_417_EXPECTATION_FAILED = 417
HTTP_418_IM_A_TEAPOT = 418
HTTP_422_UNPROCESSABLE_ENTITY = 422
HTTP_423_LOCKED = 423
HTTP_424_FAILED_DEPENDENCY = 424
HTTP_426_UPGRADE_REQUIRED = 426
HTTP_428_PRECONDITION_REQUIRED = 428
HTTP_429_TOO_MANY_REQUESTS = 429
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431
HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451
# Server error responses (5xx)
HTTP_500_INTERNAL_SERVER_ERROR = 500
HTTP_501_NOT_IMPLEMENTED = 501
HTTP_502_BAD_GATEWAY = 502
HTTP_503_SERVICE_UNAVAILABLE = 503
HTTP_504_GATEWAY_TIMEOUT = 504
HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505
HTTP_506_VARIANT_ALSO_NEGOTIATES = 506
HTTP_507_INSUFFICIENT_STORAGE = 507
HTTP_508_LOOP_DETECTED = 508
HTTP_509_BANDWIDTH_LIMIT_EXCEEDED = 509
HTTP_510_NOT_EXTENDED = 510
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511Functions for categorizing HTTP status codes.
def is_informational(code):
"""
Check if HTTP status code is informational (1xx).
Args:
code (int): HTTP status code
Returns:
bool: True if code is 1xx
"""
def is_success(code):
"""
Check if HTTP status code indicates success (2xx).
Args:
code (int): HTTP status code
Returns:
bool: True if code is 2xx
"""
def is_redirect(code):
"""
Check if HTTP status code indicates redirection (3xx).
Args:
code (int): HTTP status code
Returns:
bool: True if code is 3xx
"""
def is_client_error(code):
"""
Check if HTTP status code indicates client error (4xx).
Args:
code (int): HTTP status code
Returns:
bool: True if code is 4xx
"""
def is_server_error(code):
"""
Check if HTTP status code indicates server error (5xx).
Args:
code (int): HTTP status code
Returns:
bool: True if code is 5xx
"""Structured exception classes for API error handling.
class ErrorDetail(str):
"""
String subclass that stores an error code in addition to the error message.
"""
def __new__(cls, string, code=None):
"""
Create new ErrorDetail instance.
Args:
string (str): Error message
code (str): Error code identifier
"""
self = super().__new__(cls, string)
self.code = code
return self
def __eq__(self, other):
"""Compare ErrorDetail instances."""
def __ne__(self, other):
"""Compare ErrorDetail instances."""
def __repr__(self):
"""String representation of ErrorDetail."""
def __hash__(self):
"""Hash for ErrorDetail."""
class APIException(Exception):
"""
Base class for all API exceptions.
"""
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
default_detail = 'A server error occurred.'
default_code = 'error'
def __init__(self, detail=None, code=None):
"""
Initialize API exception.
Args:
detail (str or dict): Error detail message or dictionary
code (str): Error code identifier
"""
if detail is None:
detail = self.default_detail
if code is None:
code = self.default_code
self.detail = _get_error_details(detail, code)
def __str__(self):
"""String representation of exception."""
return str(self.detail)
def get_codes(self):
"""
Get error codes from exception detail.
Returns:
Error codes in same structure as detail
"""
return _get_codes(self.detail)
def get_full_details(self):
"""
Get full error details including codes.
Returns:
Full error details with messages and codes
"""
return _get_full_details(self.detail)
class ValidationError(APIException):
"""
Exception for validation errors (400 Bad Request).
"""
status_code = status.HTTP_400_BAD_REQUEST
default_detail = 'Invalid input.'
default_code = 'invalid'
class ParseError(APIException):
"""
Exception for malformed request data (400 Bad Request).
"""
status_code = status.HTTP_400_BAD_REQUEST
default_detail = 'Malformed request.'
default_code = 'parse_error'
class AuthenticationFailed(APIException):
"""
Exception for authentication failures (401 Unauthorized).
"""
status_code = status.HTTP_401_UNAUTHORIZED
default_detail = 'Incorrect authentication credentials.'
default_code = 'authentication_failed'
class NotAuthenticated(APIException):
"""
Exception for missing authentication (401 Unauthorized).
"""
status_code = status.HTTP_401_UNAUTHORIZED
default_detail = 'Authentication credentials were not provided.'
default_code = 'not_authenticated'
class PermissionDenied(APIException):
"""
Exception for permission errors (403 Forbidden).
"""
status_code = status.HTTP_403_FORBIDDEN
default_detail = 'You do not have permission to perform this action.'
default_code = 'permission_denied'
class NotFound(APIException):
"""
Exception for resource not found (404 Not Found).
"""
status_code = status.HTTP_404_NOT_FOUND
default_detail = 'Not found.'
default_code = 'not_found'
class MethodNotAllowed(APIException):
"""
Exception for unsupported HTTP methods (405 Method Not Allowed).
"""
status_code = status.HTTP_405_METHOD_NOT_ALLOWED
default_detail = 'Method not allowed.'
default_code = 'method_not_allowed'
def __init__(self, method, detail=None, code=None):
"""
Args:
method (str): HTTP method that was not allowed
detail: Custom error detail
code: Custom error code
"""
if detail is None:
detail = f'Method "{method}" not allowed.'
super().__init__(detail, code)
class NotAcceptable(APIException):
"""
Exception for unsupported media types (406 Not Acceptable).
"""
status_code = status.HTTP_406_NOT_ACCEPTABLE
default_detail = 'Could not satisfy the request Accept header.'
default_code = 'not_acceptable'
def __init__(self, detail=None, code=None, available_renderers=None):
"""
Args:
detail: Custom error detail
code: Custom error code
available_renderers (list): List of available renderer media types
"""
self.available_renderers = available_renderers
super().__init__(detail, code)
class UnsupportedMediaType(APIException):
"""
Exception for unsupported request media types (415 Unsupported Media Type).
"""
status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
default_detail = 'Unsupported media type in request.'
default_code = 'unsupported_media_type'
def __init__(self, media_type, detail=None, code=None):
"""
Args:
media_type (str): Unsupported media type
detail: Custom error detail
code: Custom error code
"""
if detail is None:
detail = f'Unsupported media type "{media_type}" in request.'
super().__init__(detail, code)
class Throttled(APIException):
"""
Exception for rate limit exceeded (429 Too Many Requests).
"""
status_code = status.HTTP_429_TOO_MANY_REQUESTS
default_detail = 'Request was throttled.'
default_code = 'throttled'
extra_detail_singular = 'Expected available in {wait} second.'
extra_detail_plural = 'Expected available in {wait} seconds.'
def __init__(self, wait=None, detail=None, code=None):
"""
Args:
wait (int): Seconds to wait before next request
detail: Custom error detail
code: Custom error code
"""
if detail is None:
if wait is not None:
if wait == 1:
detail = self.extra_detail_singular.format(wait=wait)
else:
detail = self.extra_detail_plural.format(wait=wait)
else:
detail = self.default_detail
self.wait = wait
super().__init__(detail, code)Functions for handling different types of errors.
def server_error(request, *args, **kwargs):
"""
Generic 500 error handler for API views.
Args:
request: HTTP request object
*args: Additional arguments
**kwargs: Additional keyword arguments
Returns:
Response: 500 error response
"""
def bad_request(request, exception, *args, **kwargs):
"""
Generic 400 error handler for API views.
Args:
request: HTTP request object
exception: Exception that caused the error
*args: Additional arguments
**kwargs: Additional keyword arguments
Returns:
Response: 400 error response
"""from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
class BookView(APIView):
def get(self, request):
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request):
serializer = BookSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
try:
book = Book.objects.get(pk=pk)
book.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except Book.DoesNotExist:
return Response(
{'error': 'Book not found'},
status=status.HTTP_404_NOT_FOUND
)from rest_framework import exceptions
from rest_framework.views import APIView
class BookView(APIView):
def get_object(self, pk):
try:
return Book.objects.get(pk=pk)
except Book.DoesNotExist:
raise exceptions.NotFound('Book not found')
def post(self, request):
# Validate permissions
if not request.user.has_perm('myapp.add_book'):
raise exceptions.PermissionDenied(
'You do not have permission to create books'
)
# Validate data
if not request.data.get('title'):
raise exceptions.ValidationError({
'title': ['This field is required.']
})
# Create book
serializer = BookSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
# Raise validation error with serializer errors
raise exceptions.ValidationError(serializer.errors)from rest_framework import exceptions, status
class BookNotAvailable(exceptions.APIException):
status_code = status.HTTP_409_CONFLICT
default_detail = 'Book is not available for checkout'
default_code = 'book_not_available'
class LibraryCardExpired(exceptions.APIException):
status_code = status.HTTP_403_FORBIDDEN
default_detail = 'Library card has expired'
default_code = 'card_expired'
def __init__(self, expiry_date=None):
if expiry_date:
detail = f'Library card expired on {expiry_date}'
else:
detail = self.default_detail
super().__init__(detail)
# Usage in views
class CheckoutView(APIView):
def post(self, request):
book_id = request.data.get('book_id')
book = Book.objects.get(pk=book_id)
if not book.is_available:
raise BookNotAvailable()
if request.user.library_card.is_expired:
raise LibraryCardExpired(request.user.library_card.expiry_date)
# Process checkout
return Response({'status': 'checked out'})from rest_framework import exceptions
# Simple string detail
raise exceptions.ValidationError('Invalid data')
# Response: {"detail": "Invalid data"}
# Dictionary detail for field errors
raise exceptions.ValidationError({
'title': ['This field is required.'],
'isbn': ['ISBN must be 13 digits.']
})
# Response: {
# "title": ["This field is required."],
# "isbn": ["ISBN must be 13 digits."]
# }
# List detail for multiple errors
raise exceptions.ValidationError([
'First error message',
'Second error message'
])
# Response: [
# "First error message",
# "Second error message"
# ]
# Error details with codes
from rest_framework.exceptions import ErrorDetail
raise exceptions.ValidationError({
'title': [ErrorDetail('This field is required.', code='required')],
'pages': [ErrorDetail('Must be a positive integer.', code='invalid')]
})from rest_framework import status
def handle_response(response_code):
if status.is_success(response_code):
print("Operation successful")
elif status.is_client_error(response_code):
print("Client error occurred")
elif status.is_server_error(response_code):
print("Server error occurred")
elif status.is_redirect(response_code):
print("Redirect required")
elif status.is_informational(response_code):
print("Informational response")
# Example usage
handle_response(status.HTTP_201_CREATED) # "Operation successful"
handle_response(status.HTTP_404_NOT_FOUND) # "Client error occurred"
handle_response(status.HTTP_500_INTERNAL_SERVER_ERROR) # "Server error occurred"def _get_error_details(data, default_code=None):
"""
Process error data into ErrorDetail instances.
Args:
data: Error data (string, dict, or list)
default_code (str): Default error code
Returns:
Processed error details
"""
def _get_codes(detail):
"""
Extract error codes from error detail structure.
Args:
detail: Error detail structure
Returns:
Error codes in same structure
"""
def _get_full_details(detail):
"""
Get full error details including both messages and codes.
Args:
detail: Error detail structure
Returns:
Full error details with messages and codes
"""Install with Tessl CLI
npx tessl i tessl/pypi-djangorestframework