@booklib/skills 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/animation-at-work/SKILL.md +246 -0
- package/animation-at-work/assets/example_asset.txt +1 -0
- package/animation-at-work/references/api_reference.md +369 -0
- package/animation-at-work/references/review-checklist.md +79 -0
- package/animation-at-work/scripts/example.py +1 -0
- package/bin/skills.js +85 -0
- package/clean-code-reviewer/SKILL.md +292 -0
- package/clean-code-reviewer/evals/evals.json +67 -0
- package/data-intensive-patterns/SKILL.md +204 -0
- package/data-intensive-patterns/assets/example_asset.txt +1 -0
- package/data-intensive-patterns/references/api_reference.md +34 -0
- package/data-intensive-patterns/references/patterns-catalog.md +551 -0
- package/data-intensive-patterns/references/review-checklist.md +193 -0
- package/data-intensive-patterns/scripts/example.py +1 -0
- package/data-pipelines/SKILL.md +252 -0
- package/data-pipelines/assets/example_asset.txt +1 -0
- package/data-pipelines/references/api_reference.md +301 -0
- package/data-pipelines/references/review-checklist.md +181 -0
- package/data-pipelines/scripts/example.py +1 -0
- package/design-patterns/SKILL.md +245 -0
- package/design-patterns/assets/example_asset.txt +1 -0
- package/design-patterns/references/api_reference.md +1 -0
- package/design-patterns/references/patterns-catalog.md +726 -0
- package/design-patterns/references/review-checklist.md +173 -0
- package/design-patterns/scripts/example.py +1 -0
- package/domain-driven-design/SKILL.md +221 -0
- package/domain-driven-design/assets/example_asset.txt +1 -0
- package/domain-driven-design/references/api_reference.md +1 -0
- package/domain-driven-design/references/patterns-catalog.md +545 -0
- package/domain-driven-design/references/review-checklist.md +158 -0
- package/domain-driven-design/scripts/example.py +1 -0
- package/effective-java/SKILL.md +195 -0
- package/effective-java/assets/example_asset.txt +1 -0
- package/effective-java/references/api_reference.md +1 -0
- package/effective-java/references/items-catalog.md +955 -0
- package/effective-java/references/review-checklist.md +216 -0
- package/effective-java/scripts/example.py +1 -0
- package/effective-kotlin/SKILL.md +225 -0
- package/effective-kotlin/assets/example_asset.txt +1 -0
- package/effective-kotlin/references/api_reference.md +1 -0
- package/effective-kotlin/references/practices-catalog.md +1228 -0
- package/effective-kotlin/references/review-checklist.md +126 -0
- package/effective-kotlin/scripts/example.py +1 -0
- package/kotlin-in-action/SKILL.md +251 -0
- package/kotlin-in-action/assets/example_asset.txt +1 -0
- package/kotlin-in-action/references/api_reference.md +1 -0
- package/kotlin-in-action/references/practices-catalog.md +436 -0
- package/kotlin-in-action/references/review-checklist.md +204 -0
- package/kotlin-in-action/scripts/example.py +1 -0
- package/lean-startup/SKILL.md +250 -0
- package/lean-startup/assets/example_asset.txt +1 -0
- package/lean-startup/references/api_reference.md +319 -0
- package/lean-startup/references/review-checklist.md +137 -0
- package/lean-startup/scripts/example.py +1 -0
- package/microservices-patterns/SKILL.md +179 -0
- package/microservices-patterns/references/patterns-catalog.md +391 -0
- package/microservices-patterns/references/review-checklist.md +169 -0
- package/package.json +17 -0
- package/refactoring-ui/SKILL.md +236 -0
- package/refactoring-ui/assets/example_asset.txt +1 -0
- package/refactoring-ui/references/api_reference.md +355 -0
- package/refactoring-ui/references/review-checklist.md +114 -0
- package/refactoring-ui/scripts/example.py +1 -0
- package/storytelling-with-data/SKILL.md +238 -0
- package/storytelling-with-data/assets/example_asset.txt +1 -0
- package/storytelling-with-data/references/api_reference.md +379 -0
- package/storytelling-with-data/references/review-checklist.md +111 -0
- package/storytelling-with-data/scripts/example.py +1 -0
- package/system-design-interview/SKILL.md +213 -0
- package/system-design-interview/assets/example_asset.txt +1 -0
- package/system-design-interview/references/api_reference.md +582 -0
- package/system-design-interview/references/review-checklist.md +201 -0
- package/system-design-interview/scripts/example.py +1 -0
- package/using-asyncio-python/SKILL.md +242 -0
- package/using-asyncio-python/assets/example_asset.txt +1 -0
- package/using-asyncio-python/references/api_reference.md +267 -0
- package/using-asyncio-python/references/review-checklist.md +149 -0
- package/using-asyncio-python/scripts/example.py +1 -0
- package/web-scraping-python/SKILL.md +259 -0
- package/web-scraping-python/assets/example_asset.txt +1 -0
- package/web-scraping-python/references/api_reference.md +393 -0
- package/web-scraping-python/references/review-checklist.md +163 -0
- package/web-scraping-python/scripts/example.py +1 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Using Asyncio in Python — Practices Catalog
|
|
2
|
+
|
|
3
|
+
Complete chapter-by-chapter catalog of practices from *Using Asyncio in Python*
|
|
4
|
+
by Caleb Hattingh.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Chapter 1: Introducing Asyncio
|
|
9
|
+
|
|
10
|
+
### What Asyncio Is
|
|
11
|
+
- **Single-threaded concurrency** — Asyncio uses a single thread with an event loop to handle many I/O operations concurrently
|
|
12
|
+
- **I/O-bound focus** — Designed for network programming, database access, file I/O — not CPU-bound computation
|
|
13
|
+
- **Cooperative multitasking** — Coroutines voluntarily yield control at await points; no preemptive switching
|
|
14
|
+
- **Event loop at the core** — The loop monitors I/O readiness and schedules coroutines to run when their I/O is ready
|
|
15
|
+
|
|
16
|
+
### When to Use Asyncio
|
|
17
|
+
- **Network servers** — Handle thousands of concurrent connections without thousands of threads
|
|
18
|
+
- **API clients** — Fetch from multiple endpoints concurrently without threading overhead
|
|
19
|
+
- **Database access** — Run multiple queries concurrently with async database drivers
|
|
20
|
+
- **Microservices** — Async is natural for services that call other services over HTTP/gRPC
|
|
21
|
+
- **NOT for CPU-bound work** — Use multiprocessing or ProcessPoolExecutor for computation
|
|
22
|
+
|
|
23
|
+
### Key Insight
|
|
24
|
+
- Asyncio makes concurrent I/O code look sequential — easier to read and reason about than callbacks or threads
|
|
25
|
+
- The main cost is that the entire ecosystem must be async-aware; mixing sync and async requires care
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Chapter 2: The Truth About Threads
|
|
30
|
+
|
|
31
|
+
### Thread Drawbacks
|
|
32
|
+
- **Race conditions** — Shared mutable state plus preemptive scheduling creates hard-to-find bugs
|
|
33
|
+
- **Resource consumption** — Each thread costs ~8MB of stack memory; thousands of threads are impractical
|
|
34
|
+
- **Difficult debugging** — Thread bugs are non-deterministic and may not reproduce
|
|
35
|
+
- **GIL limitation** — Python's Global Interpreter Lock means threads don't provide true parallelism for CPU-bound code
|
|
36
|
+
- **Complexity** — Locks, semaphores, and condition variables add complexity and potential deadlocks
|
|
37
|
+
|
|
38
|
+
### When Threads Are Still Useful
|
|
39
|
+
- **CPU-bound in executor** — ThreadPoolExecutor or ProcessPoolExecutor for blocking operations called from async code
|
|
40
|
+
- **Legacy library integration** — When async alternatives don't exist, wrap blocking calls in run_in_executor()
|
|
41
|
+
- **Simple scripts** — For quick scripts with few concurrent operations, threads may be simpler
|
|
42
|
+
|
|
43
|
+
### ThreadPoolExecutor Pattern
|
|
44
|
+
- Use `loop.run_in_executor(executor, blocking_func, *args)` to offload blocking calls
|
|
45
|
+
- Set max_workers to bound resource usage: `ThreadPoolExecutor(max_workers=5)`
|
|
46
|
+
- For CPU-bound work, prefer ProcessPoolExecutor over ThreadPoolExecutor
|
|
47
|
+
- Always shut down the executor on application exit
|
|
48
|
+
|
|
49
|
+
### The Case for Asyncio Over Threads
|
|
50
|
+
- **No race conditions by default** — Single-threaded means no shared state issues between await points
|
|
51
|
+
- **Lower resource usage** — Coroutines are lightweight; thousands cost almost nothing
|
|
52
|
+
- **Explicit yield points** — You know exactly where context switches happen (at every await)
|
|
53
|
+
- **Simpler reasoning** — Code between awaits runs atomically; no locks needed
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Chapter 3: Asyncio Walk-Through
|
|
58
|
+
|
|
59
|
+
### Quickstart: asyncio.run()
|
|
60
|
+
- **Entry point** — `asyncio.run(main())` is the recommended way to start async code (Python 3.7+)
|
|
61
|
+
- **Creates and destroys loop** — It creates a new event loop, runs the coroutine, then closes the loop
|
|
62
|
+
- **Call once at top level** — Don't call asyncio.run() from within async code; use create_task() instead
|
|
63
|
+
- **Handles cleanup** — Cancels remaining tasks and shuts down async generators on exit
|
|
64
|
+
|
|
65
|
+
### The Event Loop
|
|
66
|
+
- **Heart of asyncio** — Monitors I/O file descriptors and schedules callbacks/coroutines
|
|
67
|
+
- **loop.run_until_complete(coro)** — Runs a single coroutine to completion (low-level; prefer asyncio.run())
|
|
68
|
+
- **loop.run_forever()** — Runs the loop until stop() is called; useful for long-running servers
|
|
69
|
+
- **loop.stop()** — Stops the loop; typically called from a signal handler or callback
|
|
70
|
+
- **loop.close()** — Final cleanup; must be called after the loop is stopped
|
|
71
|
+
- **Debug mode** — `asyncio.run(main(), debug=True)` enables slow-callback warnings and extra checks
|
|
72
|
+
|
|
73
|
+
### Coroutines (async def / await)
|
|
74
|
+
- **async def** — Defines a coroutine function; calling it returns a coroutine object (doesn't execute it)
|
|
75
|
+
- **await** — Suspends the current coroutine until the awaited coroutine/future completes
|
|
76
|
+
- **Coroutine vs function** — A coroutine runs to the first await, then yields control back to the loop
|
|
77
|
+
- **Must be awaited** — A coroutine that is called but never awaited will never execute (common bug)
|
|
78
|
+
- **Chaining** — Coroutines can await other coroutines for composition
|
|
79
|
+
|
|
80
|
+
### Tasks
|
|
81
|
+
- **asyncio.create_task(coro)** — Wraps a coroutine in a Task and schedules it on the event loop (Python 3.7+)
|
|
82
|
+
- **Concurrent execution** — Tasks run concurrently; the event loop switches between them at await points
|
|
83
|
+
- **Task is a Future** — Tasks are a subclass of Future; you can await them and get results
|
|
84
|
+
- **Name tasks** — `create_task(coro, name="fetch-user")` for better debugging and logging
|
|
85
|
+
- **Keep references** — Store task references; orphaned tasks may silently swallow exceptions
|
|
86
|
+
- **task.cancel()** — Requests cancellation; raises CancelledError at the next await point in the task
|
|
87
|
+
- **task.result()** — Gets the result after the task completes; raises the exception if the task failed
|
|
88
|
+
|
|
89
|
+
### asyncio.ensure_future() vs create_task()
|
|
90
|
+
- **create_task()** — Preferred for coroutines; explicitly creates a Task
|
|
91
|
+
- **ensure_future()** — Accepts both coroutines and futures; wraps coroutines in Tasks
|
|
92
|
+
- **Recommendation** — Use create_task() for coroutines; ensure_future() only when handling generic awaitables
|
|
93
|
+
|
|
94
|
+
### Futures
|
|
95
|
+
- **Low-level construct** — Represents a result that will be available in the future
|
|
96
|
+
- **Rarely used directly** — Tasks and coroutines are higher-level and preferred
|
|
97
|
+
- **future.set_result(value)** — Sets the result; wakes up anyone awaiting the future
|
|
98
|
+
- **future.set_exception(exc)** — Sets an exception; awaiting the future will raise it
|
|
99
|
+
- **Callback based** — `future.add_done_callback(fn)` for callback-style programming
|
|
100
|
+
|
|
101
|
+
### gather() and wait()
|
|
102
|
+
- **asyncio.gather(*coros)** — Run multiple coroutines concurrently and collect all results
|
|
103
|
+
- Returns results in the same order as the input coroutines
|
|
104
|
+
- `return_exceptions=True` — Returns exceptions as results instead of raising; prevents one failure from cancelling all
|
|
105
|
+
- Without return_exceptions, first exception cancels remaining tasks
|
|
106
|
+
- **asyncio.wait(tasks, return_when=...)** — More flexible; returns (done, pending) sets
|
|
107
|
+
- `FIRST_COMPLETED` — Returns when any task finishes
|
|
108
|
+
- `FIRST_EXCEPTION` — Returns when any task raises an exception
|
|
109
|
+
- `ALL_COMPLETED` — Returns when all tasks complete (default)
|
|
110
|
+
- **asyncio.as_completed(coros)** — Iterator yielding futures as they complete; process results as they arrive
|
|
111
|
+
|
|
112
|
+
### Timeouts
|
|
113
|
+
- **asyncio.wait_for(coro, timeout=seconds)** — Cancels the coroutine if it exceeds the timeout
|
|
114
|
+
- **asyncio.timeout(seconds)** — Context manager for timeouts (Python 3.11+): `async with asyncio.timeout(5):`
|
|
115
|
+
- **Always set timeouts** — Network operations without timeouts can hang indefinitely
|
|
116
|
+
|
|
117
|
+
### Async Context Managers (async with)
|
|
118
|
+
- **__aenter__ / __aexit__** — Async versions of context manager protocols
|
|
119
|
+
- **Resource cleanup** — Ensures connections, sessions, and files are properly closed
|
|
120
|
+
- **aiohttp example** — `async with aiohttp.ClientSession() as session:` ensures session cleanup
|
|
121
|
+
- **Database pools** — `async with pool.acquire() as conn:` borrows and returns connections
|
|
122
|
+
- **@asynccontextmanager** — Decorator from contextlib for creating async context managers with yield
|
|
123
|
+
|
|
124
|
+
### Async Generators (async for)
|
|
125
|
+
- **async def with yield** — Async generator function; produces values asynchronously
|
|
126
|
+
- **async for** — Iterates over an async generator or any async iterable
|
|
127
|
+
- **Use cases** — Streaming data from network, paginated API results, database cursors
|
|
128
|
+
- **Cleanup** — Async generators are finalized when the loop shuts down (athrow GeneratorExit)
|
|
129
|
+
|
|
130
|
+
### Async Comprehensions
|
|
131
|
+
- **List** — `[x async for x in aiter]` — Async list comprehension
|
|
132
|
+
- **Set** — `{x async for x in aiter}` — Async set comprehension
|
|
133
|
+
- **Dict** — `{k: v async for k, v in aiter}` — Async dict comprehension
|
|
134
|
+
- **Filtering** — `[x async for x in aiter if await predicate(x)]`
|
|
135
|
+
- **Concise** — Combines async iteration with comprehension syntax
|
|
136
|
+
|
|
137
|
+
### Starting Up and Shutting Down
|
|
138
|
+
|
|
139
|
+
#### Startup Pattern
|
|
140
|
+
- Use asyncio.run() as the entry point
|
|
141
|
+
- Initialize resources (database pools, HTTP sessions) in the main coroutine
|
|
142
|
+
- Create long-running tasks with create_task()
|
|
143
|
+
- Use async context managers for resource lifecycle
|
|
144
|
+
|
|
145
|
+
#### Shutdown Pattern (Critical)
|
|
146
|
+
1. **Signal handling** — Register signal handlers for SIGTERM and SIGINT:
|
|
147
|
+
```
|
|
148
|
+
loop.add_signal_handler(signal.SIGTERM, handler)
|
|
149
|
+
loop.add_signal_handler(signal.SIGINT, handler)
|
|
150
|
+
```
|
|
151
|
+
2. **Cancel pending tasks** — Get all tasks and cancel them:
|
|
152
|
+
```
|
|
153
|
+
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
|
|
154
|
+
for task in tasks:
|
|
155
|
+
task.cancel()
|
|
156
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
157
|
+
```
|
|
158
|
+
3. **Shutdown async generators** — `await loop.shutdown_asyncgens()`
|
|
159
|
+
4. **Shutdown default executor** — `await loop.shutdown_default_executor()` (Python 3.9+)
|
|
160
|
+
5. **Close the loop** — `loop.close()`
|
|
161
|
+
|
|
162
|
+
#### Executor Integration
|
|
163
|
+
- **run_in_executor(executor, func, *args)** — Run blocking function in a thread/process pool
|
|
164
|
+
- **Default executor** — If executor is None, uses the default ThreadPoolExecutor
|
|
165
|
+
- **Custom executor** — Pass a custom ThreadPoolExecutor or ProcessPoolExecutor
|
|
166
|
+
- **Shutdown executor** — Call `executor.shutdown(wait=True)` or `loop.shutdown_default_executor()`
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Chapter 4: 20 Libraries You Aren't Using (But Oh, You Should)
|
|
171
|
+
|
|
172
|
+
### aiohttp — Async HTTP Client and Server
|
|
173
|
+
- **ClientSession** — Always use session for connection pooling: `async with aiohttp.ClientSession() as session:`
|
|
174
|
+
- **GET/POST** — `async with session.get(url) as resp:` — async context manager for response
|
|
175
|
+
- **Response methods** — `await resp.json()`, `await resp.text()`, `await resp.read()` for different formats
|
|
176
|
+
- **Server** — `aiohttp.web.Application()` for building async web servers
|
|
177
|
+
- **WebSockets** — Built-in WebSocket support for both client and server
|
|
178
|
+
- **Middleware** — Add middleware for logging, auth, error handling
|
|
179
|
+
- **Connection limits** — `TCPConnector(limit=100)` to control connection pool size
|
|
180
|
+
|
|
181
|
+
### aiofiles — Async File I/O
|
|
182
|
+
- **Non-blocking file ops** — `async with aiofiles.open('file.txt', 'r') as f:` — doesn't block the event loop
|
|
183
|
+
- **Same API as built-in open** — read(), write(), readline(), etc., all async
|
|
184
|
+
- **When to use** — When file I/O would block the event loop; especially in servers handling many requests
|
|
185
|
+
- **Under the hood** — Uses ThreadPoolExecutor internally; overhead is small but present
|
|
186
|
+
|
|
187
|
+
### Sanic — Async Web Framework
|
|
188
|
+
- **Flask-like API** — Familiar decorator-based routing: `@app.route('/')`
|
|
189
|
+
- **Async handlers** — Route handlers are async def; can use await freely
|
|
190
|
+
- **High performance** — Built on uvloop for faster event loop; designed for speed
|
|
191
|
+
- **Middleware** — Request and response middleware for cross-cutting concerns
|
|
192
|
+
- **WebSockets** — Built-in WebSocket support
|
|
193
|
+
|
|
194
|
+
### aioredis — Async Redis Client
|
|
195
|
+
- **Connection pooling** — `await aioredis.create_redis_pool('redis://localhost')`
|
|
196
|
+
- **Commands** — `await redis.get('key')`, `await redis.set('key', 'value')`
|
|
197
|
+
- **Pub/Sub** — Async pub/sub for real-time messaging: channel iteration with async for
|
|
198
|
+
- **Pipeline** — Batch multiple commands for efficiency
|
|
199
|
+
|
|
200
|
+
### asyncpg — Async PostgreSQL Client
|
|
201
|
+
- **High performance** — Pure Python async PostgreSQL driver; significantly faster than psycopg2
|
|
202
|
+
- **Connection pool** — `pool = await asyncpg.create_pool(dsn=...)`
|
|
203
|
+
- **Prepared statements** — `stmt = await conn.prepare('SELECT ...')` for repeated queries
|
|
204
|
+
- **Transactions** — `async with conn.transaction():` for atomic operations
|
|
205
|
+
- **Binary protocol** — Uses PostgreSQL binary protocol for better performance
|
|
206
|
+
|
|
207
|
+
### Other Notable Libraries
|
|
208
|
+
- **uvloop** — Drop-in replacement for asyncio event loop; 2-4x faster; `asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())`
|
|
209
|
+
- **aiodns** — Async DNS resolution
|
|
210
|
+
- **aiosmtplib** — Async SMTP client for sending emails
|
|
211
|
+
- **aiomysql** — Async MySQL client
|
|
212
|
+
- **motor** — Async MongoDB driver
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Chapter 5: Concluding Thoughts
|
|
217
|
+
|
|
218
|
+
### Key Takeaways
|
|
219
|
+
- Asyncio is the future of Python I/O-bound concurrency
|
|
220
|
+
- The ecosystem is maturing; most major libraries have async versions
|
|
221
|
+
- Start simple and add complexity as needed
|
|
222
|
+
- Graceful shutdown is the hardest part to get right; invest time in it
|
|
223
|
+
- Testing async code requires async-aware test frameworks (pytest-asyncio)
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Appendix A: A Short History of Async Support in Python
|
|
228
|
+
|
|
229
|
+
### Evolution Timeline
|
|
230
|
+
- **Generators (PEP 255)** — yield keyword enables lazy iteration
|
|
231
|
+
- **Generator-based coroutines (PEP 342)** — send(), throw(), close() turn generators into coroutines
|
|
232
|
+
- **yield from (PEP 380)** — Delegate to sub-generators; enables coroutine composition
|
|
233
|
+
- **asyncio module (PEP 3156)** — stdlib event loop and coroutine infrastructure (Python 3.4)
|
|
234
|
+
- **async/await syntax (PEP 492)** — Native coroutine syntax replaces @asyncio.coroutine and yield from (Python 3.5)
|
|
235
|
+
- **Async generators (PEP 525)** — async def with yield for async iteration (Python 3.6)
|
|
236
|
+
- **Async comprehensions (PEP 530)** — [x async for x in aiter] syntax (Python 3.6)
|
|
237
|
+
- **asyncio.run() (Python 3.7)** — Simplified entry point; no more manual loop management
|
|
238
|
+
- **TaskGroups (Python 3.11)** — Structured concurrency with automatic cancellation on failure
|
|
239
|
+
|
|
240
|
+
### Why This History Matters
|
|
241
|
+
- Understanding the evolution helps read older codebases that use yield from or @asyncio.coroutine
|
|
242
|
+
- Modern code should always use async/await syntax (Python 3.5+)
|
|
243
|
+
- asyncio.run() should always be the entry point (Python 3.7+)
|
|
244
|
+
- TaskGroups provide the safest concurrency model (Python 3.11+)
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Appendix B: Supplementary Material
|
|
249
|
+
|
|
250
|
+
### Debug Mode
|
|
251
|
+
- `asyncio.run(main(), debug=True)` — Enables extra checks
|
|
252
|
+
- Warns about coroutines that were never awaited
|
|
253
|
+
- Warns about callbacks that take too long (>100ms by default)
|
|
254
|
+
- Logs all exceptions in callbacks
|
|
255
|
+
|
|
256
|
+
### Common Patterns Summary
|
|
257
|
+
|
|
258
|
+
| Pattern | Implementation |
|
|
259
|
+
|---------|---------------|
|
|
260
|
+
| Concurrent fan-out | `results = await asyncio.gather(*tasks, return_exceptions=True)` |
|
|
261
|
+
| Rate limiting | `sem = asyncio.Semaphore(10); async with sem: await work()` |
|
|
262
|
+
| Timeout | `await asyncio.wait_for(coro, timeout=5.0)` |
|
|
263
|
+
| Producer-consumer | `queue = asyncio.Queue(); create_task(producer); create_task(consumer)` |
|
|
264
|
+
| Blocking integration | `await loop.run_in_executor(executor, blocking_func)` |
|
|
265
|
+
| Graceful shutdown | Signal handler → cancel tasks → gather with return_exceptions → close |
|
|
266
|
+
| Connection pool | `async with aiohttp.ClientSession() as session:` |
|
|
267
|
+
| Retry with backoff | try/except in loop with asyncio.sleep(delay * 2**attempt) |
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Using Asyncio in Python — Async Code Review Checklist
|
|
2
|
+
|
|
3
|
+
Systematic checklist for reviewing async Python code against the chapters
|
|
4
|
+
from *Using Asyncio in Python* by Caleb Hattingh.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Concurrency Model (Chapters 1–2)
|
|
9
|
+
|
|
10
|
+
### Workload Fit
|
|
11
|
+
- [ ] **Ch 1 — Asyncio appropriateness** — Is asyncio used for I/O-bound work? Is CPU-bound work offloaded to executors?
|
|
12
|
+
- [ ] **Ch 1 — Single-threaded awareness** — Does the code respect that asyncio runs on a single thread?
|
|
13
|
+
- [ ] **Ch 2 — Thread avoidance** — Are threads avoided where asyncio coroutines would suffice?
|
|
14
|
+
- [ ] **Ch 2 — Executor usage** — Is run_in_executor() used for blocking calls instead of calling them directly?
|
|
15
|
+
|
|
16
|
+
### Thread Integration
|
|
17
|
+
- [ ] **Ch 2 — ThreadPoolExecutor bounds** — Is max_workers set to prevent unbounded thread creation?
|
|
18
|
+
- [ ] **Ch 2 — ProcessPoolExecutor for CPU** — Is ProcessPoolExecutor used instead of ThreadPoolExecutor for CPU-bound work?
|
|
19
|
+
- [ ] **Ch 2 — Executor shutdown** — Is the executor properly shut down on application exit?
|
|
20
|
+
- [ ] **Ch 2 — No shared mutable state** — Is shared state between threads protected or avoided?
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 2. Coroutines & Tasks (Chapter 3)
|
|
25
|
+
|
|
26
|
+
### Coroutine Basics
|
|
27
|
+
- [ ] **Ch 3 — async def/await** — Are coroutines properly defined with async def and awaited?
|
|
28
|
+
- [ ] **Ch 3 — No unawaited coroutines** — Are all coroutine calls awaited or wrapped in create_task()?
|
|
29
|
+
- [ ] **Ch 3 — asyncio.run() entry** — Is asyncio.run() used as the entry point instead of manual loop management?
|
|
30
|
+
- [ ] **Ch 3 — No nested asyncio.run()** — Is asyncio.run() never called from within async code?
|
|
31
|
+
|
|
32
|
+
### Task Management
|
|
33
|
+
- [ ] **Ch 3 — create_task() usage** — Is create_task() used instead of ensure_future() for coroutines?
|
|
34
|
+
- [ ] **Ch 3 — Task references kept** — Are created tasks stored in variables or collections to prevent GC and silent exceptions?
|
|
35
|
+
- [ ] **Ch 3 — Named tasks** — Are tasks named for debugging: `create_task(coro, name="descriptive-name")`?
|
|
36
|
+
- [ ] **Ch 3 — Concurrent when possible** — Are independent I/O operations run concurrently via gather() or create_task(), not sequentially?
|
|
37
|
+
|
|
38
|
+
### gather() and wait()
|
|
39
|
+
- [ ] **Ch 3 — return_exceptions=True** — Is gather() called with return_exceptions=True to prevent one failure from cancelling all?
|
|
40
|
+
- [ ] **Ch 3 — Result checking** — Are gather() results checked for exceptions when return_exceptions=True?
|
|
41
|
+
- [ ] **Ch 3 — wait() for flexibility** — Is asyncio.wait() used when FIRST_COMPLETED or FIRST_EXCEPTION semantics are needed?
|
|
42
|
+
- [ ] **Ch 3 — as_completed() for streaming** — Is as_completed() used when results should be processed as they arrive?
|
|
43
|
+
|
|
44
|
+
### Cancellation & Timeouts
|
|
45
|
+
- [ ] **Ch 3 — CancelledError handling** — Is CancelledError caught in coroutines for cleanup, then re-raised or allowed to propagate?
|
|
46
|
+
- [ ] **Ch 3 — Timeouts set** — Are asyncio.wait_for() or asyncio.timeout() used for operations that could hang?
|
|
47
|
+
- [ ] **Ch 3 — Cancellation propagation** — Does task.cancel() properly propagate through the task tree?
|
|
48
|
+
- [ ] **Ch 3 — No bare except catching CancelledError** — Does `except Exception` not accidentally catch CancelledError (Python <3.9)?
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 3. Async Patterns (Chapter 3)
|
|
53
|
+
|
|
54
|
+
### Async Context Managers
|
|
55
|
+
- [ ] **Ch 3 — async with for resources** — Are connections, sessions, files, and pools managed with async with?
|
|
56
|
+
- [ ] **Ch 3 — __aenter__/__aexit__** — Do custom async context managers implement proper cleanup in __aexit__?
|
|
57
|
+
- [ ] **Ch 3 — @asynccontextmanager** — Is the contextlib decorator used for simple async context managers?
|
|
58
|
+
|
|
59
|
+
### Async Iteration
|
|
60
|
+
- [ ] **Ch 3 — async for usage** — Is async for used for iterating over async generators and streams?
|
|
61
|
+
- [ ] **Ch 3 — Async generator cleanup** — Are async generators properly finalized on shutdown?
|
|
62
|
+
- [ ] **Ch 3 — Async comprehensions** — Are async comprehensions used for concise async collection building?
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 4. Startup & Shutdown (Chapter 3)
|
|
67
|
+
|
|
68
|
+
### Startup
|
|
69
|
+
- [ ] **Ch 3 — Resource initialization** — Are database pools, HTTP sessions, and connections created in the main coroutine?
|
|
70
|
+
- [ ] **Ch 3 — Task creation** — Are long-running tasks created with create_task() after resource initialization?
|
|
71
|
+
- [ ] **Ch 3 — Context manager lifecycle** — Are resources wrapped in async with to ensure cleanup?
|
|
72
|
+
|
|
73
|
+
### Shutdown (Critical)
|
|
74
|
+
- [ ] **Ch 3 — Signal handling** — Are SIGTERM and SIGINT handlers registered with loop.add_signal_handler()?
|
|
75
|
+
- [ ] **Ch 3 — Task cancellation** — Are all pending tasks cancelled on shutdown?
|
|
76
|
+
- [ ] **Ch 3 — Cancellation awaited** — Is gather(*tasks, return_exceptions=True) used to wait for cancelled tasks?
|
|
77
|
+
- [ ] **Ch 3 — Async generator shutdown** — Is loop.shutdown_asyncgens() called?
|
|
78
|
+
- [ ] **Ch 3 — Executor shutdown** — Is loop.shutdown_default_executor() called (Python 3.9+)?
|
|
79
|
+
- [ ] **Ch 3 — Loop closed** — Is loop.close() called as the final step?
|
|
80
|
+
- [ ] **Ch 3 — No resource leaks** — Are all connections, sessions, and file handles closed on shutdown?
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 5. Blocking Prevention (Chapters 2–3)
|
|
85
|
+
|
|
86
|
+
### Event Loop Protection
|
|
87
|
+
- [ ] **Ch 2 — No time.sleep()** — Is asyncio.sleep() used instead of time.sleep()?
|
|
88
|
+
- [ ] **Ch 2 — No blocking HTTP** — Is aiohttp used instead of requests/urllib?
|
|
89
|
+
- [ ] **Ch 2 — No blocking file I/O** — Is aiofiles or run_in_executor() used instead of open()/read()/write()?
|
|
90
|
+
- [ ] **Ch 2 — No blocking database** — Are async database drivers (asyncpg, aiomysql) used instead of sync ones?
|
|
91
|
+
- [ ] **Ch 3 — Executor for legacy** — Is run_in_executor() used for any unavoidable blocking calls?
|
|
92
|
+
- [ ] **Ch 3 — Debug mode testing** — Has the code been tested with asyncio debug mode to detect slow callbacks?
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 6. Library Usage (Chapter 4)
|
|
97
|
+
|
|
98
|
+
### aiohttp
|
|
99
|
+
- [ ] **Ch 4 — ClientSession reuse** — Is a single ClientSession reused across requests, not created per-request?
|
|
100
|
+
- [ ] **Ch 4 — Session as context manager** — Is ClientSession used with async with for proper cleanup?
|
|
101
|
+
- [ ] **Ch 4 — Connection limits** — Is TCPConnector configured with appropriate connection limits?
|
|
102
|
+
- [ ] **Ch 4 — Response consumed** — Are response bodies read (json/text/read) before the response context exits?
|
|
103
|
+
|
|
104
|
+
### aiofiles
|
|
105
|
+
- [ ] **Ch 4 — Async file ops** — Is aiofiles used for file I/O in async contexts?
|
|
106
|
+
- [ ] **Ch 4 — Context manager** — Are file handles managed with async with?
|
|
107
|
+
|
|
108
|
+
### Database Clients
|
|
109
|
+
- [ ] **Ch 4 — Connection pooling** — Are database connections pooled (asyncpg.create_pool, etc.)?
|
|
110
|
+
- [ ] **Ch 4 — Pool cleanup** — Is the connection pool closed on shutdown?
|
|
111
|
+
- [ ] **Ch 4 — Transaction management** — Are transactions used with async with for atomicity?
|
|
112
|
+
- [ ] **Ch 4 — Prepared statements** — Are prepared statements used for repeated queries?
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 7. Error Handling & Resilience
|
|
117
|
+
|
|
118
|
+
### Exception Management
|
|
119
|
+
- [ ] **Ch 3 — Per-task error handling** — Does each task have its own try/except for error isolation?
|
|
120
|
+
- [ ] **Ch 3 — Exception logging** — Are task exceptions logged, not silently swallowed?
|
|
121
|
+
- [ ] **Ch 3 — Retry logic** — Are transient errors (network, timeout) retried with exponential backoff?
|
|
122
|
+
- [ ] **Ch 3 — Graceful degradation** — Does one task's failure not crash the entire application?
|
|
123
|
+
|
|
124
|
+
### Concurrency Limits
|
|
125
|
+
- [ ] **Ch 3 — Semaphore usage** — Is asyncio.Semaphore used to limit concurrent operations?
|
|
126
|
+
- [ ] **Ch 3 — Queue backpressure** — Is asyncio.Queue with maxsize used for producer-consumer backpressure?
|
|
127
|
+
- [ ] **Ch 4 — Connection pool limits** — Are HTTP/database connection pools properly sized?
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Quick Review Workflow
|
|
132
|
+
|
|
133
|
+
1. **Concurrency pass** — Verify asyncio is the right choice; check thread/executor integration
|
|
134
|
+
2. **Coroutine pass** — Check async def/await correctness, task creation, gather/wait patterns
|
|
135
|
+
3. **Resource pass** — Verify async context managers, session management, connection pooling
|
|
136
|
+
4. **Shutdown pass** — Check signal handling, task cancellation, cleanup, executor shutdown
|
|
137
|
+
5. **Blocking pass** — Scan for any blocking calls on the event loop; verify executor usage
|
|
138
|
+
6. **Library pass** — Check correct usage of aiohttp, aiofiles, asyncpg, etc.
|
|
139
|
+
7. **Error pass** — Verify cancellation handling, timeouts, retry logic, error isolation
|
|
140
|
+
8. **Prioritize findings** — Rank by severity: blocking event loop > resource leaks > missing cancellation > missing timeouts > best practices
|
|
141
|
+
|
|
142
|
+
## Severity Levels
|
|
143
|
+
|
|
144
|
+
| Severity | Description | Example |
|
|
145
|
+
|----------|-------------|---------|
|
|
146
|
+
| **Critical** | Blocks event loop or causes resource leaks | Calling requests.get() or time.sleep() directly, never closing sessions, no shutdown handling |
|
|
147
|
+
| **High** | Reliability or correctness issues | Missing CancelledError handling, no timeouts, unawaited coroutines, fire-and-forget tasks |
|
|
148
|
+
| **Medium** | Performance or maintainability gaps | Sequential awaits when concurrent possible, no connection pooling, no semaphore limiting, no logging |
|
|
149
|
+
| **Low** | Best practice improvements | Missing task names, no debug mode testing, ensure_future instead of create_task, no async comprehensions |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|