A Django library for standardized API responses.
Project description
Django Unified Response
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-spectacularintegration 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‑spectacularfor OpenAPI schema generation
🚀 Installation
pip install django-unified-response
With Swagger support
pip install "django-unified-response[swagger]"
🛠 Quick Start
- No need to add to
INSTALLED_APPS– the package has no models. - 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.
- Subclass
BaseFormatter(orDefaultFormatterto 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,
},
}
- 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
- Fork the repository
- Create a feature branch
- Make your changes and write tests
- Run
make lintandmake test - Update
CHANGELOG.md(Keep a Changelog format) - Open a pull request
📄 Changelog
All notable changes are documented in CHANGELOG.md.
📜 License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_unified_response-2.0.0.tar.gz.
File metadata
- Download URL: django_unified_response-2.0.0.tar.gz
- Upload date:
- Size: 125.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
92c9f61da4608369dc3127bd9cad98fac0e7216fb6d6d21092093d53ce988e0e
|
|
| MD5 |
050df0ba5594638da7f1af261032429f
|
|
| BLAKE2b-256 |
4b0a52c23f15da39684a5331acc26a8ec5fd6b870650e42b6c61e42e2f81eac0
|
File details
Details for the file django_unified_response-2.0.0-py3-none-any.whl.
File metadata
- Download URL: django_unified_response-2.0.0-py3-none-any.whl
- Upload date:
- Size: 12.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08ef13e0f80eb461c884563a5d3d5c8dc66c9b21ad73cb102b1905ceacc71ff1
|
|
| MD5 |
644d6ec2fb179784b10d5c22cd4a4c3e
|
|
| BLAKE2b-256 |
ef3c0ac1ea6ea7dbcaf0a0137745d78e94ee2e2f7a8938404f895a3be9ed4b6b
|