Skip to main content

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)

Source distribution for djhero 0.2.0
File Size Uploaded
djhero-0.2.0.tar.gz 16.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for djhero 0.2.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release 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