taplimit
Small, typed, thread-safe in-process rate-limit decorator: N calls per period, block or raise.
Zero dependencies, one obvious decorator, and a reusable limiter object when you need to share a budget. The clock and sleep are injectable, so tests never wait on real time.
Install
pip install taplimit
Usage
Cap a function at N calls per period. By default a call over the limit blocks until a slot frees:
from taplimit import rate_limit
@rate_limit(calls=15, period=60)
def call_api() -> None:
...
Prefer to fail fast instead of waiting? Ask it to raise:
from taplimit import rate_limit, RateLimitExceeded
@rate_limit(calls=15, period=60, on_limit="raise")
def call_api() -> None:
...
try:
call_api()
except RateLimitExceeded as exc:
print(f"slow down, retry in {exc.retry_after:.1f}s")
Share one budget across several functions, or control slots by hand, with the limiter
object. It is a decorator, a context manager, and exposes acquire / try_acquire:
from taplimit import RateLimiter
limiter = RateLimiter(calls=100, period=60)
@limiter
def read() -> None: ...
@limiter
def write() -> None: ... # read and write draw on the same 100-per-minute budget
with limiter: # guard a block of code
do_work()
if limiter.try_acquire(): # never blocks; returns False when full
do_work()
Testing without real time
clock (returns seconds) and sleep (waits seconds) are injectable, so a limiter runs
instantly under a fake clock:
now = [0.0]
slept: list[float] = []
limiter = RateLimiter(
calls=1, period=10,
clock=lambda: now[0],
sleep=lambda s: (slept.append(s), now.__setitem__(0, now[0] + s)),
)
How it limits
taplimit uses a sliding window: it keeps the timestamps of recent calls and a slot
frees exactly period seconds after the call that took it. A burst never carries over a
fixed boundary the way a reset-every-minute counter does, so the rate you ask for is the
rate you get. All state is guarded by a lock, so one decorator or limiter is safe to share
across threads.
Why
The simple niche of "let this function run at most N times per period, in one process, synchronously" is oddly unserved:
ratelimitpopularised the@limits(calls, period)shape and is still downloaded millions of times a month, but it has been abandoned since 2018 (last release 2.2.1). It also uses a fixed window and its threading story is thin.pyrate-limiterandlimitsare maintained and capable, but they are backend-oriented and heavy: buckets, optional SQLite / Redis stores, async machinery. Overkill when all you want is a decorator on one function.limiteris a token-bucket library with a differentrate/capacityAPI, not the plain calls-per-period shape.
taplimit is the small, modern, typed take: one decorator, a sliding window, thread-safe, no dependencies, and a clock you can fake in tests. Reach for it when a single process needs to stay under a rate and a full rate-limiting backend would be too much.
License
MIT. See LICENSE.
Links
- Website: https://lianes8studio.com
- GitHub: https://github.com/JaviLianes8
- LinkedIn: https://www.linkedin.com/in/jlianes/
Metadata
Release files for taplimit 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| taplimit-0.1.0.tar.gz | 8.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| taplimit-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 15.3 kB
Release files / taplimit-0.1.0.tar.gz
| Download URL | taplimit-0.1.0.tar.gz |
|---|---|
| Size | 8.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ad0d11630d2a3672413659ac8f8b2b210393fd5c99da1edc7027382e23adb889
|
|
BLAKE2b-256 checksum How to use checksums |
3e0a4868d72593a2b8a0705b333baa5cff171a91dbf09c3859ee1f142d9d81ab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.2
|
Release files / taplimit-0.1.0-py3-none-any.whl
| Download URL | taplimit-0.1.0-py3-none-any.whl |
|---|---|
| Size | 6.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3095a27d04c8c74c906f9eefe8a5c1afc0ccd0acd8498c6193a6f6b71ff6f343
|
|
BLAKE2b-256 checksum How to use checksums |
47c3202717fbe56f81893b992210344631bcd60dd70690c493b23cadfb206096
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.2
|