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.
@@ -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,,
@@ -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
-
@@ -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