Skip to main content

Python Rate-Limiter using Leaky-Bucket Algorimth Family

Project description

PyrateLimiter

The request rate limiter using Leaky-bucket algorithm

PyPI version Coverage Status Python 3.7 Python 3.8 Maintenance PyPI license HitCount


Introduction

This module can be used to apply rate-limit for API request. User defines window duration and the limit of function calls within such interval. To hold the state of the Bucket, you can use MemoryListBucket/MemoryQueueBucket as internal bucket. To use PyrateLimiter with Redis, redis-py is required to be installed. It is also possible to use your own Bucket implementation, by extending AbstractBucket from pyrate_limiter.core

Available modules

from pyrate_limiter import (
	BucketFullException,
	Duration,
	RequestRate,
	Limiter,
	MemoryListBucket,
	MemoryQueueBucket,
)

Strategies

Subscription strategies

Considering API throttling logic for usual business models of Subscription, we usually see strategies somewhat similar to these.

Some commercial/free API (Linkedin, Github etc)
- 500 requests/hour, and
- 1000 requests/day, and
- maximum 10,000 requests/month
  • RequestRate class is designed to describe this strategies - eg for the above strategies we have a Rate-Limiter defined as following
hourly_rate = RequestRate(500, Duration.HOUR) # maximum 500 requests/hour
daily_rate = RequestRate(1000, Duration.DAY) # maximum 1000 requests/day
monthly_rate = RequestRate(10000, Duration.MONTH) # and so on

limiter = Limiter(hourly_rate, daily_rate, monthly_rate, *other_rates, bucket_class=MemoryListBucket) # default is MemoryQueueBucket

# usage
identity = user_id # or ip-address, or maybe both
limiter.try_acquire(identity)

As the logic is pretty self-explainatory, note that the superior rate-limit must come after the inferiors, ie 1000 req/day must be declared after an hourly-rate-limit, and the daily-limit must be larger than hourly-limit.

  • bucket_class is the type of bucket that holds request. It could be an in-memory data structure like Python List (MemoryListBucket), or Queue MemoryQueueBucket.

  • For microservices or decentralized platform, multiple rate-Limiter may share a single store for storing request-rate history, ie Redis. This lib provides a ready-use RedisBucket to handle such case, and required redis-py as its peer-dependency. The usage difference is when using Redis, a naming prefix must be provide so the keys can be distinct for each item's identity.

from redis import ConnectionPool

pool = ConnectionPool.from_url('redis://localhost:6379')

rate = RequestRate(3, 5 * Duration.SECOND)

bucket_kwargs = {
	"redis_pool": redis_pool,
	"bucket_name": "my-ultimate-bucket-prefix"
}

# so each item buckets will have a key name as
# my-ultimate-bucket-prefix__item-identity

limiter = Limiter(rate, bucket_class=RedisBucket, bucket_kwargs=bucket_kwargs)
item = 'vutran_item'
limiter.try_acquire(item)

BucketFullException

If the Bucket is full, an exception BucketFullException will be raised, with meta-info about the identity it received, the rate that has raised, and the remaining time until the next request can be processed.

rate = RequestRate(3, 5 * Duration.SECOND)
limiter = Limiter(rate)
item = 'vutran'

has_raised = False
try:
	for _ in range(4):
		limiter.try_acquire(item)
		sleep(1)
except BucketFullException as err:
	has_raised = True
	assert str(err)
	# Bucket for vutran with Rate 3/5 is already full
	assert isinstance(err.meta_info, dict)
	# {'error': 'Bucket for vutran with Rate 3/5 is already full', 'identity': 'tranvu', 'rate': '5/5', 'remaining_time': 2}
  • *RequestRate may be required to reset on a fixed schedule, eg: every first-day of a month

Decorator usage with rate-limiting exceptions

Rate-limiting is also available in decorator form, using Limiter.ratelimit. Example:

    @limiter.ratelimit(item)
    def my_function():
        do_stuff()

It also works on async functions:

    @limiter.ratelimit(item)
    async def my_function():
        await do_stuff()

As with Limiter.try_acquire, if calls to the wrapped function exceed the rate limits you defined, a BucketFullException will be raised.

Decorator usage with rate-limiting delays

In some cases, you may want to simply slow down your calls to stay within the rate limits instead of canceling them. In that case you can use the delay flag, optionally with a max_delay (in seconds) that you are willing to wait in between calls.

Example:

    @limiter.ratelimit(item, delay=True, max_delay=10)
    def my_function():
        do_stuff()

In this case, calls may be delayed by at most 10 seconds to stay within the rate limits; any longer than that, and a BucketFullException will be raised instead. Without specifying max_delay, calls will be delayed as long as necessary.

This also works for async functions, which will be delayed using asyncio.sleep instead of time.sleep.

Spam-protection strategies

  • Sometimes, we need a rate-limiter to protect our API from spamming/ddos attack. Some usual strategies for this could be as following
1. No more than 100 requests/minute, or
2. 100 request per minute, and no more than 300 request per hour

Throttling handling

When the number of incoming requets go beyond the limit, we can either do..

1. Raise a 429 Http Error, or
2. Keep the incoming requests, wait then slowly process them one by one.

More complex scenario

https://www.keycdn.com/support/rate-limiting#types-of-rate-limits

  • *Sometimes, we may need to apply specific rate-limiting strategies based on schedules/region or some other metrics. It requires the capability to switch the strategies instantly without re-deploying the whole service.

Notes

Todo-items marked with (*) are planned for v3 release.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyrate-limiter-2.2.0.tar.gz (11.1 kB view details)

Uploaded Source

Built Distribution

pyrate_limiter-2.2.0-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

File details

Details for the file pyrate-limiter-2.2.0.tar.gz.

File metadata

  • Download URL: pyrate-limiter-2.2.0.tar.gz
  • Upload date:
  • Size: 11.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.0.10 CPython/3.7.5 Darwin/20.3.0

File hashes

Hashes for pyrate-limiter-2.2.0.tar.gz
Algorithm Hash digest
SHA256 6a71960e9df5569f9530ceb3bd19441fa73a172b883f1ed89621f1655230b02e
MD5 cff66d366fecfb33552d3976d5e75bbc
BLAKE2b-256 a66793a88ea94f63f6b95464158aa6736b60da8e4ad56dc92ce25ec7af69efdc

See more details on using hashes here.

Provenance

File details

Details for the file pyrate_limiter-2.2.0-py3-none-any.whl.

File metadata

  • Download URL: pyrate_limiter-2.2.0-py3-none-any.whl
  • Upload date:
  • Size: 9.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.0.10 CPython/3.7.5 Darwin/20.3.0

File hashes

Hashes for pyrate_limiter-2.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5805204983800a14c541fb23f3932c4295d32396f3709cc624bccdbeac026f87
MD5 bf74089a3a55e1cd60e7f4943da98430
BLAKE2b-256 28364e7a42b977db888d72cea89d52297c4b11761b46cdc6f5136875dcca536d

See more details on using hashes here.

Provenance

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page