Phase 1 — Foundations · Lesson 9 · 40 XP
Project: concurrent fetcher with retries, timeouts, validation
This is the Phase 1 project from the roadmap — it doesn't introduce new syntax, it combines Lessons 6-8 into one real piece of software: concurrent HTTP calls (async httpx), that don't hang forever (timeouts), that recover from transient failures (retries with backoff), and that never trust what came back without checking (Pydantic validation).
A timeout caps how long any single request can block — without one, a single unresponsive server can stall your whole batch indefinitely. A retry-with-backoff waits a little longer after each failure (1s, then 2s, then 4s) instead of hammering an already-struggling server immediately, which is more likely to make things worse, not better.
async def fetch_with_retry(client, url, attempts=3):
for attempt in range(attempts):
try:
r = await client.get(url, timeout=5.0)
r.raise_for_status()
return r.json()
except (httpx.HTTPError, httpx.TimeoutException):
if attempt == attempts - 1:
raise
await asyncio.sleep(2 ** attempt)Exercise
Build the full project: fetch 50 URLs concurrently with httpx.AsyncClient, a 5-second timeout per request, up to 2 retries with exponential backoff on failure, and Pydantic validation of each JSON response — then print a summary of successes, retried-then-succeeded, and hard failures.
Check yourself
1. Why add a timeout to every request instead of letting httpx wait indefinitely for a response?
2. What's the risk of retrying a failed request immediately, with no backoff delay, against a server that's already struggling?
Pydantic: validating data
Answer the check-yourself questions to unlock this