python-skills 1.0.0__py3-none-any.whl
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.
- python_skills/__init__.py +10 -0
- python_skills/__main__.py +6 -0
- python_skills/adapters/__init__.py +48 -0
- python_skills/adapters/agent_skills.py +415 -0
- python_skills/adapters/aider_adapter.py +226 -0
- python_skills/adapters/base.py +153 -0
- python_skills/adapters/claude.py +474 -0
- python_skills/adapters/cline.py +332 -0
- python_skills/adapters/codex.py +24 -0
- python_skills/adapters/continue_adapter.py +198 -0
- python_skills/adapters/cursor.py +327 -0
- python_skills/adapters/gemini.py +26 -0
- python_skills/adapters/goose.py +26 -0
- python_skills/adapters/junie.py +25 -0
- python_skills/adapters/kiro.py +382 -0
- python_skills/adapters/opencode.py +27 -0
- python_skills/adapters/roo.py +25 -0
- python_skills/adapters/universal.py +203 -0
- python_skills/adapters/vscode.py +27 -0
- python_skills/adapters/windsurf.py +26 -0
- python_skills/adapters/zed.py +27 -0
- python_skills/cli.py +326 -0
- python_skills/config.py +160 -0
- python_skills/detector.py +152 -0
- python_skills/installer.py +163 -0
- python_skills/markers.py +115 -0
- python_skills/skills/__init__.py +14 -0
- python_skills/skills/loader.py +171 -0
- python_skills/skills/metadata.py +152 -0
- python_skills/skills/registry.py +101 -0
- python_skills/state.py +204 -0
- python_skills-1.0.0.dist-info/METADATA +99 -0
- python_skills-1.0.0.dist-info/RECORD +105 -0
- python_skills-1.0.0.dist-info/WHEEL +4 -0
- python_skills-1.0.0.dist-info/entry_points.txt +2 -0
- python_skills-1.0.0.dist-info/licenses/LICENSE +21 -0
- skills/advanced_python.md +239 -0
- skills/anti_patterns/index.md +406 -0
- skills/comprehensions.md +167 -0
- skills/control_flow.md +175 -0
- skills/data_structures.md +243 -0
- skills/debugging/common_bugs.md +222 -0
- skills/debugging/inspection_techniques.md +249 -0
- skills/debugging/root_cause.md +203 -0
- skills/engineering/application_logging.md +195 -0
- skills/engineering/cli_apps.md +207 -0
- skills/engineering/configuration.md +218 -0
- skills/engineering/database.md +240 -0
- skills/engineering/dependency_management.md +205 -0
- skills/engineering/http_clients.md +267 -0
- skills/engineering/modules_packages.md +211 -0
- skills/engineering/packaging.md +197 -0
- skills/engineering/project_structure.md +155 -0
- skills/engineering/pyproject_toml.md +302 -0
- skills/engineering/virtual_environments.md +206 -0
- skills/functions.md +244 -0
- skills/generation/async_concurrency.md +291 -0
- skills/generation/error_handling.md +276 -0
- skills/generation/protocols_generics.md +243 -0
- skills/generation/type_hints.md +290 -0
- skills/generation/validation_pipeline.md +274 -0
- skills/generation/workflow.md +190 -0
- skills/oop.md +228 -0
- skills/quality/abstractions.md +154 -0
- skills/quality/comments.md +177 -0
- skills/quality/documentation.md +176 -0
- skills/quality/duplication.md +137 -0
- skills/quality/maintainability.md +142 -0
- skills/quality/naming.md +171 -0
- skills/quality/quality_functions.md +245 -0
- skills/quality/readability.md +239 -0
- skills/quality/type_annotations.md +192 -0
- skills/refactoring/behavior_preservation.md +157 -0
- skills/refactoring/incremental.md +187 -0
- skills/refactoring/interface_stability.md +199 -0
- skills/refactoring/safe_refactoring.md +206 -0
- skills/security/auth_boundaries.md +200 -0
- skills/security/command_injection.md +207 -0
- skills/security/dependency_risks.md +282 -0
- skills/security/file_handling.md +156 -0
- skills/security/input_validation.md +190 -0
- skills/security/path_traversal.md +172 -0
- skills/security/secrets.md +171 -0
- skills/security/sql_injection.md +188 -0
- skills/security/unsafe_deserialization.md +164 -0
- skills/stdlib/argparse.md +178 -0
- skills/stdlib/collections.md +212 -0
- skills/stdlib/datetime.md +187 -0
- skills/stdlib/functools.md +238 -0
- skills/stdlib/itertools.md +183 -0
- skills/stdlib/json.md +162 -0
- skills/stdlib/logging.md +185 -0
- skills/stdlib/os_sys.md +184 -0
- skills/stdlib/pathlib.md +218 -0
- skills/stdlib/re.md +171 -0
- skills/stdlib/statistics.md +112 -0
- skills/stdlib/subprocess.md +211 -0
- skills/testing/async_tests.md +249 -0
- skills/testing/coverage.md +168 -0
- skills/testing/edge_cases.md +197 -0
- skills/testing/fixtures_mocks.md +203 -0
- skills/testing/organization.md +205 -0
- skills/testing/parameterized.md +174 -0
- skills/testing/regression_tests.md +165 -0
- skills/variables_types.md +107 -0
skills/functions.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: core_functions
|
|
3
|
+
purpose: Python function definition, calling conventions, and patterns
|
|
4
|
+
category: core
|
|
5
|
+
triggers:
|
|
6
|
+
- function
|
|
7
|
+
- def
|
|
8
|
+
- method
|
|
9
|
+
- parameter
|
|
10
|
+
- argument
|
|
11
|
+
- callable
|
|
12
|
+
- closure
|
|
13
|
+
- decorator
|
|
14
|
+
dependencies:
|
|
15
|
+
- core/advanced_python.md
|
|
16
|
+
- generation/type_hints.md
|
|
17
|
+
- quality/functions.md
|
|
18
|
+
- anti_patterns/index.md
|
|
19
|
+
priority: primary
|
|
20
|
+
estimated_tokens: 2000
|
|
21
|
+
---
|
|
22
|
+
# Core: Functions
|
|
23
|
+
|
|
24
|
+
**Purpose**: Python function definition, calling conventions, and patterns.
|
|
25
|
+
|
|
26
|
+
**When to use**: Always active. Functions are the primary abstraction unit.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Core Rules
|
|
31
|
+
|
|
32
|
+
### Definition
|
|
33
|
+
```python
|
|
34
|
+
def function_name(
|
|
35
|
+
positional: type,
|
|
36
|
+
positional_with_default: type = default,
|
|
37
|
+
*args: type,
|
|
38
|
+
keyword_only: type,
|
|
39
|
+
keyword_only_with_default: type = default,
|
|
40
|
+
**kwargs: type,
|
|
41
|
+
) -> return_type:
|
|
42
|
+
"""Docstring describing what, args, returns, raises."""
|
|
43
|
+
...
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Parameter Kinds (PEP 3102, PEP 570)
|
|
47
|
+
| Kind | Syntax | Position | Use For |
|
|
48
|
+
|------|--------|----------|---------|
|
|
49
|
+
| Positional-only | `pos_only, /` | Before `/` | API stability, `self`/`cls` |
|
|
50
|
+
| Positional-or-keyword | `pos_or_kw` | Default | Most parameters |
|
|
51
|
+
| Keyword-only | `*, kw_only` | After `*` | Required named args, clarity |
|
|
52
|
+
| Var-positional | `*args` | After positional | Variable positional args |
|
|
53
|
+
| Var-keyword | `**kwargs` | Last | Variable keyword args |
|
|
54
|
+
|
|
55
|
+
### Default Arguments
|
|
56
|
+
- Evaluated **once** at function definition time
|
|
57
|
+
- **Never use mutable defaults** (`[]`, `{}`, `set()`) — creates shared state
|
|
58
|
+
- Use `None` sentinel pattern instead
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
# WRONG
|
|
62
|
+
def bad(items=[]):
|
|
63
|
+
items.append(1)
|
|
64
|
+
return items
|
|
65
|
+
|
|
66
|
+
# CORRECT
|
|
67
|
+
def good(items=None):
|
|
68
|
+
if items is None:
|
|
69
|
+
items = []
|
|
70
|
+
items.append(1)
|
|
71
|
+
return items
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Return Values
|
|
75
|
+
- Implicit `return None` if no return statement
|
|
76
|
+
- Multiple returns via tuple: `return a, b`
|
|
77
|
+
- Explicit `return` for early exits
|
|
78
|
+
- Type hint return type (`-> Type`)
|
|
79
|
+
|
|
80
|
+
### Function Attributes
|
|
81
|
+
```python
|
|
82
|
+
func.__name__ # Name
|
|
83
|
+
func.__doc__ # Docstring
|
|
84
|
+
func.__annotations__ # Type hints
|
|
85
|
+
func.__defaults__ # Default values tuple
|
|
86
|
+
func.__kwdefaults__ # Keyword-only defaults dict
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Calling Conventions
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
# Positional
|
|
95
|
+
func(1, 2)
|
|
96
|
+
|
|
97
|
+
# Keyword
|
|
98
|
+
func(pos=1, kw=2)
|
|
99
|
+
|
|
100
|
+
# Mixed (positional before keyword)
|
|
101
|
+
func(1, kw=2)
|
|
102
|
+
|
|
103
|
+
# Unpacking
|
|
104
|
+
func(*args, **kwargs)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Positional-Only Parameters (Python 3.8+)
|
|
108
|
+
```python
|
|
109
|
+
def func(pos_only, /, pos_or_kw, *, kw_only):
|
|
110
|
+
...
|
|
111
|
+
```
|
|
112
|
+
- Use `/` to enforce positional-only (e.g., `self`, `cls`, builtins like `len`)
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Advanced Patterns
|
|
117
|
+
|
|
118
|
+
### Closures
|
|
119
|
+
```python
|
|
120
|
+
def outer(x):
|
|
121
|
+
def inner(y):
|
|
122
|
+
return x + y # Captures x from enclosing scope
|
|
123
|
+
return inner
|
|
124
|
+
|
|
125
|
+
add5 = outer(5)
|
|
126
|
+
add5(3) # 8
|
|
127
|
+
```
|
|
128
|
+
- Use `nonlocal` to modify captured variables
|
|
129
|
+
|
|
130
|
+
### Callable Objects
|
|
131
|
+
```python
|
|
132
|
+
class Adder:
|
|
133
|
+
def __init__(self, n):
|
|
134
|
+
self.n = n
|
|
135
|
+
def __call__(self, x):
|
|
136
|
+
return self.n + x
|
|
137
|
+
|
|
138
|
+
add5 = Adder(5)
|
|
139
|
+
add5(3) # 8
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Decorators
|
|
143
|
+
```python
|
|
144
|
+
def decorator(func):
|
|
145
|
+
@functools.wraps(func) # Preserves metadata
|
|
146
|
+
def wrapper(*args, **kwargs):
|
|
147
|
+
return func(*args, **kwargs)
|
|
148
|
+
return wrapper
|
|
149
|
+
|
|
150
|
+
@decorator
|
|
151
|
+
def my_func():
|
|
152
|
+
...
|
|
153
|
+
```
|
|
154
|
+
- Always use `@functools.wraps` on wrapper functions
|
|
155
|
+
- Decorators execute at **definition time**
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Type Hints for Functions
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from typing import Callable, TypeVar
|
|
163
|
+
|
|
164
|
+
T = TypeVar('T')
|
|
165
|
+
|
|
166
|
+
def map_func(func: Callable[[int], T], values: list[int]) -> list[T]:
|
|
167
|
+
return [func(v) for v in values]
|
|
168
|
+
|
|
169
|
+
# Overloads for complex signatures
|
|
170
|
+
from typing import overload
|
|
171
|
+
|
|
172
|
+
@overload
|
|
173
|
+
def func(x: int) -> int: ...
|
|
174
|
+
@overload
|
|
175
|
+
def func(x: str) -> str: ...
|
|
176
|
+
def func(x: int | str) -> int | str:
|
|
177
|
+
return x
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Decision Rules
|
|
183
|
+
|
|
184
|
+
| Situation | Pattern |
|
|
185
|
+
|-----------|---------|
|
|
186
|
+
| Simple transformation | `def` + type hints |
|
|
187
|
+
| Need to capture state | Closure or callable class |
|
|
188
|
+
| Multiple related operations | Class with methods |
|
|
189
|
+
| Cross-cutting concerns | Decorator |
|
|
190
|
+
| Variable positional args | `*args` |
|
|
191
|
+
| Variable keyword args | `**kwargs` |
|
|
192
|
+
| API stability required | Positional-only (`/`) |
|
|
193
|
+
| Clarity for boolean flags | Keyword-only (`*`) |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Preferred Patterns
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
# Small, focused functions
|
|
201
|
+
def parse_date(s: str) -> datetime.date:
|
|
202
|
+
return datetime.date.fromisoformat(s)
|
|
203
|
+
|
|
204
|
+
# Early returns for guard clauses
|
|
205
|
+
def process(user: User) -> Result:
|
|
206
|
+
if not user.is_active:
|
|
207
|
+
return Result.error("inactive")
|
|
208
|
+
if not user.has_permission("read"):
|
|
209
|
+
return Result.error("forbidden")
|
|
210
|
+
return do_process(user)
|
|
211
|
+
|
|
212
|
+
# Keyword-only for clarity
|
|
213
|
+
def connect(host: str, port: int, *, timeout: float = 5.0, ssl: bool = True) -> Connection:
|
|
214
|
+
...
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Avoid
|
|
220
|
+
|
|
221
|
+
- Mutable default arguments (see anti-patterns)
|
|
222
|
+
- Functions with >7 parameters (use dataclass/config object)
|
|
223
|
+
- Deeply nested functions (limit closure depth)
|
|
224
|
+
- Decorators that change function signature unexpectedly
|
|
225
|
+
- `*args`/`**kwargs` without documentation of expected keys
|
|
226
|
+
- Modifying `**kwargs` in place (copy first)
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Validation Considerations
|
|
231
|
+
|
|
232
|
+
- Type checkers verify signatures and return types
|
|
233
|
+
- `inspect.signature()` for runtime introspection
|
|
234
|
+
- `functools.wraps` preserves signature for tooling
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Related Skills
|
|
239
|
+
|
|
240
|
+
- `generation/type_hints.md`
|
|
241
|
+
- `core/advanced_python.md` (decorators, closures)
|
|
242
|
+
- `anti_patterns/index.md` (mutable defaults)
|
|
243
|
+
- `quality/functions.md` (size, focus)
|
|
244
|
+
- `engineering/cli_apps.md` (CLI entry points)
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generation_async_concurrency
|
|
3
|
+
purpose: Async/await patterns, concurrency primitives, and correct usage
|
|
4
|
+
category: generation
|
|
5
|
+
triggers:
|
|
6
|
+
- async
|
|
7
|
+
- await
|
|
8
|
+
- concurrent
|
|
9
|
+
- parallel
|
|
10
|
+
- asyncio
|
|
11
|
+
- task
|
|
12
|
+
- semaphore
|
|
13
|
+
- queue
|
|
14
|
+
dependencies:
|
|
15
|
+
- generation/error_handling.md
|
|
16
|
+
- testing/async_tests.md
|
|
17
|
+
- stdlib/subprocess.md
|
|
18
|
+
- engineering/http_clients.md
|
|
19
|
+
priority: primary
|
|
20
|
+
estimated_tokens: 2700
|
|
21
|
+
---
|
|
22
|
+
# Generation: Async and Concurrency
|
|
23
|
+
|
|
24
|
+
**Purpose**: Async/await patterns, concurrency primitives, and correct usage.
|
|
25
|
+
|
|
26
|
+
**When to use**: I/O-bound operations, high-concurrency servers, parallel I/O.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Core Rules
|
|
31
|
+
|
|
32
|
+
### When to Use Async
|
|
33
|
+
- I/O-bound: HTTP requests, database, file I/O, subprocess
|
|
34
|
+
- Many concurrent connections (web servers, websockets)
|
|
35
|
+
- Not for CPU-bound (use multiprocessing)
|
|
36
|
+
|
|
37
|
+
### Basic Syntax
|
|
38
|
+
```python
|
|
39
|
+
import asyncio
|
|
40
|
+
|
|
41
|
+
async def fetch(url: str) -> str:
|
|
42
|
+
async with aiohttp.ClientSession() as session:
|
|
43
|
+
async with session.get(url) as resp:
|
|
44
|
+
return await resp.text()
|
|
45
|
+
|
|
46
|
+
async def main():
|
|
47
|
+
result = await fetch("https://example.com")
|
|
48
|
+
print(result)
|
|
49
|
+
|
|
50
|
+
asyncio.run(main())
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Task Management
|
|
54
|
+
```python
|
|
55
|
+
async def main():
|
|
56
|
+
# Concurrent execution
|
|
57
|
+
task1 = asyncio.create_task(fetch(url1))
|
|
58
|
+
task2 = asyncio.create_task(fetch(url2))
|
|
59
|
+
result1, result2 = await asyncio.gather(task1, task2)
|
|
60
|
+
|
|
61
|
+
# With timeout
|
|
62
|
+
try:
|
|
63
|
+
result = await asyncio.wait_for(fetch(url), timeout=5.0)
|
|
64
|
+
except asyncio.TimeoutError:
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
# As completed
|
|
68
|
+
for coro in asyncio.as_completed([fetch(u) for u in urls]):
|
|
69
|
+
result = await coro
|
|
70
|
+
process(result)
|
|
71
|
+
|
|
72
|
+
# Shield from cancellation
|
|
73
|
+
await asyncio.shield(critical_operation())
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Cancellation
|
|
77
|
+
```python
|
|
78
|
+
async def long_running():
|
|
79
|
+
try:
|
|
80
|
+
while True:
|
|
81
|
+
await asyncio.sleep(1)
|
|
82
|
+
# Check cancellation
|
|
83
|
+
except asyncio.CancelledError:
|
|
84
|
+
cleanup()
|
|
85
|
+
raise # Must re-raise!
|
|
86
|
+
|
|
87
|
+
# Cancellation propagation
|
|
88
|
+
async def parent():
|
|
89
|
+
child = asyncio.create_task(long_running())
|
|
90
|
+
await asyncio.sleep(0.1)
|
|
91
|
+
child.cancel()
|
|
92
|
+
try:
|
|
93
|
+
await child
|
|
94
|
+
except asyncio.CancelledError:
|
|
95
|
+
pass
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Async Context Managers
|
|
99
|
+
```python
|
|
100
|
+
class AsyncResource:
|
|
101
|
+
async def __aenter__(self):
|
|
102
|
+
await self.connect()
|
|
103
|
+
return self
|
|
104
|
+
|
|
105
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
106
|
+
await self.close()
|
|
107
|
+
return False # Don't suppress
|
|
108
|
+
|
|
109
|
+
async def use():
|
|
110
|
+
async with AsyncResource() as r:
|
|
111
|
+
await r.do_something()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Async Iterators
|
|
115
|
+
```python
|
|
116
|
+
async def async_gen() -> AsyncGenerator[int, None]:
|
|
117
|
+
for i in range(10):
|
|
118
|
+
await asyncio.sleep(0.1)
|
|
119
|
+
yield i
|
|
120
|
+
|
|
121
|
+
async def consume():
|
|
122
|
+
async for item in async_gen():
|
|
123
|
+
print(item)
|
|
124
|
+
|
|
125
|
+
# Or collect
|
|
126
|
+
items = [item async for item in async_gen()]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Synchronization Primitives
|
|
130
|
+
```python
|
|
131
|
+
# Lock
|
|
132
|
+
lock = asyncio.Lock()
|
|
133
|
+
async with lock:
|
|
134
|
+
critical_section()
|
|
135
|
+
|
|
136
|
+
# Semaphore (limit concurrency)
|
|
137
|
+
sem = asyncio.Semaphore(10)
|
|
138
|
+
async with sem:
|
|
139
|
+
await limited_operation()
|
|
140
|
+
|
|
141
|
+
# Event
|
|
142
|
+
event = asyncio.Event()
|
|
143
|
+
async def waiter():
|
|
144
|
+
await event.wait()
|
|
145
|
+
async def setter():
|
|
146
|
+
await asyncio.sleep(1)
|
|
147
|
+
event.set()
|
|
148
|
+
|
|
149
|
+
# Queue
|
|
150
|
+
queue: asyncio.Queue[str] = asyncio.Queue()
|
|
151
|
+
await queue.put("item")
|
|
152
|
+
item = await queue.get()
|
|
153
|
+
|
|
154
|
+
# Condition
|
|
155
|
+
cond = asyncio.Condition()
|
|
156
|
+
async with cond:
|
|
157
|
+
await cond.wait()
|
|
158
|
+
cond.notify()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Running in Threads (Blocking I/O)
|
|
162
|
+
```python
|
|
163
|
+
# Run sync function in thread pool
|
|
164
|
+
result = await asyncio.to_thread(blocking_func, arg1, arg2)
|
|
165
|
+
|
|
166
|
+
# Custom executor
|
|
167
|
+
loop = asyncio.get_event_loop()
|
|
168
|
+
result = await loop.run_in_executor(None, blocking_func, arg1)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Exception Handling
|
|
172
|
+
```python
|
|
173
|
+
async def main():
|
|
174
|
+
try:
|
|
175
|
+
await risky_async()
|
|
176
|
+
except SpecificError:
|
|
177
|
+
handle()
|
|
178
|
+
except ExceptionGroup as eg: # Python 3.11+
|
|
179
|
+
for e in eg.exceptions:
|
|
180
|
+
handle(e)
|
|
181
|
+
except* ValueError as eg: # except* syntax (3.11+)
|
|
182
|
+
for e in eg.exceptions:
|
|
183
|
+
handle(e)
|
|
184
|
+
finally:
|
|
185
|
+
cleanup()
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Task Groups (Python 3.11+)
|
|
189
|
+
```python
|
|
190
|
+
async def main():
|
|
191
|
+
async with asyncio.TaskGroup() as tg:
|
|
192
|
+
task1 = tg.create_task(fetch(url1))
|
|
193
|
+
task2 = tg.create_task(fetch(url2))
|
|
194
|
+
# All tasks completed or first exception raised
|
|
195
|
+
results = [task1.result(), task2.result()]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Decision Rules
|
|
201
|
+
|
|
202
|
+
| Situation | Pattern |
|
|
203
|
+
|-----------|---------|
|
|
204
|
+
| Multiple independent I/O | `asyncio.gather` / `TaskGroup` |
|
|
205
|
+
| Rate limiting | `Semaphore` |
|
|
206
|
+
| Producer/consumer | `asyncio.Queue` |
|
|
207
|
+
| Timeout | `asyncio.wait_for` |
|
|
208
|
+
| Cancellation | `task.cancel()` + `CancelledError` handling |
|
|
209
|
+
| Blocking call | `asyncio.to_thread` |
|
|
210
|
+
| Background task | `create_task` (track for cleanup) |
|
|
211
|
+
| Cleanup on exit | `async with` / `try/finally` |
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Preferred Patterns
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
# Bounded concurrency
|
|
219
|
+
async def fetch_all(urls: list[str], max_concurrent: int = 10) -> list[str]:
|
|
220
|
+
sem = asyncio.Semaphore(max_concurrent)
|
|
221
|
+
|
|
222
|
+
async def bounded_fetch(url: str) -> str:
|
|
223
|
+
async with sem:
|
|
224
|
+
return await fetch(url)
|
|
225
|
+
|
|
226
|
+
return await asyncio.gather(*[bounded_fetch(u) for u in urls])
|
|
227
|
+
|
|
228
|
+
# Retry with backoff
|
|
229
|
+
async def retry_async(
|
|
230
|
+
func: Callable[..., Awaitable[T]],
|
|
231
|
+
*args,
|
|
232
|
+
attempts: int = 3,
|
|
233
|
+
base_delay: float = 1.0,
|
|
234
|
+
**kwargs,
|
|
235
|
+
) -> T:
|
|
236
|
+
for attempt in range(attempts):
|
|
237
|
+
try:
|
|
238
|
+
return await func(*args, **kwargs)
|
|
239
|
+
except Exception:
|
|
240
|
+
if attempt == attempts - 1:
|
|
241
|
+
raise
|
|
242
|
+
await asyncio.sleep(base_delay * (2 ** attempt))
|
|
243
|
+
|
|
244
|
+
# Graceful shutdown
|
|
245
|
+
async def run_server():
|
|
246
|
+
server = await start_server()
|
|
247
|
+
try:
|
|
248
|
+
await server.serve_forever()
|
|
249
|
+
except asyncio.CancelledError:
|
|
250
|
+
await server.shutdown()
|
|
251
|
+
raise
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Avoid
|
|
257
|
+
|
|
258
|
+
- `asyncio.run()` inside async function (nested event loops)
|
|
259
|
+
- Blocking calls in async functions (use `to_thread`)
|
|
260
|
+
- `asyncio.sleep(0)` for yielding (use `await asyncio.shield` or proper design)
|
|
261
|
+
- Creating tasks without tracking (memory leaks)
|
|
262
|
+
- Catching `CancelledError` without re-raising
|
|
263
|
+
- Mixing `asyncio` with `threading` primitives
|
|
264
|
+
- Global event loop references
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Python Version Notes
|
|
269
|
+
|
|
270
|
+
- 3.11+: `TaskGroup`, `except*`, `asyncio.timeout()`
|
|
271
|
+
- 3.10+: `asyncio.timeout()` context manager
|
|
272
|
+
- 3.7+: `asyncio.run()`, `asyncio.create_task()`
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Validation Considerations
|
|
277
|
+
|
|
278
|
+
- Test cancellation scenarios
|
|
279
|
+
- Test timeout behavior
|
|
280
|
+
- Check for resource leaks (open connections, files)
|
|
281
|
+
- Verify no blocking calls in async path
|
|
282
|
+
- `pytest-asyncio` for testing
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Related Skills
|
|
287
|
+
|
|
288
|
+
- `generation/error_handling.md`
|
|
289
|
+
- `testing/async_tests.md`
|
|
290
|
+
- `stdlib/subprocess.md` (async subprocess)
|
|
291
|
+
- `engineering/http_clients.md` (aiohttp, httpx)
|