METADATA 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. Metadata-Version: 2.4
  2. Name: limits
  3. Version: 4.6
  4. Summary: Rate limiting utilities
  5. Home-page: https://limits.readthedocs.org
  6. Author: Ali-Akber Saifee
  7. Author-email: ali@indydevs.org
  8. License: MIT
  9. Project-URL: Source, https://github.com/alisaifee/limits
  10. Classifier: Development Status :: 5 - Production/Stable
  11. Classifier: Intended Audience :: Developers
  12. Classifier: License :: OSI Approved :: MIT License
  13. Classifier: Operating System :: MacOS
  14. Classifier: Operating System :: POSIX :: Linux
  15. Classifier: Operating System :: OS Independent
  16. Classifier: Topic :: Software Development :: Libraries :: Python Modules
  17. Classifier: Programming Language :: Python :: 3.10
  18. Classifier: Programming Language :: Python :: 3.11
  19. Classifier: Programming Language :: Python :: 3.12
  20. Classifier: Programming Language :: Python :: 3.13
  21. Classifier: Programming Language :: Python :: Implementation :: PyPy
  22. Requires-Python: >=3.10
  23. License-File: LICENSE.txt
  24. Requires-Dist: deprecated>=1.2
  25. Requires-Dist: packaging<25,>=21
  26. Requires-Dist: typing_extensions
  27. Provides-Extra: redis
  28. Requires-Dist: redis!=4.5.2,!=4.5.3,<6.0.0,>3; extra == "redis"
  29. Provides-Extra: rediscluster
  30. Requires-Dist: redis!=4.5.2,!=4.5.3,>=4.2.0; extra == "rediscluster"
  31. Provides-Extra: memcached
  32. Requires-Dist: pymemcache<5.0.0,>3; extra == "memcached"
  33. Provides-Extra: mongodb
  34. Requires-Dist: pymongo<5,>4.1; extra == "mongodb"
  35. Provides-Extra: etcd
  36. Requires-Dist: etcd3; extra == "etcd"
  37. Provides-Extra: valkey
  38. Requires-Dist: valkey>=6; extra == "valkey"
  39. Provides-Extra: async-redis
  40. Requires-Dist: coredis<5,>=3.4.0; extra == "async-redis"
  41. 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"
  44. Provides-Extra: async-mongodb
  45. Requires-Dist: motor<4,>=3; extra == "async-mongodb"
  46. Provides-Extra: async-etcd
  47. Requires-Dist: aetcd; extra == "async-etcd"
  48. Provides-Extra: async-valkey
  49. Requires-Dist: valkey>=6; extra == "async-valkey"
  50. Provides-Extra: all
  51. Requires-Dist: redis!=4.5.2,!=4.5.3,<6.0.0,>3; extra == "all"
  52. Requires-Dist: redis!=4.5.2,!=4.5.3,>=4.2.0; extra == "all"
  53. Requires-Dist: pymemcache<5.0.0,>3; extra == "all"
  54. Requires-Dist: pymongo<5,>4.1; extra == "all"
  55. Requires-Dist: etcd3; extra == "all"
  56. Requires-Dist: valkey>=6; extra == "all"
  57. 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"
  60. Requires-Dist: motor<4,>=3; extra == "all"
  61. Requires-Dist: aetcd; extra == "all"
  62. Requires-Dist: valkey>=6; extra == "all"
  63. Dynamic: author
  64. Dynamic: author-email
  65. Dynamic: classifier
  66. Dynamic: description
  67. Dynamic: home-page
  68. Dynamic: license
  69. Dynamic: license-file
  70. Dynamic: project-url
  71. Dynamic: provides-extra
  72. Dynamic: requires-dist
  73. Dynamic: requires-python
  74. Dynamic: summary
  75. .. |ci| image:: https://github.com/alisaifee/limits/actions/workflows/main.yml/badge.svg?branch=master
  76. :target: https://github.com/alisaifee/limits/actions?query=branch%3Amaster+workflow%3ACI
  77. .. |codecov| image:: https://codecov.io/gh/alisaifee/limits/branch/master/graph/badge.svg
  78. :target: https://codecov.io/gh/alisaifee/limits
  79. .. |pypi| image:: https://img.shields.io/pypi/v/limits.svg?style=flat-square
  80. :target: https://pypi.python.org/pypi/limits
  81. .. |pypi-versions| image:: https://img.shields.io/pypi/pyversions/limits?style=flat-square
  82. :target: https://pypi.python.org/pypi/limits
  83. .. |license| image:: https://img.shields.io/pypi/l/limits.svg?style=flat-square
  84. :target: https://pypi.python.org/pypi/limits
  85. .. |docs| image:: https://readthedocs.org/projects/limits/badge/?version=latest
  86. :target: https://limits.readthedocs.org
  87. limits
  88. ------
  89. |docs| |ci| |codecov| |pypi| |pypi-versions| |license|
  90. **limits** is a python library for rate limiting via multiple strategies
  91. with commonly used storage backends (Redis, Memcached, MongoDB & Etcd).
  92. The library provides identical APIs for use in sync and
  93. `async <https://limits.readthedocs.io/en/stable/async.html>`_ codebases.
  94. Supported Strategies
  95. ====================
  96. All strategies support the follow methods:
  97. - `hit <https://limits.readthedocs.io/en/stable/api.html#limits.strategies.RateLimiter.hit>`_: consume a request.
  98. - `test <https://limits.readthedocs.io/en/stable/api.html#limits.strategies.RateLimiter.test>`_: check if a request is allowed.
  99. - `get_window_stats <https://limits.readthedocs.io/en/stable/api.html#limits.strategies.RateLimiter.get_window_stats>`_: retrieve remaining quota and reset time.
  100. Fixed Window
  101. ------------
  102. `Fixed Window <https://limits.readthedocs.io/en/latest/strategies.html#fixed-window>`_
  103. This strategy is the most memory‑efficient because it uses a single counter per resource and
  104. rate limit. When the first request arrives, a window is started for a fixed duration
  105. (e.g., for a rate limit of 10 requests per minute the window expires in 60 seconds from the first request).
  106. All requests in that window increment the counter and when the window expires, the counter resets.
  107. Burst traffic that bypasses the rate limit may occur at window boundaries.
  108. For example, with a rate limit of 10 requests per minute:
  109. - At **00:00:45**, the first request arrives, starting a window from **00:00:45** to **00:01:45**.
  110. - All requests between **00:00:45** and **00:01:45** count toward the limit.
  111. - If 10 requests occur at any time in that window, any further request before **00:01:45** is rejected.
  112. - At **00:01:45**, the counter resets and a new window starts which would allow 10 requests
  113. until **00:02:45**.
  114. Moving Window
  115. -------------
  116. `Moving Window <https://limits.readthedocs.io/en/latest/strategies.html#moving-window>`_
  117. This strategy adds each request’s timestamp to a log if the ``nth`` oldest entry (where ``n``
  118. is the limit) is either not present or is older than the duration of the window (for example with a rate limit of
  119. ``10 requests per minute`` if there are either less than 10 entries or the 10th oldest entry is at least
  120. 60 seconds old). Upon adding a new entry to the log "expired" entries are truncated.
  121. For example, with a rate limit of 10 requests per minute:
  122. - At **00:00:10**, a client sends 1 requests which are allowed.
  123. - At **00:00:20**, a client sends 2 requests which are allowed.
  124. - At **00:00:30**, the client sends 4 requests which are allowed.
  125. - At **00:00:50**, the client sends 3 requests which are allowed (total = 10).
  126. - At **00:01:11**, the client sends 1 request. The strategy checks the timestamp of the
  127. 10th oldest entry (**00:00:10**) which is now 61 seconds old and thus expired. The request
  128. is allowed.
  129. - At **00:01:12**, the client sends 1 request. The 10th oldest entry's timestamp is **00:00:20**
  130. which is only 52 seconds old. The request is rejected.
  131. Sliding Window Counter
  132. ------------------------
  133. `Sliding Window Counter <https://limits.readthedocs.io/en/latest/strategies.html#sliding-window-counter>`_
  134. This strategy approximates the moving window while using less memory by maintaining
  135. two counters:
  136. - **Current bucket:** counts requests in the ongoing period.
  137. - **Previous bucket:** counts requests in the immediately preceding period.
  138. When a request arrives, the effective request count is calculated as::
  139. weighted_count = current_count + floor(previous_count * weight)
  140. The weight is based on how much time has elapsed in the current bucket::
  141. weight = (bucket_duration - elapsed_time) / bucket_duration
  142. If ``weighted_count`` is below the limit, the request is allowed.
  143. For example, with a rate limit of 10 requests per minute:
  144. Assume:
  145. - The current bucket (spanning **00:01:00** to **00:02:00**) has 8 hits.
  146. - The previous bucket (spanning **00:00:00** to **00:01:00**) has 4 hits.
  147. Scenario 1:
  148. - A new request arrives at **00:01:30**, 30 seconds into the current bucket.
  149. - ``weight = (60 - 30) / 60 = 0.5``.
  150. - ``weighted_count = floor(8 + (4 * 0.5)) = floor(8 + 2) = 10``.
  151. - Since the weighted count equals the limit, the request is rejected.
  152. Scenario 2:
  153. - A new request arrives at **00:01:40**, 40 seconds into the current bucket.
  154. - ``weight = (60 - 40) / 60 ≈ 0.33``.
  155. - ``weighted_count = floor(8 + (4 * 0.33)) = floor(8 + 1.32) = 9``.
  156. - Since the weighted count is below the limit, the request is allowed.
  157. Storage backends
  158. ================
  159. - `Redis <https://limits.readthedocs.io/en/latest/storage.html#redis-storage>`_
  160. - `Memcached <https://limits.readthedocs.io/en/latest/storage.html#memcached-storage>`_
  161. - `MongoDB <https://limits.readthedocs.io/en/latest/storage.html#mongodb-storage>`_
  162. - `Etcd <https://limits.readthedocs.io/en/latest/storage.html#etcd-storage>`_
  163. - `In-Memory <https://limits.readthedocs.io/en/latest/storage.html#in-memory-storage>`_
  164. Dive right in
  165. =============
  166. Initialize the storage backend
  167. .. code-block:: python
  168. from limits import storage
  169. backend = storage.MemoryStorage()
  170. # or memcached
  171. backend = storage.MemcachedStorage("memcached://localhost:11211")
  172. # or redis
  173. backend = storage.RedisStorage("redis://localhost:6379")
  174. # or mongodb
  175. backend = storage.MongoDbStorage("mongodb://localhost:27017")
  176. # or use the factory
  177. storage_uri = "memcached://localhost:11211"
  178. backend = storage.storage_from_string(storage_uri)
  179. Initialize a rate limiter with a strategy
  180. .. code-block:: python
  181. from limits import strategies
  182. strategy = strategies.MovingWindowRateLimiter(backend)
  183. # or fixed window
  184. strategy = strategies.FixedWindowRateLimiter(backend)
  185. # or sliding window
  186. strategy = strategies.SlidingWindowCounterRateLimiter(backend)
  187. Initialize a rate limit
  188. .. code-block:: python
  189. from limits import parse
  190. one_per_minute = parse("1/minute")
  191. Initialize a rate limit explicitly
  192. .. code-block:: python
  193. from limits import RateLimitItemPerSecond
  194. one_per_second = RateLimitItemPerSecond(1, 1)
  195. Test the limits
  196. .. code-block:: python
  197. import time
  198. assert True == strategy.hit(one_per_minute, "test_namespace", "foo")
  199. assert False == strategy.hit(one_per_minute, "test_namespace", "foo")
  200. assert True == strategy.hit(one_per_minute, "test_namespace", "bar")
  201. assert True == strategy.hit(one_per_second, "test_namespace", "foo")
  202. assert False == strategy.hit(one_per_second, "test_namespace", "foo")
  203. time.sleep(1)
  204. assert True == strategy.hit(one_per_second, "test_namespace", "foo")
  205. Check specific limits without hitting them
  206. .. code-block:: python
  207. assert True == strategy.hit(one_per_second, "test_namespace", "foo")
  208. while not strategy.test(one_per_second, "test_namespace", "foo"):
  209. time.sleep(0.01)
  210. assert True == strategy.hit(one_per_second, "test_namespace", "foo")
  211. Query available capacity and reset time for a limit
  212. .. code-block:: python
  213. assert True == strategy.hit(one_per_minute, "test_namespace", "foo")
  214. window = strategy.get_window_stats(one_per_minute, "test_namespace", "foo")
  215. assert window.remaining == 0
  216. assert False == strategy.hit(one_per_minute, "test_namespace", "foo")
  217. time.sleep(window.reset_time - time.time())
  218. assert True == strategy.hit(one_per_minute, "test_namespace", "foo")
  219. Links
  220. =====
  221. * `Documentation <http://limits.readthedocs.org/en/latest>`_
  222. * `Changelog <http://limits.readthedocs.org/en/stable/changelog.html>`_