update to python fastpi
This commit is contained in:
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,259 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: limits
|
||||
Version: 5.6.0
|
||||
Summary: Rate limiting utilities
|
||||
Project-URL: Homepage, https://limits.readthedocs.org
|
||||
Project-URL: Source, https://github.com/alisaifee/limits
|
||||
Project-URL: Documentation, https://limits.readthedocs.org
|
||||
Author-email: Ali-Akber Saifee <ali@indydevs.org>
|
||||
Maintainer-email: Ali-Akber Saifee <ali@indydevs.org>
|
||||
License-Expression: MIT
|
||||
License-File: LICENSE.txt
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: MacOS
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Operating System :: POSIX :: Linux
|
||||
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
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Requires-Python: >=3.10
|
||||
Requires-Dist: deprecated>=1.2
|
||||
Requires-Dist: packaging>=21
|
||||
Requires-Dist: typing-extensions
|
||||
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-redis
|
||||
Requires-Dist: coredis<6,>=3.4.0; extra == 'async-redis'
|
||||
Provides-Extra: async-valkey
|
||||
Requires-Dist: valkey>=6; extra == 'async-valkey'
|
||||
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: redis
|
||||
Requires-Dist: redis!=4.5.2,!=4.5.3,<7.0.0,>3; extra == 'redis'
|
||||
Provides-Extra: rediscluster
|
||||
Requires-Dist: redis!=4.5.2,!=4.5.3,>=4.2.0; extra == 'rediscluster'
|
||||
Provides-Extra: valkey
|
||||
Requires-Dist: valkey>=6; extra == 'valkey'
|
||||
Description-Content-Type: text/x-rst
|
||||
|
||||
.. |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,75 @@
|
||||
limits-5.6.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
limits-5.6.0.dist-info/METADATA,sha256=AnwByiIGfXg-bL9FEzNEZ82RG2OXhbzkCMds8DZtob4,10357
|
||||
limits-5.6.0.dist-info/RECORD,,
|
||||
limits-5.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
||||
limits-5.6.0.dist-info/licenses/LICENSE.txt,sha256=T6i7kq7F5gIPfcno9FCxU5Hcwm22Bjq0uHZV3ElcjsQ,1061
|
||||
limits/__init__.py,sha256=o797RFCrvTx-9XGu8tsQYH5ZOtX-UVh6xwq_eatc_AU,734
|
||||
limits/__pycache__/__init__.cpython-312.pyc,,
|
||||
limits/__pycache__/_version.cpython-312.pyc,,
|
||||
limits/__pycache__/errors.cpython-312.pyc,,
|
||||
limits/__pycache__/limits.cpython-312.pyc,,
|
||||
limits/__pycache__/strategies.cpython-312.pyc,,
|
||||
limits/__pycache__/typing.cpython-312.pyc,,
|
||||
limits/__pycache__/util.cpython-312.pyc,,
|
||||
limits/_version.py,sha256=3ucixGE1aqqT6_wVh9iJlYsogjPN83_jiyXCqsGzfqU,704
|
||||
limits/_version.pyi,sha256=Y25n44pyE3vp92MiABKrcK3IWRyQ1JG1rZ4Ufqy2nC0,17
|
||||
limits/aio/__init__.py,sha256=yxvWb_ZmV245Hg2LqD365WC5IDllcGDMw6udJ1jNp1g,118
|
||||
limits/aio/__pycache__/__init__.cpython-312.pyc,,
|
||||
limits/aio/__pycache__/strategies.cpython-312.pyc,,
|
||||
limits/aio/storage/__init__.py,sha256=vKeArUnN1ld_0mQOBBZPCjaQgM5xI1GBPM7_F2Ydz5c,646
|
||||
limits/aio/storage/__pycache__/__init__.cpython-312.pyc,,
|
||||
limits/aio/storage/__pycache__/base.cpython-312.pyc,,
|
||||
limits/aio/storage/__pycache__/memory.cpython-312.pyc,,
|
||||
limits/aio/storage/__pycache__/mongodb.cpython-312.pyc,,
|
||||
limits/aio/storage/base.py,sha256=56UyNz3I3J-4pQecjsaCK4pUC4L3R_9GzDnutdTrfKs,6706
|
||||
limits/aio/storage/memcached/__init__.py,sha256=SjAEgxC6hPjobtyTf7tq3vThPMMbS4lGdtTo5kvoz64,6885
|
||||
limits/aio/storage/memcached/__pycache__/__init__.cpython-312.pyc,,
|
||||
limits/aio/storage/memcached/__pycache__/bridge.cpython-312.pyc,,
|
||||
limits/aio/storage/memcached/__pycache__/emcache.cpython-312.pyc,,
|
||||
limits/aio/storage/memcached/__pycache__/memcachio.cpython-312.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=-U_GWPWmR77Hzi1Oa1_L1WjiAlROTS8PNG8PROAm13c,9842
|
||||
limits/aio/storage/mongodb.py,sha256=0kwDyivA53ZIOUH4DNnCjVG3olLJqAWhXctjPrnHUp0,19252
|
||||
limits/aio/storage/redis/__init__.py,sha256=RjGus6rj-RhUR4eqTcnpxgicCt_rPtwFkC_SmbKfoqQ,15032
|
||||
limits/aio/storage/redis/__pycache__/__init__.cpython-312.pyc,,
|
||||
limits/aio/storage/redis/__pycache__/bridge.cpython-312.pyc,,
|
||||
limits/aio/storage/redis/__pycache__/coredis.cpython-312.pyc,,
|
||||
limits/aio/storage/redis/__pycache__/redispy.cpython-312.pyc,,
|
||||
limits/aio/storage/redis/__pycache__/valkey.cpython-312.pyc,,
|
||||
limits/aio/storage/redis/bridge.py,sha256=tz6WGViOqIm81hjGPUOBlz-Qw0tSB71NIttn7Xb5lok,3189
|
||||
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=ip7NJ_6FvEtICr90tesayaXcsqrmpG7VlC3PwxbfiVQ,10736
|
||||
limits/errors.py,sha256=s1el9Vg0ly-z92guvnvYNgKi3_aVqpiw_sufemiLLTI,662
|
||||
limits/limits.py,sha256=EztiGCXBVwIqNtps77HiW6vLlMO93wCh7mu5W7BuhwI,5011
|
||||
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-312.pyc,,
|
||||
limits/storage/__pycache__/base.cpython-312.pyc,,
|
||||
limits/storage/__pycache__/memcached.cpython-312.pyc,,
|
||||
limits/storage/__pycache__/memory.cpython-312.pyc,,
|
||||
limits/storage/__pycache__/mongodb.cpython-312.pyc,,
|
||||
limits/storage/__pycache__/redis.cpython-312.pyc,,
|
||||
limits/storage/__pycache__/redis_cluster.cpython-312.pyc,,
|
||||
limits/storage/__pycache__/redis_sentinel.cpython-312.pyc,,
|
||||
limits/storage/__pycache__/registry.cpython-312.pyc,,
|
||||
limits/storage/base.py,sha256=QFVhOS8VdR7PDhaYMSc77SLg8yaGm0PCNNrMu4ZamfY,7264
|
||||
limits/storage/memcached.py,sha256=AzT3vz-MnkFxS0mF3C0QjGPzCnmUt29qTnuOKhKVKYI,10455
|
||||
limits/storage/memory.py,sha256=4W8hWIEzwQpoh1z0LcfwuP6DqeFoVuOEM2u8WpZkfdQ,8957
|
||||
limits/storage/mongodb.py,sha256=Cg_Vj33N7Ozxdmq7RGMCerg1XuVOhRAU7eusfhiSZBc,18170
|
||||
limits/storage/redis.py,sha256=zTwxV5qosxGBTrkZmD4UWQdvavDbWpYHXY7H3hXH-Sw,10791
|
||||
limits/storage/redis_cluster.py,sha256=GkL8GCQFfxDriMzsPMkaj6pMEX5FvQXYpUtXLY5q8fQ,4621
|
||||
limits/storage/redis_sentinel.py,sha256=OSb61DxgUxMgXSIjaM_pF5-entD8XntD56xt0rFu89k,4479
|
||||
limits/storage/registry.py,sha256=CxSaDBGR5aBJPFAIsfX9axCnbcThN3Bu-EH4wHrXtu8,650
|
||||
limits/strategies.py,sha256=Q03NTAyADtwMalhRkOSdk6UE1gVfVt5n258xVyA481o,10732
|
||||
limits/typing.py,sha256=pVt5D23MhQSUGqi0MBG5FCSqDwta2ygu18BpKvJFxow,3283
|
||||
limits/util.py,sha256=283O2aXnN7DmaqjTeTiF-KYn5wVbnpXJ8vb-6LvY5lY,5983
|
||||
@@ -0,0 +1,4 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: hatchling 1.27.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.
|
||||
|
||||
Reference in New Issue
Block a user