# Limiters
Provides logic for handling two distrinct types of rate limits: concurrency- and time-based.
The data structures are distributed, using Redis, and leverages Lua scripts to save round-trips. A sync and async version is available for
both.
Compatible with redis-clusters. Currently only supports Python 3.11, but adding support for other versions would be simple if desired.
## Installation
```
pip install limiters
```
## Semaphore
The `Semaphore` classes are useful if you're working with concurrency-based
rate limits. Say, you are allowed to have 5 active requests at the time
for a given API token.
On trying to acquire the Semaphore, beware that the client will block the thread until the Semaphore is acquired, or the `max_sleep` limit
is exceeded.
If the `max_sleep` limit is exceeded, a `MaxSleepExceededError` is raised.
Here's how you might use the async version:
```python
from httpx import AsyncClient
from limiters import AsyncSemaphore
limiter = AsyncSemaphore(
name="foo", # name of the resource you are limiting traffic for
capacity=5, # allow 5 concurrent requests
max_sleep=30, # raise an error if it takes longer than 30 seconds to acquire the semaphore
expiry=30 # set expiry on the semaphore keys in Redis to prevent deadlocks
)
async def get_foo():
async with AsyncClient() as client:
async with limiter:
client.get(...)
async def main():
await asyncio.gather(
get_foo() for i in range(100)
)
```
and here is how you might use the sync version:
```python
import requests
from limiters import SyncSemaphore
limiter = SyncSemaphore(
name="foo",
capacity=5,
max_sleep=30,
expiry=30
)
def main():
with limiter:
requests.get(...)
```
## Token bucket
The `TocketBucket` classes 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 the `max_sleep` limit is exceeded, a `MaxSleepExceededError` is raised.
Here's how you might use the async version:
```python
from httpx import AsyncClient
from limiters import AsyncTokenBucket
limiter = AsyncTokenBucket(
name="foo", # name of the resource you are limiting traffic for
capacity=5, # hold up to 5 tokens
refill_frequency=1, # add tokens every second
refill_amount=1, # add 1 token when refilling
max_sleep=30, # raise an error there are no free tokens for 30 seconds
)
async def get_foo():
async with AsyncClient() as client:
async with limiter:
client.get(...)
async def main():
await asyncio.gather(
get_foo() for i in range(100)
)
```
and here is how you might use the sync version:
```python
import requests
from limiters import SyncTokenBucket
limiter = SyncTokenBucket(
name="foo", # name of the resource you are limiting traffic for
capacity=5, # hold up to 5 tokens
refill_frequency=1, # add tokens every second
refill_amount=1, # add 1 token when refilling
max_sleep=30, # raise an error there are no free tokens for 30 seconds
)
def main():
with limiter:
requests.get(...)
```
## Contributing
Contributions are very welcome. Here's how to get started:
- Set up a Python 3.11+ venv, and install `poetry`
- Install dependencies with `poetry install`
- Run `pre-commit install` to set up pre-commit
- Run `docker compose up` to run Redis (or run it some other way)
- Make your code changes
- Add tests
- Commit your changes and open a PR
Raw data
{
"_id": null,
"home_page": "https://github.com/otovo/limiters",
"name": "limiters",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.11,<4.0",
"maintainer_email": "",
"keywords": "async,sync,rate,limiting,limiters",
"author": "Sondre Lilleb\u00f8 Gundersen",
"author_email": "sondrelg@live.no",
"download_url": "https://files.pythonhosted.org/packages/6a/54/847903a58ebc7d150295b6557ea89c368c3f62ffdb536cb00204744add3f/limiters-0.1.2.tar.gz",
"platform": null,
"description": "# Limiters\n\nProvides logic for handling two distrinct types of rate limits: concurrency- and time-based.\n\nThe data structures are distributed, using Redis, and leverages Lua scripts to save round-trips. A sync and async version is available for\nboth.\n\nCompatible with redis-clusters. Currently only supports Python 3.11, but adding support for other versions would be simple if desired.\n\n## Installation\n\n```\npip install limiters\n```\n\n## Semaphore\n\nThe `Semaphore` classes are useful if you're working with concurrency-based\nrate limits. Say, you are allowed to have 5 active requests at the time\nfor a given API token.\n\nOn trying to acquire the Semaphore, beware that the client will block the thread until the Semaphore is acquired, or the `max_sleep` limit\nis exceeded.\n\nIf the `max_sleep` limit is exceeded, a `MaxSleepExceededError` is raised.\n\nHere's how you might use the async version:\n\n```python\nfrom httpx import AsyncClient\n\nfrom limiters import AsyncSemaphore\n\nlimiter = AsyncSemaphore(\n name=\"foo\", # name of the resource you are limiting traffic for\n capacity=5, # allow 5 concurrent requests\n max_sleep=30, # raise an error if it takes longer than 30 seconds to acquire the semaphore\n expiry=30 # set expiry on the semaphore keys in Redis to prevent deadlocks\n)\n\n\nasync def get_foo():\n async with AsyncClient() as client:\n async with limiter:\n client.get(...)\n\n\nasync def main():\n await asyncio.gather(\n get_foo() for i in range(100)\n )\n```\n\nand here is how you might use the sync version:\n\n```python\nimport requests\n\nfrom limiters import SyncSemaphore\n\nlimiter = SyncSemaphore(\n name=\"foo\",\n capacity=5,\n max_sleep=30,\n expiry=30\n)\n\n\ndef main():\n with limiter:\n requests.get(...)\n```\n\n## Token bucket\n\nThe `TocketBucket` classes are useful if you're working with time-based\nrate limits. Say, you are allowed 100 requests per minute, for a given API token.\n\nIf the `max_sleep` limit is exceeded, a `MaxSleepExceededError` is raised.\n\nHere's how you might use the async version:\n\n```python\nfrom httpx import AsyncClient\n\nfrom limiters import AsyncTokenBucket\n\nlimiter = AsyncTokenBucket(\n name=\"foo\", # name of the resource you are limiting traffic for\n capacity=5, # hold up to 5 tokens\n refill_frequency=1, # add tokens every second\n refill_amount=1, # add 1 token when refilling\n max_sleep=30, # raise an error there are no free tokens for 30 seconds\n)\n\n\nasync def get_foo():\n async with AsyncClient() as client:\n async with limiter:\n client.get(...)\n\n\nasync def main():\n await asyncio.gather(\n get_foo() for i in range(100)\n )\n```\n\nand here is how you might use the sync version:\n\n```python\nimport requests\n\nfrom limiters import SyncTokenBucket\n\nlimiter = SyncTokenBucket(\n name=\"foo\", # name of the resource you are limiting traffic for\n capacity=5, # hold up to 5 tokens\n refill_frequency=1, # add tokens every second\n refill_amount=1, # add 1 token when refilling\n max_sleep=30, # raise an error there are no free tokens for 30 seconds\n)\n\n\ndef main():\n with limiter:\n requests.get(...)\n```\n\n## Contributing\n\nContributions are very welcome. Here's how to get started:\n\n- Set up a Python 3.11+ venv, and install `poetry`\n- Install dependencies with `poetry install`\n- Run `pre-commit install` to set up pre-commit\n- Run `docker compose up` to run Redis (or run it some other way)\n- Make your code changes\n- Add tests\n- Commit your changes and open a PR\n",
"bugtrack_url": null,
"license": "BSD-4-Clause",
"summary": "Distributed rate limiters",
"version": "0.1.2",
"project_urls": {
"Homepage": "https://github.com/otovo/limiters",
"Repository": "https://github.com/otovo/limiters"
},
"split_keywords": [
"async",
"sync",
"rate",
"limiting",
"limiters"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "a70058056ac01edaa8a5188dbd474a12721b272940058e06acf47e8d15d928bf",
"md5": "7a28e1f7ed7b7bbb7c6f3c10888b6d4f",
"sha256": "57423433c8246f2f3477ccbf5dfa716387ea710fe1e2ce2cd01e5c4d266a60dc"
},
"downloads": -1,
"filename": "limiters-0.1.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7a28e1f7ed7b7bbb7c6f3c10888b6d4f",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11,<4.0",
"size": 9503,
"upload_time": "2023-05-22T11:11:34",
"upload_time_iso_8601": "2023-05-22T11:11:34.073088Z",
"url": "https://files.pythonhosted.org/packages/a7/00/58056ac01edaa8a5188dbd474a12721b272940058e06acf47e8d15d928bf/limiters-0.1.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "6a54847903a58ebc7d150295b6557ea89c368c3f62ffdb536cb00204744add3f",
"md5": "f881d15b6ce91b759b375ecfc65c065f",
"sha256": "21c187ae92aa46dc368129b147bb90aa778ac7a96603d1fdde748a2990bfc901"
},
"downloads": -1,
"filename": "limiters-0.1.2.tar.gz",
"has_sig": false,
"md5_digest": "f881d15b6ce91b759b375ecfc65c065f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11,<4.0",
"size": 8485,
"upload_time": "2023-05-22T11:11:36",
"upload_time_iso_8601": "2023-05-22T11:11:36.494703Z",
"url": "https://files.pythonhosted.org/packages/6a/54/847903a58ebc7d150295b6557ea89c368c3f62ffdb536cb00204744add3f/limiters-0.1.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-05-22 11:11:36",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "otovo",
"github_project": "limiters",
"github_not_found": true,
"lcname": "limiters"
}