limits 4.7.2__tar.gz → 5.0.0rc1__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 (99) hide show
  1. {limits-4.7.2 → limits-5.0.0rc1}/HISTORY.rst +16 -82
  2. {limits-4.7.2 → limits-5.0.0rc1}/PKG-INFO +7 -14
  3. {limits-4.7.2 → limits-5.0.0rc1}/README.rst +4 -3
  4. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/_static/custom.css +1 -1
  5. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/api.rst +0 -12
  6. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/async.rst +3 -6
  7. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/conf.py +12 -14
  8. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/index.rst +2 -1
  9. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/installation.rst +0 -20
  10. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/quickstart.rst +42 -27
  11. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/storage.rst +3 -36
  12. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/strategies.rst +0 -11
  13. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/theme_config.py +2 -0
  14. {limits-4.7.2 → limits-5.0.0rc1}/limits/_version.py +3 -3
  15. {limits-4.7.2 → limits-5.0.0rc1}/limits/aio/storage/__init__.py +0 -2
  16. {limits-4.7.2 → limits-5.0.0rc1}/limits/aio/storage/base.py +1 -5
  17. {limits-4.7.2 → limits-5.0.0rc1}/limits/aio/storage/memcached.py +34 -58
  18. {limits-4.7.2 → limits-5.0.0rc1}/limits/aio/storage/memory.py +20 -38
  19. {limits-4.7.2 → limits-5.0.0rc1}/limits/aio/storage/mongodb.py +26 -31
  20. {limits-4.7.2 → limits-5.0.0rc1}/limits/aio/storage/redis/__init__.py +2 -4
  21. {limits-4.7.2 → limits-5.0.0rc1}/limits/aio/storage/redis/bridge.py +0 -1
  22. {limits-4.7.2 → limits-5.0.0rc1}/limits/aio/storage/redis/coredis.py +2 -6
  23. {limits-4.7.2 → limits-5.0.0rc1}/limits/aio/storage/redis/redispy.py +1 -8
  24. {limits-4.7.2 → limits-5.0.0rc1}/limits/aio/strategies.py +1 -28
  25. {limits-4.7.2 → limits-5.0.0rc1}/limits/resources/redis/lua_scripts/acquire_moving_window.lua +5 -2
  26. limits-5.0.0rc1/limits/resources/redis/lua_scripts/moving_window.lua +30 -0
  27. {limits-4.7.2 → limits-5.0.0rc1}/limits/storage/__init__.py +0 -2
  28. {limits-4.7.2 → limits-5.0.0rc1}/limits/storage/base.py +1 -5
  29. {limits-4.7.2 → limits-5.0.0rc1}/limits/storage/memcached.py +6 -29
  30. {limits-4.7.2 → limits-5.0.0rc1}/limits/storage/memory.py +16 -35
  31. {limits-4.7.2 → limits-5.0.0rc1}/limits/storage/mongodb.py +25 -34
  32. {limits-4.7.2 → limits-5.0.0rc1}/limits/storage/redis.py +1 -7
  33. {limits-4.7.2 → limits-5.0.0rc1}/limits/strategies.py +1 -31
  34. {limits-4.7.2 → limits-5.0.0rc1}/limits/typing.py +0 -50
  35. {limits-4.7.2 → limits-5.0.0rc1}/limits.egg-info/PKG-INFO +7 -14
  36. {limits-4.7.2 → limits-5.0.0rc1}/limits.egg-info/SOURCES.txt +0 -4
  37. {limits-4.7.2 → limits-5.0.0rc1}/limits.egg-info/requires.txt +2 -20
  38. {limits-4.7.2 → limits-5.0.0rc1}/requirements/docs.txt +1 -1
  39. limits-5.0.0rc1/requirements/storage/async-memcached.txt +1 -0
  40. {limits-4.7.2 → limits-5.0.0rc1}/requirements/test.txt +1 -6
  41. {limits-4.7.2 → limits-5.0.0rc1}/setup.cfg +0 -6
  42. {limits-4.7.2 → limits-5.0.0rc1}/setup.py +0 -2
  43. {limits-4.7.2 → limits-5.0.0rc1}/tests/test_storage.py +0 -9
  44. {limits-4.7.2 → limits-5.0.0rc1}/tests/test_strategy.py +8 -43
  45. limits-4.7.2/limits/aio/storage/etcd.py +0 -146
  46. limits-4.7.2/limits/resources/redis/lua_scripts/moving_window.lua +0 -21
  47. limits-4.7.2/limits/storage/etcd.py +0 -139
  48. limits-4.7.2/requirements/storage/async-etcd.txt +0 -1
  49. limits-4.7.2/requirements/storage/async-memcached.txt +0 -2
  50. limits-4.7.2/requirements/storage/etcd.txt +0 -1
  51. {limits-4.7.2 → limits-5.0.0rc1}/CLASSIFIERS +0 -0
  52. {limits-4.7.2 → limits-5.0.0rc1}/CONTRIBUTIONS.rst +0 -0
  53. {limits-4.7.2 → limits-5.0.0rc1}/LICENSE.txt +0 -0
  54. {limits-4.7.2 → limits-5.0.0rc1}/MANIFEST.in +0 -0
  55. {limits-4.7.2 → limits-5.0.0rc1}/doc/Makefile +0 -0
  56. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/changelog.rst +0 -0
  57. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/custom-storage.rst +0 -0
  58. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/ext/_static/benchmark-chart.css +0 -0
  59. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/ext/_static/js/benchmark-chart.js +0 -0
  60. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/ext/_static/js/benchmark-details.js +0 -0
  61. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/ext/_static/js/benchmark-loader.js +0 -0
  62. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/ext/_templates/git_info.js +0 -0
  63. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/ext/bench_chart.py +0 -0
  64. {limits-4.7.2 → limits-5.0.0rc1}/doc/source/performance.rst +0 -0
  65. {limits-4.7.2 → limits-5.0.0rc1}/limits/__init__.py +0 -0
  66. {limits-4.7.2 → limits-5.0.0rc1}/limits/aio/__init__.py +0 -0
  67. {limits-4.7.2 → limits-5.0.0rc1}/limits/aio/storage/redis/valkey.py +0 -0
  68. {limits-4.7.2 → limits-5.0.0rc1}/limits/errors.py +0 -0
  69. {limits-4.7.2 → limits-5.0.0rc1}/limits/limits.py +0 -0
  70. {limits-4.7.2 → limits-5.0.0rc1}/limits/py.typed +0 -0
  71. {limits-4.7.2 → limits-5.0.0rc1}/limits/resources/redis/lua_scripts/acquire_sliding_window.lua +0 -0
  72. {limits-4.7.2 → limits-5.0.0rc1}/limits/resources/redis/lua_scripts/clear_keys.lua +0 -0
  73. {limits-4.7.2 → limits-5.0.0rc1}/limits/resources/redis/lua_scripts/incr_expire.lua +0 -0
  74. {limits-4.7.2 → limits-5.0.0rc1}/limits/resources/redis/lua_scripts/sliding_window.lua +0 -0
  75. {limits-4.7.2 → limits-5.0.0rc1}/limits/storage/redis_cluster.py +0 -0
  76. {limits-4.7.2 → limits-5.0.0rc1}/limits/storage/redis_sentinel.py +0 -0
  77. {limits-4.7.2 → limits-5.0.0rc1}/limits/storage/registry.py +0 -0
  78. {limits-4.7.2 → limits-5.0.0rc1}/limits/util.py +0 -0
  79. {limits-4.7.2 → limits-5.0.0rc1}/limits/version.py +0 -0
  80. {limits-4.7.2 → limits-5.0.0rc1}/limits.egg-info/dependency_links.txt +0 -0
  81. {limits-4.7.2 → limits-5.0.0rc1}/limits.egg-info/not-zip-safe +0 -0
  82. {limits-4.7.2 → limits-5.0.0rc1}/limits.egg-info/top_level.txt +0 -0
  83. {limits-4.7.2 → limits-5.0.0rc1}/pyproject.toml +0 -0
  84. {limits-4.7.2 → limits-5.0.0rc1}/requirements/ci.txt +0 -0
  85. {limits-4.7.2 → limits-5.0.0rc1}/requirements/dev.txt +0 -0
  86. {limits-4.7.2 → limits-5.0.0rc1}/requirements/main.txt +0 -0
  87. {limits-4.7.2 → limits-5.0.0rc1}/requirements/storage/async-mongodb.txt +0 -0
  88. {limits-4.7.2 → limits-5.0.0rc1}/requirements/storage/async-redis.txt +0 -0
  89. {limits-4.7.2 → limits-5.0.0rc1}/requirements/storage/async-valkey.txt +0 -0
  90. {limits-4.7.2 → limits-5.0.0rc1}/requirements/storage/memcached.txt +0 -0
  91. {limits-4.7.2 → limits-5.0.0rc1}/requirements/storage/mongodb.txt +0 -0
  92. {limits-4.7.2 → limits-5.0.0rc1}/requirements/storage/redis.txt +0 -0
  93. {limits-4.7.2 → limits-5.0.0rc1}/requirements/storage/rediscluster.txt +0 -0
  94. {limits-4.7.2 → limits-5.0.0rc1}/requirements/storage/valkey.txt +0 -0
  95. {limits-4.7.2 → limits-5.0.0rc1}/tests/test_limit_granularities.py +0 -0
  96. {limits-4.7.2 → limits-5.0.0rc1}/tests/test_limits.py +0 -0
  97. {limits-4.7.2 → limits-5.0.0rc1}/tests/test_ratelimit_parser.py +0 -0
  98. {limits-4.7.2 → limits-5.0.0rc1}/tests/test_utils.py +0 -0
  99. {limits-4.7.2 → limits-5.0.0rc1}/versioneer.py +0 -0
@@ -3,6 +3,22 @@
3
3
  Changelog
4
4
  =========
5
5
 
6
+ v5.0.0rc1
7
+ ---------
8
+ Release Date: 2025-04-09
9
+
10
+ * Backward incompatible changes
11
+
12
+ * Dropped support for Fixed Window with Elastic Expiry strategy
13
+ * Dropped support for etcd
14
+ * Replaced async support for memached from :pypi:`emcache` to :pypi`:memcachio`
15
+
16
+ * Performance
17
+
18
+ * Improved performance of in-memory moving window ``test`` and ``get_window_stats`` operations.
19
+ * Improved performance of redis moving window ``test`` and ``get_window_stats`` operations.
20
+ * Improved performance of mongodb moving window ``test`` and ``get_window_stats`` operations.
21
+
6
22
  v4.7.2
7
23
  ------
8
24
  Release Date: 2025-04-09
@@ -793,85 +809,3 @@ Release Date: 2015-01-08
793
809
 
794
810
  * Initial import of common rate limiting code from `Flask-Limiter <https://github.com/alisaifee/flask-limiter>`_
795
811
 
796
-
797
-
798
-
799
-
800
-
801
-
802
-
803
-
804
-
805
-
806
-
807
-
808
-
809
-
810
-
811
-
812
-
813
-
814
-
815
-
816
-
817
-
818
-
819
-
820
-
821
-
822
-
823
-
824
-
825
-
826
-
827
-
828
-
829
-
830
-
831
-
832
-
833
-
834
-
835
-
836
-
837
-
838
-
839
-
840
-
841
-
842
-
843
-
844
-
845
-
846
-
847
-
848
-
849
-
850
-
851
-
852
-
853
-
854
-
855
-
856
-
857
-
858
-
859
-
860
-
861
-
862
-
863
-
864
-
865
-
866
-
867
-
868
-
869
-
870
-
871
-
872
-
873
-
874
-
875
-
876
-
877
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: limits
3
- Version: 4.7.2
3
+ Version: 5.0.0rc1
4
4
  Summary: Rate limiting utilities
5
5
  Home-page: https://limits.readthedocs.org
6
6
  Author: Ali-Akber Saifee
@@ -32,19 +32,14 @@ Provides-Extra: memcached
32
32
  Requires-Dist: pymemcache<5.0.0,>3; extra == "memcached"
33
33
  Provides-Extra: mongodb
34
34
  Requires-Dist: pymongo<5,>4.1; extra == "mongodb"
35
- Provides-Extra: etcd
36
- Requires-Dist: etcd3; extra == "etcd"
37
35
  Provides-Extra: valkey
38
36
  Requires-Dist: valkey>=6; extra == "valkey"
39
37
  Provides-Extra: async-redis
40
38
  Requires-Dist: coredis<5,>=3.4.0; extra == "async-redis"
41
39
  Provides-Extra: async-memcached
42
- Requires-Dist: emcache>=0.6.1; python_version < "3.11" and extra == "async-memcached"
43
- Requires-Dist: emcache>=1; (python_version >= "3.11" and python_version < "3.13.0") and extra == "async-memcached"
40
+ Requires-Dist: memcachio>=0.3; extra == "async-memcached"
44
41
  Provides-Extra: async-mongodb
45
42
  Requires-Dist: motor<4,>=3; extra == "async-mongodb"
46
- Provides-Extra: async-etcd
47
- Requires-Dist: aetcd; extra == "async-etcd"
48
43
  Provides-Extra: async-valkey
49
44
  Requires-Dist: valkey>=6; extra == "async-valkey"
50
45
  Provides-Extra: all
@@ -52,13 +47,10 @@ Requires-Dist: redis!=4.5.2,!=4.5.3,<6.0.0,>3; extra == "all"
52
47
  Requires-Dist: redis!=4.5.2,!=4.5.3,>=4.2.0; extra == "all"
53
48
  Requires-Dist: pymemcache<5.0.0,>3; extra == "all"
54
49
  Requires-Dist: pymongo<5,>4.1; extra == "all"
55
- Requires-Dist: etcd3; extra == "all"
56
50
  Requires-Dist: valkey>=6; extra == "all"
57
51
  Requires-Dist: coredis<5,>=3.4.0; extra == "all"
58
- Requires-Dist: emcache>=0.6.1; python_version < "3.11" and extra == "all"
59
- Requires-Dist: emcache>=1; (python_version >= "3.11" and python_version < "3.13.0") and extra == "all"
52
+ Requires-Dist: memcachio>=0.3; extra == "all"
60
53
  Requires-Dist: motor<4,>=3; extra == "all"
61
- Requires-Dist: aetcd; extra == "all"
62
54
  Requires-Dist: valkey>=6; extra == "all"
63
55
  Dynamic: author
64
56
  Dynamic: author-email
@@ -86,13 +78,14 @@ Dynamic: summary
86
78
  .. |docs| image:: https://readthedocs.org/projects/limits/badge/?version=latest
87
79
  :target: https://limits.readthedocs.org
88
80
 
81
+ ######
89
82
  limits
90
- ------
83
+ ######
91
84
  |docs| |ci| |codecov| |pypi| |pypi-versions| |license|
92
85
 
93
86
 
94
87
  **limits** is a python library for rate limiting via multiple strategies
95
- with commonly used storage backends (Redis, Memcached, MongoDB & Etcd).
88
+ with commonly used storage backends (Redis, Memcached & MongoDB).
96
89
 
97
90
  The library provides identical APIs for use in sync and
98
91
  `async <https://limits.readthedocs.io/en/stable/async.html>`_ codebases.
@@ -188,13 +181,13 @@ Scenario 2:
188
181
  - ``weighted_count = floor(8 + (4 * 0.33)) = floor(8 + 1.32) = 9``.
189
182
  - Since the weighted count is below the limit, the request is allowed.
190
183
 
184
+
191
185
  Storage backends
192
186
  ================
193
187
 
194
188
  - `Redis <https://limits.readthedocs.io/en/latest/storage.html#redis-storage>`_
195
189
  - `Memcached <https://limits.readthedocs.io/en/latest/storage.html#memcached-storage>`_
196
190
  - `MongoDB <https://limits.readthedocs.io/en/latest/storage.html#mongodb-storage>`_
197
- - `Etcd <https://limits.readthedocs.io/en/latest/storage.html#etcd-storage>`_
198
191
  - `In-Memory <https://limits.readthedocs.io/en/latest/storage.html#in-memory-storage>`_
199
192
 
200
193
  Dive right in
@@ -11,13 +11,14 @@
11
11
  .. |docs| image:: https://readthedocs.org/projects/limits/badge/?version=latest
12
12
  :target: https://limits.readthedocs.org
13
13
 
14
+ ######
14
15
  limits
15
- ------
16
+ ######
16
17
  |docs| |ci| |codecov| |pypi| |pypi-versions| |license|
17
18
 
18
19
 
19
20
  **limits** is a python library for rate limiting via multiple strategies
20
- with commonly used storage backends (Redis, Memcached, MongoDB & Etcd).
21
+ with commonly used storage backends (Redis, Memcached & MongoDB).
21
22
 
22
23
  The library provides identical APIs for use in sync and
23
24
  `async <https://limits.readthedocs.io/en/stable/async.html>`_ codebases.
@@ -113,13 +114,13 @@ Scenario 2:
113
114
  - ``weighted_count = floor(8 + (4 * 0.33)) = floor(8 + 1.32) = 9``.
114
115
  - Since the weighted count is below the limit, the request is allowed.
115
116
 
117
+
116
118
  Storage backends
117
119
  ================
118
120
 
119
121
  - `Redis <https://limits.readthedocs.io/en/latest/storage.html#redis-storage>`_
120
122
  - `Memcached <https://limits.readthedocs.io/en/latest/storage.html#memcached-storage>`_
121
123
  - `MongoDB <https://limits.readthedocs.io/en/latest/storage.html#mongodb-storage>`_
122
- - `Etcd <https://limits.readthedocs.io/en/latest/storage.html#etcd-storage>`_
123
124
  - `In-Memory <https://limits.readthedocs.io/en/latest/storage.html#in-memory-storage>`_
124
125
 
125
126
  Dive right in
@@ -1,7 +1,7 @@
1
1
  .badges {
2
2
  display: flex;
3
3
  padding: 5px;
4
- flex-direction: rootow;
4
+ flex-direction: row;
5
5
  justify-content: center;
6
6
  }
7
7
  .header-badge {
@@ -28,7 +28,6 @@ a single parameter: a subclass of :class:`~limits.storage.Storage`.
28
28
  Provided by :mod:`limits.strategies`
29
29
 
30
30
  .. autoclass:: FixedWindowRateLimiter
31
- .. autoclass:: FixedWindowElasticExpiryRateLimiter
32
31
  .. autoclass:: MovingWindowRateLimiter
33
32
  .. autoclass:: SlidingWindowCounterRateLimiter
34
33
 
@@ -47,7 +46,6 @@ expose async variants and expect a subclass of :class:`limits.aio.storage.Storag
47
46
  Provided by :mod:`limits.aio.strategies`
48
47
 
49
48
  .. autoclass:: FixedWindowRateLimiter
50
- .. autoclass:: FixedWindowElasticExpiryRateLimiter
51
49
  .. autoclass:: MovingWindowRateLimiter
52
50
  .. autoclass:: SlidingWindowCounterRateLimiter
53
51
 
@@ -101,11 +99,6 @@ MongoDB Storage
101
99
 
102
100
  .. autoclass:: MongoDBStorage
103
101
 
104
- Etcd Storage
105
- ^^^^^^^^^^^^
106
-
107
- .. autoclass:: EtcdStorage
108
-
109
102
 
110
103
  Async Storage
111
104
  -------------
@@ -144,11 +137,6 @@ Async MongoDB Storage
144
137
 
145
138
  .. autoclass:: MongoDBStorage
146
139
 
147
- Async Etcd Storage
148
- ^^^^^^^^^^^^^^^^^^
149
-
150
- .. autoclass:: EtcdStorage
151
-
152
140
  Abstract storage classes
153
141
  ------------------------
154
142
 
@@ -2,10 +2,8 @@
2
2
  Async Support
3
3
  =============
4
4
 
5
- .. versionadded:: 2.1
6
-
7
- A new namespace ``limits.aio`` is available which mirrors the original
8
- ``limits.storage`` and ``limits.strategies`` packages.
5
+ The namespace ``limits.aio`` mirrors ``limits.storage`` and ``limits.strategies``
6
+ with async variants.
9
7
 
10
8
  The following async storage backends are implemented:
11
9
 
@@ -14,9 +12,8 @@ The following async storage backends are implemented:
14
12
  or `redis-py <https://redis-py.readthedocs.io>`_. Refer to
15
13
  :paramref:`limits.aio.storage.RedisStorage.implementation` for
16
14
  details on selecting the dependency)
17
- - Memcached (via `emcache <https://emcache.readthedocs.org>`_)
15
+ - Memcached (via `memcachio <https://memcachio.readthedocs.org>`_)
18
16
  - MongoDB (via `motor <https://motor.readthedocs.org>`_)
19
- - Etcd (via `aetcd <https://aetcd.readthedocs.org>`_)
20
17
 
21
18
  Quick start
22
19
  ===========
@@ -1,17 +1,14 @@
1
1
  #
2
+ from __future__ import annotations
2
3
 
3
4
  import os
4
5
  import sys
5
6
  from pathlib import Path
6
7
 
7
- from docutils import nodes
8
- from sphinx.application import Sphinx
9
- from sphinx.util.docutils import SphinxDirective
10
-
11
8
  sys.path.insert(0, os.path.abspath("../../"))
12
9
  sys.path.insert(0, os.path.abspath("./"))
13
10
 
14
- from theme_config import *
11
+ from theme_config import * # noqa
15
12
 
16
13
  import limits
17
14
 
@@ -28,16 +25,18 @@ release = version
28
25
 
29
26
 
30
27
  if branch_from_env := os.environ.get("READTHEDOCS_VERSION", None):
28
+ branch_from_env = "master" if branch_from_env == "latest" else branch_from_env
31
29
  benchmark_git_context = {
32
30
  "branch": branch_from_env,
33
- "sha": os.environ.get("READTHEDOCS_GIT_COMMIT_HASH", "")
31
+ "sha": os.environ.get("READTHEDOCS_GIT_COMMIT_HASH", ""),
34
32
  }
35
33
  else:
36
34
  import limits._version
35
+
37
36
  git_info = limits._version.git_pieces_from_vcs("", os.path.abspath("../../"), False)
38
37
  benchmark_git_context = {
39
38
  "branch": git_info.get("branch", ""),
40
- "sha": git_info.get("long", None)
39
+ "sha": git_info.get("long", None),
41
40
  }
42
41
 
43
42
  html_static_path = ["_static"]
@@ -48,18 +47,19 @@ html_css_files = [
48
47
  ]
49
48
 
50
49
  html_title = f"{project} <small><b style='color: var(--color-brand-primary)'>{{{release}}}</b></small>"
50
+
51
51
  try:
52
52
  ahead = int(ahead)
53
53
  if ahead > 0:
54
- html_theme_options[
54
+ html_theme_options[ # noqa
55
55
  "announcement"
56
56
  ] = f"""
57
57
  This is a development version. The documentation for the latest version: <b>{release}</b> can be found <a href="/en/stable">here</a>
58
58
  """
59
59
  html_title = f"{project} <small><b style='color: var(--color-brand-primary)'>{{dev}}</b></small>"
60
- except:
60
+ except ValueError:
61
61
  pass
62
- sys.path.append(str(Path('ext').resolve()))
62
+ sys.path.append(str(Path("ext").resolve()))
63
63
 
64
64
  extensions = [
65
65
  "sphinx.ext.autodoc",
@@ -93,18 +93,16 @@ autosectionlabel_prefix_document = True
93
93
 
94
94
  extlinks = {"pypi": ("https://pypi.org/project/%s", "%s")}
95
95
 
96
- copybutton_exclude = '.gp, .go'
96
+ copybutton_exclude = ".gp, .go"
97
97
 
98
98
  intersphinx_mapping = {
99
99
  "python": ("http://docs.python.org/", None),
100
100
  "coredis": ("https://coredis.readthedocs.io/en/latest/", None),
101
- "emcache": ("https://emcache.readthedocs.io/en/latest/", None),
101
+ "memcachio": ("https://memcachio.readthedocs.io/en/latest/", None),
102
102
  "motor": ("https://motor.readthedocs.io/en/stable/", None),
103
103
  "redis-py-cluster": ("https://redis-py-cluster.readthedocs.io/en/latest/", None),
104
104
  "redis-py": ("https://redis-py.readthedocs.io/en/latest/", None),
105
105
  "pymemcache": ("https://pymemcache.readthedocs.io/en/latest/", None),
106
106
  "pymongo": ("https://pymongo.readthedocs.io/en/stable/", None),
107
- "python-etcd3": ("https://python-etcd3.readthedocs.io/en/latest/", None),
108
- "aetcd": ("https://aetcd.readthedocs.io/en/latest/", None),
109
107
  "valkey-py": ("https://valkey-py.readthedocs.io/en/latest/", None),
110
108
  }
@@ -18,7 +18,7 @@
18
18
  ----
19
19
 
20
20
  **limits** is a python library for rate limiting via multiple strategies
21
- with commonly used storage backends (Redis, Memcached, MongoDB & Etcd).
21
+ with commonly used storage backends (Redis, Memcached & MongoDB).
22
22
 
23
23
  The library provides identical APIs for use in sync and
24
24
  :ref:`async <async:async support>` codebases.
@@ -92,4 +92,5 @@ References
92
92
  - `Redis: rate limiting pattern #2 <http://redis.io/commands/INCR>`_
93
93
  - `DomainTools: redis rate limiter <https://github.com/DomainTools/rate-limit>`_
94
94
  - `Cloudflare: How we built rate limiting capable of scaling to millions of domains <https://blog.cloudflare.com/counting-things-a-lot-of-different-things/>`_
95
+
95
96
  .. include:: ../../CONTRIBUTIONS.rst
@@ -48,16 +48,6 @@ Install the package with pip:
48
48
 
49
49
  .. literalinclude:: ../../requirements/storage/mongodb.txt
50
50
 
51
- .. tab:: Etcd
52
-
53
- .. code:: console
54
-
55
- $ pip install limits[etcd]
56
-
57
- Includes:
58
-
59
- .. literalinclude:: ../../requirements/storage/etcd.txt
60
-
61
51
  .. tab:: Valkey
62
52
 
63
53
  .. code:: console
@@ -115,16 +105,6 @@ along with the package using the following extras:
115
105
 
116
106
  .. literalinclude:: ../../requirements/storage/async-mongodb.txt
117
107
 
118
- .. tab:: Etcd
119
-
120
- .. code:: console
121
-
122
- $ pip install limits[async-etcd]
123
-
124
- Includes:
125
-
126
- .. literalinclude:: ../../requirements/storage/async-etcd.txt
127
-
128
108
  .. tab:: Valkey
129
109
 
130
110
  .. code:: console
@@ -92,49 +92,64 @@ Test the limits
92
92
  Consume the limits
93
93
  ------------------
94
94
 
95
- .. code::
96
-
97
- assert True == limiter.hit(one_per_minute, "test_namespace", "foo")
98
- assert False == limiter.hit(one_per_minute, "test_namespace", "foo")
99
- assert True == limiter.hit(one_per_minute, "test_namespace", "bar")
100
-
101
- assert True == limiter.hit(one_per_second, "test_namespace", "foo")
102
- assert False == limiter.hit(one_per_second, "test_namespace", "foo")
103
- time.sleep(1)
104
- assert True == limiter.hit(one_per_second, "test_namespace", "foo")
95
+ .. code-block:: python-console
96
+
97
+ >>> limiter.hit(one_per_minute, "test_namespace", "foo")
98
+ True
99
+ >>> limiter.hit(one_per_minute, "test_namespace", "foo")
100
+ False
101
+ >>> limiter.hit(one_per_minute, "test_namespace", "bar")
102
+ True
103
+
104
+ >>> limiter.hit(one_per_second, "test_namespace", "foo")
105
+ True
106
+ >>> limiter.hit(one_per_second, "test_namespace", "foo")
107
+ False
108
+ >>> time.sleep(1)
109
+ >>> limiter.hit(one_per_second, "test_namespace", "foo")
110
+ True
105
111
 
106
112
  Check without consuming
107
113
  -----------------------
108
114
 
109
- .. code::
115
+ .. code-block:: python-console
110
116
 
111
- assert True == limiter.hit(one_per_second, "test_namespace", "foo")
112
- while not limiter.test(one_per_second, "test_namespace", "foo"):
113
- time.sleep(0.01)
114
- assert True == limiter.hit(one_per_second, "test_namespace", "foo")
117
+ >>> limiter.hit(one_per_second, "test_namespace", "foo")
118
+ True
119
+ >>> while not limiter.test(one_per_second, "test_namespace", "foo"):
120
+ ... time.sleep(0.01)
121
+ >>> limiter.hit(one_per_second, "test_namespace", "foo")
122
+ True
115
123
 
116
124
  Query available capacity and reset time
117
125
  -----------------------------------------
118
126
 
119
- .. code::
127
+ .. code-block:: python-console
120
128
 
121
- assert True == limiter.hit(one_per_minute, "test_namespace", "foo")
122
- window = limiter.get_window_stats(one_per_minute, "test_namespace", "foo")
123
- assert window.remaining == 0
124
- assert False == limiter.hit(one_per_minute, "test_namespace", "foo")
125
- time.sleep(window.reset_time - time.time())
126
- assert True == limiter.hit(one_per_minute, "test_namespace", "foo")
129
+ >>> limiter.hit(one_per_minute, "test_namespace", "foo")
130
+ True
131
+ >>> window = limiter.get_window_stats(one_per_minute, "test_namespace", "foo")
132
+ >>> window.remaining
133
+ 0
134
+ >>> limiter.hit(one_per_minute, "test_namespace", "foo")
135
+ False
136
+ >>> time.sleep(window.reset_time - time.time())
137
+ >>> limiter.hit(one_per_minute, "test_namespace", "foo")
138
+ True
127
139
 
128
140
 
129
141
  Clear a limit
130
142
  =============
131
143
 
132
- .. code::
144
+ .. code-block:: python-console
133
145
 
134
- assert True == limiter.hit(one_per_minute, "test_namespace", "foo")
135
- assert False == limiter.hit(one_per_minute, "test_namespace", "foo")
136
- limiter.clear(one_per_minute, "test_namespace", "foo")
137
- assert True == limiter.hit(one_per_minute, "test_namespace", "foo")
146
+ >>> limiter.hit(one_per_minute, "test_namespace", "foo")
147
+ True
148
+ >>> limiter.hit(one_per_minute, "test_namespace", "foo")
149
+ False
150
+ >>> limiter.clear(one_per_minute, "test_namespace", "foo")
151
+ >>> limiter.hit(one_per_minute, "test_namespace", "foo")
152
+ True
138
153
 
139
154
 
140
155
 
@@ -36,18 +36,10 @@ the results in `github <https://github.com/alisaifee/limits/actions/workflows/co
36
36
  :pypi:`redis` can be used instead of :pypi:`coredis` by setting
37
37
  :paramref:`limits.aio.storage.RedisStorage.implementation` to ``redispy``
38
38
 
39
- `Redis <https://redis.io>`_
39
+ `Redis Server <https://redis.io>`_
40
40
 
41
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"
42
42
 
43
- Redis with SSL
44
-
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"
46
-
47
- `Redis Sentinel <https://redis.io/topics/sentinel>`_
48
-
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"
50
-
51
43
  .. tab:: Redis Cluster
52
44
 
53
45
  Dependency versions:
@@ -63,7 +55,7 @@ the results in `github <https://github.com/alisaifee/limits/actions/workflows/co
63
55
  :pypi:`redis` can be used instead of :pypi:`coredis` by setting
64
56
  :paramref:`limits.aio.storage.RedisClusterStorage.implementation` to ``redispy``
65
57
 
66
- `Redis cluster <https://redis.io/topics/cluster-tutorial>`_
58
+ `Redis Cluster <https://redis.io/topics/cluster-tutorial>`_
67
59
 
68
60
  .. 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"
69
61
 
@@ -95,20 +87,6 @@ the results in `github <https://github.com/alisaifee/limits/actions/workflows/co
95
87
 
96
88
  .. 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"
97
89
 
98
- .. tab:: Etcd
99
-
100
- Dependency versions:
101
-
102
- .. literalinclude:: ../../requirements/storage/etcd.txt
103
-
104
- Dependency versions (async):
105
-
106
- .. literalinclude:: ../../requirements/storage/async-etcd.txt
107
-
108
- `Etcd <https://www.etcd.io/>`_
109
-
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"
111
-
112
90
  .. tab:: Valkey
113
91
 
114
92
  Dependency versions:
@@ -246,20 +224,9 @@ Examples:
246
224
 
247
225
  Depends on: :pypi:`pymongo`
248
226
 
249
- Etcd Storage
250
- ------------
251
-
252
- Requires the location of an etcd node
253
-
254
- Example: ``etcd://localhost:2379``
255
-
256
- Depends on: :pypi:`etcd3`
257
-
258
227
  Async Storage
259
228
  =============
260
229
 
261
- .. versionadded:: 2.1
262
-
263
230
  When using limits in an async code base the same uri schema can be used
264
231
  to query for an async implementation of the storage by prefixing the
265
232
  scheme with ``async+``.
@@ -271,7 +238,7 @@ For example:
271
238
  - ``async+redis+cluster://localhost:7000,localhost:7001``
272
239
  - ``async+redis+sentinel://:sekret@localhost:26379/my-redis-service``
273
240
  - ``async+memcached://localhost:11211``
274
- - ``async+etcd://localhost:2379``
241
+ - ``async+mongodb://localhost:27017``
275
242
  - ``async+memory://``
276
243
 
277
244
  For implementation details of currently supported async backends refer to :ref:`api:async storage`
@@ -41,17 +41,6 @@ For example, with a rate limit of 10 requests per minute:
41
41
  (e.g., combine a 2 requests per second limit with a 10 requests per minute limit).
42
42
 
43
43
 
44
-
45
- Fixed Window with Elastic Expiry
46
- ==================================
47
- .. deprecated:: 4.1
48
-
49
- This variant extends the window’s expiry with each hit by resetting the timer on
50
- every request. Although designed to impose larger penalties for breaches, it is now
51
- deprecated and should not be used.
52
-
53
-
54
-
55
44
  Moving Window
56
45
  =============
57
46
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  colors = {
2
4
  "bg0": " #fbf1c7",
3
5
  "bg1": " #ebdbb2",
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-04-09T14:27:52-0700",
11
+ "date": "2025-04-09T18:20:47-0700",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "451bd270b52aff6324de3ed6576a8fc87fbdaf9a",
15
- "version": "4.7.2"
14
+ "full-revisionid": "4a01f1090a5accfb05b7db6dc6469f7c51d4fa67",
15
+ "version": "5.0.0rc1"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -6,14 +6,12 @@ Implementations of storage backends to be used with
6
6
  from __future__ import annotations
7
7
 
8
8
  from .base import MovingWindowSupport, SlidingWindowCounterSupport, Storage
9
- from .etcd import EtcdStorage
10
9
  from .memcached import MemcachedStorage
11
10
  from .memory import MemoryStorage
12
11
  from .mongodb import MongoDBStorage
13
12
  from .redis import RedisClusterStorage, RedisSentinelStorage, RedisStorage
14
13
 
15
14
  __all__ = [
16
- "EtcdStorage",
17
15
  "MemcachedStorage",
18
16
  "MemoryStorage",
19
17
  "MongoDBStorage",
@@ -75,16 +75,12 @@ class Storage(LazyDependency, metaclass=StorageRegistry):
75
75
  raise NotImplementedError
76
76
 
77
77
  @abstractmethod
78
- async def incr(
79
- self, key: str, expiry: int, elastic_expiry: bool = False, amount: int = 1
80
- ) -> int:
78
+ async def incr(self, key: str, expiry: int, amount: int = 1) -> int:
81
79
  """
82
80
  increments the counter for a given rate limit key
83
81
 
84
82
  :param key: the key to increment
85
83
  :param expiry: amount in seconds for the key to expire in
86
- :param elastic_expiry: whether to keep extending the rate limit
87
- window every hit.
88
84
  :param amount: the number to increment by
89
85
  """
90
86
  raise NotImplementedError