From 68dbb4890c121e60a74df828b85cc915ea1e3fb1 Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Sun, 20 Aug 2023 23:21:03 +0530 Subject: [PATCH 01/15] 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. --- rest_framework/throttling.py | 10 ++++++--- .../utils/throttling_duration_parser.py | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 rest_framework/utils/throttling_duration_parser.py diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index c0d6cf42f..90c8cd918 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -5,6 +5,7 @@ import time from django.core.cache import cache as default_cache from django.core.exceptions import ImproperlyConfigured +from utils.throttling_duration_parser import parse_quantity_and_unit from rest_framework.settings import api_settings @@ -94,7 +95,7 @@ class SimpleRateThrottle(BaseThrottle): msg = "No default throttle rate set for '%s' scope" % self.scope raise ImproperlyConfigured(msg) - def parse_rate(self, rate): + def parse_rate(self,rate): """ Given the request rate string, return a two tuple of: , @@ -102,9 +103,12 @@ class SimpleRateThrottle(BaseThrottle): if rate is None: return (None, None) num, period = rate.split('/') + quantity, unit = parse_quantity_and_unit(period).values() 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/rest_framework/utils/throttling_duration_parser.py b/rest_framework/utils/throttling_duration_parser.py new file mode 100644 index 000000000..859fd432f --- /dev/null +++ b/rest_framework/utils/throttling_duration_parser.py @@ -0,0 +1,22 @@ +def parse_quantity_and_unit(quantity_unit_string): + """ + Parse a combined quantity and unit string and return a dictionary containing the parsed values. + + Args: + quantity_unit_string (str): A string that combines a numeric quantity and a unit, e.g., "5min", "10h". + + Returns: + dict: A dictionary containing the parsed quantity and unit, with keys 'quantity' and 'unit'. + If the input string contains only a unit (e.g., "ms"), quantity will be set to 1. + """ + i = 0 + quantity_unit_dict = {} + while i < len(quantity_unit_string) and quantity_unit_string[i].isnumeric(): + i += 1 + if i == 0: + quantity_unit_dict['quantity'] = 1 + quantity_unit_dict['unit'] = quantity_unit_string + else: + quantity_unit_dict['quantity'] = int(quantity_unit_string[:i]) + quantity_unit_dict['unit'] = quantity_unit_string[i:] + return quantity_unit_dict \ No newline at end of file From 35a63d86670b7ccb86d8b87e382f966b5d109cb1 Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Sun, 20 Aug 2023 23:40:05 +0530 Subject: [PATCH 02/15] 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. --- rest_framework/throttling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 90c8cd918..3483b1713 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -102,10 +102,10 @@ class SimpleRateThrottle(BaseThrottle): """ if rate is None: return (None, None) + num, period = rate.split('/') quantity, unit = parse_quantity_and_unit(period).values() num_requests = int(num) - duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[unit[0]] total_duration = duration * int(quantity) return (num_requests, total_duration) From 71289fff0d3d26e1c5f2115f4a10e8405e4899ec Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Sun, 20 Aug 2023 23:57:51 +0530 Subject: [PATCH 03/15] adding tests --- tests/test_throttling.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_throttling.py b/tests/test_throttling.py index d5a61232d..6d9fc6e92 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -18,6 +18,7 @@ from rest_framework.throttling import ( UserRateThrottle ) from rest_framework.views import APIView +from utils.throttling_duration_parser import parse_quantity_and_unit class User3SecRateThrottle(UserRateThrottle): @@ -465,6 +466,16 @@ class SimpleRateThrottleTests(TestCase): 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 = parse_quantity_and_unit("5min") + assert result == {'quantity': 5, 'unit': 'min'} + + result = parse_quantity_and_unit("h") + assert result == {'quantity': 1, 'unit': 'h'} + + result = parse_quantity_and_unit("123s") + assert result == {'quantity': 123, 'unit': 's'} def test_allow_request_returns_true_if_rate_is_none(self): assert SimpleRateThrottle().allow_request(request={}, view={}) is True From decd4b83e129309e67f17e0fb2e4ae050ef4d339 Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Mon, 21 Aug 2023 00:03:30 +0530 Subject: [PATCH 04/15] adding tests --- tests/test_throttling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_throttling.py b/tests/test_throttling.py index 6d9fc6e92..0962269a8 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -18,7 +18,7 @@ from rest_framework.throttling import ( UserRateThrottle ) from rest_framework.views import APIView -from utils.throttling_duration_parser import parse_quantity_and_unit +from rest_framework.utils.throttling_duration_parser import parse_quantity_and_unit class User3SecRateThrottle(UserRateThrottle): From c10c92c59b1d6861d054cf9bf8b218da26762b58 Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Mon, 21 Aug 2023 00:21:23 +0530 Subject: [PATCH 05/15] fixing the whitespaces --- rest_framework/throttling.py | 3 +-- rest_framework/utils/throttling_duration_parser.py | 2 +- tests/test_throttling.py | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 3483b1713..a438bedd4 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -95,14 +95,13 @@ class SimpleRateThrottle(BaseThrottle): msg = "No default throttle rate set for '%s' scope" % self.scope raise ImproperlyConfigured(msg) - def parse_rate(self,rate): + def parse_rate(self, rate): """ Given the request rate string, return a two tuple of: , """ if rate is None: return (None, None) - num, period = rate.split('/') quantity, unit = parse_quantity_and_unit(period).values() num_requests = int(num) diff --git a/rest_framework/utils/throttling_duration_parser.py b/rest_framework/utils/throttling_duration_parser.py index 859fd432f..923c4dc61 100644 --- a/rest_framework/utils/throttling_duration_parser.py +++ b/rest_framework/utils/throttling_duration_parser.py @@ -19,4 +19,4 @@ def parse_quantity_and_unit(quantity_unit_string): else: quantity_unit_dict['quantity'] = int(quantity_unit_string[:i]) quantity_unit_dict['unit'] = quantity_unit_string[i:] - return quantity_unit_dict \ No newline at end of file + return quantity_unit_dict diff --git a/tests/test_throttling.py b/tests/test_throttling.py index 0962269a8..0563bd9d8 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -470,10 +470,8 @@ class SimpleRateThrottleTests(TestCase): def test_parse_quantity_and_unit_parses_correctly(self): result = parse_quantity_and_unit("5min") assert result == {'quantity': 5, 'unit': 'min'} - result = parse_quantity_and_unit("h") assert result == {'quantity': 1, 'unit': 'h'} - result = parse_quantity_and_unit("123s") assert result == {'quantity': 123, 'unit': 's'} From 9becb0ed5308cdf6efcbdcf998c68a0711a88894 Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Mon, 21 Aug 2023 00:27:16 +0530 Subject: [PATCH 06/15] fixing the whitespaces --- tests/test_throttling.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_throttling.py b/tests/test_throttling.py index 0563bd9d8..030e40acb 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -18,7 +18,9 @@ from rest_framework.throttling import ( UserRateThrottle ) from rest_framework.views import APIView -from rest_framework.utils.throttling_duration_parser import parse_quantity_and_unit +from rest_framework.utils.throttling_duration_parser import ( + parse_quantity_and_unit +) class User3SecRateThrottle(UserRateThrottle): From e4a8a1e08b002bb55f34a9d28f378da2fe70d838 Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Mon, 21 Aug 2023 00:29:02 +0530 Subject: [PATCH 07/15] fixing the api view --- tests/test_throttling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_throttling.py b/tests/test_throttling.py index 030e40acb..315ffc575 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -17,10 +17,10 @@ from rest_framework.throttling import ( AnonRateThrottle, BaseThrottle, ScopedRateThrottle, SimpleRateThrottle, UserRateThrottle ) -from rest_framework.views import APIView from rest_framework.utils.throttling_duration_parser import ( parse_quantity_and_unit ) +from rest_framework.views import APIView class User3SecRateThrottle(UserRateThrottle): From 5d684b7e0677d80c244323caf64603ef40009b40 Mon Sep 17 00:00:00 2001 From: PravinKamble123 <91125540+PravinKamble123@users.noreply.github.com> Date: Mon, 21 Aug 2023 21:01:05 +0530 Subject: [PATCH 08/15] Update rest_framework/throttling.py Co-authored-by: Devid <13779643+sevdog@users.noreply.github.com> --- rest_framework/throttling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index a438bedd4..08a536ee1 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -5,7 +5,7 @@ import time from django.core.cache import cache as default_cache from django.core.exceptions import ImproperlyConfigured -from utils.throttling_duration_parser import parse_quantity_and_unit +from rest_framework.utils.throttling_duration_parser import parse_quantity_and_unit from rest_framework.settings import api_settings From f62927c1dfee4af4a502e3f43c7610a74e324cba Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Mon, 21 Aug 2023 21:41:01 +0530 Subject: [PATCH 09/15] update the throtting_duration_parser.py returning tuple instead of dict --- rest_framework/throttling.py | 2 +- rest_framework/utils/throttling_duration_parser.py | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index a438bedd4..9acc2371d 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -103,7 +103,7 @@ class SimpleRateThrottle(BaseThrottle): if rate is None: return (None, None) num, period = rate.split('/') - quantity, unit = parse_quantity_and_unit(period).values() + quantity, unit = parse_quantity_and_unit(period) num_requests = int(num) duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[unit[0]] total_duration = duration * int(quantity) diff --git a/rest_framework/utils/throttling_duration_parser.py b/rest_framework/utils/throttling_duration_parser.py index 923c4dc61..b8d2b30be 100644 --- a/rest_framework/utils/throttling_duration_parser.py +++ b/rest_framework/utils/throttling_duration_parser.py @@ -7,16 +7,15 @@ def parse_quantity_and_unit(quantity_unit_string): Returns: dict: A dictionary containing the parsed quantity and unit, with keys 'quantity' and 'unit'. - If the input string contains only a unit (e.g., "ms"), quantity will be set to 1. + If the input string contains only a unit (e.g., "m"), quantity will be set to 1. """ i = 0 - quantity_unit_dict = {} while i < len(quantity_unit_string) and quantity_unit_string[i].isnumeric(): i += 1 + if i == 0: - quantity_unit_dict['quantity'] = 1 - quantity_unit_dict['unit'] = quantity_unit_string + return (1, quantity_unit_string) else: - quantity_unit_dict['quantity'] = int(quantity_unit_string[:i]) - quantity_unit_dict['unit'] = quantity_unit_string[i:] - return quantity_unit_dict + quantity = int(quantity_unit_string[:i]) + unit = quantity_unit_string[i:] + return (quantity, unit) From fea49b77d507089cbde89b06167b13adc6b99677 Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Mon, 21 Aug 2023 21:51:20 +0530 Subject: [PATCH 10/15] update the tests --- tests/test_throttling.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_throttling.py b/tests/test_throttling.py index 315ffc575..2cfdecd4f 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -471,11 +471,11 @@ class SimpleRateThrottleTests(TestCase): def test_parse_quantity_and_unit_parses_correctly(self): result = parse_quantity_and_unit("5min") - assert result == {'quantity': 5, 'unit': 'min'} + assert result == (5, 'min') result = parse_quantity_and_unit("h") - assert result == {'quantity': 1, 'unit': 'h'} + assert result == (1, 'h') result = parse_quantity_and_unit("123s") - assert result == {'quantity': 123, 'unit': 's'} + assert result == (123, 's') def test_allow_request_returns_true_if_rate_is_none(self): assert SimpleRateThrottle().allow_request(request={}, view={}) is True From 436f61ca21ede4cd0842fff5014fddf9350bc466 Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Mon, 21 Aug 2023 21:55:48 +0530 Subject: [PATCH 11/15] fix the white spacing --- rest_framework/throttling.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 3587d13dc..92bb9cd1c 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -5,9 +5,10 @@ import time from django.core.cache import cache as default_cache from django.core.exceptions import ImproperlyConfigured -from rest_framework.utils.throttling_duration_parser import parse_quantity_and_unit - from rest_framework.settings import api_settings +from rest_framework.utils.throttling_duration_parser import ( + parse_quantity_and_unit +) class BaseThrottle: From 6844bc2b1fe0991823f116db9b50fc51ec6444ae Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Mon, 21 Aug 2023 22:20:41 +0530 Subject: [PATCH 12/15] fix the white spacing --- rest_framework/throttling.py | 1 + tests/test_throttling.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 92bb9cd1c..5f0272c10 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -5,6 +5,7 @@ import time from django.core.cache import cache as default_cache from django.core.exceptions import ImproperlyConfigured + from rest_framework.settings import api_settings from rest_framework.utils.throttling_duration_parser import ( parse_quantity_and_unit diff --git a/tests/test_throttling.py b/tests/test_throttling.py index 2cfdecd4f..dbbaf9453 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -468,7 +468,7 @@ class SimpleRateThrottleTests(TestCase): 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 = parse_quantity_and_unit("5min") assert result == (5, 'min') From 8a77717188c168beff77932b17dcbd2aebd52eaf Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Tue, 22 Aug 2023 22:28:59 +0530 Subject: [PATCH 13/15] update the docs with examples --- docs/api-guide/throttling.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) 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 From 4d54da3176897aa7f10b8fbd48577ca9f725d14d Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Tue, 22 Aug 2023 23:47:00 +0530 Subject: [PATCH 14/15] updating the docs and removing the extra file from package --- rest_framework/throttling.py | 23 +++++++++++++++---- .../utils/throttling_duration_parser.py | 21 ----------------- tests/test_throttling.py | 5 +--- 3 files changed, 20 insertions(+), 29 deletions(-) delete mode 100644 rest_framework/utils/throttling_duration_parser.py diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 5f0272c10..3af69c4ad 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -7,9 +7,6 @@ from django.core.cache import cache as default_cache from django.core.exceptions import ImproperlyConfigured from rest_framework.settings import api_settings -from rest_framework.utils.throttling_duration_parser import ( - parse_quantity_and_unit -) class BaseThrottle: @@ -97,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: @@ -105,7 +120,7 @@ class SimpleRateThrottle(BaseThrottle): if rate is None: return (None, None) num, period = rate.split('/') - quantity, unit = parse_quantity_and_unit(period) + quantity, unit = self.parse_quantity_and_unit(period) num_requests = int(num) duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[unit[0]] total_duration = duration * int(quantity) diff --git a/rest_framework/utils/throttling_duration_parser.py b/rest_framework/utils/throttling_duration_parser.py deleted file mode 100644 index b8d2b30be..000000000 --- a/rest_framework/utils/throttling_duration_parser.py +++ /dev/null @@ -1,21 +0,0 @@ -def parse_quantity_and_unit(quantity_unit_string): - """ - Parse a combined quantity and unit string and return a dictionary containing the parsed values. - - Args: - quantity_unit_string (str): A string that combines a numeric quantity and a unit, e.g., "5min", "10h". - - Returns: - dict: A dictionary containing the parsed quantity and unit, with keys 'quantity' and 'unit'. - If the input string contains only a unit (e.g., "m"), quantity will be set to 1. - """ - 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) diff --git a/tests/test_throttling.py b/tests/test_throttling.py index dbbaf9453..d0aa0584c 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -15,10 +15,7 @@ from rest_framework.settings import api_settings from rest_framework.test import APIRequestFactory, force_authenticate from rest_framework.throttling import ( AnonRateThrottle, BaseThrottle, ScopedRateThrottle, SimpleRateThrottle, - UserRateThrottle -) -from rest_framework.utils.throttling_duration_parser import ( - parse_quantity_and_unit + UserRateThrottle, parse_quantity_and_unit ) from rest_framework.views import APIView From a5aa48b6c43b4567e0bbe23372bda8c75ed83e69 Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Tue, 22 Aug 2023 23:57:48 +0530 Subject: [PATCH 15/15] updating the docs and removing the extra file from package --- tests/test_throttling.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_throttling.py b/tests/test_throttling.py index d0aa0584c..b9b877827 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -15,7 +15,7 @@ from rest_framework.settings import api_settings from rest_framework.test import APIRequestFactory, force_authenticate from rest_framework.throttling import ( AnonRateThrottle, BaseThrottle, ScopedRateThrottle, SimpleRateThrottle, - UserRateThrottle, parse_quantity_and_unit + UserRateThrottle ) from rest_framework.views import APIView @@ -467,11 +467,11 @@ class SimpleRateThrottleTests(TestCase): assert rate == (None, None) def test_parse_quantity_and_unit_parses_correctly(self): - result = parse_quantity_and_unit("5min") + result = SimpleRateThrottle().parse_quantity_and_unit("5min") assert result == (5, 'min') - result = parse_quantity_and_unit("h") + result = SimpleRateThrottle().parse_quantity_and_unit("h") assert result == (1, 'h') - result = parse_quantity_and_unit("123s") + result = SimpleRateThrottle().parse_quantity_and_unit("123s") assert result == (123, 's') def test_allow_request_returns_true_if_rate_is_none(self):