Time-based rate limiting. Smooth, predictable throughput control.
import asyncio
import time
class TokenBucket:
def __init__(self, rate: float, capacity: int):
"""
rate: tokens per second (e.g., 10)
capacity: max tokens in bucket (e.g., 10 for 1-second burst)
"""
self.rate = rate
self.capacity = capacity
self.tokens = capacity
self.last_update = time.monotonic()
async def acquire(self, count: int = 1) -> None:
"""Wait until `count` tokens are available, then consume."""
while True:
self._refill()
if self.tokens >= count:
self.tokens -= count
return
# Wait before trying again
wait_time = (count - self.tokens) / self.rate
await asyncio.sleep(wait_time)
def _refill(self) -> None:
"""Add tokens based on elapsed time."""
now = time.monotonic()
elapsed = now - self.last_update
self.tokens = min(
self.capacity,
self.tokens + elapsed * self.rate
)
self.last_update = now
async def main():
bucket = TokenBucket(rate=10, capacity=10) # 10 req/sec
async def make_request(i):
await bucket.acquire(1)
print(f"Request {i} proceeding at {time.time():.2f}")
# Send 20 requests
tasks = [make_request(i) for i in range(20)]
await asyncio.gather(*tasks)
asyncio.run(main())
from fastapi import FastAPI, HTTPException
import time
app = FastAPI()
bucket = TokenBucket(rate=100, capacity=100) # 100 req/sec limit
@app.get("/api/data")
async def get_data():
try:
# Non-blocking check: fail fast if no tokens
bucket._refill()
if bucket.tokens >= 1:
bucket.tokens -= 1
return {"data": "here"}
else:
# Return 429 Too Many Requests
raise HTTPException(status_code=429, detail="Rate limit exceeded")
except Exception as e:
raise HTTPException(status_code=429)
# OR: blocking approach (wait for tokens)
@app.get("/api/data-queued")
async def get_data_queued():
await bucket.acquire(1)
return {"data": "here"}
| Pattern | Controls | Use Case |
|---|---|---|
| Semaphore | Concurrency (N in flight) | Connection pooling, cap parallel work |
| Token Bucket | Rate (N per second) | API limits, bandwidth, smooth flow |
| Queue | Job buffering | Backpressure, worker pools |