Skip to content

Rate Limiter

Pure Python rate limiter with pluggable strategies — because nothing says "I understand distributed systems" like reinventing the wheel with asyncio.Lock.

View Source on GitHub

Quick Start

from rate_limiter import RateLimiter, TokenBucketStrategy

limiter = RateLimiter(lambda: TokenBucketStrategy(capacity=10, rate=2))

# Per-key rate limiting
allowed = await limiter.allow("client-192.168.1.1")  # True

Architecture

The rate limiter uses the Strategy pattern: a single RateLimiter manages per-key buckets, each backed by a pluggable strategy that implements the IStrategy protocol.

graph TB
    subgraph core [Core]
        RL["RateLimiter"] -->|"per-key lookup"| Buckets["dict[str, IStrategy]"]
        RL -->|"factory()"| ISt["IStrategy Protocol"]
    end
    subgraph strategies [Strategies]
        ISt -.-> TB[Token Bucket]
        ISt -.-> FW[Fixed Window]
        ISt -.-> LB[Leaky Bucket]
        ISt -.-> SWL[Sliding Window Log]
        ISt -.-> SWC[Sliding Window Counter]
        ISt -.-> CL[Concurrency Limiter]
    end

The RateLimiter lazily creates one strategy instance per key on first access. The strategy factory is a callable you provide at construction time — swap algorithms without touching the rest of your code.

class RateLimiter:
    def __init__(self, strategy_factory: Callable[[], IStrategy]):
        self._strategy_factory = strategy_factory
        self._buckets: dict[str, IStrategy] = {}

    async def allow(self, key: str) -> bool:
        if key not in self._buckets:
            self._buckets[key] = self._strategy_factory()
        return await self._buckets[key].allow()

Strategy Comparison

Strategy Time Memory Accuracy Bursts Boundary Issue
Token Bucket O(1) O(1) Exact Allowed (up to capacity) None
Fixed Window O(1) O(1) Exact within window Allowed at boundary 2x burst at edges
Leaky Bucket O(1) O(1) Exact Rejected None
Sliding Window Log O(n) O(n) Perfect Smooth None
Sliding Window Counter O(1) O(1) Approximate Smooth Negligible
Concurrency Limiter O(1) O(1) Exact N/A (concurrent, not rate) N/A

Constraints

  • Dependencies: None — pure Python 3.12+ and asyncio
  • Testing: pytest + pytest-asyncio
  • Code Quality: Type-annotated, protocol-driven