Initial commit
This commit is contained in:
202
.claude/skills/async-await-checker/SKILL.md
Normal file
202
.claude/skills/async-await-checker/SKILL.md
Normal file
@@ -0,0 +1,202 @@
|
||||
---
|
||||
name: async-await-checker
|
||||
description: Automatically applies when writing Python functions that call async operations. Ensures proper async/await pattern usage (not asyncio.run) to prevent event loop errors.
|
||||
---
|
||||
|
||||
# Async/Await Pattern Enforcer
|
||||
|
||||
When you are writing or modifying Python functions that:
|
||||
- Call any function with `async def`
|
||||
- Work with async I/O operations (database, HTTP, file I/O)
|
||||
- Need to run in an async context (FastAPI, async frameworks)
|
||||
|
||||
**Always apply these patterns:**
|
||||
|
||||
## ✅ Correct Pattern
|
||||
|
||||
```python
|
||||
# Helper function
|
||||
async def fetch_user_data(user_id: str) -> dict:
|
||||
"""Fetch user data from database."""
|
||||
result = await db.query(user_id) # ✅ Use await
|
||||
return result
|
||||
|
||||
# API endpoint (FastAPI)
|
||||
@app.get("/users/{user_id}")
|
||||
async def get_user(user_id: str) -> dict: # ✅ async def
|
||||
data = await fetch_user_data(user_id) # ✅ await
|
||||
return data
|
||||
|
||||
# Multiple async calls
|
||||
async def process_order(order_id: str) -> dict:
|
||||
# ✅ Run sequentially
|
||||
user = await fetch_user(order_id)
|
||||
payment = await process_payment(user.id)
|
||||
|
||||
# ✅ Run in parallel with asyncio.gather
|
||||
results = await asyncio.gather(
|
||||
send_email(user.email),
|
||||
update_inventory(order_id),
|
||||
log_transaction(payment.id)
|
||||
)
|
||||
|
||||
return {"status": "success"}
|
||||
```
|
||||
|
||||
## ❌ Incorrect Pattern (Causes Runtime Errors)
|
||||
|
||||
```python
|
||||
# ❌ Don't do this
|
||||
def fetch_user_data(user_id: str):
|
||||
result = asyncio.run(db.query(user_id)) # ❌ asyncio.run in event loop = error!
|
||||
return result
|
||||
|
||||
# ❌ Missing async
|
||||
@app.get("/users/{user_id}")
|
||||
def get_user(user_id: str): # ❌ Should be async def
|
||||
data = fetch_user_data(user_id) # ❌ Not awaiting
|
||||
return data
|
||||
|
||||
# ❌ Blocking in async function
|
||||
async def process_order(order_id: str):
|
||||
time.sleep(5) # ❌ Blocks event loop! Use asyncio.sleep(5)
|
||||
return await fetch_data()
|
||||
```
|
||||
|
||||
## Why This Matters
|
||||
|
||||
**Runtime Error:** `asyncio.run()` fails when called from within an already-running event loop.
|
||||
|
||||
**Solution:** Always use `async def` + `await` pattern.
|
||||
|
||||
**Performance:** Blocking operations in async functions defeat the purpose of async code.
|
||||
|
||||
## Common Async Operations
|
||||
|
||||
**Database queries:**
|
||||
```python
|
||||
async def get_records():
|
||||
async with db.session() as session:
|
||||
result = await session.execute(query)
|
||||
return result.fetchall()
|
||||
```
|
||||
|
||||
**HTTP requests:**
|
||||
```python
|
||||
import httpx
|
||||
|
||||
async def fetch_api_data(url: str):
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(url)
|
||||
return response.json()
|
||||
```
|
||||
|
||||
**File I/O:**
|
||||
```python
|
||||
import aiofiles
|
||||
|
||||
async def read_file(path: str):
|
||||
async with aiofiles.open(path, 'r') as f:
|
||||
content = await f.read()
|
||||
return content
|
||||
```
|
||||
|
||||
## Error Handling in Async
|
||||
|
||||
```python
|
||||
async def safe_api_call(url: str):
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(url, timeout=10.0)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except httpx.TimeoutException:
|
||||
raise TimeoutError(f"Request to {url} timed out")
|
||||
except httpx.HTTPStatusError as e:
|
||||
raise APIError(f"API error: {e.response.status_code}")
|
||||
```
|
||||
|
||||
## Testing Async Code
|
||||
|
||||
```python
|
||||
import pytest
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_user_data():
|
||||
"""Test async function"""
|
||||
result = await fetch_user_data("user_123")
|
||||
assert result["id"] == "user_123"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_with_mock():
|
||||
"""Test with mocked async dependency"""
|
||||
with patch('module.db.query') as mock_query:
|
||||
mock_query.return_value = {"id": "test"}
|
||||
result = await fetch_user_data("test")
|
||||
assert result["id"] == "test"
|
||||
```
|
||||
|
||||
## ❌ Anti-Patterns
|
||||
|
||||
```python
|
||||
# ❌ Mixing sync and async incorrectly
|
||||
def sync_function():
|
||||
return asyncio.run(async_function()) # Only OK at top level!
|
||||
|
||||
# ❌ Not using asyncio.gather for parallel operations
|
||||
async def slow_version():
|
||||
result1 = await operation1() # Waits
|
||||
result2 = await operation2() # Waits
|
||||
result3 = await operation3() # Waits
|
||||
return [result1, result2, result3]
|
||||
|
||||
# ✅ Better: parallel execution
|
||||
async def fast_version():
|
||||
results = await asyncio.gather(
|
||||
operation1(),
|
||||
operation2(),
|
||||
operation3()
|
||||
)
|
||||
return results
|
||||
|
||||
# ❌ Forgetting error handling in gather
|
||||
async def unsafe():
|
||||
await asyncio.gather(op1(), op2()) # If op1 fails, op2 continues
|
||||
|
||||
# ✅ Better: return exceptions
|
||||
async def safe():
|
||||
results = await asyncio.gather(
|
||||
op1(), op2(),
|
||||
return_exceptions=True
|
||||
)
|
||||
for result in results:
|
||||
if isinstance(result, Exception):
|
||||
handle_error(result)
|
||||
```
|
||||
|
||||
## Best Practices Checklist
|
||||
|
||||
- ✅ Use `async def` for any function that awaits
|
||||
- ✅ Use `await` for all async calls
|
||||
- ✅ Use `asyncio.gather()` for parallel operations
|
||||
- ✅ Use `async with` for async context managers
|
||||
- ✅ Use `asyncio.sleep()` instead of `time.sleep()`
|
||||
- ✅ Add proper error handling with try/except
|
||||
- ✅ Use `@pytest.mark.asyncio` for async tests
|
||||
- ✅ Consider timeouts for external operations
|
||||
- ✅ Use `return_exceptions=True` in gather when appropriate
|
||||
|
||||
## Auto-Apply
|
||||
|
||||
When you see code calling async functions, automatically:
|
||||
1. Make the calling function `async def`
|
||||
2. Use `await` for async calls
|
||||
3. Update callers to also be async (chain up)
|
||||
4. Add `@pytest.mark.asyncio` to tests
|
||||
5. Replace blocking calls with async equivalents
|
||||
|
||||
## Related Skills
|
||||
|
||||
- pytest-patterns - For testing async code
|
||||
- structured-errors - For async error handling
|
||||
- tool-design-pattern - For async tools
|
||||
Reference in New Issue
Block a user