Files
igny8/backend/igny8_core/api/response.py
IGNY8 VPS (Salman) 84c18848b0 Refactor error_response function for improved argument handling
- Enhanced the `error_response` function to support backward compatibility by normalizing arguments when positional arguments are misused.
- Updated various views to pass `None` for the `errors` parameter in `error_response` calls, ensuring consistent response formatting.
- Adjusted logging in `ContentSyncService` and `WordPressClient` to use debug level for expected 401 errors, improving log clarity.
- Removed deprecated fields from serializers and views, streamlining content management processes.
2025-11-22 03:04:35 +00:00

177 lines
5.9 KiB
Python

"""
Unified API Response Helpers
Provides consistent response format across all endpoints
"""
from rest_framework.response import Response
from rest_framework import status
import uuid
from typing import Any
from django.http import HttpRequest
def get_request_id(request):
"""Get request ID from request object (set by middleware) or headers, or generate new one"""
if not request:
return None
# First check if middleware set request_id on request object
if hasattr(request, 'request_id') and request.request_id:
return request.request_id
# Fallback to headers
if hasattr(request, 'META'):
request_id = request.META.get('HTTP_X_REQUEST_ID') or request.META.get('X-Request-ID')
if request_id:
return request_id
# Generate new request ID if none found
return str(uuid.uuid4())
def success_response(data=None, message=None, status_code=status.HTTP_200_OK, request=None):
"""
Create a standardized success response
Args:
data: Response data (dict, list, or None)
message: Optional success message
status_code: HTTP status code (default: 200)
request: Request object (optional, for request_id)
Returns:
Response object with unified format
"""
response_data = {
'success': True,
}
if data is not None:
response_data['data'] = data
if message:
response_data['message'] = message
# Add request_id if request is provided
if request:
response_data['request_id'] = get_request_id(request)
return Response(response_data, status=status_code)
def error_response(error=None, errors=None, status_code=status.HTTP_400_BAD_REQUEST, request=None, debug_info=None):
"""
Create a standardized error response
Args:
error: Top-level error message
errors: Field-specific errors (dict of field -> list of errors)
status_code: HTTP status code (default: 400)
request: Request object (optional, for request_id)
debug_info: Debug information (only in DEBUG mode)
Returns:
Response object with unified error format
"""
response_data = {
'success': False,
}
# Backwards compatibility: some callers used positional args in the order
# (error, status_code, request) which maps to (error, errors, status_code=request)
# causing `status_code` to be a Request object and raising TypeError.
# Detect this misuse and normalize arguments:
try:
if request is None and status_code is not None:
# If status_code appears to be a Request object, shift arguments
if isinstance(status_code, HttpRequest) or hasattr(status_code, 'META'):
# original call looked like: error_response(msg, status.HTTP_400_BAD_REQUEST, request)
# which resulted in: errors = status.HTTP_400..., status_code = request
request = status_code
# If `errors` holds an int-like HTTP status, use it as status_code
if isinstance(errors, int):
status_code = errors
errors = None
else:
# fallback to default 400
status_code = status.HTTP_400_BAD_REQUEST
except Exception:
# Defensive: if introspection fails, continue with provided args
pass
if error:
response_data['error'] = error
elif status_code == status.HTTP_400_BAD_REQUEST:
response_data['error'] = 'Bad request'
elif status_code == status.HTTP_401_UNAUTHORIZED:
response_data['error'] = 'Authentication required'
elif status_code == status.HTTP_403_FORBIDDEN:
response_data['error'] = 'Permission denied'
elif status_code == status.HTTP_404_NOT_FOUND:
response_data['error'] = 'Resource not found'
elif status_code == status.HTTP_409_CONFLICT:
response_data['error'] = 'Conflict'
elif status_code == status.HTTP_422_UNPROCESSABLE_ENTITY:
response_data['error'] = 'Validation failed'
elif status_code == status.HTTP_429_TOO_MANY_REQUESTS:
response_data['error'] = 'Rate limit exceeded'
elif status_code >= 500:
response_data['error'] = 'Internal server error'
else:
response_data['error'] = 'An error occurred'
if errors:
response_data['errors'] = errors
# Add request_id if request is provided
if request:
response_data['request_id'] = get_request_id(request)
# Add debug info in DEBUG mode
if debug_info:
response_data['debug'] = debug_info
return Response(response_data, status=status_code)
def paginated_response(paginated_data, message=None, request=None):
"""
Create a standardized paginated response
Args:
paginated_data: Paginated data dict from DRF paginator (contains count, next, previous, results)
message: Optional success message
request: Request object (optional, for request_id)
Returns:
Response object with unified paginated format
"""
response_data = {
'success': True,
}
# Copy pagination fields from DRF paginator
if isinstance(paginated_data, dict):
response_data.update({
'count': paginated_data.get('count', 0),
'next': paginated_data.get('next'),
'previous': paginated_data.get('previous'),
'results': paginated_data.get('results', [])
})
else:
# Fallback if paginated_data is not a dict
response_data['count'] = 0
response_data['next'] = None
response_data['previous'] = None
response_data['results'] = []
if message:
response_data['message'] = message
# Add request_id if request is provided
if request:
response_data['request_id'] = get_request_id(request)
return Response(response_data, status=status.HTTP_200_OK)