From f23c37e76a9716835ce68c0b8b01bfe42d9a1c5e Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Sun, 20 Aug 2023 23:21:03 +0530 Subject: [PATCH] feat: Add support for flexible throttling intervals This commit introduces the ability to set custom time intervals for throttling, allowing users to specify intervals like per 5 minutes, per 2 hours, and per 5 days. This enhancement provides more flexibility in controlling request rates. --- docs/api-guide/throttling.md | 35 +++++++++++++++++++++++++++++++++++ rest_framework/throttling.py | 24 ++++++++++++++++++++++-- tests/test_throttling.py | 8 ++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index 4c58fa713..1fc314b29 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -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 diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index c0d6cf42f..3af69c4ad 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -94,6 +94,24 @@ class SimpleRateThrottle(BaseThrottle): 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: @@ -102,9 +120,11 @@ class SimpleRateThrottle(BaseThrottle): 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): """ diff --git a/tests/test_throttling.py b/tests/test_throttling.py index d5a61232d..b9b877827 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -466,6 +466,14 @@ class SimpleRateThrottleTests(TestCase): 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