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

packages = \
['pyrate_limiter']

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

entry_points = \
{'console_scripts': ['cover = scripts:cover',
                     'lint = scripts:lint',
                     'test = scripts:test']}

setup_kwargs = {
    'name': 'pyrate-limiter',
    'version': '2.3.4',
    '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[![Coverage Status](https://coveralls.io/repos/github/vutran1710/PyrateLimiter/badge.svg?branch=master)](https://coveralls.io/github/vutran1710/PyrateLimiter?branch=master)\n[![Python 3.7](https://img.shields.io/badge/python-3.7-blue.svg)](https://www.python.org/downloads/release/python-370/)\n[![Python 3.8](https://img.shields.io/badge/python-3.8-blue.svg)](https://www.python.org/downloads/release/python-380/)\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[![HitCount](http://hits.dwyl.io/vutran1710/PyrateLimiter.svg)](http://hits.dwyl.io/vutran1710/PyrateLimiter)\n\n<br>\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.\nTo hold the state of the Bucket, you can use MemoryListBucket/MemoryQueueBucket as internal bucket.\nTo use PyrateLimiter with Redis, redis-py is required to be installed.\nIt is also possible to use your own Bucket implementation, by extending AbstractBucket from pyrate_limiter.core\n\n## Available modules\n```python\nfrom pyrate_limiter import (\n\tBucketFullException,\n\tDuration,\n\tRequestRate,\n\tLimiter,\n\tMemoryListBucket,\n\tMemoryQueueBucket,\n)\n```\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\t  request-rate history, ie `Redis`. This lib provides a ready-use `RedisBucket` to handle such case, and required\n\t  `redis-py` as its peer-dependency. The usage difference is when using Redis, a naming `prefix` must be provide so\n\t  the keys can be distinct for each item\'s identity.\n\n``` python\nfrom redis import ConnectionPool\n\npool = ConnectionPool.from_url(\'redis://localhost:6379\')\n\nrate = RequestRate(3, 5 * Duration.SECOND)\n\nbucket_kwargs = {\n\t"redis_pool": redis_pool,\n\t"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)\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\tfor _ in range(4):\n\t\tlimiter.try_acquire(item)\n\t\tsleep(1)\nexcept BucketFullException as err:\n\thas_raised = True\n\tassert str(err)\n\t# Bucket for vutran with Rate 3/5 is already full\n\tassert isinstance(err.meta_info, dict)\n\t# {\'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\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## 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,
    'entry_points': entry_points,
    'python_requires': '>=3.6,<4.0',
}


setup(**setup_kwargs)
