A Django middleware library that provides gradual request throttling with configurable delay strategies. Unlike traditional rate limiting that immediately blocks requests, this library applies progressive delays to slow down excessive requests gracefully.
- Gradual Throttling: Apply progressive delays instead of hard blocking
- Configurable Delay Strategies: Linear, exponential, or custom delay algorithms
- Flexible Key Functions: Throttle by IP address, user ID, or custom keys
- Django Cache Integration: Works with any Django-compatible cache backend
- Comprehensive Configuration: Extensive settings for fine-tuning behavior
- Monitoring & Debugging: Built-in hooks and dry-run mode
- Headers Support: Optional response headers for client awareness
- Path & User Exemptions: Skip throttling for specific paths or user types
pip install django-gradual-throttle
- Add the middleware to your Django settings:
MIDDLEWARE = [
# ... other middleware
'django_gradual_throttle.middleware.GradualThrottleMiddleware',
# ... rest of middleware
]
- Basic configuration in
settings.py
:
# Allow 60 requests per minute
DJANGO_GRADUAL_THROTTLE_RATE = 60
DJANGO_GRADUAL_THROTTLE_WINDOW = 60 # seconds
# Apply 0.2s delay per excess request, max 5s
DJANGO_GRADUAL_THROTTLE_BASE_DELAY = 0.2
DJANGO_GRADUAL_THROTTLE_MAX_DELAY = 5.0
- That's it! The middleware will now apply gradual throttling to your Django application.
All settings are optional and have sensible defaults:
# Enable/disable throttling (default: True)
DJANGO_GRADUAL_THROTTLE_ENABLED = True
# Requests allowed per time window (default: 60)
DJANGO_GRADUAL_THROTTLE_RATE = 60
# Time window in seconds (default: 60)
DJANGO_GRADUAL_THROTTLE_WINDOW = 60
# Base delay per excess request in seconds (default: 0.2)
DJANGO_GRADUAL_THROTTLE_BASE_DELAY = 0.2
# Maximum delay in seconds (default: 5.0)
DJANGO_GRADUAL_THROTTLE_MAX_DELAY = 5.0
# Cache backend to use (default: 'default')
DJANGO_GRADUAL_THROTTLE_CACHE_ALIAS = 'throttle_cache'
# Key function for identifying requests (default: IP/User based)
DJANGO_GRADUAL_THROTTLE_KEY_FUNC = 'myapp.utils.custom_key_func'
# Delay strategy class (default: linear)
DJANGO_GRADUAL_THROTTLE_DELAY_STRATEGY = 'myapp.strategies.CustomDelayStrategy'
# Paths to exempt from throttling
DJANGO_GRADUAL_THROTTLE_EXEMPT_PATHS = ['/admin/', '/health/']
# User attributes that exempt from throttling
DJANGO_GRADUAL_THROTTLE_EXEMPT_USERS = ['is_staff', 'is_superuser']
# Hook function for monitoring/logging
DJANGO_GRADUAL_THROTTLE_HOOK = 'myapp.monitoring.throttle_hook'
# Add throttling headers to responses (default: True)
DJANGO_GRADUAL_THROTTLE_HEADERS_ENABLED = True
# Hard limit - return 429 after this many requests (default: 0 = disabled)
DJANGO_GRADUAL_THROTTLE_HARD_LIMIT = 200
# Dry run mode - log delays instead of applying them (default: False)
DJANGO_GRADUAL_THROTTLE_DRY_RUN = False
DJANGO_GRADUAL_THROTTLE_DELAY_STRATEGY = 'django_gradual_throttle.strategies.linear.LinearDelayStrategy'
Delay increases linearly: delay = base_delay * excess_requests
DJANGO_GRADUAL_THROTTLE_DELAY_STRATEGY = 'django_gradual_throttle.strategies.exponential.ExponentialDelayStrategy'
Delay increases exponentially: delay = base_delay * (multiplier ^ (excess_requests - 1))
Create your own delay strategy by extending BaseDelayStrategy
:
# myapp/strategies.py
from django_gradual_throttle.strategies.base import BaseDelayStrategy
class CustomDelayStrategy(BaseDelayStrategy):
def calculate_delay(self, excess_requests: int) -> float:
if excess_requests <= 0:
return 0.0
# Custom logic here
delay = self.base_delay * (excess_requests ** 1.5)
return self._clamp_delay(delay)
By default, requests are keyed by IP address for anonymous users and user ID for authenticated users. You can customize this:
# myapp/utils.py
def custom_key_func(request):
"""Custom key function example."""
if hasattr(request, 'tenant'):
return f"throttle:tenant:{request.tenant.id}"
return f"throttle:ip:{get_client_ip(request)}"
# settings.py
DJANGO_GRADUAL_THROTTLE_KEY_FUNC = 'myapp.utils.custom_key_func'
Set up monitoring by providing a hook function:
# myapp/monitoring.py
import logging
logger = logging.getLogger(__name__)
def throttle_hook(request, action, **kwargs):
"""Hook function for monitoring throttling events."""
if action == 'throttled':
logger.warning(
f"Request throttled: {kwargs['current_count']} requests, "
f"{kwargs['delay']:.2f}s delay"
)
elif action == 'hard_limit_exceeded':
logger.error(f"Hard limit exceeded: {kwargs['current_count']} requests")
# settings.py
DJANGO_GRADUAL_THROTTLE_HOOK = 'myapp.monitoring.throttle_hook'
When DJANGO_GRADUAL_THROTTLE_HEADERS_ENABLED
is True
, the following headers are added:
X-Throttle-Remaining
: Requests remaining in current windowX-Throttle-Limit
: Request limit per windowX-Throttle-Window
: Time window in secondsX-Throttle-Delay
: Applied delay in seconds (if any)X-Throttle-Excess
: Number of excess requests (if any)
The library works with any Django cache backend:
# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
},
'throttle': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
DJANGO_GRADUAL_THROTTLE_CACHE_ALIAS = 'throttle'
The library includes comprehensive tests. To run them:
# Install development dependencies
pip install -e .[dev]
# Run tests
python -m pytest
# Run with coverage
python -m pytest --cov=django_gradual_throttle --cov-report=html
For testing and development, use dry run mode to log delays without applying them:
DJANGO_GRADUAL_THROTTLE_DRY_RUN = True
# 100 requests per 5 minutes, 0.1s delay per excess request
DJANGO_GRADUAL_THROTTLE_RATE = 100
DJANGO_GRADUAL_THROTTLE_WINDOW = 300
DJANGO_GRADUAL_THROTTLE_BASE_DELAY = 0.1
DJANGO_GRADUAL_THROTTLE_MAX_DELAY = 10.0
# 1000 requests per hour, exponential delays, hard limit at 2000
DJANGO_GRADUAL_THROTTLE_RATE = 1000
DJANGO_GRADUAL_THROTTLE_WINDOW = 3600
DJANGO_GRADUAL_THROTTLE_DELAY_STRATEGY = 'django_gradual_throttle.strategies.exponential.ExponentialDelayStrategy'
DJANGO_GRADUAL_THROTTLE_HARD_LIMIT = 2000
# Exempt admin and health checks, enable dry run
DJANGO_GRADUAL_THROTTLE_EXEMPT_PATHS = ['/admin/', '/health/', '/metrics/']
DJANGO_GRADUAL_THROTTLE_EXEMPT_USERS = ['is_staff']
DJANGO_GRADUAL_THROTTLE_DRY_RUN = True # For development
- Cache Performance: Use Redis or Memcached for production deployments
- Key Distribution: Ensure your key function distributes load evenly
- Window Size: Larger windows use more memory but provide smoother throttling
- Delay Granularity: Very small delays (< 0.01s) may not be effective
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the MIT License - see the LICENSE file for details.
- Initial release
- Linear and exponential delay strategies
- Comprehensive configuration options
- Django cache integration
- Monitoring hooks and dry run mode
- Full test coverage