DRF throttle (APIView)

hi
is there any way that I throttle a request base on specific status code?
as you see in code, i throttle all request no matter what is the status code.
but i want to the throttle incoming request if all the latest request ends with 404 status code not any other status code

class CheckUserPhone(APIView):
    throttle_scope = "check_phone"
    throttle_classes = (ScopedRateThrottle,)
    serializer_class = CheckUserPhoneSerializer

    def post(self, request):
        serializer = self.serializer_class(data=request.data)
        serializer.is_valid(raise_exception=True)
        phone = get_phone_from_serializer(serializer)

        user = User.objects.filter(phone=phone)
        if user.exist():
            return Response({"message": LoginSituation.LOGIN_REQUIRED}, status=status.HTTP_200_OK)
        sms_provider_result, code = generate_otp_and_send(phone)
        if sms_provider_result:
            save_otp_inside_cache(phone, code)
            return Response(
                {"message": LoginSituation.REGISTER_REQUIRED}, status=status.HTTP_404_NOT_FOUND
            )
        return Response({"message": FaultCode.SMS_PROVIDER_FAILURE}, status=status.HTTP_400_BAD_REQUEST)

You can create a custom throttle class. I adapted a sample code from my old projects to your code as follows. You can review it.

Custom Throttle Class

from rest_framework.throttling import BaseThrottle
from django.core.cache import cache
from django.utils import timezone
from datetime import timedelta

class StatusCodeThrottle(BaseThrottle):
    cache_key_prefix = 'status_code_throttle_'
    rate = 5  
    duration = 60  

    def get_cache_key(self, request):
        return f"{self.cache_key_prefix}{request.user.id}"

    def allow_request(self, request, view):
        cache_key = self.get_cache_key(request)
        request_data = cache.get(cache_key, [])
        now = timezone.now()
        
        request_data = [entry for entry in request_data if entry > now - timedelta(seconds=self.duration)]
        cache.set(cache_key, request_data, timeout=self.duration)
        
        if len(request_data) >= self.rate:
            return False
        
        return True

    def record_status_code(self, request, status_code):
        if status_code == 404:
            cache_key = self.get_cache_key(request)
            request_data = cache.get(cache_key, [])
            request_data.append(timezone.now())
            cache.set(cache_key, request_data, timeout=self.duration)

Using the Throttle in CheckUserPhoneView

from rest_framework.response import Response
from rest_framework import status
from rest_framework.views import APIView
from .throttles import StatusCodeThrottle

class CheckUserPhone(APIView):
    throttle_classes = (StatusCodeThrottle,)
    serializer_class = CheckUserPhoneSerializer

    def post(self, request):
        throttle = StatusCodeThrottle()
        if not throttle.allow_request(request, self):
            return Response({"detail": "Request limit reached"}, status=status.HTTP_429_TOO_MANY_REQUESTS)

        serializer = self.serializer_class(data=request.data)
        serializer.is_valid(raise_exception=True)
        phone = get_phone_from_serializer(serializer)
        
        user = User.objects.filter(phone=phone)
        if user.exists():
            response = Response({"message": LoginSituation.LOGIN_REQUIRED}, status=status.HTTP_200_OK)
        else:
            sms_provider_result, code = generate_otp_and_send(phone)
            if sms_provider_result:
                save_otp_inside_cache(phone, code)
                response = Response({"message": LoginSituation.REGISTER_REQUIRED}, status=status.HTTP_404_NOT_FOUND)
            else:
                response = Response({"message": FaultCode.SMS_PROVIDER_FAILURE}, status=status.HTTP_400_BAD_REQUEST)

        throttle.record_status_code(request, response.status_code)
        return response

1 Like

thank you, i wrote some code like that but not as good as your code

You are welcome, if you have a problem, feel free to tag me and I will be happy to help.

1 Like