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
Metadata
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 | Provenance | Actions |
|---|---|---|---|---|
| djhero-0.2.0.tar.gz | 16.4 kB | Without provenance | Details for djhero-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| djhero-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for djhero-0.2.0-py3-none-any.whl |
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
|
Security for djhero 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.