When integrating external APIs or querying cloud databases, transient network timeouts and rate limits can occur. An exponential backoff decorator retries the operation with progressively increasing delays.
The Decorator Implementation
import time
import random
import functools
from typing import Callable, Type, Tuple
def retry_with_backoff(
retries: int = 3,
initial_delay: float = 0.5,
backoff_factor: float = 2.0,
jitter: bool = True,
exceptions: Tuple[Type[Exception], ...] = (Exception,)
):
"""
Retries an operation with exponential backoff and optional jitter.
"""
def decorator(func: Callable):
@functools.wraps(func)
def wrapper(*args, **kwargs):
delay = initial_delay
attempt = 0
while attempt < retries:
try:
return func(*args, **kwargs)
except exceptions as e:
attempt += 1
if attempt >= retries:
raise e
sleep_time = delay * (1 + random.uniform(0, 0.1) if jitter else 1)
print(f"[{func.__name__}] Attempt {attempt} failed: {e}. Retrying in {sleep_time:.2f}s...")
time.sleep(sleep_time)
delay *= backoff_factor
return wrapper
return decoratorExample Usage
import urllib.request
@retry_with_backoff(retries=4, initial_delay=1.0, exceptions=(urllib.error.URLError,))
def fetch_user_data(user_id: int):
url = f"https://api.example.com/users/{user_id}"
with urllib.request.urlopen(url, timeout=5) as response:
return response.read()
# Execution will automatically retry up to 4 times on transient network failures!
data = fetch_user_data(42)