Decorator-based rate limiter for Python — sliding window algorithm, burst allowance, per-key limits, multiple windows, and full async support. Zero dependencies.
pip install ratelimiterNo dependencies. Requires Python 3.8+.
from ratelimiter import rate_limit, RateLimitExceeded
@rate_limit(10, "second")
def call_api():
return requests.get("https://api.example.com/data")from ratelimiter import RateLimiter, rate_limit
# As a reusable limiter object
limiter = RateLimiter(limit=10, window="second")
@limiter
def fetch(): ...
# As a one-off decorator
@rate_limit(100, "minute")
def send_email(): ...| Window | Value |
|---|---|
"second" |
1 second |
"minute" |
60 seconds |
"hour" |
3600 seconds |
"day" |
86400 seconds |
Allow a temporary burst above the base limit:
@rate_limit(10, "second", burst=5)
def fn(): ...
# Allows up to 15 calls/second in bursts, then enforces 10/secEnforce multiple limits simultaneously:
limiter = RateLimiter(
limit=10, window="second",
additional_windows=[
{"limit": 100, "window": "minute"},
{"limit": 1000, "window": "day"},
]
)
@limiter
def api_call(): ...limiter = RateLimiter(
limit=10, window="minute",
key_func=lambda args, kwargs: kwargs.get("user_id")
)
@limiter
def create_post(user_id, content): ...
create_post(user_id="alice", content="hello") # alice's limit
create_post(user_id="bob", content="hi") # bob's own limitWorks transparently with async def:
@rate_limit(50, "second")
async def async_fetch(url):
async with aiohttp.ClientSession() as s:
return await s.get(url)limiter = RateLimiter(5, "second", raise_on_limit=False)
@limiter
def fn(): ...
# Automatically waits for a slot instead of raisingfrom ratelimiter import RateLimitExceeded
try:
call_api()
except RateLimitExceeded as e:
print(f"Limited! Retry after {e.retry_after:.2f}s")
time.sleep(e.retry_after)# Check status without consuming a slot
status = limiter.check()
# {"allowed": True, "remaining": 8, "retry_after": 0}
# Usage stats
stats = limiter.stats()
# {"total_calls": 42, "rejected_calls": 3, "current_count": 7, ...}
# Reset
limiter.reset()Test any rate limit configuration interactively:
ratelimiter-test --limit 5 --window second
ratelimiter-test --limit 10 --window minute --burst 3 --calls 15Output:
Call 1/15 ✅ ALLOWED remaining=9 [█████████░]
Call 2/15 ✅ ALLOWED remaining=8 [████████░░]
...
Call 11/15 ❌ BLOCKED (retry in 0.823s)
RateLimiter(
limit, # Max calls per window
window="second", # "second", "minute", "hour", "day"
burst=0, # Extra burst calls above limit
key_func=None, # Callable(args, kwargs) -> str for per-key limiting
raise_on_limit=True, # False = wait instead of raise
additional_windows=[], # [{"limit": N, "window": "..."}]
)| Method | Description |
|---|---|
__call__(func) |
Use as decorator |
check(key) |
Check status without consuming |
reset(key) |
Reset counter |
stats(key) |
Get usage statistics |
Functional decorator factory — same parameters as RateLimiter.
pip install pytest
pytest tests/ -vMIT © prabhay759