| """ |
| Rate Limiting Middleware |
| ======================== |
| |
| Redis-backed rate limiting for API endpoints. |
| Supports: |
| - Per-endpoint rate limits |
| - Tier-based limits (FREE, BASIC, PREMIUM, ENTERPRISE) |
| - Burst protection |
| - Automatic key expiration |
| """ |
|
|
| import logging |
|
|
| import redis |
| from fastapi import Request |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| |
|
|
| RATE_LIMITS = { |
| "FREE": {"rph": 10, "rpd": 100, "burst": 2}, |
| "BASIC": {"rph": 50, "rpd": 500, "burst": 5}, |
| "PREMIUM": {"rph": 200, "rpd": 2000, "burst": 10}, |
| "ENTERPRISE": {"rph": 1000, "rpd": 10000, "burst": 50}, |
| } |
|
|
| |
| DEFAULT_LIMITS = {"rph": 10, "rpd": 100, "burst": 2} |
|
|
|
|
| class RateLimiter: |
| """Rate limiter with Redis backend.""" |
|
|
| def __init__(self, redis_client: redis.Redis | None = None): |
| self.redis = redis_client |
| self.enabled = redis_client is not None |
|
|
| def check(self, key: str, limit: int, window: int = 3600) -> tuple[bool, int]: |
| """ |
| Check if request is within rate limit. |
| |
| Args: |
| key: Unique identifier for rate limit bucket |
| limit: Maximum requests allowed |
| window: Time window in seconds (default: 1 hour) |
| |
| Returns: |
| Tuple of (allowed, remaining_count) |
| """ |
| if not self.enabled: |
| return True, limit |
|
|
| try: |
| |
| pipe = self.redis.pipeline() |
| pipe.incr(key) |
| pipe.expire(key, window) |
| results = pipe.execute() |
|
|
| current_count = results[0] |
| remaining = max(0, limit - current_count) |
|
|
| if current_count > limit: |
| logger.warning(f"Rate limit exceeded for key: {key}") |
| return False, 0 |
|
|
| return True, remaining |
|
|
| except redis.RedisError as e: |
| logger.error(f"Redis error in rate limiter: {e}") |
| return True, limit |
|
|
| def get_tier_limits(self, tier: str) -> dict[str, int]: |
| """Get rate limit configuration for a tier.""" |
| return RATE_LIMITS.get(tier.upper(), DEFAULT_LIMITS) |
|
|
| def check_tier(self, request: Request, tier: str, endpoint: str = "default") -> bool: |
| """ |
| Check rate limit for a specific tier and endpoint. |
| |
| Args: |
| request: FastAPI request object |
| tier: User's authentication tier |
| endpoint: API endpoint name |
| |
| Returns: |
| True if allowed, False if rate limited |
| """ |
| if not self.enabled: |
| return True |
|
|