limits 4.0.0__py3-none-any.whl → 4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- limits/_version.py +3 -3
- limits/aio/storage/__init__.py +2 -1
- limits/aio/storage/base.py +70 -24
- limits/aio/storage/etcd.py +6 -2
- limits/aio/storage/memcached.py +157 -33
- limits/aio/storage/memory.py +98 -13
- limits/aio/storage/mongodb.py +233 -19
- limits/aio/storage/redis.py +100 -15
- limits/aio/strategies.py +122 -1
- limits/limits.py +10 -11
- limits/resources/redis/lua_scripts/acquire_sliding_window.lua +45 -0
- limits/resources/redis/lua_scripts/sliding_window.lua +17 -0
- limits/storage/__init__.py +7 -6
- limits/storage/base.py +92 -24
- limits/storage/etcd.py +6 -2
- limits/storage/memcached.py +142 -36
- limits/storage/memory.py +97 -12
- limits/storage/mongodb.py +220 -20
- limits/storage/redis.py +159 -138
- limits/storage/redis_cluster.py +3 -3
- limits/storage/redis_sentinel.py +12 -35
- limits/storage/registry.py +3 -3
- limits/strategies.py +119 -5
- limits/typing.py +43 -15
- limits/util.py +27 -18
- limits-4.1.dist-info/METADATA +268 -0
- limits-4.1.dist-info/RECORD +39 -0
- limits-4.0.0.dist-info/METADATA +0 -192
- limits-4.0.0.dist-info/RECORD +0 -37
- {limits-4.0.0.dist-info → limits-4.1.dist-info}/LICENSE.txt +0 -0
- {limits-4.0.0.dist-info → limits-4.1.dist-info}/WHEEL +0 -0
- {limits-4.0.0.dist-info → limits-4.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: limits
|
|
3
|
+
Version: 4.1
|
|
4
|
+
Summary: Rate limiting utilities
|
|
5
|
+
Home-page: https://limits.readthedocs.org
|
|
6
|
+
Author: Ali-Akber Saifee
|
|
7
|
+
Author-email: ali@indydevs.org
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Source, https://github.com/alisaifee/limits
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
License-File: LICENSE.txt
|
|
25
|
+
Requires-Dist: deprecated >=1.2
|
|
26
|
+
Requires-Dist: packaging <25,>=21
|
|
27
|
+
Requires-Dist: typing-extensions
|
|
28
|
+
Provides-Extra: all
|
|
29
|
+
Requires-Dist: redis !=4.5.2,!=4.5.3,<6.0.0,>3 ; extra == 'all'
|
|
30
|
+
Requires-Dist: redis !=4.5.2,!=4.5.3,>=4.2.0 ; extra == 'all'
|
|
31
|
+
Requires-Dist: pymemcache <5.0.0,>3 ; extra == 'all'
|
|
32
|
+
Requires-Dist: pymongo <5,>4.1 ; extra == 'all'
|
|
33
|
+
Requires-Dist: etcd3 ; extra == 'all'
|
|
34
|
+
Requires-Dist: coredis <5,>=3.4.0 ; extra == 'all'
|
|
35
|
+
Requires-Dist: motor <4,>=3 ; extra == 'all'
|
|
36
|
+
Requires-Dist: aetcd ; extra == 'all'
|
|
37
|
+
Requires-Dist: emcache >=0.6.1 ; (python_version < "3.11") and extra == 'all'
|
|
38
|
+
Requires-Dist: emcache >=1 ; (python_version >= "3.11" and python_version < "3.13.0") and extra == 'all'
|
|
39
|
+
Provides-Extra: async-etcd
|
|
40
|
+
Requires-Dist: aetcd ; extra == 'async-etcd'
|
|
41
|
+
Provides-Extra: async-memcached
|
|
42
|
+
Requires-Dist: emcache >=0.6.1 ; (python_version < "3.11") and extra == 'async-memcached'
|
|
43
|
+
Requires-Dist: emcache >=1 ; (python_version >= "3.11" and python_version < "3.13.0") and extra == 'async-memcached'
|
|
44
|
+
Provides-Extra: async-mongodb
|
|
45
|
+
Requires-Dist: motor <4,>=3 ; extra == 'async-mongodb'
|
|
46
|
+
Provides-Extra: async-redis
|
|
47
|
+
Requires-Dist: coredis <5,>=3.4.0 ; extra == 'async-redis'
|
|
48
|
+
Provides-Extra: etcd
|
|
49
|
+
Requires-Dist: etcd3 ; extra == 'etcd'
|
|
50
|
+
Provides-Extra: memcached
|
|
51
|
+
Requires-Dist: pymemcache <5.0.0,>3 ; extra == 'memcached'
|
|
52
|
+
Provides-Extra: mongodb
|
|
53
|
+
Requires-Dist: pymongo <5,>4.1 ; extra == 'mongodb'
|
|
54
|
+
Provides-Extra: redis
|
|
55
|
+
Requires-Dist: redis !=4.5.2,!=4.5.3,<6.0.0,>3 ; extra == 'redis'
|
|
56
|
+
Provides-Extra: rediscluster
|
|
57
|
+
Requires-Dist: redis !=4.5.2,!=4.5.3,>=4.2.0 ; extra == 'rediscluster'
|
|
58
|
+
|
|
59
|
+
.. |ci| image:: https://github.com/alisaifee/limits/actions/workflows/main.yml/badge.svg?branch=master
|
|
60
|
+
:target: https://github.com/alisaifee/limits/actions?query=branch%3Amaster+workflow%3ACI
|
|
61
|
+
.. |codecov| image:: https://codecov.io/gh/alisaifee/limits/branch/master/graph/badge.svg
|
|
62
|
+
:target: https://codecov.io/gh/alisaifee/limits
|
|
63
|
+
.. |pypi| image:: https://img.shields.io/pypi/v/limits.svg?style=flat-square
|
|
64
|
+
:target: https://pypi.python.org/pypi/limits
|
|
65
|
+
.. |pypi-versions| image:: https://img.shields.io/pypi/pyversions/limits?style=flat-square
|
|
66
|
+
:target: https://pypi.python.org/pypi/limits
|
|
67
|
+
.. |license| image:: https://img.shields.io/pypi/l/limits.svg?style=flat-square
|
|
68
|
+
:target: https://pypi.python.org/pypi/limits
|
|
69
|
+
.. |docs| image:: https://readthedocs.org/projects/limits/badge/?version=latest
|
|
70
|
+
:target: https://limits.readthedocs.org
|
|
71
|
+
|
|
72
|
+
limits
|
|
73
|
+
------
|
|
74
|
+
|docs| |ci| |codecov| |pypi| |pypi-versions| |license|
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
**limits** is a python library for rate limiting via multiple strategies
|
|
78
|
+
with commonly used storage backends (Redis, Memcached, MongoDB & Etcd).
|
|
79
|
+
|
|
80
|
+
The library provides identical APIs for use in sync and
|
|
81
|
+
`async <https://limits.readthedocs.io/en/stable/async.html>`_ codebases.
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
Supported Strategies
|
|
85
|
+
====================
|
|
86
|
+
|
|
87
|
+
All strategies support the follow methods:
|
|
88
|
+
|
|
89
|
+
- `hit <https://limits.readthedocs.io/en/stable/api.html#limits.strategies.RateLimiter.hit>`_: consume a request.
|
|
90
|
+
- `test <https://limits.readthedocs.io/en/stable/api.html#limits.strategies.RateLimiter.test>`_: check if a request is allowed.
|
|
91
|
+
- `get_window_stats <https://limits.readthedocs.io/en/stable/api.html#limits.strategies.RateLimiter.get_window_stats>`_: retrieve remaining quota and reset time.
|
|
92
|
+
|
|
93
|
+
Fixed Window
|
|
94
|
+
------------
|
|
95
|
+
`Fixed Window <https://limits.readthedocs.io/en/latest/strategies.html#fixed-window>`_
|
|
96
|
+
|
|
97
|
+
This strategy is the most memory‑efficient because it uses a single counter per resource and
|
|
98
|
+
rate limit. When the first request arrives, a window is started for a fixed duration
|
|
99
|
+
(e.g., for a rate limit of 10 requests per minute the window expires in 60 seconds from the first request).
|
|
100
|
+
All requests in that window increment the counter and when the window expires, the counter resets.
|
|
101
|
+
|
|
102
|
+
Burst traffic that bypasses the rate limit may occur at window boundaries.
|
|
103
|
+
|
|
104
|
+
For example, with a rate limit of 10 requests per minute:
|
|
105
|
+
|
|
106
|
+
- At **00:00:45**, the first request arrives, starting a window from **00:00:45** to **00:01:45**.
|
|
107
|
+
- All requests between **00:00:45** and **00:01:45** count toward the limit.
|
|
108
|
+
- If 10 requests occur at any time in that window, any further request before **00:01:45** is rejected.
|
|
109
|
+
- At **00:01:45**, the counter resets and a new window starts which would allow 10 requests
|
|
110
|
+
until **00:02:45**.
|
|
111
|
+
|
|
112
|
+
Moving Window
|
|
113
|
+
-------------
|
|
114
|
+
`Moving Window <https://limits.readthedocs.io/en/latest/strategies.html#moving-window>`_
|
|
115
|
+
|
|
116
|
+
This strategy adds each request’s timestamp to a log if the ``nth`` oldest entry (where ``n``
|
|
117
|
+
is the limit) is either not present or is older than the duration of the window (for example with a rate limit of
|
|
118
|
+
``10 requests per minute`` if there are either less than 10 entries or the 10th oldest entry is atleast
|
|
119
|
+
60 seconds old). Upon adding a new entry to the log "expired" entries are truncated.
|
|
120
|
+
|
|
121
|
+
For example, with a rate limit of 10 requests per minute:
|
|
122
|
+
|
|
123
|
+
- At **00:00:10**, a client sends 1 requests which are allowed.
|
|
124
|
+
- At **00:00:20**, a client sends 2 requests which are allowed.
|
|
125
|
+
- At **00:00:30**, the client sends 4 requests which are allowed.
|
|
126
|
+
- At **00:00:50**, the client sends 3 requests which are allowed (total = 10).
|
|
127
|
+
- At **00:01:11**, the client sends 1 request. The strategy checks the timestamp of the
|
|
128
|
+
10th oldest entry (**00:00:10**) which is now 61 seconds old and thus expired. The request
|
|
129
|
+
is allowed.
|
|
130
|
+
- At **00:01:12**, the client sends 1 request. The 10th oldest entry's timestamp is **00:00:20**
|
|
131
|
+
which is only 52 seconds old. The request is rejected.
|
|
132
|
+
|
|
133
|
+
Sliding Window Counter
|
|
134
|
+
------------------------
|
|
135
|
+
`Sliding Window Counter <https://limits.readthedocs.io/en/latest/strategies.html#sliding-window-counter>`_
|
|
136
|
+
|
|
137
|
+
This strategy approximates the moving window while using less memory by maintaining
|
|
138
|
+
two counters:
|
|
139
|
+
|
|
140
|
+
- **Current bucket:** counts requests in the ongoing period.
|
|
141
|
+
- **Previous bucket:** counts requests in the immediately preceding period.
|
|
142
|
+
|
|
143
|
+
When a request arrives, the effective request count is calculated as::
|
|
144
|
+
|
|
145
|
+
weighted_count = current_count + floor(previous_count * weight)
|
|
146
|
+
|
|
147
|
+
The weight is based on how much time has elapsed in the current bucket::
|
|
148
|
+
|
|
149
|
+
weight = (bucket_duration - elapsed_time) / bucket_duration
|
|
150
|
+
|
|
151
|
+
If ``weighted_count`` is below the limit, the request is allowed.
|
|
152
|
+
|
|
153
|
+
For example, with a rate limit of 10 requests per minute:
|
|
154
|
+
|
|
155
|
+
Assume:
|
|
156
|
+
|
|
157
|
+
- The current bucket (spanning **00:01:00** to **00:02:00**) has 8 hits.
|
|
158
|
+
- The previous bucket (spanning **00:00:00** to **00:01:00**) has 4 hits.
|
|
159
|
+
|
|
160
|
+
Scenario 1:
|
|
161
|
+
|
|
162
|
+
- A new request arrives at **00:01:30**, 30 seconds into the current bucket.
|
|
163
|
+
- ``weight = (60 - 30) / 60 = 0.5``.
|
|
164
|
+
- ``weighted_count = floor(8 + (4 * 0.5)) = floor(8 + 2) = 10``.
|
|
165
|
+
- Since the weighted count equals the limit, the request is rejected.
|
|
166
|
+
|
|
167
|
+
Scenario 2:
|
|
168
|
+
|
|
169
|
+
- A new request arrives at **00:01:40**, 40 seconds into the current bucket.
|
|
170
|
+
- ``weight = (60 - 40) / 60 ≈ 0.33``.
|
|
171
|
+
- ``weighted_count = floor(8 + (4 * 0.33)) = floor(8 + 1.32) = 9``.
|
|
172
|
+
- Since the weighted count is below the limit, the request is allowed.
|
|
173
|
+
|
|
174
|
+
Storage backends
|
|
175
|
+
================
|
|
176
|
+
|
|
177
|
+
- `Redis <https://limits.readthedocs.io/en/latest/storage.html#redis-storage>`_
|
|
178
|
+
- `Memcached <https://limits.readthedocs.io/en/latest/storage.html#memcached-storage>`_
|
|
179
|
+
- `MongoDB <https://limits.readthedocs.io/en/latest/storage.html#mongodb-storage>`_
|
|
180
|
+
- `Etcd <https://limits.readthedocs.io/en/latest/storage.html#etcd-storage>`_
|
|
181
|
+
- `In-Memory <https://limits.readthedocs.io/en/latest/storage.html#in-memory-storage>`_
|
|
182
|
+
|
|
183
|
+
Dive right in
|
|
184
|
+
=============
|
|
185
|
+
|
|
186
|
+
Initialize the storage backend
|
|
187
|
+
|
|
188
|
+
.. code-block:: python
|
|
189
|
+
|
|
190
|
+
from limits import storage
|
|
191
|
+
backend = storage.MemoryStorage()
|
|
192
|
+
# or memcached
|
|
193
|
+
backend = storage.MemcachedStorage("memcached://localhost:11211")
|
|
194
|
+
# or redis
|
|
195
|
+
backend = storage.RedisStorage("redis://localhost:6379")
|
|
196
|
+
# or mongodb
|
|
197
|
+
backend = storage.MongoDbStorage("mongodb://localhost:27017")
|
|
198
|
+
# or use the factory
|
|
199
|
+
storage_uri = "memcached://localhost:11211"
|
|
200
|
+
backend = storage.storage_from_string(storage_uri)
|
|
201
|
+
|
|
202
|
+
Initialize a rate limiter with a strategy
|
|
203
|
+
|
|
204
|
+
.. code-block:: python
|
|
205
|
+
|
|
206
|
+
from limits import strategies
|
|
207
|
+
strategy = strategies.MovingWindowRateLimiter(backend)
|
|
208
|
+
# or fixed window
|
|
209
|
+
strategy = strategies.FixedWindowRateLimiter(backend)
|
|
210
|
+
# or sliding window
|
|
211
|
+
strategy = strategies.SlidingWindowCounterRateLimiter(backend)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
Initialize a rate limit
|
|
215
|
+
|
|
216
|
+
.. code-block:: python
|
|
217
|
+
|
|
218
|
+
from limits import parse
|
|
219
|
+
one_per_minute = parse("1/minute")
|
|
220
|
+
|
|
221
|
+
Initialize a rate limit explicitly
|
|
222
|
+
|
|
223
|
+
.. code-block:: python
|
|
224
|
+
|
|
225
|
+
from limits import RateLimitItemPerSecond
|
|
226
|
+
one_per_second = RateLimitItemPerSecond(1, 1)
|
|
227
|
+
|
|
228
|
+
Test the limits
|
|
229
|
+
|
|
230
|
+
.. code-block:: python
|
|
231
|
+
|
|
232
|
+
import time
|
|
233
|
+
assert True == strategy.hit(one_per_minute, "test_namespace", "foo")
|
|
234
|
+
assert False == strategy.hit(one_per_minute, "test_namespace", "foo")
|
|
235
|
+
assert True == strategy.hit(one_per_minute, "test_namespace", "bar")
|
|
236
|
+
|
|
237
|
+
assert True == strategy.hit(one_per_second, "test_namespace", "foo")
|
|
238
|
+
assert False == strategy.hit(one_per_second, "test_namespace", "foo")
|
|
239
|
+
time.sleep(1)
|
|
240
|
+
assert True == strategy.hit(one_per_second, "test_namespace", "foo")
|
|
241
|
+
|
|
242
|
+
Check specific limits without hitting them
|
|
243
|
+
|
|
244
|
+
.. code-block:: python
|
|
245
|
+
|
|
246
|
+
assert True == strategy.hit(one_per_second, "test_namespace", "foo")
|
|
247
|
+
while not strategy.test(one_per_second, "test_namespace", "foo"):
|
|
248
|
+
time.sleep(0.01)
|
|
249
|
+
assert True == strategy.hit(one_per_second, "test_namespace", "foo")
|
|
250
|
+
|
|
251
|
+
Query available capacity and reset time for a limit
|
|
252
|
+
|
|
253
|
+
.. code-block:: python
|
|
254
|
+
|
|
255
|
+
assert True == strategy.hit(one_per_minute, "test_namespace", "foo")
|
|
256
|
+
window = strategy.get_window_stats(one_per_minute, "test_namespace", "foo")
|
|
257
|
+
assert window.remaining == 0
|
|
258
|
+
assert False == strategy.hit(one_per_minute, "test_namespace", "foo")
|
|
259
|
+
time.sleep(window.reset_time - time.time())
|
|
260
|
+
assert True == strategy.hit(one_per_minute, "test_namespace", "foo")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
Links
|
|
264
|
+
=====
|
|
265
|
+
|
|
266
|
+
* `Documentation <http://limits.readthedocs.org/en/latest>`_
|
|
267
|
+
* `Changelog <http://limits.readthedocs.org/en/stable/changelog.html>`_
|
|
268
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
limits/__init__.py,sha256=j_yVhgN9pdz8o5rQjVwdJTBSq8F-CTzof9kkiYgjRbw,728
|
|
2
|
+
limits/_version.py,sha256=PIGOsmzILcAnImlW2gkexgfobAMt1PHUP-qZuYYI6Es,495
|
|
3
|
+
limits/errors.py,sha256=xCKGOVJiD-g8FlsQQb17AW2pTUvalYSuizPpvEVoYJE,626
|
|
4
|
+
limits/limits.py,sha256=YzzZP8_ay_zlMMnnY2xhAcFTTFvFe5HEk8NQlvUTru4,4907
|
|
5
|
+
limits/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
limits/strategies.py,sha256=18DpCMqDGaZDMYJYY9RuFCA20SINT3az8G-IWCG9bQE,10877
|
|
7
|
+
limits/typing.py,sha256=Me0f4A544qM73fgcr1PU3lrO9zj4EaNaQ6aQVAHfjuc,4193
|
|
8
|
+
limits/util.py,sha256=XAl7V8vZtxL4WSZ7nyFau0R6-gMUDeSV3QQ531T1tYI,6059
|
|
9
|
+
limits/version.py,sha256=YwkF3dtq1KGzvmL3iVGctA8NNtGlK_0arrzZkZGVjUs,47
|
|
10
|
+
limits/aio/__init__.py,sha256=IOetunwQy1c5GefzitK8lewbTzHGiE-kmE9NlqSdr3U,82
|
|
11
|
+
limits/aio/strategies.py,sha256=bY_F-fv0CgQjOHJJPA3iNFuomBvBiTC-3KQ-jSfV_0Y,10744
|
|
12
|
+
limits/aio/storage/__init__.py,sha256=QFoni6CYQMN-UPmja2UXiJmB_Fr6JiVWerDz3uS_HOQ,659
|
|
13
|
+
limits/aio/storage/base.py,sha256=HK6TjsJhar_6y22SBmrJJGMCmFdCmhe69LUOoWhkr3E,6494
|
|
14
|
+
limits/aio/storage/etcd.py,sha256=TEtuPP6l31e5zI7k6dGzNJVM6Vmt0wrm-QzkZvkPPvg,5004
|
|
15
|
+
limits/aio/storage/memcached.py,sha256=dQmSfBRQoUJn4Sj4kYTHd7vEQuMzY3tTp-bnSzfLsZw,10362
|
|
16
|
+
limits/aio/storage/memory.py,sha256=7qrI3cg7QnWmaLLryvqoxCLP1221ExD8UMkh7uCfuuE,9152
|
|
17
|
+
limits/aio/storage/mongodb.py,sha256=6SPdleRWhk0eqQoRZJPEgbRrrt9Pb5IOdrK0tvLxIo4,19371
|
|
18
|
+
limits/aio/storage/redis.py,sha256=sff6HZoEJXLR175lV9k14HjWgmf7Q4hK68N1ZL9BFtE,18746
|
|
19
|
+
limits/resources/redis/lua_scripts/acquire_moving_window.lua,sha256=5CFJX7D6T6RG5SFr6eVZ6zepmI1EkGWmKeVEO4QNrWo,483
|
|
20
|
+
limits/resources/redis/lua_scripts/acquire_sliding_window.lua,sha256=OhVI1MAN_gT92P6r-2CEmvy1yvQVjYCCZxWIxfXYceY,1329
|
|
21
|
+
limits/resources/redis/lua_scripts/clear_keys.lua,sha256=zU0cVfLGmapRQF9x9u0GclapM_IB2pJLszNzVQ1QRK4,184
|
|
22
|
+
limits/resources/redis/lua_scripts/incr_expire.lua,sha256=Uq9NcrrcDI-F87TDAJexoSJn2SDgeXIUEYozCp9S3oA,195
|
|
23
|
+
limits/resources/redis/lua_scripts/moving_window.lua,sha256=5hUZghISDh8Cbg8HJediM_OKjjNMF-0CBywWmsc93vA,430
|
|
24
|
+
limits/resources/redis/lua_scripts/sliding_window.lua,sha256=qG3Yg30Dq54QpRUcR9AOrKQ5bdJiaYpCacTm6Kxblvc,713
|
|
25
|
+
limits/storage/__init__.py,sha256=mGbsTP5P8beXXKqYxsicNW8leU9wfotCy-GIcN7pdKs,2662
|
|
26
|
+
limits/storage/base.py,sha256=pqH9ARhu35oTLew0K1WfT6QHPLMp4NzxHrHBWZ2mRDM,7113
|
|
27
|
+
limits/storage/etcd.py,sha256=F66CdOlZMapEvAF55D8fn3jktkx-yEqAfLza-n2T6NY,4668
|
|
28
|
+
limits/storage/memcached.py,sha256=wIegarAR6-19N6T-_YzV0zBd3-W3kf6osVSCQ5DMBRw,11233
|
|
29
|
+
limits/storage/memory.py,sha256=a6kbG2f5lg9aG3oRWXPyzYjwFBn_aD_n0ejbqoEj-b8,8734
|
|
30
|
+
limits/storage/mongodb.py,sha256=RRK9NXg2Sq-sLAfOQp9S1jVbbheL8yGGJLvwfgWsi-g,18304
|
|
31
|
+
limits/storage/redis.py,sha256=Pp5o3yLDiTHomHu1pUL6Ghz4TZOcoDirqSN-UnDZKv4,9652
|
|
32
|
+
limits/storage/redis_cluster.py,sha256=hAwBGsU1FkG4w2xjtzUJF8g3f_iP_qzCz1B4ZW63I9A,3733
|
|
33
|
+
limits/storage/redis_sentinel.py,sha256=h1wpBM3NYosBPSavTrhH16yFw17911YID04AB6WYSJM,3561
|
|
34
|
+
limits/storage/registry.py,sha256=Az-0CkVv9b5uZAvIVDYabilngIsChdFdtnTOur1JA94,689
|
|
35
|
+
limits-4.1.dist-info/LICENSE.txt,sha256=T6i7kq7F5gIPfcno9FCxU5Hcwm22Bjq0uHZV3ElcjsQ,1061
|
|
36
|
+
limits-4.1.dist-info/METADATA,sha256=u7wxlD0LDCr1uoi4e6nS9rKMtjzGmfHUvO4Tkz9-DI4,10992
|
|
37
|
+
limits-4.1.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
38
|
+
limits-4.1.dist-info/top_level.txt,sha256=C7g5ahldPoU2s6iWTaJayUrbGmPK1d6e9t5Nn0vQ2jM,7
|
|
39
|
+
limits-4.1.dist-info/RECORD,,
|
limits-4.0.0.dist-info/METADATA
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: limits
|
|
3
|
-
Version: 4.0.0
|
|
4
|
-
Summary: Rate limiting utilities
|
|
5
|
-
Home-page: https://limits.readthedocs.org
|
|
6
|
-
Author: Ali-Akber Saifee
|
|
7
|
-
Author-email: ali@indydevs.org
|
|
8
|
-
License: MIT
|
|
9
|
-
Project-URL: Source, https://github.com/alisaifee/limits
|
|
10
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
-
Classifier: Operating System :: MacOS
|
|
14
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
-
Classifier: Operating System :: OS Independent
|
|
16
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
-
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
23
|
-
Requires-Python: >=3.9
|
|
24
|
-
License-File: LICENSE.txt
|
|
25
|
-
Requires-Dist: deprecated >=1.2
|
|
26
|
-
Requires-Dist: packaging <25,>=21
|
|
27
|
-
Requires-Dist: typing-extensions
|
|
28
|
-
Provides-Extra: all
|
|
29
|
-
Requires-Dist: redis !=4.5.2,!=4.5.3,<6.0.0,>3 ; extra == 'all'
|
|
30
|
-
Requires-Dist: redis !=4.5.2,!=4.5.3,>=4.2.0 ; extra == 'all'
|
|
31
|
-
Requires-Dist: pymemcache <5.0.0,>3 ; extra == 'all'
|
|
32
|
-
Requires-Dist: pymongo <5,>4.1 ; extra == 'all'
|
|
33
|
-
Requires-Dist: etcd3 ; extra == 'all'
|
|
34
|
-
Requires-Dist: coredis <5,>=3.4.0 ; extra == 'all'
|
|
35
|
-
Requires-Dist: motor <4,>=3 ; extra == 'all'
|
|
36
|
-
Requires-Dist: aetcd ; extra == 'all'
|
|
37
|
-
Requires-Dist: emcache >=0.6.1 ; (python_version < "3.11") and extra == 'all'
|
|
38
|
-
Requires-Dist: emcache >=1 ; (python_version >= "3.11" and python_version < "3.13.0") and extra == 'all'
|
|
39
|
-
Provides-Extra: async-etcd
|
|
40
|
-
Requires-Dist: aetcd ; extra == 'async-etcd'
|
|
41
|
-
Provides-Extra: async-memcached
|
|
42
|
-
Requires-Dist: emcache >=0.6.1 ; (python_version < "3.11") and extra == 'async-memcached'
|
|
43
|
-
Requires-Dist: emcache >=1 ; (python_version >= "3.11" and python_version < "3.13.0") and extra == 'async-memcached'
|
|
44
|
-
Provides-Extra: async-mongodb
|
|
45
|
-
Requires-Dist: motor <4,>=3 ; extra == 'async-mongodb'
|
|
46
|
-
Provides-Extra: async-redis
|
|
47
|
-
Requires-Dist: coredis <5,>=3.4.0 ; extra == 'async-redis'
|
|
48
|
-
Provides-Extra: etcd
|
|
49
|
-
Requires-Dist: etcd3 ; extra == 'etcd'
|
|
50
|
-
Provides-Extra: memcached
|
|
51
|
-
Requires-Dist: pymemcache <5.0.0,>3 ; extra == 'memcached'
|
|
52
|
-
Provides-Extra: mongodb
|
|
53
|
-
Requires-Dist: pymongo <5,>4.1 ; extra == 'mongodb'
|
|
54
|
-
Provides-Extra: redis
|
|
55
|
-
Requires-Dist: redis !=4.5.2,!=4.5.3,<6.0.0,>3 ; extra == 'redis'
|
|
56
|
-
Provides-Extra: rediscluster
|
|
57
|
-
Requires-Dist: redis !=4.5.2,!=4.5.3,>=4.2.0 ; extra == 'rediscluster'
|
|
58
|
-
|
|
59
|
-
.. |ci| image:: https://github.com/alisaifee/limits/workflows/CI/badge.svg?branch=master
|
|
60
|
-
:target: https://github.com/alisaifee/limits/actions?query=branch%3Amaster+workflow%3ACI
|
|
61
|
-
.. |codecov| image:: https://codecov.io/gh/alisaifee/limits/branch/master/graph/badge.svg
|
|
62
|
-
:target: https://codecov.io/gh/alisaifee/limits
|
|
63
|
-
.. |pypi| image:: https://img.shields.io/pypi/v/limits.svg?style=flat-square
|
|
64
|
-
:target: https://pypi.python.org/pypi/limits
|
|
65
|
-
.. |pypi-versions| image:: https://img.shields.io/pypi/pyversions/limits?style=flat-square
|
|
66
|
-
:target: https://pypi.python.org/pypi/limits
|
|
67
|
-
.. |license| image:: https://img.shields.io/pypi/l/limits.svg?style=flat-square
|
|
68
|
-
:target: https://pypi.python.org/pypi/limits
|
|
69
|
-
.. |docs| image:: https://readthedocs.org/projects/limits/badge/?version=latest
|
|
70
|
-
:target: https://limits.readthedocs.org
|
|
71
|
-
|
|
72
|
-
limits
|
|
73
|
-
------
|
|
74
|
-
|docs| |ci| |codecov| |pypi| |pypi-versions| |license|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
**limits** is a python library to perform rate limiting with commonly used storage backends (Redis, Memcached, MongoDB & Etcd).
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
Supported Strategies
|
|
81
|
-
====================
|
|
82
|
-
`Fixed Window <https://limits.readthedocs.io/en/latest/strategies.html#fixed-window>`_
|
|
83
|
-
This strategy resets at a fixed interval (start of minute, hour, day etc).
|
|
84
|
-
For example, given a rate limit of ``10/minute`` the strategy will:
|
|
85
|
-
|
|
86
|
-
- Allow 10 requests between ``00:01:00`` and ``00:02:00``
|
|
87
|
-
- Allow 10 requests at ``00:00:59`` and 10 more requests at ``00:01:00``
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
`Fixed Window (Elastic) <https://limits.readthedocs.io/en/latest/strategies.html#fixed-window-with-elastic-expiry>`_
|
|
91
|
-
Identical to Fixed window, except every breach of rate limit results in an extension
|
|
92
|
-
to the time out. For example a rate limit of `1/minute` hit twice within a minute will
|
|
93
|
-
result in a lock-out for two minutes.
|
|
94
|
-
|
|
95
|
-
`Moving Window <https://limits.readthedocs.io/en/latest/strategies.html#moving-window>`_
|
|
96
|
-
Sliding window strategy enforces a rate limit of N/(m time units)
|
|
97
|
-
on the **last m** time units at the second granularity.
|
|
98
|
-
|
|
99
|
-
For example, with a rate limit of ``10/minute``:
|
|
100
|
-
|
|
101
|
-
- Allow 9 requests that arrive at ``00:00:59``
|
|
102
|
-
- Allow another request that arrives at ``00:01:00``
|
|
103
|
-
- Reject the request that arrives at ``00:01:01``
|
|
104
|
-
|
|
105
|
-
Storage backends
|
|
106
|
-
================
|
|
107
|
-
|
|
108
|
-
- `Redis <https://limits.readthedocs.io/en/latest/storage.html#redis-storage>`_
|
|
109
|
-
- `Memcached <https://limits.readthedocs.io/en/latest/storage.html#memcached-storage>`_
|
|
110
|
-
- `MongoDB <https://limits.readthedocs.io/en/latest/storage.html#mongodb-storage>`_
|
|
111
|
-
- `Etcd <https://limits.readthedocs.io/en/latest/storage.html#etcd-storage>`_
|
|
112
|
-
- `In-Memory <https://limits.readthedocs.io/en/latest/storage.html#in-memory-storage>`_
|
|
113
|
-
|
|
114
|
-
Dive right in
|
|
115
|
-
=============
|
|
116
|
-
|
|
117
|
-
Initialize the storage backend
|
|
118
|
-
|
|
119
|
-
.. code-block:: python
|
|
120
|
-
|
|
121
|
-
from limits import storage
|
|
122
|
-
memory_storage = storage.MemoryStorage()
|
|
123
|
-
# or memcached
|
|
124
|
-
memcached_storage = storage.MemcachedStorage("memcached://localhost:11211")
|
|
125
|
-
# or redis
|
|
126
|
-
redis_storage = storage.RedisStorage("redis://localhost:6379")
|
|
127
|
-
# or use the factory
|
|
128
|
-
storage_uri = "memcached://localhost:11211"
|
|
129
|
-
some_storage = storage.storage_from_string(storage_uri)
|
|
130
|
-
|
|
131
|
-
Initialize a rate limiter with the Moving Window Strategy
|
|
132
|
-
|
|
133
|
-
.. code-block:: python
|
|
134
|
-
|
|
135
|
-
from limits import strategies
|
|
136
|
-
moving_window = strategies.MovingWindowRateLimiter(memory_storage)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
Initialize a rate limit
|
|
140
|
-
|
|
141
|
-
.. code-block:: python
|
|
142
|
-
|
|
143
|
-
from limits import parse
|
|
144
|
-
one_per_minute = parse("1/minute")
|
|
145
|
-
|
|
146
|
-
Initialize a rate limit explicitly
|
|
147
|
-
|
|
148
|
-
.. code-block:: python
|
|
149
|
-
|
|
150
|
-
from limits import RateLimitItemPerSecond
|
|
151
|
-
one_per_second = RateLimitItemPerSecond(1, 1)
|
|
152
|
-
|
|
153
|
-
Test the limits
|
|
154
|
-
|
|
155
|
-
.. code-block:: python
|
|
156
|
-
|
|
157
|
-
assert True == moving_window.hit(one_per_minute, "test_namespace", "foo")
|
|
158
|
-
assert False == moving_window.hit(one_per_minute, "test_namespace", "foo")
|
|
159
|
-
assert True == moving_window.hit(one_per_minute, "test_namespace", "bar")
|
|
160
|
-
|
|
161
|
-
assert True == moving_window.hit(one_per_second, "test_namespace", "foo")
|
|
162
|
-
assert False == moving_window.hit(one_per_second, "test_namespace", "foo")
|
|
163
|
-
time.sleep(1)
|
|
164
|
-
assert True == moving_window.hit(one_per_second, "test_namespace", "foo")
|
|
165
|
-
|
|
166
|
-
Check specific limits without hitting them
|
|
167
|
-
|
|
168
|
-
.. code-block:: python
|
|
169
|
-
|
|
170
|
-
assert True == moving_window.hit(one_per_second, "test_namespace", "foo")
|
|
171
|
-
while not moving_window.test(one_per_second, "test_namespace", "foo"):
|
|
172
|
-
time.sleep(0.01)
|
|
173
|
-
assert True == moving_window.hit(one_per_second, "test_namespace", "foo")
|
|
174
|
-
|
|
175
|
-
Query available capacity and reset time for a limit
|
|
176
|
-
|
|
177
|
-
.. code-block:: python
|
|
178
|
-
|
|
179
|
-
assert True == moving_window.hit(one_per_minute, "test_namespace", "foo")
|
|
180
|
-
window = moving_window.get_window_stats(one_per_minute, "test_namespace", "foo")
|
|
181
|
-
assert window.remaining == 0
|
|
182
|
-
assert False == moving_window.hit(one_per_minute, "test_namespace", "foo")
|
|
183
|
-
time.sleep(window.reset_time - time.time())
|
|
184
|
-
assert True == moving_window.hit(one_per_minute, "test_namespace", "foo")
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
Links
|
|
188
|
-
=====
|
|
189
|
-
|
|
190
|
-
* `Documentation <http://limits.readthedocs.org/en/latest>`_
|
|
191
|
-
* `Changelog <http://limits.readthedocs.org/en/stable/changelog.html>`_
|
|
192
|
-
|
limits-4.0.0.dist-info/RECORD
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
limits/__init__.py,sha256=j_yVhgN9pdz8o5rQjVwdJTBSq8F-CTzof9kkiYgjRbw,728
|
|
2
|
-
limits/_version.py,sha256=bGcuGXkHFvjcc-8fCFZ7Jl28gop0tHquuV_Enu505jw,497
|
|
3
|
-
limits/errors.py,sha256=xCKGOVJiD-g8FlsQQb17AW2pTUvalYSuizPpvEVoYJE,626
|
|
4
|
-
limits/limits.py,sha256=ZsXESq2e1ji7c2ZKjSkIAasCjiLdjVLPUa9oah_I8U4,4943
|
|
5
|
-
limits/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
limits/strategies.py,sha256=Zy6PIhkysPbxnMzFjyXEsxMM6jhRoQ5XT5WskTNruK0,6949
|
|
7
|
-
limits/typing.py,sha256=4yitf6iwDK-QEfSxv3EbTdGLOrqowLFffHAqYRUqiYY,3275
|
|
8
|
-
limits/util.py,sha256=fTx0JQBT6ZY3fxGefjT07CLimUVCFjS1jLqskXmb7Eo,5726
|
|
9
|
-
limits/version.py,sha256=YwkF3dtq1KGzvmL3iVGctA8NNtGlK_0arrzZkZGVjUs,47
|
|
10
|
-
limits/aio/__init__.py,sha256=IOetunwQy1c5GefzitK8lewbTzHGiE-kmE9NlqSdr3U,82
|
|
11
|
-
limits/aio/strategies.py,sha256=SHjmJnmy7Nh4tBydkA-0qPaULYcLOAM91T4RPybq0Sg,6768
|
|
12
|
-
limits/aio/storage/__init__.py,sha256=CbtuSlVl1jPyN_vsEI_ApWblDblVaL46xcZ2M_oM0V8,595
|
|
13
|
-
limits/aio/storage/base.py,sha256=V1Ur9Cu29_vP5IYBIsWHTgrc4riW8FEyz5Dcvv6fPoc,4821
|
|
14
|
-
limits/aio/storage/etcd.py,sha256=krqjWujvybuaFa2g_FkPr2ZtX9Ac1-oJzErfGW3h27o,4783
|
|
15
|
-
limits/aio/storage/memcached.py,sha256=n8b9GVtXMWdc-w4-xP1_MPJ9dgVcgoJ5j53mTdU6E3E,4799
|
|
16
|
-
limits/aio/storage/memory.py,sha256=4ah9RpE5r7Q2yOLT-OndhP4ZHmvcwV3rKvCicnK2CJc,5845
|
|
17
|
-
limits/aio/storage/mongodb.py,sha256=pXS2JFqMPkfPLYhQatG2ImAeYas8CelTwiOGo-3eFpM,10701
|
|
18
|
-
limits/aio/storage/redis.py,sha256=JQm4pkwynSo1k6wFVB7SyRsh7yav0_61Px1FqUwuGl4,15658
|
|
19
|
-
limits/resources/redis/lua_scripts/acquire_moving_window.lua,sha256=5CFJX7D6T6RG5SFr6eVZ6zepmI1EkGWmKeVEO4QNrWo,483
|
|
20
|
-
limits/resources/redis/lua_scripts/clear_keys.lua,sha256=zU0cVfLGmapRQF9x9u0GclapM_IB2pJLszNzVQ1QRK4,184
|
|
21
|
-
limits/resources/redis/lua_scripts/incr_expire.lua,sha256=Uq9NcrrcDI-F87TDAJexoSJn2SDgeXIUEYozCp9S3oA,195
|
|
22
|
-
limits/resources/redis/lua_scripts/moving_window.lua,sha256=5hUZghISDh8Cbg8HJediM_OKjjNMF-0CBywWmsc93vA,430
|
|
23
|
-
limits/storage/__init__.py,sha256=XAW1jVDMLFkPr_Tl1SXpg_p4Y3nhEatTSYq1MlnYJcA,2564
|
|
24
|
-
limits/storage/base.py,sha256=E7ZInoGZqoM1QIpd1f8lvytlic4sMOQdl_eTzD-mlWk,4631
|
|
25
|
-
limits/storage/etcd.py,sha256=Q1tndCAAJp0jnir-b-ZBN3-7Kf3v_uwNAqQJLmqB96Q,4440
|
|
26
|
-
limits/storage/memcached.py,sha256=1maTeD7EbSWq0NgSZcukn-4QdVGcU7-sxZIpUDuh3kw,6637
|
|
27
|
-
limits/storage/memory.py,sha256=ButyS6v7o7DB55bwM3CltFK4Fc5mwKuFEBPgat51CXU,5550
|
|
28
|
-
limits/storage/mongodb.py,sha256=7z5I2kkPaGDKKkQqHD9vp1z1G6842SbtHFaHfVRQUF0,9830
|
|
29
|
-
limits/storage/redis.py,sha256=R7UbE5ng1NjIHEO17gu-vIZ4qgy91JctbPYGEkZ2iM0,8483
|
|
30
|
-
limits/storage/redis_cluster.py,sha256=MsiEpwHphQd0P88AwGw1NVSi3UwVrhsg-pvzkHxU2kw,3739
|
|
31
|
-
limits/storage/redis_sentinel.py,sha256=665CvL3UZYB2sB_vVkZ4CCaPKcbIXvQUWuDWnBoSOLU,4124
|
|
32
|
-
limits/storage/registry.py,sha256=xcBcxuu6srqmoS4WqDpkCXnRLB19ctH98v21P8S9kS8,708
|
|
33
|
-
limits-4.0.0.dist-info/LICENSE.txt,sha256=T6i7kq7F5gIPfcno9FCxU5Hcwm22Bjq0uHZV3ElcjsQ,1061
|
|
34
|
-
limits-4.0.0.dist-info/METADATA,sha256=KraoprBoY3V_JScEnVSxHI5KRbmc3rkwsMZiPhwfgeM,7662
|
|
35
|
-
limits-4.0.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
36
|
-
limits-4.0.0.dist-info/top_level.txt,sha256=C7g5ahldPoU2s6iWTaJayUrbGmPK1d6e9t5Nn0vQ2jM,7
|
|
37
|
-
limits-4.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|