32 lines
1.2 KiB
Python
32 lines
1.2 KiB
Python
import logging
|
|
import time
|
|
from collections.abc import Callable
|
|
from functools import wraps
|
|
from typing import Any
|
|
|
|
from playwright.sync_api import Error, TimeoutError as PlaywrightTimeoutError
|
|
|
|
logger = logging.getLogger("iaai_scraper.retry")
|
|
|
|
|
|
def retryable(max_attempts: int, delay_seconds: float = 2.5) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
@wraps(func)
|
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
last_error: Exception | None = None
|
|
for attempt in range(1, max_attempts + 1):
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except (PlaywrightTimeoutError, Error, ConnectionError, OSError, RuntimeError) as exc:
|
|
last_error = exc
|
|
logger.warning("%s failed on attempt %s/%s: %s", func.__name__, attempt, max_attempts, exc)
|
|
if attempt < max_attempts:
|
|
time.sleep(delay_seconds * (2 ** (attempt - 1)))
|
|
if last_error is not None:
|
|
raise last_error
|
|
raise RuntimeError("Retry wrapper failed without a captured exception")
|
|
|
|
return wrapper
|
|
|
|
return decorator
|