limits 3.3.1__tar.gz → 3.5.0__tar.gz
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-3.3.1 → limits-3.5.0}/HISTORY.rst +26 -0
- {limits-3.3.1 → limits-3.5.0}/PKG-INFO +1 -1
- {limits-3.3.1 → limits-3.5.0}/limits/_version.py +3 -3
- {limits-3.3.1 → limits-3.5.0}/limits/aio/storage/redis.py +0 -7
- {limits-3.3.1 → limits-3.5.0}/limits/aio/strategies.py +1 -2
- {limits-3.3.1 → limits-3.5.0}/limits/resources/redis/lua_scripts/acquire_moving_window.lua +4 -5
- {limits-3.3.1 → limits-3.5.0}/limits/storage/memory.py +1 -1
- {limits-3.3.1 → limits-3.5.0}/limits/storage/redis_sentinel.py +1 -6
- {limits-3.3.1 → limits-3.5.0}/limits/strategies.py +1 -2
- {limits-3.3.1 → limits-3.5.0}/limits.egg-info/PKG-INFO +1 -1
- {limits-3.3.1 → limits-3.5.0}/limits.egg-info/SOURCES.txt +0 -1
- {limits-3.3.1 → limits-3.5.0}/requirements/docs.txt +4 -4
- {limits-3.3.1 → limits-3.5.0}/tests/test_strategy.py +10 -0
- limits-3.3.1/limits/resources/redis/lua_scripts/gcra_consume.lua +0 -50
- {limits-3.3.1 → limits-3.5.0}/CLASSIFIERS +0 -0
- {limits-3.3.1 → limits-3.5.0}/CONTRIBUTIONS.rst +0 -0
- {limits-3.3.1 → limits-3.5.0}/LICENSE.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/MANIFEST.in +0 -0
- {limits-3.3.1 → limits-3.5.0}/README.rst +0 -0
- {limits-3.3.1 → limits-3.5.0}/doc/Makefile +0 -0
- {limits-3.3.1 → limits-3.5.0}/doc/source/_static/custom.css +0 -0
- {limits-3.3.1 → limits-3.5.0}/doc/source/api.rst +0 -0
- {limits-3.3.1 → limits-3.5.0}/doc/source/async.rst +0 -0
- {limits-3.3.1 → limits-3.5.0}/doc/source/changelog.rst +0 -0
- {limits-3.3.1 → limits-3.5.0}/doc/source/conf.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/doc/source/custom-storage.rst +0 -0
- {limits-3.3.1 → limits-3.5.0}/doc/source/index.rst +0 -0
- {limits-3.3.1 → limits-3.5.0}/doc/source/installation.rst +0 -0
- {limits-3.3.1 → limits-3.5.0}/doc/source/quickstart.rst +0 -0
- {limits-3.3.1 → limits-3.5.0}/doc/source/storage.rst +0 -0
- {limits-3.3.1 → limits-3.5.0}/doc/source/strategies.rst +0 -0
- {limits-3.3.1 → limits-3.5.0}/doc/source/theme_config.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/__init__.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/aio/__init__.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/aio/storage/__init__.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/aio/storage/base.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/aio/storage/etcd.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/aio/storage/memcached.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/aio/storage/memory.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/aio/storage/mongodb.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/errors.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/limits.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/py.typed +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/resources/redis/lua_scripts/clear_keys.lua +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/resources/redis/lua_scripts/incr_expire.lua +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/resources/redis/lua_scripts/moving_window.lua +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/storage/__init__.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/storage/base.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/storage/etcd.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/storage/memcached.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/storage/mongodb.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/storage/redis.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/storage/redis_cluster.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/storage/registry.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/typing.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/util.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits/version.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits.egg-info/dependency_links.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits.egg-info/not-zip-safe +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits.egg-info/requires.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/limits.egg-info/top_level.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/pyproject.toml +0 -0
- {limits-3.3.1 → limits-3.5.0}/requirements/ci.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/requirements/dev.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/requirements/main.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/requirements/storage/async-etcd.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/requirements/storage/async-memcached.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/requirements/storage/async-mongodb.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/requirements/storage/async-redis.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/requirements/storage/etcd.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/requirements/storage/memcached.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/requirements/storage/mongodb.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/requirements/storage/redis.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/requirements/storage/rediscluster.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/requirements/test.txt +0 -0
- {limits-3.3.1 → limits-3.5.0}/setup.cfg +0 -0
- {limits-3.3.1 → limits-3.5.0}/setup.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/tests/test_limit_granularities.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/tests/test_limits.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/tests/test_ratelimit_parser.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/tests/test_storage.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/tests/test_utils.py +0 -0
- {limits-3.3.1 → limits-3.5.0}/versioneer.py +0 -0
|
@@ -3,6 +3,30 @@
|
|
|
3
3
|
Changelog
|
|
4
4
|
=========
|
|
5
5
|
|
|
6
|
+
v3.5.0
|
|
7
|
+
------
|
|
8
|
+
Release Date: 2023-05-16
|
|
9
|
+
|
|
10
|
+
* Bug Fix
|
|
11
|
+
|
|
12
|
+
* Handle ``cost`` > 8000 when using redis
|
|
13
|
+
* Remove arbitrary default timeout for redis+sentinel
|
|
14
|
+
|
|
15
|
+
v3.4.0
|
|
16
|
+
------
|
|
17
|
+
Release Date: 2023-04-17
|
|
18
|
+
|
|
19
|
+
* Bug Fix
|
|
20
|
+
|
|
21
|
+
* Remove use of weakreferences to storages in strategy
|
|
22
|
+
classes as this was not documented or required and
|
|
23
|
+
led to usability issues.
|
|
24
|
+
|
|
25
|
+
* Chores
|
|
26
|
+
|
|
27
|
+
* Update documentation dependencies
|
|
28
|
+
* Remove unused gcra lua script
|
|
29
|
+
|
|
6
30
|
v3.3.1
|
|
7
31
|
------
|
|
8
32
|
Release Date: 2023-03-22
|
|
@@ -582,6 +606,8 @@ Release Date: 2015-01-08
|
|
|
582
606
|
|
|
583
607
|
|
|
584
608
|
|
|
609
|
+
|
|
610
|
+
|
|
585
611
|
|
|
586
612
|
|
|
587
613
|
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2023-
|
|
11
|
+
"date": "2023-05-16T15:04:57-0700",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "3.
|
|
14
|
+
"full-revisionid": "d61ade38645d742e4614dd1c1c0283d6ace40bf0",
|
|
15
|
+
"version": "3.5.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -349,11 +349,6 @@ class RedisSentinelStorage(RedisStorage):
|
|
|
349
349
|
STORAGE_SCHEME = ["async+redis+sentinel"]
|
|
350
350
|
"""The storage scheme for redis accessed via a redis sentinel installation"""
|
|
351
351
|
|
|
352
|
-
DEFAULT_OPTIONS: Dict[str, Union[float, str, bool]] = {
|
|
353
|
-
"stream_timeout": 0.2,
|
|
354
|
-
}
|
|
355
|
-
"Default options passed to :class:`~coredis.sentinel.Sentinel`"
|
|
356
|
-
|
|
357
352
|
DEPENDENCIES = {"coredis.sentinel": Version("3.4.0")}
|
|
358
353
|
|
|
359
354
|
def __init__(
|
|
@@ -402,8 +397,6 @@ class RedisSentinelStorage(RedisStorage):
|
|
|
402
397
|
if self.service_name is None:
|
|
403
398
|
raise ConfigurationError("'service_name' not provided")
|
|
404
399
|
|
|
405
|
-
connection_options.setdefault("stream_timeout", 0.2)
|
|
406
|
-
|
|
407
400
|
super(RedisStorage, self).__init__()
|
|
408
401
|
|
|
409
402
|
self.dependency = self.dependencies["coredis.sentinel"].module
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Asynchronous rate limiting strategies
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import weakref
|
|
6
5
|
from abc import ABC, abstractmethod
|
|
7
6
|
from typing import cast
|
|
8
7
|
|
|
@@ -15,7 +14,7 @@ from .storage import MovingWindowSupport, Storage
|
|
|
15
14
|
class RateLimiter(ABC):
|
|
16
15
|
def __init__(self, storage: StorageTypes):
|
|
17
16
|
assert isinstance(storage, Storage)
|
|
18
|
-
self.storage: Storage =
|
|
17
|
+
self.storage: Storage = storage
|
|
19
18
|
|
|
20
19
|
@abstractmethod
|
|
21
20
|
async def hit(self, item: RateLimitItem, *identifiers: str, cost: int = 1) -> bool:
|
|
@@ -9,15 +9,14 @@ end
|
|
|
9
9
|
|
|
10
10
|
local entry = redis.call('lindex', KEYS[1], limit - amount)
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
if entry and tonumber(entry) >= timestamp - expiry then
|
|
14
13
|
return false
|
|
15
14
|
end
|
|
16
|
-
|
|
17
|
-
for i=1, amount do
|
|
18
|
-
|
|
15
|
+
|
|
16
|
+
for i = 1, amount do
|
|
17
|
+
redis.call('lpush', KEYS[1], timestamp)
|
|
19
18
|
end
|
|
20
|
-
|
|
19
|
+
|
|
21
20
|
redis.call('ltrim', KEYS[1], 0, limit - 1)
|
|
22
21
|
redis.call('expire', KEYS[1], expiry)
|
|
23
22
|
|
|
@@ -7,7 +7,7 @@ from limits.storage.base import MovingWindowSupport, Storage
|
|
|
7
7
|
from limits.typing import Dict, List, Optional, Tuple
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class LockableEntry(threading._RLock):
|
|
10
|
+
class LockableEntry(threading._RLock): # type: ignore
|
|
11
11
|
def __init__(self, expiry: float) -> None:
|
|
12
12
|
self.atime = time.time()
|
|
13
13
|
self.expiry = self.atime + expiry
|
|
@@ -21,11 +21,6 @@ class RedisSentinelStorage(RedisStorage):
|
|
|
21
21
|
STORAGE_SCHEME = ["redis+sentinel"]
|
|
22
22
|
"""The storage scheme for redis accessed via a redis sentinel installation"""
|
|
23
23
|
|
|
24
|
-
DEFAULT_OPTIONS: Dict[str, Union[float, str, bool]] = {
|
|
25
|
-
"socket_timeout": 0.2,
|
|
26
|
-
}
|
|
27
|
-
"Default options passed to :class:`~redis.sentinel.Sentinel`"
|
|
28
|
-
|
|
29
24
|
DEPENDENCIES = {"redis.sentinel": Version("3.0")}
|
|
30
25
|
|
|
31
26
|
def __init__(
|
|
@@ -79,7 +74,7 @@ class RedisSentinelStorage(RedisStorage):
|
|
|
79
74
|
self.sentinel: "redis.sentinel.Sentinel" = sentinel_dep.Sentinel(
|
|
80
75
|
sentinel_configuration,
|
|
81
76
|
sentinel_kwargs={**parsed_auth, **sentinel_options},
|
|
82
|
-
**{**
|
|
77
|
+
**{**parsed_auth, **options}
|
|
83
78
|
)
|
|
84
79
|
self.storage = self.sentinel.master_for(self.service_name)
|
|
85
80
|
self.storage_slave = self.sentinel.slave_for(self.service_name)
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Rate limiting strategies
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import weakref
|
|
6
5
|
from abc import ABCMeta, abstractmethod
|
|
7
6
|
from typing import Dict, Type, Union, cast
|
|
8
7
|
|
|
@@ -14,7 +13,7 @@ from .util import WindowStats
|
|
|
14
13
|
class RateLimiter(metaclass=ABCMeta):
|
|
15
14
|
def __init__(self, storage: StorageTypes):
|
|
16
15
|
assert isinstance(storage, Storage)
|
|
17
|
-
self.storage: Storage =
|
|
16
|
+
self.storage: Storage = storage
|
|
18
17
|
|
|
19
18
|
@abstractmethod
|
|
20
19
|
def hit(self, item: RateLimitItem, *identifiers: str, cost: int = 1) -> bool:
|
|
@@ -47,7 +47,6 @@ limits/aio/storage/mongodb.py
|
|
|
47
47
|
limits/aio/storage/redis.py
|
|
48
48
|
limits/resources/redis/lua_scripts/acquire_moving_window.lua
|
|
49
49
|
limits/resources/redis/lua_scripts/clear_keys.lua
|
|
50
|
-
limits/resources/redis/lua_scripts/gcra_consume.lua
|
|
51
50
|
limits/resources/redis/lua_scripts/incr_expire.lua
|
|
52
51
|
limits/resources/redis/lua_scripts/moving_window.lua
|
|
53
52
|
limits/storage/__init__.py
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
-r main.txt
|
|
2
|
-
furo==
|
|
2
|
+
furo==2023.3.27
|
|
3
3
|
Sphinx>=5
|
|
4
|
-
sphinx-copybutton==0.5.
|
|
4
|
+
sphinx-copybutton==0.5.2
|
|
5
5
|
sphinx-autobuild==2021.3.14
|
|
6
|
-
sphinxext-opengraph==0.8.
|
|
7
|
-
sphinx-inline-tabs==
|
|
6
|
+
sphinxext-opengraph==0.8.2
|
|
7
|
+
sphinx-inline-tabs==2023.4.21
|
|
8
8
|
sphinx-paramlinks==0.5.4
|
|
9
9
|
sphinxcontrib-programoutput==0.17
|
|
10
10
|
|
|
@@ -134,6 +134,16 @@ class TestWindow:
|
|
|
134
134
|
limiter.clear(five_per_min)
|
|
135
135
|
assert limiter.hit(five_per_min)
|
|
136
136
|
|
|
137
|
+
@moving_window_storage
|
|
138
|
+
def test_moving_window_huge_cost_sync(self, uri, args, fixture):
|
|
139
|
+
storage = storage_from_string(uri, **args)
|
|
140
|
+
limiter = MovingWindowRateLimiter(storage)
|
|
141
|
+
many_per_min = RateLimitItemPerMinute(1_000_000)
|
|
142
|
+
limiter.hit(many_per_min, cost=1_000_000)
|
|
143
|
+
assert not limiter.hit(many_per_min, cost=2)
|
|
144
|
+
limiter.clear(many_per_min)
|
|
145
|
+
assert limiter.hit(many_per_min)
|
|
146
|
+
|
|
137
147
|
@pytest.mark.memcached
|
|
138
148
|
def test_moving_window_memcached(self, memcached):
|
|
139
149
|
storage = MemcachedStorage("memcached://localhost:22122")
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
local rate_limit_key = KEYS[1]
|
|
2
|
-
local burst = ARGV[1]
|
|
3
|
-
local rate = ARGV[2]
|
|
4
|
-
local period = ARGV[3]
|
|
5
|
-
local cost = ARGV[4]
|
|
6
|
-
|
|
7
|
-
local emission_interval = period / rate
|
|
8
|
-
local increment = emission_interval * cost
|
|
9
|
-
local burst_offset = emission_interval * burst
|
|
10
|
-
|
|
11
|
-
local tat = redis.call("GET", rate_limit_key)
|
|
12
|
-
|
|
13
|
-
local time = redis.call("TIME")
|
|
14
|
-
local now = tonumber(time[1]) + tonumber(time[2])/1000000.0
|
|
15
|
-
|
|
16
|
-
if not tat then
|
|
17
|
-
tat = now
|
|
18
|
-
else
|
|
19
|
-
tat = tonumber(tat)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
tat = math.max(tat, now)
|
|
23
|
-
|
|
24
|
-
local new_tat = tat + increment
|
|
25
|
-
local allow_at = new_tat - burst_offset
|
|
26
|
-
local diff = now - allow_at
|
|
27
|
-
|
|
28
|
-
local consumed = 0
|
|
29
|
-
local retry_in = 0
|
|
30
|
-
local reset_in
|
|
31
|
-
|
|
32
|
-
local remaining = math.floor(diff / emission_interval) -- poor man's round
|
|
33
|
-
if remaining < 0 then
|
|
34
|
-
consumed = 0
|
|
35
|
-
remaining = math.floor((now - (tat - burst_offset)) / emission_interval)
|
|
36
|
-
reset_in = math.ceil(tat - now)
|
|
37
|
-
retry_in = math.ceil(diff * -1)
|
|
38
|
-
elseif remaining == 0 and increment <= 0 then
|
|
39
|
-
consumed = 1
|
|
40
|
-
remaining = 0
|
|
41
|
-
reset_in = math.ceil(tat - now)
|
|
42
|
-
else
|
|
43
|
-
consumed = 1
|
|
44
|
-
reset_in = math.ceil(new_tat - now)
|
|
45
|
-
if increment > 0 then
|
|
46
|
-
redis.call("SET", rate_limit_key, new_tat, "PX", reset_in)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
return {consumed, remaining, retry_in, reset_in}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|