Reputation: 3848
I am using DRF for rest apis so now i am applying throttling to my apis. For that I created following throttle scopes
userRateThrottle
anonRateThrottle
burstRateThrottle
perViewsThrottles (varies with view)
currently i getting below response:
{"detail":"Request was throttled. Expected available in 32.0 seconds."}
I want response something like this:
{"message":"request limit exceeded","availableIn":"32.0 seconds","throttleType":"type"}
There is nothing in DRF docs for customisation. How can i customise my response according to requirement?
Upvotes: 13
Views: 10762
Reputation: 7386
You can change message of throttled response by overriding throttled
methods of your view. For example:
from rest_framework.exceptions import Throttled
class SomeView(APIView):
def throttled(self, request, wait):
raise Throttled(detail={
"message":"request limit exceeded",
"availableIn":f"{wait} seconds",
"throttleType":"type"
})
Upvotes: 21
Reputation: 135
I know this is an old thread, but adding to Rahul's answer, here's a way to include the throttleType in the message:
You will first need to override the Throttled exception class:
Create a file called rest_exceptions.py
, and create the following:
import math
import inspect
from django.utils.encoding import force_text
from django.utils.translation import ungettext
from rest_framework import exceptions, throttling
class CustomThrottled(exceptions.Throttled):
def __init__(self, wait=None, detail=None, throttle_instance=None):
if throttle_instance is None:
self.throttle_instance = None
else:
self.throttle_instance = throttle_instance
if detail is not None:
self.detail = force_text(detail)
else:
self.detail = force_text(self.default_detail)
if wait is None:
self.wait = None
else:
self.wait = math.ceil(wait)
Here you add a kwarg for the instance of the throttle that raises the exception (if provided). You can also override the behavior of the detail message, and do what you wish with the wait
value as well. I've decided to not concatenate detail and wait, but rather use the raw detail message.
Next, you'll want to create a custom viewset that passes the throttler to the throttled exception. Create a file called rest_viewsets.py
and create the following:
from rest_framework import viewsets
from .rest_exceptions import CustomThrottled
class ThrottledViewSet(viewsets.ViewSet):
"""
Adds customizability to the throtted method for better clarity.
"""
throttled_exception_class = CustomThrottled
def throttled(self, request, wait, throttle_instance=None):
"""
If request is throttled, determine what kind of exception to raise.
"""
raise self.get_throttled_exception_class()(wait, detail=self.get_throttled_message(request),
throttle_instance=throttle_instance)
def get_throttled_message(self, request):
"""
Add a custom throttled exception message to pass to the user.
Note that this does not account for the wait message, which will be added at the
end of this message.
"""
return None
def get_throttled_exception_class(self):
"""
Return the throttled exception class to use.
"""
return self.throttled_exception_class
def check_throttles(self, request):
"""
Check if request should be throttled.
Raises an appropriate exception if the request is throttled.
"""
for throttle in self.get_throttles():
if not throttle.allow_request(request, self):
self.throttled(request, throttle.wait(), throttle_instance=throttle)
Now that you have a custom exception that will store the throttle instance, and a viewset that will pass the instance to the exception, your next step is to implement a view that inherits this viewset, and also uses one of the throttle classes you had listed. In your views.py
, under the intended view (since you didn't provide that, I'm going to call it MyViewset
):
from .rest_viewsets import ThrottledViewSet
from rest_framework import throttling
class MyViewset(ThrottledViewSet):
throttle_classes = (throttling.userRateThrottle,) # Add more here as you wish
throttled_exception_class = CustomThrottled # This is the default already, but let's be specific anyway
def get_throttled_message(self, request):
"""Add a custom message to the throttled error."""
return "request limit exceeded"
At this point, your app will check for throttles like usual, but will also pass along the throttle instance as well. I have also overridden the throttle message to what you wanted. We can now tap into the solution that Rahul has provided, with a few modifications. Create a custom exception handler:
from rest_framework.views import exception_handler
from .rest_exceptions import CustomThrottled
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
if isinstance(exc, CustomThrottled): # check that a CustomThrottled exception is raised
custom_response_data = { # prepare custom response data
'message': exc.detail,
'availableIn': '%d seconds'%exc.wait,
'throttleType': type(exc.throttle_instance).__name__
}
response.data = custom_response_data # set the custom response data on response object
return response
You could easily access any other attribute of the throttle class at this point, but you only wanted the class name.
Last but not least, add your handler to the DRF settings:
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
Upvotes: 5
Reputation: 47906
To do that, you can implement a custom exception handler function that returns the custom response in case of a Throttled
exceptions.
from rest_framework.views import exception_handler
from rest_framework.exceptions import Throttled
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
if isinstance(exc, Throttled): # check that a Throttled exception is raised
custom_response_data = { # prepare custom response data
'message': 'request limit exceeded',
'availableIn': '%d seconds'%exc.wait
}
response.data = custom_response_data # set the custom response data on response object
return response
Then, you need to add this custom exception handler to your DRF settings.
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
I think it would be slightly difficult to know the throttleType
without changing some DRF code as DRF raises a Throttled
exception in case of any the Throttle classes throttling a request. No information is passed to the Throttled
exception about which throttle_class
is raising that exception.
Upvotes: 25