Merge pull request #1 from chinmayroy/chinmayroy-patch-1

Update response.py
This commit is contained in:
Chinmay Roy 2025-03-23 12:46:46 +06:00 committed by GitHub
commit f73c43d082
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,34 +1,38 @@
""" import re
The Response class in REST framework is similar to HTTPResponse, except that
it is initialized with unrendered data, instead of a pre-rendered string.
The appropriate renderer is called during Django's template response rendering.
"""
from http.client import responses from http.client import responses
from django.template.response import SimpleTemplateResponse from django.template.response import SimpleTemplateResponse
from rest_framework.serializers import Serializer from rest_framework.serializers import Serializer
from rest_framework.renderers import JSONRenderer
from rest_framework import status
# Function to convert snake_case string to camelCase
def camel_case(snake_str):
components = snake_str.split('_') # Split the string by underscores
return components[0] + ''.join(x.title() for x in components[1:]) # Join components into camelCase
# Function to recursively convert all dictionary keys in nested structures from snake_case to camelCase
def convert_keys_to_camel_case(data):
if isinstance(data, dict):
# If it's a dictionary, convert each key recursively
return {camel_case(key): convert_keys_to_camel_case(value) for key, value in data.items()}
elif isinstance(data, list):
# If it's a list, apply the conversion to each item
return [convert_keys_to_camel_case(item) for item in data]
else:
# If it's neither a dictionary nor a list, return the value as is
return data
# Custom response class that extends SimpleTemplateResponse for custom formatting
class Response(SimpleTemplateResponse): class Response(SimpleTemplateResponse):
"""
An HttpResponse that allows its data to be rendered into
arbitrary media types.
"""
def __init__(self, data=None, status=None, def __init__(self, data=None, status=None,
template_name=None, headers=None, template_name=None, headers=None,
exception=False, content_type=None): exception=False, content_type=None,
""" renderer=JSONRenderer(), accepted_media_type='application/json', request=None):
Alters the init arguments slightly. # Initialize the parent class
For example, drop 'template_name', and instead use 'data'.
Setting 'renderer' and 'media_type' will typically be deferred,
For example being set automatically by the `APIView`.
"""
super().__init__(None, status=status) super().__init__(None, status=status)
# If data is an instance of Serializer, raise an error as it should be serialized first
if isinstance(data, Serializer): if isinstance(data, Serializer):
msg = ( msg = (
'You passed a Serializer instance as data, but ' 'You passed a Serializer instance as data, but '
@ -37,30 +41,45 @@ class Response(SimpleTemplateResponse):
) )
raise AssertionError(msg) raise AssertionError(msg)
# Initialize other attributes
self.data = data self.data = data
self.template_name = template_name self.template_name = template_name
self.exception = exception self.exception = exception
self.content_type = content_type self.content_type = content_type
# Set the renderer (default is JSONRenderer) and media type (default is JSON)
self.accepted_renderer = renderer
self.accepted_media_type = accepted_media_type
# Store the request object for rendering context
self.renderer_context = {
'request': request
}
# Add any custom headers if provided
if headers: if headers:
for name, value in headers.items(): for name, value in headers.items():
self[name] = value self[name] = value
# Allow generic typing checking for responses. # For Python 3.7+, this allows you to treat CustomResponse as a generic class
def __class_getitem__(cls, *args, **kwargs): def __class_getitem__(cls, *args, **kwargs):
return cls return cls
# Property that renders the response content
@property @property
def rendered_content(self): def rendered_content(self):
renderer = getattr(self, 'accepted_renderer', None) # Get the renderer, media type, and context from the response
accepted_media_type = getattr(self, 'accepted_media_type', None) renderer = self.accepted_renderer
context = getattr(self, 'renderer_context', None) accepted_media_type = self.accepted_media_type
context = self.renderer_context
# Assertions to ensure required settings are provided
assert renderer, ".accepted_renderer not set on Response" assert renderer, ".accepted_renderer not set on Response"
assert accepted_media_type, ".accepted_media_type not set on Response" assert accepted_media_type, ".accepted_media_type not set on Response"
assert context is not None, ".renderer_context not set on Response" assert context is not None, ".renderer_context not set on Response"
context['response'] = self context['response'] = self # Add the current response to the context
# Determine content type and charset
media_type = renderer.media_type media_type = renderer.media_type
charset = renderer.charset charset = renderer.charset
content_type = self.content_type content_type = self.content_type
@ -69,33 +88,60 @@ class Response(SimpleTemplateResponse):
content_type = "{}; charset={}".format(media_type, charset) content_type = "{}; charset={}".format(media_type, charset)
elif content_type is None: elif content_type is None:
content_type = media_type content_type = media_type
self['Content-Type'] = content_type self['Content-Type'] = content_type # Set the content type header
# Render the response data
ret = renderer.render(self.data, accepted_media_type, context) ret = renderer.render(self.data, accepted_media_type, context)
# Get the status code
status_code = self.status_code
# Map status codes to status messages
code_to_msg = {
status.HTTP_200_OK: "success",
status.HTTP_202_ACCEPTED: "accepted",
status.HTTP_201_CREATED: "created",
status.HTTP_204_NO_CONTENT: "no_content",
status.HTTP_400_BAD_REQUEST: "validation_error",
status.HTTP_401_UNAUTHORIZED: "unauthorized",
status.HTTP_403_FORBIDDEN: "forbidden",
status.HTTP_404_NOT_FOUND: "not_found",
status.HTTP_406_NOT_ACCEPTABLE: "not_acceptable",
status.HTTP_500_INTERNAL_SERVER_ERROR: "server_error",
}
# Get the message corresponding to the status code
status_message = code_to_msg.get(status_code, "unknown_error")
# Create the final structured response
structured_response = {
"status": status_message,
"code": status_code,
"data": convert_keys_to_camel_case(self.data.get("data", {})),
"message": self.data.get("message", ""),
}
# If the renderer returns a string, return the rendered JSON response
if isinstance(ret, str): if isinstance(ret, str):
assert charset, ( assert charset, (
'renderer returned unicode, and did not specify ' 'renderer returned unicode, and did not specify '
'a charset value.' 'a charset value.'
) )
return ret.encode(charset) return JSONRenderer().render(structured_response)
# If no content was rendered, remove the content type header
if not ret: if not ret:
del self['Content-Type'] del self['Content-Type']
return ret return JSONRenderer().render(structured_response)
# Property to get the status text corresponding to the status code
@property @property
def status_text(self): def status_text(self):
"""
Returns reason text corresponding to our HTTP response status code.
Provided for convenience.
"""
return responses.get(self.status_code, '') return responses.get(self.status_code, '')
# Override __getstate__ to remove non-essential state info
def __getstate__(self): def __getstate__(self):
"""
Remove attributes from the response that shouldn't be cached.
"""
state = super().__getstate__() state = super().__getstate__()
for key in ( for key in (
'accepted_renderer', 'renderer_context', 'resolver_match', 'accepted_renderer', 'renderer_context', 'resolver_match',
@ -103,5 +149,5 @@ class Response(SimpleTemplateResponse):
): ):
if key in state: if key in state:
del state[key] del state[key]
state['_closable_objects'] = [] state['_closable_objects'] = [] # Clear closable objects to avoid issues
return state return state