Skip to main content

API Response Shaper

License PyPI Release Pylint Score Supported Python Versions Supported Django Versions Pre-commit Open Issues Last Commit Languages Coverage

api-response-shaper is a Django middleware and decorator-based package that helps structure API responses in a consistent format. It includes dynamic configurations and various pre-built response formats, such as paginated responses, batch responses, minimal success responses, and error responses. This package allows developers to streamline response shaping for their Django REST Framework (DRF) APIs, ensuring a uniform response format across different API endpoints.

Features

  • Dynamic Middleware: Automatically structures API responses for successful and error cases.
  • Customizable Handlers: Easily switch between different response formats using decorators.
  • Pre-built Response Types: Supports a variety of response types, including:
    • Standard API responses
    • Paginated responses
    • Batch operation responses
    • Error responses
    • Authentication responses
    • Minimal success responses
  • Custom Response Handlers: Define custom success and error handlers for fine-tuned control.
  • Exclusion Paths: Skip specific routes from being processed by the middleware.
  • Django Version: Compatible with Django REST Framework (DRF) and Django middleware.

Project Detail

  • Language: Python >= 3.10, < 3.14
  • Framework: Django >= 5.2, < 5.3
  • Django REST Framework: >= 3.18, < 3.19

The CI compatibility matrix covers Python 3.10-3.13, Django 5.2 LTS, and DRF 3.18. Python 3.10 remains supported until October 2026. DRF 3.18 uses dictionary-shaped errors for list serializers (many=True); structured error extraction preserves this upstream representation.

Documentation

The documentation is organized into the following sections:

Setup

Setting up the api-response-shaper is simple, folow these steps to setup:

  1. Install the Package:

To install the api-response-shaper, simply use pip:

$ pip install api-response-shaper
  1. Add to Installed Apps

Add response_shaper to your INSTALLED_APPS in your Django settings file:

INSTALLED_APPS = [
    # ...
    'response_shaper',
    # ...
]
  1. Configure Middleware:

Add DynamicResponseMiddleware middleware to the MIDDLEWARE list in your Django settings.py:

MIDDLEWARE = [
    # ...
    'response_shaper.middleware.DynamicResponseMiddleware',
    # ...
]

Once this middleware is added, eligible application/json API responses are dynamically structured. Successful and error responses use a consistent envelope while streaming responses, bodyless HTTP statuses, already content-encoded responses, excluded paths, and non-application/json media types are left untouched. You can customize the response format using settings or decorators for specific views, but by default, this middleware provides a standardized API response format. Responses produced by this package's own decorators/helpers are marked internally so the global middleware does not shape them a second time. You can also configure the api-response-shaper for your project needs, for more details, please refer to the Settings section.


Usage

Decorators for Response Formatting

You can apply different response formats using decorators in your views. Available decorators:

  • @format_api_response: Applies the standard API response format.
  • @format_paginated_response: Applies the paginated response format.
  • @format_error_response: Applies the error response format.
  • @format_minimal_success_response: Applies the minimal success response format.
  • @format_batch_response: Applies the batch operation response format.
  • @format_auth_response: Applies the authentication response format.

Example usage:

from rest_framework.response import Response
from response_shaper.decorators import format_api_response, format_paginated_response

@format_api_response
def my_api_view(request):
    data = {"key": "value"}
    return Response(data, status=200)

@format_paginated_response
def paginated_view(request):
    paginated_data = {
        "data": [{"id": 1}, {"id": 2}],
        "page": 1,
        "total_pages": 5,
        "total_items": 50,
    }
    return Response(paginated_data, status=200)

Custom Success and Error Handlers

If you want more control over the response structure, you can define custom handlers. Use the RESPONSE_SHAPER settings to specify the paths to your custom handlers. for more details, please refer to the Settings.

For example, define a custom success handler in myapp.responses:

# myapp/responses.py
from rest_framework.response import Response

def custom_success_handler(response):
    return Response({
        "custom_status": "success",
        "code": response.status_code,
        "payload": response.data
    }, status=response.status_code)

Then configure this handler in your settings:

RESPONSE_SHAPER_SUCCESS_HANDLER = "path.to_your.custom_success_handler"

Built-In Response Types

  • Standard API Response:
api_response(
  success: bool = True,
  message: str = None,
  data: dict = None,
  errors: dict = None,
  status_code: int = 200
) -> Response

  • Paginated Response:
paginated_api_response(
  success: bool = True,
  data: list = None,
  page: int = None,
  total_pages: int = None,
  total_items: int = None,
  status_code: int = 200
) -> Response

  • Error Response:
error_api_response(
  message: str = None,
  errors: dict = None,
  error_code: str = None,
  status_code: int = 400
) -> Response

  • Minimal Success Response:
minimal_success_response(
  message: str = "Request successful",
  status_code: int = 200
) -> Response

  • Batch Operation Response:
batch_api_response(
  success: bool = True,
  results: list = None,
  errors: dict = None,
  status_code: int = 200
) -> Response`

  • Authentication Response:
auth_api_response(
  success: bool = True,
  message: str = None,
  token: str = None,
  user: dict = None,
  errors: dict = None,
  status_code: int = 200
) -> Response

DynamicResponseMiddleware

The DynamicResponseMiddleware is designed to structure API responses in a consistent format for both synchronous and asynchronous workflows. It ensures that all responses, whether successful or erroneous, follow a standardized JSON structure. This middleware is highly configurable and supports custom handlers for success and error responses.

Key Features

  • Consistent Response Format: Shapes eligible application/json responses into a standardized structure.
  • Async Support: Supports synchronous/asynchronous middleware, async custom handlers, and async decorated views.
  • Exception Handling: Automatically catches and processes Django exceptions, returning structured error responses.
  • Renderer Preservation: DRF responses keep their negotiated renderer instead of being re-serialized through Django's JsonResponse.
  • Response Metadata Safety: Preserves valid headers/cookies while dropping stale body-derived metadata such as ETag and Content-Length when the body changes.
  • Customizable Handlers: Allows customization of success and error response formats via the RESPONSE_SHAPER configuration.

Middleware shaping boundaries

The built-in middleware shapes only responses whose media type is exactly application/json (parameters such as charset are allowed). It intentionally bypasses streaming responses, 1xx, 204, 205, and 304 responses, already content-encoded bodies, and other JSON-based media types such as application/problem+json or application/json-seq. Those formats can have their own representation contracts and are not rewritten implicitly.

Performance benchmark

Run the standalone old-versus-new JsonResponse shaping benchmark from the repository root:

python benchmarks/benchmark_middleware.py

Use --items, --iterations, and --repeat to change the workload. The script also verifies that DRF responses use their negotiated renderer exactly once and remain the original DRF Response object.


Exception Handling

The middleware automatically handles Django exceptions and structures error responses for both synchronous and asynchronous workflows. It ensures consistent error responses for a wide range of exceptions, including:

Common Exceptions

  • 404 Not Found:

    • ObjectDoesNotExist: The requested object was not found.
    • FieldDoesNotExist: The requested field does not exist.
    • EmptyResultSet: No results were found for the query.
  • 400 Bad Request:

    • MultipleObjectsReturned: Multiple objects were returned when only one was expected.
    • SuspiciousOperation: A suspicious operation was detected (e.g., security issues).
    • DisallowedHost: The request's host header is invalid or disallowed.
    • DisallowedRedirect: The request attempted a disallowed redirect.
    • BadRequest: A generic bad request error occurred.
    • FieldError: An error occurred with a field in the request.
    • ValidationError: Validation of the request data failed.
    • IntegrityError: A database integrity constraint was violated.
    • DataError: Invalid data was provided to the database.
  • 403 Forbidden:

    • PermissionDenied: The user does not have permission to perform the requested action.
  • 500 Internal Server Error:

    • MiddlewareNotUsed: A middleware component was not used.
    • ImproperlyConfigured: The application is improperly configured.
    • ProgrammingError: A database programming error occurred.
    • OperationalError: A database operational error occurred.
    • InternalError: An internal database error occurred.
    • DatabaseError: A generic database error occurred.
    • Generic exceptions: Any unexpected exception is caught and returned as a 500 error.

Async Support for Exception Handling

With the addition of async support, the middleware now seamlessly handles exceptions in asynchronous contexts. The ExceptionHandler class processes exceptions and returns structured JSON responses, whether the request is synchronous or asynchronous. For example:

  • Synchronous Workflow: Exceptions are caught and processed in the process_exception method.
  • Asynchronous Workflow: Exceptions are handled in the same way, ensuring consistent error responses across both sync and async views.

Settings

In this section, we dive deep into the settings configuration and defaults.

Default configuration:

Here are the default settings that are automatically applied:

# settings.py

RESPONSE_SHAPER_DEBUG_MODE = False
RESPONSE_SHAPER_RETURN_ERROR_AS_DICT = True
RESPONSE_SHAPER_ERROR_EXTRACTION = "first"
RESPONSE_SHAPER_EXCLUDED_PATHS = ["/admin/", "/schema/swagger-ui/", "/schema/redoc/", "/schema/"]
RESPONSE_SHAPER_SUCCESS_HANDLER = ""
RESPONSE_SHAPER_ERROR_HANDLER = ""

RESPONSE_SHAPER_DEBUG_MODE

  • Type: bool
  • Description: When set to True, disables response shaping for debugging purposes.
  • Default: False

RESPONSE_SHAPER_RETURN_ERROR_AS_DICT

  • Type: bool
  • Description: Controls the format of dict error messages extracted by the ExceptionHandler. When True, errors with nested dictionary structure are returned as a dictionary containing the innermost key-value pair from nested error structures. When False, only the innermost error message is returned as a string. This applies to error responses shaped by the handler, particularly for validation.
  • Default: True

Example:

# With RESPONSE_SHAPER_RETURN_ERROR_AS_DICT = True
error_input = {"field": {"detail": {"code": "invalid"}}}
# Result: {"code": "invalid"}

# With RESPONSE_SHAPER_RETURN_ERROR_AS_DICT = False
error_input = {"field": {"detail": {"code": "invalid"}}}
# Result: "invalid"

RESPONSE_SHAPER_ERROR_EXTRACTION

  • Type: str
  • Default: "first"
  • Description: Controls how error payloads are reduced before they are placed in the shaped response.

Built-in strategies:

  • "first": Legacy behavior. Traverses dictionaries/lists and returns only the first error encountered. This remains the default for backward compatibility.
  • "smart": Traverses normal error trees, but preserves a flat mapping of terminal values as one structured error payload. The decision is structural and does not depend on reserved keys such as code or detail. Terminal values and empty containers are preserved without coercing them to strings.
  • "full": Preserves the complete error payload without extracting a first error.

For example, with "smart":

RESPONSE_SHAPER_ERROR_EXTRACTION = "smart"

raise serializers.ValidationError(
    {
        "code": "map_bbox_required",
        "detail": "geometry_bbox is required for map requests.",
        "parameter": "geometry_bbox",
    }
)

The complete structured payload is preserved in the response error field, while a normal serializer error tree such as:

{
    "geometry_bbox": ["This field is required."],
    "zoom": ["Invalid zoom."],
}

continues to return only the first error.

For domain-specific rules, set the option to a dotted path to a callable:

RESPONSE_SHAPER_ERROR_EXTRACTION = "my_project.api.errors.extract_error"

The callable receives the original error data and must return the value that should be placed in the shaped response's error field.

Note: No generic library can perfectly infer whether every possible nested mapping is an error tree or an intentionally structured object. Use "full" or a custom extractor when your API's error contract is more complex than the "smart" structural heuristic.

RESPONSE_SHAPER_EXCLUDED_PATHS

  • Type: List[str]
  • Description: A list of URL paths where the middleware will not shape responses. This is useful for excluding admin or documentation routes from being processed, ensuring that those paths retain their original behavior.
  • Default: ["/admin/", "/schema/swagger-ui/", "/schema/redoc/", "/schema/"]

RESPONSE_SHAPER_SUCCESS_HANDLER

  • Type: str
  • Description: Optional dotted path to a callable that manages successful responses. An empty string uses the built-in success handler. Explicit invalid or non-callable paths fail fast through Django's configuration checks/runtime initialization instead of silently falling back. Sync and async callables are supported.
  • Default: "" (built-in success handler)

RESPONSE_SHAPER_ERROR_HANDLER

  • Type: str
  • Description: Optional dotted path to a callable that manages error responses. An empty string uses the built-in error handler. Explicit invalid or non-callable paths fail fast instead of silently falling back. Sync and async callables are supported.
  • Default: "" (built-in error handler)

HTTP headers from specialized helpers

redirect_response(redirect_url=...) sets the standard Location header when a URL is supplied. rate_limited_response(retry_after=...) sets Retry-After when a retry value is supplied. The values remain available in the JSON payload as before.

Thank you for using api-response-shaper. We hope this package enhances your Django application's API responses. If you have any questions or issues, feel free to open an issue on our GitHub repository.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

api_response_shaper-1.3.0.tar.gz (39.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

api_response_shaper-1.3.0-py3-none-any.whl (42.9 kB view details)

Uploaded Python 3

File details

Details for the file api_response_shaper-1.3.0.tar.gz.

File metadata

  • Download URL: api_response_shaper-1.3.0.tar.gz
  • Upload date:
  • Size: 39.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.2 CPython/3.14.7 Linux/6.17.0-1022-azure

File hashes

Hashes for api_response_shaper-1.3.0.tar.gz
Algorithm Hash digest
SHA256 00cf0f36bcf2406b08fcf40e0883441a001c52151dfaf5d3ad5347e6fb67342c
MD5 bc60c15887662836e881951a9560df4b
BLAKE2b-256 15b748a1dd2832f734dda1e9c161535b5ab5434f7816613bdbf94f38b198394d

See more details on using hashes here.

File details

Details for the file api_response_shaper-1.3.0-py3-none-any.whl.

File metadata

  • Download URL: api_response_shaper-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 42.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.2 CPython/3.14.7 Linux/6.17.0-1022-azure

File hashes

Hashes for api_response_shaper-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2b730010e785fe38424413d4d635df45bafdbcdbac2898e5361f23bb4dadfc16
MD5 9d4539a5ca2d4ef8b9b69abdda294976
BLAKE2b-256 86ba1d49d2b538ef062230bbba29ec7589db5f19f7a34f36cfa8e85f267a2510

See more details on using hashes here.

Release history Release notifications | RSS feed

1.4.0

2 files

This release

1.3.0 This release

2 files

1.2.1

2 files

1.2.0

2 files

1.1.0

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page