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 CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-04-23T10:35:38-0700",
11
+ "date": "2025-06-13T16:23:24-0700",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "2b76ea0a8aa2a37b2069ab5990e9ae180de6491f",
15
- "version": "5.1.0"
14
+ "full-revisionid": "311fe101b2d63c03e8c6a8f0ab363dd49f3e733b",
15
+ "version": "5.3.0"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -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 {"acquire_sliding_window_entry", "get_sliding_window"}:
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]:
@@ -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,
@@ -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 {"acquire_sliding_window_entry", "get_sliding_window"}:
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."""
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: limits
3
- Version: 5.1.0
3
+ Version: 5.3.0
4
4
  Summary: Rate limiting utilities
5
5
  Home-page: https://limits.readthedocs.org
6
6
  Author: Ali-Akber Saifee
@@ -1,23 +1,23 @@
1
1
  limits/__init__.py,sha256=gPUFrt02kHF_syLjiVRSs-S4UVGpRMcM2VMFNhF6G24,748
2
- limits/_version.py,sha256=lLFEUIKCe-XUvEBxgpwkb-a4zLkFZoXtkgamjxeqlCA,497
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=LeZ6lnE73EIQqQ8TfKaTzlxNvBMrZOOSXFB0l8D17fI,9946
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=RzZExH2r6jnHra4SpDHqtZCC0Bo3085zUJYo2boAj6Y,9897
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=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
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=dU0FsaDv53kKGpZg-CA_F1VjLEdrxGakSHpfuh1cyYM,14756
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=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=GwD1fODE8pZxm403UwEuhbRHG1bprqoxyuFkIp_K5QQ,10552
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.1.0.dist-info/licenses/LICENSE.txt,sha256=T6i7kq7F5gIPfcno9FCxU5Hcwm22Bjq0uHZV3ElcjsQ,1061
41
- limits-5.1.0.dist-info/METADATA,sha256=Me_Gk0sTOOVK8weSyIufZ1ie9SpIKiEA2bQIMQrwBbo,10900
42
- limits-5.1.0.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
43
- limits-5.1.0.dist-info/top_level.txt,sha256=C7g5ahldPoU2s6iWTaJayUrbGmPK1d6e9t5Nn0vQ2jM,7
44
- limits-5.1.0.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5