Retry code until it succeeds
Wait strategies determine how long to wait between retry attempts. Tenacity provides 9+ sophisticated backoff algorithms including exponential backoff, jitter, fixed delays, and strategy composition to optimize retry timing for different scenarios.
from tenacity.wait import wait_base
class wait_base(ABC):
"""
Abstract base class for all wait strategies.
Provides arithmetic operators for combining wait strategies
and defines the interface all wait strategies must implement.
"""
@abstractmethod
def __call__(self, retry_state: RetryCallState) -> float:
"""
Calculate wait time before next retry attempt.
Parameters:
- retry_state: Complete state of current retry session
Returns:
Wait time in seconds (float)
"""
def __add__(self, other: 'wait_base') -> 'wait_combine':
"""Add wait times from two strategies."""
def __radd__(self, other: Union[int, float, 'wait_base']) -> 'wait_combine':
"""Support for sum() builtin and reverse addition."""from tenacity.wait import WaitBaseT
WaitBaseT = Union[wait_base, Callable[[RetryCallState], Union[float, int]]]from tenacity import wait_none
class wait_none(wait_fixed):
"""
No wait between retries (0 seconds).
Inherits from wait_fixed with wait time of 0.
Useful for immediate retries without delay.
"""
def __init__(self):
"""Initialize with 0 second wait time."""
super().__init__(0)from tenacity import wait_fixed
from tenacity._utils import time_unit_type
class wait_fixed(wait_base):
"""
Fixed wait time between all retries.
Simplest wait strategy - same delay between every attempt.
"""
def __init__(self, wait: time_unit_type):
"""
Initialize with fixed wait time.
Parameters:
- wait: Fixed time to wait between attempts
Can be int/float (seconds) or timedelta
"""from datetime import timedelta
# No delay between retries
@retry(wait=wait_none())
def immediate_retry():
pass
# Fixed 2 second delay
@retry(wait=wait_fixed(2))
def fixed_delay():
pass
# Fixed delay using timedelta
@retry(wait=wait_fixed(timedelta(seconds=5)))
def fixed_delay_timedelta():
passfrom tenacity import wait_random
class wait_random(wait_base):
"""
Random wait time within specified range.
Generates uniformly distributed random wait times between
min and max values. Helps avoid thundering herd problems.
"""
def __init__(
self,
min: time_unit_type = 0,
max: time_unit_type = 1
):
"""
Initialize with random wait range.
Parameters:
- min: Minimum wait time (inclusive)
- max: Maximum wait time (inclusive)
"""# Random wait between 0 and 1 second (default)
@retry(wait=wait_random())
def random_delay_default():
pass
# Random wait between 1 and 5 seconds
@retry(wait=wait_random(min=1, max=5))
def random_delay_range():
passfrom tenacity import wait_incrementing
from tenacity._utils import MAX_WAIT
class wait_incrementing(wait_base):
"""
Incrementally increasing wait time.
Increases wait time by fixed increment on each attempt.
Useful for gradually backing off without exponential growth.
"""
def __init__(
self,
start: time_unit_type = 0,
increment: time_unit_type = 100,
max: time_unit_type = MAX_WAIT
):
"""
Initialize with incremental parameters.
Parameters:
- start: Starting wait time for first retry
- increment: Amount to increase wait time each attempt
- max: Maximum wait time (caps the increment)
"""# Start at 1 second, increment by 2 seconds each attempt
@retry(wait=wait_incrementing(start=1, increment=2))
def incremental_backoff():
pass
# Attempt 1: wait 1s
# Attempt 2: wait 3s
# Attempt 3: wait 5s
# etc.
# Capped incremental backoff
@retry(wait=wait_incrementing(start=0, increment=5, max=30))
def capped_incremental():
passfrom tenacity import wait_exponential
class wait_exponential(wait_base):
"""
Exponential backoff with fixed intervals.
Classic exponential backoff algorithm. Wait time grows
exponentially: multiplier * (exp_base ^ (attempt_number - 1))
"""
def __init__(
self,
multiplier: Union[int, float] = 1,
max: time_unit_type = MAX_WAIT,
exp_base: Union[int, float] = 2,
min: time_unit_type = 0
):
"""
Initialize exponential backoff parameters.
Parameters:
- multiplier: Multiplier for exponential calculation
- max: Maximum wait time (caps exponential growth)
- exp_base: Base for exponent calculation (default 2)
- min: Minimum wait time (floor for calculation)
"""from tenacity import wait_random_exponential, wait_full_jitter
class wait_random_exponential(wait_exponential):
"""
Exponential backoff with random jitter (Full Jitter algorithm).
Implements AWS's Full Jitter algorithm: random(0, exponential_result).
Prevents thundering herd while maintaining exponential backoff benefits.
"""
def __init__(
self,
multiplier: Union[int, float] = 1,
max: time_unit_type = MAX_WAIT,
exp_base: Union[int, float] = 2,
min: time_unit_type = 0
):
"""Same parameters as wait_exponential, but with random jitter applied."""
# Alias for wait_random_exponential
wait_full_jitter = wait_random_exponentialfrom tenacity import wait_exponential_jitter
class wait_exponential_jitter(wait_base):
"""
Exponential backoff with configurable jitter.
More flexible jitter implementation allowing custom jitter amounts.
Formula: min(max, exponential_result + random(-jitter, jitter))
"""
def __init__(
self,
initial: float = 1,
max: float = MAX_WAIT,
exp_base: float = 2,
jitter: float = 1
):
"""
Initialize exponential backoff with jitter.
Parameters:
- initial: Initial wait time
- max: Maximum wait time
- exp_base: Base for exponential calculation
- jitter: Maximum jitter amount (±jitter seconds)
"""# Standard exponential backoff: 2, 4, 8, 16 seconds...
@retry(wait=wait_exponential(multiplier=2, min=2, max=60))
def exponential_api_call():
pass
# Exponential with full jitter (recommended for distributed systems)
@retry(wait=wait_random_exponential(multiplier=1, min=4, max=10))
def jittered_api_call():
pass
# Custom exponential with base 3
@retry(wait=wait_exponential(multiplier=1, exp_base=3, max=100))
def base3_exponential():
pass
# Exponential with configurable jitter
@retry(wait=wait_exponential_jitter(initial=1, jitter=0.5, max=30))
def custom_jitter():
passfrom tenacity import wait_combine
class wait_combine(wait_base):
"""
Sum multiple wait strategies.
Adds together the wait times from multiple strategies.
Useful for combining fixed base delays with variable components.
"""
def __init__(self, *strategies: wait_base):
"""
Initialize with strategies to combine.
Parameters:
- *strategies: Variable number of wait strategies to sum
"""from tenacity import wait_chain
class wait_chain(wait_base):
"""
Chain wait strategies in sequence.
Uses each strategy in turn, then repeats the last strategy.
Allows different wait patterns for early vs. later attempts.
"""
def __init__(self, *strategies: wait_base):
"""
Initialize with strategies to chain.
Parameters:
- *strategies: Wait strategies to use in sequence
"""# Combine fixed delay + random jitter
combined_wait = wait_combine(
wait_fixed(3), # Base 3 second delay
wait_random(0, 2) # Plus 0-2 seconds random
)
@retry(wait=combined_wait)
def combined_strategy():
pass
# Chain different strategies for different phases
chained_wait = wait_chain(
wait_fixed(1), # First few attempts: 1 second
wait_fixed(5), # Next attempts: 5 seconds
wait_exponential(multiplier=2) # Final attempts: exponential
)
@retry(wait=chained_wait)
def phased_backoff():
pass# Use + operator to combine strategies
base_delay = wait_fixed(2)
jitter = wait_random(0, 1)
combined = base_delay + jitter
# Chain multiple additions
complex_wait = wait_fixed(1) + wait_random(0, 2) + wait_exponential(multiplier=0.5)
@retry(wait=complex_wait)
def arithmetic_combination():
pass
# Use sum() for multiple strategies
strategies = [wait_fixed(1), wait_random(0, 1), wait_exponential(multiplier=0.5)]
summed_wait = sum(strategies)# AWS SDK-style backoff with full jitter
@retry(
wait=wait_random_exponential(multiplier=1, max=20),
stop=stop_after_attempt(10),
retry=retry_if_exception_type((ConnectionError, TimeoutError))
)
def aws_api_call():
pass# Conservative database retry with incremental backoff
@retry(
wait=wait_incrementing(start=2, increment=2, max=30),
stop=stop_after_attempt(5),
retry=retry_if_exception_type(ConnectionError)
)
def connect_to_db():
pass# Polite web scraping with randomized delays
@retry(
wait=wait_combine(
wait_fixed(1), # Minimum 1 second between requests
wait_random(0, 3) # Plus 0-3 seconds random delay
),
stop=stop_after_attempt(3),
retry=retry_if_exception_type((requests.ConnectionError, requests.Timeout))
)
def scrape_webpage():
pass# Exponential backoff with circuit breaker timing
@retry(
wait=wait_chain(
wait_fixed(0.1), # Fast initial retries
wait_exponential(multiplier=1, min=1, max=60) # Then exponential
),
stop=stop_after_delay(300), # 5 minute circuit open time
retry=retry_if_exception_type(ServiceUnavailableError)
)
def circuit_breaker_call():
pass# Custom fibonacci-style backoff
class wait_fibonacci(wait_base):
def __init__(self, max_wait=MAX_WAIT):
self.max_wait = max_wait
def __call__(self, retry_state):
attempt = retry_state.attempt_number
if attempt == 1:
return min(1, self.max_wait)
elif attempt == 2:
return min(1, self.max_wait)
else:
# Simplified fibonacci: fib(n) = fib(n-1) + fib(n-2)
a, b = 1, 1
for _ in range(attempt - 2):
a, b = b, a + b
return min(b, self.max_wait)
@retry(wait=wait_fibonacci(max_wait=60))
def fibonacci_backoff():
pass# Dynamic wait based on time of day (lighter load during off-hours)
class wait_time_aware(wait_base):
def __call__(self, retry_state):
import datetime
hour = datetime.datetime.now().hour
# Longer waits during business hours (9 AM - 5 PM)
if 9 <= hour <= 17:
base_wait = 10
else:
base_wait = 2
# Still apply exponential backoff
multiplier = 2 ** (retry_state.attempt_number - 1)
return min(base_wait * multiplier, 300) # Max 5 minutes
@retry(wait=wait_time_aware())
def time_sensitive_operation():
pass# Efficient wait for high-frequency retries
@retry(
wait=wait_none(), # No delay for immediate retries
stop=stop_after_attempt(3),
retry=retry_if_exception_type(TransientError)
)
def high_frequency_operation():
pass
# Bounded exponential for long-running services
@retry(
wait=wait_exponential(multiplier=1, min=1, max=60), # Cap at 1 minute
stop=stop_never, # Retry indefinitely
retry=retry_if_exception_type(RecoverableError)
)
def service_operation():
pass# Fast waits for testing
@retry(
wait=wait_fixed(0.1), # 100ms for fast tests
stop=stop_after_attempt(3)
)
def test_operation():
pass
# Zero wait for unit tests
@retry(
wait=wait_none(), # No delay in tests
stop=stop_after_attempt(2)
)
def unit_test_function():
passfrom tenacity._utils import MAX_WAIT, time_unit_type, to_seconds
# Maximum wait time constant
MAX_WAIT: float # sys.maxsize / 2
# Type for time specifications
time_unit_type = Union[int, float, timedelta]
# Convert time units to seconds
def to_seconds(time_unit: time_unit_type) -> float:
"""Convert int/float/timedelta to seconds."""This comprehensive coverage of wait strategies provides sophisticated control over retry timing, enabling optimal backoff behavior for various scenarios from high-frequency operations to long-running distributed systems.
Install with Tessl CLI
npx tessl i tessl/pypi-tenacity