Created
March 14, 2026 23:32
-
-
Save mohashari/36de1600ab15c5de232d0d66eac2979a to your computer and use it in GitHub Desktop.
Python Async Programming: asyncio Patterns for Backend Engineers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import asyncio | |
| import httpx | |
| async def fetch_user(client: httpx.AsyncClient, user_id: int) -> dict: | |
| response = await client.get(f"https://api.internal/users/{user_id}") | |
| response.raise_for_status() | |
| return response.json() | |
| async def fetch_permissions(client: httpx.AsyncClient, user_id: int) -> list: | |
| response = await client.get(f"https://api.internal/permissions/{user_id}") | |
| response.raise_for_status() | |
| return response.json() | |
| async def fetch_activity(client: httpx.AsyncClient, user_id: int) -> list: | |
| response = await client.get(f"https://api.internal/activity/{user_id}") | |
| response.raise_for_status() | |
| return response.json() | |
| async def build_user_context(user_id: int) -> dict: | |
| async with httpx.AsyncClient(timeout=5.0) as client: | |
| user, permissions, activity = await asyncio.gather( | |
| fetch_user(client, user_id), | |
| fetch_permissions(client, user_id), | |
| fetch_activity(client, user_id), | |
| return_exceptions=True, # don't cancel siblings on one failure | |
| ) | |
| if isinstance(user, Exception): | |
| raise RuntimeError(f"Failed to fetch user: {user}") | |
| return { | |
| "user": user, | |
| "permissions": permissions if not isinstance(permissions, Exception) else [], | |
| "activity": activity if not isinstance(activity, Exception) else [], | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import asyncio | |
| import httpx | |
| async def fetch_with_limit( | |
| client: httpx.AsyncClient, | |
| sem: asyncio.Semaphore, | |
| url: str, | |
| ) -> dict: | |
| async with sem: # blocks here if semaphore count is exhausted | |
| response = await client.get(url) | |
| return response.json() | |
| async def batch_fetch(urls: list[str], max_concurrency: int = 20) -> list[dict]: | |
| sem = asyncio.Semaphore(max_concurrency) | |
| async with httpx.AsyncClient() as client: | |
| tasks = [fetch_with_limit(client, sem, url) for url in urls] | |
| return await asyncio.gather(*tasks, return_exceptions=True) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import asyncio | |
| async def process_event(event: dict) -> None: | |
| # simulate processing | |
| await asyncio.sleep(0.1) | |
| if event.get("poison"): | |
| raise ValueError(f"Poison event: {event['id']}") | |
| async def handle_batch(events: list[dict]) -> None: | |
| async with asyncio.TaskGroup() as tg: | |
| for event in events: | |
| tg.create_task(process_event(event)) | |
| # if any task raises, all others are cancelled and the exception propagates here |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import asyncio | |
| import asyncpg | |
| async def get_user(pool: asyncpg.Pool, user_id: int) -> dict | None: | |
| async with pool.acquire() as conn: | |
| row = await conn.fetchrow( | |
| "SELECT id, email, created_at FROM users WHERE id = $1", | |
| user_id, | |
| ) | |
| return dict(row) if row else None | |
| async def main(): | |
| pool = await asyncpg.create_pool( | |
| dsn="postgresql://user:pass@localhost/mydb", | |
| min_size=5, | |
| max_size=20, | |
| ) | |
| try: | |
| user = await get_user(pool, 42) | |
| print(user) | |
| finally: | |
| await pool.close() | |
| asyncio.run(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import asyncio | |
| import hashlib | |
| from concurrent.futures import ProcessPoolExecutor | |
| def compute_hash(data: bytes) -> str: | |
| # CPU-intensive — would block the event loop if called directly | |
| return hashlib.sha256(data).hexdigest() | |
| async def hash_file(path: str) -> str: | |
| loop = asyncio.get_running_loop() | |
| with open(path, "rb") as f: | |
| data = f.read() | |
| # offload to process pool to bypass the GIL | |
| with ProcessPoolExecutor() as pool: | |
| return await loop.run_in_executor(pool, compute_hash, data) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import asyncio | |
| import httpx | |
| async def fetch_with_timeout(url: str, timeout_sec: float = 3.0) -> dict: | |
| async with httpx.AsyncClient() as client: | |
| try: | |
| return await asyncio.wait_for( | |
| client.get(url), # this is a coroutine | |
| timeout=timeout_sec, | |
| ) | |
| except asyncio.TimeoutError: | |
| raise RuntimeError(f"Request to {url} timed out after {timeout_sec}s") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import asyncio | |
| import time | |
| async def bad_example(): | |
| # This blocks the event loop for 2 seconds — nothing else runs | |
| time.sleep(2) | |
| async def good_example(): | |
| # This yields control; other coroutines run during the wait | |
| await asyncio.sleep(2) | |
| async def main(): | |
| await asyncio.gather(good_example(), good_example(), good_example()) | |
| asyncio.run(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment