💡 Python QuickBits — 🔄 Retry Logic Decorator


Description:

Transient failures are common — API calls might timeout, or I/O operations may fail.

Instead of crashing, it’s better to retry automatically a few times before giving up.

With a simple decorator, you can add resilience to any function.


📝 The Retry Decorator

import functools
import time

def retry(max_attempts=3, delay=1):
    """
    Decorator that retries a function if it raises an Exception.
    Args:
        max_attempts: number of total attempts (default 3)
        delay: delay in seconds between retries
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"[Attempt {attempt}] Failed with {e}")
                    if attempt < max_attempts:
                        time.sleep(delay)   # wait before retrying
                    else:
                        raise              # re-raise last error
        return wrapper
    return decorator

This decorator:

  • Tries the function up to max_attempts.
  • Waits delay seconds between attempts.
  • Raises the last exception if all attempts fail.

🔄 Example: Flaky Task

Here’s a simulated function that randomly fails, wrapped with @retry:

import random

@retry(max_attempts=5, delay=0.5)
def flaky_task():
    """Simulate a flaky task that succeeds randomly."""
    if random.random() < 0.7:   # 70% chance of failure
        raise ValueError("Random failure")
    return "Success!"

print("Running flaky_task with retry logic...")
print("Result:", flaky_task())

Running this might show several failed attempts before success.


✅ Key Points

  • ✅ Add resilience to unreliable operations.
  • ✅ Pure standard library, no dependencies.
  • ✅ Adjustable retries and delays.
  • 🐍 Perfect for API calls, DB queries, file operations.

Code Snippet:

import functools   # to preserve metadata when writing decorators
import time        # to add delays between retries
import random      # to simulate flaky behavior


def retry(max_attempts=3, delay=1):
    """
    Decorator that retries a function if it raises an Exception.
    Args:
        max_attempts: number of total attempts (default 3)
        delay: delay in seconds between retries
    """
    def decorator(func):
        @functools.wraps(func)  # preserve function metadata
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)   # call the original function
                except Exception as e:
                    print(f"[Attempt {attempt}] Failed with {e}")
                    if attempt < max_attempts:     # if not last attempt
                        time.sleep(delay)          # wait before retrying
                    else:
                        raise                      # re-raise last exception
        return wrapper
    return decorator


@retry(max_attempts=5, delay=0.5)
def flaky_task():
    """Simulate a flaky task that succeeds randomly."""
    if random.random() < 0.7:   # 70% chance to fail
        raise ValueError("Random failure")
    return "Success!"

print("Running flaky_task with retry logic...")
print("Result:", flaky_task())

Link copied!

Comments

Add Your Comment

Comment Added!