Skip to content

Commit 0513735

Browse files
committed
Add @throttle_scope function based view decorator
1 parent 385df20 commit 0513735

3 files changed

Lines changed: 39 additions & 2 deletions

File tree

docs/api-guide/views.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ The available decorators are:
182182
* `@parser_classes(...)`
183183
* `@authentication_classes(...)`
184184
* `@throttle_classes(...)`
185+
* `@throttle_scope(...)`
185186
* `@permission_classes(...)`
186187
* `@content_negotiation_class(...)`
187188
* `@metadata_class(...)`

rest_framework/decorators.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ def handler(self, *args, **kwargs):
6767
WrappedAPIView.throttle_classes = getattr(func, 'throttle_classes',
6868
APIView.throttle_classes)
6969

70+
WrappedAPIView.throttle_scope = getattr(func, 'throttle_scope',
71+
None)
72+
7073
WrappedAPIView.permission_classes = getattr(func, 'permission_classes',
7174
APIView.permission_classes)
7275

@@ -136,6 +139,14 @@ def decorator(func):
136139
return decorator
137140

138141

142+
def throttle_scope(throttle_scope):
143+
def decorator(func):
144+
_check_decorator_order(func, 'throttle_scope')
145+
func.throttle_scope = throttle_scope
146+
return func
147+
return decorator
148+
149+
139150
def permission_classes(permission_classes):
140151
def decorator(func):
141152
_check_decorator_order(func, 'permission_classes')

tests/test_decorators.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from rest_framework.decorators import (
99
action, api_view, authentication_classes, content_negotiation_class,
1010
metadata_class, parser_classes, permission_classes, renderer_classes,
11-
schema, throttle_classes, versioning_class
11+
schema, throttle_classes, throttle_scope, versioning_class
1212
)
1313
from rest_framework.negotiation import BaseContentNegotiation
1414
from rest_framework.parsers import JSONParser
@@ -17,7 +17,7 @@
1717
from rest_framework.response import Response
1818
from rest_framework.schemas import AutoSchema
1919
from rest_framework.test import APIRequestFactory
20-
from rest_framework.throttling import UserRateThrottle
20+
from rest_framework.throttling import ScopedRateThrottle, UserRateThrottle
2121
from rest_framework.versioning import QueryParameterVersioning
2222
from rest_framework.views import APIView
2323

@@ -153,6 +153,31 @@ def view(request):
153153
response = view(request)
154154
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS
155155

156+
def test_throttle_scope(self):
157+
scope = "x"
158+
159+
class OncePerDayScopedThrottle(ScopedRateThrottle):
160+
THROTTLE_RATES = {scope: "1/day"}
161+
162+
@api_view(['GET'])
163+
@throttle_classes([OncePerDayScopedThrottle])
164+
@throttle_scope(scope)
165+
def view_1(request):
166+
return Response({})
167+
168+
@api_view(['GET'])
169+
@throttle_classes([OncePerDayScopedThrottle])
170+
@throttle_scope(scope)
171+
def view_2(request):
172+
return Response({})
173+
174+
request = self.factory.get('/')
175+
response = view_1(request)
176+
assert response.status_code == status.HTTP_200_OK
177+
178+
response = view_2(request)
179+
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS
180+
156181
def test_versioning_class(self):
157182
@api_view(["GET"])
158183
@versioning_class(QueryParameterVersioning)

0 commit comments

Comments
 (0)