← Back to Python series
🚀
Advanced
asyncio · aiohttp · Typed API · pytest · packaging

Week 10 — Advanced Capstone: Async REST Client

Combine the full advanced track into a production-grade async REST API client library, complete with type hints, retry logic, caching, pytest test suite, and a pip-installable package.

capstoneasyncioaiohttptypingpytestpackage
Duration
4 hours
Level
📊 Advanced
Prerequisite
🎯 Advanced Weeks 1–9
OUTCOME
A fully typed, tested, and packaged async HTTP client library

What you'll learn

  • 1Design a clean async client API with type hints
  • 2Implement retry, timeout, and rate-limiting middleware
  • 3Cache responses with TTL using asyncio
  • 4Write 90%+ coverage tests with pytest-asyncio
  • 5Package and install as a distributable library

1. Project Specification

python
# Usage example
from apiclient import AsyncClient, RetryPolicy

async def main():
    policy = RetryPolicy(max_retries=3, backoff=2.0)
    async with AsyncClient("https://api.example.com", retry=policy) as client:
        users = await client.get("/users", params={"page": 1})
        post  = await client.post("/posts", json={"title": "Hello"})
        print(users, post)

2. Architecture

  • AsyncClient — main client class with session management
  • RetryPolicy dataclass — max_retries, backoff, retry_on (status codes)
  • ResponseCache — asyncio-based TTL cache
  • Middleware protocol — pluggable request/response hooks
  • Exceptions — APIError, RateLimitError, TimeoutError

3. Key Implementation Points

python
import asyncio, aiohttp
from dataclasses import dataclass, field
from typing import Any, Optional

@dataclass
class RetryPolicy:
    max_retries: int = 3
    backoff: float = 1.0
    retry_on: tuple = (429, 500, 502, 503)

async def _request_with_retry(
    session: aiohttp.ClientSession,
    method: str,
    url: str,
    policy: RetryPolicy,
    **kwargs: Any,
) -> aiohttp.ClientResponse:
    last_exc: Optional[Exception] = None
    for attempt in range(policy.max_retries + 1):
        try:
            resp = await session.request(method, url, **kwargs)
            if resp.status in policy.retry_on and attempt < policy.max_retries:
                await asyncio.sleep(policy.backoff * (2 ** attempt))
                continue
            return resp
        except aiohttp.ClientError as e:
            last_exc = e
            if attempt < policy.max_retries:
                await asyncio.sleep(policy.backoff * (2 ** attempt))
    raise last_exc or RuntimeError("Request failed")

📝 Exercises

Try them yourself first, then open the solution to compare.

Exercise 1

Implement the client

Goal: Build the async REST client library described above.

Requirements
  • AsyncClient as async context manager
  • RetryPolicy with exponential backoff
  • Response cache with TTL
  • pytest-asyncio test suite (90%+ coverage)
  • Installable with pip (pyproject.toml)
Grading
  • · Core client works — 30%
  • · Retry logic correct — 25%
  • · Cache implementation — 20%
  • · Tests 90%+ coverage — 25%
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub ↗