djhero
Powerful decorators & utilities for Django developers. One package, zero hassle.
Stop installing 5+ different packages for common Django patterns. djhero gives you 24 decorators, 5 utilities, and 5 signals — all production-ready.
Install
pip install djhero
Quick Start
from djhero import (
rate_limit, cache_view, log_request, auth_required,
json_response, validate_json, handle_exceptions, cors,
)
@rate_limit("100/h")
@cache_view(ttl=300)
@log_request
@cors()
@handle_exceptions
def product_list(request):
products = Product.objects.all()
return JsonResponse({"products": list(products.values())})
@auth_required()
@validate_json("name", "email")
@json_response
def create_user(request):
data = request.json_data
user = User.objects.create(name=data["name"], email=data["email"])
return {"id": user.id, "created": True}, 201
Decorators
Caching
@cache_view(ttl=300, key_prefix="djhero")
Cache view responses automatically.
@cache_view(ttl=60)
def my_view(request):
...
@cache_per_user(ttl=300)
Cache responses per authenticated user.
@cache_per_user(ttl=120)
def user_dashboard(request):
...
Rate Limiting
@rate_limit(limit, key_func=None, message=None)
Limit requests per time window. Adds X-RateLimit-Limit and X-RateLimit-Remaining headers.
@rate_limit("100/h") # 100 per hour
@rate_limit("10/m") # 10 per minute
@rate_limit("1000/d") # 1000 per day
@rate_limit("5/s") # 5 per second
# Custom key:
@rate_limit("100/h", key_func=lambda r: r.user.id)
@throttle(rate, key_func=None, message=None)
Enforce minimum interval between requests.
@throttle("1/s")
@throttle("5/m")
Auth & Permissions
@auth_required(login_url=None, message=None)
Require authentication. Returns JSON 401 for AJAX, redirects for browser.
@auth_required()
@auth_required(login_url="/login/", message="Please log in")
@permission_required(*perms, message=None)
@permission_required("app.can_edit", "app.can_delete")
@staff_required
@staff_required
def admin_panel(request):
...
@superuser_required
@superuser_required
def danger_zone(request):
...
Request Handling
@json_response
Automatically convert dict/list returns to JsonResponse.
@json_response
def my_view(request):
return {"message": "hello"} # -> JsonResponse 200
return {"created": True}, 201 # -> JsonResponse 201
return [1, 2, 3] # -> JsonResponse(safe=False)
@validate_json(*required_fields)
Validate JSON body and attach parsed data to request.json_data.
@validate_json("name", "email")
def create(request):
data = request.json_data # already parsed & validated
@require_fields(*fields, source="POST")
@require_fields("username", "password", source="POST")
@require_fields("q", source="GET")
@handle_exceptions
Catch exceptions and return JSON error response.
@handle_exceptions
@handle_exceptions(log_errors=True, default_status=500)
@timeout(seconds=30, message=None)
@timeout(seconds=5)
def slow_view(request):
...
@log_request / @log_request(level, log_body)
Log method, path, status, duration, IP. Adds X-Response-Time header.
@log_request
@log_request(level=logging.DEBUG, log_body=True)
@ajax_required
@ajax_required
def api_endpoint(request):
...
@method_required(*methods)
@method_required("GET", "POST")
@disable_csrf
@disable_csrf
def webhook(request):
...
HTTP Features
@cors(origins, methods, headers, max_age)
@cors() # Allow all
@cors(origins="https://example.com") # Specific origin
@cors(origins=["https://a.com", "https://b.com"]) # Multiple origins
@paginate(per_page=20, max_per_page=100)
Adds request.page, request.per_page, request.offset.
@paginate(per_page=25)
def list_view(request):
items = Item.objects.all()[request.offset:request.offset + request.per_page]
@etag(etag_func)
ETag support for conditional GET requests.
@etag(lambda req: hashlib.md5(str(req.GET).encode()).hexdigest())
def my_view(request):
...
@deprecated(message, sunset_date)
Adds Deprecation and Sunset headers.
@deprecated("Use /api/v2/users instead", sunset_date="2025-12-31")
@retry_on_error(max_retries=3, delay=0.5, exceptions=(Exception,))
@retry_on_error(max_retries=3, exceptions=(ConnectionError,))
def external_api(request):
...
@query_debugger
Log and count DB queries. Adds X-Query-Count and X-Query-Time headers.
@query_debugger
@query_debugger(warn_threshold=5)
Utilities
from djhero import success_response, error_response, paginated_response, get_json_body, get_client_ip
# Standardized responses
return success_response({"user": user_data}, message="Created", status=201)
return error_response("Not found", status=404)
return error_response("Validation failed", errors={"email": "Invalid"}, status=422)
# Paginated queryset response
return paginated_response(User.objects.all(), page=1, per_page=10)
# Parse JSON body safely
data = get_json_body(request, default={})
# Get client IP
ip = get_client_ip(request)
Signals
Listen to djhero events:
from djhero import rate_limit_exceeded, auth_failed, view_timeout, view_exception, deprecated_view_accessed
@receiver(rate_limit_exceeded)
def on_rate_limit(sender, request, limit, ip, **kwargs):
notify_admin(f"Rate limit hit by {ip}")
@receiver(view_exception)
def on_error(sender, request, exception, **kwargs):
send_to_sentry(exception)
@receiver(deprecated_view_accessed)
def on_deprecated(sender, request, message, **kwargs):
log_deprecated_usage(request.path)
Composing Decorators
Stack them freely:
@rate_limit("50/h")
@auth_required()
@cache_view(ttl=120)
@cors()
@log_request
@handle_exceptions
@timeout(seconds=10)
@json_response
def api_view(request):
return {"data": "fast & safe"}
Requirements
- Python 3.8+
- Django 4.0+
License
MIT
Release files for djhero 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| djhero-0.2.0.tar.gz | 16.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| djhero-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:28.4 kB
Release files / djhero-0.2.0.tar.gz
| Download URL | djhero-0.2.0.tar.gz |
|---|---|
| Size | 16.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
82fb0d2c3f81243b1a526c1410fb6ebc4800179a15dc535c9c224054b0c8a5e7
|
|
BLAKE2b-256 checksum How to use checksums |
0bc831796cc690e37d52c3b5fa2b5d7e40b36e0edaf4adeb8e2d04d6e4183c10
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.12
|
Release files / djhero-0.2.0-py3-none-any.whl
| Download URL | djhero-0.2.0-py3-none-any.whl |
|---|---|
| Size | 12.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e5f304f7e4228ab12e00a7008cf94d89e9dc9a28ec010d42a8b2e71a1cd0dfd8
|
|
BLAKE2b-256 checksum How to use checksums |
707765750b971bd465ac588014df6fefd3190eb19f229c95df2d3ae3006cbebd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.12
|