Retry idempotent GET requests in Python

A Python function that retries an idempotent GET request a fixed number of times with a delay between attempts, raising a RuntimeError only after all retries fail.

Easy Python 3.9+ Aug 9, 2026 Microservices patterns 14 views 0 copies

Python code

23 lines
Python 3.9+
import time
import urllib.error
import urllib.request
from http.client import HTTPException

def fetch_with_retry(url, max_retries=3, delay=1.0):
    for attempt in range(1, max_retries + 1):
        try:
            with urllib.request.urlopen(url, timeout=5) as response:
                return response.read().decode()
        except (urllib.error.URLError, HTTPException, TimeoutError) as e:
            print(f"Attempt {attempt} failed for {url}: {e}")

            if attempt < max_retries:
                time.sleep(delay)
    raise RuntimeError(f"Failed after {max_retries} attempts: {url}")

if __name__ == "__main__":
    try:
        result = fetch_with_retry("http://127.0.0.1:9999/mock", max_retries=3, delay=0.5)
        print(f"Success: {result}")
    except RuntimeError as err:
        print(err)

Output

stdout
Attempt 1 failed for http://127.0.0.1:9999/mock: <urlopen error [Errno 111] Connection refused>
Attempt 2 failed for http://127.0.0.1:9999/mock: <urlopen error [Errno 111] Connection refused>
Attempt 3 failed for http://127.0.0.1:9999/mock: <urlopen error [Errno 111] Connection refused>
Failed after 3 attempts: http://127.0.0.1:9999/mock

How it works

The urllib.request.urlopen call performs the GET request inside a context manager, automatically closing the response. The try/except catches network-level failures like URLError, HTTPException, and TimeoutError, which are the typical exceptions for transient network issues. The loop iterates from 1 to max_retries, printing the attempt number on each failure and sleeping delay seconds between attempts. If all attempts fail, the function raises a RuntimeError so the caller can handle the final failure explicitly. Because GET requests are idempotent, retrying them is safe, which makes this a common pattern in microservice clients.

Common mistakes

  • Retrying non-idempotent requests (POST, PUT) without idempotency keys, which can cause duplicate side effects.
  • Not catching both `urllib.error.URLError` and `HTTPException`, missing timeouts.
  • Sleeping before the first attempt or after the last one, wasting time.
  • Forgetting to close the response object when not using a context manager.

Variations

  1. Use `requests.Session` with `HTTPAdapter` to get built-in retry support.
  2. Add exponential backoff by multiplying `delay` by a factor on each retry.

Real-world use cases

  • A microservice calling a downstream HTTP API that occasionally fails due to transient network errors.
  • A health check endpoint that retries a few times before declaring a service unhealthy.
  • A background worker fetching configuration from a remote endpoint with short-lived connectivity issues.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Microservices patterns

Related tutorials and quizzes for this topic.