Initial commit
This commit is contained in:
82
skills/api-design-standards/templates/rate-limiter.py
Normal file
82
skills/api-design-standards/templates/rate-limiter.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# Grey Haven Studio - Rate Limiter Template
|
||||
# Add this to app/core/rate_limit.py
|
||||
|
||||
from fastapi import Request, HTTPException, status
|
||||
from upstash_redis import Redis
|
||||
import os
|
||||
|
||||
# Doppler provides REDIS_URL
|
||||
redis = Redis.from_url(os.getenv("REDIS_URL"))
|
||||
|
||||
|
||||
class RateLimiter:
|
||||
"""
|
||||
Rate limiter using Upstash Redis.
|
||||
|
||||
Usage:
|
||||
rate_limit_strict = RateLimiter(max_requests=10, window=60)
|
||||
rate_limit_normal = RateLimiter(max_requests=100, window=60)
|
||||
|
||||
@router.post("", dependencies=[Depends(rate_limit_strict)])
|
||||
async def create_resource():
|
||||
pass
|
||||
"""
|
||||
|
||||
def __init__(self, max_requests: int = 100, window: int = 60):
|
||||
"""
|
||||
Initialize rate limiter.
|
||||
|
||||
Args:
|
||||
max_requests: Maximum requests allowed in window
|
||||
window: Time window in seconds
|
||||
"""
|
||||
self.max_requests = max_requests
|
||||
self.window = window
|
||||
|
||||
async def __call__(self, request: Request):
|
||||
"""Check rate limit for current request."""
|
||||
# Get client identifier (IP address or user ID from JWT)
|
||||
client_id = self._get_client_id(request)
|
||||
|
||||
# Rate limit key
|
||||
key = f"rate_limit:{client_id}:{request.url.path}"
|
||||
|
||||
# Increment counter
|
||||
count = redis.incr(key)
|
||||
|
||||
# Set expiration on first request
|
||||
if count == 1:
|
||||
redis.expire(key, self.window)
|
||||
|
||||
# Check if limit exceeded
|
||||
if count > self.max_requests:
|
||||
retry_after = redis.ttl(key)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||
detail=f"Rate limit exceeded. Try again in {retry_after} seconds.",
|
||||
headers={"Retry-After": str(retry_after)},
|
||||
)
|
||||
|
||||
def _get_client_id(self, request: Request) -> str:
|
||||
"""Get client identifier (IP or user ID)."""
|
||||
# Prefer user ID from JWT if available
|
||||
if hasattr(request.state, "user") and request.state.user:
|
||||
return f"user:{request.state.user.id}"
|
||||
|
||||
# Fallback to IP address
|
||||
return f"ip:{request.client.host}"
|
||||
|
||||
|
||||
# Rate limit configurations
|
||||
rate_limit_strict = RateLimiter(max_requests=10, window=60) # 10 req/min
|
||||
rate_limit_normal = RateLimiter(max_requests=100, window=60) # 100 req/min
|
||||
rate_limit_relaxed = RateLimiter(max_requests=1000, window=60) # 1000 req/min
|
||||
|
||||
|
||||
# Apply to routes:
|
||||
# from app.core.rate_limit import rate_limit_strict, rate_limit_normal
|
||||
#
|
||||
# @router.post("", dependencies=[Depends(rate_limit_strict)])
|
||||
# async def create_user():
|
||||
# """Create user (10 req/min limit)."""
|
||||
# pass
|
||||
Reference in New Issue
Block a user