Initial commit: fastapi-traffic rate limiting library
- Core rate limiting with multiple algorithms (sliding window, token bucket, etc.) - SQLite and memory backends - Decorator and dependency injection patterns - Middleware support - Example usage files
This commit is contained in:
108
examples/03_backends.py
Normal file
108
examples/03_backends.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""Examples demonstrating different storage backends."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from fastapi_traffic import (
|
||||
MemoryBackend,
|
||||
RateLimitExceeded,
|
||||
RateLimiter,
|
||||
SQLiteBackend,
|
||||
rate_limit,
|
||||
)
|
||||
from fastapi_traffic.core.limiter import set_limiter
|
||||
|
||||
|
||||
# Choose backend based on environment
|
||||
def get_backend():
|
||||
"""Select appropriate backend based on environment."""
|
||||
backend_type = os.getenv("RATE_LIMIT_BACKEND", "memory")
|
||||
|
||||
if backend_type == "sqlite":
|
||||
# SQLite - Good for single-instance apps, persists across restarts
|
||||
return SQLiteBackend("rate_limits.db")
|
||||
|
||||
elif backend_type == "redis":
|
||||
# Redis - Required for distributed/multi-instance deployments
|
||||
# Requires: pip install redis
|
||||
try:
|
||||
from fastapi_traffic import RedisBackend
|
||||
import asyncio
|
||||
|
||||
async def create_redis():
|
||||
return await RedisBackend.from_url(
|
||||
os.getenv("REDIS_URL", "redis://localhost:6379/0"),
|
||||
key_prefix="myapp_ratelimit",
|
||||
)
|
||||
|
||||
return asyncio.get_event_loop().run_until_complete(create_redis())
|
||||
except ImportError:
|
||||
print("Redis not installed, falling back to memory backend")
|
||||
return MemoryBackend()
|
||||
|
||||
else:
|
||||
# Memory - Fast, but resets on restart, not shared across instances
|
||||
return MemoryBackend()
|
||||
|
||||
|
||||
backend = get_backend()
|
||||
limiter = RateLimiter(backend)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
await limiter.initialize()
|
||||
set_limiter(limiter)
|
||||
yield
|
||||
await limiter.close()
|
||||
|
||||
|
||||
app = FastAPI(title="Storage Backends Example", lifespan=lifespan)
|
||||
|
||||
|
||||
@app.exception_handler(RateLimitExceeded)
|
||||
async def rate_limit_handler(request: Request, exc: RateLimitExceeded) -> JSONResponse:
|
||||
return JSONResponse(
|
||||
status_code=429,
|
||||
content={"error": "rate_limit_exceeded", "retry_after": exc.retry_after},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/resource")
|
||||
@rate_limit(100, 60)
|
||||
async def get_resource(request: Request) -> dict[str, str]:
|
||||
return {"message": "Resource data", "backend": type(backend).__name__}
|
||||
|
||||
|
||||
@app.get("/backend-info")
|
||||
async def backend_info() -> dict[str, Any]:
|
||||
"""Get information about the current backend."""
|
||||
info = {
|
||||
"backend_type": type(backend).__name__,
|
||||
"description": "",
|
||||
}
|
||||
|
||||
if isinstance(backend, MemoryBackend):
|
||||
info["description"] = "In-memory storage, fast but ephemeral"
|
||||
elif isinstance(backend, SQLiteBackend):
|
||||
info["description"] = "SQLite storage, persistent, single-instance"
|
||||
else:
|
||||
info["description"] = "Redis storage, distributed, multi-instance"
|
||||
|
||||
return info
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
# Run with different backends:
|
||||
# RATE_LIMIT_BACKEND=memory python 03_backends.py
|
||||
# RATE_LIMIT_BACKEND=sqlite python 03_backends.py
|
||||
# RATE_LIMIT_BACKEND=redis REDIS_URL=redis://localhost:6379/0 python 03_backends.py
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
Reference in New Issue
Block a user