Package: redis-rate-limiters
Redis rate limitersA library which regulates traffic, with respect to concurrency or time.
It implements sync and async context managers for asemaphore- and atoken bucket-implementation.The rate limiters are distributed, using Redis, and leverages Lua scripts to
improve performance and simplify the code. Lua scripts
run on Redis, and make each implementation fully atomic, while
also reducing the number of round-trips required.Use is supported for standalone redis instances, and clusters.
We currently only support Python 3.11, but can add support for older versions if needed.Installationpip install redis-rate-limitersUsageSemaphoreThe semaphore classes are useful when you have concurrency restrictions;
e.g., say you're allowed 5 active requests at the time for a given API token.Beware that the client will block until the Semaphore is acquired,
or themax_sleeplimit is exceeded. If themax_sleeplimit is exceeded, aMaxSleepExceededErroris raised.Here's how you might use the async version:importasynciofromhttpximportAsyncClientfromredis.asyncioimportRedisfromlimitersimportAsyncSemaphorelimiter=AsyncSemaphore(name="foo",# name of the resource you are limiting traffic forcapacity=5,# allow 5 concurrent requestsmax_sleep=30,# raise an error if it takes longer than 30 seconds to acquire the semaphoreexpiry=30,# set expiry on the semaphore keys in Redis to prevent deadlocksconnection=Redis.from_url("redis://localhost:6379"),)asyncdefget_foo():asyncwithAsyncClient()asclient:asyncwithlimiter:client.get(...)asyncdefmain():awaitasyncio.gather(get_foo()foriinrange(100))and here is how you might use the sync version:importrequestsfromredisimportRedisfromlimitersimportSyncSemaphorelimiter=SyncSemaphore(name="foo",capacity=5,max_sleep=30,expiry=30,connection=Redis.from_url("redis://localhost:6379"),)defmain():withlimiter:requests.get(...)Token bucketTheTocketBucketclasses are useful if you're working with time-based
rate limits. Say, you are allowed 100 requests per minute, for a given API token.If themax_sleeplimit is exceeded, aMaxSleepExceededErroris raised.Here's how you might use the async version:importasynciofromhttpximportAsyncClientfromredis.asyncioimportRedisfromlimitersimportAsyncTokenBucketlimiter=AsyncTokenBucket(name="foo",# name of the resource you are limiting traffic forcapacity=5,# hold up to 5 tokensrefill_frequency=1,# add tokens every secondrefill_amount=1,# add 1 token when refillingmax_sleep=30,# raise an error there are no free tokens for 30 secondsconnection=Redis.from_url("redis://localhost:6379"),)asyncdefget_foo():asyncwithAsyncClient()asclient:asyncwithlimiter:client.get(...)asyncdefmain():awaitasyncio.gather(get_foo()foriinrange(100))and here is how you might use the sync version:importrequestsfromredisimportRedisfromlimitersimportSyncTokenBucketlimiter=SyncTokenBucket(name="foo",capacity=5,refill_frequency=1,refill_amount=1,max_sleep=30,connection=Redis.from_url("redis://localhost:6379"),)defmain():withlimiter:requests.get(...)Using them as a decoratorWe don't ship decorators in the package, but if you would
like to limit the rate at which a whole function is run,
you can create your own, like this:fromlimitersimportAsyncSemaphore# Define a decorator functiondeflimit(name,capacity):defmiddle(f):asyncdefinner(*args,**kwargs):asyncwithAsyncSemaphore(name=name,capacity=capacity):returnawaitf(*args,**kwargs)returninnerreturnmiddle# Then pass the relevant limiter arguments like this@limit(name="foo",capacity=5)deffetch_foo(id:UUID)->Foo:ContributingContributions are very welcome. Here's how to get started:Set up a Python 3.11+ venv, andpip install poetryInstall dependencies withpoetry installRunpre-commit installto set up pre-commitInstalljustand runjust setupIf you prefer not to install just, just take a look at the justfile and
run the commands yourself.Make your code changes, with testsCommit your changes and open a PRPublishing a new versionTo publish a new version:Update the package version in thepyproject.tomlOpenGithub releasesPress "Draft a new release"Set a tag matching the new version (for example,v0.4.2)Set the title matching the tagAdd some release notes, explaining what has changedPublishOnce the release is published, ourpublish workflowshould be triggered
to push the new version to PyPI.