limits 4.7.3__py3-none-any.whl → 5.0.0rc2__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 +0 -2
- limits/aio/storage/base.py +1 -5
- limits/aio/storage/memcached/__init__.py +184 -0
- limits/aio/storage/memcached/bridge.py +73 -0
- limits/aio/storage/memcached/emcache.py +112 -0
- limits/aio/storage/memcached/memcachio.py +104 -0
- limits/aio/storage/memory.py +41 -48
- limits/aio/storage/mongodb.py +26 -31
- limits/aio/storage/redis/__init__.py +2 -4
- limits/aio/storage/redis/bridge.py +0 -1
- limits/aio/storage/redis/coredis.py +2 -6
- limits/aio/storage/redis/redispy.py +1 -8
- limits/aio/strategies.py +1 -28
- limits/resources/redis/lua_scripts/acquire_moving_window.lua +5 -2
- limits/resources/redis/lua_scripts/moving_window.lua +23 -14
- limits/storage/__init__.py +0 -2
- limits/storage/base.py +1 -5
- limits/storage/memcached.py +8 -29
- limits/storage/memory.py +16 -35
- limits/storage/mongodb.py +25 -34
- limits/storage/redis.py +1 -7
- limits/strategies.py +1 -31
- limits/typing.py +1 -50
- {limits-4.7.3.dist-info → limits-5.0.0rc2.dist-info}/METADATA +8 -14
- limits-5.0.0rc2.dist-info/RECORD +44 -0
- limits/aio/storage/etcd.py +0 -146
- limits/aio/storage/memcached.py +0 -281
- limits/storage/etcd.py +0 -139
- limits-4.7.3.dist-info/RECORD +0 -43
- {limits-4.7.3.dist-info → limits-5.0.0rc2.dist-info}/WHEEL +0 -0
- {limits-4.7.3.dist-info → limits-5.0.0rc2.dist-info}/licenses/LICENSE.txt +0 -0
- {limits-4.7.3.dist-info → limits-5.0.0rc2.dist-info}/top_level.txt +0 -0
limits/storage/mongodb.py
CHANGED
|
@@ -144,9 +144,7 @@ class MongoDBStorageBase(
|
|
|
144
144
|
|
|
145
145
|
return counter and counter["count"] or 0
|
|
146
146
|
|
|
147
|
-
def incr(
|
|
148
|
-
self, key: str, expiry: int, elastic_expiry: bool = False, amount: int = 1
|
|
149
|
-
) -> int:
|
|
147
|
+
def incr(self, key: str, expiry: int, amount: int = 1) -> int:
|
|
150
148
|
"""
|
|
151
149
|
increments the counter for a given rate limit key
|
|
152
150
|
|
|
@@ -175,9 +173,7 @@ class MongoDBStorageBase(
|
|
|
175
173
|
"$cond": {
|
|
176
174
|
"if": {"$lt": ["$expireAt", "$$NOW"]},
|
|
177
175
|
"then": expiration,
|
|
178
|
-
"else":
|
|
179
|
-
expiration if elastic_expiry else "$expireAt"
|
|
180
|
-
),
|
|
176
|
+
"else": "$expireAt",
|
|
181
177
|
}
|
|
182
178
|
},
|
|
183
179
|
}
|
|
@@ -210,13 +206,13 @@ class MongoDBStorageBase(
|
|
|
210
206
|
:return: (start of window, number of acquired entries)
|
|
211
207
|
"""
|
|
212
208
|
timestamp = time.time()
|
|
213
|
-
result
|
|
209
|
+
if result := list(
|
|
214
210
|
self.windows.aggregate(
|
|
215
211
|
[
|
|
216
212
|
{"$match": {"_id": key}},
|
|
217
213
|
{
|
|
218
214
|
"$project": {
|
|
219
|
-
"
|
|
215
|
+
"filteredEntries": {
|
|
220
216
|
"$filter": {
|
|
221
217
|
"input": "$entries",
|
|
222
218
|
"as": "entry",
|
|
@@ -225,21 +221,16 @@ class MongoDBStorageBase(
|
|
|
225
221
|
}
|
|
226
222
|
}
|
|
227
223
|
},
|
|
228
|
-
{"$unwind": "$entries"},
|
|
229
224
|
{
|
|
230
|
-
"$
|
|
231
|
-
"
|
|
232
|
-
"
|
|
233
|
-
"count": {"$sum": 1},
|
|
225
|
+
"$project": {
|
|
226
|
+
"min": {"$min": "$filteredEntries"},
|
|
227
|
+
"count": {"$size": "$filteredEntries"},
|
|
234
228
|
}
|
|
235
229
|
},
|
|
236
230
|
]
|
|
237
231
|
)
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
if result:
|
|
232
|
+
):
|
|
241
233
|
return result[0]["min"], result[0]["count"]
|
|
242
|
-
|
|
243
234
|
return timestamp, 0
|
|
244
235
|
|
|
245
236
|
def acquire_entry(self, key: str, limit: int, expiry: int, amount: int = 1) -> bool:
|
|
@@ -299,7 +290,7 @@ class MongoDBStorageBase(
|
|
|
299
290
|
"$cond": {
|
|
300
291
|
"if": {
|
|
301
292
|
"$lte": [
|
|
302
|
-
{"$subtract": ["$
|
|
293
|
+
{"$subtract": ["$expireAt", "$$NOW"]},
|
|
303
294
|
expiry_ms,
|
|
304
295
|
]
|
|
305
296
|
},
|
|
@@ -311,7 +302,7 @@ class MongoDBStorageBase(
|
|
|
311
302
|
"$cond": {
|
|
312
303
|
"if": {
|
|
313
304
|
"$lte": [
|
|
314
|
-
{"$subtract": ["$
|
|
305
|
+
{"$subtract": ["$expireAt", "$$NOW"]},
|
|
315
306
|
expiry_ms,
|
|
316
307
|
]
|
|
317
308
|
},
|
|
@@ -319,29 +310,29 @@ class MongoDBStorageBase(
|
|
|
319
310
|
"else": {"$ifNull": ["$currentCount", 0]},
|
|
320
311
|
}
|
|
321
312
|
},
|
|
322
|
-
"
|
|
313
|
+
"expireAt": {
|
|
323
314
|
"$cond": {
|
|
324
315
|
"if": {
|
|
325
316
|
"$lte": [
|
|
326
|
-
{"$subtract": ["$
|
|
317
|
+
{"$subtract": ["$expireAt", "$$NOW"]},
|
|
327
318
|
expiry_ms,
|
|
328
319
|
]
|
|
329
320
|
},
|
|
330
321
|
"then": {
|
|
331
|
-
"$add": ["$
|
|
322
|
+
"$add": ["$expireAt", expiry_ms],
|
|
332
323
|
},
|
|
333
|
-
"else": "$
|
|
324
|
+
"else": "$expireAt",
|
|
334
325
|
}
|
|
335
326
|
},
|
|
336
327
|
}
|
|
337
328
|
}
|
|
338
329
|
],
|
|
339
330
|
return_document=self.lib.ReturnDocument.AFTER,
|
|
340
|
-
projection=["currentCount", "previousCount", "
|
|
331
|
+
projection=["currentCount", "previousCount", "expireAt"],
|
|
341
332
|
):
|
|
342
333
|
expires_at = (
|
|
343
|
-
(result["
|
|
344
|
-
if result.get("
|
|
334
|
+
(result["expireAt"].replace(tzinfo=datetime.timezone.utc).timestamp())
|
|
335
|
+
if result.get("expireAt")
|
|
345
336
|
else time.time()
|
|
346
337
|
)
|
|
347
338
|
current_ttl = max(0, expires_at - time.time())
|
|
@@ -368,7 +359,7 @@ class MongoDBStorageBase(
|
|
|
368
359
|
"$cond": {
|
|
369
360
|
"if": {
|
|
370
361
|
"$lte": [
|
|
371
|
-
{"$subtract": ["$
|
|
362
|
+
{"$subtract": ["$expireAt", "$$NOW"]},
|
|
372
363
|
expiry_ms,
|
|
373
364
|
]
|
|
374
365
|
},
|
|
@@ -384,7 +375,7 @@ class MongoDBStorageBase(
|
|
|
384
375
|
"$cond": {
|
|
385
376
|
"if": {
|
|
386
377
|
"$lte": [
|
|
387
|
-
{"$subtract": ["$
|
|
378
|
+
{"$subtract": ["$expireAt", "$$NOW"]},
|
|
388
379
|
expiry_ms,
|
|
389
380
|
]
|
|
390
381
|
},
|
|
@@ -392,22 +383,22 @@ class MongoDBStorageBase(
|
|
|
392
383
|
"else": {"$ifNull": ["$currentCount", 0]},
|
|
393
384
|
}
|
|
394
385
|
},
|
|
395
|
-
"
|
|
386
|
+
"expireAt": {
|
|
396
387
|
"$cond": {
|
|
397
388
|
"if": {
|
|
398
389
|
"$lte": [
|
|
399
|
-
{"$subtract": ["$
|
|
390
|
+
{"$subtract": ["$expireAt", "$$NOW"]},
|
|
400
391
|
expiry_ms,
|
|
401
392
|
]
|
|
402
393
|
},
|
|
403
394
|
"then": {
|
|
404
395
|
"$cond": {
|
|
405
|
-
"if": {"$gt": ["$
|
|
406
|
-
"then": {"$add": ["$
|
|
396
|
+
"if": {"$gt": ["$expireAt", 0]},
|
|
397
|
+
"then": {"$add": ["$expireAt", expiry_ms]},
|
|
407
398
|
"else": {"$add": ["$$NOW", 2 * expiry_ms]},
|
|
408
399
|
}
|
|
409
400
|
},
|
|
410
|
-
"else": "$
|
|
401
|
+
"else": "$expireAt",
|
|
411
402
|
}
|
|
412
403
|
},
|
|
413
404
|
}
|
|
@@ -427,7 +418,7 @@ class MongoDBStorageBase(
|
|
|
427
418
|
0,
|
|
428
419
|
{
|
|
429
420
|
"$subtract": [
|
|
430
|
-
"$
|
|
421
|
+
"$expireAt",
|
|
431
422
|
{
|
|
432
423
|
"$add": [
|
|
433
424
|
"$$NOW",
|
limits/storage/redis.py
CHANGED
|
@@ -201,7 +201,6 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
|
|
|
201
201
|
self,
|
|
202
202
|
key: str,
|
|
203
203
|
expiry: int,
|
|
204
|
-
elastic_expiry: bool = False,
|
|
205
204
|
amount: int = 1,
|
|
206
205
|
) -> int:
|
|
207
206
|
"""
|
|
@@ -213,12 +212,7 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
|
|
|
213
212
|
:param amount: the number to increment by
|
|
214
213
|
"""
|
|
215
214
|
key = self.prefixed_key(key)
|
|
216
|
-
|
|
217
|
-
value = self.get_connection().incrby(key, amount)
|
|
218
|
-
self.get_connection().expire(key, expiry)
|
|
219
|
-
return value
|
|
220
|
-
else:
|
|
221
|
-
return int(self.lua_incr_expire([key], [expiry, amount]))
|
|
215
|
+
return int(self.lua_incr_expire([key], [expiry, amount]))
|
|
222
216
|
|
|
223
217
|
def get(self, key: str) -> int:
|
|
224
218
|
"""
|
limits/strategies.py
CHANGED
|
@@ -8,7 +8,7 @@ import time
|
|
|
8
8
|
from abc import ABCMeta, abstractmethod
|
|
9
9
|
from math import floor, inf
|
|
10
10
|
|
|
11
|
-
from deprecated.sphinx import
|
|
11
|
+
from deprecated.sphinx import versionadded
|
|
12
12
|
|
|
13
13
|
from limits.storage.base import SlidingWindowCounterSupport
|
|
14
14
|
|
|
@@ -148,7 +148,6 @@ class FixedWindowRateLimiter(RateLimiter):
|
|
|
148
148
|
self.storage.incr(
|
|
149
149
|
item.key_for(*identifiers),
|
|
150
150
|
item.get_expiry(),
|
|
151
|
-
elastic_expiry=False,
|
|
152
151
|
amount=cost,
|
|
153
152
|
)
|
|
154
153
|
<= item.amount
|
|
@@ -286,43 +285,14 @@ class SlidingWindowCounterRateLimiter(RateLimiter):
|
|
|
286
285
|
return WindowStats(now + min(previous_reset_in, current_reset_in), remaining)
|
|
287
286
|
|
|
288
287
|
|
|
289
|
-
@deprecated(version="4.1", action="always")
|
|
290
|
-
class FixedWindowElasticExpiryRateLimiter(FixedWindowRateLimiter):
|
|
291
|
-
"""
|
|
292
|
-
Reference: :ref:`strategies:fixed window with elastic expiry`
|
|
293
|
-
"""
|
|
294
|
-
|
|
295
|
-
def hit(self, item: RateLimitItem, *identifiers: str, cost: int = 1) -> bool:
|
|
296
|
-
"""
|
|
297
|
-
Consume the rate limit
|
|
298
|
-
|
|
299
|
-
:param item: The rate limit item
|
|
300
|
-
:param identifiers: variable list of strings to uniquely identify this
|
|
301
|
-
instance of the limit
|
|
302
|
-
:param cost: The cost of this hit, default 1
|
|
303
|
-
"""
|
|
304
|
-
|
|
305
|
-
return (
|
|
306
|
-
self.storage.incr(
|
|
307
|
-
item.key_for(*identifiers),
|
|
308
|
-
item.get_expiry(),
|
|
309
|
-
elastic_expiry=True,
|
|
310
|
-
amount=cost,
|
|
311
|
-
)
|
|
312
|
-
<= item.amount
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
|
|
316
288
|
KnownStrategy = (
|
|
317
289
|
type[SlidingWindowCounterRateLimiter]
|
|
318
290
|
| type[FixedWindowRateLimiter]
|
|
319
|
-
| type[FixedWindowElasticExpiryRateLimiter]
|
|
320
291
|
| type[MovingWindowRateLimiter]
|
|
321
292
|
)
|
|
322
293
|
|
|
323
294
|
STRATEGIES: dict[str, KnownStrategy] = {
|
|
324
295
|
"sliding-window-counter": SlidingWindowCounterRateLimiter,
|
|
325
296
|
"fixed-window": FixedWindowRateLimiter,
|
|
326
|
-
"fixed-window-elastic-expiry": FixedWindowElasticExpiryRateLimiter,
|
|
327
297
|
"moving-window": MovingWindowRateLimiter,
|
|
328
298
|
}
|
limits/typing.py
CHANGED
|
@@ -30,54 +30,6 @@ if TYPE_CHECKING:
|
|
|
30
30
|
import redis
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
class ItemP(Protocol):
|
|
34
|
-
value: bytes
|
|
35
|
-
flags: int | None
|
|
36
|
-
cas: int | None
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class EmcacheClientP(Protocol):
|
|
40
|
-
async def add(
|
|
41
|
-
self,
|
|
42
|
-
key: bytes,
|
|
43
|
-
value: bytes,
|
|
44
|
-
*,
|
|
45
|
-
flags: int = 0,
|
|
46
|
-
exptime: int = 0,
|
|
47
|
-
noreply: bool = False,
|
|
48
|
-
) -> None: ...
|
|
49
|
-
|
|
50
|
-
async def get(self, key: bytes, return_flags: bool = False) -> ItemP | None: ...
|
|
51
|
-
|
|
52
|
-
async def get_many(self, keys: Iterable[bytes]) -> dict[bytes, ItemP]: ...
|
|
53
|
-
|
|
54
|
-
async def gets(self, key: bytes, return_flags: bool = False) -> ItemP | None: ...
|
|
55
|
-
|
|
56
|
-
async def increment(
|
|
57
|
-
self, key: bytes, value: int, *, noreply: bool = False
|
|
58
|
-
) -> int | None: ...
|
|
59
|
-
|
|
60
|
-
async def decrement(
|
|
61
|
-
self, key: bytes, value: int, *, noreply: bool = False
|
|
62
|
-
) -> int | None: ...
|
|
63
|
-
|
|
64
|
-
async def delete(self, key: bytes, *, noreply: bool = False) -> None: ...
|
|
65
|
-
|
|
66
|
-
async def set(
|
|
67
|
-
self,
|
|
68
|
-
key: bytes,
|
|
69
|
-
value: bytes,
|
|
70
|
-
*,
|
|
71
|
-
flags: int = 0,
|
|
72
|
-
exptime: int = 0,
|
|
73
|
-
noreply: bool = False,
|
|
74
|
-
) -> None: ...
|
|
75
|
-
|
|
76
|
-
async def touch(
|
|
77
|
-
self, key: bytes, exptime: int, *, noreply: bool = False
|
|
78
|
-
) -> None: ...
|
|
79
|
-
|
|
80
|
-
|
|
81
33
|
class MemcachedClientP(Protocol):
|
|
82
34
|
def add(
|
|
83
35
|
self,
|
|
@@ -155,8 +107,7 @@ __all__ = [
|
|
|
155
107
|
"Callable",
|
|
156
108
|
"ClassVar",
|
|
157
109
|
"Counter",
|
|
158
|
-
"
|
|
159
|
-
"ItemP",
|
|
110
|
+
"Iterable",
|
|
160
111
|
"Literal",
|
|
161
112
|
"MemcachedClientP",
|
|
162
113
|
"MongoClient",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: limits
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0.0rc2
|
|
4
4
|
Summary: Rate limiting utilities
|
|
5
5
|
Home-page: https://limits.readthedocs.org
|
|
6
6
|
Author: Ali-Akber Saifee
|
|
@@ -32,19 +32,14 @@ Provides-Extra: memcached
|
|
|
32
32
|
Requires-Dist: pymemcache<5.0.0,>3; extra == "memcached"
|
|
33
33
|
Provides-Extra: mongodb
|
|
34
34
|
Requires-Dist: pymongo<5,>4.1; extra == "mongodb"
|
|
35
|
-
Provides-Extra: etcd
|
|
36
|
-
Requires-Dist: etcd3; extra == "etcd"
|
|
37
35
|
Provides-Extra: valkey
|
|
38
36
|
Requires-Dist: valkey>=6; extra == "valkey"
|
|
39
37
|
Provides-Extra: async-redis
|
|
40
38
|
Requires-Dist: coredis<5,>=3.4.0; extra == "async-redis"
|
|
41
39
|
Provides-Extra: async-memcached
|
|
42
|
-
Requires-Dist:
|
|
43
|
-
Requires-Dist: emcache>=1; (python_version >= "3.11" and python_version < "3.13.0") and extra == "async-memcached"
|
|
40
|
+
Requires-Dist: memcachio>=0.3; extra == "async-memcached"
|
|
44
41
|
Provides-Extra: async-mongodb
|
|
45
42
|
Requires-Dist: motor<4,>=3; extra == "async-mongodb"
|
|
46
|
-
Provides-Extra: async-etcd
|
|
47
|
-
Requires-Dist: aetcd; extra == "async-etcd"
|
|
48
43
|
Provides-Extra: async-valkey
|
|
49
44
|
Requires-Dist: valkey>=6; extra == "async-valkey"
|
|
50
45
|
Provides-Extra: all
|
|
@@ -52,13 +47,10 @@ Requires-Dist: redis!=4.5.2,!=4.5.3,<6.0.0,>3; extra == "all"
|
|
|
52
47
|
Requires-Dist: redis!=4.5.2,!=4.5.3,>=4.2.0; extra == "all"
|
|
53
48
|
Requires-Dist: pymemcache<5.0.0,>3; extra == "all"
|
|
54
49
|
Requires-Dist: pymongo<5,>4.1; extra == "all"
|
|
55
|
-
Requires-Dist: etcd3; extra == "all"
|
|
56
50
|
Requires-Dist: valkey>=6; extra == "all"
|
|
57
51
|
Requires-Dist: coredis<5,>=3.4.0; extra == "all"
|
|
58
|
-
Requires-Dist:
|
|
59
|
-
Requires-Dist: emcache>=1; (python_version >= "3.11" and python_version < "3.13.0") and extra == "all"
|
|
52
|
+
Requires-Dist: memcachio>=0.3; extra == "all"
|
|
60
53
|
Requires-Dist: motor<4,>=3; extra == "all"
|
|
61
|
-
Requires-Dist: aetcd; extra == "all"
|
|
62
54
|
Requires-Dist: valkey>=6; extra == "all"
|
|
63
55
|
Dynamic: author
|
|
64
56
|
Dynamic: author-email
|
|
@@ -86,13 +78,14 @@ Dynamic: summary
|
|
|
86
78
|
.. |docs| image:: https://readthedocs.org/projects/limits/badge/?version=latest
|
|
87
79
|
:target: https://limits.readthedocs.org
|
|
88
80
|
|
|
81
|
+
######
|
|
89
82
|
limits
|
|
90
|
-
|
|
83
|
+
######
|
|
91
84
|
|docs| |ci| |codecov| |pypi| |pypi-versions| |license|
|
|
92
85
|
|
|
93
86
|
|
|
94
87
|
**limits** is a python library for rate limiting via multiple strategies
|
|
95
|
-
with commonly used storage backends (Redis, Memcached
|
|
88
|
+
with commonly used storage backends (Redis, Memcached & MongoDB).
|
|
96
89
|
|
|
97
90
|
The library provides identical APIs for use in sync and
|
|
98
91
|
`async <https://limits.readthedocs.io/en/stable/async.html>`_ codebases.
|
|
@@ -188,13 +181,13 @@ Scenario 2:
|
|
|
188
181
|
- ``weighted_count = floor(8 + (4 * 0.33)) = floor(8 + 1.32) = 9``.
|
|
189
182
|
- Since the weighted count is below the limit, the request is allowed.
|
|
190
183
|
|
|
184
|
+
|
|
191
185
|
Storage backends
|
|
192
186
|
================
|
|
193
187
|
|
|
194
188
|
- `Redis <https://limits.readthedocs.io/en/latest/storage.html#redis-storage>`_
|
|
195
189
|
- `Memcached <https://limits.readthedocs.io/en/latest/storage.html#memcached-storage>`_
|
|
196
190
|
- `MongoDB <https://limits.readthedocs.io/en/latest/storage.html#mongodb-storage>`_
|
|
197
|
-
- `Etcd <https://limits.readthedocs.io/en/latest/storage.html#etcd-storage>`_
|
|
198
191
|
- `In-Memory <https://limits.readthedocs.io/en/latest/storage.html#in-memory-storage>`_
|
|
199
192
|
|
|
200
193
|
Dive right in
|
|
@@ -281,5 +274,6 @@ Links
|
|
|
281
274
|
=====
|
|
282
275
|
|
|
283
276
|
* `Documentation <http://limits.readthedocs.org/en/latest>`_
|
|
277
|
+
* `Benchmarks <http://limits.readthedocs.org/en/latest/performance.html>`_
|
|
284
278
|
* `Changelog <http://limits.readthedocs.org/en/stable/changelog.html>`_
|
|
285
279
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
limits/__init__.py,sha256=gPUFrt02kHF_syLjiVRSs-S4UVGpRMcM2VMFNhF6G24,748
|
|
2
|
+
limits/_version.py,sha256=NfHJ8jCFa69gQxFo8GoNxy5o_PAmF3e9sDd1Q4gjLTU,500
|
|
3
|
+
limits/errors.py,sha256=s1el9Vg0ly-z92guvnvYNgKi3_aVqpiw_sufemiLLTI,662
|
|
4
|
+
limits/limits.py,sha256=YzzZP8_ay_zlMMnnY2xhAcFTTFvFe5HEk8NQlvUTru4,4907
|
|
5
|
+
limits/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
limits/strategies.py,sha256=LeZ6lnE73EIQqQ8TfKaTzlxNvBMrZOOSXFB0l8D17fI,9946
|
|
7
|
+
limits/typing.py,sha256=pVt5D23MhQSUGqi0MBG5FCSqDwta2ygu18BpKvJFxow,3283
|
|
8
|
+
limits/util.py,sha256=nk5QYvezFuXPq1OTEj04RrZFSWIH-khT0e_Dim6zGCw,6002
|
|
9
|
+
limits/version.py,sha256=YwkF3dtq1KGzvmL3iVGctA8NNtGlK_0arrzZkZGVjUs,47
|
|
10
|
+
limits/aio/__init__.py,sha256=yxvWb_ZmV245Hg2LqD365WC5IDllcGDMw6udJ1jNp1g,118
|
|
11
|
+
limits/aio/strategies.py,sha256=RzZExH2r6jnHra4SpDHqtZCC0Bo3085zUJYo2boAj6Y,9897
|
|
12
|
+
limits/aio/storage/__init__.py,sha256=vKeArUnN1ld_0mQOBBZPCjaQgM5xI1GBPM7_F2Ydz5c,646
|
|
13
|
+
limits/aio/storage/base.py,sha256=VfHpL9Z3RL76eKhoaSQKLKQsqcF5B2bnF6gfa-8ltWA,6296
|
|
14
|
+
limits/aio/storage/memory.py,sha256=sWrDzOe-6Opy9uFmfP1S38IbN2_wNCBaIHTS4UTRy6g,9562
|
|
15
|
+
limits/aio/storage/mongodb.py,sha256=tIMfQrseONRMR2nuRmPO7ocp8dTCABfqBICS_kgp550,19141
|
|
16
|
+
limits/aio/storage/memcached/__init__.py,sha256=VMWsH4XpaPswtPV7cQmsfckhVRbOOrKvoUPYnGt5MRY,6611
|
|
17
|
+
limits/aio/storage/memcached/bridge.py,sha256=3CEruS6LvZWDQPGPLlwY4hemy6oN0WWduUE7t8vyXBI,2017
|
|
18
|
+
limits/aio/storage/memcached/emcache.py,sha256=J01jP-Udd2fLgamCh2CX9NEIvhN8eZVTzUok096Bbe4,3833
|
|
19
|
+
limits/aio/storage/memcached/memcachio.py,sha256=OoGVqOVG0pVX2McFeTGQ_AbiqQUu_FYwWItpQMtNV7g,3491
|
|
20
|
+
limits/aio/storage/redis/__init__.py,sha256=lwoKk91YLEBlZ3W6hCnQ1e7Gc6LxpvSzZZW16saCyR4,14143
|
|
21
|
+
limits/aio/storage/redis/bridge.py,sha256=eoRi9h2bSy194cVwoKgRYQV1HQ7SvwarL-4LeazrxeA,3145
|
|
22
|
+
limits/aio/storage/redis/coredis.py,sha256=IzfEyXBvQbr4QUWML9xAd87a2aHCvglOBEjAg-Vq4z0,7420
|
|
23
|
+
limits/aio/storage/redis/redispy.py,sha256=HS1H6E9g0dP3G-8tSUILIFoc8JWpeRQOiBxcpL3I0gM,8310
|
|
24
|
+
limits/aio/storage/redis/valkey.py,sha256=f_-HPZhzNspywGybMNIL0F5uDZk76v8_K9wuC5ZeKhc,248
|
|
25
|
+
limits/resources/redis/lua_scripts/acquire_moving_window.lua,sha256=Vz0HkI_bSFLW668lEVw8paKlTLEuU4jZk1fpdSuz3zg,594
|
|
26
|
+
limits/resources/redis/lua_scripts/acquire_sliding_window.lua,sha256=OhVI1MAN_gT92P6r-2CEmvy1yvQVjYCCZxWIxfXYceY,1329
|
|
27
|
+
limits/resources/redis/lua_scripts/clear_keys.lua,sha256=zU0cVfLGmapRQF9x9u0GclapM_IB2pJLszNzVQ1QRK4,184
|
|
28
|
+
limits/resources/redis/lua_scripts/incr_expire.lua,sha256=Uq9NcrrcDI-F87TDAJexoSJn2SDgeXIUEYozCp9S3oA,195
|
|
29
|
+
limits/resources/redis/lua_scripts/moving_window.lua,sha256=zlieQwfET0BC7sxpfiOuzPa1wwmrwWLy7IF8LxNa_Lw,717
|
|
30
|
+
limits/resources/redis/lua_scripts/sliding_window.lua,sha256=qG3Yg30Dq54QpRUcR9AOrKQ5bdJiaYpCacTm6Kxblvc,713
|
|
31
|
+
limits/storage/__init__.py,sha256=9iNxIlwzLQw2d54EcMa2LBJ47wiWCPOnHgn6ddqKkDI,2652
|
|
32
|
+
limits/storage/base.py,sha256=IdOL_iqR9KhaJO73M_h9c6OYe8Ox632pxx5uXaL9Dbo,6860
|
|
33
|
+
limits/storage/memcached.py,sha256=5GUKGWS_BYTwUss2WmOlCwBtOieGT7AFUcpX65WYXdQ,10217
|
|
34
|
+
limits/storage/memory.py,sha256=rVlsirSp9LDhuqNFp6KMLR85fJc9xwrU58IHIVz6eq4,8719
|
|
35
|
+
limits/storage/mongodb.py,sha256=V4Ib_AwPFX6JpNI7oUUGJx_3MxD8EmYAi4Q6QcWnQ5U,18071
|
|
36
|
+
limits/storage/redis.py,sha256=i_6qh4S6JQd-lG6eRJdTPxNnZIAkm4G0cA0mfow9OOk,10389
|
|
37
|
+
limits/storage/redis_cluster.py,sha256=z6aONMl4p1AY78G3J0BbtK--uztz88krwnpiOsU61BM,4447
|
|
38
|
+
limits/storage/redis_sentinel.py,sha256=AN0WtwHN88TvXk0C2uUE8l5Jhsd1ZxU8XSqrEyQSR20,4327
|
|
39
|
+
limits/storage/registry.py,sha256=CxSaDBGR5aBJPFAIsfX9axCnbcThN3Bu-EH4wHrXtu8,650
|
|
40
|
+
limits-5.0.0rc2.dist-info/licenses/LICENSE.txt,sha256=T6i7kq7F5gIPfcno9FCxU5Hcwm22Bjq0uHZV3ElcjsQ,1061
|
|
41
|
+
limits-5.0.0rc2.dist-info/METADATA,sha256=0bbBoIEtEg06utKBNEGa7iCNVmbQZ6xLFaIfoxx5aDU,10903
|
|
42
|
+
limits-5.0.0rc2.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
43
|
+
limits-5.0.0rc2.dist-info/top_level.txt,sha256=C7g5ahldPoU2s6iWTaJayUrbGmPK1d6e9t5Nn0vQ2jM,7
|
|
44
|
+
limits-5.0.0rc2.dist-info/RECORD,,
|
limits/aio/storage/etcd.py
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import asyncio
|
|
4
|
-
import time
|
|
5
|
-
import urllib.parse
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
7
|
-
|
|
8
|
-
from deprecated.sphinx import deprecated
|
|
9
|
-
|
|
10
|
-
from limits.aio.storage.base import Storage
|
|
11
|
-
from limits.errors import ConcurrentUpdateError
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
import aetcd
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@deprecated(version="4.4")
|
|
18
|
-
class EtcdStorage(Storage):
|
|
19
|
-
"""
|
|
20
|
-
Rate limit storage with etcd as backend.
|
|
21
|
-
|
|
22
|
-
Depends on :pypi:`aetcd`.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
STORAGE_SCHEME = ["async+etcd"]
|
|
26
|
-
"""The async storage scheme for etcd"""
|
|
27
|
-
DEPENDENCIES = ["aetcd"]
|
|
28
|
-
|
|
29
|
-
PREFIX = "limits"
|
|
30
|
-
MAX_RETRIES = 5
|
|
31
|
-
|
|
32
|
-
def __init__(
|
|
33
|
-
self,
|
|
34
|
-
uri: str,
|
|
35
|
-
max_retries: int = MAX_RETRIES,
|
|
36
|
-
wrap_exceptions: bool = False,
|
|
37
|
-
**options: str,
|
|
38
|
-
) -> None:
|
|
39
|
-
"""
|
|
40
|
-
:param uri: etcd location of the form
|
|
41
|
-
``async+etcd://host:port``,
|
|
42
|
-
:param max_retries: Maximum number of attempts to retry
|
|
43
|
-
in the case of concurrent updates to a rate limit key
|
|
44
|
-
:param wrap_exceptions: Whether to wrap storage exceptions in
|
|
45
|
-
:exc:`limits.errors.StorageError` before raising it.
|
|
46
|
-
:param options: all remaining keyword arguments are passed
|
|
47
|
-
directly to the constructor of :class:`aetcd.client.Client`
|
|
48
|
-
:raise ConfigurationError: when :pypi:`aetcd` is not available
|
|
49
|
-
"""
|
|
50
|
-
parsed = urllib.parse.urlparse(uri)
|
|
51
|
-
self.lib = self.dependencies["aetcd"].module
|
|
52
|
-
self.storage: aetcd.Client = self.lib.Client(
|
|
53
|
-
host=parsed.hostname, port=parsed.port, **options
|
|
54
|
-
)
|
|
55
|
-
self.max_retries = max_retries
|
|
56
|
-
super().__init__(uri, wrap_exceptions=wrap_exceptions)
|
|
57
|
-
|
|
58
|
-
@property
|
|
59
|
-
def base_exceptions(
|
|
60
|
-
self,
|
|
61
|
-
) -> type[Exception] | tuple[type[Exception], ...]: # pragma: no cover
|
|
62
|
-
return self.lib.ClientError # type: ignore[no-any-return]
|
|
63
|
-
|
|
64
|
-
def prefixed_key(self, key: str) -> bytes:
|
|
65
|
-
return f"{self.PREFIX}/{key}".encode()
|
|
66
|
-
|
|
67
|
-
async def incr(
|
|
68
|
-
self, key: str, expiry: int, elastic_expiry: bool = False, amount: int = 1
|
|
69
|
-
) -> int:
|
|
70
|
-
retries = 0
|
|
71
|
-
etcd_key = self.prefixed_key(key)
|
|
72
|
-
while retries < self.max_retries:
|
|
73
|
-
now = time.time()
|
|
74
|
-
lease = await self.storage.lease(expiry)
|
|
75
|
-
window_end = now + expiry
|
|
76
|
-
create_attempt = await self.storage.transaction(
|
|
77
|
-
compare=[self.storage.transactions.create(etcd_key) == b"0"],
|
|
78
|
-
success=[
|
|
79
|
-
self.storage.transactions.put(
|
|
80
|
-
etcd_key, f"{amount}:{window_end}".encode(), lease=lease.id
|
|
81
|
-
)
|
|
82
|
-
],
|
|
83
|
-
failure=[self.storage.transactions.get(etcd_key)],
|
|
84
|
-
)
|
|
85
|
-
if create_attempt[0]:
|
|
86
|
-
return amount
|
|
87
|
-
else:
|
|
88
|
-
cur = create_attempt[1][0][0][1]
|
|
89
|
-
cur_value, window_end = cur.value.split(b":")
|
|
90
|
-
window_end = float(window_end)
|
|
91
|
-
if window_end <= now:
|
|
92
|
-
await asyncio.gather(
|
|
93
|
-
self.storage.revoke_lease(cur.lease),
|
|
94
|
-
self.storage.delete(etcd_key),
|
|
95
|
-
)
|
|
96
|
-
else:
|
|
97
|
-
if elastic_expiry:
|
|
98
|
-
await self.storage.refresh_lease(cur.lease)
|
|
99
|
-
window_end = now + expiry
|
|
100
|
-
new = int(cur_value) + amount
|
|
101
|
-
if (
|
|
102
|
-
await self.storage.transaction(
|
|
103
|
-
compare=[
|
|
104
|
-
self.storage.transactions.value(etcd_key) == cur.value
|
|
105
|
-
],
|
|
106
|
-
success=[
|
|
107
|
-
self.storage.transactions.put(
|
|
108
|
-
etcd_key,
|
|
109
|
-
f"{new}:{window_end}".encode(),
|
|
110
|
-
lease=cur.lease,
|
|
111
|
-
)
|
|
112
|
-
],
|
|
113
|
-
failure=[],
|
|
114
|
-
)
|
|
115
|
-
)[0]:
|
|
116
|
-
return new
|
|
117
|
-
retries += 1
|
|
118
|
-
raise ConcurrentUpdateError(key, retries)
|
|
119
|
-
|
|
120
|
-
async def get(self, key: str) -> int:
|
|
121
|
-
cur = await self.storage.get(self.prefixed_key(key))
|
|
122
|
-
if cur:
|
|
123
|
-
amount, expiry = cur.value.split(b":")
|
|
124
|
-
if float(expiry) > time.time():
|
|
125
|
-
return int(amount)
|
|
126
|
-
return 0
|
|
127
|
-
|
|
128
|
-
async def get_expiry(self, key: str) -> float:
|
|
129
|
-
cur = await self.storage.get(self.prefixed_key(key))
|
|
130
|
-
if cur:
|
|
131
|
-
window_end = float(cur.value.split(b":")[1])
|
|
132
|
-
return window_end
|
|
133
|
-
return time.time()
|
|
134
|
-
|
|
135
|
-
async def check(self) -> bool:
|
|
136
|
-
try:
|
|
137
|
-
await self.storage.status()
|
|
138
|
-
return True
|
|
139
|
-
except: # noqa
|
|
140
|
-
return False
|
|
141
|
-
|
|
142
|
-
async def reset(self) -> int | None:
|
|
143
|
-
return (await self.storage.delete_prefix(f"{self.PREFIX}/".encode())).deleted
|
|
144
|
-
|
|
145
|
-
async def clear(self, key: str) -> None:
|
|
146
|
-
await self.storage.delete(self.prefixed_key(key))
|