mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-18 07:19:35 +00:00
84 lines
2.4 KiB
Python
84 lines
2.4 KiB
Python
"""
|
|
Rate limiting via slowapi.
|
|
تحديد المعدل عبر slowapi.
|
|
|
|
Default policy (per route):
|
|
POST /api/v1/leads → 10/min
|
|
POST /api/v1/sales/* → 30/min
|
|
POST /api/v1/webhooks/wa → 100/min
|
|
Other API routes → 60/min
|
|
Global (per IP, all paths) → 1000/min
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from typing import Any
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.responses import JSONResponse
|
|
|
|
try:
|
|
from slowapi import Limiter, _rate_limit_exceeded_handler
|
|
from slowapi.errors import RateLimitExceeded
|
|
from slowapi.middleware import SlowAPIMiddleware
|
|
from slowapi.util import get_remote_address
|
|
|
|
_HAS_SLOWAPI = True
|
|
except ImportError: # pragma: no cover
|
|
_HAS_SLOWAPI = False
|
|
Limiter = None # type: ignore
|
|
RateLimitExceeded = Exception # type: ignore
|
|
|
|
|
|
def _key_func(request: Request) -> str:
|
|
"""Prefer API key (authenticated callers) over IP."""
|
|
key = request.headers.get("X-API-Key")
|
|
if key:
|
|
return f"api:{key[:16]}"
|
|
if _HAS_SLOWAPI:
|
|
return get_remote_address(request)
|
|
return request.client.host if request.client else "anon"
|
|
|
|
|
|
DEFAULT_GLOBAL_LIMIT = os.getenv("RL_GLOBAL", "1000/minute")
|
|
|
|
limiter: Any = None
|
|
if _HAS_SLOWAPI:
|
|
limiter = Limiter(
|
|
key_func=_key_func,
|
|
default_limits=[DEFAULT_GLOBAL_LIMIT],
|
|
storage_uri=os.getenv("RL_STORAGE_URI", "memory://"),
|
|
strategy="fixed-window",
|
|
)
|
|
|
|
|
|
# Per-route limits (applied via decorators in routers)
|
|
LIMITS = {
|
|
"leads_create": os.getenv("RL_LEADS", "10/minute"),
|
|
"sales_any": os.getenv("RL_SALES", "30/minute"),
|
|
"whatsapp_webhook": os.getenv("RL_WA_WEBHOOK", "100/minute"),
|
|
"generic_api": os.getenv("RL_GENERIC", "60/minute"),
|
|
}
|
|
|
|
|
|
def setup_rate_limit(app: FastAPI) -> None:
|
|
"""Wire slowapi into the FastAPI app. No-op if slowapi is missing."""
|
|
if not _HAS_SLOWAPI or limiter is None:
|
|
return
|
|
|
|
app.state.limiter = limiter
|
|
|
|
async def _rate_limit_handler(request: Request, exc: RateLimitExceeded) -> JSONResponse:
|
|
return JSONResponse(
|
|
status_code=429,
|
|
content={
|
|
"error": "RateLimitExceeded",
|
|
"detail": f"Too many requests: {exc.detail}",
|
|
"ar": "تجاوزت الحد المسموح، يرجى المحاولة لاحقاً.",
|
|
},
|
|
)
|
|
|
|
app.add_exception_handler(RateLimitExceeded, _rate_limit_handler)
|
|
app.add_middleware(SlowAPIMiddleware)
|