Source code for durin.throttling

"""
Durin provides a throttling class which make use of the
:class:`durin.models.Client` model it offers.

Usage is the same way as other
`DRF throttling classes <https://django-rest-framework.org/api-guide/throttling/>`__.

Example ``settings.py``::

        #...snip...
        REST_FRAMEWORK = {
            "DEFAULT_THROTTLE_CLASSES": ["durin.throttling.UserClientRateThrottle"],
            "DEFAULT_THROTTLE_RATES": {"user_per_client": "10/min"},
        }
        #...snip...

.. data:: "user_per_client"

    default ``scope`` for the :class:`UserClientRateThrottle` class.

    The rate defined here serves as the default rate incase the
    ``throttle_rate`` field on :class:`durin.models.Client` is ``null``.
"""

from django.core.exceptions import ValidationError as DjValidationError
from rest_framework.throttling import UserRateThrottle


[docs]class UserClientRateThrottle(UserRateThrottle): # lgtm [py/missing-call-to-init] """ Throttles requests by identifying the *authed* **user-client pair**. This is useful if you want to define different user throttle rates per :class:`durin.models.Client` instance. .. versionadded:: 0.2 """ #: Same as the default cache_format = "throttle_%(scope)s_%(ident)s" #: Scope for this throttle scope = "user_per_client" def __init__(self): # lgtm [py/missing-call-to-init] pass
[docs] def allow_request(self, request, view): """ The ``rate`` is set here because we need access to ``request`` object which is not available inside :py:meth:`~get_rate`. """ if request.user.is_authenticated and hasattr(request, "_auth"): rate = request._auth.client.throttle_rate self.rate = rate if rate else self.get_rate() else: self.rate = self.get_rate() self.num_requests, self.duration = self.parse_rate(self.rate) return super().allow_request(request, view)
[docs] def get_cache_key(self, request, view) -> str: if request.user.is_authenticated: # overwrite if hasattr(request, "_auth"): ident = self._get_user_client_ident(request) else: ident = request.user.pk else: ident = self.get_ident(request) return self.cache_format % {"scope": self.scope, "ident": ident}
def _get_user_client_ident(self, request) -> str: """ Identify the user-client pair making the request. (assumes that ``request._auth`` is set, see :py:meth:`~get_cache_key`). """ user_pk = request._auth.user_id client_pk = request._auth.client_id ident = "user-{0}.client-{1}".format(user_pk, client_pk) return ident
[docs] @staticmethod def validate_client_throttle_rate(rate): """ Used for validating the :attr:`throttle_rate` field on :class:`durin.models.Client`. *For internal use only.* """ TIME_PERIODS_MAP = {"s": 1, "m": 60, "h": 3600, "d": 86400} try: num, period = rate.split("/") return int(num), TIME_PERIODS_MAP[period] except KeyError: raise DjValidationError("invalid period '{0}'.".format(period)) except Exception as e: raise DjValidationError(e)