Implement user-specific question numbering and update database schema. Added triggers for automatic question numbering and adjustments upon deletion. Enhanced CRUD operations to manage user_question_number effectively.
This commit is contained in:
13
services/rate_limiting/__init__.py
Normal file
13
services/rate_limiting/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
Rate limiting сервисы
|
||||
"""
|
||||
|
||||
from .rate_limit_config import RateLimitSettings, get_rate_limit_config, get_adaptive_config
|
||||
from .rate_limiter import RateLimitConfig, send_with_rate_limit, telegram_rate_limiter
|
||||
from .rate_limit_service import RateLimitService
|
||||
|
||||
__all__ = [
|
||||
'RateLimitSettings', 'get_rate_limit_config', 'get_adaptive_config',
|
||||
'RateLimitConfig', 'send_with_rate_limit', 'telegram_rate_limiter',
|
||||
'RateLimitService'
|
||||
]
|
||||
150
services/rate_limiting/rate_limit_config.py
Normal file
150
services/rate_limiting/rate_limit_config.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
Конфигурация для rate limiting в AnonBot
|
||||
"""
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Загружаем переменные окружения
|
||||
load_dotenv()
|
||||
|
||||
|
||||
@dataclass
|
||||
class RateLimitSettings:
|
||||
"""Настройки rate limiting для разных типов сообщений"""
|
||||
|
||||
# Основные настройки
|
||||
messages_per_second: float = float(os.getenv('RATE_LIMIT_MESSAGES_PER_SECOND', '0.5')) # Максимум 0.5 сообщений в секунду на чат
|
||||
burst_limit: int = int(os.getenv('RATE_LIMIT_BURST_LIMIT', '2')) # Максимум 2 сообщения подряд
|
||||
retry_after_multiplier: float = float(os.getenv('RATE_LIMIT_RETRY_MULTIPLIER', '1.5')) # Множитель для увеличения задержки при retry
|
||||
max_retry_delay: float = float(os.getenv('RATE_LIMIT_MAX_RETRY_DELAY', '30.0')) # Максимальная задержка между попытками
|
||||
max_retries: int = int(os.getenv('RATE_LIMIT_MAX_RETRIES', '3')) # Максимальное количество повторных попыток
|
||||
|
||||
# Специальные настройки для разных типов сообщений
|
||||
voice_message_delay: float = float(os.getenv('RATE_LIMIT_VOICE_DELAY', '2.0')) # Дополнительная задержка для голосовых сообщений
|
||||
media_message_delay: float = float(os.getenv('RATE_LIMIT_MEDIA_DELAY', '1.5')) # Дополнительная задержка для медиа сообщений
|
||||
text_message_delay: float = float(os.getenv('RATE_LIMIT_TEXT_DELAY', '1.0')) # Дополнительная задержка для текстовых сообщений
|
||||
|
||||
# Настройки для разных типов чатов
|
||||
private_chat_multiplier: float = float(os.getenv('RATE_LIMIT_PRIVATE_MULTIPLIER', '1.0')) # Множитель для приватных чатов
|
||||
group_chat_multiplier: float = float(os.getenv('RATE_LIMIT_GROUP_MULTIPLIER', '0.8')) # Множитель для групповых чатов
|
||||
channel_multiplier: float = float(os.getenv('RATE_LIMIT_CHANNEL_MULTIPLIER', '0.6')) # Множитель для каналов
|
||||
|
||||
# Глобальные ограничения
|
||||
global_messages_per_second: float = float(os.getenv('RATE_LIMIT_GLOBAL_MESSAGES_PER_SECOND', '10.0')) # Максимум 10 сообщений в секунду глобально
|
||||
global_burst_limit: int = int(os.getenv('RATE_LIMIT_GLOBAL_BURST_LIMIT', '20')) # Максимум 20 сообщений подряд глобально
|
||||
|
||||
|
||||
# Конфигурации для разных сценариев использования
|
||||
# Основаны на официальных лимитах Telegram Bot API:
|
||||
# - 1 сообщение в секунду в личных чатах
|
||||
# - 20 сообщений в минуту в групповых чатах (0.33 в секунду)
|
||||
# - 30 запросов в секунду глобально
|
||||
|
||||
DEVELOPMENT_CONFIG = RateLimitSettings(
|
||||
messages_per_second=0.8, # Более мягкие ограничения для разработки (80% от лимита)
|
||||
burst_limit=3, # До 3 сообщений подряд
|
||||
retry_after_multiplier=1.2,
|
||||
max_retry_delay=15.0,
|
||||
max_retries=2,
|
||||
voice_message_delay=1.5,
|
||||
media_message_delay=1.2,
|
||||
text_message_delay=1.0
|
||||
)
|
||||
|
||||
PRODUCTION_CONFIG = RateLimitSettings(
|
||||
messages_per_second=0.5, # Консервативные ограничения (50% от лимита)
|
||||
burst_limit=2, # До 2 сообщений подряд
|
||||
retry_after_multiplier=1.5,
|
||||
max_retry_delay=30.0,
|
||||
max_retries=3,
|
||||
voice_message_delay=2.5, # Дополнительная задержка для голосовых
|
||||
media_message_delay=2.0, # Дополнительная задержка для медиа
|
||||
text_message_delay=1.5, # Дополнительная задержка для текста
|
||||
global_messages_per_second=20.0, # 20 из 30 доступных запросов в секунду
|
||||
global_burst_limit=15 # До 15 сообщений подряд глобально
|
||||
)
|
||||
|
||||
STRICT_CONFIG = RateLimitSettings(
|
||||
messages_per_second=0.3, # Очень консервативные ограничения (30% от лимита)
|
||||
burst_limit=1, # Только 1 сообщение подряд
|
||||
retry_after_multiplier=2.0,
|
||||
max_retry_delay=60.0,
|
||||
max_retries=5,
|
||||
voice_message_delay=3.0,
|
||||
media_message_delay=2.5,
|
||||
text_message_delay=2.0,
|
||||
global_messages_per_second=10.0, # 10 из 30 доступных запросов в секунду
|
||||
global_burst_limit=8 # До 8 сообщений подряд глобально
|
||||
)
|
||||
|
||||
|
||||
def get_rate_limit_config(environment: str = None) -> RateLimitSettings:
|
||||
"""
|
||||
Получает конфигурацию rate limiting в зависимости от окружения
|
||||
|
||||
Args:
|
||||
environment: Окружение ('development', 'production', 'strict')
|
||||
Если не указано, берется из переменной окружения RATE_LIMIT_ENV
|
||||
|
||||
Returns:
|
||||
RateLimitSettings: Конфигурация для указанного окружения
|
||||
"""
|
||||
if environment is None:
|
||||
environment = os.getenv('RATE_LIMIT_ENV', 'production')
|
||||
|
||||
configs = {
|
||||
"development": DEVELOPMENT_CONFIG,
|
||||
"production": PRODUCTION_CONFIG,
|
||||
"strict": STRICT_CONFIG
|
||||
}
|
||||
|
||||
return configs.get(environment, PRODUCTION_CONFIG)
|
||||
|
||||
|
||||
def get_adaptive_config(
|
||||
current_error_rate: float,
|
||||
base_config: Optional[RateLimitSettings] = None
|
||||
) -> RateLimitSettings:
|
||||
"""
|
||||
Получает адаптивную конфигурацию на основе текущего уровня ошибок
|
||||
|
||||
Args:
|
||||
current_error_rate: Текущий уровень ошибок (0.0 - 1.0)
|
||||
base_config: Базовая конфигурация
|
||||
|
||||
Returns:
|
||||
RateLimitSettings: Адаптированная конфигурация
|
||||
"""
|
||||
if base_config is None:
|
||||
base_config = PRODUCTION_CONFIG
|
||||
|
||||
# Если уровень ошибок высокий, ужесточаем ограничения
|
||||
if current_error_rate > 0.1: # Более 10% ошибок
|
||||
return RateLimitSettings(
|
||||
messages_per_second=base_config.messages_per_second * 0.5,
|
||||
burst_limit=max(1, base_config.burst_limit - 1),
|
||||
retry_after_multiplier=base_config.retry_after_multiplier * 1.5,
|
||||
max_retry_delay=base_config.max_retry_delay * 1.5,
|
||||
max_retries=base_config.max_retries + 1,
|
||||
voice_message_delay=base_config.voice_message_delay * 1.5,
|
||||
media_message_delay=base_config.media_message_delay * 1.3,
|
||||
text_message_delay=base_config.text_message_delay * 1.2
|
||||
)
|
||||
|
||||
# Если уровень ошибок низкий, можно немного ослабить ограничения
|
||||
elif current_error_rate < 0.01: # Менее 1% ошибок
|
||||
return RateLimitSettings(
|
||||
messages_per_second=base_config.messages_per_second * 1.2,
|
||||
burst_limit=base_config.burst_limit + 1,
|
||||
retry_after_multiplier=base_config.retry_after_multiplier * 0.9,
|
||||
max_retry_delay=base_config.max_retry_delay * 0.8,
|
||||
max_retries=max(1, base_config.max_retries - 1),
|
||||
voice_message_delay=base_config.voice_message_delay * 0.8,
|
||||
media_message_delay=base_config.media_message_delay * 0.9,
|
||||
text_message_delay=base_config.text_message_delay * 0.9
|
||||
)
|
||||
|
||||
# Возвращаем базовую конфигурацию
|
||||
return base_config
|
||||
142
services/rate_limiting/rate_limit_service.py
Normal file
142
services/rate_limiting/rate_limit_service.py
Normal file
@@ -0,0 +1,142 @@
|
||||
"""
|
||||
Сервис для управления rate limiting в AnonBot
|
||||
"""
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
|
||||
from aiogram.exceptions import TelegramAPIError, TelegramRetryAfter
|
||||
|
||||
from config.constants import MIN_REQUESTS_FOR_ADAPTATION, HIGH_ERROR_RATE_THRESHOLD, LOW_ERROR_RATE_THRESHOLD
|
||||
from services.infrastructure.logger import get_logger
|
||||
from .rate_limit_config import RateLimitSettings, get_adaptive_config, get_rate_limit_config
|
||||
from .rate_limiter import send_with_rate_limit, telegram_rate_limiter
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class RateLimitService:
|
||||
"""Сервис для управления rate limiting"""
|
||||
|
||||
def __init__(self):
|
||||
self.rate_limiter = telegram_rate_limiter
|
||||
self.config = get_rate_limit_config()
|
||||
self.stats = {
|
||||
'total_requests': 0,
|
||||
'successful_requests': 0,
|
||||
'failed_requests': 0,
|
||||
'retry_after_errors': 0,
|
||||
'other_errors': 0,
|
||||
'total_wait_time': 0.0
|
||||
}
|
||||
|
||||
async def send_with_rate_limit(
|
||||
self,
|
||||
send_func: Callable,
|
||||
chat_id: int,
|
||||
*args,
|
||||
**kwargs
|
||||
) -> Any:
|
||||
"""
|
||||
Отправляет сообщение с соблюдением rate limit
|
||||
|
||||
Args:
|
||||
send_func: Функция отправки (например, bot.send_message)
|
||||
chat_id: ID чата
|
||||
*args, **kwargs: Аргументы для функции отправки
|
||||
|
||||
Returns:
|
||||
Результат выполнения функции отправки
|
||||
"""
|
||||
self.stats['total_requests'] += 1
|
||||
logger.info(f"Обработка rate limit запроса для чата {chat_id}")
|
||||
|
||||
try:
|
||||
result, wait_time = await self.rate_limiter.send_with_rate_limit(send_func, chat_id, *args, **kwargs)
|
||||
self.stats['successful_requests'] += 1
|
||||
self.stats['total_wait_time'] += wait_time
|
||||
logger.info(f"Rate limited сообщение успешно отправлено в чат {chat_id}, время ожидания: {wait_time:.2f}с")
|
||||
return result
|
||||
|
||||
except TelegramRetryAfter as e:
|
||||
self.stats['failed_requests'] += 1
|
||||
self.stats['retry_after_errors'] += 1
|
||||
logger.warning(f"Превышен rate limit для чата {chat_id}: {e}")
|
||||
raise
|
||||
|
||||
except TelegramAPIError as e:
|
||||
self.stats['failed_requests'] += 1
|
||||
self.stats['other_errors'] += 1
|
||||
logger.error(f"Ошибка Telegram API для чата {chat_id}: {e}")
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
self.stats['failed_requests'] += 1
|
||||
self.stats['other_errors'] += 1
|
||||
logger.error(f"Неожиданная ошибка в rate limit сервисе для чата {chat_id}: {e}")
|
||||
raise
|
||||
|
||||
def get_stats(self) -> Dict[str, Any]:
|
||||
"""Получает статистику rate limiting"""
|
||||
total = self.stats['total_requests']
|
||||
if total == 0:
|
||||
return {
|
||||
'total_requests': 0,
|
||||
'successful_requests': 0,
|
||||
'failed_requests': 0,
|
||||
'success_rate': 0.0,
|
||||
'error_rate': 0.0,
|
||||
'retry_after_errors': 0,
|
||||
'other_errors': 0,
|
||||
'retry_after_rate': 0.0,
|
||||
'other_error_rate': 0.0,
|
||||
'average_wait_time': 0.0
|
||||
}
|
||||
|
||||
return {
|
||||
'total_requests': total,
|
||||
'successful_requests': self.stats['successful_requests'],
|
||||
'failed_requests': self.stats['failed_requests'],
|
||||
'success_rate': self.stats['successful_requests'] / total,
|
||||
'error_rate': self.stats['failed_requests'] / total,
|
||||
'retry_after_errors': self.stats['retry_after_errors'],
|
||||
'other_errors': self.stats['other_errors'],
|
||||
'retry_after_rate': self.stats['retry_after_errors'] / total,
|
||||
'other_error_rate': self.stats['other_errors'] / total,
|
||||
'average_wait_time': self.stats['total_wait_time'] / total if total > 0 else 0.0
|
||||
}
|
||||
|
||||
def reset_stats(self):
|
||||
"""Сбрасывает статистику"""
|
||||
self.stats = {
|
||||
'total_requests': 0,
|
||||
'successful_requests': 0,
|
||||
'failed_requests': 0,
|
||||
'retry_after_errors': 0,
|
||||
'other_errors': 0,
|
||||
'total_wait_time': 0.0
|
||||
}
|
||||
logger.info("Статистика rate limit сброшена")
|
||||
|
||||
def update_config(self, new_config: RateLimitSettings):
|
||||
"""Обновляет конфигурацию rate limiting"""
|
||||
self.config = new_config
|
||||
logger.info(f"Конфигурация rate limit обновлена: {new_config}")
|
||||
|
||||
def get_adaptive_config(self) -> RateLimitSettings:
|
||||
"""Получает адаптивную конфигурацию на основе текущей статистики"""
|
||||
error_rate = self.stats['failed_requests'] / max(1, self.stats['total_requests'])
|
||||
return get_adaptive_config(error_rate, self.config)
|
||||
|
||||
def should_adapt_config(self) -> bool:
|
||||
"""Определяет, нужно ли адаптировать конфигурацию"""
|
||||
if self.stats['total_requests'] < MIN_REQUESTS_FOR_ADAPTATION: # Недостаточно данных
|
||||
return False
|
||||
|
||||
error_rate = self.stats['failed_requests'] / self.stats['total_requests']
|
||||
return error_rate > HIGH_ERROR_RATE_THRESHOLD or error_rate < LOW_ERROR_RATE_THRESHOLD # Высокий или низкий уровень ошибок
|
||||
|
||||
async def adapt_config_if_needed(self):
|
||||
"""Адаптирует конфигурацию если необходимо"""
|
||||
if self.should_adapt_config():
|
||||
new_config = self.get_adaptive_config()
|
||||
self.update_config(new_config)
|
||||
logger.info("Конфигурация rate limit адаптирована на основе текущей производительности")
|
||||
230
services/rate_limiting/rate_limiter.py
Normal file
230
services/rate_limiting/rate_limiter.py
Normal file
@@ -0,0 +1,230 @@
|
||||
"""
|
||||
Rate limiter для предотвращения Flood control ошибок в Telegram Bot API
|
||||
"""
|
||||
import asyncio
|
||||
import time
|
||||
from typing import Dict, Optional, Any, Callable
|
||||
from dataclasses import dataclass
|
||||
from aiogram.exceptions import TelegramRetryAfter, TelegramAPIError
|
||||
|
||||
from services.infrastructure.logger import get_logger
|
||||
from .rate_limit_config import RateLimitSettings, get_rate_limit_config
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RateLimitConfig:
|
||||
"""Конфигурация для rate limiting"""
|
||||
messages_per_second: float = 0.5 # Максимум 0.5 сообщений в секунду на чат
|
||||
burst_limit: int = 3 # Максимум 3 сообщения подряд
|
||||
retry_after_multiplier: float = 1.2 # Множитель для увеличения задержки при retry
|
||||
max_retry_delay: float = 60.0 # Максимальная задержка между попытками
|
||||
|
||||
|
||||
class ChatRateLimiter:
|
||||
"""Rate limiter для конкретного чата"""
|
||||
|
||||
def __init__(self, config: RateLimitConfig):
|
||||
self.config = config
|
||||
self.last_send_time = 0.0
|
||||
self.burst_count = 0
|
||||
self.burst_reset_time = 0.0
|
||||
self.retry_delay = 1.0
|
||||
|
||||
async def wait_if_needed(self) -> None:
|
||||
"""Ждет если необходимо для соблюдения rate limit"""
|
||||
current_time = time.time()
|
||||
|
||||
# Сбрасываем счетчик burst если прошло достаточно времени
|
||||
if current_time >= self.burst_reset_time:
|
||||
self.burst_count = 0
|
||||
self.burst_reset_time = current_time + 1.0
|
||||
|
||||
# Проверяем burst limit
|
||||
if self.burst_count >= self.config.burst_limit:
|
||||
wait_time = self.burst_reset_time - current_time
|
||||
if wait_time > 0:
|
||||
logger.info(f"Достигнут лимит burst, ожидание {wait_time:.2f}с")
|
||||
await asyncio.sleep(wait_time)
|
||||
current_time = time.time()
|
||||
self.burst_count = 0
|
||||
self.burst_reset_time = current_time + 1.0
|
||||
|
||||
# Проверяем минимальный интервал между сообщениями
|
||||
time_since_last = current_time - self.last_send_time
|
||||
min_interval = 1.0 / self.config.messages_per_second
|
||||
|
||||
if time_since_last < min_interval:
|
||||
wait_time = min_interval - time_since_last
|
||||
logger.debug(f"Rate limiting: waiting {wait_time:.2f}s")
|
||||
await asyncio.sleep(wait_time)
|
||||
|
||||
# Обновляем время последней отправки
|
||||
self.last_send_time = time.time()
|
||||
self.burst_count += 1
|
||||
|
||||
|
||||
class GlobalRateLimiter:
|
||||
"""Глобальный rate limiter для всех чатов"""
|
||||
|
||||
def __init__(self, config: RateLimitConfig):
|
||||
self.config = config
|
||||
self.chat_limiters: Dict[int, ChatRateLimiter] = {}
|
||||
self.global_last_send = 0.0
|
||||
self.global_min_interval = 0.1 # Минимум 100ms между любыми сообщениями
|
||||
|
||||
def get_chat_limiter(self, chat_id: int) -> ChatRateLimiter:
|
||||
"""Получает rate limiter для конкретного чата"""
|
||||
if chat_id not in self.chat_limiters:
|
||||
self.chat_limiters[chat_id] = ChatRateLimiter(self.config)
|
||||
return self.chat_limiters[chat_id]
|
||||
|
||||
async def wait_if_needed(self, chat_id: int) -> None:
|
||||
"""Ждет если необходимо для соблюдения глобального и чат-специфичного rate limit"""
|
||||
current_time = time.time()
|
||||
|
||||
# Глобальный rate limit
|
||||
time_since_global = current_time - self.global_last_send
|
||||
if time_since_global < self.global_min_interval:
|
||||
wait_time = self.global_min_interval - time_since_global
|
||||
logger.info(f"Применен глобальный rate limit для чата {chat_id}, ожидание {wait_time:.2f}с")
|
||||
await asyncio.sleep(wait_time)
|
||||
current_time = time.time()
|
||||
|
||||
# Чат-специфичный rate limit
|
||||
chat_limiter = self.get_chat_limiter(chat_id)
|
||||
await chat_limiter.wait_if_needed()
|
||||
|
||||
self.global_last_send = time.time()
|
||||
|
||||
|
||||
class RetryHandler:
|
||||
"""Обработчик повторных попыток с экспоненциальной задержкой"""
|
||||
|
||||
def __init__(self, config: RateLimitConfig):
|
||||
self.config = config
|
||||
|
||||
async def execute_with_retry(
|
||||
self,
|
||||
func: Callable,
|
||||
chat_id: int,
|
||||
*args,
|
||||
max_retries: int = 3,
|
||||
**kwargs
|
||||
) -> tuple[Any, float]:
|
||||
"""Выполняет функцию с повторными попытками при ошибках"""
|
||||
retry_count = 0
|
||||
current_delay = self.config.retry_after_multiplier
|
||||
total_wait_time = 0.0
|
||||
|
||||
while retry_count <= max_retries:
|
||||
try:
|
||||
result = await func(*args, **kwargs)
|
||||
# Записываем успешный запрос
|
||||
logger.debug(f"Rate limit запрос успешен для чата {chat_id}")
|
||||
return result, total_wait_time
|
||||
|
||||
except TelegramRetryAfter as e:
|
||||
retry_count += 1
|
||||
if retry_count > max_retries:
|
||||
logger.error(f"Превышено максимальное количество попыток для RetryAfter: {e}")
|
||||
raise
|
||||
|
||||
# Используем время ожидания от Telegram или наше увеличенное
|
||||
wait_time = max(e.retry_after, current_delay)
|
||||
wait_time = min(wait_time, self.config.max_retry_delay)
|
||||
total_wait_time += wait_time
|
||||
|
||||
logger.info(f"RetryAfter ошибка для чата {chat_id}, ожидание {wait_time:.2f}с (попытка {retry_count}/{max_retries})")
|
||||
await asyncio.sleep(wait_time)
|
||||
current_delay *= self.config.retry_after_multiplier
|
||||
|
||||
except TelegramAPIError as e:
|
||||
retry_count += 1
|
||||
if retry_count > max_retries:
|
||||
logger.error(f"Превышено максимальное количество попыток для TelegramAPIError: {e}")
|
||||
raise
|
||||
|
||||
wait_time = min(current_delay, self.config.max_retry_delay)
|
||||
total_wait_time += wait_time
|
||||
logger.info(f"TelegramAPIError для чата {chat_id}, ожидание {wait_time:.2f}с (попытка {retry_count}/{max_retries}): {e}")
|
||||
await asyncio.sleep(wait_time)
|
||||
current_delay *= self.config.retry_after_multiplier
|
||||
|
||||
except Exception as e:
|
||||
# Для других ошибок не делаем retry
|
||||
logger.error(f"Ошибка без повторных попыток: {e}")
|
||||
raise
|
||||
|
||||
|
||||
class TelegramRateLimiter:
|
||||
"""Основной класс для rate limiting в Telegram боте"""
|
||||
|
||||
def __init__(self, config: Optional[RateLimitConfig] = None):
|
||||
self.config = config or RateLimitConfig()
|
||||
self.global_limiter = GlobalRateLimiter(self.config)
|
||||
self.retry_handler = RetryHandler(self.config)
|
||||
|
||||
async def send_with_rate_limit(
|
||||
self,
|
||||
send_func: Callable,
|
||||
chat_id: int,
|
||||
*args,
|
||||
**kwargs
|
||||
) -> tuple[Any, float]:
|
||||
"""Отправляет сообщение с соблюдением rate limit и retry логики"""
|
||||
|
||||
async def _send():
|
||||
await self.global_limiter.wait_if_needed(chat_id)
|
||||
# Добавляем chat_id в kwargs для функции отправки
|
||||
send_kwargs = kwargs.copy()
|
||||
send_kwargs['chat_id'] = chat_id
|
||||
return await send_func(*args, **send_kwargs)
|
||||
|
||||
return await self.retry_handler.execute_with_retry(_send, chat_id)
|
||||
|
||||
async def execute_with_rate_limit(
|
||||
self,
|
||||
handler_func: Callable,
|
||||
chat_id: int
|
||||
) -> tuple[Any, float]:
|
||||
"""Выполняет обработчик с соблюдением rate limit (без добавления chat_id в kwargs)"""
|
||||
|
||||
async def _execute():
|
||||
await self.global_limiter.wait_if_needed(chat_id)
|
||||
return await handler_func()
|
||||
|
||||
return await self.retry_handler.execute_with_retry(_execute, chat_id)
|
||||
|
||||
|
||||
def _create_rate_limit_config(settings: RateLimitSettings) -> RateLimitConfig:
|
||||
"""Создает RateLimitConfig из RateLimitSettings"""
|
||||
return RateLimitConfig(
|
||||
messages_per_second=settings.messages_per_second,
|
||||
burst_limit=settings.burst_limit,
|
||||
retry_after_multiplier=settings.retry_after_multiplier,
|
||||
max_retry_delay=settings.max_retry_delay
|
||||
)
|
||||
|
||||
|
||||
# Получаем конфигурацию из настроек
|
||||
_rate_limit_settings = get_rate_limit_config()
|
||||
_default_config = _create_rate_limit_config(_rate_limit_settings)
|
||||
|
||||
telegram_rate_limiter = TelegramRateLimiter(_default_config)
|
||||
|
||||
|
||||
async def send_with_rate_limit(send_func: Callable, chat_id: int, *args, **kwargs) -> tuple[Any, float]:
|
||||
"""
|
||||
Удобная функция для отправки сообщений с rate limiting
|
||||
|
||||
Args:
|
||||
send_func: Функция отправки (например, bot.send_message)
|
||||
chat_id: ID чата
|
||||
*args, **kwargs: Аргументы для функции отправки
|
||||
|
||||
Returns:
|
||||
Кортеж (результат выполнения функции отправки, общее время ожидания)
|
||||
"""
|
||||
return await telegram_rate_limiter.send_with_rate_limit(send_func, chat_id, *args, **kwargs)
|
||||
Reference in New Issue
Block a user