Rate-Limiting Decorators

Decorators are great for APIs because they cleanly abstract shared functionality from different endpoints. Rate limiting is a great use case for decorators and should be incorporated into just about every API. Rate limiting ensures that there aren’t too many calls originating from the same user/IP address/etc. in a given timeframe. This can prevent the server or database from getting bogged down with spammy requests.

Let’s look at an example. Your application is a math API and has an endpoint /get_fibonacci for computing the nth element of the Fibonacci sequence. This is a computationally expensive operation, and you know that users don’t need to hit this endpoint more than a few times per day. That is, if a user is calling it multiple times in a short window, either there’s an error in the application that is causing that function to be called many times, or the user is abusing the API. Either way, the overloaded server is doing more work than is necessary, and we can prevent this with rate limiting.

To be safe, let’s allow each user call this endpoint 10 times every 5 minutes. If we only expect 2 or 3 calls every 24 hours, this should be plenty, and legitimate users won’t be locked out. If the application has a login system, you can limit based on username. Otherwise, IP address, user agent, or some other identifying data will work. You will need to store a dictionary that maps each user to the number of calls in the past 5 minutes. If you are looking for code to use in your project, you might want to check out a complete example (← code snippet uses Flask).

For an easier code example, let’s say we want to limit API calls by time of day instead of rate–calls are allowed only between midnight and noon. (There’s a use case for that, right?…🙂) This would be our code:

@app.route("/get_fibonacci", methods=["GET"])
@ratelimit
def fibonacci():
    n = int(flask.request.args["n"])
    a = 0
    b = 1
    while b < n:
        a = b
        b = a + b
    return str(b)

def ratelimit(func):
    def ratelimit_wrapper(*args, **kwargs):
        if datetime.datetime.utcnow().time().hour > 12:
            return "Sorry, please try again tomorrow in the AM!"
        return func(*args, **kwargs)
    return ratelimit_wrapper

Then navigate to {host}/get_fibonacci?n=5 to see it in action.

This means that before the given Fibonacci number is computed, the decorator checks to see if the current time is before or after noon. If it’s after noon, the error message is returned to the user. Otherwise, the fibonacci() function runs and returns the result to the requester.

Here’s a line-by-line description of everything that’s going on:

  • 1. This function can be accessed via GET at {host/application_root}/get_fibonacci
  • 2. Attaches the ratelimit decorator to this endpoint (more accurately, it wraps this function with the ratelimit function)
  • 3. Function definition
  • 4. Extract GET parameter from the URL
  • 5/6. Initialize first two elements of the Fibonacci sequence
  • 7. Repeat until we find the nth element
  • 8/9. Get the next element in the sequence
  • 10. Return the nth element (View function expects a string response)
  • 12. Decorator definition
  • 13. Wrapper function that surrounds the decorator–accepts any arguments
  • 14. Check the time in case it’s after noon
  • 15. This is returned to the requester without ever calling fibonacci() if it’s after noon
  • 16. It’s before noon, so return the output of the original function
  • 17. Return this decorator wrapper

Decorators can be confusing at first, but they’re powerful and great for writing simple, clean code. If you have any questions, please let me know in the comments below!

Happy coding!
Ryan from The Bunch

One thought on “Rate-Limiting Decorators”

Leave a Reply

Your email address will not be published. Required fields are marked *