| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- Metadata-Version: 2.4
- Name: limits
- Version: 4.6
- Summary: Rate limiting utilities
- Home-page: https://limits.readthedocs.org
- Author: Ali-Akber Saifee
- Author-email: ali@indydevs.org
- License: MIT
- Project-URL: Source, https://github.com/alisaifee/limits
- Classifier: Development Status :: 5 - Production/Stable
- Classifier: Intended Audience :: Developers
- Classifier: License :: OSI Approved :: MIT License
- Classifier: Operating System :: MacOS
- Classifier: Operating System :: POSIX :: Linux
- Classifier: Operating System :: OS Independent
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
- Classifier: Programming Language :: Python :: 3.10
- Classifier: Programming Language :: Python :: 3.11
- Classifier: Programming Language :: Python :: 3.12
- Classifier: Programming Language :: Python :: 3.13
- Classifier: Programming Language :: Python :: Implementation :: PyPy
- Requires-Python: >=3.10
- License-File: LICENSE.txt
- Requires-Dist: deprecated>=1.2
- Requires-Dist: packaging<25,>=21
- Requires-Dist: typing_extensions
- Provides-Extra: redis
- Requires-Dist: redis!=4.5.2,!=4.5.3,<6.0.0,>3; extra == "redis"
- Provides-Extra: rediscluster
- Requires-Dist: redis!=4.5.2,!=4.5.3,>=4.2.0; extra == "rediscluster"
- Provides-Extra: memcached
- Requires-Dist: pymemcache<5.0.0,>3; extra == "memcached"
- Provides-Extra: mongodb
- Requires-Dist: pymongo<5,>4.1; extra == "mongodb"
- Provides-Extra: etcd
- Requires-Dist: etcd3; extra == "etcd"
- Provides-Extra: valkey
- Requires-Dist: valkey>=6; extra == "valkey"
- Provides-Extra: async-redis
- Requires-Dist: coredis<5,>=3.4.0; extra == "async-redis"
- Provides-Extra: async-memcached
- Requires-Dist: emcache>=0.6.1; python_version < "3.11" and extra == "async-memcached"
- Requires-Dist: emcache>=1; (python_version >= "3.11" and python_version < "3.13.0") and extra == "async-memcached"
- Provides-Extra: async-mongodb
- Requires-Dist: motor<4,>=3; extra == "async-mongodb"
- Provides-Extra: async-etcd
- Requires-Dist: aetcd; extra == "async-etcd"
- Provides-Extra: async-valkey
- Requires-Dist: valkey>=6; extra == "async-valkey"
- Provides-Extra: all
- Requires-Dist: redis!=4.5.2,!=4.5.3,<6.0.0,>3; extra == "all"
- Requires-Dist: redis!=4.5.2,!=4.5.3,>=4.2.0; extra == "all"
- Requires-Dist: pymemcache<5.0.0,>3; extra == "all"
- Requires-Dist: pymongo<5,>4.1; extra == "all"
- Requires-Dist: etcd3; extra == "all"
- Requires-Dist: valkey>=6; extra == "all"
- Requires-Dist: coredis<5,>=3.4.0; extra == "all"
- Requires-Dist: emcache>=0.6.1; python_version < "3.11" and extra == "all"
- Requires-Dist: emcache>=1; (python_version >= "3.11" and python_version < "3.13.0") and extra == "all"
- Requires-Dist: motor<4,>=3; extra == "all"
- Requires-Dist: aetcd; extra == "all"
- Requires-Dist: valkey>=6; extra == "all"
- Dynamic: author
- Dynamic: author-email
- Dynamic: classifier
- Dynamic: description
- Dynamic: home-page
- Dynamic: license
- Dynamic: license-file
- Dynamic: project-url
- Dynamic: provides-extra
- Dynamic: requires-dist
- Dynamic: requires-python
- Dynamic: summary
- .. |ci| image:: https://github.com/alisaifee/limits/actions/workflows/main.yml/badge.svg?branch=master
- :target: https://github.com/alisaifee/limits/actions?query=branch%3Amaster+workflow%3ACI
- .. |codecov| image:: https://codecov.io/gh/alisaifee/limits/branch/master/graph/badge.svg
- :target: https://codecov.io/gh/alisaifee/limits
- .. |pypi| image:: https://img.shields.io/pypi/v/limits.svg?style=flat-square
- :target: https://pypi.python.org/pypi/limits
- .. |pypi-versions| image:: https://img.shields.io/pypi/pyversions/limits?style=flat-square
- :target: https://pypi.python.org/pypi/limits
- .. |license| image:: https://img.shields.io/pypi/l/limits.svg?style=flat-square
- :target: https://pypi.python.org/pypi/limits
- .. |docs| image:: https://readthedocs.org/projects/limits/badge/?version=latest
- :target: https://limits.readthedocs.org
- limits
- ------
- |docs| |ci| |codecov| |pypi| |pypi-versions| |license|
- **limits** is a python library for rate limiting via multiple strategies
- with commonly used storage backends (Redis, Memcached, MongoDB & Etcd).
- The library provides identical APIs for use in sync and
- `async <https://limits.readthedocs.io/en/stable/async.html>`_ codebases.
- Supported Strategies
- ====================
- All strategies support the follow methods:
- - `hit <https://limits.readthedocs.io/en/stable/api.html#limits.strategies.RateLimiter.hit>`_: consume a request.
- - `test <https://limits.readthedocs.io/en/stable/api.html#limits.strategies.RateLimiter.test>`_: check if a request is allowed.
- - `get_window_stats <https://limits.readthedocs.io/en/stable/api.html#limits.strategies.RateLimiter.get_window_stats>`_: retrieve remaining quota and reset time.
- Fixed Window
- ------------
- `Fixed Window <https://limits.readthedocs.io/en/latest/strategies.html#fixed-window>`_
- This strategy is the most memory‑efficient because it uses a single counter per resource and
- rate limit. When the first request arrives, a window is started for a fixed duration
- (e.g., for a rate limit of 10 requests per minute the window expires in 60 seconds from the first request).
- All requests in that window increment the counter and when the window expires, the counter resets.
- Burst traffic that bypasses the rate limit may occur at window boundaries.
- For example, with a rate limit of 10 requests per minute:
- - At **00:00:45**, the first request arrives, starting a window from **00:00:45** to **00:01:45**.
- - All requests between **00:00:45** and **00:01:45** count toward the limit.
- - If 10 requests occur at any time in that window, any further request before **00:01:45** is rejected.
- - At **00:01:45**, the counter resets and a new window starts which would allow 10 requests
- until **00:02:45**.
- Moving Window
- -------------
- `Moving Window <https://limits.readthedocs.io/en/latest/strategies.html#moving-window>`_
- This strategy adds each request’s timestamp to a log if the ``nth`` oldest entry (where ``n``
- is the limit) is either not present or is older than the duration of the window (for example with a rate limit of
- ``10 requests per minute`` if there are either less than 10 entries or the 10th oldest entry is at least
- 60 seconds old). Upon adding a new entry to the log "expired" entries are truncated.
- For example, with a rate limit of 10 requests per minute:
- - At **00:00:10**, a client sends 1 requests which are allowed.
- - At **00:00:20**, a client sends 2 requests which are allowed.
- - At **00:00:30**, the client sends 4 requests which are allowed.
- - At **00:00:50**, the client sends 3 requests which are allowed (total = 10).
- - At **00:01:11**, the client sends 1 request. The strategy checks the timestamp of the
- 10th oldest entry (**00:00:10**) which is now 61 seconds old and thus expired. The request
- is allowed.
- - At **00:01:12**, the client sends 1 request. The 10th oldest entry's timestamp is **00:00:20**
- which is only 52 seconds old. The request is rejected.
- Sliding Window Counter
- ------------------------
- `Sliding Window Counter <https://limits.readthedocs.io/en/latest/strategies.html#sliding-window-counter>`_
- This strategy approximates the moving window while using less memory by maintaining
- two counters:
- - **Current bucket:** counts requests in the ongoing period.
- - **Previous bucket:** counts requests in the immediately preceding period.
- When a request arrives, the effective request count is calculated as::
- weighted_count = current_count + floor(previous_count * weight)
- The weight is based on how much time has elapsed in the current bucket::
- weight = (bucket_duration - elapsed_time) / bucket_duration
- If ``weighted_count`` is below the limit, the request is allowed.
- For example, with a rate limit of 10 requests per minute:
- Assume:
- - The current bucket (spanning **00:01:00** to **00:02:00**) has 8 hits.
- - The previous bucket (spanning **00:00:00** to **00:01:00**) has 4 hits.
- Scenario 1:
- - A new request arrives at **00:01:30**, 30 seconds into the current bucket.
- - ``weight = (60 - 30) / 60 = 0.5``.
- - ``weighted_count = floor(8 + (4 * 0.5)) = floor(8 + 2) = 10``.
- - Since the weighted count equals the limit, the request is rejected.
- Scenario 2:
- - A new request arrives at **00:01:40**, 40 seconds into the current bucket.
- - ``weight = (60 - 40) / 60 ≈ 0.33``.
- - ``weighted_count = floor(8 + (4 * 0.33)) = floor(8 + 1.32) = 9``.
- - Since the weighted count is below the limit, the request is allowed.
- Storage backends
- ================
- - `Redis <https://limits.readthedocs.io/en/latest/storage.html#redis-storage>`_
- - `Memcached <https://limits.readthedocs.io/en/latest/storage.html#memcached-storage>`_
- - `MongoDB <https://limits.readthedocs.io/en/latest/storage.html#mongodb-storage>`_
- - `Etcd <https://limits.readthedocs.io/en/latest/storage.html#etcd-storage>`_
- - `In-Memory <https://limits.readthedocs.io/en/latest/storage.html#in-memory-storage>`_
- Dive right in
- =============
- Initialize the storage backend
- .. code-block:: python
- from limits import storage
- backend = storage.MemoryStorage()
- # or memcached
- backend = storage.MemcachedStorage("memcached://localhost:11211")
- # or redis
- backend = storage.RedisStorage("redis://localhost:6379")
- # or mongodb
- backend = storage.MongoDbStorage("mongodb://localhost:27017")
- # or use the factory
- storage_uri = "memcached://localhost:11211"
- backend = storage.storage_from_string(storage_uri)
- Initialize a rate limiter with a strategy
- .. code-block:: python
- from limits import strategies
- strategy = strategies.MovingWindowRateLimiter(backend)
- # or fixed window
- strategy = strategies.FixedWindowRateLimiter(backend)
- # or sliding window
- strategy = strategies.SlidingWindowCounterRateLimiter(backend)
- Initialize a rate limit
- .. code-block:: python
- from limits import parse
- one_per_minute = parse("1/minute")
- Initialize a rate limit explicitly
- .. code-block:: python
- from limits import RateLimitItemPerSecond
- one_per_second = RateLimitItemPerSecond(1, 1)
- Test the limits
- .. code-block:: python
- import time
- assert True == strategy.hit(one_per_minute, "test_namespace", "foo")
- assert False == strategy.hit(one_per_minute, "test_namespace", "foo")
- assert True == strategy.hit(one_per_minute, "test_namespace", "bar")
- assert True == strategy.hit(one_per_second, "test_namespace", "foo")
- assert False == strategy.hit(one_per_second, "test_namespace", "foo")
- time.sleep(1)
- assert True == strategy.hit(one_per_second, "test_namespace", "foo")
- Check specific limits without hitting them
- .. code-block:: python
- assert True == strategy.hit(one_per_second, "test_namespace", "foo")
- while not strategy.test(one_per_second, "test_namespace", "foo"):
- time.sleep(0.01)
- assert True == strategy.hit(one_per_second, "test_namespace", "foo")
- Query available capacity and reset time for a limit
- .. code-block:: python
- assert True == strategy.hit(one_per_minute, "test_namespace", "foo")
- window = strategy.get_window_stats(one_per_minute, "test_namespace", "foo")
- assert window.remaining == 0
- assert False == strategy.hit(one_per_minute, "test_namespace", "foo")
- time.sleep(window.reset_time - time.time())
- assert True == strategy.hit(one_per_minute, "test_namespace", "foo")
- Links
- =====
- * `Documentation <http://limits.readthedocs.org/en/latest>`_
- * `Changelog <http://limits.readthedocs.org/en/stable/changelog.html>`_
|