| name | python-async |
| description | Python asyncio patterns: async/await, concurrent tasks, aiohttp, timeouts, queues, background tasks, common pitfalls |
Python Async Skill
When to activate
- Writing async Python code with
asyncio
- Running I/O operations concurrently (multiple API calls, DB queries)
- Using
aiohttp for async HTTP requests
- Managing background tasks in FastAPI or other async frameworks
- Debugging async code (event loop issues, deadlocks, blocking calls)
- Setting up async task queues or worker patterns
When NOT to use
- CPU-bound work — async doesn't help here, use
multiprocessing or ProcessPoolExecutor
- Simple scripts with one or two sequential I/O calls —
requests is simpler
- When the library you need only has a sync interface — wrap it instead
Instructions
Core patterns
import asyncio
async def fetch_user(user_id: int) -> dict:
await asyncio.sleep(0.1)
return {"id": user_id, "name": "Alice"}
user = asyncio.run(fetch_user(1))
async def main():
user = await fetch_user(1)
print(user)
Concurrent execution — the key skill
async def fetch_all_sequential(ids: list[int]) -> list[dict]:
results = []
for id in ids:
results.append(await fetch_user(id))
return results
async def fetch_all_concurrent(ids: list[int]) -> list[dict]:
tasks = [fetch_user(id) for id in ids]
return await asyncio.gather(*tasks)
results = await asyncio.gather(*tasks, return_exceptions=True)
users = [r for r in results if not isinstance(r, Exception)]
errors = [r for r in results if isinstance(r, Exception)]
TaskGroup (Python 3.11+ — structured concurrency)
async def fetch_dashboard(user_id: str) -> dict:
async with asyncio.TaskGroup() as tg:
user_task = tg.create_task(fetch_user(user_id))
orders_task = tg.create_task(fetch_orders(user_id))
metrics_task = tg.create_task(fetch_metrics(user_id))
return {
"user": user_task.result(),
"orders": orders_task.result(),
"metrics": metrics_task.result(),
}
Timeouts
import asyncio
try:
result = await asyncio.wait_for(fetch_user(id), timeout=5.0)
except asyncio.TimeoutError:
raise HTTPException(504, "Upstream timed out")
try:
async with asyncio.timeout(10.0):
async with asyncio.TaskGroup() as tg:
t1 = tg.create_task(call_service_a())
t2 = tg.create_task(call_service_b())
except TimeoutError:
...
aiohttp — async HTTP client
import aiohttp
class HttpClient:
_session: aiohttp.ClientSession | None = None
@classmethod
async def get_session(cls) -> aiohttp.ClientSession:
if cls._session is None or cls._session.closed:
cls._session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=10),
headers={"User-Agent": "myapp/1.0"},
)
return cls._session
@classmethod
async def close(cls):
if cls._session:
await cls._session.close()
async def fetch_github_user(username: str) -> dict:
session = await HttpClient.get_session()
async with session.get(f"https://api.github.com/users/{username}") as resp:
resp.raise_for_status()
return await resp.json()
async def fetch_many(urls: list[]) -> []:
session = HttpClient.get_session()
() -> :
session.get(url) resp:
resp.raise_for_status()
resp.json()
asyncio.gather(*[fetch(url) url urls])
Background tasks in FastAPI
from fastapi import BackgroundTasks
@router.post("/orders")
async def create_order(
payload: CreateOrderRequest,
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db),
):
order = await order_service.create(db, payload)
background_tasks.add_task(send_confirmation_email, order.id)
background_tasks.add_task(update_inventory, order.items)
return order
Async queues for producer-consumer
import asyncio
async def producer(queue: asyncio.Queue, items: list):
for item in items:
await queue.put(item)
await queue.put(None)
async def consumer(queue: asyncio.Queue, worker_id: int):
while True:
item = await queue.get()
if item is None:
queue.task_done()
break
await process(item)
queue.task_done()
async def main():
queue = asyncio.Queue(maxsize=100)
items = list(range(1000))
workers = [asyncio.create_task(consumer(queue, i)) for i in range(5)]
await producer(queue, items)
await queue.join()
for w in workers:
w.cancel()
Semaphore — limit concurrency
semaphore = asyncio.Semaphore(10)
async def fetch_with_limit(url: str) -> dict:
async with semaphore:
async with session.get(url) as resp:
return await resp.json()
results = await asyncio.gather(*[fetch_with_limit(url) for url in urls])
Common pitfalls
Blocking the event loop (the #1 async mistake):
async def do_work():
time.sleep(1)
requests.get(url)
async def do_work():
await asyncio.sleep(1)
async with session.get(url):
...
result = await asyncio.get_event_loop().run_in_executor(
None, sync_blocking_function, arg1, arg2
)
Forgetting to await:
async def bad():
result = fetch_user(1)
async def good():
result = await fetch_user(1)
Creating tasks without awaiting them:
async def bad():
asyncio.create_task(risky_operation())
async def good():
task = asyncio.create_task(risky_operation())
task.add_done_callback(lambda t: t.exception() and log_error(t.exception()))
Async context managers
class AsyncDatabaseConnection:
async def __aenter__(self):
self.conn = await asyncpg.connect(DATABASE_URL)
return self.conn
async def __aexit__(self, exc_type, exc, tb):
await self.conn.close()
async def main():
async with AsyncDatabaseConnection() as conn:
await conn.fetch("SELECT 1")
async def stream_rows(query: str):
async with AsyncDatabaseConnection() as conn:
async for row in conn.cursor(query):
yield dict(row)
async def process():
async for row in stream_rows("SELECT * FROM large_table"):
await handle(row)
Example
User: Fetch data from 3 external APIs concurrently with individual timeouts, retry failed requests once, and return partial results if some fail — all within a 10-second total budget.
Expected output:
async def fetch_dashboard(user_id: str) -> DashboardData:
async with asyncio.timeout(10.0):
async with asyncio.TaskGroup() as tg:
profile_task = tg.create_task(
fetch_with_retry(f"/profiles/{user_id}", timeout=3)
)
orders_task = tg.create_task(
fetch_with_retry(f"/orders/{user_id}", timeout=5)
)
metrics_task = tg.create_task(
fetch_with_retry(f"/metrics/{user_id}", timeout=3)
)
return DashboardData(
profile=profile_task.result(),
orders=orders_task.result(),
metrics=metrics_task.result(),
)