Custom throttling response in django rest framework
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.
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"
})
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 itMyViewset
):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' }