Update 2025-04-24_11:44:19
This commit is contained in:
@ -0,0 +1 @@
|
||||
pip
|
@ -0,0 +1,279 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: limits
|
||||
Version: 5.0.0
|
||||
Summary: Rate limiting utilities
|
||||
Home-page: https://limits.readthedocs.org
|
||||
Author: Ali-Akber Saifee
|
||||
Author-email: ali@indydevs.org
|
||||
License: MIT
|
||||
Project-URL: Source, https://github.com/alisaifee/limits
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: MacOS
|
||||
Classifier: Operating System :: POSIX :: Linux
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Requires-Python: >=3.10
|
||||
License-File: LICENSE.txt
|
||||
Requires-Dist: deprecated>=1.2
|
||||
Requires-Dist: packaging<25,>=21
|
||||
Requires-Dist: typing_extensions
|
||||
Provides-Extra: redis
|
||||
Requires-Dist: redis!=4.5.2,!=4.5.3,<6.0.0,>3; extra == "redis"
|
||||
Provides-Extra: rediscluster
|
||||
Requires-Dist: redis!=4.5.2,!=4.5.3,>=4.2.0; extra == "rediscluster"
|
||||
Provides-Extra: memcached
|
||||
Requires-Dist: pymemcache<5.0.0,>3; extra == "memcached"
|
||||
Provides-Extra: mongodb
|
||||
Requires-Dist: pymongo<5,>4.1; extra == "mongodb"
|
||||
Provides-Extra: valkey
|
||||
Requires-Dist: valkey>=6; extra == "valkey"
|
||||
Provides-Extra: async-redis
|
||||
Requires-Dist: coredis<5,>=3.4.0; extra == "async-redis"
|
||||
Provides-Extra: async-memcached
|
||||
Requires-Dist: memcachio>=0.3; extra == "async-memcached"
|
||||
Provides-Extra: async-mongodb
|
||||
Requires-Dist: motor<4,>=3; extra == "async-mongodb"
|
||||
Provides-Extra: async-valkey
|
||||
Requires-Dist: valkey>=6; extra == "async-valkey"
|
||||
Provides-Extra: all
|
||||
Requires-Dist: redis!=4.5.2,!=4.5.3,<6.0.0,>3; extra == "all"
|
||||
Requires-Dist: redis!=4.5.2,!=4.5.3,>=4.2.0; extra == "all"
|
||||
Requires-Dist: pymemcache<5.0.0,>3; extra == "all"
|
||||
Requires-Dist: pymongo<5,>4.1; extra == "all"
|
||||
Requires-Dist: valkey>=6; extra == "all"
|
||||
Requires-Dist: coredis<5,>=3.4.0; extra == "all"
|
||||
Requires-Dist: memcachio>=0.3; extra == "all"
|
||||
Requires-Dist: motor<4,>=3; extra == "all"
|
||||
Requires-Dist: valkey>=6; extra == "all"
|
||||
Dynamic: author
|
||||
Dynamic: author-email
|
||||
Dynamic: classifier
|
||||
Dynamic: description
|
||||
Dynamic: home-page
|
||||
Dynamic: license
|
||||
Dynamic: license-file
|
||||
Dynamic: project-url
|
||||
Dynamic: provides-extra
|
||||
Dynamic: requires-dist
|
||||
Dynamic: requires-python
|
||||
Dynamic: summary
|
||||
|
||||
.. |ci| image:: https://github.com/alisaifee/limits/actions/workflows/main.yml/badge.svg?branch=master
|
||||
:target: https://github.com/alisaifee/limits/actions?query=branch%3Amaster+workflow%3ACI
|
||||
.. |codecov| image:: https://codecov.io/gh/alisaifee/limits/branch/master/graph/badge.svg
|
||||
:target: https://codecov.io/gh/alisaifee/limits
|
||||
.. |pypi| image:: https://img.shields.io/pypi/v/limits.svg?style=flat-square
|
||||
:target: https://pypi.python.org/pypi/limits
|
||||
.. |pypi-versions| image:: https://img.shields.io/pypi/pyversions/limits?style=flat-square
|
||||
:target: https://pypi.python.org/pypi/limits
|
||||
.. |license| image:: https://img.shields.io/pypi/l/limits.svg?style=flat-square
|
||||
:target: https://pypi.python.org/pypi/limits
|
||||
.. |docs| image:: https://readthedocs.org/projects/limits/badge/?version=latest
|
||||
:target: https://limits.readthedocs.org
|
||||
|
||||
######
|
||||
limits
|
||||
######
|
||||
|docs| |ci| |codecov| |pypi| |pypi-versions| |license|
|
||||
|
||||
|
||||
**limits** is a python library for rate limiting via multiple strategies
|
||||
with commonly used storage backends (Redis, Memcached & MongoDB).
|
||||
|
||||
The library provides identical APIs for use in sync and
|
||||
`async <https://limits.readthedocs.io/en/stable/async.html>`_ codebases.
|
||||
|
||||
|
||||
Supported Strategies
|
||||
====================
|
||||
|
||||
All strategies support the follow methods:
|
||||
|
||||
- `hit <https://limits.readthedocs.io/en/stable/api.html#limits.strategies.RateLimiter.hit>`_: consume a request.
|
||||
- `test <https://limits.readthedocs.io/en/stable/api.html#limits.strategies.RateLimiter.test>`_: check if a request is allowed.
|
||||
- `get_window_stats <https://limits.readthedocs.io/en/stable/api.html#limits.strategies.RateLimiter.get_window_stats>`_: retrieve remaining quota and reset time.
|
||||
|
||||
Fixed Window
|
||||
------------
|
||||
`Fixed Window <https://limits.readthedocs.io/en/latest/strategies.html#fixed-window>`_
|
||||
|
||||
This strategy is the most memory‑efficient because it uses a single counter per resource and
|
||||
rate limit. When the first request arrives, a window is started for a fixed duration
|
||||
(e.g., for a rate limit of 10 requests per minute the window expires in 60 seconds from the first request).
|
||||
All requests in that window increment the counter and when the window expires, the counter resets.
|
||||
|
||||
Burst traffic that bypasses the rate limit may occur at window boundaries.
|
||||
|
||||
For example, with a rate limit of 10 requests per minute:
|
||||
|
||||
- At **00:00:45**, the first request arrives, starting a window from **00:00:45** to **00:01:45**.
|
||||
- All requests between **00:00:45** and **00:01:45** count toward the limit.
|
||||
- If 10 requests occur at any time in that window, any further request before **00:01:45** is rejected.
|
||||
- At **00:01:45**, the counter resets and a new window starts which would allow 10 requests
|
||||
until **00:02:45**.
|
||||
|
||||
Moving Window
|
||||
-------------
|
||||
`Moving Window <https://limits.readthedocs.io/en/latest/strategies.html#moving-window>`_
|
||||
|
||||
This strategy adds each request’s timestamp to a log if the ``nth`` oldest entry (where ``n``
|
||||
is the limit) is either not present or is older than the duration of the window (for example with a rate limit of
|
||||
``10 requests per minute`` if there are either less than 10 entries or the 10th oldest entry is at least
|
||||
60 seconds old). Upon adding a new entry to the log "expired" entries are truncated.
|
||||
|
||||
For example, with a rate limit of 10 requests per minute:
|
||||
|
||||
- At **00:00:10**, a client sends 1 requests which are allowed.
|
||||
- At **00:00:20**, a client sends 2 requests which are allowed.
|
||||
- At **00:00:30**, the client sends 4 requests which are allowed.
|
||||
- At **00:00:50**, the client sends 3 requests which are allowed (total = 10).
|
||||
- At **00:01:11**, the client sends 1 request. The strategy checks the timestamp of the
|
||||
10th oldest entry (**00:00:10**) which is now 61 seconds old and thus expired. The request
|
||||
is allowed.
|
||||
- At **00:01:12**, the client sends 1 request. The 10th oldest entry's timestamp is **00:00:20**
|
||||
which is only 52 seconds old. The request is rejected.
|
||||
|
||||
Sliding Window Counter
|
||||
------------------------
|
||||
`Sliding Window Counter <https://limits.readthedocs.io/en/latest/strategies.html#sliding-window-counter>`_
|
||||
|
||||
This strategy approximates the moving window while using less memory by maintaining
|
||||
two counters:
|
||||
|
||||
- **Current bucket:** counts requests in the ongoing period.
|
||||
- **Previous bucket:** counts requests in the immediately preceding period.
|
||||
|
||||
When a request arrives, the effective request count is calculated as::
|
||||
|
||||
weighted_count = current_count + floor(previous_count * weight)
|
||||
|
||||
The weight is based on how much time has elapsed in the current bucket::
|
||||
|
||||
weight = (bucket_duration - elapsed_time) / bucket_duration
|
||||
|
||||
If ``weighted_count`` is below the limit, the request is allowed.
|
||||
|
||||
For example, with a rate limit of 10 requests per minute:
|
||||
|
||||
Assume:
|
||||
|
||||
- The current bucket (spanning **00:01:00** to **00:02:00**) has 8 hits.
|
||||
- The previous bucket (spanning **00:00:00** to **00:01:00**) has 4 hits.
|
||||
|
||||
Scenario 1:
|
||||
|
||||
- A new request arrives at **00:01:30**, 30 seconds into the current bucket.
|
||||
- ``weight = (60 - 30) / 60 = 0.5``.
|
||||
- ``weighted_count = floor(8 + (4 * 0.5)) = floor(8 + 2) = 10``.
|
||||
- Since the weighted count equals the limit, the request is rejected.
|
||||
|
||||
Scenario 2:
|
||||
|
||||
- A new request arrives at **00:01:40**, 40 seconds into the current bucket.
|
||||
- ``weight = (60 - 40) / 60 ≈ 0.33``.
|
||||
- ``weighted_count = floor(8 + (4 * 0.33)) = floor(8 + 1.32) = 9``.
|
||||
- Since the weighted count is below the limit, the request is allowed.
|
||||
|
||||
|
||||
Storage backends
|
||||
================
|
||||
|
||||
- `Redis <https://limits.readthedocs.io/en/latest/storage.html#redis-storage>`_
|
||||
- `Memcached <https://limits.readthedocs.io/en/latest/storage.html#memcached-storage>`_
|
||||
- `MongoDB <https://limits.readthedocs.io/en/latest/storage.html#mongodb-storage>`_
|
||||
- `In-Memory <https://limits.readthedocs.io/en/latest/storage.html#in-memory-storage>`_
|
||||
|
||||
Dive right in
|
||||
=============
|
||||
|
||||
Initialize the storage backend
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from limits import storage
|
||||
backend = storage.MemoryStorage()
|
||||
# or memcached
|
||||
backend = storage.MemcachedStorage("memcached://localhost:11211")
|
||||
# or redis
|
||||
backend = storage.RedisStorage("redis://localhost:6379")
|
||||
# or mongodb
|
||||
backend = storage.MongoDbStorage("mongodb://localhost:27017")
|
||||
# or use the factory
|
||||
storage_uri = "memcached://localhost:11211"
|
||||
backend = storage.storage_from_string(storage_uri)
|
||||
|
||||
Initialize a rate limiter with a strategy
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from limits import strategies
|
||||
strategy = strategies.MovingWindowRateLimiter(backend)
|
||||
# or fixed window
|
||||
strategy = strategies.FixedWindowRateLimiter(backend)
|
||||
# or sliding window
|
||||
strategy = strategies.SlidingWindowCounterRateLimiter(backend)
|
||||
|
||||
|
||||
Initialize a rate limit
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from limits import parse
|
||||
one_per_minute = parse("1/minute")
|
||||
|
||||
Initialize a rate limit explicitly
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from limits import RateLimitItemPerSecond
|
||||
one_per_second = RateLimitItemPerSecond(1, 1)
|
||||
|
||||
Test the limits
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import time
|
||||
assert True == strategy.hit(one_per_minute, "test_namespace", "foo")
|
||||
assert False == strategy.hit(one_per_minute, "test_namespace", "foo")
|
||||
assert True == strategy.hit(one_per_minute, "test_namespace", "bar")
|
||||
|
||||
assert True == strategy.hit(one_per_second, "test_namespace", "foo")
|
||||
assert False == strategy.hit(one_per_second, "test_namespace", "foo")
|
||||
time.sleep(1)
|
||||
assert True == strategy.hit(one_per_second, "test_namespace", "foo")
|
||||
|
||||
Check specific limits without hitting them
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
assert True == strategy.hit(one_per_second, "test_namespace", "foo")
|
||||
while not strategy.test(one_per_second, "test_namespace", "foo"):
|
||||
time.sleep(0.01)
|
||||
assert True == strategy.hit(one_per_second, "test_namespace", "foo")
|
||||
|
||||
Query available capacity and reset time for a limit
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
assert True == strategy.hit(one_per_minute, "test_namespace", "foo")
|
||||
window = strategy.get_window_stats(one_per_minute, "test_namespace", "foo")
|
||||
assert window.remaining == 0
|
||||
assert False == strategy.hit(one_per_minute, "test_namespace", "foo")
|
||||
time.sleep(window.reset_time - time.time())
|
||||
assert True == strategy.hit(one_per_minute, "test_namespace", "foo")
|
||||
|
||||
|
||||
Links
|
||||
=====
|
||||
|
||||
* `Documentation <http://limits.readthedocs.org/en/latest>`_
|
||||
* `Benchmarks <http://limits.readthedocs.org/en/latest/performance.html>`_
|
||||
* `Changelog <http://limits.readthedocs.org/en/stable/changelog.html>`_
|
||||
|
@ -0,0 +1,77 @@
|
||||
limits-5.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
limits-5.0.0.dist-info/METADATA,sha256=7BekivDD_wa-dZNTl9L7JLcXs0Dxp_ta5_XWsC9nxQY,10900
|
||||
limits-5.0.0.dist-info/RECORD,,
|
||||
limits-5.0.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
||||
limits-5.0.0.dist-info/licenses/LICENSE.txt,sha256=T6i7kq7F5gIPfcno9FCxU5Hcwm22Bjq0uHZV3ElcjsQ,1061
|
||||
limits-5.0.0.dist-info/top_level.txt,sha256=C7g5ahldPoU2s6iWTaJayUrbGmPK1d6e9t5Nn0vQ2jM,7
|
||||
limits/__init__.py,sha256=gPUFrt02kHF_syLjiVRSs-S4UVGpRMcM2VMFNhF6G24,748
|
||||
limits/__pycache__/__init__.cpython-311.pyc,,
|
||||
limits/__pycache__/_version.cpython-311.pyc,,
|
||||
limits/__pycache__/errors.cpython-311.pyc,,
|
||||
limits/__pycache__/limits.cpython-311.pyc,,
|
||||
limits/__pycache__/strategies.cpython-311.pyc,,
|
||||
limits/__pycache__/typing.cpython-311.pyc,,
|
||||
limits/__pycache__/util.cpython-311.pyc,,
|
||||
limits/__pycache__/version.cpython-311.pyc,,
|
||||
limits/_version.py,sha256=7_DQRCmIEsWJeuCEDoA9jxz1DI-qiHiKrasIqMZLnk0,497
|
||||
limits/aio/__init__.py,sha256=yxvWb_ZmV245Hg2LqD365WC5IDllcGDMw6udJ1jNp1g,118
|
||||
limits/aio/__pycache__/__init__.cpython-311.pyc,,
|
||||
limits/aio/__pycache__/strategies.cpython-311.pyc,,
|
||||
limits/aio/storage/__init__.py,sha256=vKeArUnN1ld_0mQOBBZPCjaQgM5xI1GBPM7_F2Ydz5c,646
|
||||
limits/aio/storage/__pycache__/__init__.cpython-311.pyc,,
|
||||
limits/aio/storage/__pycache__/base.cpython-311.pyc,,
|
||||
limits/aio/storage/__pycache__/memory.cpython-311.pyc,,
|
||||
limits/aio/storage/__pycache__/mongodb.cpython-311.pyc,,
|
||||
limits/aio/storage/base.py,sha256=VfHpL9Z3RL76eKhoaSQKLKQsqcF5B2bnF6gfa-8ltWA,6296
|
||||
limits/aio/storage/memcached/__init__.py,sha256=VMWsH4XpaPswtPV7cQmsfckhVRbOOrKvoUPYnGt5MRY,6611
|
||||
limits/aio/storage/memcached/__pycache__/__init__.cpython-311.pyc,,
|
||||
limits/aio/storage/memcached/__pycache__/bridge.cpython-311.pyc,,
|
||||
limits/aio/storage/memcached/__pycache__/emcache.cpython-311.pyc,,
|
||||
limits/aio/storage/memcached/__pycache__/memcachio.cpython-311.pyc,,
|
||||
limits/aio/storage/memcached/bridge.py,sha256=3CEruS6LvZWDQPGPLlwY4hemy6oN0WWduUE7t8vyXBI,2017
|
||||
limits/aio/storage/memcached/emcache.py,sha256=J01jP-Udd2fLgamCh2CX9NEIvhN8eZVTzUok096Bbe4,3833
|
||||
limits/aio/storage/memcached/memcachio.py,sha256=OoGVqOVG0pVX2McFeTGQ_AbiqQUu_FYwWItpQMtNV7g,3491
|
||||
limits/aio/storage/memory.py,sha256=sWrDzOe-6Opy9uFmfP1S38IbN2_wNCBaIHTS4UTRy6g,9562
|
||||
limits/aio/storage/mongodb.py,sha256=tIMfQrseONRMR2nuRmPO7ocp8dTCABfqBICS_kgp550,19141
|
||||
limits/aio/storage/redis/__init__.py,sha256=lwoKk91YLEBlZ3W6hCnQ1e7Gc6LxpvSzZZW16saCyR4,14143
|
||||
limits/aio/storage/redis/__pycache__/__init__.cpython-311.pyc,,
|
||||
limits/aio/storage/redis/__pycache__/bridge.cpython-311.pyc,,
|
||||
limits/aio/storage/redis/__pycache__/coredis.cpython-311.pyc,,
|
||||
limits/aio/storage/redis/__pycache__/redispy.cpython-311.pyc,,
|
||||
limits/aio/storage/redis/__pycache__/valkey.cpython-311.pyc,,
|
||||
limits/aio/storage/redis/bridge.py,sha256=eoRi9h2bSy194cVwoKgRYQV1HQ7SvwarL-4LeazrxeA,3145
|
||||
limits/aio/storage/redis/coredis.py,sha256=IzfEyXBvQbr4QUWML9xAd87a2aHCvglOBEjAg-Vq4z0,7420
|
||||
limits/aio/storage/redis/redispy.py,sha256=HS1H6E9g0dP3G-8tSUILIFoc8JWpeRQOiBxcpL3I0gM,8310
|
||||
limits/aio/storage/redis/valkey.py,sha256=f_-HPZhzNspywGybMNIL0F5uDZk76v8_K9wuC5ZeKhc,248
|
||||
limits/aio/strategies.py,sha256=RzZExH2r6jnHra4SpDHqtZCC0Bo3085zUJYo2boAj6Y,9897
|
||||
limits/errors.py,sha256=s1el9Vg0ly-z92guvnvYNgKi3_aVqpiw_sufemiLLTI,662
|
||||
limits/limits.py,sha256=YzzZP8_ay_zlMMnnY2xhAcFTTFvFe5HEk8NQlvUTru4,4907
|
||||
limits/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
limits/resources/redis/lua_scripts/acquire_moving_window.lua,sha256=Vz0HkI_bSFLW668lEVw8paKlTLEuU4jZk1fpdSuz3zg,594
|
||||
limits/resources/redis/lua_scripts/acquire_sliding_window.lua,sha256=OhVI1MAN_gT92P6r-2CEmvy1yvQVjYCCZxWIxfXYceY,1329
|
||||
limits/resources/redis/lua_scripts/clear_keys.lua,sha256=zU0cVfLGmapRQF9x9u0GclapM_IB2pJLszNzVQ1QRK4,184
|
||||
limits/resources/redis/lua_scripts/incr_expire.lua,sha256=Uq9NcrrcDI-F87TDAJexoSJn2SDgeXIUEYozCp9S3oA,195
|
||||
limits/resources/redis/lua_scripts/moving_window.lua,sha256=zlieQwfET0BC7sxpfiOuzPa1wwmrwWLy7IF8LxNa_Lw,717
|
||||
limits/resources/redis/lua_scripts/sliding_window.lua,sha256=qG3Yg30Dq54QpRUcR9AOrKQ5bdJiaYpCacTm6Kxblvc,713
|
||||
limits/storage/__init__.py,sha256=9iNxIlwzLQw2d54EcMa2LBJ47wiWCPOnHgn6ddqKkDI,2652
|
||||
limits/storage/__pycache__/__init__.cpython-311.pyc,,
|
||||
limits/storage/__pycache__/base.cpython-311.pyc,,
|
||||
limits/storage/__pycache__/memcached.cpython-311.pyc,,
|
||||
limits/storage/__pycache__/memory.cpython-311.pyc,,
|
||||
limits/storage/__pycache__/mongodb.cpython-311.pyc,,
|
||||
limits/storage/__pycache__/redis.cpython-311.pyc,,
|
||||
limits/storage/__pycache__/redis_cluster.cpython-311.pyc,,
|
||||
limits/storage/__pycache__/redis_sentinel.cpython-311.pyc,,
|
||||
limits/storage/__pycache__/registry.cpython-311.pyc,,
|
||||
limits/storage/base.py,sha256=IdOL_iqR9KhaJO73M_h9c6OYe8Ox632pxx5uXaL9Dbo,6860
|
||||
limits/storage/memcached.py,sha256=5GUKGWS_BYTwUss2WmOlCwBtOieGT7AFUcpX65WYXdQ,10217
|
||||
limits/storage/memory.py,sha256=rVlsirSp9LDhuqNFp6KMLR85fJc9xwrU58IHIVz6eq4,8719
|
||||
limits/storage/mongodb.py,sha256=V4Ib_AwPFX6JpNI7oUUGJx_3MxD8EmYAi4Q6QcWnQ5U,18071
|
||||
limits/storage/redis.py,sha256=i_6qh4S6JQd-lG6eRJdTPxNnZIAkm4G0cA0mfow9OOk,10389
|
||||
limits/storage/redis_cluster.py,sha256=z6aONMl4p1AY78G3J0BbtK--uztz88krwnpiOsU61BM,4447
|
||||
limits/storage/redis_sentinel.py,sha256=AN0WtwHN88TvXk0C2uUE8l5Jhsd1ZxU8XSqrEyQSR20,4327
|
||||
limits/storage/registry.py,sha256=CxSaDBGR5aBJPFAIsfX9axCnbcThN3Bu-EH4wHrXtu8,650
|
||||
limits/strategies.py,sha256=LeZ6lnE73EIQqQ8TfKaTzlxNvBMrZOOSXFB0l8D17fI,9946
|
||||
limits/typing.py,sha256=pVt5D23MhQSUGqi0MBG5FCSqDwta2ygu18BpKvJFxow,3283
|
||||
limits/util.py,sha256=nk5QYvezFuXPq1OTEj04RrZFSWIH-khT0e_Dim6zGCw,6002
|
||||
limits/version.py,sha256=YwkF3dtq1KGzvmL3iVGctA8NNtGlK_0arrzZkZGVjUs,47
|
@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (78.1.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
@ -0,0 +1,20 @@
|
||||
Copyright (c) 2023 Ali-Akber Saifee
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
@ -0,0 +1 @@
|
||||
limits
|
Reference in New Issue
Block a user