limits 4.7.2__py3-none-any.whl → 4.8.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-09T14:27:52-0700",
11
+ "date": "2025-04-23T13:02:47-0700",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "451bd270b52aff6324de3ed6576a8fc87fbdaf9a",
15
- "version": "4.7.2"
14
+ "full-revisionid": "742f7a2fdf50b5fbd1a263bd50b5dc7f1f64aa48",
15
+ "version": "4.8.0"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -93,7 +93,10 @@ class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingW
93
93
  """
94
94
  :param key: the key to clear rate limits for
95
95
  """
96
- await (await self.get_storage()).delete(key.encode("utf-8"))
96
+ try:
97
+ await (await self.get_storage()).delete(key.encode("utf-8"))
98
+ except self.dependency.NotFoundCommandError:
99
+ pass
97
100
 
98
101
  async def decr(self, key: str, amount: int = 1, noreply: bool = False) -> int:
99
102
  """
@@ -51,6 +51,8 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
51
51
  "valkey": Version("6.0"),
52
52
  }
53
53
  MODE: Literal["BASIC", "CLUSTER", "SENTINEL"] = "BASIC"
54
+ PREFIX = "LIMITS"
55
+
54
56
  bridge: RedisBridge
55
57
  storage_exceptions: tuple[Exception, ...]
56
58
  target_server: Literal["redis", "valkey"]
@@ -60,6 +62,7 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
60
62
  uri: str,
61
63
  wrap_exceptions: bool = False,
62
64
  implementation: Literal["redispy", "coredis", "valkey"] = "coredis",
65
+ key_prefix: str = PREFIX,
63
66
  **options: float | str | bool,
64
67
  ) -> None:
65
68
  """
@@ -86,6 +89,7 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
86
89
  - ``redispy``: :class:`redis.asyncio.client.Redis`
87
90
  - ``valkey``: :class:`valkey.asyncio.client.Valkey`
88
91
 
92
+ :param key_prefix: the prefix for each key created in redis
89
93
  :param options: all remaining keyword arguments are passed
90
94
  directly to the constructor of :class:`coredis.Redis` or :class:`redis.asyncio.client.Redis`
91
95
  :raise ConfigurationError: when the redis library is not available
@@ -97,12 +101,18 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
97
101
  super().__init__(uri, wrap_exceptions=wrap_exceptions)
98
102
  self.options = options
99
103
  if self.target_server == "valkey" or implementation == "valkey":
100
- self.bridge = ValkeyBridge(uri, self.dependencies["valkey"].module)
104
+ self.bridge = ValkeyBridge(
105
+ uri, self.dependencies["valkey"].module, key_prefix
106
+ )
101
107
  else:
102
108
  if implementation == "redispy":
103
- self.bridge = RedispyBridge(uri, self.dependencies["redis"].module)
109
+ self.bridge = RedispyBridge(
110
+ uri, self.dependencies["redis"].module, key_prefix
111
+ )
104
112
  else:
105
- self.bridge = CoredisBridge(uri, self.dependencies["coredis"].module)
113
+ self.bridge = CoredisBridge(
114
+ uri, self.dependencies["coredis"].module, key_prefix
115
+ )
106
116
  self.configure_bridge()
107
117
  self.bridge.register_scripts()
108
118
 
@@ -228,7 +238,7 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
228
238
  async def reset(self) -> int | None:
229
239
  """
230
240
  This function calls a Lua Script to delete keys prefixed with
231
- ``self.PREFIX`` in blocks of 5000.
241
+ :paramref:`RedisStorage.key_prefix` in blocks of 5000.
232
242
 
233
243
  .. warning:: This operation was designed to be fast, but was not tested
234
244
  on a large production based system. Be careful with its usage as it
@@ -270,6 +280,7 @@ class RedisClusterStorage(RedisStorage):
270
280
  uri: str,
271
281
  wrap_exceptions: bool = False,
272
282
  implementation: Literal["redispy", "coredis", "valkey"] = "coredis",
283
+ key_prefix: str = RedisStorage.PREFIX,
273
284
  **options: float | str | bool,
274
285
  ) -> None:
275
286
  """
@@ -285,6 +296,7 @@ class RedisClusterStorage(RedisStorage):
285
296
  - ``coredis``: :class:`coredis.RedisCluster`
286
297
  - ``redispy``: :class:`redis.asyncio.cluster.RedisCluster`
287
298
  - ``valkey``: :class:`valkey.asyncio.cluster.ValkeyCluster`
299
+ :param key_prefix: the prefix for each key created in redis
288
300
  :param options: all remaining keyword arguments are passed
289
301
  directly to the constructor of :class:`coredis.RedisCluster` or
290
302
  :class:`redis.asyncio.RedisCluster`
@@ -295,6 +307,7 @@ class RedisClusterStorage(RedisStorage):
295
307
  uri,
296
308
  wrap_exceptions=wrap_exceptions,
297
309
  implementation=implementation,
310
+ key_prefix=key_prefix,
298
311
  **options,
299
312
  )
300
313
 
@@ -305,8 +318,8 @@ class RedisClusterStorage(RedisStorage):
305
318
  """
306
319
  Redis Clusters are sharded and deleting across shards
307
320
  can't be done atomically. Because of this, this reset loops over all
308
- keys that are prefixed with ``self.PREFIX`` and calls delete on them,
309
- one at a time.
321
+ keys that are prefixed with :paramref:`RedisClusterStorage.key_prefix`
322
+ and calls delete on them one at a time.
310
323
 
311
324
  .. warning:: This operation was not tested with extremely large data sets.
312
325
  On a large production based system, care should be taken with its
@@ -356,6 +369,7 @@ class RedisSentinelStorage(RedisStorage):
356
369
  uri: str,
357
370
  wrap_exceptions: bool = False,
358
371
  implementation: Literal["redispy", "coredis", "valkey"] = "coredis",
372
+ key_prefix: str = RedisStorage.PREFIX,
359
373
  service_name: str | None = None,
360
374
  use_replicas: bool = True,
361
375
  sentinel_kwargs: dict[str, float | str | bool] | None = None,
@@ -374,6 +388,7 @@ class RedisSentinelStorage(RedisStorage):
374
388
  - ``coredis``: :class:`coredis.sentinel.Sentinel`
375
389
  - ``redispy``: :class:`redis.asyncio.sentinel.Sentinel`
376
390
  - ``valkey``: :class:`valkey.asyncio.sentinel.Sentinel`
391
+ :param key_prefix: the prefix for each key created in redis
377
392
  :param service_name: sentinel service name (if not provided in `uri`)
378
393
  :param use_replicas: Whether to use replicas for read only operations
379
394
  :param sentinel_kwargs: optional arguments to pass as
@@ -393,6 +408,7 @@ class RedisSentinelStorage(RedisStorage):
393
408
  uri,
394
409
  wrap_exceptions=wrap_exceptions,
395
410
  implementation=implementation,
411
+ key_prefix=key_prefix,
396
412
  **options,
397
413
  )
398
414
 
@@ -8,7 +8,6 @@ from limits.util import get_package_data
8
8
 
9
9
 
10
10
  class RedisBridge(ABC):
11
- PREFIX = "LIMITS"
12
11
  RES_DIR = "resources/redis/lua_scripts"
13
12
 
14
13
  SCRIPT_MOVING_WINDOW = get_package_data(f"{RES_DIR}/moving_window.lua")
@@ -26,18 +25,20 @@ class RedisBridge(ABC):
26
25
  self,
27
26
  uri: str,
28
27
  dependency: ModuleType,
28
+ key_prefix: str,
29
29
  ) -> None:
30
30
  self.uri = uri
31
31
  self.parsed_uri = urllib.parse.urlparse(self.uri)
32
32
  self.dependency = dependency
33
33
  self.parsed_auth = {}
34
+ self.key_prefix = key_prefix
34
35
  if self.parsed_uri.username:
35
36
  self.parsed_auth["username"] = self.parsed_uri.username
36
37
  if self.parsed_uri.password:
37
38
  self.parsed_auth["password"] = self.parsed_uri.password
38
39
 
39
40
  def prefixed_key(self, key: str) -> str:
40
- return f"{self.PREFIX}:{key}"
41
+ return f"{self.key_prefix}:{key}"
41
42
 
42
43
  @abstractmethod
43
44
  def register_scripts(self) -> None: ...
limits/storage/redis.py CHANGED
@@ -68,6 +68,7 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
68
68
  self,
69
69
  uri: str,
70
70
  connection_pool: redis.connection.ConnectionPool | None = None,
71
+ key_prefix: str = PREFIX,
71
72
  wrap_exceptions: bool = False,
72
73
  **options: float | str | bool,
73
74
  ) -> None:
@@ -82,6 +83,7 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
82
83
  :pypi:`valkey`.
83
84
  :param connection_pool: if provided, the redis client is initialized with
84
85
  the connection pool and any other params passed as :paramref:`options`
86
+ :param key_prefix: the prefix for each key created in redis
85
87
  :param wrap_exceptions: Whether to wrap storage exceptions in
86
88
  :exc:`limits.errors.StorageError` before raising it.
87
89
  :param options: all remaining keyword arguments are passed
@@ -89,6 +91,7 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
89
91
  :raise ConfigurationError: when the :pypi:`redis` library is not available
90
92
  """
91
93
  super().__init__(uri, wrap_exceptions=wrap_exceptions, **options)
94
+ self.key_prefix = key_prefix
92
95
  self.target_server = "valkey" if uri.startswith("valkey") else "redis"
93
96
  self.dependency = self.dependencies[self.target_server].module
94
97
 
@@ -165,7 +168,7 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
165
168
  return f"{self._current_window_key(key)}/-1"
166
169
 
167
170
  def prefixed_key(self, key: str) -> str:
168
- return f"{self.PREFIX}:{key}"
171
+ return f"{self.key_prefix}:{key}"
169
172
 
170
173
  def get_moving_window(self, key: str, limit: int, expiry: int) -> tuple[float, int]:
171
174
  """
@@ -301,7 +304,7 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
301
304
  def reset(self) -> int | None:
302
305
  """
303
306
  This function calls a Lua Script to delete keys prefixed with
304
- ``self.PREFIX`` in blocks of 5000.
307
+ :paramref:`RedisStorage.key_prefix` in blocks of 5000.
305
308
 
306
309
  .. warning::
307
310
  This operation was designed to be fast, but was not tested
@@ -56,6 +56,7 @@ class RedisClusterStorage(RedisStorage):
56
56
  def __init__(
57
57
  self,
58
58
  uri: str,
59
+ key_prefix: str = RedisStorage.PREFIX,
59
60
  wrap_exceptions: bool = False,
60
61
  **options: float | str | bool,
61
62
  ) -> None:
@@ -65,6 +66,7 @@ class RedisClusterStorage(RedisStorage):
65
66
 
66
67
  If the uri scheme is ``valkey+cluster`` the implementation used will be from
67
68
  :pypi:`valkey`.
69
+ :param key_prefix: the prefix for each key created in redis
68
70
  :param wrap_exceptions: Whether to wrap storage exceptions in
69
71
  :exc:`limits.errors.StorageError` before raising it.
70
72
  :param options: all remaining keyword arguments are passed
@@ -86,6 +88,7 @@ class RedisClusterStorage(RedisStorage):
86
88
  host, port = loc.split(":")
87
89
  cluster_hosts.append((host, int(port)))
88
90
 
91
+ self.key_prefix = key_prefix
89
92
  self.storage = None
90
93
  self.target_server = "valkey" if uri.startswith("valkey") else "redis"
91
94
  merged_options = {**self.DEFAULT_OPTIONS, **parsed_auth, **options}
@@ -108,8 +111,8 @@ class RedisClusterStorage(RedisStorage):
108
111
  """
109
112
  Redis Clusters are sharded and deleting across shards
110
113
  can't be done atomically. Because of this, this reset loops over all
111
- keys that are prefixed with ``self.PREFIX`` and calls delete on them,
112
- one at a time.
114
+ keys that are prefixed with :paramref:`RedisClusterStorage.prefix` and
115
+ calls delete on them one at a time.
113
116
 
114
117
  .. warning::
115
118
  This operation was not tested with extremely large data sets.
@@ -45,6 +45,7 @@ class RedisSentinelStorage(RedisStorage):
45
45
  service_name: str | None = None,
46
46
  use_replicas: bool = True,
47
47
  sentinel_kwargs: dict[str, float | str | bool] | None = None,
48
+ key_prefix: str = RedisStorage.PREFIX,
48
49
  wrap_exceptions: bool = False,
49
50
  **options: float | str | bool,
50
51
  ) -> None:
@@ -59,6 +60,7 @@ class RedisSentinelStorage(RedisStorage):
59
60
  :param use_replicas: Whether to use replicas for read only operations
60
61
  :param sentinel_kwargs: kwargs to pass as
61
62
  :attr:`sentinel_kwargs` to :class:`redis.sentinel.Sentinel`
63
+ :param key_prefix: the prefix for each key created in redis
62
64
  :param wrap_exceptions: Whether to wrap storage exceptions in
63
65
  :exc:`limits.errors.StorageError` before raising it.
64
66
  :param options: all remaining keyword arguments are passed
@@ -87,6 +89,7 @@ class RedisSentinelStorage(RedisStorage):
87
89
  for loc in parsed.netloc[sep:].split(","):
88
90
  host, port = loc.split(":")
89
91
  sentinel_configuration.append((host, int(port)))
92
+ self.key_prefix = key_prefix
90
93
  self.service_name = (
91
94
  parsed.path.replace("/", "") if parsed.path else service_name
92
95
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: limits
3
- Version: 4.7.2
3
+ Version: 4.8.0
4
4
  Summary: Rate limiting utilities
5
5
  Home-page: https://limits.readthedocs.org
6
6
  Author: Ali-Akber Saifee
@@ -1,5 +1,5 @@
1
1
  limits/__init__.py,sha256=gPUFrt02kHF_syLjiVRSs-S4UVGpRMcM2VMFNhF6G24,748
2
- limits/_version.py,sha256=WS_qWia_2D12fx8eH9Ix80GnKTrf5Yaps8T8rEk3YVE,497
2
+ limits/_version.py,sha256=h-KKhJ2DZQ_fAbevntsdgCkvSGnkjnPK0UGE1UUYZ5Q,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
@@ -12,11 +12,11 @@ limits/aio/strategies.py,sha256=9jnVU_nynm0zL1EjQLE3fus1vFbLqA-19ZRQhhexAGo,1077
12
12
  limits/aio/storage/__init__.py,sha256=gL4DGTV-XDksZxofaP__sGvwehN8MuuJQuRZeuGwiOQ,695
13
13
  limits/aio/storage/base.py,sha256=376Bs7l285vRTRa3-DqcVRnqOPdf3BpoCqbJr3XA9u8,6439
14
14
  limits/aio/storage/etcd.py,sha256=QJY8B9jNCNZLpo_cEMUyQkYsk4UIfoMhz7lJUl-ROmk,5081
15
- limits/aio/storage/memcached.py,sha256=kSX7B0CRS-qDVCQot4AW0rF3YwomXRyWg6W0_RyaHhM,10361
15
+ limits/aio/storage/memcached.py,sha256=elK5XWQr_P1wnWLxSuWQMEBndx0y4VUW0M1kcWJvIYI,10448
16
16
  limits/aio/storage/memory.py,sha256=KzpDnr-o3U6Ar2GkhAUTDokQ8tstUFUIe44_rYlMD3k,9765
17
17
  limits/aio/storage/mongodb.py,sha256=AyF5CxpuL0-O-PLZPBWPSbyL6JCRpKAlbIfczLSzFFY,19388
18
- limits/aio/storage/redis/__init__.py,sha256=p6amEcujcImDUxcYCsfBaLBKp1qH9xDXDjv3FWWfGow,14203
19
- limits/aio/storage/redis/bridge.py,sha256=cKs77RoCxUPfYD6_o1AiHfqpkeq_DFqMtVQKMLhEWdY,3183
18
+ limits/aio/storage/redis/__init__.py,sha256=ps-gH7auC7BCYYva1Rc110D1VUu0SQk9FVGk2R38UAo,14816
19
+ limits/aio/storage/redis/bridge.py,sha256=SRbr_p9-aHHc39fd9pf9ud8k6mtaW-K1W2BIxKAuO_Q,3227
20
20
  limits/aio/storage/redis/coredis.py,sha256=YT8cBx25MeSy9ApSJBfOK8VKduABTRefsnd9GhWscsI,7494
21
21
  limits/aio/storage/redis/redispy.py,sha256=ZAxHOFGAjRHsPzjfLowq5nMlVkK_YhVGHOOV8K4gMmU,8547
22
22
  limits/aio/storage/redis/valkey.py,sha256=f_-HPZhzNspywGybMNIL0F5uDZk76v8_K9wuC5ZeKhc,248
@@ -32,12 +32,12 @@ limits/storage/etcd.py,sha256=oaNYOUryK-YmWKfmPtodRLjJGDMV4lGEYW9PU7FI_Ko,4745
32
32
  limits/storage/memcached.py,sha256=NJqHpbfZjT7YpjlK0dUX1-k_nb7HxPMQvBBapgUaHhY,11217
33
33
  limits/storage/memory.py,sha256=xLg3NDsnVEI3ds4QGS7MpH-VjjEJqwroOnz3LA8UQQ8,9226
34
34
  limits/storage/mongodb.py,sha256=UOn1owOnCJ_HxYQS2A82PKqJ_2gg46yWNBV6S73uVeE,18314
35
- limits/storage/redis.py,sha256=b2m5TrPNwS7NBY5btwCN0esGyCVQTwwgCn6f-sTcgFQ,10613
36
- limits/storage/redis_cluster.py,sha256=z6aONMl4p1AY78G3J0BbtK--uztz88krwnpiOsU61BM,4447
37
- limits/storage/redis_sentinel.py,sha256=AN0WtwHN88TvXk0C2uUE8l5Jhsd1ZxU8XSqrEyQSR20,4327
35
+ limits/storage/redis.py,sha256=Hyf0qPfBZ91BP0bejrib23RaLsxgAOGXDTAuM0jGQCE,10776
36
+ limits/storage/redis_cluster.py,sha256=GkL8GCQFfxDriMzsPMkaj6pMEX5FvQXYpUtXLY5q8fQ,4621
37
+ limits/storage/redis_sentinel.py,sha256=OSb61DxgUxMgXSIjaM_pF5-entD8XntD56xt0rFu89k,4479
38
38
  limits/storage/registry.py,sha256=CxSaDBGR5aBJPFAIsfX9axCnbcThN3Bu-EH4wHrXtu8,650
39
- limits-4.7.2.dist-info/licenses/LICENSE.txt,sha256=T6i7kq7F5gIPfcno9FCxU5Hcwm22Bjq0uHZV3ElcjsQ,1061
40
- limits-4.7.2.dist-info/METADATA,sha256=nqjHZ5XRKBzhgdQ2kofnp2xS1w2Q25JBDPgPtOh0QOs,11379
41
- limits-4.7.2.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
42
- limits-4.7.2.dist-info/top_level.txt,sha256=C7g5ahldPoU2s6iWTaJayUrbGmPK1d6e9t5Nn0vQ2jM,7
43
- limits-4.7.2.dist-info/RECORD,,
39
+ limits-4.8.0.dist-info/licenses/LICENSE.txt,sha256=T6i7kq7F5gIPfcno9FCxU5Hcwm22Bjq0uHZV3ElcjsQ,1061
40
+ limits-4.8.0.dist-info/METADATA,sha256=b1YnStikSsY31eCVn21tMzteOfA3FpofrhAu1U_9J-g,11379
41
+ limits-4.8.0.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
42
+ limits-4.8.0.dist-info/top_level.txt,sha256=C7g5ahldPoU2s6iWTaJayUrbGmPK1d6e9t5Nn0vQ2jM,7
43
+ limits-4.8.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (79.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5