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

packages = \
['pyrate_limiter']

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

extras_require = \
{'all': ['filelock>=3.0', 'redis>=3.3,<4.0', 'redis-py-cluster>=2.1.3,<3.0.0'],
 'docs': ['furo>=2022.3.4,<2023.0.0',
          'myst-parser>=0.17',
          'sphinx>=4.3.0,<5.0.0',
          'sphinx-autodoc-typehints>=1.17,<2.0',
          'sphinx-copybutton>=0.5',
          'sphinxcontrib-apidoc>=0.3,<0.4']}

setup_kwargs = {
    'name': 'pyrate-limiter',
    'version': '2.8.4',
    'description': 'Python Rate-Limiter using Leaky-Bucket Algorithm',
    'long_description': '<img align="left" width="95" height="120" src="docs/_static/logo.png">\n\n# PyrateLimiter\nThe request rate limiter using Leaky-bucket algorithm.\n\nFull project documentation can be found at [pyratelimiter.readthedocs.io](https://pyratelimiter.readthedocs.io).\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## Contents\n- [PyrateLimiter](#pyratelimiter)\n  - [Contents](#contents)\n  - [Features](#features)\n  - [Installation](#installation)\n  - [Basic usage](#basic-usage)\n    - [Defining rate limits](#defining-rate-limits)\n    - [Applying rate limits](#applying-rate-limits)\n    - [Identities](#identities)\n  - [Handling exceeded limits](#handling-exceeded-limits)\n    - [Bucket analogy](#bucket-analogy)\n    - [Rate limit exceptions](#rate-limit-exceptions)\n    - [Rate limit delays](#rate-limit-delays)\n  - [Additional usage options](#additional-usage-options)\n    - [Decorator](#decorator)\n    - [Contextmanager](#contextmanager)\n    - [Async decorator/contextmanager](#async-decoratorcontextmanager)\n  - [Backends](#backends)\n    - [Memory](#memory)\n    - [SQLite](#sqlite)\n    - [Redis](#redis)\n    - [Custom backends](#custom-backends)\n  - [Additional features](#additional-features)\n    - [Time sources](#time-sources)\n  - [Examples](#examples)\n\n## Features\n* Tracks any number of rate limits and intervals you want to define\n* Independently tracks rate limits for multiple services or resources\n* Handles exceeded rate limits by either raising errors or adding delays\n* Several usage options including a normal function call, a decorator, or a contextmanager\n* Async support\n* Includes optional SQLite and Redis backends, which can be used to persist limit tracking across\n  multiple threads, processes, or application restarts\n\n## Installation\nInstall using pip:\n```\npip install pyrate-limiter\n```\n\nOr using conda:\n```\nconda install --channel conda-forge pyrate-limiter\n```\n\n## Basic usage\n\n### Defining rate limits\nConsider some public API (like LinkedIn, GitHub, etc.) that has rate limits like the following:\n```\n- 500 requests per hour\n- 1000 requests per day\n- 10000 requests per month\n```\n\nYou can define these rates using the `RequestRate` class, and add them to a `Limiter`:\n``` python\nfrom pyrate_limiter import Duration, RequestRate, Limiter\n\nhourly_rate = RequestRate(500, Duration.HOUR) # 500 requests per hour\ndaily_rate = RequestRate(1000, Duration.DAY) # 1000 requests per day\nmonthly_rate = RequestRate(10000, Duration.MONTH) # 10000 requests per month\n\nlimiter = Limiter(hourly_rate, daily_rate, monthly_rate)\n```\n\nor\n\n``` python\nfrom pyrate_limiter import Duration, RequestRate, Limiter\n\nrate_limits = (\n      RequestRate(500, Duration.HOUR), # 500 requests per hour\n      RequestRate(1000, Duration.DAY), # 1000 requests per day\n      RequestRate(10000, Duration.MONTH), # 10000 requests per month\n)\n\nlimiter = Limiter(*rate_limits)\n```\n\nNote that these rates need to be ordered by interval length; in other words, an hourly rate must\ncome before a daily rate, etc.\n\n### Applying rate limits\nThen, use `Limiter.try_acquire()` wherever you are making requests (or other rate-limited operations).\nThis will raise an exception if the rate limit is exceeded.\n\n```python\nimport requests\n\ndef request_function():\n    limiter.try_acquire(\'identity\')\n    requests.get(\'https://example.com\')\n\nwhile True:\n    request_function()\n```\n\nAlternatively, you can use `Limiter.ratelimit()` as a function decorator:\n```python\n@limiter.ratelimit(\'identity\')\ndef request_function():\n    requests.get(\'https://example.com\')\n```\nSee [Additional usage options](#additional-usage-options) below for more details.\n\n### Identities\nNote that both `try_acquire()` and `ratelimit()` take one or more `identity` arguments. Typically this is\nthe name of the service or resource that is being rate-limited. This allows you to track rate limits\nfor these resources independently. For example, if you have a service that is rate-limited by user:\n```python\ndef request_function(user_ids):\n    limiter.try_acquire(*user_ids)\n    for user_id in user_ids:\n        requests.get(f\'https://example.com?user_id={user_id}\')\n```\n\n## Handling exceeded limits\nWhen a rate limit is exceeded, you have two options: raise an exception, or add delays.\n\n### Bucket analogy\n<img height="300" align="right" src="https://upload.wikimedia.org/wikipedia/commons/c/c4/Leaky_bucket_analogy.JPG">\n\nAt this point it\'s useful to introduce the analogy of "buckets" used for rate-limiting. Here is a\nquick summary:\n\n* This library implements the [Leaky Bucket algorithm](https://en.wikipedia.org/wiki/Leaky_bucket).\n* It is named after the idea of representing some kind of fixed capacity -- like a network or service -- as a bucket.\n* The bucket "leaks" at a constant rate. For web services, this represents the **ideal or permitted request rate**.\n* The bucket is "filled" at an intermittent, unpredicatble rate, representing the **actual rate of requests**.\n* When the bucket is "full", it will overflow, representing **canceled or delayed requests**.\n\n### Rate limit exceptions\nBy default, a `BucketFullException` will be raised when a rate limit is exceeded.\nThe error contains a `meta_info` attribute with the following information:\n* `identity`: The identity it received\n* `rate`: The specific rate that has been exceeded\n* `remaining_time`: The remaining time until the next request can be sent\n\nHere\'s an example that will raise an exception on the 4th request:\n```python\nfrom pyrate_limiter import (Duration, RequestRate,\n                            Limiter, BucketFullException)\n\nrate = RequestRate(3, Duration.SECOND)\nlimiter = Limiter(rate)\n\nfor _ in range(4):\n    try:\n        limiter.try_acquire(\'vutran\')\n    except BucketFullException as err:\n        print(err)\n        # Output: Bucket for vutran with Rate 3/1 is already full\n        print(err.meta_info)\n        # Output: {\'identity\': \'vutran\', \'rate\': \'3/1\', \'remaining_time\': 2.9,\n        #          \'error\': \'Bucket for vutran with Rate 3/1 is already full\'}\n```\n\nThe rate part of the output is constructed as: `limit / interval`. On the above example, the limit\nis 3 and the interval is 1, hence the `Rate 3/1`.\n\n### Rate limit delays\nYou may want to simply slow down your requests to stay within the rate limits instead of canceling\nthem. In that case you can use the `delay` argument. Note that this is only available for\n`Limiter.ratelimit()`:\n```python\n@limiter.ratelimit(\'identity\', delay=True)\ndef my_function():\n    do_stuff()\n```\n\nIf you exceed a rate limit with a long interval (daily, monthly, etc.), you may not want to delay\nthat long. In this case, you can set a `max_delay` (in seconds) that you are willing to wait in\nbetween calls:\n```python\n@limiter.ratelimit(\'identity\', delay=True, max_delay=360)\ndef my_function():\n    do_stuff()\n```\nIn this case, calls may be delayed by at most 360 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## Additional usage options\nBesides `Limiter.try_acquire()`, some additional usage options are available using `Limiter.ratelimit()`:\n### Decorator\n`Limiter.ratelimit()` can be used as a decorator:\n```python\n@limiter.ratelimit(\'identity\')\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### Contextmanager\n`Limiter.ratelimit()` also works as a contextmanager:\n\n```python\ndef my_function():\n    with limiter.ratelimit(\'identity\', delay=True):\n        do_stuff()\n```\n\n### Async decorator/contextmanager\n`Limiter.ratelimit()` also support async functions, either as a decorator or contextmanager:\n```python\n@limiter.ratelimit(\'identity\', delay=True)\nasync def my_function():\n    await do_stuff()\n\nasync def my_function():\n    async with limiter.ratelimit(\'identity\'):\n        await do_stuff()\n```\n\nWhen delays are enabled for an async function, `asyncio.sleep()` will be used instead of `time.sleep()`.\n\n## 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(bucket_class=SQLiteBucket)\n```\n\nBy default, the database will be stored in the system temp directory. You can specify a different\npath via `bucket_kwargs`:\n```python\nlimiter = Limiter(\n    bucket_class=SQLiteBucket,\n    bucket_kwargs={\'path\': \'/path/to/db.sqlite\'},\n)\n```\n\n#### Concurrency\nThis backend is thread-safe, and may also be used with multiple child processes that share the same\n`Limiter` object, e.g. if created with `ProcessPoolExecutor` or `multiprocessing.Process`.\n\nIf you want to use SQLite with multiple processes with no shared state, for example if created by\nrunning multiple scripts or by an external process, some additional protections are needed. For\nthese cases, a separate `FileLockSQLiteBucket` class is available. This requires installing the\n[py-filelock](https://py-filelock.readthedocs.io) library.\n```python\nlimiter = Limiter(bucket_class=FileLockSQLiteBucket)\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\nNote that this backend requires a `bucket_name` argument, which will be used as a prefix for the\nRedis keys created. This can be used to disambiguate between multiple services using the same Redis\ninstance with pyrate-limiter.\n\n**Important**: you might want to consider adding `expire_time` for each buckets. In a scenario where some `identity` produces a request rate that is too sparsed, it is a good practice to expire the bucket which holds such identity\'s info to save memory.\n\n```python\nfrom pyrate_limiter import Limiter, RedisBucket, Duration, RequestRate\n\nrates = [\n    RequestRate(5, 10 * Duration.SECOND),\n    RequestRate(8, 20 * Duration.SECOND),\n]\n\nlimiter = Limiter(\n    *rates\n    bucket_class=RedisBucket,\n    bucket_kwargs={\n        \'bucket_name\':\n        \'my_service\',\n        \'expire_time\': rates[-1].interval,\n    },\n)\n\n```\n\n#### Connection settings\nIf you need to pass additional connection settings, you can use the `redis_pool` bucket argument:\n```python\nfrom redis import ConnectionPool\n\nredis_pool = ConnectionPool(host=\'localhost\', port=6379, db=0)\n\nrate = RequestRate(5, 10 * Duration.SECOND)\n\nlimiter = Limiter(\n    rate,\n    bucket_class=RedisBucket,\n    bucket_kwargs={\'redis_pool\': redis_pool, \'bucket_name\': \'my_service\'},\n)\n```\n\n#### Redis clusters\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\n## Additional features\n\n### Time sources\nBy default, monotonic time is used, to ensure requests are always logged in the correct order.\n\nYou can specify a custom time source with the `time_function` argument. For example, you may want to\nuse the current UTC time for consistency across a distributed application using a Redis backend.\n```python\nfrom datetime import datetime\nfrom pyrate_limiter import Duration, Limiter, RequestRate\n\nrate = RequestRate(5, Duration.SECOND)\nlimiter_datetime = Limiter(rate, time_function=lambda: datetime.utcnow().timestamp())\n```\n\nOr simply use the basic `time.time()` function:\n```python\nfrom time import time\n\nrate = RequestRate(5, Duration.SECOND)\nlimiter_time = Limiter(rate, time_function=time)\n```\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)\n\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',
    'author': 'vutr',
    'author_email': 'me@vutr.io',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'https://github.com/vutran1710/PyrateLimiter',
    'packages': packages,
    'package_data': package_data,
    'extras_require': extras_require,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
