Update 2025-04-24_11:44:19

This commit is contained in:
oib
2025-04-24 11:44:23 +02:00
commit e748c737f4
3408 changed files with 717481 additions and 0 deletions

View File

@ -0,0 +1 @@
pip

View File

@ -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 memoryefficient 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 requests 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>`_

View File

@ -0,0 +1,77 @@
limits-5.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
limits-5.0.0.dist-info/METADATA,sha256=7BekivDD_wa-dZNTl9L7JLcXs0Dxp_ta5_XWsC9nxQY,10900
limits-5.0.0.dist-info/RECORD,,
limits-5.0.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
limits-5.0.0.dist-info/licenses/LICENSE.txt,sha256=T6i7kq7F5gIPfcno9FCxU5Hcwm22Bjq0uHZV3ElcjsQ,1061
limits-5.0.0.dist-info/top_level.txt,sha256=C7g5ahldPoU2s6iWTaJayUrbGmPK1d6e9t5Nn0vQ2jM,7
limits/__init__.py,sha256=gPUFrt02kHF_syLjiVRSs-S4UVGpRMcM2VMFNhF6G24,748
limits/__pycache__/__init__.cpython-311.pyc,,
limits/__pycache__/_version.cpython-311.pyc,,
limits/__pycache__/errors.cpython-311.pyc,,
limits/__pycache__/limits.cpython-311.pyc,,
limits/__pycache__/strategies.cpython-311.pyc,,
limits/__pycache__/typing.cpython-311.pyc,,
limits/__pycache__/util.cpython-311.pyc,,
limits/__pycache__/version.cpython-311.pyc,,
limits/_version.py,sha256=7_DQRCmIEsWJeuCEDoA9jxz1DI-qiHiKrasIqMZLnk0,497
limits/aio/__init__.py,sha256=yxvWb_ZmV245Hg2LqD365WC5IDllcGDMw6udJ1jNp1g,118
limits/aio/__pycache__/__init__.cpython-311.pyc,,
limits/aio/__pycache__/strategies.cpython-311.pyc,,
limits/aio/storage/__init__.py,sha256=vKeArUnN1ld_0mQOBBZPCjaQgM5xI1GBPM7_F2Ydz5c,646
limits/aio/storage/__pycache__/__init__.cpython-311.pyc,,
limits/aio/storage/__pycache__/base.cpython-311.pyc,,
limits/aio/storage/__pycache__/memory.cpython-311.pyc,,
limits/aio/storage/__pycache__/mongodb.cpython-311.pyc,,
limits/aio/storage/base.py,sha256=VfHpL9Z3RL76eKhoaSQKLKQsqcF5B2bnF6gfa-8ltWA,6296
limits/aio/storage/memcached/__init__.py,sha256=VMWsH4XpaPswtPV7cQmsfckhVRbOOrKvoUPYnGt5MRY,6611
limits/aio/storage/memcached/__pycache__/__init__.cpython-311.pyc,,
limits/aio/storage/memcached/__pycache__/bridge.cpython-311.pyc,,
limits/aio/storage/memcached/__pycache__/emcache.cpython-311.pyc,,
limits/aio/storage/memcached/__pycache__/memcachio.cpython-311.pyc,,
limits/aio/storage/memcached/bridge.py,sha256=3CEruS6LvZWDQPGPLlwY4hemy6oN0WWduUE7t8vyXBI,2017
limits/aio/storage/memcached/emcache.py,sha256=J01jP-Udd2fLgamCh2CX9NEIvhN8eZVTzUok096Bbe4,3833
limits/aio/storage/memcached/memcachio.py,sha256=OoGVqOVG0pVX2McFeTGQ_AbiqQUu_FYwWItpQMtNV7g,3491
limits/aio/storage/memory.py,sha256=sWrDzOe-6Opy9uFmfP1S38IbN2_wNCBaIHTS4UTRy6g,9562
limits/aio/storage/mongodb.py,sha256=tIMfQrseONRMR2nuRmPO7ocp8dTCABfqBICS_kgp550,19141
limits/aio/storage/redis/__init__.py,sha256=lwoKk91YLEBlZ3W6hCnQ1e7Gc6LxpvSzZZW16saCyR4,14143
limits/aio/storage/redis/__pycache__/__init__.cpython-311.pyc,,
limits/aio/storage/redis/__pycache__/bridge.cpython-311.pyc,,
limits/aio/storage/redis/__pycache__/coredis.cpython-311.pyc,,
limits/aio/storage/redis/__pycache__/redispy.cpython-311.pyc,,
limits/aio/storage/redis/__pycache__/valkey.cpython-311.pyc,,
limits/aio/storage/redis/bridge.py,sha256=eoRi9h2bSy194cVwoKgRYQV1HQ7SvwarL-4LeazrxeA,3145
limits/aio/storage/redis/coredis.py,sha256=IzfEyXBvQbr4QUWML9xAd87a2aHCvglOBEjAg-Vq4z0,7420
limits/aio/storage/redis/redispy.py,sha256=HS1H6E9g0dP3G-8tSUILIFoc8JWpeRQOiBxcpL3I0gM,8310
limits/aio/storage/redis/valkey.py,sha256=f_-HPZhzNspywGybMNIL0F5uDZk76v8_K9wuC5ZeKhc,248
limits/aio/strategies.py,sha256=RzZExH2r6jnHra4SpDHqtZCC0Bo3085zUJYo2boAj6Y,9897
limits/errors.py,sha256=s1el9Vg0ly-z92guvnvYNgKi3_aVqpiw_sufemiLLTI,662
limits/limits.py,sha256=YzzZP8_ay_zlMMnnY2xhAcFTTFvFe5HEk8NQlvUTru4,4907
limits/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
limits/resources/redis/lua_scripts/acquire_moving_window.lua,sha256=Vz0HkI_bSFLW668lEVw8paKlTLEuU4jZk1fpdSuz3zg,594
limits/resources/redis/lua_scripts/acquire_sliding_window.lua,sha256=OhVI1MAN_gT92P6r-2CEmvy1yvQVjYCCZxWIxfXYceY,1329
limits/resources/redis/lua_scripts/clear_keys.lua,sha256=zU0cVfLGmapRQF9x9u0GclapM_IB2pJLszNzVQ1QRK4,184
limits/resources/redis/lua_scripts/incr_expire.lua,sha256=Uq9NcrrcDI-F87TDAJexoSJn2SDgeXIUEYozCp9S3oA,195
limits/resources/redis/lua_scripts/moving_window.lua,sha256=zlieQwfET0BC7sxpfiOuzPa1wwmrwWLy7IF8LxNa_Lw,717
limits/resources/redis/lua_scripts/sliding_window.lua,sha256=qG3Yg30Dq54QpRUcR9AOrKQ5bdJiaYpCacTm6Kxblvc,713
limits/storage/__init__.py,sha256=9iNxIlwzLQw2d54EcMa2LBJ47wiWCPOnHgn6ddqKkDI,2652
limits/storage/__pycache__/__init__.cpython-311.pyc,,
limits/storage/__pycache__/base.cpython-311.pyc,,
limits/storage/__pycache__/memcached.cpython-311.pyc,,
limits/storage/__pycache__/memory.cpython-311.pyc,,
limits/storage/__pycache__/mongodb.cpython-311.pyc,,
limits/storage/__pycache__/redis.cpython-311.pyc,,
limits/storage/__pycache__/redis_cluster.cpython-311.pyc,,
limits/storage/__pycache__/redis_sentinel.cpython-311.pyc,,
limits/storage/__pycache__/registry.cpython-311.pyc,,
limits/storage/base.py,sha256=IdOL_iqR9KhaJO73M_h9c6OYe8Ox632pxx5uXaL9Dbo,6860
limits/storage/memcached.py,sha256=5GUKGWS_BYTwUss2WmOlCwBtOieGT7AFUcpX65WYXdQ,10217
limits/storage/memory.py,sha256=rVlsirSp9LDhuqNFp6KMLR85fJc9xwrU58IHIVz6eq4,8719
limits/storage/mongodb.py,sha256=V4Ib_AwPFX6JpNI7oUUGJx_3MxD8EmYAi4Q6QcWnQ5U,18071
limits/storage/redis.py,sha256=i_6qh4S6JQd-lG6eRJdTPxNnZIAkm4G0cA0mfow9OOk,10389
limits/storage/redis_cluster.py,sha256=z6aONMl4p1AY78G3J0BbtK--uztz88krwnpiOsU61BM,4447
limits/storage/redis_sentinel.py,sha256=AN0WtwHN88TvXk0C2uUE8l5Jhsd1ZxU8XSqrEyQSR20,4327
limits/storage/registry.py,sha256=CxSaDBGR5aBJPFAIsfX9axCnbcThN3Bu-EH4wHrXtu8,650
limits/strategies.py,sha256=LeZ6lnE73EIQqQ8TfKaTzlxNvBMrZOOSXFB0l8D17fI,9946
limits/typing.py,sha256=pVt5D23MhQSUGqi0MBG5FCSqDwta2ygu18BpKvJFxow,3283
limits/util.py,sha256=nk5QYvezFuXPq1OTEj04RrZFSWIH-khT0e_Dim6zGCw,6002
limits/version.py,sha256=YwkF3dtq1KGzvmL3iVGctA8NNtGlK_0arrzZkZGVjUs,47

View File

@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: setuptools (78.1.0)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -0,0 +1,20 @@
Copyright (c) 2023 Ali-Akber Saifee
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1 @@
limits