pythonintermediate

Python Retry Decorator with Backoff

Create a retry decorator with exponential backoff and configurable attempts for flaky operations.

python
import time
import random
from functools import wraps
from typing import Type


def retry(
    max_attempts: int = 3,
    base_delay: float = 1.0,
    max_delay: float = 60.0,
    exceptions: tuple[Type[Exception], ...] = (Exception,),
    jitter: bool = True,
):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    if attempt == max_attempts:
                        raise
                    delay = min(base_delay * 2 ** (attempt - 1), max_delay)
                    if jitter:
                        delay *= 0.5 + random.random()
                    print(f"Attempt {attempt} failed: {e}. Retrying in {delay:.1f}s")
                    time.sleep(delay)
        return wrapper
    return decorator


@retry(max_attempts=5, exceptions=(ConnectionError, TimeoutError))
def fetch_data(url: str) -> dict:
    import urllib.request, json
    with urllib.request.urlopen(url, timeout=10) as resp:
        return json.loads(resp.read())


# Async version
import asyncio

def async_retry(max_attempts=3, base_delay=1.0, exceptions=(Exception,)):
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return await func(*args, **kwargs)
                except exceptions:
                    if attempt == max_attempts:
                        raise
                    delay = base_delay * 2 ** (attempt - 1)
                    await asyncio.sleep(delay)
        return wrapper
    return decorator

Use Cases

  • Resilient HTTP requests to external APIs
  • Retrying flaky database connections

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.