Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for flexible throttling intervals #9079

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/api-guide/throttling.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,41 @@ For example, given the following views...
User requests to either `ContactListView` or `ContactDetailView` would be restricted to a total of 1000 requests per-day. User requests to `UploadView` would be restricted to 20 requests per day.

---
## Customizing Throttling Time

You can now set custom and flexible time periods for the throttling classes. This enhancement allows you to specify a time unit for throttling, giving you more control over how the rate limits are applied.

To set custom and flexible time periods for throttling, you can use the throttle_duration parameter. The throttle_duration parameter accepts a string that combines a numeric quantity and a time unit, similar to the style used in other Django settings. For example, you can set a throttling duration of "10s" for 10 seconds or "5m" for 5 minutes.

## Examples

1. Custom Time Period for User Throttling

To limit the rate of requests for authenticated users to 5 requests every 15 minutes, you can use the throttle_duration parameter as follows:

REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'user': '5/15m', # Allow 5 requests every 15 minutes for each user
}
}

2. Scoped Throttling with Custom Time

You can also apply custom throttling rates to specific views using the ScopedRateThrottle class. For example, to limit requests to the "write" scope to 3 requests every 30 seconds:


REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.ScopedRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'user': '10/minute', # Default rate for all users
'write': '3/30s', # Allow 3 requests every 30 seconds for "write" scope
'custom_scope': '20/1h', # Allow 20 requests every 1 hour for a custom scope
}
}

With these enhancements, you can have more granular control over how you limit the rate of incoming requests to your API views in Django REST framework.

# Custom throttles

Expand Down
25 changes: 22 additions & 3 deletions rest_framework/throttling.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from django.core.cache import cache as default_cache
from django.core.exceptions import ImproperlyConfigured

from rest_framework.settings import api_settings


Expand Down Expand Up @@ -94,6 +93,24 @@ def get_rate(self):
msg = "No default throttle rate set for '%s' scope" % self.scope
raise ImproperlyConfigured(msg)

def parse_quantity_and_unit(self, quantity_unit_string):
"""
Parse a combined quantity and unit string and return a tuple with parsed values.

Returns:
tuple: A tuple containing the parsed values (quantity, unit).
"""
i = 0
while i < len(quantity_unit_string) and quantity_unit_string[i].isnumeric():
i += 1

if i == 0:
return (1, quantity_unit_string)
else:
quantity = int(quantity_unit_string[:i])
unit = quantity_unit_string[i:]
return (quantity, unit)

def parse_rate(self, rate):
"""
Given the request rate string, return a two tuple of:
Expand All @@ -102,9 +119,11 @@ def parse_rate(self, rate):
if rate is None:
return (None, None)
num, period = rate.split('/')
quantity, unit = self.parse_quantity_and_unit(period)
num_requests = int(num)
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
return (num_requests, duration)
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[unit[0]]
total_duration = duration * int(quantity)
return (num_requests, total_duration)

def allow_request(self, request, view):
"""
Expand Down
8 changes: 8 additions & 0 deletions tests/test_throttling.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,14 @@ def test_parse_rate_returns_tuple_with_none_if_rate_not_provided(self):
rate = SimpleRateThrottle().parse_rate(None)
assert rate == (None, None)

def test_parse_quantity_and_unit_parses_correctly(self):
result = SimpleRateThrottle().parse_quantity_and_unit("5min")
assert result == (5, 'min')
result = SimpleRateThrottle().parse_quantity_and_unit("h")
assert result == (1, 'h')
result = SimpleRateThrottle().parse_quantity_and_unit("123s")
assert result == (123, 's')

def test_allow_request_returns_true_if_rate_is_none(self):
assert SimpleRateThrottle().allow_request(request={}, view={}) is True

Expand Down
Loading