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 +3 -3
- limits/aio/storage/memcached.py +4 -1
- limits/aio/storage/redis/__init__.py +22 -6
- limits/aio/storage/redis/bridge.py +3 -2
- limits/storage/redis.py +5 -2
- limits/storage/redis_cluster.py +5 -2
- limits/storage/redis_sentinel.py +3 -0
- {limits-4.7.2.dist-info → limits-4.8.0.dist-info}/METADATA +1 -1
- {limits-4.7.2.dist-info → limits-4.8.0.dist-info}/RECORD +12 -12
- {limits-4.7.2.dist-info → limits-4.8.0.dist-info}/WHEEL +1 -1
- {limits-4.7.2.dist-info → limits-4.8.0.dist-info}/licenses/LICENSE.txt +0 -0
- {limits-4.7.2.dist-info → limits-4.8.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-04-
|
|
11
|
+
"date": "2025-04-23T13:02:47-0700",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "4.
|
|
14
|
+
"full-revisionid": "742f7a2fdf50b5fbd1a263bd50b5dc7f1f64aa48",
|
|
15
|
+
"version": "4.8.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
limits/aio/storage/memcached.py
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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(
|
|
109
|
+
self.bridge = RedispyBridge(
|
|
110
|
+
uri, self.dependencies["redis"].module, key_prefix
|
|
111
|
+
)
|
|
104
112
|
else:
|
|
105
|
-
self.bridge = CoredisBridge(
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
limits/storage/redis_cluster.py
CHANGED
|
@@ -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
|
|
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.
|
limits/storage/redis_sentinel.py
CHANGED
|
@@ -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,5 +1,5 @@
|
|
|
1
1
|
limits/__init__.py,sha256=gPUFrt02kHF_syLjiVRSs-S4UVGpRMcM2VMFNhF6G24,748
|
|
2
|
-
limits/_version.py,sha256=
|
|
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=
|
|
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=
|
|
19
|
-
limits/aio/storage/redis/bridge.py,sha256=
|
|
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=
|
|
36
|
-
limits/storage/redis_cluster.py,sha256=
|
|
37
|
-
limits/storage/redis_sentinel.py,sha256=
|
|
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.
|
|
40
|
-
limits-4.
|
|
41
|
-
limits-4.
|
|
42
|
-
limits-4.
|
|
43
|
-
limits-4.
|
|
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,,
|
|
File without changes
|
|
File without changes
|