Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/hesabixAPI/app/services/tax_rate_limiter.py
2026-04-14 19:34:55 +03:30

152 lines
4.7 KiB
Python
Executable file

"""
Rate Limiting برای ارسال فاکتورها به سامانه مالیاتی
"""
from __future__ import annotations
import time
import logging
from typing import Dict, Any, Optional
from datetime import datetime, timedelta
from app.core.cache import get_cache
from app.core.responses import ApiError
logger = logging.getLogger(__name__)
class TaxRateLimiter:
"""Rate limiter برای عملیات مالیاتی"""
def __init__(self):
self.cache = get_cache()
self.enabled = self.cache.enabled
def check_rate_limit(
self,
business_id: int,
operation: str = "send_invoice",
max_requests: int = 100,
window_seconds: int = 3600, # 1 ساعت
) -> tuple[bool, Optional[Dict[str, Any]]]:
"""
بررسی rate limit
Args:
business_id: شناسه کسب‌وکار
operation: نوع عملیات
max_requests: حداکثر تعداد درخواست
window_seconds: بازه زمانی (ثانیه)
Returns:
(allowed: bool, info: Optional[Dict]) - info شامل اطلاعات rate limit
"""
if not self.enabled:
return True, None
cache_key = f"tax_rate_limit:{business_id}:{operation}"
now = time.time()
# دریافت اطلاعات فعلی
rate_data = self.cache.get(cache_key)
if not rate_data or not isinstance(rate_data, dict):
# اولین درخواست
rate_data = {
"count": 1,
"window_start": now,
"reset_at": now + window_seconds,
}
self.cache.set(cache_key, rate_data, ttl=window_seconds)
return True, {
"remaining": max_requests - 1,
"reset_at": datetime.fromtimestamp(rate_data["reset_at"]).isoformat(),
}
window_start = rate_data.get("window_start", now)
count = rate_data.get("count", 0)
# بررسی اینکه آیا window منقضی شده
if now >= rate_data.get("reset_at", now + window_seconds):
# شروع window جدید
rate_data = {
"count": 1,
"window_start": now,
"reset_at": now + window_seconds,
}
self.cache.set(cache_key, rate_data, ttl=window_seconds)
return True, {
"remaining": max_requests - 1,
"reset_at": datetime.fromtimestamp(rate_data["reset_at"]).isoformat(),
}
# بررسی limit
if count >= max_requests:
reset_at = datetime.fromtimestamp(rate_data["reset_at"]).isoformat()
return False, {
"limit_exceeded": True,
"current_count": count,
"max_requests": max_requests,
"reset_at": reset_at,
}
# افزایش تعداد
rate_data["count"] = count + 1
remaining = max_requests - rate_data["count"]
self.cache.set(cache_key, rate_data, ttl=int(rate_data["reset_at"] - now))
return True, {
"remaining": remaining,
"current_count": rate_data["count"],
"reset_at": datetime.fromtimestamp(rate_data["reset_at"]).isoformat(),
}
def get_rate_limit_info(
self,
business_id: int,
operation: str = "send_invoice",
) -> Dict[str, Any]:
"""دریافت اطلاعات rate limit"""
if not self.enabled:
return {"enabled": False}
cache_key = f"tax_rate_limit:{business_id}:{operation}"
rate_data = self.cache.get(cache_key)
if not rate_data:
return {
"enabled": True,
"current_count": 0,
"remaining": 100, # default
}
now = time.time()
if now >= rate_data.get("reset_at", now + 3600):
return {
"enabled": True,
"current_count": 0,
"remaining": 100,
}
return {
"enabled": True,
"current_count": rate_data.get("count", 0),
"remaining": 100 - rate_data.get("count", 0), # default max
"reset_at": datetime.fromtimestamp(rate_data["reset_at"]).isoformat(),
}
# Global instance
_rate_limiter: Optional[TaxRateLimiter] = None
def get_tax_rate_limiter() -> TaxRateLimiter:
"""دریافت instance از TaxRateLimiter"""
global _rate_limiter
if _rate_limiter is None:
_rate_limiter = TaxRateLimiter()
return _rate_limiter