PEP 484 type stubs for Django REST Framework enabling static type checking with comprehensive type definitions for all major DRF components
—
Django REST Framework provides enhanced Request and Response objects that extend Django's base HTTP handling with additional features for API development. The type stubs ensure complete type safety for request parsing, response rendering, and content negotiation.
class Request(HttpRequest):
"""Enhanced request object with DRF-specific functionality."""
# Parser and authentication configuration
parsers: Sequence[BaseParser] | None
authenticators: Sequence[BaseAuthentication | ForcedAuthentication] | None
negotiator: BaseContentNegotiation | None
# Content negotiation results
accepted_renderer: BaseRenderer
accepted_media_type: str
# Authentication results
user: AbstractUser | AnonymousUser
auth: Any # Token, session, or custom auth object
# Parsed data properties
@property
def data(self) -> dict[str, Any]:
"""
Parsed request data from request body.
Returns:
dict[str, Any]: Parsed data (JSON, form data, etc.)
"""
...
@property
def query_params(self) -> QueryDict:
"""
Query parameters from URL (immutable).
Returns:
QueryDict: URL query parameters
"""
...
def __init__(
self,
request: HttpRequest,
parsers: Sequence[BaseParser] | None = None,
authenticators: Sequence[BaseAuthentication] | None = None,
negotiator: BaseContentNegotiation | None = None,
parser_context: dict[str, Any] | None = None
) -> None: ...Key Properties:
data: dict[str, Any] - Parsed request body (JSON, form data, files)query_params: QueryDict - URL query parameters (immutable)user: AbstractUser | AnonymousUser - Authenticated user or anonymous userauth: Any - Authentication object (token, session data, etc.)def is_form_media_type(media_type: str) -> bool:
"""
Check if media type is form-encoded.
Args:
media_type: Content-Type header value
Returns:
bool: True if form media type
"""
...
def clone_request(request: Request, method: str) -> Request:
"""
Clone a request with a different HTTP method.
Args:
request: Original request object
method: New HTTP method
Returns:
Request: Cloned request with new method
"""
...class ForcedAuthentication:
"""Force authentication for testing purposes."""
def __init__(self, user: AbstractUser, token: Any = None) -> None: ...Parameters:
user: AbstractUser - User to authenticate astoken: Any - Optional authentication tokenclass Empty:
"""Marker class for empty request data."""
pass
# Singleton instance
empty: Emptyclass Response(SimpleTemplateResponse):
"""Enhanced response object with DRF functionality."""
# Response data and metadata
data: Any
exception: bool
content_type: str | None
# Content negotiation results
accepted_renderer: BaseRenderer
accepted_media_type: str
renderer_context: dict[str, Any]
def __init__(
self,
data: Any = None,
status: int | None = None,
template_name: str | list[str] | None = None,
headers: dict[str, str] | None = None,
exception: bool = False,
content_type: str | None = None
) -> None: ...
@property
def rendered_content(self) -> bytes:
"""Get rendered response content."""
...
@property
def status_text(self) -> str:
"""Get HTTP status text."""
...
def render(self) -> Response:
"""
Render the response content using the configured renderer.
Returns:
Response: Self after rendering
"""
...Parameters:
data: Any - Response data to serializestatus: int | None - HTTP status codetemplate_name: str | list[str] | None - Template for HTML responsesheaders: dict[str, str] | None - Additional HTTP headersexception: bool - Whether response represents an exceptioncontent_type: str | None - Override content typefrom rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class BookCreateView(APIView):
"""Demonstrate request data access."""
def post(self, request: Request) -> Response:
"""Handle POST request with data parsing."""
# Access parsed JSON/form data
title = request.data.get('title')
author_id = request.data.get('author_id')
# Access query parameters
format_type = request.query_params.get('format', 'json')
include_meta = request.query_params.get('include_meta', 'false').lower() == 'true'
# Access authentication info
user = request.user
auth_token = request.auth
# Access request metadata
content_type = request.content_type
method = request.method
# Validate and process data
if not title or not author_id:
return Response(
{'error': 'Title and author_id are required'},
status=status.HTTP_400_BAD_REQUEST
)
# Create book instance
book = Book.objects.create(
title=title,
author_id=author_id,
created_by=user if user.is_authenticated else None
)
# Return response based on format preference
response_data = {
'id': book.id,
'title': book.title,
'author_id': book.author_id
}
if include_meta:
response_data['meta'] = {
'created_by': user.username if user.is_authenticated else None,
'created_at': book.created_at.isoformat(),
'format': format_type
}
return Response(response_data, status=status.HTTP_201_CREATED)from django.core.files.storage import default_storage
class FileUploadView(APIView):
"""Handle file uploads with request data access."""
def post(self, request: Request) -> Response:
"""Process file upload request."""
# Access uploaded files
uploaded_file = request.data.get('file')
if not uploaded_file:
return Response(
{'error': 'No file provided'},
status=status.HTTP_400_BAD_REQUEST
)
# Access additional form fields
title = request.data.get('title', uploaded_file.name)
description = request.data.get('description', '')
# Validate file
if uploaded_file.size > 10 * 1024 * 1024: # 10MB limit
return Response(
{'error': 'File too large'},
status=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE
)
# Save file
file_path = default_storage.save(
f"uploads/{uploaded_file.name}",
uploaded_file
)
# Create database record
document = Document.objects.create(
title=title,
description=description,
file_path=file_path,
uploaded_by=request.user
)
return Response({
'id': document.id,
'title': document.title,
'file_url': default_storage.url(file_path)
}, status=status.HTTP_201_CREATED)from rest_framework.response import Response
from rest_framework import status
class BookDetailView(APIView):
"""Demonstrate various response patterns."""
def get(self, request: Request, pk: int) -> Response:
"""Return different response types based on conditions."""
try:
book = Book.objects.get(pk=pk)
except Book.DoesNotExist:
# Error response with custom status
return Response(
{'error': 'Book not found'},
status=status.HTTP_404_NOT_FOUND
)
# Check permissions
if book.is_private and request.user != book.owner:
return Response(
{'error': 'Access denied'},
status=status.HTTP_403_FORBIDDEN
)
# Success response with data
serializer = BookSerializer(book, context={'request': request})
return Response(serializer.data, status=status.HTTP_200_OK)
def put(self, request: Request, pk: int) -> Response:
"""Update with validation response patterns."""
try:
book = Book.objects.get(pk=pk)
except Book.DoesNotExist:
return Response(
{'error': 'Book not found'},
status=status.HTTP_404_NOT_FOUND
)
# Validate and update
serializer = BookSerializer(book, data=request.data, partial=False)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
else:
# Validation error response
return Response(
serializer.errors,
status=status.HTTP_400_BAD_REQUEST
)
def delete(self, request: Request, pk: int) -> Response:
"""Delete with empty response."""
try:
book = Book.objects.get(pk=pk)
book.delete()
# Empty success response
return Response(status=status.HTTP_204_NO_CONTENT)
except Book.DoesNotExist:
return Response(
{'error': 'Book not found'},
status=status.HTTP_404_NOT_FOUND
)class CustomResponseView(APIView):
"""Demonstrate custom response headers and content types."""
def get(self, request: Request) -> Response:
"""Response with custom headers."""
data = {'message': 'Custom response'}
headers = {
'X-Custom-Header': 'Custom Value',
'X-Request-ID': str(uuid.uuid4()),
'Cache-Control': 'no-cache, no-store',
}
return Response(
data,
status=status.HTTP_200_OK,
headers=headers
)
def post(self, request: Request) -> Response:
"""Response with custom content type."""
# Process request data
result = {'processed': True, 'timestamp': timezone.now().isoformat()}
return Response(
result,
status=status.HTTP_201_CREATED,
content_type='application/vnd.api+json'
)class ConditionalResponseView(APIView):
"""Demonstrate conditional response handling."""
def get(self, request: Request) -> Response:
"""Return different responses based on request parameters."""
# Check for format preference
format_param = request.query_params.get('format', 'json')
if format_param == 'xml':
return Response(
{'message': 'XML format requested'},
content_type='application/xml'
)
elif format_param == 'csv':
# Return CSV data
csv_data = "id,name,value\n1,test,100"
return Response(
csv_data,
content_type='text/csv',
headers={'Content-Disposition': 'attachment; filename="data.csv"'}
)
else:
# Default JSON response
return Response({
'data': ['item1', 'item2', 'item3'],
'format': 'json'
})from rest_framework.request import override_method
class MethodOverrideView(APIView):
"""Handle method override for clients that don't support all HTTP methods."""
def post(self, request: Request) -> Response:
"""Handle POST with method override."""
# Check for method override header
override_method_header = request.META.get('HTTP_X_HTTP_METHOD_OVERRIDE')
if override_method_header:
with override_method(request, override_method_header):
if override_method_header.upper() == 'PUT':
return self.put(request)
elif override_method_header.upper() == 'PATCH':
return self.patch(request)
elif override_method_header.upper() == 'DELETE':
return self.delete(request)
# Regular POST handling
return Response({'method': 'POST'})
def put(self, request: Request) -> Response:
return Response({'method': 'PUT'})
def patch(self, request: Request) -> Response:
return Response({'method': 'PATCH'})
def delete(self, request: Request) -> Response:
return Response({'method': 'DELETE'})import json
from django.http import StreamingHttpResponse
class StreamingResponseView(APIView):
"""Handle large dataset streaming."""
def get(self, request: Request) -> StreamingHttpResponse:
"""Stream large JSON response."""
def generate_data():
"""Generator function for streaming data."""
yield '{"items": ['
first = True
for book in Book.objects.iterator(chunk_size=1000):
if not first:
yield ','
first = False
book_data = {
'id': book.id,
'title': book.title,
'author': book.author.name if book.author else None
}
yield json.dumps(book_data)
yield ']}'
response = StreamingHttpResponse(
generate_data(),
content_type='application/json'
)
response['Content-Disposition'] = 'attachment; filename="books.json"'
return responseclass RequestContextView(APIView):
"""Access comprehensive request context information."""
def get(self, request: Request) -> Response:
"""Return detailed request context."""
context = {
# HTTP Information
'method': request.method,
'path': request.path,
'full_path': request.get_full_path(),
'scheme': request.scheme,
'is_secure': request.is_secure(),
# Authentication
'user': {
'username': request.user.username if request.user.is_authenticated else None,
'is_authenticated': request.user.is_authenticated,
'is_staff': getattr(request.user, 'is_staff', False),
},
'auth_type': type(request.auth).__name__ if request.auth else None,
# Content Information
'content_type': request.content_type,
'accepted_media_type': getattr(request, 'accepted_media_type', None),
'accepted_renderer': type(getattr(request, 'accepted_renderer', None)).__name__,
# Client Information
'user_agent': request.META.get('HTTP_USER_AGENT'),
'remote_addr': request.META.get('REMOTE_ADDR'),
'host': request.get_host(),
# Data Summary
'has_data': bool(request.data),
'data_keys': list(request.data.keys()) if request.data else [],
'query_params': dict(request.query_params),
}
return Response(context)class ErrorResponseView(APIView):
"""Demonstrate standardized error response patterns."""
def post(self, request: Request) -> Response:
"""Handle request with comprehensive error handling."""
try:
# Validate required fields
required_fields = ['name', 'email']
missing_fields = []
for field in required_fields:
if field not in request.data:
missing_fields.append(field)
if missing_fields:
return Response({
'error': 'Missing required fields',
'code': 'MISSING_FIELDS',
'details': {
'missing_fields': missing_fields
}
}, status=status.HTTP_400_BAD_REQUEST)
# Validate email format
email = request.data['email']
if '@' not in email:
return Response({
'error': 'Invalid email format',
'code': 'INVALID_EMAIL',
'details': {
'field': 'email',
'value': email
}
}, status=status.HTTP_400_BAD_REQUEST)
# Process successful request
return Response({
'message': 'Success',
'data': request.data
}, status=status.HTTP_201_CREATED)
except Exception as e:
# Handle unexpected errors
return Response({
'error': 'Internal server error',
'code': 'INTERNAL_ERROR',
'details': {
'message': str(e) if settings.DEBUG else 'An unexpected error occurred'
}
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)class ResponseFormatView(APIView):
"""Demonstrate consistent response formatting."""
def format_success_response(
self,
data: Any,
message: str = 'Success',
status_code: int = status.HTTP_200_OK
) -> Response:
"""Format successful response with consistent structure."""
response_data = {
'success': True,
'message': message,
'data': data,
'timestamp': timezone.now().isoformat()
}
return Response(response_data, status=status_code)
def format_error_response(
self,
error: str,
code: str | None = None,
details: dict[str, Any] | None = None,
status_code: int = status.HTTP_400_BAD_REQUEST
) -> Response:
"""Format error response with consistent structure."""
response_data = {
'success': False,
'error': error,
'code': code,
'details': details or {},
'timestamp': timezone.now().isoformat()
}
return Response(response_data, status=status_code)
def get(self, request: Request) -> Response:
"""Example using formatted responses."""
books = Book.objects.all()[:10]
serializer = BookSerializer(books, many=True)
return self.format_success_response(
data=serializer.data,
message='Books retrieved successfully'
)This comprehensive request and response system provides type-safe HTTP handling with full mypy support, enabling confident API development with proper request parsing, response formatting, and error handling patterns.
Install with Tessl CLI
npx tessl i tessl/pypi-djangorestframework-stubs