limits 4.1__tar.gz → 4.3__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-4.1 → limits-4.3}/CLASSIFIERS +0 -1
- {limits-4.1 → limits-4.3}/HISTORY.rst +26 -0
- {limits-4.1 → limits-4.3}/PKG-INFO +9 -4
- {limits-4.1 → limits-4.3}/README.rst +1 -1
- {limits-4.1 → limits-4.3}/doc/source/async.rst +4 -1
- {limits-4.1 → limits-4.3}/doc/source/conf.py +1 -0
- {limits-4.1 → limits-4.3}/doc/source/index.rst +2 -2
- {limits-4.1 → limits-4.3}/doc/source/installation.rst +28 -0
- {limits-4.1 → limits-4.3}/doc/source/storage.rst +39 -10
- {limits-4.1 → limits-4.3}/doc/source/strategies.rst +1 -1
- {limits-4.1 → limits-4.3}/limits/__init__.py +8 -6
- {limits-4.1 → limits-4.3}/limits/_version.py +4 -4
- {limits-4.1 → limits-4.3}/limits/aio/__init__.py +2 -0
- {limits-4.1 → limits-4.3}/limits/aio/storage/__init__.py +6 -4
- {limits-4.1 → limits-4.3}/limits/aio/storage/base.py +5 -8
- {limits-4.1 → limits-4.3}/limits/aio/storage/etcd.py +6 -4
- {limits-4.1 → limits-4.3}/limits/aio/storage/memcached.py +6 -4
- {limits-4.1 → limits-4.3}/limits/aio/storage/memory.py +42 -26
- {limits-4.1 → limits-4.3}/limits/aio/storage/mongodb.py +4 -7
- limits-4.3/limits/aio/storage/redis/__init__.py +402 -0
- limits-4.3/limits/aio/storage/redis/bridge.py +120 -0
- limits-4.3/limits/aio/storage/redis/coredis.py +209 -0
- limits-4.3/limits/aio/storage/redis/redispy.py +257 -0
- limits-4.3/limits/aio/storage/redis/valkey.py +9 -0
- {limits-4.1 → limits-4.3}/limits/aio/strategies.py +4 -2
- {limits-4.1 → limits-4.3}/limits/errors.py +2 -0
- {limits-4.1 → limits-4.3}/limits/storage/__init__.py +14 -11
- {limits-4.1 → limits-4.3}/limits/storage/base.py +5 -10
- {limits-4.1 → limits-4.3}/limits/storage/etcd.py +6 -4
- {limits-4.1 → limits-4.3}/limits/storage/memcached.py +6 -7
- {limits-4.1 → limits-4.3}/limits/storage/memory.py +42 -31
- {limits-4.1 → limits-4.3}/limits/storage/mongodb.py +7 -10
- {limits-4.1 → limits-4.3}/limits/storage/redis.py +48 -18
- {limits-4.1 → limits-4.3}/limits/storage/redis_cluster.py +31 -11
- {limits-4.1 → limits-4.3}/limits/storage/redis_sentinel.py +35 -11
- {limits-4.1 → limits-4.3}/limits/storage/registry.py +1 -3
- {limits-4.1 → limits-4.3}/limits/strategies.py +11 -9
- {limits-4.1 → limits-4.3}/limits/typing.py +45 -42
- {limits-4.1 → limits-4.3}/limits/util.py +12 -12
- {limits-4.1 → limits-4.3}/limits.egg-info/PKG-INFO +9 -4
- {limits-4.1 → limits-4.3}/limits.egg-info/SOURCES.txt +7 -1
- {limits-4.1 → limits-4.3}/limits.egg-info/requires.txt +7 -0
- limits-4.3/pyproject.toml +25 -0
- limits-4.3/requirements/storage/async-valkey.txt +1 -0
- limits-4.3/requirements/storage/valkey.txt +1 -0
- {limits-4.1 → limits-4.3}/requirements/test.txt +2 -0
- {limits-4.1 → limits-4.3}/setup.cfg +0 -8
- {limits-4.1 → limits-4.3}/setup.py +3 -1
- {limits-4.1 → limits-4.3}/tests/test_limit_granularities.py +2 -0
- {limits-4.1 → limits-4.3}/tests/test_limits.py +2 -0
- {limits-4.1 → limits-4.3}/tests/test_ratelimit_parser.py +2 -0
- {limits-4.1 → limits-4.3}/tests/test_storage.py +5 -3
- {limits-4.1 → limits-4.3}/tests/test_strategy.py +2 -0
- {limits-4.1 → limits-4.3}/tests/test_utils.py +2 -0
- {limits-4.1 → limits-4.3}/versioneer.py +430 -342
- limits-4.1/limits/aio/storage/redis.py +0 -555
- limits-4.1/pyproject.toml +0 -10
- {limits-4.1 → limits-4.3}/CONTRIBUTIONS.rst +0 -0
- {limits-4.1 → limits-4.3}/LICENSE.txt +0 -0
- {limits-4.1 → limits-4.3}/MANIFEST.in +0 -0
- {limits-4.1 → limits-4.3}/doc/Makefile +0 -0
- {limits-4.1 → limits-4.3}/doc/source/_static/custom.css +0 -0
- {limits-4.1 → limits-4.3}/doc/source/api.rst +0 -0
- {limits-4.1 → limits-4.3}/doc/source/changelog.rst +0 -0
- {limits-4.1 → limits-4.3}/doc/source/custom-storage.rst +0 -0
- {limits-4.1 → limits-4.3}/doc/source/quickstart.rst +0 -0
- {limits-4.1 → limits-4.3}/doc/source/theme_config.py +0 -0
- {limits-4.1 → limits-4.3}/limits/limits.py +0 -0
- {limits-4.1 → limits-4.3}/limits/py.typed +0 -0
- {limits-4.1 → limits-4.3}/limits/resources/redis/lua_scripts/acquire_moving_window.lua +0 -0
- {limits-4.1 → limits-4.3}/limits/resources/redis/lua_scripts/acquire_sliding_window.lua +0 -0
- {limits-4.1 → limits-4.3}/limits/resources/redis/lua_scripts/clear_keys.lua +0 -0
- {limits-4.1 → limits-4.3}/limits/resources/redis/lua_scripts/incr_expire.lua +0 -0
- {limits-4.1 → limits-4.3}/limits/resources/redis/lua_scripts/moving_window.lua +0 -0
- {limits-4.1 → limits-4.3}/limits/resources/redis/lua_scripts/sliding_window.lua +0 -0
- {limits-4.1 → limits-4.3}/limits/version.py +0 -0
- {limits-4.1 → limits-4.3}/limits.egg-info/dependency_links.txt +0 -0
- {limits-4.1 → limits-4.3}/limits.egg-info/not-zip-safe +0 -0
- {limits-4.1 → limits-4.3}/limits.egg-info/top_level.txt +0 -0
- {limits-4.1 → limits-4.3}/requirements/ci.txt +0 -0
- {limits-4.1 → limits-4.3}/requirements/dev.txt +0 -0
- {limits-4.1 → limits-4.3}/requirements/docs.txt +0 -0
- {limits-4.1 → limits-4.3}/requirements/main.txt +0 -0
- {limits-4.1 → limits-4.3}/requirements/storage/async-etcd.txt +0 -0
- {limits-4.1 → limits-4.3}/requirements/storage/async-memcached.txt +0 -0
- {limits-4.1 → limits-4.3}/requirements/storage/async-mongodb.txt +0 -0
- {limits-4.1 → limits-4.3}/requirements/storage/async-redis.txt +0 -0
- {limits-4.1 → limits-4.3}/requirements/storage/etcd.txt +0 -0
- {limits-4.1 → limits-4.3}/requirements/storage/memcached.txt +0 -0
- {limits-4.1 → limits-4.3}/requirements/storage/mongodb.txt +0 -0
- {limits-4.1 → limits-4.3}/requirements/storage/redis.txt +0 -0
- {limits-4.1 → limits-4.3}/requirements/storage/rediscluster.txt +0 -0
|
@@ -5,7 +5,6 @@ Operating System :: MacOS
|
|
|
5
5
|
Operating System :: POSIX :: Linux
|
|
6
6
|
Operating System :: OS Independent
|
|
7
7
|
Topic :: Software Development :: Libraries :: Python Modules
|
|
8
|
-
Programming Language :: Python :: 3.9
|
|
9
8
|
Programming Language :: Python :: 3.10
|
|
10
9
|
Programming Language :: Python :: 3.11
|
|
11
10
|
Programming Language :: Python :: 3.12
|
|
@@ -3,6 +3,30 @@
|
|
|
3
3
|
Changelog
|
|
4
4
|
=========
|
|
5
5
|
|
|
6
|
+
v4.3
|
|
7
|
+
----
|
|
8
|
+
Release Date: 2025-03-14
|
|
9
|
+
|
|
10
|
+
* Feature
|
|
11
|
+
|
|
12
|
+
* Add support for ``valkey://`` schemas and using ``valkey-py``
|
|
13
|
+
dependency
|
|
14
|
+
|
|
15
|
+
* Compatibility
|
|
16
|
+
|
|
17
|
+
* Drop support for python 3.9
|
|
18
|
+
* Improve typing to use python 3.10+ features
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
v4.2
|
|
22
|
+
----
|
|
23
|
+
Release Date: 2025-03-11
|
|
24
|
+
|
|
25
|
+
* Feature
|
|
26
|
+
|
|
27
|
+
* Add support for using ``redis-py`` instead of ``coredis``
|
|
28
|
+
which asyncio + redis storages
|
|
29
|
+
|
|
6
30
|
v4.1
|
|
7
31
|
----
|
|
8
32
|
Release Date: 2025-03-07
|
|
@@ -781,6 +805,8 @@ Release Date: 2015-01-08
|
|
|
781
805
|
|
|
782
806
|
|
|
783
807
|
|
|
808
|
+
|
|
809
|
+
|
|
784
810
|
|
|
785
811
|
|
|
786
812
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: limits
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3
|
|
4
4
|
Summary: Rate limiting utilities
|
|
5
5
|
Home-page: https://limits.readthedocs.org
|
|
6
6
|
Author: Ali-Akber Saifee
|
|
@@ -14,13 +14,12 @@ Classifier: Operating System :: MacOS
|
|
|
14
14
|
Classifier: Operating System :: POSIX :: Linux
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
21
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
23
|
-
Requires-Python: >=3.
|
|
22
|
+
Requires-Python: >=3.10
|
|
24
23
|
License-File: LICENSE.txt
|
|
25
24
|
Requires-Dist: deprecated>=1.2
|
|
26
25
|
Requires-Dist: packaging<25,>=21
|
|
@@ -35,6 +34,8 @@ Provides-Extra: mongodb
|
|
|
35
34
|
Requires-Dist: pymongo<5,>4.1; extra == "mongodb"
|
|
36
35
|
Provides-Extra: etcd
|
|
37
36
|
Requires-Dist: etcd3; extra == "etcd"
|
|
37
|
+
Provides-Extra: valkey
|
|
38
|
+
Requires-Dist: valkey>=6; extra == "valkey"
|
|
38
39
|
Provides-Extra: async-redis
|
|
39
40
|
Requires-Dist: coredis<5,>=3.4.0; extra == "async-redis"
|
|
40
41
|
Provides-Extra: async-memcached
|
|
@@ -44,17 +45,21 @@ Provides-Extra: async-mongodb
|
|
|
44
45
|
Requires-Dist: motor<4,>=3; extra == "async-mongodb"
|
|
45
46
|
Provides-Extra: async-etcd
|
|
46
47
|
Requires-Dist: aetcd; extra == "async-etcd"
|
|
48
|
+
Provides-Extra: async-valkey
|
|
49
|
+
Requires-Dist: valkey>=6; extra == "async-valkey"
|
|
47
50
|
Provides-Extra: all
|
|
48
51
|
Requires-Dist: redis!=4.5.2,!=4.5.3,<6.0.0,>3; extra == "all"
|
|
49
52
|
Requires-Dist: redis!=4.5.2,!=4.5.3,>=4.2.0; extra == "all"
|
|
50
53
|
Requires-Dist: pymemcache<5.0.0,>3; extra == "all"
|
|
51
54
|
Requires-Dist: pymongo<5,>4.1; extra == "all"
|
|
52
55
|
Requires-Dist: etcd3; extra == "all"
|
|
56
|
+
Requires-Dist: valkey>=6; extra == "all"
|
|
53
57
|
Requires-Dist: coredis<5,>=3.4.0; extra == "all"
|
|
54
58
|
Requires-Dist: emcache>=0.6.1; python_version < "3.11" and extra == "all"
|
|
55
59
|
Requires-Dist: emcache>=1; (python_version >= "3.11" and python_version < "3.13.0") and extra == "all"
|
|
56
60
|
Requires-Dist: motor<4,>=3; extra == "all"
|
|
57
61
|
Requires-Dist: aetcd; extra == "all"
|
|
62
|
+
Requires-Dist: valkey>=6; extra == "all"
|
|
58
63
|
Dynamic: author
|
|
59
64
|
Dynamic: author-email
|
|
60
65
|
Dynamic: classifier
|
|
@@ -126,7 +131,7 @@ Moving Window
|
|
|
126
131
|
|
|
127
132
|
This strategy adds each request’s timestamp to a log if the ``nth`` oldest entry (where ``n``
|
|
128
133
|
is the limit) is either not present or is older than the duration of the window (for example with a rate limit of
|
|
129
|
-
``10 requests per minute`` if there are either less than 10 entries or the 10th oldest entry is
|
|
134
|
+
``10 requests per minute`` if there are either less than 10 entries or the 10th oldest entry is at least
|
|
130
135
|
60 seconds old). Upon adding a new entry to the log "expired" entries are truncated.
|
|
131
136
|
|
|
132
137
|
For example, with a rate limit of 10 requests per minute:
|
|
@@ -57,7 +57,7 @@ Moving Window
|
|
|
57
57
|
|
|
58
58
|
This strategy adds each request’s timestamp to a log if the ``nth`` oldest entry (where ``n``
|
|
59
59
|
is the limit) is either not present or is older than the duration of the window (for example with a rate limit of
|
|
60
|
-
``10 requests per minute`` if there are either less than 10 entries or the 10th oldest entry is
|
|
60
|
+
``10 requests per minute`` if there are either less than 10 entries or the 10th oldest entry is at least
|
|
61
61
|
60 seconds old). Upon adding a new entry to the log "expired" entries are truncated.
|
|
62
62
|
|
|
63
63
|
For example, with a rate limit of 10 requests per minute:
|
|
@@ -10,7 +10,10 @@ A new namespace ``limits.aio`` is available which mirrors the original
|
|
|
10
10
|
The following async storage backends are implemented:
|
|
11
11
|
|
|
12
12
|
- In-Memory
|
|
13
|
-
- Redis (via `coredis <https://coredis.readthedocs.org>`_
|
|
13
|
+
- Redis (via `coredis <https://coredis.readthedocs.org>`_
|
|
14
|
+
or `redis-py <https://redis-py.readthedocs.io>`_. Refer to
|
|
15
|
+
:paramref:`limits.aio.storage.RedisStorage.implementation` for
|
|
16
|
+
details on selecting the dependency)
|
|
14
17
|
- Memcached (via `emcache <https://emcache.readthedocs.org>`_)
|
|
15
18
|
- MongoDB (via `motor <https://motor.readthedocs.org>`_)
|
|
16
19
|
- Etcd (via `aetcd <https://aetcd.readthedocs.org>`_)
|
|
@@ -81,4 +81,5 @@ intersphinx_mapping = {
|
|
|
81
81
|
"pymongo": ("https://pymongo.readthedocs.io/en/stable/", None),
|
|
82
82
|
"python-etcd3": ("https://python-etcd3.readthedocs.io/en/latest/", None),
|
|
83
83
|
"aetcd": ("https://aetcd.readthedocs.io/en/latest/", None),
|
|
84
|
+
"valkey-py": ("https://valkey-py.readthedocs.io/en/latest/", None),
|
|
84
85
|
}
|
|
@@ -55,12 +55,12 @@ To get started
|
|
|
55
55
|
|
|
56
56
|
.. code:: console
|
|
57
57
|
|
|
58
|
-
$ git clone
|
|
58
|
+
$ git clone https://github.com/alisaifee/limits.git
|
|
59
59
|
$ cd limits
|
|
60
60
|
$ pip install -r requirements/dev.txt
|
|
61
61
|
|
|
62
62
|
Since `limits` integrates with various backend storages, local development and running tests
|
|
63
|
-
requires a
|
|
63
|
+
requires a working `docker & docker-compose installation <https://docs.docker.com/compose/gettingstarted/>`_.
|
|
64
64
|
|
|
65
65
|
Running the tests will start the relevant containers automatically - but will leave them running
|
|
66
66
|
so as to not incur the overhead of starting up on each test run. To run the tests:
|
|
@@ -58,6 +58,16 @@ Install the package with pip:
|
|
|
58
58
|
|
|
59
59
|
.. literalinclude:: ../../requirements/storage/etcd.txt
|
|
60
60
|
|
|
61
|
+
.. tab:: Valkey
|
|
62
|
+
|
|
63
|
+
.. code:: console
|
|
64
|
+
|
|
65
|
+
$ pip install limits[valkey]
|
|
66
|
+
|
|
67
|
+
Includes:
|
|
68
|
+
|
|
69
|
+
.. literalinclude:: ../../requirements/storage/valkey.txt
|
|
70
|
+
|
|
61
71
|
More details around the specifics of each storage backend can be
|
|
62
72
|
found in :ref:`storage`
|
|
63
73
|
|
|
@@ -79,6 +89,12 @@ along with the package using the following extras:
|
|
|
79
89
|
|
|
80
90
|
.. literalinclude:: ../../requirements/storage/async-redis.txt
|
|
81
91
|
|
|
92
|
+
.. versionadded:: 4.2
|
|
93
|
+
:pypi:`redis` if installed can be used instead of :pypi:`coredis` by setting
|
|
94
|
+
:paramref:`~limits.aio.storage.Redis.implementation` to ``redispy``.
|
|
95
|
+
See :class:`limits.aio.storage.RedisStorage` for more details.
|
|
96
|
+
|
|
97
|
+
|
|
82
98
|
.. tab:: Memcached
|
|
83
99
|
|
|
84
100
|
.. code:: console
|
|
@@ -108,3 +124,15 @@ along with the package using the following extras:
|
|
|
108
124
|
Includes:
|
|
109
125
|
|
|
110
126
|
.. literalinclude:: ../../requirements/storage/async-etcd.txt
|
|
127
|
+
|
|
128
|
+
.. tab:: Valkey
|
|
129
|
+
|
|
130
|
+
.. code:: console
|
|
131
|
+
|
|
132
|
+
$ pip install limits[async-valkey]
|
|
133
|
+
|
|
134
|
+
Includes:
|
|
135
|
+
|
|
136
|
+
.. literalinclude:: ../../requirements/storage/async-valkey.txt
|
|
137
|
+
|
|
138
|
+
|
|
@@ -31,17 +31,22 @@ the results in `github <https://github.com/alisaifee/limits/actions/workflows/co
|
|
|
31
31
|
|
|
32
32
|
.. literalinclude:: ../../requirements/storage/async-redis.txt
|
|
33
33
|
|
|
34
|
+
.. note::
|
|
35
|
+
.. versionadded:: 4.2
|
|
36
|
+
:pypi:`redis` can be used instead of :pypi:`coredis` by setting
|
|
37
|
+
:paramref:`limits.aio.storage.RedisStorage.implementation` to ``redispy``
|
|
38
|
+
|
|
34
39
|
`Redis <https://redis.io>`_
|
|
35
40
|
|
|
36
|
-
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_REDIS_SERVER_VERSION=[\d\.]+' | cut -d = -f 2"
|
|
41
|
+
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_REDIS_SERVER_VERSION=[\d\.]+' | cut -d = -f 2 | sort --version-sort | uniq"
|
|
37
42
|
|
|
38
43
|
Redis with SSL
|
|
39
44
|
|
|
40
|
-
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_REDIS_SERVER_SSL_VERSION=[\d\.]+' | cut -d = -f 2"
|
|
45
|
+
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_REDIS_SERVER_SSL_VERSION=[\d\.]+' | cut -d = -f 2 | sort --version-sort | uniq"
|
|
41
46
|
|
|
42
47
|
`Redis Sentinel <https://redis.io/topics/sentinel>`_
|
|
43
48
|
|
|
44
|
-
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_REDIS_SENTINEL_SERVER_VERSION=[\d\.]+' | cut -d = -f 2"
|
|
49
|
+
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_REDIS_SENTINEL_SERVER_VERSION=[\d\.]+' | cut -d = -f 2 | sort --version-sort | uniq"
|
|
45
50
|
|
|
46
51
|
.. tab:: Redis Cluster
|
|
47
52
|
|
|
@@ -53,9 +58,14 @@ the results in `github <https://github.com/alisaifee/limits/actions/workflows/co
|
|
|
53
58
|
|
|
54
59
|
.. literalinclude:: ../../requirements/storage/async-redis.txt
|
|
55
60
|
|
|
61
|
+
.. note::
|
|
62
|
+
.. versionadded:: 4.2
|
|
63
|
+
:pypi:`redis` can be used instead of :pypi:`coredis` by setting
|
|
64
|
+
:paramref:`limits.aio.storage.RedisClusterStorage.implementation` to ``redispy``
|
|
65
|
+
|
|
56
66
|
`Redis cluster <https://redis.io/topics/cluster-tutorial>`_
|
|
57
67
|
|
|
58
|
-
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_REDIS_SERVER_VERSION=[\d\.]+' | cut -d = -f 2"
|
|
68
|
+
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_REDIS_SERVER_VERSION=[\d\.]+' | cut -d = -f 2 | sort --version-sort | uniq"
|
|
59
69
|
|
|
60
70
|
.. tab:: Memcached
|
|
61
71
|
|
|
@@ -69,7 +79,7 @@ the results in `github <https://github.com/alisaifee/limits/actions/workflows/co
|
|
|
69
79
|
|
|
70
80
|
`Memcached <https://memcached.org/>`_
|
|
71
81
|
|
|
72
|
-
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_MEMCACHED_SERVER_VERSION=[\d\.]+' | cut -d = -f 2"
|
|
82
|
+
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_MEMCACHED_SERVER_VERSION=[\d\.]+' | cut -d = -f 2 | sort --version-sort | uniq"
|
|
73
83
|
|
|
74
84
|
.. tab:: MongoDB
|
|
75
85
|
|
|
@@ -83,7 +93,7 @@ the results in `github <https://github.com/alisaifee/limits/actions/workflows/co
|
|
|
83
93
|
|
|
84
94
|
`MongoDB <https://www.mongodb.com/>`_
|
|
85
95
|
|
|
86
|
-
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_MONGODB_SERVER_VERSION=[\d\.]+' | cut -d = -f 2"
|
|
96
|
+
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_MONGODB_SERVER_VERSION=[\d\.]+' | cut -d = -f 2 | sort --version-sort | uniq"
|
|
87
97
|
|
|
88
98
|
.. tab:: Etcd
|
|
89
99
|
|
|
@@ -97,8 +107,21 @@ the results in `github <https://github.com/alisaifee/limits/actions/workflows/co
|
|
|
97
107
|
|
|
98
108
|
`Etcd <https://www.etcd.io/>`_
|
|
99
109
|
|
|
100
|
-
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_ETCD_SERVER_VERSION=[\d\.]+' | cut -d = -f 2"
|
|
110
|
+
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_ETCD_SERVER_VERSION=[\d\.]+' | cut -d = -f 2 | sort --version-sort | uniq"
|
|
101
111
|
|
|
112
|
+
.. tab:: Valkey
|
|
113
|
+
|
|
114
|
+
Dependency versions:
|
|
115
|
+
|
|
116
|
+
.. literalinclude:: ../../requirements/storage/valkey.txt
|
|
117
|
+
|
|
118
|
+
Dependency versions (async):
|
|
119
|
+
|
|
120
|
+
.. literalinclude:: ../../requirements/storage/async-valkey.txt
|
|
121
|
+
|
|
122
|
+
`Valkey <https://www.valkey.io/>`_
|
|
123
|
+
|
|
124
|
+
.. program-output:: bash -c "cat ../../.github/workflows/compatibility.yml | grep -o -P 'LIMITS_VALKEY_SERVER_VERSION=[\d\.]+' | cut -d = -f 2 | sort --version-sort | uniq"
|
|
102
125
|
|
|
103
126
|
Storage scheme
|
|
104
127
|
==============
|
|
@@ -152,7 +175,7 @@ If the redis server is listening over a unix domain socket you can use :code:`re
|
|
|
152
175
|
or :code:`redis+unix:///path/to/socket?db=n` (for database `n`).
|
|
153
176
|
|
|
154
177
|
If the database is password protected the password can be provided in the url, for example
|
|
155
|
-
:code:`redis://:foobared@localhost:6379` or :code:`redis+unix://:foobered/path/to/socket` if using a UDS
|
|
178
|
+
:code:`redis://:foobared@localhost:6379` or :code:`redis+unix://:foobered/path/to/socket` if using a UDS.
|
|
156
179
|
|
|
157
180
|
For scenarios where a redis connection pool is already available and can be reused, it can be provided
|
|
158
181
|
in :paramref:`~limits.storage.storage_from_string.options`, for example::
|
|
@@ -162,6 +185,12 @@ in :paramref:`~limits.storage.storage_from_string.options`, for example::
|
|
|
162
185
|
|
|
163
186
|
Depends on: :pypi:`redis`
|
|
164
187
|
|
|
188
|
+
|
|
189
|
+
.. versionadded:: 4.3
|
|
190
|
+
|
|
191
|
+
If the database ``uri`` scheme uses ``valkey`` instead of ``redis`` the implementation
|
|
192
|
+
used will be from :pypi:`valkey` instead of :pypi:`redis`.
|
|
193
|
+
|
|
165
194
|
Redis+SSL Storage
|
|
166
195
|
-----------------
|
|
167
196
|
|
|
@@ -175,7 +204,7 @@ Depends on: :pypi:`redis`
|
|
|
175
204
|
Redis+Sentinel Storage
|
|
176
205
|
----------------------
|
|
177
206
|
|
|
178
|
-
Requires the location(s) of the redis
|
|
207
|
+
Requires the location(s) of the redis sentinel instances and the `service-name`
|
|
179
208
|
that is monitored by the sentinels.
|
|
180
209
|
:code:`redis+sentinel://localhost:26379/my-redis-service`
|
|
181
210
|
or :code:`redis+sentinel://localhost:26379,localhost:26380/my-redis-service`.
|
|
@@ -187,7 +216,7 @@ When authentication details are provided in the url they will be used for both t
|
|
|
187
216
|
and as connection arguments for the underlying redis nodes managed by the sentinel.
|
|
188
217
|
|
|
189
218
|
If you need fine grained control it is recommended to use the additional :paramref:`~limits.storage.storage_from_string.options`
|
|
190
|
-
arguments. More details can be found in the API documentation for :class:`~limits.storage.RedisSentinelStorage` (or the
|
|
219
|
+
arguments. More details can be found in the API documentation for :class:`~limits.storage.RedisSentinelStorage` (or the async version: :class:`~limits.aio.storage.RedisSentinelStorage`).
|
|
191
220
|
|
|
192
221
|
Depends on: :pypi:`redis`
|
|
193
222
|
|
|
@@ -57,7 +57,7 @@ Moving Window
|
|
|
57
57
|
|
|
58
58
|
This strategy adds each request’s timestamp to a log if the ``nth`` oldest entry (where ``n``
|
|
59
59
|
is the limit) is either not present or is older than the duration of the window (for example with a rate limit of
|
|
60
|
-
``10 requests per minute`` if there are either less than 10 entries or the 10th oldest entry is
|
|
60
|
+
``10 requests per minute`` if there are either less than 10 entries or the 10th oldest entry is at least
|
|
61
61
|
60 seconds old). Upon adding a new entry to the log "expired" entries are truncated.
|
|
62
62
|
|
|
63
63
|
For example, with a rate limit of 10 requests per minute:
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Rate limiting with commonly used storage backends
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
from . import _version, aio, storage, strategies
|
|
6
8
|
from .limits import (
|
|
7
9
|
RateLimitItem,
|
|
@@ -16,18 +18,18 @@ from .util import WindowStats, parse, parse_many
|
|
|
16
18
|
|
|
17
19
|
__all__ = [
|
|
18
20
|
"RateLimitItem",
|
|
19
|
-
"RateLimitItemPerYear",
|
|
20
|
-
"RateLimitItemPerMonth",
|
|
21
21
|
"RateLimitItemPerDay",
|
|
22
22
|
"RateLimitItemPerHour",
|
|
23
23
|
"RateLimitItemPerMinute",
|
|
24
|
+
"RateLimitItemPerMonth",
|
|
24
25
|
"RateLimitItemPerSecond",
|
|
26
|
+
"RateLimitItemPerYear",
|
|
27
|
+
"WindowStats",
|
|
25
28
|
"aio",
|
|
26
|
-
"storage",
|
|
27
|
-
"strategies",
|
|
28
29
|
"parse",
|
|
29
30
|
"parse_many",
|
|
30
|
-
"
|
|
31
|
+
"storage",
|
|
32
|
+
"strategies",
|
|
31
33
|
]
|
|
32
34
|
|
|
33
|
-
__version__ = _version.get_versions()["version"]
|
|
35
|
+
__version__ = _version.get_versions()["version"]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
# This file was generated by 'versioneer.py' (0.
|
|
2
|
+
# This file was generated by 'versioneer.py' (0.29) from
|
|
3
3
|
# revision-control system data, or from the parent directory name of an
|
|
4
4
|
# unpacked source archive. Distribution tarballs contain a pre-generated copy
|
|
5
5
|
# of this file.
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2025-03-
|
|
11
|
+
"date": "2025-03-14T16:19:10-0700",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "4.
|
|
14
|
+
"full-revisionid": "0bcebd7b69d035e3df82779a50fb2d1e901b9ef9",
|
|
15
|
+
"version": "4.3"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -3,6 +3,8 @@ Implementations of storage backends to be used with
|
|
|
3
3
|
:class:`limits.aio.strategies.RateLimiter` strategies
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
6
8
|
from .base import MovingWindowSupport, SlidingWindowCounterSupport, Storage
|
|
7
9
|
from .etcd import EtcdStorage
|
|
8
10
|
from .memcached import MemcachedStorage
|
|
@@ -11,14 +13,14 @@ from .mongodb import MongoDBStorage
|
|
|
11
13
|
from .redis import RedisClusterStorage, RedisSentinelStorage, RedisStorage
|
|
12
14
|
|
|
13
15
|
__all__ = [
|
|
14
|
-
"Storage",
|
|
15
|
-
"MovingWindowSupport",
|
|
16
|
-
"SlidingWindowCounterSupport",
|
|
17
16
|
"EtcdStorage",
|
|
18
17
|
"MemcachedStorage",
|
|
19
18
|
"MemoryStorage",
|
|
20
19
|
"MongoDBStorage",
|
|
21
|
-
"
|
|
20
|
+
"MovingWindowSupport",
|
|
22
21
|
"RedisClusterStorage",
|
|
23
22
|
"RedisSentinelStorage",
|
|
23
|
+
"RedisStorage",
|
|
24
|
+
"SlidingWindowCounterSupport",
|
|
25
|
+
"Storage",
|
|
24
26
|
]
|
|
@@ -11,11 +11,8 @@ from limits.typing import (
|
|
|
11
11
|
Any,
|
|
12
12
|
Awaitable,
|
|
13
13
|
Callable,
|
|
14
|
-
Optional,
|
|
15
14
|
P,
|
|
16
15
|
R,
|
|
17
|
-
Type,
|
|
18
|
-
Union,
|
|
19
16
|
cast,
|
|
20
17
|
)
|
|
21
18
|
from limits.util import LazyDependency
|
|
@@ -43,7 +40,7 @@ class Storage(LazyDependency, metaclass=StorageRegistry):
|
|
|
43
40
|
Base class to extend when implementing an async storage backend.
|
|
44
41
|
"""
|
|
45
42
|
|
|
46
|
-
STORAGE_SCHEME:
|
|
43
|
+
STORAGE_SCHEME: list[str] | None
|
|
47
44
|
"""The storage schemes to register against this implementation"""
|
|
48
45
|
|
|
49
46
|
def __init_subclass__(cls, **kwargs: Any) -> None: # type:ignore[explicit-any]
|
|
@@ -61,9 +58,9 @@ class Storage(LazyDependency, metaclass=StorageRegistry):
|
|
|
61
58
|
|
|
62
59
|
def __init__(
|
|
63
60
|
self,
|
|
64
|
-
uri:
|
|
61
|
+
uri: str | None = None,
|
|
65
62
|
wrap_exceptions: bool = False,
|
|
66
|
-
**options:
|
|
63
|
+
**options: float | str | bool,
|
|
67
64
|
) -> None:
|
|
68
65
|
"""
|
|
69
66
|
:param wrap_exceptions: Whether to wrap storage exceptions in
|
|
@@ -74,7 +71,7 @@ class Storage(LazyDependency, metaclass=StorageRegistry):
|
|
|
74
71
|
|
|
75
72
|
@property
|
|
76
73
|
@abstractmethod
|
|
77
|
-
def base_exceptions(self) ->
|
|
74
|
+
def base_exceptions(self) -> type[Exception] | tuple[type[Exception], ...]:
|
|
78
75
|
raise NotImplementedError
|
|
79
76
|
|
|
80
77
|
@abstractmethod
|
|
@@ -114,7 +111,7 @@ class Storage(LazyDependency, metaclass=StorageRegistry):
|
|
|
114
111
|
raise NotImplementedError
|
|
115
112
|
|
|
116
113
|
@abstractmethod
|
|
117
|
-
async def reset(self) ->
|
|
114
|
+
async def reset(self) -> int | None:
|
|
118
115
|
"""
|
|
119
116
|
reset storage to clear limits
|
|
120
117
|
"""
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import time
|
|
3
5
|
import urllib.parse
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
5
7
|
|
|
6
8
|
from limits.aio.storage.base import Storage
|
|
7
9
|
from limits.errors import ConcurrentUpdateError
|
|
@@ -44,7 +46,7 @@ class EtcdStorage(Storage):
|
|
|
44
46
|
"""
|
|
45
47
|
parsed = urllib.parse.urlparse(uri)
|
|
46
48
|
self.lib = self.dependencies["aetcd"].module
|
|
47
|
-
self.storage:
|
|
49
|
+
self.storage: aetcd.Client = self.lib.Client(
|
|
48
50
|
host=parsed.hostname, port=parsed.port, **options
|
|
49
51
|
)
|
|
50
52
|
self.max_retries = max_retries
|
|
@@ -53,7 +55,7 @@ class EtcdStorage(Storage):
|
|
|
53
55
|
@property
|
|
54
56
|
def base_exceptions(
|
|
55
57
|
self,
|
|
56
|
-
) ->
|
|
58
|
+
) -> type[Exception] | tuple[type[Exception], ...]: # pragma: no cover
|
|
57
59
|
return self.lib.ClientError # type: ignore[no-any-return]
|
|
58
60
|
|
|
59
61
|
def prefixed_key(self, key: str) -> bytes:
|
|
@@ -134,7 +136,7 @@ class EtcdStorage(Storage):
|
|
|
134
136
|
except: # noqa
|
|
135
137
|
return False
|
|
136
138
|
|
|
137
|
-
async def reset(self) ->
|
|
139
|
+
async def reset(self) -> int | None:
|
|
138
140
|
return (await self.storage.delete_prefix(f"{self.PREFIX}/".encode())).deleted
|
|
139
141
|
|
|
140
142
|
async def clear(self, key: str) -> None:
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import time
|
|
2
4
|
import urllib.parse
|
|
3
5
|
from collections.abc import Iterable
|
|
@@ -7,7 +9,7 @@ from deprecated.sphinx import versionadded
|
|
|
7
9
|
|
|
8
10
|
from limits.aio.storage.base import SlidingWindowCounterSupport, Storage
|
|
9
11
|
from limits.storage.base import TimestampedSlidingWindow
|
|
10
|
-
from limits.typing import EmcacheClientP, ItemP
|
|
12
|
+
from limits.typing import EmcacheClientP, ItemP
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
@versionadded(version="2.1")
|
|
@@ -27,7 +29,7 @@ class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingW
|
|
|
27
29
|
self,
|
|
28
30
|
uri: str,
|
|
29
31
|
wrap_exceptions: bool = False,
|
|
30
|
-
**options:
|
|
32
|
+
**options: float | str | bool,
|
|
31
33
|
) -> None:
|
|
32
34
|
"""
|
|
33
35
|
:param uri: memcached location of the form
|
|
@@ -54,7 +56,7 @@ class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingW
|
|
|
54
56
|
@property
|
|
55
57
|
def base_exceptions(
|
|
56
58
|
self,
|
|
57
|
-
) ->
|
|
59
|
+
) -> type[Exception] | tuple[type[Exception], ...]: # pragma: no cover
|
|
58
60
|
return (
|
|
59
61
|
self.dependency.ClusterNoAvailableNodes,
|
|
60
62
|
self.dependency.CommandError,
|
|
@@ -204,7 +206,7 @@ class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingW
|
|
|
204
206
|
except: # noqa
|
|
205
207
|
return False
|
|
206
208
|
|
|
207
|
-
async def reset(self) ->
|
|
209
|
+
async def reset(self) -> int | None:
|
|
208
210
|
raise NotImplementedError
|
|
209
211
|
|
|
210
212
|
async def acquire_sliding_window_entry(
|