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.
Python code
23 linesimport 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
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
- Use `requests.Session` with `HTTPAdapter` to get built-in retry support.
- 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
More from Microservices patterns
- BFF aggregation pattern: combine multiple service responses in Python easy
- Backward Compatible Schema Evolution in Python medium
- Bulkhead Thread Pool per Service Mock in Python medium
- CQRS with Separate Read and Write Repositories in Python medium
- Cache-Aside Pattern in Python: Per-Service Mock easy
- Consumer Driven Contract Pact Mock in Python medium
Keep learning
Related tutorials and quizzes for this topic.