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.
Files changed (92) hide show
  1. {limits-4.1 → limits-4.3}/CLASSIFIERS +0 -1
  2. {limits-4.1 → limits-4.3}/HISTORY.rst +26 -0
  3. {limits-4.1 → limits-4.3}/PKG-INFO +9 -4
  4. {limits-4.1 → limits-4.3}/README.rst +1 -1
  5. {limits-4.1 → limits-4.3}/doc/source/async.rst +4 -1
  6. {limits-4.1 → limits-4.3}/doc/source/conf.py +1 -0
  7. {limits-4.1 → limits-4.3}/doc/source/index.rst +2 -2
  8. {limits-4.1 → limits-4.3}/doc/source/installation.rst +28 -0
  9. {limits-4.1 → limits-4.3}/doc/source/storage.rst +39 -10
  10. {limits-4.1 → limits-4.3}/doc/source/strategies.rst +1 -1
  11. {limits-4.1 → limits-4.3}/limits/__init__.py +8 -6
  12. {limits-4.1 → limits-4.3}/limits/_version.py +4 -4
  13. {limits-4.1 → limits-4.3}/limits/aio/__init__.py +2 -0
  14. {limits-4.1 → limits-4.3}/limits/aio/storage/__init__.py +6 -4
  15. {limits-4.1 → limits-4.3}/limits/aio/storage/base.py +5 -8
  16. {limits-4.1 → limits-4.3}/limits/aio/storage/etcd.py +6 -4
  17. {limits-4.1 → limits-4.3}/limits/aio/storage/memcached.py +6 -4
  18. {limits-4.1 → limits-4.3}/limits/aio/storage/memory.py +42 -26
  19. {limits-4.1 → limits-4.3}/limits/aio/storage/mongodb.py +4 -7
  20. limits-4.3/limits/aio/storage/redis/__init__.py +402 -0
  21. limits-4.3/limits/aio/storage/redis/bridge.py +120 -0
  22. limits-4.3/limits/aio/storage/redis/coredis.py +209 -0
  23. limits-4.3/limits/aio/storage/redis/redispy.py +257 -0
  24. limits-4.3/limits/aio/storage/redis/valkey.py +9 -0
  25. {limits-4.1 → limits-4.3}/limits/aio/strategies.py +4 -2
  26. {limits-4.1 → limits-4.3}/limits/errors.py +2 -0
  27. {limits-4.1 → limits-4.3}/limits/storage/__init__.py +14 -11
  28. {limits-4.1 → limits-4.3}/limits/storage/base.py +5 -10
  29. {limits-4.1 → limits-4.3}/limits/storage/etcd.py +6 -4
  30. {limits-4.1 → limits-4.3}/limits/storage/memcached.py +6 -7
  31. {limits-4.1 → limits-4.3}/limits/storage/memory.py +42 -31
  32. {limits-4.1 → limits-4.3}/limits/storage/mongodb.py +7 -10
  33. {limits-4.1 → limits-4.3}/limits/storage/redis.py +48 -18
  34. {limits-4.1 → limits-4.3}/limits/storage/redis_cluster.py +31 -11
  35. {limits-4.1 → limits-4.3}/limits/storage/redis_sentinel.py +35 -11
  36. {limits-4.1 → limits-4.3}/limits/storage/registry.py +1 -3
  37. {limits-4.1 → limits-4.3}/limits/strategies.py +11 -9
  38. {limits-4.1 → limits-4.3}/limits/typing.py +45 -42
  39. {limits-4.1 → limits-4.3}/limits/util.py +12 -12
  40. {limits-4.1 → limits-4.3}/limits.egg-info/PKG-INFO +9 -4
  41. {limits-4.1 → limits-4.3}/limits.egg-info/SOURCES.txt +7 -1
  42. {limits-4.1 → limits-4.3}/limits.egg-info/requires.txt +7 -0
  43. limits-4.3/pyproject.toml +25 -0
  44. limits-4.3/requirements/storage/async-valkey.txt +1 -0
  45. limits-4.3/requirements/storage/valkey.txt +1 -0
  46. {limits-4.1 → limits-4.3}/requirements/test.txt +2 -0
  47. {limits-4.1 → limits-4.3}/setup.cfg +0 -8
  48. {limits-4.1 → limits-4.3}/setup.py +3 -1
  49. {limits-4.1 → limits-4.3}/tests/test_limit_granularities.py +2 -0
  50. {limits-4.1 → limits-4.3}/tests/test_limits.py +2 -0
  51. {limits-4.1 → limits-4.3}/tests/test_ratelimit_parser.py +2 -0
  52. {limits-4.1 → limits-4.3}/tests/test_storage.py +5 -3
  53. {limits-4.1 → limits-4.3}/tests/test_strategy.py +2 -0
  54. {limits-4.1 → limits-4.3}/tests/test_utils.py +2 -0
  55. {limits-4.1 → limits-4.3}/versioneer.py +430 -342
  56. limits-4.1/limits/aio/storage/redis.py +0 -555
  57. limits-4.1/pyproject.toml +0 -10
  58. {limits-4.1 → limits-4.3}/CONTRIBUTIONS.rst +0 -0
  59. {limits-4.1 → limits-4.3}/LICENSE.txt +0 -0
  60. {limits-4.1 → limits-4.3}/MANIFEST.in +0 -0
  61. {limits-4.1 → limits-4.3}/doc/Makefile +0 -0
  62. {limits-4.1 → limits-4.3}/doc/source/_static/custom.css +0 -0
  63. {limits-4.1 → limits-4.3}/doc/source/api.rst +0 -0
  64. {limits-4.1 → limits-4.3}/doc/source/changelog.rst +0 -0
  65. {limits-4.1 → limits-4.3}/doc/source/custom-storage.rst +0 -0
  66. {limits-4.1 → limits-4.3}/doc/source/quickstart.rst +0 -0
  67. {limits-4.1 → limits-4.3}/doc/source/theme_config.py +0 -0
  68. {limits-4.1 → limits-4.3}/limits/limits.py +0 -0
  69. {limits-4.1 → limits-4.3}/limits/py.typed +0 -0
  70. {limits-4.1 → limits-4.3}/limits/resources/redis/lua_scripts/acquire_moving_window.lua +0 -0
  71. {limits-4.1 → limits-4.3}/limits/resources/redis/lua_scripts/acquire_sliding_window.lua +0 -0
  72. {limits-4.1 → limits-4.3}/limits/resources/redis/lua_scripts/clear_keys.lua +0 -0
  73. {limits-4.1 → limits-4.3}/limits/resources/redis/lua_scripts/incr_expire.lua +0 -0
  74. {limits-4.1 → limits-4.3}/limits/resources/redis/lua_scripts/moving_window.lua +0 -0
  75. {limits-4.1 → limits-4.3}/limits/resources/redis/lua_scripts/sliding_window.lua +0 -0
  76. {limits-4.1 → limits-4.3}/limits/version.py +0 -0
  77. {limits-4.1 → limits-4.3}/limits.egg-info/dependency_links.txt +0 -0
  78. {limits-4.1 → limits-4.3}/limits.egg-info/not-zip-safe +0 -0
  79. {limits-4.1 → limits-4.3}/limits.egg-info/top_level.txt +0 -0
  80. {limits-4.1 → limits-4.3}/requirements/ci.txt +0 -0
  81. {limits-4.1 → limits-4.3}/requirements/dev.txt +0 -0
  82. {limits-4.1 → limits-4.3}/requirements/docs.txt +0 -0
  83. {limits-4.1 → limits-4.3}/requirements/main.txt +0 -0
  84. {limits-4.1 → limits-4.3}/requirements/storage/async-etcd.txt +0 -0
  85. {limits-4.1 → limits-4.3}/requirements/storage/async-memcached.txt +0 -0
  86. {limits-4.1 → limits-4.3}/requirements/storage/async-mongodb.txt +0 -0
  87. {limits-4.1 → limits-4.3}/requirements/storage/async-redis.txt +0 -0
  88. {limits-4.1 → limits-4.3}/requirements/storage/etcd.txt +0 -0
  89. {limits-4.1 → limits-4.3}/requirements/storage/memcached.txt +0 -0
  90. {limits-4.1 → limits-4.3}/requirements/storage/mongodb.txt +0 -0
  91. {limits-4.1 → limits-4.3}/requirements/storage/redis.txt +0 -0
  92. {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.1
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.9
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 atleast
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 atleast
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 git://github.com/alisaifee/limits.git
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 a working `docker & docker-compose installation <https://docs.docker.com/compose/gettingstarted/>`_.
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 sentinal instances and the `service-name`
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 aysnc version: :class:`~limits.aio.storage.RedisSentinelStorage`).
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 atleast
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
- "WindowStats",
31
+ "storage",
32
+ "strategies",
31
33
  ]
32
34
 
33
- __version__ = _version.get_versions()["version"] # type: ignore
35
+ __version__ = _version.get_versions()["version"]
@@ -1,5 +1,5 @@
1
1
 
2
- # This file was generated by 'versioneer.py' (0.22) from
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-07T12:07:00-0800",
11
+ "date": "2025-03-14T16:19:10-0700",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "aff8ca13e30c9d754690a2931498f023d35dc62f",
15
- "version": "4.1"
14
+ "full-revisionid": "0bcebd7b69d035e3df82779a50fb2d1e901b9ef9",
15
+ "version": "4.3"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from . import storage, strategies
2
4
 
3
5
  __all__ = [
@@ -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
- "RedisStorage",
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: Optional[list[str]]
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: Optional[str] = None,
61
+ uri: str | None = None,
65
62
  wrap_exceptions: bool = False,
66
- **options: Union[float, str, bool],
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) -> Union[Type[Exception], tuple[Type[Exception], ...]]:
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) -> Optional[int]:
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, Optional, Union
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: "aetcd.Client" = self.lib.Client(
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
- ) -> Union[type[Exception], tuple[type[Exception], ...]]: # pragma: no cover
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) -> Optional[int]:
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, Optional, Type, Union
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: Union[float, str, bool],
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
- ) -> Union[Type[Exception], tuple[Type[Exception], ...]]: # pragma: no cover
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) -> Optional[int]:
209
+ async def reset(self) -> int | None:
208
210
  raise NotImplementedError
209
211
 
210
212
  async def acquire_sliding_window_entry(