RateLimitMiddleware

Global before-middleware that enforces per-user request rate limits using the FSM storage's atomic increment() counter. In v0.5.0 the implementation was completely rewritten — a single storage call replaces the previous broken CAS loop.

Basic Setup

from kurigram_addons import RateLimitMiddleware, KurigramClient, MemoryStorage

app = KurigramClient("bot", bot_token="TOKEN", storage=MemoryStorage())

@app.on_startup
async def setup_middleware(client):
    rate_limiter = RateLimitMiddleware(
        limit=5,       # max 5 requests
        period=60,     # per 60-second window
    )
    await client.include_middleware(rate_limiter, kind="before", priority=100)

Custom Response When Limited

async def on_limited(update, client):
    if hasattr(update, "reply"):
        await update.reply("⏳ Too many requests — please wait a moment.")

rate_limiter = RateLimitMiddleware(
    limit=5,
    period=60,
    on_limited=on_limited,
)

Custom Key Function

By default requests are bucketed per user ID. Supply key_functo bucket by chat, IP, or any other dimension.

def key_by_chat(update) -> str:
    chat = getattr(update, "chat", None)
    return f"chat:{chat.id}" if chat else "unknown"

rate_limiter = RateLimitMiddleware(
    limit=20, period=60,
    key_func=key_by_chat,
)

Parameters

ParameterTypeDefaultDescription
limitintMax requests per window (required)
periodfloatWindow duration in seconds (required)
on_limitedCallable | NoneNoneAsync callback when limit exceeded; receives (update, client)
key_funcCallable | Noneuser IDExtract rate-limit bucket key from update

How It Works

Each request calls storage.increment(key, amount=1, ttl=period), which atomically increments a counter and sets a TTL if the key is new. If the returned count exceeds limit, on_limitedis called and the handler is skipped. Counters are stored under a dedicated__rl__: key namespace to prevent collisions with real FSM state.