limits 4.7.1__py3-none-any.whl → 5.0.0rc1__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-08T15:55:28-0700",
11
+ "date": "2025-04-09T18:20:47-0700",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "47c207f7d014fe33f24688ccdff740c1e10654a7",
15
- "version": "4.7.1"
14
+ "full-revisionid": "4a01f1090a5accfb05b7db6dc6469f7c51d4fa67",
15
+ "version": "5.0.0rc1"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -6,14 +6,12 @@ Implementations of storage backends to be used with
6
6
  from __future__ import annotations
7
7
 
8
8
  from .base import MovingWindowSupport, SlidingWindowCounterSupport, Storage
9
- from .etcd import EtcdStorage
10
9
  from .memcached import MemcachedStorage
11
10
  from .memory import MemoryStorage
12
11
  from .mongodb import MongoDBStorage
13
12
  from .redis import RedisClusterStorage, RedisSentinelStorage, RedisStorage
14
13
 
15
14
  __all__ = [
16
- "EtcdStorage",
17
15
  "MemcachedStorage",
18
16
  "MemoryStorage",
19
17
  "MongoDBStorage",
@@ -75,16 +75,12 @@ class Storage(LazyDependency, metaclass=StorageRegistry):
75
75
  raise NotImplementedError
76
76
 
77
77
  @abstractmethod
78
- async def incr(
79
- self, key: str, expiry: int, elastic_expiry: bool = False, amount: int = 1
80
- ) -> int:
78
+ async def incr(self, key: str, expiry: int, amount: int = 1) -> int:
81
79
  """
82
80
  increments the counter for a given rate limit key
83
81
 
84
82
  :param key: the key to increment
85
83
  :param expiry: amount in seconds for the key to expire in
86
- :param elastic_expiry: whether to keep extending the rate limit
87
- window every hit.
88
84
  :param amount: the number to increment by
89
85
  """
90
86
  raise NotImplementedError
@@ -4,26 +4,33 @@ import time
4
4
  import urllib.parse
5
5
  from collections.abc import Iterable
6
6
  from math import ceil, floor
7
+ from typing import TYPE_CHECKING
7
8
 
8
- from deprecated.sphinx import versionadded
9
+ from deprecated.sphinx import versionadded, versionchanged
9
10
 
10
11
  from limits.aio.storage.base import SlidingWindowCounterSupport, Storage
11
12
  from limits.storage.base import TimestampedSlidingWindow
12
- from limits.typing import EmcacheClientP, ItemP
13
+
14
+ if TYPE_CHECKING:
15
+ import memcachio
13
16
 
14
17
 
15
18
  @versionadded(version="2.1")
19
+ @versionchanged(
20
+ version="5.0",
21
+ reason="Switched to :pypi:`memcachio` for async memcached support",
22
+ )
16
23
  class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingWindow):
17
24
  """
18
25
  Rate limit storage with memcached as backend.
19
26
 
20
- Depends on :pypi:`emcache`
27
+ Depends on :pypi:`memcachio`
21
28
  """
22
29
 
23
30
  STORAGE_SCHEME = ["async+memcached"]
24
31
  """The storage scheme for memcached to be used in an async context"""
25
32
 
26
- DEPENDENCIES = ["emcache"]
33
+ DEPENDENCIES = ["memcachio"]
27
34
 
28
35
  def __init__(
29
36
  self,
@@ -37,8 +44,8 @@ class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingW
37
44
  :param wrap_exceptions: Whether to wrap storage exceptions in
38
45
  :exc:`limits.errors.StorageError` before raising it.
39
46
  :param options: all remaining keyword arguments are passed
40
- directly to the constructor of :class:`emcache.Client`
41
- :raise ConfigurationError: when :pypi:`emcache` is not available
47
+ directly to the constructor of :class:`memcachio.Client`
48
+ :raise ConfigurationError: when :pypi:`memcachio` is not available
42
49
  """
43
50
  parsed = urllib.parse.urlparse(uri)
44
51
  self.hosts = []
@@ -51,21 +58,21 @@ class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingW
51
58
  self._options = options
52
59
  self._storage = None
53
60
  super().__init__(uri, wrap_exceptions=wrap_exceptions, **options)
54
- self.dependency = self.dependencies["emcache"].module
61
+ self.dependency = self.dependencies["memcachio"].module
55
62
 
56
63
  @property
57
64
  def base_exceptions(
58
65
  self,
59
66
  ) -> type[Exception] | tuple[type[Exception], ...]: # pragma: no cover
60
67
  return (
61
- self.dependency.ClusterNoAvailableNodes,
62
- self.dependency.CommandError,
68
+ self.dependency.errors.NoNodeAvailable,
69
+ self.dependency.errors.MemcachioConnectionError,
63
70
  )
64
71
 
65
- async def get_storage(self) -> EmcacheClientP:
72
+ async def get_storage(self) -> memcachio.Client[bytes]:
66
73
  if not self._storage:
67
- self._storage = await self.dependency.create_client(
68
- [self.dependency.MemcachedHostAddress(h, p) for h, p in self.hosts],
74
+ self._storage = self.dependency.Client(
75
+ [(h, p) for h, p in self.hosts],
69
76
  **self._options,
70
77
  )
71
78
  assert self._storage
@@ -75,19 +82,18 @@ class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingW
75
82
  """
76
83
  :param key: the key to get the counter value for
77
84
  """
78
- item = await (await self.get_storage()).get(key.encode("utf-8"))
79
-
85
+ item = (await self.get_many([key])).get(key.encode("utf-8"), None)
80
86
  return item and int(item.value) or 0
81
87
 
82
- async def get_many(self, keys: Iterable[str]) -> dict[bytes, ItemP]:
88
+ async def get_many(
89
+ self, keys: Iterable[str]
90
+ ) -> dict[bytes, memcachio.MemcachedItem[bytes]]:
83
91
  """
84
92
  Return multiple counters at once
85
93
 
86
94
  :param keys: the keys to get the counter values for
87
95
  """
88
- return await (await self.get_storage()).get_many(
89
- [k.encode("utf-8") for k in keys]
90
- )
96
+ return await (await self.get_storage()).get(*[k.encode("utf-8") for k in keys])
91
97
 
92
98
  async def clear(self, key: str) -> None:
93
99
  """
@@ -107,17 +113,12 @@ class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingW
107
113
  """
108
114
  storage = await self.get_storage()
109
115
  limit_key = key.encode("utf-8")
110
- try:
111
- value = await storage.decrement(limit_key, amount, noreply=noreply) or 0
112
- except self.dependency.NotFoundCommandError:
113
- value = 0
114
- return value
116
+ return await storage.decr(limit_key, amount, noreply=noreply) or 0
115
117
 
116
118
  async def incr(
117
119
  self,
118
120
  key: str,
119
121
  expiry: float,
120
- elastic_expiry: bool = False,
121
122
  amount: int = 1,
122
123
  set_expiration_key: bool = True,
123
124
  ) -> int:
@@ -126,7 +127,6 @@ class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingW
126
127
 
127
128
  :param key: the key to increment
128
129
  :param expiry: amount in seconds for the key to expire in
129
- :param elastic_expiry: whether to keep extending the rate limit
130
130
  window every hit.
131
131
  :param amount: the number to increment by
132
132
  :param set_expiration_key: if set to False, the expiration time won't be stored but the key will still expire
@@ -134,53 +134,29 @@ class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingW
134
134
  storage = await self.get_storage()
135
135
  limit_key = key.encode("utf-8")
136
136
  expire_key = self._expiration_key(key).encode()
137
- value = None
138
- try:
139
- value = await storage.increment(limit_key, amount) or amount
140
- if elastic_expiry:
141
- await storage.touch(limit_key, exptime=ceil(expiry))
142
- if set_expiration_key:
143
- await storage.set(
144
- expire_key,
145
- str(expiry + time.time()).encode("utf-8"),
146
- exptime=ceil(expiry),
147
- noreply=False,
148
- )
149
- return value
150
- except self.dependency.NotFoundCommandError:
151
- # Incrementation failed because the key doesn't exist
137
+ if (value := (await storage.incr(limit_key, amount))) is None:
152
138
  storage = await self.get_storage()
153
- try:
154
- await storage.add(limit_key, f"{amount}".encode(), exptime=ceil(expiry))
139
+ if await storage.add(limit_key, f"{amount}".encode(), expiry=ceil(expiry)):
155
140
  if set_expiration_key:
156
141
  await storage.set(
157
142
  expire_key,
158
143
  str(expiry + time.time()).encode("utf-8"),
159
- exptime=ceil(expiry),
144
+ expiry=ceil(expiry),
160
145
  noreply=False,
161
146
  )
162
- value = amount
163
- except self.dependency.NotStoredStorageCommandError:
164
- # Coult not add the key, probably because a concurrent call has added it
147
+ return amount
148
+ else:
165
149
  storage = await self.get_storage()
166
- value = await storage.increment(limit_key, amount) or amount
167
- if elastic_expiry:
168
- await storage.touch(limit_key, exptime=ceil(expiry))
169
- if set_expiration_key:
170
- await storage.set(
171
- expire_key,
172
- str(expiry + time.time()).encode("utf-8"),
173
- exptime=ceil(expiry),
174
- noreply=False,
175
- )
176
- return value
150
+ return await storage.incr(limit_key, amount) or amount
151
+ return value
177
152
 
178
153
  async def get_expiry(self, key: str) -> float:
179
154
  """
180
155
  :param key: the key to get the expiry for
181
156
  """
182
157
  storage = await self.get_storage()
183
- item = await storage.get(self._expiration_key(key).encode("utf-8"))
158
+ expiration_key = self._expiration_key(key).encode("utf-8")
159
+ item = (await storage.get(expiration_key)).get(expiration_key, None)
184
160
 
185
161
  return item and float(item.value) or time.time()
186
162
 
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import bisect
4
5
  import time
5
6
  from collections import Counter, defaultdict
6
7
  from math import floor
@@ -28,7 +29,7 @@ class MemoryStorage(
28
29
  ):
29
30
  """
30
31
  rate limit storage using :class:`collections.Counter`
31
- as an in memory storage for fixed and elastic window strategies,
32
+ as an in memory storage for fixed & sliding window strategies,
32
33
  and a simple list to implement moving window strategy.
33
34
  """
34
35
 
@@ -61,11 +62,16 @@ class MemoryStorage(
61
62
  asyncio.ensure_future(self.__schedule_expiry())
62
63
 
63
64
  async def __expire_events(self) -> None:
65
+ now = time.time()
64
66
  for key in list(self.events.keys()):
67
+ cutoff = await asyncio.to_thread(
68
+ lambda evts: bisect.bisect_left(
69
+ evts, -now, key=lambda event: -event.expiry
70
+ ),
71
+ self.events[key],
72
+ )
65
73
  async with self.locks[key]:
66
- for event in list(self.events[key]):
67
- if event.expiry <= time.time() and event in self.events[key]:
68
- self.events[key].remove(event)
74
+ self.events[key] = self.events[key][:cutoff]
69
75
  if not self.events.get(key, None):
70
76
  self.events.pop(key, None)
71
77
  self.locks.pop(key, None)
@@ -86,26 +92,20 @@ class MemoryStorage(
86
92
  ) -> type[Exception] | tuple[type[Exception], ...]: # pragma: no cover
87
93
  return ValueError
88
94
 
89
- async def incr(
90
- self, key: str, expiry: float, elastic_expiry: bool = False, amount: int = 1
91
- ) -> int:
95
+ async def incr(self, key: str, expiry: float, amount: int = 1) -> int:
92
96
  """
93
97
  increments the counter for a given rate limit key
94
98
 
95
99
  :param key: the key to increment
96
100
  :param expiry: amount in seconds for the key to expire in
97
- :param elastic_expiry: whether to keep extending the rate limit
98
- window every hit.
99
101
  :param amount: the number to increment by
100
102
  """
101
103
  await self.get(key)
102
104
  await self.__schedule_expiry()
103
105
  async with self.locks[key]:
104
106
  self.storage[key] += amount
105
-
106
- if elastic_expiry or self.storage[key] == amount:
107
+ if self.storage[key] == amount:
107
108
  self.expirations[key] = time.time() + expiry
108
-
109
109
  return self.storage.get(key, amount)
110
110
 
111
111
  async def decr(self, key: str, amount: int = 1) -> int:
@@ -165,8 +165,7 @@ class MemoryStorage(
165
165
  if entry and entry.atime >= timestamp - expiry:
166
166
  return False
167
167
  else:
168
- self.events[key][:0] = [Entry(expiry) for _ in range(amount)]
169
-
168
+ self.events[key][:0] = [Entry(expiry)] * amount
170
169
  return True
171
170
 
172
171
  async def get_expiry(self, key: str) -> float:
@@ -176,22 +175,6 @@ class MemoryStorage(
176
175
 
177
176
  return self.expirations.get(key, time.time())
178
177
 
179
- async def get_num_acquired(self, key: str, expiry: int) -> int:
180
- """
181
- returns the number of entries already acquired
182
-
183
- :param key: rate limit key to acquire an entry in
184
- :param expiry: expiry of the entry
185
- """
186
- timestamp = time.time()
187
-
188
- return (
189
- len([k for k in self.events.get(key, []) if k.atime >= timestamp - expiry])
190
- if self.events.get(key)
191
- else 0
192
- )
193
-
194
- # FIXME: arg limit is not used
195
178
  async def get_moving_window(
196
179
  self, key: str, limit: int, expiry: int
197
180
  ) -> tuple[float, int]:
@@ -203,14 +186,14 @@ class MemoryStorage(
203
186
  :param expiry: expiry of entry
204
187
  :return: (start of window, number of acquired entries)
205
188
  """
206
- timestamp = time.time()
207
- acquired = await self.get_num_acquired(key, expiry)
208
189
 
209
- for item in self.events.get(key, [])[::-1]:
210
- if item.atime >= timestamp - expiry:
211
- return item.atime, acquired
212
-
213
- return timestamp, acquired
190
+ timestamp = time.time()
191
+ if events := self.events.get(key, []):
192
+ oldest = bisect.bisect_left(
193
+ events, -(timestamp - expiry), key=lambda entry: -entry.atime
194
+ )
195
+ return events[oldest - 1].atime, oldest
196
+ return timestamp, 0
214
197
 
215
198
  async def acquire_sliding_window_entry(
216
199
  self,
@@ -242,7 +225,6 @@ class MemoryStorage(
242
225
  # Limitation: during high concurrency at the end of the window,
243
226
  # the counter is shifted and cannot be decremented, so less requests than expected are allowed.
244
227
  await self.decr(current_key, amount)
245
- # print("Concurrent call, reverting the counter increase")
246
228
  return False
247
229
  return True
248
230
 
@@ -169,16 +169,12 @@ class MongoDBStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
169
169
 
170
170
  return counter and counter["count"] or 0
171
171
 
172
- async def incr(
173
- self, key: str, expiry: int, elastic_expiry: bool = False, amount: int = 1
174
- ) -> int:
172
+ async def incr(self, key: str, expiry: int, amount: int = 1) -> int:
175
173
  """
176
174
  increments the counter for a given rate limit key
177
175
 
178
176
  :param key: the key to increment
179
177
  :param expiry: amount in seconds for the key to expire in
180
- :param elastic_expiry: whether to keep extending the rate limit
181
- window every hit.
182
178
  :param amount: the number to increment by
183
179
  """
184
180
  await self.create_indices()
@@ -205,7 +201,7 @@ class MongoDBStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
205
201
  "$cond": {
206
202
  "if": {"$lt": ["$expireAt", "$$NOW"]},
207
203
  "then": expiration,
208
- "else": (expiration if elastic_expiry else "$expireAt"),
204
+ "else": "$expireAt",
209
205
  }
210
206
  },
211
207
  }
@@ -241,15 +237,16 @@ class MongoDBStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
241
237
  :param int expiry: expiry of entry
242
238
  :return: (start of window, number of acquired entries)
243
239
  """
240
+
244
241
  timestamp = time.time()
245
- if result := (
246
- await self.database[self.__collection_mapping["windows"]]
242
+ if (
243
+ result := await self.database[self.__collection_mapping["windows"]]
247
244
  .aggregate(
248
245
  [
249
246
  {"$match": {"_id": key}},
250
247
  {
251
248
  "$project": {
252
- "entries": {
249
+ "filteredEntries": {
253
250
  "$filter": {
254
251
  "input": "$entries",
255
252
  "as": "entry",
@@ -258,12 +255,10 @@ class MongoDBStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
258
255
  }
259
256
  }
260
257
  },
261
- {"$unwind": "$entries"},
262
258
  {
263
- "$group": {
264
- "_id": "$_id",
265
- "min": {"$min": "$entries"},
266
- "count": {"$sum": 1},
259
+ "$project": {
260
+ "min": {"$min": "$filteredEntries"},
261
+ "count": {"$size": "$filteredEntries"},
267
262
  }
268
263
  },
269
264
  ]
@@ -337,7 +332,7 @@ class MongoDBStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
337
332
  "$cond": {
338
333
  "if": {
339
334
  "$lte": [
340
- {"$subtract": ["$expiresAt", "$$NOW"]},
335
+ {"$subtract": ["$expireAt", "$$NOW"]},
341
336
  expiry_ms,
342
337
  ]
343
338
  },
@@ -353,7 +348,7 @@ class MongoDBStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
353
348
  "$cond": {
354
349
  "if": {
355
350
  "$lte": [
356
- {"$subtract": ["$expiresAt", "$$NOW"]},
351
+ {"$subtract": ["$expireAt", "$$NOW"]},
357
352
  expiry_ms,
358
353
  ]
359
354
  },
@@ -361,22 +356,22 @@ class MongoDBStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
361
356
  "else": {"$ifNull": ["$currentCount", 0]},
362
357
  }
363
358
  },
364
- "expiresAt": {
359
+ "expireAt": {
365
360
  "$cond": {
366
361
  "if": {
367
362
  "$lte": [
368
- {"$subtract": ["$expiresAt", "$$NOW"]},
363
+ {"$subtract": ["$expireAt", "$$NOW"]},
369
364
  expiry_ms,
370
365
  ]
371
366
  },
372
367
  "then": {
373
368
  "$cond": {
374
- "if": {"$gt": ["$expiresAt", 0]},
375
- "then": {"$add": ["$expiresAt", expiry_ms]},
369
+ "if": {"$gt": ["$expireAt", 0]},
370
+ "then": {"$add": ["$expireAt", expiry_ms]},
376
371
  "else": {"$add": ["$$NOW", 2 * expiry_ms]},
377
372
  }
378
373
  },
379
- "else": "$expiresAt",
374
+ "else": "$expireAt",
380
375
  }
381
376
  },
382
377
  }
@@ -396,7 +391,7 @@ class MongoDBStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
396
391
  0,
397
392
  {
398
393
  "$subtract": [
399
- "$expiresAt",
394
+ "$expireAt",
400
395
  {
401
396
  "$add": [
402
397
  "$$NOW",
@@ -464,7 +459,7 @@ class MongoDBStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
464
459
  "$cond": {
465
460
  "if": {
466
461
  "$lte": [
467
- {"$subtract": ["$expiresAt", "$$NOW"]},
462
+ {"$subtract": ["$expireAt", "$$NOW"]},
468
463
  expiry_ms,
469
464
  ]
470
465
  },
@@ -476,7 +471,7 @@ class MongoDBStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
476
471
  "$cond": {
477
472
  "if": {
478
473
  "$lte": [
479
- {"$subtract": ["$expiresAt", "$$NOW"]},
474
+ {"$subtract": ["$expireAt", "$$NOW"]},
480
475
  expiry_ms,
481
476
  ]
482
477
  },
@@ -484,27 +479,27 @@ class MongoDBStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
484
479
  "else": {"$ifNull": ["$currentCount", 0]},
485
480
  }
486
481
  },
487
- "expiresAt": {
482
+ "expireAt": {
488
483
  "$cond": {
489
484
  "if": {
490
485
  "$lte": [
491
- {"$subtract": ["$expiresAt", "$$NOW"]},
486
+ {"$subtract": ["$expireAt", "$$NOW"]},
492
487
  expiry_ms,
493
488
  ]
494
489
  },
495
- "then": {"$add": ["$expiresAt", expiry_ms]},
496
- "else": "$expiresAt",
490
+ "then": {"$add": ["$expireAt", expiry_ms]},
491
+ "else": "$expireAt",
497
492
  }
498
493
  },
499
494
  }
500
495
  }
501
496
  ],
502
497
  return_document=self.proxy_dependency.module.ReturnDocument.AFTER,
503
- projection=["currentCount", "previousCount", "expiresAt"],
498
+ projection=["currentCount", "previousCount", "expireAt"],
504
499
  ):
505
500
  expires_at = (
506
- (result["expiresAt"].replace(tzinfo=datetime.timezone.utc).timestamp())
507
- if result.get("expiresAt")
501
+ (result["expireAt"].replace(tzinfo=datetime.timezone.utc).timestamp())
502
+ if result.get("expireAt")
508
503
  else time.time()
509
504
  )
510
505
  current_ttl = max(0, expires_at - time.time())
@@ -139,9 +139,7 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
139
139
  ) -> type[Exception] | tuple[type[Exception], ...]: # pragma: no cover
140
140
  return self.bridge.base_exceptions
141
141
 
142
- async def incr(
143
- self, key: str, expiry: int, elastic_expiry: bool = False, amount: int = 1
144
- ) -> int:
142
+ async def incr(self, key: str, expiry: int, amount: int = 1) -> int:
145
143
  """
146
144
  increments the counter for a given rate limit key
147
145
 
@@ -150,7 +148,7 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
150
148
  :param amount: the number to increment by
151
149
  """
152
150
 
153
- return await self.bridge.incr(key, expiry, elastic_expiry, amount)
151
+ return await self.bridge.incr(key, expiry, amount)
154
152
 
155
153
  async def get(self, key: str) -> int:
156
154
  """
@@ -68,7 +68,6 @@ class RedisBridge(ABC):
68
68
  self,
69
69
  key: str,
70
70
  expiry: int,
71
- elastic_expiry: bool = False,
72
71
  amount: int = 1,
73
72
  ) -> int: ...
74
73
 
@@ -112,14 +112,10 @@ class CoredisBridge(RedisBridge):
112
112
  self.SCRIPT_ACQUIRE_SLIDING_WINDOW
113
113
  )
114
114
 
115
- async def incr(
116
- self, key: str, expiry: int, elastic_expiry: bool = False, amount: int = 1
117
- ) -> int:
115
+ async def incr(self, key: str, expiry: int, amount: int = 1) -> int:
118
116
  key = self.prefixed_key(key)
119
- value = await self.get_connection().incrby(key, amount)
120
- if elastic_expiry or value == amount:
117
+ if (value := await self.get_connection().incrby(key, amount)) == amount:
121
118
  await self.get_connection().expire(key, expiry)
122
-
123
119
  return value
124
120
 
125
121
  async def get(self, key: str) -> int:
@@ -119,7 +119,6 @@ class RedispyBridge(RedisBridge):
119
119
  self,
120
120
  key: str,
121
121
  expiry: int,
122
- elastic_expiry: bool = False,
123
122
  amount: int = 1,
124
123
  ) -> int:
125
124
  """
@@ -131,13 +130,7 @@ class RedispyBridge(RedisBridge):
131
130
  :param amount: the number to increment by
132
131
  """
133
132
  key = self.prefixed_key(key)
134
-
135
- if elastic_expiry:
136
- value = await self.get_connection().incrby(key, amount)
137
- await self.get_connection().expire(key, expiry)
138
- return value
139
- else:
140
- return cast(int, await self.lua_incr_expire([key], [expiry, amount]))
133
+ return cast(int, await self.lua_incr_expire([key], [expiry, amount]))
141
134
 
142
135
  async def get(self, key: str) -> int:
143
136
  """
limits/aio/strategies.py CHANGED
@@ -8,7 +8,7 @@ import time
8
8
  from abc import ABC, abstractmethod
9
9
  from math import floor, inf
10
10
 
11
- from deprecated.sphinx import deprecated, versionadded
11
+ from deprecated.sphinx import versionadded
12
12
 
13
13
  from ..limits import RateLimitItem
14
14
  from ..storage import StorageTypes
@@ -150,7 +150,6 @@ class FixedWindowRateLimiter(RateLimiter):
150
150
  await self.storage.incr(
151
151
  item.key_for(*identifiers),
152
152
  item.get_expiry(),
153
- elastic_expiry=False,
154
153
  amount=cost,
155
154
  )
156
155
  <= item.amount
@@ -304,34 +303,8 @@ class SlidingWindowCounterRateLimiter(RateLimiter):
304
303
  return WindowStats(now + min(previous_reset_in, current_reset_in), remaining)
305
304
 
306
305
 
307
- @deprecated(version="4.1")
308
- class FixedWindowElasticExpiryRateLimiter(FixedWindowRateLimiter):
309
- """
310
- Reference: :ref:`strategies:fixed window with elastic expiry`
311
- """
312
-
313
- async def hit(self, item: RateLimitItem, *identifiers: str, cost: int = 1) -> bool:
314
- """
315
- Consume the rate limit
316
-
317
- :param item: a :class:`limits.limits.RateLimitItem` instance
318
- :param identifiers: variable list of strings to uniquely identify the
319
- limit
320
- :param cost: The cost of this hit, default 1
321
- """
322
- amount = await self.storage.incr(
323
- item.key_for(*identifiers),
324
- item.get_expiry(),
325
- elastic_expiry=True,
326
- amount=cost,
327
- )
328
-
329
- return amount <= item.amount
330
-
331
-
332
306
  STRATEGIES = {
333
307
  "sliding-window-counter": SlidingWindowCounterRateLimiter,
334
308
  "fixed-window": FixedWindowRateLimiter,
335
- "fixed-window-elastic-expiry": FixedWindowElasticExpiryRateLimiter,
336
309
  "moving-window": MovingWindowRateLimiter,
337
310
  }