Update 2025-04-24_11:44:19
This commit is contained in:
@ -0,0 +1,279 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: limits
|
||||
Version: 5.0.0
|
||||
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: 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: memcachio>=0.3; extra == "async-memcached"
|
||||
Provides-Extra: async-mongodb
|
||||
Requires-Dist: motor<4,>=3; extra == "async-mongodb"
|
||||
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: valkey>=6; extra == "all"
|
||||
Requires-Dist: coredis<5,>=3.4.0; extra == "all"
|
||||
Requires-Dist: memcachio>=0.3; extra == "all"
|
||||
Requires-Dist: motor<4,>=3; 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).
|
||||
|
||||
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>`_
|
||||
- `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>`_
|
||||
* `Benchmarks <http://limits.readthedocs.org/en/latest/performance.html>`_
|
||||
* `Changelog <http://limits.readthedocs.org/en/stable/changelog.html>`_
|
||||
|
Reference in New Issue
Block a user