Skip to main content

A Django library for standardized API responses.

Project description

Django Unified Response

PyPI version Python Support Django Support License Tests

Wrap every Django REST Framework response in a clean, consistent, and fully customisable JSON envelope — no boilerplate, no weird edge cases.


✨ Why yet another response wrapper?

  • Zero boilerplate – all views automatically get the same envelope.
  • DRF error‑proof – the messiest validation errors are turned into a predictable, structured format.
  • Pagination‑aware – detects standard, cursor, and custom paginators, keeping metadata tidy.
  • Swagger‑ready – optional drf-spectacular integration generates the exact schemas.
  • Totally customisable – replace the envelope globally by writing a formatter class, without touching the library.

📦 What responses look like

✅ Success

{
    "success": true,
    "data": { "id": 1, "name": "Test Item" },
    "meta": {}
}

❌ Client error (4xx)

{
    "success": false,
    "error": {
        "type": "Fail",
        "code": "validation_error",
        "message": "Input validation failed.",
        "details": [
            { "field": "email", "issue": "Enter a valid email address." }
        ]
    }
}

💥 Server error (5xx)

{
    "success": false,
    "error": {
        "type": "Error",
        "code": "HTTP_500",
        "message": "A server error occurred.",
        "details": null
    }
}

⚙️ Requirements

  • Python 3.10+
  • Django 3.2 / 4.0 / 4.1 / 4.2
  • Django REST Framework 3.12+
  • (Optional) drf‑spectacular for OpenAPI schema generation

🚀 Installation

pip install django-unified-response

With Swagger support

pip install "django-unified-response[swagger]"

🛠 Quick Start

  1. No need to add to INSTALLED_APPS – the package has no models.
  2. Configure Django REST Framework in settings.py:
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'django_unified_response.renderers.UnifiedJSONRenderer',
        # Keep BrowsableAPIRenderer for the DRF web interface
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
    'EXCEPTION_HANDLER': 'django_unified_response.handlers.unified_exception_handler',
}

That’s it! Every response is now unified.


🔧 Configuration

All library settings live in the DUR_SETTINGS dictionary:

# settings.py
DUR_SETTINGS = {
    # Formatter class that defines the envelope (default shown)
    "FORMATTER_CLASS": "django_unified_response.formatters.DefaultFormatter",
    # Convert snake_case keys to camelCase
    "CAMELCASE_KEYS": False,
    # Temporarily disable the entire wrapper
    "ENABLE": True,
}
Setting Type Default Description
FORMATTER_CLASS string (import path) "django_unified_response.formatters.DefaultFormatter" Path to a formatter class (see Advanced Customisation)
CAMELCASE_KEYS bool False If True, all response keys become camelCase
ENABLE bool True Set to False to return raw DRF responses globally

🧪 Usage

Success responses

Return a standard DRF Response. The renderer wraps it automatically.

from rest_framework.views import APIView
from rest_framework.response import Response

class MyView(APIView):
    def get(self, request):
        return Response({"id": 1, "name": "Amir"})

Client receives:

{
    "success": true,
    "data": { "id": 1, "name": "Amir" },
    "meta": {}
}

Including metadata

If your response already has "data" and/or "meta" keys, the renderer respects them:

return Response({
    "data": {"items": [...]},
    "meta": {"page": 1, "total": 42}
})

Paginated responses

The renderer auto‑detects DRF pagination (any class that returns "results"), moves the results to data, and puts the rest (count, next, previous, cursor, etc.) under meta.pagination.

Bypassing the wrapper

Use the decorator on any view to keep the raw DRF response:

from django_unified_response.decorators import bypass_unified_response

@bypass_unified_response
class HealthCheckView(APIView):
    def get(self, request):
        return Response({"status": "ok"})

Error responses

Standard DRF exceptions

Raised automatically by serializers — the handler formats them.

serializer.is_valid(raise_exception=True)  # yields a formatted 4xx

Custom library exceptions

Import and raise for business‑logic errors:

from django_unified_response.exceptions import (
    NotFoundException,
    IntegrityException,
    ValidationException,
    AuthenticationFailedException,
)

def get_product(request, pk):
    try:
        product = Product.objects.get(pk=pk)
    except Product.DoesNotExist:
        raise NotFoundException()  # 404, code "not_found"

def create_product(request):
    try:
        ...
    except IntegrityError:
        raise IntegrityException(
            message="SKU already exists.",
            details={"sku": "duplicate"}
        )

📘 Swagger / OpenAPI

Install the [swagger] extra, then set the schema class:

REST_FRAMEWORK = {
    # ...
    'DEFAULT_SCHEMA_CLASS': 'django_unified_response.schema.UnifiedResponseAutoSchema',
}

Your OpenAPI docs will automatically show the unified success/error shapes.


🎨 Advanced Customisation

You can replace the entire envelope by writing your own formatter.

  1. Subclass BaseFormatter (or DefaultFormatter to override only parts):
# my_app/formatters.py
from django_unified_response.formatters import BaseFormatter

class MyFormatter(BaseFormatter):
    def format_success(self, data, meta=None):
        return {"ok": True, "result": data, "extra": meta or {}}

    def format_fail(self, error_code, message, details=None):
        return {
            "ok": False,
            "problem": {
                "code": error_code,
                "what": message,
                "fields": details or [],
            },
        }

    def format_error(self, error_code, message, details=None):
        return {
            "ok": False,
            "problem": {
                "code": error_code,
                "what": "Internal error",
                "trace": message if settings.DEBUG else None,
            },
        }
  1. Point to it in DUR_SETTINGS:
DUR_SETTINGS = {
    "FORMATTER_CLASS": "my_app.formatters.MyFormatter",
}

Every response now follows your own contract.


👩‍💻 Development

Prerequisites

  • uv
  • Python 3.10+

Setup

git clone https://github.com/amirhh-2000/django-unified-response.git
cd django-unified-response
make install-dev

Commands

make install-dev   # install development deps
make test          # run tests
make lint          # ruff linter
make format        # ruff formatter
make security      # bandit security checks
make clean         # remove build artifacts

🤝 Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes and write tests
  4. Run make lint and make test
  5. Update CHANGELOG.md (Keep a Changelog format)
  6. Open a pull request

📄 Changelog

All notable changes are documented in CHANGELOG.md.


📜 License

MIT. See LICENSE.

Project details


Download files

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

Source Distribution

django_unified_response-2.0.1.tar.gz (126.7 kB view details)

Uploaded Source

Built Distribution

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

django_unified_response-2.0.1-py3-none-any.whl (12.0 kB view details)

Uploaded Python 3

File details

Details for the file django_unified_response-2.0.1.tar.gz.

File metadata

  • Download URL: django_unified_response-2.0.1.tar.gz
  • Upload date:
  • Size: 126.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for django_unified_response-2.0.1.tar.gz
Algorithm Hash digest
SHA256 dce36210f9fb8ff3c429c3933c11cf856c49ac12c47ea312064581e5ab878285
MD5 b1fd8bafec8b0cc1e47650d8ab6e44a1
BLAKE2b-256 3faecced7491c76b081dae86bf161c97c69e8a667728c376b5287ef295a22d44

See more details on using hashes here.

File details

Details for the file django_unified_response-2.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for django_unified_response-2.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 96e32d26361303b17a39ea0b2fbb39c4bcb869bf359f8b852a86ed6bf4c834ac
MD5 88a13ef094201bb4c0023762d7c6894f
BLAKE2b-256 b15a4b0042b5c012cc6e9d9ec4a20afe1a480ddabb5de73a0f730afe8092181d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page