Production-grade retries made easy.
Global configuration functions for controlling retry behavior system-wide and enabling test mode with modified retry parameters for faster test execution and predictable behavior.
Control whether retry behavior is active across the entire application. When deactivated, all retry decorators and contexts will execute only once without retries.
def is_active() -> bool:
"""
Check whether retrying is globally active.
Returns:
bool: True if retrying is active, False otherwise
"""
def set_active(active: bool) -> None:
"""
Globally activate or deactivate retrying.
When deactivated, all retry decorators and retry contexts will
execute only once regardless of configured attempts.
Parameters:
- active: bool - Whether to activate retrying
This function is idempotent and can be called repeatedly.
"""Usage Examples:
import stamina
# Check current state
print(f"Retries active: {stamina.is_active()}") # True by default
# Temporarily disable retries
stamina.set_active(False)
@stamina.retry(on=Exception, attempts=5)
def unreliable_function():
raise ValueError("This will not retry")
try:
unreliable_function() # Fails immediately, no retries
except ValueError:
print("Function failed without retries")
# Re-enable retries
stamina.set_active(True)
# Emergency circuit breaker pattern
def emergency_disable_retries():
"""Disable retries when system is under stress."""
stamina.set_active(False)
logger.warning("Retries disabled due to system overload")
def restore_retries():
"""Re-enable retries when system recovers."""
stamina.set_active(True)
logger.info("Retries re-enabled")Test mode provides predictable retry behavior for testing scenarios by disabling backoff delays and controlling attempt counts.
def is_testing() -> bool:
"""
Check whether test mode is enabled.
Returns:
bool: True if test mode is active, False otherwise
"""
def set_testing(
testing: bool,
*,
attempts: int = 1,
cap: bool = False
) -> _RestoreTestingCM:
"""
Activate or deactivate test mode.
In test mode:
- All backoff delays are set to 0 (no waiting)
- Attempt counts are controlled by the attempts parameter
Parameters:
- testing: bool - Whether to enable test mode
- attempts: int - Number of attempts in test mode (default: 1)
- cap: bool - If True, cap attempts rather than override (default: False)
Returns:
Context manager that restores previous testing state when exited
When cap=True:
- If attempts=3 and user specifies 5 attempts, use 3 (capped)
- If attempts=3 and user specifies 2 attempts, use 2 (user's value)
This function can be used as a context manager and is idempotent.
"""Usage Examples:
import stamina
# Basic test mode usage
def test_retry_behavior():
# Enable test mode with single attempt
stamina.set_testing(True, attempts=1)
@stamina.retry(on=ValueError, attempts=5) # Will only try once
def failing_function():
raise ValueError("Test failure")
try:
failing_function()
assert False, "Should have failed"
except ValueError:
pass # Expected
# Disable test mode
stamina.set_testing(False)
# Test mode with multiple attempts
def test_eventual_success():
stamina.set_testing(True, attempts=3) # Allow 3 attempts in test
call_count = 0
@stamina.retry(on=ValueError, attempts=10) # Normally 10, but test caps at 3
def eventually_succeeds():
nonlocal call_count
call_count += 1
if call_count < 3:
raise ValueError("Not yet")
return "success"
result = eventually_succeeds()
assert result == "success"
assert call_count == 3
stamina.set_testing(False)
# Context manager usage
def test_with_context_manager():
with stamina.set_testing(True, attempts=2):
# Test code here runs with test mode
@stamina.retry(on=Exception, attempts=5)
def test_function():
raise Exception("Test")
try:
test_function() # Will try 2 times, not 5
except Exception:
pass
# Test mode automatically restored after context
# Capped attempts mode
def test_capped_attempts():
with stamina.set_testing(True, attempts=3, cap=True):
# User specifies 2 attempts, test allows up to 3, so use 2
@stamina.retry(on=ValueError, attempts=2)
def function_a():
raise ValueError("Test")
# User specifies 5 attempts, test caps at 3, so use 3
@stamina.retry(on=ValueError, attempts=5)
def function_b():
raise ValueError("Test")Common patterns for testing retry behavior:
import pytest
import stamina
class TestRetryBehavior:
def setup_method(self):
"""Reset retry state before each test."""
stamina.set_active(True)
stamina.set_testing(False)
def test_successful_retry(self):
"""Test that retries eventually succeed."""
with stamina.set_testing(True, attempts=3):
call_count = 0
@stamina.retry(on=ValueError, attempts=5)
def flaky_function():
nonlocal call_count
call_count += 1
if call_count < 3:
raise ValueError("Not ready")
return "success"
result = flaky_function()
assert result == "success"
assert call_count == 3
def test_exhausted_retries(self):
"""Test behavior when all retries are exhausted."""
with stamina.set_testing(True, attempts=2):
call_count = 0
@stamina.retry(on=ValueError, attempts=3) # Will be capped at 2
def always_fails():
nonlocal call_count
call_count += 1
raise ValueError(f"Failure {call_count}")
with pytest.raises(ValueError, match="Failure 2"):
always_fails()
assert call_count == 2
def test_no_retries_when_inactive(self):
"""Test that inactive mode prevents retries."""
stamina.set_active(False)
call_count = 0
@stamina.retry(on=ValueError, attempts=5)
def should_not_retry():
nonlocal call_count
call_count += 1
raise ValueError("Single failure")
with pytest.raises(ValueError):
should_not_retry()
assert call_count == 1 # Called only once
def test_context_manager_cleanup(self):
"""Test that context managers properly restore state."""
original_testing = stamina.is_testing()
try:
with stamina.set_testing(True, attempts=2):
assert stamina.is_testing() == True
# Test code here
# State should be restored
assert stamina.is_testing() == original_testing
finally:
stamina.set_testing(original_testing)
# Pytest fixtures for common test setups
@pytest.fixture
def no_retries():
"""Fixture to disable retries for fast tests."""
with stamina.set_testing(True, attempts=1):
yield
@pytest.fixture
def fast_retries():
"""Fixture for tests that need some retries but fast execution."""
with stamina.set_testing(True, attempts=3):
yield
def test_with_no_retries(no_retries):
"""Test using the no_retries fixture."""
@stamina.retry(on=ValueError, attempts=10)
def fails_once():
raise ValueError("Test failure")
with pytest.raises(ValueError):
fails_once() # Fails immediately
def test_with_fast_retries(fast_retries):
"""Test using the fast_retries fixture."""
call_count = 0
@stamina.retry(on=ValueError, attempts=10)
def succeeds_on_third():
nonlocal call_count
call_count += 1
if call_count < 3:
raise ValueError("Not yet")
return "success"
result = succeeds_on_third()
assert result == "success"Configure retry behavior during application initialization:
import stamina
import os
def configure_retries():
"""Configure retry behavior based on environment."""
env = os.getenv("ENVIRONMENT", "production")
if env == "test":
# Fast, predictable retries for tests
stamina.set_testing(True, attempts=2)
elif env == "development":
# Shorter timeouts for development
# Use default active behavior but configure individual retries
pass
elif env == "production":
# Full retry behavior (default)
stamina.set_active(True)
else:
raise ValueError(f"Unknown environment: {env}")
# Call during app startup
configure_retries()Integrate with application health checks:
import stamina
from your_app import health_check
def monitor_system_health():
"""Disable retries when system is unhealthy."""
if not health_check.is_healthy():
stamina.set_active(False)
logger.warning("Retries disabled due to system health issues")
else:
stamina.set_active(True)
# Run periodically
import threading
import time
def health_monitor_loop():
while True:
monitor_system_health()
time.sleep(30) # Check every 30 seconds
health_thread = threading.Thread(target=health_monitor_loop, daemon=True)
health_thread.start()class _RestoreTestingCM:
"""Context manager that restores previous testing state when exited."""
def __enter__(self) -> None: ...
def __exit__(self, exc_type, exc_val, exc_tb) -> None: ...Install with Tessl CLI
npx tessl i tessl/pypi-stamina