limits 5.1.0__py3-none-any.whl → 5.3.0__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/base.py +15 -1
- limits/aio/storage/memcached/__init__.py +6 -0
- limits/aio/storage/memory.py +12 -6
- limits/aio/storage/mongodb.py +3 -0
- limits/aio/storage/redis/__init__.py +7 -0
- limits/aio/strategies.py +5 -0
- limits/storage/base.py +15 -1
- limits/storage/memcached.py +6 -0
- limits/storage/memory.py +6 -0
- limits/storage/mongodb.py +3 -0
- limits/storage/redis.py +6 -0
- limits/strategies.py +5 -0
- {limits-5.1.0.dist-info → limits-5.3.0.dist-info}/METADATA +1 -1
- {limits-5.1.0.dist-info → limits-5.3.0.dist-info}/RECORD +18 -18
- {limits-5.1.0.dist-info → limits-5.3.0.dist-info}/WHEEL +1 -1
- {limits-5.1.0.dist-info → limits-5.3.0.dist-info}/licenses/LICENSE.txt +0 -0
- {limits-5.1.0.dist-info → limits-5.3.0.dist-info}/top_level.txt +0 -0
limits/_version.py
CHANGED
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2025-
|
|
11
|
+
"date": "2025-06-13T16:23:24-0700",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "5.
|
|
14
|
+
"full-revisionid": "311fe101b2d63c03e8c6a8f0ab363dd49f3e733b",
|
|
15
|
+
"version": "5.3.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
limits/aio/storage/base.py
CHANGED
|
@@ -175,7 +175,11 @@ class SlidingWindowCounterSupport(ABC):
|
|
|
175
175
|
"""
|
|
176
176
|
|
|
177
177
|
def __init_subclass__(cls, **kwargs: Any) -> None: # type: ignore[explicit-any]
|
|
178
|
-
for method in {
|
|
178
|
+
for method in {
|
|
179
|
+
"acquire_sliding_window_entry",
|
|
180
|
+
"get_sliding_window",
|
|
181
|
+
"clear_sliding_window",
|
|
182
|
+
}:
|
|
179
183
|
setattr(
|
|
180
184
|
cls,
|
|
181
185
|
method,
|
|
@@ -218,3 +222,13 @@ class SlidingWindowCounterSupport(ABC):
|
|
|
218
222
|
- current window TTL
|
|
219
223
|
"""
|
|
220
224
|
raise NotImplementedError
|
|
225
|
+
|
|
226
|
+
@abstractmethod
|
|
227
|
+
async def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
228
|
+
"""
|
|
229
|
+
Resets the rate limit key(s) for the sliding window
|
|
230
|
+
|
|
231
|
+
:param key: the key to clear rate limits for
|
|
232
|
+
:param expiry: the rate limit expiry, needed to compute the key in some implemenations
|
|
233
|
+
"""
|
|
234
|
+
...
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import time
|
|
4
5
|
from math import floor
|
|
5
6
|
|
|
@@ -167,6 +168,11 @@ class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingW
|
|
|
167
168
|
previous_key, current_key, expiry, now
|
|
168
169
|
)
|
|
169
170
|
|
|
171
|
+
async def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
172
|
+
now = time.time()
|
|
173
|
+
previous_key, current_key = self.sliding_window_keys(key, expiry, now)
|
|
174
|
+
await asyncio.gather(self.clear(previous_key), self.clear(current_key))
|
|
175
|
+
|
|
170
176
|
async def _get_sliding_window_info(
|
|
171
177
|
self, previous_key: str, current_key: str, expiry: int, now: float
|
|
172
178
|
) -> tuple[int, float, int, float]:
|
limits/aio/storage/memory.py
CHANGED
|
@@ -65,13 +65,13 @@ class MemoryStorage(
|
|
|
65
65
|
try:
|
|
66
66
|
now = time.time()
|
|
67
67
|
for key in list(self.events.keys()):
|
|
68
|
-
cutoff = await asyncio.to_thread(
|
|
69
|
-
lambda evts: bisect.bisect_left(
|
|
70
|
-
evts, -now, key=lambda event: -event.expiry
|
|
71
|
-
),
|
|
72
|
-
self.events[key],
|
|
73
|
-
)
|
|
74
68
|
async with self.locks[key]:
|
|
69
|
+
cutoff = await asyncio.to_thread(
|
|
70
|
+
lambda evts: bisect.bisect_left(
|
|
71
|
+
evts, -now, key=lambda event: -event.expiry
|
|
72
|
+
),
|
|
73
|
+
self.events[key],
|
|
74
|
+
)
|
|
75
75
|
if self.events.get(key, []):
|
|
76
76
|
self.events[key] = self.events[key][:cutoff]
|
|
77
77
|
if not self.events.get(key, None):
|
|
@@ -241,6 +241,12 @@ class MemoryStorage(
|
|
|
241
241
|
previous_key, current_key, expiry, now
|
|
242
242
|
)
|
|
243
243
|
|
|
244
|
+
async def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
245
|
+
now = time.time()
|
|
246
|
+
previous_key, current_key = self.sliding_window_keys(key, expiry, now)
|
|
247
|
+
await self.clear(current_key)
|
|
248
|
+
await self.clear(previous_key)
|
|
249
|
+
|
|
244
250
|
async def _get_sliding_window_info(
|
|
245
251
|
self,
|
|
246
252
|
previous_key: str,
|
limits/aio/storage/mongodb.py
CHANGED
|
@@ -513,5 +513,8 @@ class MongoDBStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
|
|
|
513
513
|
)
|
|
514
514
|
return 0, 0.0, 0, 0.0
|
|
515
515
|
|
|
516
|
+
async def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
517
|
+
return await self.clear(key)
|
|
518
|
+
|
|
516
519
|
def __del__(self) -> None:
|
|
517
520
|
self.storage and self.storage.close()
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
3
5
|
from deprecated.sphinx import versionadded, versionchanged
|
|
4
6
|
from packaging.version import Version
|
|
5
7
|
|
|
@@ -219,6 +221,11 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
|
|
|
219
221
|
current_key = self._current_window_key(key)
|
|
220
222
|
return await self.bridge.get_sliding_window(previous_key, current_key, expiry)
|
|
221
223
|
|
|
224
|
+
async def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
225
|
+
previous_key = self._previous_window_key(key)
|
|
226
|
+
current_key = self._current_window_key(key)
|
|
227
|
+
await asyncio.gather(self.clear(previous_key), self.clear(current_key))
|
|
228
|
+
|
|
222
229
|
async def get_expiry(self, key: str) -> float:
|
|
223
230
|
"""
|
|
224
231
|
:param key: the key to get the expiry for
|
limits/aio/strategies.py
CHANGED
|
@@ -302,6 +302,11 @@ class SlidingWindowCounterRateLimiter(RateLimiter):
|
|
|
302
302
|
|
|
303
303
|
return WindowStats(now + min(previous_reset_in, current_reset_in), remaining)
|
|
304
304
|
|
|
305
|
+
async def clear(self, item: RateLimitItem, *identifiers: str) -> None:
|
|
306
|
+
return await cast(
|
|
307
|
+
SlidingWindowCounterSupport, self.storage
|
|
308
|
+
).clear_sliding_window(item.key_for(*identifiers), item.get_expiry())
|
|
309
|
+
|
|
305
310
|
|
|
306
311
|
STRATEGIES = {
|
|
307
312
|
"sliding-window-counter": SlidingWindowCounterRateLimiter,
|
limits/storage/base.py
CHANGED
|
@@ -167,7 +167,11 @@ class SlidingWindowCounterSupport(ABC):
|
|
|
167
167
|
"""
|
|
168
168
|
|
|
169
169
|
def __init_subclass__(cls, **kwargs: Any) -> None: # type: ignore[explicit-any]
|
|
170
|
-
for method in {
|
|
170
|
+
for method in {
|
|
171
|
+
"acquire_sliding_window_entry",
|
|
172
|
+
"get_sliding_window",
|
|
173
|
+
"clear_sliding_window",
|
|
174
|
+
}:
|
|
171
175
|
setattr(
|
|
172
176
|
cls,
|
|
173
177
|
method,
|
|
@@ -207,6 +211,16 @@ class SlidingWindowCounterSupport(ABC):
|
|
|
207
211
|
"""
|
|
208
212
|
raise NotImplementedError
|
|
209
213
|
|
|
214
|
+
@abstractmethod
|
|
215
|
+
def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
216
|
+
"""
|
|
217
|
+
Resets the rate limit key(s) for the sliding window
|
|
218
|
+
|
|
219
|
+
:param key: the key to clear rate limits for
|
|
220
|
+
:param expiry: the rate limit expiry, needed to compute the key in some implemenations
|
|
221
|
+
"""
|
|
222
|
+
...
|
|
223
|
+
|
|
210
224
|
|
|
211
225
|
class TimestampedSlidingWindow:
|
|
212
226
|
"""Helper class for storage that support the sliding window counter, with timestamp based keys."""
|
limits/storage/memcached.py
CHANGED
|
@@ -282,6 +282,12 @@ class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingW
|
|
|
282
282
|
previous_key, current_key = self.sliding_window_keys(key, expiry, now)
|
|
283
283
|
return self._get_sliding_window_info(previous_key, current_key, expiry, now)
|
|
284
284
|
|
|
285
|
+
def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
286
|
+
now = time.time()
|
|
287
|
+
previous_key, current_key = self.sliding_window_keys(key, expiry, now)
|
|
288
|
+
self.clear(previous_key)
|
|
289
|
+
self.clear(current_key)
|
|
290
|
+
|
|
285
291
|
def _get_sliding_window_info(
|
|
286
292
|
self, previous_key: str, current_key: str, expiry: int, now: float
|
|
287
293
|
) -> tuple[int, float, int, float]:
|
limits/storage/memory.py
CHANGED
|
@@ -237,6 +237,12 @@ class MemoryStorage(
|
|
|
237
237
|
previous_key, current_key = self.sliding_window_keys(key, expiry, now)
|
|
238
238
|
return self._get_sliding_window_info(previous_key, current_key, expiry, now)
|
|
239
239
|
|
|
240
|
+
def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
241
|
+
now = time.time()
|
|
242
|
+
previous_key, current_key = self.sliding_window_keys(key, expiry, now)
|
|
243
|
+
self.clear(previous_key)
|
|
244
|
+
self.clear(current_key)
|
|
245
|
+
|
|
240
246
|
def check(self) -> bool:
|
|
241
247
|
"""
|
|
242
248
|
check if storage is healthy
|
limits/storage/mongodb.py
CHANGED
|
@@ -470,6 +470,9 @@ class MongoDBStorageBase(
|
|
|
470
470
|
)
|
|
471
471
|
return cast(bool, result["_acquired"])
|
|
472
472
|
|
|
473
|
+
def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
474
|
+
return self.clear(key)
|
|
475
|
+
|
|
473
476
|
def __del__(self) -> None:
|
|
474
477
|
if self.storage:
|
|
475
478
|
self.storage.close()
|
limits/storage/redis.py
CHANGED
|
@@ -200,6 +200,12 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
|
|
|
200
200
|
)
|
|
201
201
|
return 0, 0.0, 0, 0.0
|
|
202
202
|
|
|
203
|
+
def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
204
|
+
previous_key = self._previous_window_key(key)
|
|
205
|
+
current_key = self._current_window_key(key)
|
|
206
|
+
self.clear(previous_key)
|
|
207
|
+
self.clear(current_key)
|
|
208
|
+
|
|
203
209
|
def incr(
|
|
204
210
|
self,
|
|
205
211
|
key: str,
|
limits/strategies.py
CHANGED
|
@@ -284,6 +284,11 @@ class SlidingWindowCounterRateLimiter(RateLimiter):
|
|
|
284
284
|
|
|
285
285
|
return WindowStats(now + min(previous_reset_in, current_reset_in), remaining)
|
|
286
286
|
|
|
287
|
+
def clear(self, item: RateLimitItem, *identifiers: str) -> None:
|
|
288
|
+
return cast(SlidingWindowCounterSupport, self.storage).clear_sliding_window(
|
|
289
|
+
item.key_for(*identifiers), item.get_expiry()
|
|
290
|
+
)
|
|
291
|
+
|
|
287
292
|
|
|
288
293
|
KnownStrategy = (
|
|
289
294
|
type[SlidingWindowCounterRateLimiter]
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
limits/__init__.py,sha256=gPUFrt02kHF_syLjiVRSs-S4UVGpRMcM2VMFNhF6G24,748
|
|
2
|
-
limits/_version.py,sha256=
|
|
2
|
+
limits/_version.py,sha256=zdxTHiQVSU3wNvKwUyOaoK8MMVvzbTzqFicCzdjC7j4,497
|
|
3
3
|
limits/errors.py,sha256=s1el9Vg0ly-z92guvnvYNgKi3_aVqpiw_sufemiLLTI,662
|
|
4
4
|
limits/limits.py,sha256=YzzZP8_ay_zlMMnnY2xhAcFTTFvFe5HEk8NQlvUTru4,4907
|
|
5
5
|
limits/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
limits/strategies.py,sha256=
|
|
6
|
+
limits/strategies.py,sha256=blVIWefjRmDZvD78tneZ9aHUgCGWddyHjJaPuKxP07s,10169
|
|
7
7
|
limits/typing.py,sha256=pVt5D23MhQSUGqi0MBG5FCSqDwta2ygu18BpKvJFxow,3283
|
|
8
8
|
limits/util.py,sha256=nk5QYvezFuXPq1OTEj04RrZFSWIH-khT0e_Dim6zGCw,6002
|
|
9
9
|
limits/version.py,sha256=YwkF3dtq1KGzvmL3iVGctA8NNtGlK_0arrzZkZGVjUs,47
|
|
10
10
|
limits/aio/__init__.py,sha256=yxvWb_ZmV245Hg2LqD365WC5IDllcGDMw6udJ1jNp1g,118
|
|
11
|
-
limits/aio/strategies.py,sha256=
|
|
11
|
+
limits/aio/strategies.py,sha256=XSNE0SOSbJDjlodA_8AsL7oBYdfIn3JYh6iuR2z_cyU,10132
|
|
12
12
|
limits/aio/storage/__init__.py,sha256=vKeArUnN1ld_0mQOBBZPCjaQgM5xI1GBPM7_F2Ydz5c,646
|
|
13
|
-
limits/aio/storage/base.py,sha256=
|
|
14
|
-
limits/aio/storage/memory.py,sha256
|
|
15
|
-
limits/aio/storage/mongodb.py,sha256=
|
|
16
|
-
limits/aio/storage/memcached/__init__.py,sha256=
|
|
13
|
+
limits/aio/storage/base.py,sha256=56UyNz3I3J-4pQecjsaCK4pUC4L3R_9GzDnutdTrfKs,6706
|
|
14
|
+
limits/aio/storage/memory.py,sha256=-U_GWPWmR77Hzi1Oa1_L1WjiAlROTS8PNG8PROAm13c,9842
|
|
15
|
+
limits/aio/storage/mongodb.py,sha256=0kwDyivA53ZIOUH4DNnCjVG3olLJqAWhXctjPrnHUp0,19252
|
|
16
|
+
limits/aio/storage/memcached/__init__.py,sha256=SjAEgxC6hPjobtyTf7tq3vThPMMbS4lGdtTo5kvoz64,6885
|
|
17
17
|
limits/aio/storage/memcached/bridge.py,sha256=3CEruS6LvZWDQPGPLlwY4hemy6oN0WWduUE7t8vyXBI,2017
|
|
18
18
|
limits/aio/storage/memcached/emcache.py,sha256=J01jP-Udd2fLgamCh2CX9NEIvhN8eZVTzUok096Bbe4,3833
|
|
19
19
|
limits/aio/storage/memcached/memcachio.py,sha256=OoGVqOVG0pVX2McFeTGQ_AbiqQUu_FYwWItpQMtNV7g,3491
|
|
20
|
-
limits/aio/storage/redis/__init__.py,sha256=
|
|
20
|
+
limits/aio/storage/redis/__init__.py,sha256=RjGus6rj-RhUR4eqTcnpxgicCt_rPtwFkC_SmbKfoqQ,15032
|
|
21
21
|
limits/aio/storage/redis/bridge.py,sha256=tz6WGViOqIm81hjGPUOBlz-Qw0tSB71NIttn7Xb5lok,3189
|
|
22
22
|
limits/aio/storage/redis/coredis.py,sha256=IzfEyXBvQbr4QUWML9xAd87a2aHCvglOBEjAg-Vq4z0,7420
|
|
23
23
|
limits/aio/storage/redis/redispy.py,sha256=HS1H6E9g0dP3G-8tSUILIFoc8JWpeRQOiBxcpL3I0gM,8310
|
|
@@ -29,16 +29,16 @@ limits/resources/redis/lua_scripts/incr_expire.lua,sha256=Uq9NcrrcDI-F87TDAJexoS
|
|
|
29
29
|
limits/resources/redis/lua_scripts/moving_window.lua,sha256=zlieQwfET0BC7sxpfiOuzPa1wwmrwWLy7IF8LxNa_Lw,717
|
|
30
30
|
limits/resources/redis/lua_scripts/sliding_window.lua,sha256=qG3Yg30Dq54QpRUcR9AOrKQ5bdJiaYpCacTm6Kxblvc,713
|
|
31
31
|
limits/storage/__init__.py,sha256=9iNxIlwzLQw2d54EcMa2LBJ47wiWCPOnHgn6ddqKkDI,2652
|
|
32
|
-
limits/storage/base.py,sha256=
|
|
33
|
-
limits/storage/memcached.py,sha256=
|
|
34
|
-
limits/storage/memory.py,sha256=
|
|
35
|
-
limits/storage/mongodb.py,sha256=
|
|
36
|
-
limits/storage/redis.py,sha256=
|
|
32
|
+
limits/storage/base.py,sha256=QFVhOS8VdR7PDhaYMSc77SLg8yaGm0PCNNrMu4ZamfY,7264
|
|
33
|
+
limits/storage/memcached.py,sha256=AzT3vz-MnkFxS0mF3C0QjGPzCnmUt29qTnuOKhKVKYI,10455
|
|
34
|
+
limits/storage/memory.py,sha256=4W8hWIEzwQpoh1z0LcfwuP6DqeFoVuOEM2u8WpZkfdQ,8957
|
|
35
|
+
limits/storage/mongodb.py,sha256=Cg_Vj33N7Ozxdmq7RGMCerg1XuVOhRAU7eusfhiSZBc,18170
|
|
36
|
+
limits/storage/redis.py,sha256=zTwxV5qosxGBTrkZmD4UWQdvavDbWpYHXY7H3hXH-Sw,10791
|
|
37
37
|
limits/storage/redis_cluster.py,sha256=GkL8GCQFfxDriMzsPMkaj6pMEX5FvQXYpUtXLY5q8fQ,4621
|
|
38
38
|
limits/storage/redis_sentinel.py,sha256=OSb61DxgUxMgXSIjaM_pF5-entD8XntD56xt0rFu89k,4479
|
|
39
39
|
limits/storage/registry.py,sha256=CxSaDBGR5aBJPFAIsfX9axCnbcThN3Bu-EH4wHrXtu8,650
|
|
40
|
-
limits-5.
|
|
41
|
-
limits-5.
|
|
42
|
-
limits-5.
|
|
43
|
-
limits-5.
|
|
44
|
-
limits-5.
|
|
40
|
+
limits-5.3.0.dist-info/licenses/LICENSE.txt,sha256=T6i7kq7F5gIPfcno9FCxU5Hcwm22Bjq0uHZV3ElcjsQ,1061
|
|
41
|
+
limits-5.3.0.dist-info/METADATA,sha256=saaz-nkRG9gi-N5HyQ2eFhNjvPe2TrJnScsaZTkowEM,10900
|
|
42
|
+
limits-5.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
43
|
+
limits-5.3.0.dist-info/top_level.txt,sha256=C7g5ahldPoU2s6iWTaJayUrbGmPK1d6e9t5Nn0vQ2jM,7
|
|
44
|
+
limits-5.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|