How to Add Subscription Based Throttling to a Django API
Tags: python, django, saas, api
Published: 2020-06-14 15:55:03

---
title: How to Add Subscription Based Throttling to a Django API
published: true
description: Extending the Django Rest Framework to throttle API requests based on user-specific limits
tags: #python #django #saas #api
//cover_image: https://direct_url_to_image.jpg
---

Python was a natural choice when I started [SocialSentiment.io](https://socialsentiment.io). It let me use the same language for both the machine learning algorithms and web development. And I had used [Django](https://www.djangoproject.com/) previously for other projects. The [Django Rest Framework](https://www.django-rest-framework.org/) (DRF) is a great package to quickly and easily extend a Django project to offer APIs. Today we'll look at how to extend its capabilities to support custom throttling based on user subscriptions.

## Subscription Model

First let's define our application's [subscription model](https://socialsentiment.io/plans/) and throttling requirements:
- A free tier allowing a few hundred API requests per day
- A low cost paid tier offering a few thousand requests per day
- A higher cost tier offering unlimited requests
- All tiers limited to 5 requests per second

This is a very common use case for a modern SaaS application.

## Custom Throttling Class

One great thing about Django Rest Framework is it includes many built-in options for authentication and throttling.  Each can be applied globally or to specific endpoints.  If you desire any type of dynamic throttling options you'll need to extend it. Fortunately the architecture of DRF lets you override just about any part of it.

Let's start by writing a custom class that overrides DRF's `UserRateThrottle`:

```python
from rest_framework.throttling import UserRateThrottle

class SubscriptionRateThrottle(UserRateThrottle):
    # Define a custom scope name to be referenced by DRF in settings.py
    scope = "subscription"

    def __init__(self):
        super().__init__()

    def allow_request(self, request, view):
        """
        Override rest_framework.throttling.SimpleRateThrottle.allow_request

        Check to see if the request should be throttled.

        On success calls `throttle_success`.
        On failure calls `throttle_failure`.
        """
        if request.user.is_staff:
            # No throttling
            return True

        if request.user.is_authenticated:
            user_daily_limit = get_user_daily_limit(request.user)
            if user_daily_limit:
                # Override the default from settings.py
                self.duration = 86400
                self.num_requests = user_daily_limit
            else:
                # No limit == unlimited plan
                return True

        # Original logic from the parent method...

        if self.rate is None:
            return True

        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True

        self.history = self.cache.get(self.key, [])
        self.now = self.timer()

        # Drop any requests from the history which have now passed the
        # throttle duration
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()
        return self.throttle_success()
```

What we're doing is dynamically looking up the user-specific throttle at the key moment to override the default DRF picks up from your settings file.  Define a method `get_user_daily_limit` to look up the value. I highly recommend using Django's cache methods if this is stored in a database for performance.

## Settings

Next let's see what's required in `settings.py`:

```python
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [...],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated'
    ],
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.UserRateThrottle',
        'app.throttling.SubscriptionDailyRateThrottle'
    ],
    'DEFAULT_THROTTLE_RATES': {
        'user': '5/second',
        'subscription': '200/day'
    }
}
```

Here we set up two types of throttling.  The built-in `UserRateThrottle` will handle the global 5 requests per second limit. It finds that setting in `DEFAULT_THROTTLE_RATES` with key `user`. Our custom throttle class is also enabled and defaults to the `subscription` value if a user subscription isn't found.  Of course the application should be written so this never happens, but it's good to have a fallback plan if a user isn't configured properly.

## Subscriptions

How you code and model your subscriptions is up to you. In my case I wrote static classes that define the details of each subscription tier.  A `Subscription` model links the user to a specific plan with details such as start time, payment details, etc. 

The nice thing is Django and DRF don't dictate how you design your user subscriptions.  Any way you choose to model it they'll handle because you can customize every aspect of authorization and throttling.

## Conclusion

So far I only have good things to say about the flexibility of Django and DRF and the customizations they allow.  They took the right approach in offering a wide variety of built-in capabilities while allowing developers the opportunity to easily extend or override them.  It's been working great for SocialSentiment.io and [our APIs](https://socialsentiment.io/api/v1/getting-started/).  I'd like to hear how others have added their own features to Django Rest Framework in the comments below.