Handle network failures gracefully. Bounds, backoff, and partial-failure handling.
await client.get(url) and hope. If it hangs, you wait forever. If one fails, the whole batch fails.
asyncio.timeout(5) cancels if it runs too long.gather(..., return_exceptions=True) so one failure doesn't sink the batch.import asyncio
import httpx
async def fetch_with_retry(client, url, max_retries=3):
for attempt in range(1, max_retries + 1):
try:
# asyncio.timeout cancels the call if it runs too long
async with asyncio.timeout(5):
response = await client.get(url)
response.raise_for_status()
return response.json()
except (asyncio.TimeoutError, httpx.HTTPError) as e:
if attempt == max_retries:
raise # last attempt, give up
# Exponential backoff: 0.5s, 1s, 2s, 4s...
backoff = 0.5 * (2 ** (attempt - 1))
print(f"Attempt {attempt} failed ({e}); retrying in {backoff}s")
await asyncio.sleep(backoff)
async def main():
async with httpx.AsyncClient() as client:
result = await fetch_with_retry(client, "https://api.example.com/data")
print(result)
asyncio.run(main())
async def main():
urls = ["https://api.example.com/user/1", ...]
async with httpx.AsyncClient() as client:
tasks = [fetch_with_retry(client, url) for url in urls]
# return_exceptions=True โ failures are returned as exception objects
# So one failure doesn't cancel the rest
results = await asyncio.gather(*tasks, return_exceptions=True)
# Process results
for url, result in zip(urls, results):
if isinstance(result, Exception):
print(f"FAILED {url}: {result}")
else:
print(f"SUCCESS {url}: {result}")
asyncio.timeout(seconds)
Context manager. Cancels the block if it runs longer than `seconds`. Raises asyncio.TimeoutError.
gather(..., return_exceptions=True)
One failure doesn't cancel others. Failed tasks return exception objects in the results list.