Skip to main content
Glama
retry.py3.27 kB
"""Retry policies with exponential backoff.""" import asyncio import logging from typing import Callable, Optional, Tuple, Type from functools import wraps from dataclasses import dataclass logger = logging.getLogger(__name__) @dataclass class RetryPolicy: """Retry policy configuration.""" max_attempts: int = 3 initial_delay: float = 1.0 max_delay: float = 60.0 exponential_base: float = 2.0 jitter: bool = True retryable_exceptions: Tuple[Type[Exception], ...] = (Exception,) async def exponential_backoff( func: Callable, policy: Optional[RetryPolicy] = None, *args, **kwargs, ): """ Execute function with exponential backoff retry. Args: func: Async function to execute policy: Retry policy configuration *args: Function arguments **kwargs: Function keyword arguments Returns: Function result Raises: Last exception if all retries exhausted """ policy = policy or RetryPolicy() last_exception = None for attempt in range(policy.max_attempts): try: return await func(*args, **kwargs) except policy.retryable_exceptions as e: last_exception = e if attempt + 1 >= policy.max_attempts: logger.error(f"All {policy.max_attempts} retry attempts exhausted") raise # Calculate delay with exponential backoff delay = min( policy.initial_delay * (policy.exponential_base**attempt), policy.max_delay, ) # Add jitter if enabled if policy.jitter: import random delay = delay * (0.5 + random.random()) logger.warning( f"Attempt {attempt + 1}/{policy.max_attempts} failed, " f"retrying in {delay:.2f}s: {e}" ) await asyncio.sleep(delay) # Should never reach here, but just in case if last_exception: raise last_exception def retry( max_attempts: int = 3, initial_delay: float = 1.0, max_delay: float = 60.0, exponential_base: float = 2.0, jitter: bool = True, retryable_exceptions: Tuple[Type[Exception], ...] = (Exception,), ): """ Decorator for automatic retry with exponential backoff. Args: max_attempts: Maximum retry attempts initial_delay: Initial delay in seconds max_delay: Maximum delay in seconds exponential_base: Base for exponential backoff jitter: Whether to add random jitter retryable_exceptions: Tuple of exception types to retry on Example: @retry(max_attempts=5, initial_delay=0.5) async def flaky_api_call(): ... """ policy = RetryPolicy( max_attempts=max_attempts, initial_delay=initial_delay, max_delay=max_delay, exponential_base=exponential_base, jitter=jitter, retryable_exceptions=retryable_exceptions, ) def decorator(func: Callable) -> Callable: @wraps(func) async def wrapper(*args, **kwargs): return await exponential_backoff(func, policy, *args, **kwargs) return wrapper return decorator

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vespo92/TrueNasCoreMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server