|
|
|
|
|
from functools import wraps |
|
|
from typing import Callable, TypeVar |
|
|
|
|
|
from app.config.config import settings |
|
|
from app.log.logger import get_retry_logger |
|
|
from app.utils.helpers import redact_key_for_logging |
|
|
|
|
|
T = TypeVar("T") |
|
|
logger = get_retry_logger() |
|
|
|
|
|
|
|
|
class RetryHandler: |
|
|
"""重试处理装饰器""" |
|
|
|
|
|
def __init__(self, key_arg: str = "api_key"): |
|
|
self.key_arg = key_arg |
|
|
|
|
|
def __call__(self, func: Callable[..., T]) -> Callable[..., T]: |
|
|
@wraps(func) |
|
|
async def wrapper(*args, **kwargs) -> T: |
|
|
last_exception = None |
|
|
|
|
|
for attempt in range(settings.MAX_RETRIES): |
|
|
retries = attempt + 1 |
|
|
try: |
|
|
return await func(*args, **kwargs) |
|
|
except Exception as e: |
|
|
last_exception = e |
|
|
logger.warning( |
|
|
f"API call failed with error: {str(e)}. Attempt {retries} of {settings.MAX_RETRIES}" |
|
|
) |
|
|
|
|
|
|
|
|
key_manager = kwargs.get("key_manager") |
|
|
if key_manager: |
|
|
old_key = kwargs.get(self.key_arg) |
|
|
new_key = await key_manager.handle_api_failure(old_key, retries) |
|
|
if new_key: |
|
|
kwargs[self.key_arg] = new_key |
|
|
logger.info(f"Switched to new API key: {redact_key_for_logging(new_key)}") |
|
|
else: |
|
|
logger.error(f"No valid API key available after {retries} retries.") |
|
|
break |
|
|
|
|
|
logger.error( |
|
|
f"All retry attempts failed, raising final exception: {str(last_exception)}" |
|
|
) |
|
|
raise last_exception |
|
|
|
|
|
return wrapper |
|
|
|