Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Python resilience patterns including automatic retries, exponential backoff, timeouts, and fault-tolerant decorators. Use when adding retry logic, implementing timeouts, building fault-tolerant services, or handling transient failures.
.claude/skills/wshobson-python-resilience/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-09 | ✗→✓ | ▲ Improved | 50% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 64% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 22% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 9% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 49% | 0% |
Build fault-tolerant Python applications that gracefully handle transient failures, network issues, and service outages. Resilience patterns keep systems running when dependencies are unreliable.
Retry transient errors (network timeouts, temporary service issues). Don't retry permanent errors (invalid credentials, bad requests).
Increase wait time between retries to avoid overwhelming recovering services.
Add randomness to backoff to prevent thundering herd when many clients retry simultaneously.
Cap both attempt count and total duration to prevent infinite retry loops.
pythonfrom tenacity import retry, stop_after_attempt, wait_exponential_jitter @retry( stop=stop_after_attempt(3), wait=wait_exponential_jitter(initial=1, max=10), ) def call_external_service(request: dict) -> dict: return httpx.post("https://api.example.com", json=request).json()
Use the tenacity library for production-grade retry logic. For simpler cases, consider built-in retry functionality or a lightweight custom implementation.
pythonfrom tenacity import ( retry, stop_after_attempt, stop_after_delay, wait_exponential_jitter, retry_if_exception_type, ) TRANSIENT_ERRORS = (ConnectionError, TimeoutError, OSError) @retry( retry=retry_if_exception_type(TRANSIENT_ERRORS), stop=stop_after_attempt(5) | stop_after_delay(60), wait=wait_exponential_jitter(initial=1, max=30), ) def fetch_data(url: str) -> dict: """Fetch data with automatic retry on transient failures.""" response = httpx.get(url, timeout=30) response.raise_for_status() return response.json()
Whitelist specific transient exceptions. Never retry:
ValueError, TypeError - These are bugs, not transient issuesAuthenticationError - Invalid credentials won't become validpythonfrom tenacity import retry, retry_if_exception_type import httpx # Define what's retryable RETRYABLE_EXCEPTIONS = ( ConnectionError, TimeoutError, httpx.ConnectTimeout, httpx.ReadTimeout, ) @retry( retry=retry_if_exception_type(RETRYABLE_EXCEPTIONS), stop=stop_after_attempt(3), wait=wait_exponential_jitter(initial=1, max=10), ) def resilient_api_call(endpoint: str) -> dict: """Make API call with retry on network issues.""" return httpx.get(endpoint, timeout=10).json()
Retry specific HTTP status codes that indicate transient issues.
pythonfrom tenacity import retry, retry_if_result, stop_after_attempt import httpx RETRY_STATUS_CODES = {429, 502, 503, 504} def should_retry_response(response: httpx.Response) -> bool: """Check if response indicates a retryable error.""" return response.status_code in RETRY_STATUS_CODES @retry( retry=retry_if_result(should_retry_response), stop=stop_after_attempt(3), wait=wait_exponential_jitter(initial=1, max=10), ) def http_request(method: str, url: str, **kwargs) -> httpx.Response: """Make HTTP request with retry on transient status codes.""" return httpx.request(method, url, timeout=30, **kwargs)
Handle both network exceptions and HTTP status codes.
pythonfrom tenacity import ( retry, retry_if_exception_type, retry_if_result, stop_after_attempt, wait_exponential_jitter, before_sleep_log, ) import logging import httpx logger = logging.getLogger(__name__) TRANSIENT_EXCEPTIONS = ( ConnectionError, TimeoutError, httpx.ConnectError, httpx.ReadTimeout, ) RETRY_STATUS_CODES = {429, 500, 502, 503, 504} def is_retryable_response(response: httpx.Response) -> bool: return response.status_code in RETRY_STATUS_CODES @retry( retry=( retry_if_exception_type(TRANSIENT_EXCEPTIONS) | retry_if_result(is_retryable_response) ), stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=1, max=30), before_sleep=before_sleep_log(logger, logging.WARNING), ) def robust_http_call( method: str, url: str, **kwargs, ) -> httpx.Response: """HTTP call with comprehensive retry handling.""" return httpx.request(method, url, timeout=30, **kwargs)
Detailed sections (starting with ## Advanced Patterns) live in references/details.md. Read that file when the navigation summary above is insufficient.
stop_after_attempt(5) | stop_after_delay(60)| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-10 | pass→pass | 14,512 | 16,085 | +11% | 1 | 1 | 0% | 2,643 | 4,498 | +70% | 0 | 0 | — |
case-11 | pass→pass | 76,896 | 13,245 | -83% | 1 | 1 | 0% | 2,974 | 4,005 | +35% | 0 | 0 | — |
case-09 | fail→pass | 10,566 | 7,842 | -26% | 1 | 1 | 0% | 2,165 | 3,250 | +50% | 0 | 0 | — |
case-01 | fail→fail | 10,087 | 9,396 | -7% | 1 | 1 | 0% | 2,431 | 3,815 | +57% | 0 | 0 | — |
case-02 | fail→pass | 8,358 | 6,023 | -28% | 1 | 1 | 0% | 1,874 | 3,076 | +64% | 0 | 0 | — |
case-03 | fail→pass | 13,073 | 8,537 | -35% | 1 | 1 | 0% | 2,826 | 3,438 | +22% | 0 | 0 | — |
case-04 | pass→pass | 13,167 | 7,715 | -41% | 1 | 1 | 0% | 2,597 | 3,400 | +31% | 0 | 0 | — |
case-05 | fail→pass | 16,860 | 8,892 | -47% | 1 | 1 | 0% | 3,301 | 3,586 | +9% | 0 | 0 | — |
case-06 | pass→pass | 8,030 | 4,096 | -49% | 1 | 1 | 0% | 1,628 | 2,374 | +46% | 0 | 0 | — |
case-07 | pass→pass | 13,014 | 12,336 | -5% | 1 | 1 | 0% | 2,512 | 3,944 | +57% | 0 | 0 | — |
case-08 | pass→pass | 10,293 | 4,571 | -56% | 1 | 1 | 0% | 1,842 | 2,379 | +29% | 0 | 0 | — |
case-12 | pass→pass | 19,225 | 15,914 | -17% | 1 | 1 | 0% | 3,616 | 4,331 | +20% | 0 | 0 | — |
case-13 | pass→pass | 12,709 | 13,144 | +3% | 1 | 1 | 0% | 2,637 | 4,063 | +54% | 0 | 0 | — |
case-14 | pass→pass | 11,805 | 10,902 | -8% | 1 | 1 | 0% | 2,619 | 3,973 | +52% | 0 | 0 | — |
case-15 | pass→pass | 7,598 | 5,040 | -34% | 1 | 1 | 0% | 1,613 | 2,600 | +61% | 0 | 0 | — |
case-16 | pass→pass | 12,626 | 9,431 | -25% | 1 | 1 | 0% | 2,340 | 3,579 | +53% | 0 | 0 | — |
case-17 | pass→pass | 5,763 | 3,678 | -36% | 1 | 1 | 0% | 1,074 | 2,258 | +110% | 0 | 0 | — |
case-18 | fail→pass | 9,289 | 6,143 | -34% | 1 | 1 | 0% | 1,905 | 2,830 | +49% | 0 | 0 | — |
case-19 | pass→pass | 12,885 | 10,797 | -16% | 1 | 1 | 0% | 2,221 | 3,766 | +70% | 0 | 0 | — |
case-20 | pass→pass | 16,205 | 13,096 | -19% | 1 | 1 | 0% | 3,058 | 4,138 | +35% | 0 | 0 | — |
case-21 | pass→pass | 12,288 | 9,227 | -25% | 1 | 1 | 0% | 2,282 | 3,459 | +52% | 0 | 0 | — |
case-22 | pass→pass | 5,854 | 5,533 | -5% | 1 | 1 | 0% | 1,343 | 2,742 | +104% | 0 | 0 | — |
case-23 | pass→pass | 72,518 | 7,630 | -89% | 1 | 1 | 0% | 2,293 | 3,155 | +38% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +22 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.