Skip to main content
Glama
retry.pyโ€ข3.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