# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['pyrate_limiter']

package_data = \
{'': ['*']}

setup_kwargs = {
    'name': 'pyrate-limiter',
    'version': '2.6.2',
    'description': 'Python Rate-Limiter using Leaky-Bucket Algorimth Family',
    'long_description': '<img align="left" width="95" height="120" src="https://raw.githubusercontent.com/vutran1710/PyrateLimiter/master/img/log.png">\n\n# PyrateLimiter\nThe request rate limiter using Leaky-bucket algorithm\n\n[![PyPI version](https://badge.fury.io/py/pyrate-limiter.svg)](https://badge.fury.io/py/pyrate-limiter)\n[![PyPI - Python Versions](https://img.shields.io/pypi/pyversions/pyrate-limiter)](https://pypi.org/project/pyrate-limiter)\n[![codecov](https://codecov.io/gh/vutran1710/PyrateLimiter/branch/master/graph/badge.svg?token=E0Q0YBSINS)](https://codecov.io/gh/vutran1710/PyrateLimiter)\n[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/vutran1710/PyrateLimiter/graphs/commit-activity)\n[![PyPI license](https://img.shields.io/pypi/l/ansicolortags.svg)](https://pypi.python.org/pypi/pyrate-limiter/)\n\n<br>\n\n- [PyrateLimiter](#pyratelimiter)\n  * [Introduction](#introduction)\n  * [Available modules](#available-modules)\n  * [Bucket backends](#bucket-backends)\n    + [Memory](#memory)\n    + [SQLite](#sqlite)\n    + [Redis](#redis)\n    + [Custom backends](#custom-backends)\n  * [Strategies](#strategies)\n    + [Subscription strategies](#subscription-strategies)\n    + [BucketFullException](#bucketfullexception)\n    + [Decorator](#decorator)\n    + [Rate-limiting delays](#rate-limiting-delays)\n    + [Contextmanager](#contextmanager)\n    + [Async decorator/contextmanager](#async-decorator-contextmanager)\n    + [Examples](#examples)\n    + [Spam-protection strategies](#spam-protection-strategies)\n    + [Throttling handling](#throttling-handling)\n    + [More complex scenario](#more-complex-scenario)\n  * [Development](#development)\n  * [Notes](#notes)\n\n## Introduction\nThis module can be used to apply rate-limit for API request. User defines window duration and the limit of function calls within such interval.\n\n## Available modules\n```python\nfrom pyrate_limiter import (\n    BucketFullException,\n    Duration,\n    RequestRate,\n    Limiter,\n    MemoryListBucket,\n    MemoryQueueBucket,\n    SQLiteBucket,\n    RedisBucket,\n    RedisClusterBucket,\n)\n```\n\n## Bucket backends\nA few different bucket backends are available, which can be selected using the `bucket_class`\nargument for `Limiter`. Any additional backend-specific arguments can be passed\nvia `bucket_kwargs`.\n\n### Memory\nThe default bucket is stored in memory, backed by a `queue.Queue`. A list implementation is also available:\n```python\nfrom pyrate_limiter import Limiter, MemoryListBucket\n\nlimiter = Limiter(bucket_class=MemoryListBucket)\n```\n\n### SQLite\nIf you need to persist the bucket state, a SQLite backend is available.\n\nBy default it will store the state in the system temp directory, and you can use\nthe `path` argument to use a different location:\n```python\nfrom pyrate_limiter import Limiter, SQLiteBucket\n\nlimiter = Limiter(\n    bucket_class=SQLiteBucket,\n    bucket_kwargs={\'path\': \'/tmp/pyrate_limiter.sqlite\'},\n)\n```\n\n### Redis\nIf you have a larger, distributed application, Redis is an ideal backend. This\noption requires [redis-py](https://github.com/andymccurdy/redis-py).\n\nYou can use the `redis_pool` argument to pass any connection settings:\n```python\nfrom pyrate_limiter import Limiter, RedisBucket\nfrom redis import ConnectionPool\n\nredis_pool = ConnectionPool(host=\'localhost\', port=6379, db=0)\nlimiter = Limiter(\n    bucket_class=RedisBucket,\n    bucket_kwargs={\'redis_pool\': redis_pool},\n)\n```\n\nRedis clusters are also supported, which requires\n[redis-py-cluster](https://github.com/Grokzen/redis-py-cluster):\n```python\nfrom pyrate_limiter import Limiter, RedisClusterBucket\n\nlimiter = Limiter(bucket_class=RedisClusterBucket)\n```\n\n### Custom backends\nIf these don\'t suit your needs, you can also create your own bucket backend by extending `pyrate_limiter.bucket.AbstractBucket`.\n\n## Strategies\n\n### Subscription strategies\n\nConsidering API throttling logic for usual business models of Subscription, we usually see strategies somewhat similar to these.\n\n``` shell\nSome commercial/free API (Linkedin, Github etc)\n- 500 requests/hour, and\n- 1000 requests/day, and\n- maximum 10,000 requests/month\n```\n\n- [x] `RequestRate` class is designed to describe this strategies - eg for the above strategies we have a Rate-Limiter defined\nas following\n\n``` python\nhourly_rate = RequestRate(500, Duration.HOUR) # maximum 500 requests/hour\ndaily_rate = RequestRate(1000, Duration.DAY) # maximum 1000 requests/day\nmonthly_rate = RequestRate(10000, Duration.MONTH) # and so on\n\nlimiter = Limiter(hourly_rate, daily_rate, monthly_rate, *other_rates, bucket_class=MemoryListBucket) # default is MemoryQueueBucket\n\n# usage\nidentity = user_id # or ip-address, or maybe both\nlimiter.try_acquire(identity)\n```\n\nAs the logic is pretty self-explainatory, note that the superior rate-limit must come after the inferiors, ie\n1000 req/day must be declared after an hourly-rate-limit, and the daily-limit must be larger than hourly-limit.\n\n- [x] `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`.\n\n\n- [x] For microservices or decentralized platform, multiple rate-Limiter may share a single store for storing\n      request-rate history, ie `Redis`. This lib provides ready-to-use `RedisBucket` (`redis-py` is required), and `RedisClusterBucket` (with `redis-py-cluster` being required). The usage difference is when using Redis, a naming `prefix` must be provide so the keys can be distinct for each item\'s identity.\n\n``` python\nfrom pyrate_limiter.bucket import RedisBucket, RedisClusterBucket\nfrom redis import ConnectionPool\n\nredis_pool = ConnectionPool.from_url(\'redis://localhost:6379\')\n\nrate = RequestRate(3, 5 * Duration.SECOND)\n\nbucket_kwargs = {\n    "redis_pool": redis_pool,\n    "bucket_name": "my-ultimate-bucket-prefix"\n}\n\n# so each item buckets will have a key name as\n# my-ultimate-bucket-prefix__item-identity\n\nlimiter = Limiter(rate, bucket_class=RedisBucket, bucket_kwargs=bucket_kwargs)\n# or RedisClusterBucket when used with a redis cluster\n# limiter = Limiter(rate, bucket_class=RedisClusterBucket, bucket_kwargs=bucket_kwargs)\nitem = \'vutran_item\'\nlimiter.try_acquire(item)\n```\n\n### BucketFullException\nIf 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.\n\n```python\nrate = RequestRate(3, 5 * Duration.SECOND)\nlimiter = Limiter(rate)\nitem = \'vutran\'\n\nhas_raised = False\ntry:\n    for _ in range(4):\n        limiter.try_acquire(item)\n        sleep(1)\nexcept BucketFullException as err:\n    has_raised = True\n    assert str(err)\n    # Bucket for vutran with Rate 3/5 is already full\n    assert isinstance(err.meta_info, dict)\n    # {\'error\': \'Bucket for vutran with Rate 3/5 is already full\', \'identity\': \'tranvu\', \'rate\': \'5/5\', \'remaining_time\': 2}\n```\n\n- [ ] *RequestRate may be required to `reset` on a fixed schedule, eg: every first-day of a month\n\n### Decorator\nRate-limiting is also available in decorator form, using `Limiter.ratelimit`. Example:\n```python\n@limiter.ratelimit(item)\ndef my_function():\n    do_stuff()\n```\n\nAs with `Limiter.try_acquire`, if calls to the wrapped function exceed the rate limits you\ndefined, a `BucketFullException` will be raised.\n\n### Rate-limiting delays\nIn some cases, you may want to simply slow down your calls to stay within the rate limits instead of\ncanceling them. In that case you can use the `delay` flag, optionally with a `max_delay`\n(in seconds) that you are willing to wait in between calls.\n\nExample:\n```python\n@limiter.ratelimit(item, delay=True, max_delay=10)\ndef my_function():\n    do_stuff()\n```\n\nIn this case, calls may be delayed by at most 10 seconds to stay within the rate limits; any longer\nthan that, and a `BucketFullException` will be raised instead. Without specifying `max_delay`, calls\nwill be delayed as long as necessary.\n\n### Contextmanager\n`Limiter.ratelimit` also works as a contextmanager:\n\n```python\ndef my_function():\n    with limiter.ratelimit(item, delay=True):\n        do_stuff()\n```\n\n### Async decorator/contextmanager\nAll the above features of `Limiter.ratelimit` also work on async functions:\n```python\n@limiter.ratelimit(item, delay=True)\nasync def my_function():\n    await do_stuff()\n\nasync def my_function():\n    async with limiter.ratelimit(item):\n        await do_stuff()\n```\n\nWhen delays are enabled, `asyncio.sleep` will be used instead of `time.sleep`.\n\n### Examples\nTo prove that pyrate-limiter is working as expected, here is a complete example to demonstrate\nrate-limiting with delays:\n```python\nfrom time import perf_counter as time\nfrom pyrate_limiter import Duration, Limiter, RequestRate\n\nlimiter = Limiter(RequestRate(5, Duration.SECOND))\nn_requests = 27\n\n@limiter.ratelimit("test", delay=True)\ndef limited_function(start_time):\n    print(f"t + {(time() - start_time):.5f}")\n\nstart_time = time()\nfor _ in range(n_requests):\n    limited_function(start_time)\nprint(f"Ran {n_requests} requests in {time() - start_time:.5f} seconds")\n```\n\nAnd an equivalent example for async usage:\n```python\nimport asyncio\nfrom time import perf_counter as time\nfrom pyrate_limiter import Duration, Limiter, RequestRate\n\nlimiter = Limiter(RequestRate(5, Duration.SECOND))\nn_requests = 27\n\n@limiter.ratelimit("test", delay=True)\nasync def limited_function(start_time):\n    print(f"t + {(time() - start_time):.5f}")\n\nasync def test_ratelimit():\n    start_time = time()\n    tasks = [limited_function(start_time) for _ in range(n_requests)]\n    await asyncio.gather(*tasks)\n    print(f"Ran {n_requests} requests in {time() - start_time:.5f} seconds")\n\nasyncio.run(test_ratelimit())\n```\n\nLimiter can be used with any custom time source. For example user may want to use\nRedis time to use same time in distributed installation. By default `time.monotonic` is\nused. To adjust time source use `time_function` parameter with any no arguments function.\n```python\nfrom datetime import datetime\nfrom pyrate_limiter import Duration, Limiter, RequestRate\nfrom time import time\n\nlimiter_datetime = Limiter(RequestRate(5, Duration.SECOND), time_function=lambda: datetime.utcnow().timestamp())\nlimiter_time = Limiter(RequestRate(5, Duration.SECOND), time_function=time)\n```\n\n### Spam-protection strategies\n- [x] Sometimes, we need a rate-limiter to protect our API from spamming/ddos attack. Some usual strategies for this could be as\nfollowing\n\n``` shell\n1. No more than 100 requests/minute, or\n2. 100 request per minute, and no more than 300 request per hour\n```\n\n### Throttling handling\nWhen the number of incoming requets go beyond the limit, we can either do..\n\n``` shell\n1. Raise a 429 Http Error, or\n2. Keep the incoming requests, wait then slowly process them one by one.\n```\n\n### More complex scenario\nhttps://www.keycdn.com/support/rate-limiting#types-of-rate-limits\n\n- [ ] *Sometimes, we may need to apply specific rate-limiting strategies based on schedules/region or some other metrics. It\nrequires the capability to `switch` the strategies instantly without re-deploying the whole service.\n\n## Development\n\n### Setup & Commands\n- To setup local development,  *Poetry* and *Python 3.6* is required. Python can be installed using *Pyenv* or normal installation from binary source. To install *poetry*, follow the official guideline (https://python-poetry.org/docs/#installation).\n\nThen, in the repository directory...\n```shell\n$ poetry install\n```\n\nSome shortcuts are included for some common development tasks, using [nox](https://nox.thea.codes):\n- Run tests with: `nox -e test`\n- To run tests with coverage: `nox -e cover`\n- Format & check for lint error: `nox -e lint`\n- To run linting for every commit, run: `pre-commit install`\n\n### Guideline & Notes\nWe have GitHub Action CICD to do the checking, testing and publishing work. So, there are few small notes when making Pull Request:\n- All existing tests must pass (Of course!)\n- Reduction in *Coverage* shall result in failure. (below 98% is not accepted)\n- When you are making bug fixes, or adding more features, remember to bump the version number in **pyproject.toml**. The number should follow *semantic-versioning* rules\n\n## Notes\nTodo-items marked with (*) are planned for v3 release.\n',
    'author': 'vutr',
    'author_email': 'me@vutr.io',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/vutran1710/PyrateLimiter',
    'packages': packages,
    'package_data': package_data,
    'python_requires': '>=3.6.2,<4.0.0',
}


setup(**setup_kwargs)
