The generic retry decorator defaults to all exceptions and can repeat side effects
Source references: 3The reference implementation catches Exception by default and invokes any supplied function again after failure. It does not distinguish transient faults from validation failures or check whether the operation is idempotent.
If an agent applies it to charging, writes, messaging, or resource creation, a successful first operation followed by a failed response could cause duplicate charges, records, or notifications.
The decorator defaults to catching Exception and invokes the supplied function again after failure, which can repeat non-idempotent effects such as charges, writes, or messages. The shown usage narrows retries to NetworkError, reducing risk in that particular example, but the broad default remains available. The risk occurs if users retain the default or wrap non-idempotent operations; they can require explicit transient-error classes and idempotency safeguards.
def retry( max_attempts: int = 3, backoff_factor: float = 2.0, exceptions: tuple = (Exception,)): """Retry decorator with exponential backoff.""" def decorator(func: Callable[..., T]) -> Callable[..., T]: @wraps(func) def wrapper(*args, **kwargs) -> T: last_exception = None for attempt in range(max_attempts): try: return func(*args, **kwargs) except exceptions as e: last_exception = e if attempt < max_attempts - 1: sleep_time = backoff_factor ** attempt time.sleep(sleep_time) continue raise raise last_exceptionShow 2 other places
last_exception = None for attempt in range(max_attempts): try: return func(*args, **kwargs) except exceptions as e: last_exception = e if attempt < max_attempts - 1: sleep_time = backoff_factor ** attempt time.sleep(sleep_time) continue raise raise last_exception# Usage@retry(max_attempts=3, exceptions=(NetworkError,))def fetch_data(url: str) -> dict: response = requests.get(url, timeout=5) response.raise_for_status() return response.json()