Token bucket and sliding window rate limiters built with clean, testable primitives. Flowguard shows how to design thread-safe throttling with pluggable time sources, a decorator middleware layer, and a deterministic simulation harness.
- Demonstrates systems thinking with two canonical algorithms implemented from scratch (no dependencies).
- Thread-safe design and deterministic time abstraction for reproducible tests.
- Professional project hygiene: typed API, CI workflow, unit tests, and CLI demo.
- Clean architecture: core strategies, middleware decorator, utility time providers, and a separate simulation surface.
flowguard/limits/: reusable limiter primitives (TokenBucket,SlidingWindow,Decision).flowguard/utils/time_provider.py: injectable clocks (MonotonicTime,ManualTime) for determinism.flowguard/middleware/decorators.py:rate_limiteddecorator for protecting callables.cli/simulate.py: deterministic simulator comparing strategies on the same traffic pattern.tests/: Pytest coverage for refill math, window pruning, and wait-time reporting.
# From repo root
python3 -m venv .venv && source .venv/bin/activate
python3 -m pip install --upgrade pip
python3 -m pip install -e '.[dev]' # or: python3 -m pip install pytest
# Run the deterministic simulator
PYTHONPATH=. python3 cli/simulate.py
# Run tests
PYTHONPATH=. python3 -m pytest t(s) req token bucket tokens next sliding window next
-----------------------------------------------------------------------------
0.40 1 ALLOW 9.00 0.00 ALLOW 0.00
0.43 2 ALLOW 8.15 0.00 ALLOW 0.00
0.46 3 ALLOW 7.30 0.00 ALLOW 0.00
0.86 4 ALLOW 8.30 0.00 ALLOW 0.00
0.94 5 ALLOW 7.70 0.00 ALLOW 0.00
0.99 6 ALLOW 6.95 0.00 ALLOW 0.00
1.04 7 ALLOW 6.20 0.00 ALLOW 0.00
1.09 8 ALLOW 5.45 0.00 ALLOW 0.00
1.09 9 ALLOW 4.45 0.00 ALLOW 0.00
1.12 10 ALLOW 3.60 0.00 ALLOW 0.00
1.52 11 ALLOW 4.60 0.00 ALLOW 0.00
1.92 12 ALLOW 5.60 0.00 ALLOW 0.00
2.17 13 ALLOW 5.85 0.00 BLOCK 0.73
2.20 14 ALLOW 5.00 0.00 BLOCK 0.70
2.45 15 ALLOW 5.25 0.00 BLOCK 0.45
2.57 16 ALLOW 4.85 0.00 BLOCK 0.33
2.60 17 ALLOW 4.00 0.00 BLOCK 0.30
2.60 18 ALLOW 3.00 0.00 BLOCK 0.30
2.63 19 ALLOW 2.15 0.00 BLOCK 0.27
2.68 20 ALLOW 1.40 0.00 BLOCK 0.22
2.73 21 ALLOW 0.65 0.00 BLOCK 0.17
2.98 22 ALLOW 0.90 0.00 ALLOW 0.00
3.23 23 ALLOW 1.15 0.00 ALLOW 0.00
3.26 24 ALLOW 0.30 0.00 ALLOW 0.00
3.51 25 ALLOW 0.55 0.00 ALLOW 0.00
3.56 26 BLOCK 0.80 0.04 ALLOW 0.00
3.56 27 BLOCK 0.80 0.04 ALLOW 0.00
3.96 28 ALLOW 1.80 0.00 ALLOW 0.00
4.36 29 ALLOW 2.80 0.00 ALLOW 0.00
4.61 30 ALLOW 3.05 0.00 ALLOW 0.00
Summary
-------
Token bucket: 28/30 allowed
Sliding window: 21/30 allowed
from flowguard import TokenBucket, rate_limited
# Allow bursts of 10 requests with steady 5 rps refill
limiter = TokenBucket(capacity=10, refill_rate=5)
@rate_limited(limiter)
def handle_request(payload):
return f"processed {payload}"
for i in range(15):
try:
print(handle_request(i))
except Exception as exc:
print(f"dropped {i}: {exc}")- Deterministic time: all limiters consume a
TimeProvider, making simulations and tests reproducible without real waits. - Thread safety: critical sections guarded with locks for correctness under concurrency.
- Extensible decisions: the
Decisionobject surfaces tokens remaining and time until next availability for better UX/logging. - Middleware layer: decorator lets you protect any callable; swap strategies without touching business logic.
Built by Mo Shirmohammadi (USC CS) — github.com/mohosy