Skip to content

Instantly share code, notes, and snippets.

@robbienohra
Created March 26, 2026 20:11
Show Gist options
  • Select an option

  • Save robbienohra/bb1c1ff646999e7ad6f43704e5468da8 to your computer and use it in GitHub Desktop.

Select an option

Save robbienohra/bb1c1ff646999e7ad6f43704e5468da8 to your computer and use it in GitHub Desktop.
decorator.py
import functools
import time
# --- 1. The Decorator ---
def retry_api_call():
"""Decorator that introspects 'self' for config, allowing kwargs overrides."""
def decorator(func):
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
# Extract per-call overrides if the user provided them
call_retries = kwargs.pop("retries", None)
call_delay = kwargs.pop("delay", None)
# Fallback to instance defaults (from 'self') if no overrides exist
retries = call_retries if call_retries is not None else self.retries
delay = call_delay if call_delay is not None else self.delay
print(
f" [Decorator] Resolved config -> Retries: {retries}, Delay: {delay}s"
)
for attempt in range(1, retries + 1):
try:
print(f" [Decorator] Attempt {attempt} of {retries}...")
# Call the actual API method
return func(self, *args, **kwargs)
except Exception as e:
if attempt == retries:
print(
f" [Decorator] All {retries} attempts failed. Raising exception."
)
raise e
print(
f" [Decorator] Failed. Waiting {delay}s before next attempt...\n"
)
time.sleep(delay)
return wrapper
return decorator
# --- 2. The API Client ---
class MyAPIClient:
def __init__(self, retries=3, delay=1.0):
# Client-wide defaults
self.retries = retries
self.delay = delay
@retry_api_call()
def fetch_data(self, endpoint):
"""A simulated API call that always fails to demonstrate retries."""
print(f" [Client] Hitting endpoint: {endpoint}")
raise ConnectionError("503 Service Unavailable")
# --- 3. Local Testing / Execution ---
if __name__ == "__main__":
# Initialize the client with a default 3 retries and a fast 0.2s delay for testing
client = MyAPIClient(retries=4, delay=0.2)
print("=== TEST 1: Using Client-Wide Defaults ===")
try:
# We don't pass any kwargs, so it falls back to the client's init settings
client.fetch_data("/users")
except ConnectionError:
print("Caught expected ConnectionError from Test 1.\n")
print("=== TEST 2: Overriding Config for a Specific Call ===")
try:
# We pass kwargs directly to the method to override the client defaults
client.fetch_data("/payments", retries=2, delay=0.5)
except ConnectionError:
print("Caught expected ConnectionError from Test 2.\n")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment