devflow-kit 1.1.0 → 1.2.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/CHANGELOG.md +39 -0
- package/README.md +23 -6
- package/dist/plugins.js +67 -3
- package/package.json +2 -1
- package/plugins/devflow-accessibility/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-ambient/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-ambient/skills/ambient-router/SKILL.md +1 -1
- package/plugins/devflow-ambient/skills/ambient-router/references/skill-catalog.md +4 -0
- package/plugins/devflow-audit-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-code-review/.claude-plugin/plugin.json +1 -4
- package/plugins/devflow-code-review/agents/reviewer.md +8 -0
- package/plugins/devflow-code-review/commands/code-review-teams.md +11 -1
- package/plugins/devflow-code-review/commands/code-review.md +12 -2
- package/plugins/devflow-core-skills/.claude-plugin/plugin.json +2 -6
- package/plugins/devflow-debug/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-frontend-design/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-go/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-go/skills/go/SKILL.md +187 -0
- package/plugins/devflow-go/skills/go/references/concurrency.md +312 -0
- package/plugins/devflow-go/skills/go/references/detection.md +129 -0
- package/plugins/devflow-go/skills/go/references/patterns.md +232 -0
- package/plugins/devflow-go/skills/go/references/violations.md +205 -0
- package/plugins/devflow-implement/.claude-plugin/plugin.json +1 -3
- package/plugins/devflow-implement/agents/coder.md +11 -6
- package/plugins/devflow-java/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-java/skills/java/SKILL.md +183 -0
- package/plugins/devflow-java/skills/java/references/detection.md +120 -0
- package/plugins/devflow-java/skills/java/references/modern-java.md +270 -0
- package/plugins/devflow-java/skills/java/references/patterns.md +235 -0
- package/plugins/devflow-java/skills/java/references/violations.md +213 -0
- package/plugins/devflow-python/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-python/skills/python/SKILL.md +188 -0
- package/plugins/devflow-python/skills/python/references/async.md +220 -0
- package/plugins/devflow-python/skills/python/references/detection.md +128 -0
- package/plugins/devflow-python/skills/python/references/patterns.md +226 -0
- package/plugins/devflow-python/skills/python/references/violations.md +204 -0
- package/plugins/devflow-react/.claude-plugin/plugin.json +15 -0
- package/plugins/{devflow-core-skills → devflow-react}/skills/react/SKILL.md +1 -1
- package/plugins/{devflow-core-skills → devflow-react}/skills/react/references/patterns.md +3 -3
- package/plugins/devflow-resolve/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-rust/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-rust/skills/rust/SKILL.md +193 -0
- package/plugins/devflow-rust/skills/rust/references/detection.md +131 -0
- package/plugins/devflow-rust/skills/rust/references/ownership.md +242 -0
- package/plugins/devflow-rust/skills/rust/references/patterns.md +210 -0
- package/plugins/devflow-rust/skills/rust/references/violations.md +191 -0
- package/plugins/devflow-self-review/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-specify/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-typescript/.claude-plugin/plugin.json +15 -0
- package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/references/patterns.md +3 -3
- package/shared/agents/coder.md +11 -6
- package/shared/agents/reviewer.md +8 -0
- package/shared/skills/ambient-router/SKILL.md +1 -1
- package/shared/skills/ambient-router/references/skill-catalog.md +4 -0
- package/shared/skills/go/SKILL.md +187 -0
- package/shared/skills/go/references/concurrency.md +312 -0
- package/shared/skills/go/references/detection.md +129 -0
- package/shared/skills/go/references/patterns.md +232 -0
- package/shared/skills/go/references/violations.md +205 -0
- package/shared/skills/java/SKILL.md +183 -0
- package/shared/skills/java/references/detection.md +120 -0
- package/shared/skills/java/references/modern-java.md +270 -0
- package/shared/skills/java/references/patterns.md +235 -0
- package/shared/skills/java/references/violations.md +213 -0
- package/shared/skills/python/SKILL.md +188 -0
- package/shared/skills/python/references/async.md +220 -0
- package/shared/skills/python/references/detection.md +128 -0
- package/shared/skills/python/references/patterns.md +226 -0
- package/shared/skills/python/references/violations.md +204 -0
- package/shared/skills/react/SKILL.md +1 -1
- package/shared/skills/react/references/patterns.md +3 -3
- package/shared/skills/rust/SKILL.md +193 -0
- package/shared/skills/rust/references/detection.md +131 -0
- package/shared/skills/rust/references/ownership.md +242 -0
- package/shared/skills/rust/references/patterns.md +210 -0
- package/shared/skills/rust/references/violations.md +191 -0
- package/shared/skills/typescript/references/patterns.md +3 -3
- package/plugins/devflow-code-review/skills/react/SKILL.md +0 -276
- package/plugins/devflow-code-review/skills/react/references/patterns.md +0 -1331
- package/plugins/devflow-core-skills/skills/accessibility/SKILL.md +0 -229
- package/plugins/devflow-core-skills/skills/accessibility/references/detection.md +0 -171
- package/plugins/devflow-core-skills/skills/accessibility/references/patterns.md +0 -670
- package/plugins/devflow-core-skills/skills/accessibility/references/violations.md +0 -419
- package/plugins/devflow-core-skills/skills/frontend-design/SKILL.md +0 -254
- package/plugins/devflow-core-skills/skills/frontend-design/references/detection.md +0 -184
- package/plugins/devflow-core-skills/skills/frontend-design/references/patterns.md +0 -511
- package/plugins/devflow-core-skills/skills/frontend-design/references/violations.md +0 -453
- package/plugins/devflow-core-skills/skills/react/references/violations.md +0 -565
- package/plugins/devflow-implement/skills/accessibility/SKILL.md +0 -229
- package/plugins/devflow-implement/skills/accessibility/references/detection.md +0 -171
- package/plugins/devflow-implement/skills/accessibility/references/patterns.md +0 -670
- package/plugins/devflow-implement/skills/accessibility/references/violations.md +0 -419
- package/plugins/devflow-implement/skills/frontend-design/SKILL.md +0 -254
- package/plugins/devflow-implement/skills/frontend-design/references/detection.md +0 -184
- package/plugins/devflow-implement/skills/frontend-design/references/patterns.md +0 -511
- package/plugins/devflow-implement/skills/frontend-design/references/violations.md +0 -453
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/SKILL.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/detection.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/patterns.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/violations.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/SKILL.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/detection.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/patterns.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/violations.md +0 -0
- /package/plugins/{devflow-code-review → devflow-react}/skills/react/references/violations.md +0 -0
- /package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/SKILL.md +0 -0
- /package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/references/violations.md +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "devflow-python",
|
|
3
|
+
"description": "Python language patterns - type hints, protocols, dataclasses, async programming",
|
|
4
|
+
"author": {
|
|
5
|
+
"name": "DevFlow Contributors",
|
|
6
|
+
"email": "dean@keren.dev"
|
|
7
|
+
},
|
|
8
|
+
"version": "1.2.0",
|
|
9
|
+
"homepage": "https://github.com/dean0x/devflow",
|
|
10
|
+
"repository": "https://github.com/dean0x/devflow",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": ["python", "type-hints", "dataclasses", "async"],
|
|
13
|
+
"agents": [],
|
|
14
|
+
"skills": ["python"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python
|
|
3
|
+
description: This skill should be used when the user works with Python files (.py), asks about "type hints", "protocols", "dataclasses", "async/await", "decorators", or discusses Pythonic patterns and data modeling. Provides patterns for type safety, error handling, data modeling, and async programming.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
allowed-tools: Read, Grep, Glob
|
|
6
|
+
activation:
|
|
7
|
+
file-patterns:
|
|
8
|
+
- "**/*.py"
|
|
9
|
+
exclude:
|
|
10
|
+
- "venv/**"
|
|
11
|
+
- ".venv/**"
|
|
12
|
+
- "**/__pycache__/**"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Python Patterns
|
|
16
|
+
|
|
17
|
+
Reference for Python-specific patterns, type safety, and idioms.
|
|
18
|
+
|
|
19
|
+
## Iron Law
|
|
20
|
+
|
|
21
|
+
> **EXPLICIT IS BETTER THAN IMPLICIT**
|
|
22
|
+
>
|
|
23
|
+
> Type-hint every function signature. Name every exception. Use dataclasses over raw dicts.
|
|
24
|
+
> Python's flexibility is a strength only when boundaries are explicit. Implicit behavior
|
|
25
|
+
> causes debugging nightmares and makes codebases hostile to newcomers.
|
|
26
|
+
|
|
27
|
+
## When This Skill Activates
|
|
28
|
+
|
|
29
|
+
- Working with Python codebases
|
|
30
|
+
- Designing typed APIs with type hints
|
|
31
|
+
- Modeling data with dataclasses or Pydantic
|
|
32
|
+
- Implementing async code
|
|
33
|
+
- Structuring Python packages
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Type Safety
|
|
38
|
+
|
|
39
|
+
### Type Hint Everything
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
# BAD: def process(data, config): ...
|
|
43
|
+
# GOOD:
|
|
44
|
+
def process(data: list[dict[str, Any]], config: AppConfig) -> ProcessResult:
|
|
45
|
+
...
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Use Protocols for Structural Typing
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from typing import Protocol
|
|
52
|
+
|
|
53
|
+
class Repository(Protocol):
|
|
54
|
+
def find_by_id(self, id: str) -> User | None: ...
|
|
55
|
+
def save(self, entity: User) -> User: ...
|
|
56
|
+
|
|
57
|
+
# Any class with these methods satisfies Repository — no inheritance needed
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Strict Optional Handling
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# BAD: def get_name(user): return user.name
|
|
64
|
+
# GOOD:
|
|
65
|
+
def get_name(user: User | None) -> str:
|
|
66
|
+
if user is None:
|
|
67
|
+
return "Anonymous"
|
|
68
|
+
return user.name
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Error Handling
|
|
74
|
+
|
|
75
|
+
### Custom Exception Hierarchies
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
class AppError(Exception):
|
|
79
|
+
"""Base application error."""
|
|
80
|
+
|
|
81
|
+
class NotFoundError(AppError):
|
|
82
|
+
def __init__(self, entity: str, id: str) -> None:
|
|
83
|
+
super().__init__(f"{entity} {id} not found")
|
|
84
|
+
self.entity = entity
|
|
85
|
+
self.id = id
|
|
86
|
+
|
|
87
|
+
class ValidationError(AppError):
|
|
88
|
+
def __init__(self, field: str, message: str) -> None:
|
|
89
|
+
super().__init__(f"Validation failed for {field}: {message}")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Context Managers for Resources
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from contextlib import contextmanager
|
|
96
|
+
|
|
97
|
+
@contextmanager
|
|
98
|
+
def database_transaction(conn: Connection):
|
|
99
|
+
try:
|
|
100
|
+
yield conn
|
|
101
|
+
conn.commit()
|
|
102
|
+
except Exception:
|
|
103
|
+
conn.rollback()
|
|
104
|
+
raise
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Data Modeling
|
|
110
|
+
|
|
111
|
+
### Dataclasses Over Raw Dicts
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
# BAD: user = {"name": "Alice", "email": "alice@example.com"}
|
|
115
|
+
# GOOD:
|
|
116
|
+
@dataclass(frozen=True)
|
|
117
|
+
class User:
|
|
118
|
+
name: str
|
|
119
|
+
email: str
|
|
120
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Pydantic for Validation at Boundaries
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from pydantic import BaseModel, EmailStr
|
|
127
|
+
|
|
128
|
+
class CreateUserRequest(BaseModel):
|
|
129
|
+
name: str
|
|
130
|
+
email: EmailStr
|
|
131
|
+
age: int = Field(ge=0, le=150)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Pythonic Patterns
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
# Comprehensions over loops for transforms
|
|
140
|
+
names = [user.name for user in users if user.active]
|
|
141
|
+
|
|
142
|
+
# Enumerate over manual index tracking
|
|
143
|
+
for i, item in enumerate(items):
|
|
144
|
+
process(i, item)
|
|
145
|
+
|
|
146
|
+
# EAFP: Easier to Ask Forgiveness than Permission
|
|
147
|
+
try:
|
|
148
|
+
value = mapping[key]
|
|
149
|
+
except KeyError:
|
|
150
|
+
value = default
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Anti-Patterns
|
|
156
|
+
|
|
157
|
+
| Pattern | Bad | Good |
|
|
158
|
+
|---------|-----|------|
|
|
159
|
+
| Bare except | `except:` | `except (ValueError, KeyError):` |
|
|
160
|
+
| Mutable default | `def fn(items=[])` | `def fn(items: list | None = None)` |
|
|
161
|
+
| No type hints | `def process(data)` | `def process(data: DataFrame) -> Result` |
|
|
162
|
+
| String typing | `x: "MyClass"` (without reason) | `from __future__ import annotations` |
|
|
163
|
+
| God class | `class App` with 50 methods | Compose smaller focused classes |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Extended References
|
|
168
|
+
|
|
169
|
+
For additional patterns and examples:
|
|
170
|
+
- `references/violations.md` - Common Python violations
|
|
171
|
+
- `references/patterns.md` - Extended Python patterns
|
|
172
|
+
- `references/detection.md` - Detection patterns for Python issues
|
|
173
|
+
- `references/async.md` - Async Python patterns
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Checklist
|
|
178
|
+
|
|
179
|
+
- [ ] All functions have type hints (params + return)
|
|
180
|
+
- [ ] Custom exceptions with meaningful messages
|
|
181
|
+
- [ ] Dataclasses or Pydantic for structured data
|
|
182
|
+
- [ ] No bare `except:` clauses
|
|
183
|
+
- [ ] No mutable default arguments
|
|
184
|
+
- [ ] Context managers for resource management
|
|
185
|
+
- [ ] `from __future__ import annotations` for forward refs
|
|
186
|
+
- [ ] Protocols for structural typing (not ABC unless needed)
|
|
187
|
+
- [ ] Comprehensions for simple transforms
|
|
188
|
+
- [ ] Tests use pytest with fixtures
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Async Python Patterns
|
|
2
|
+
|
|
3
|
+
Deep-dive on async Python programming. Reference from main SKILL.md.
|
|
4
|
+
|
|
5
|
+
## Core asyncio Patterns
|
|
6
|
+
|
|
7
|
+
### Basic Async Function
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import asyncio
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
async def fetch_user(user_id: str) -> User:
|
|
14
|
+
async with aiohttp.ClientSession() as session:
|
|
15
|
+
async with session.get(f"/api/users/{user_id}") as response:
|
|
16
|
+
data = await response.json()
|
|
17
|
+
return User.model_validate(data)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Concurrent Execution with gather
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
async def load_dashboard(user_id: str) -> Dashboard:
|
|
24
|
+
# Independent fetches run concurrently
|
|
25
|
+
user, orders, preferences = await asyncio.gather(
|
|
26
|
+
fetch_user(user_id),
|
|
27
|
+
fetch_orders(user_id),
|
|
28
|
+
fetch_preferences(user_id),
|
|
29
|
+
)
|
|
30
|
+
return Dashboard(user=user, orders=orders, preferences=preferences)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Structured Concurrency with TaskGroup
|
|
34
|
+
|
|
35
|
+
### TaskGroup for Safe Concurrency (Python 3.11+)
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
async def process_batch(items: list[Item]) -> list[Result]:
|
|
39
|
+
results: list[Result] = []
|
|
40
|
+
|
|
41
|
+
async with asyncio.TaskGroup() as tg:
|
|
42
|
+
for item in items:
|
|
43
|
+
tg.create_task(process_and_collect(item, results))
|
|
44
|
+
|
|
45
|
+
return results
|
|
46
|
+
|
|
47
|
+
async def process_and_collect(item: Item, results: list[Result]) -> None:
|
|
48
|
+
result = await process_item(item)
|
|
49
|
+
results.append(result)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Error Handling with TaskGroup
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
async def resilient_batch(items: list[Item]) -> tuple[list[Result], list[Error]]:
|
|
56
|
+
results: list[Result] = []
|
|
57
|
+
errors: list[Error] = []
|
|
58
|
+
|
|
59
|
+
# TaskGroup cancels all tasks if one raises — wrap individual tasks
|
|
60
|
+
async def safe_process(item: Item) -> None:
|
|
61
|
+
try:
|
|
62
|
+
result = await process_item(item)
|
|
63
|
+
results.append(result)
|
|
64
|
+
except ProcessingError as e:
|
|
65
|
+
errors.append(Error(item_id=item.id, message=str(e)))
|
|
66
|
+
|
|
67
|
+
async with asyncio.TaskGroup() as tg:
|
|
68
|
+
for item in items:
|
|
69
|
+
tg.create_task(safe_process(item))
|
|
70
|
+
|
|
71
|
+
return results, errors
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Async Generators
|
|
75
|
+
|
|
76
|
+
### Streaming Results
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from typing import AsyncGenerator
|
|
80
|
+
|
|
81
|
+
async def stream_results(query: str, params: tuple = ()) -> AsyncGenerator[Record, None]:
|
|
82
|
+
async with get_connection() as conn:
|
|
83
|
+
cursor = await conn.execute(query, params)
|
|
84
|
+
async for row in cursor:
|
|
85
|
+
yield Record.from_row(row)
|
|
86
|
+
|
|
87
|
+
# Usage
|
|
88
|
+
async for record in stream_results("SELECT * FROM events WHERE type = ?", ("click",)):
|
|
89
|
+
await process(record)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Async Generator with Cleanup
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
async def paginated_fetch(url: str, page_size: int = 100) -> AsyncGenerator[Item, None]:
|
|
96
|
+
page = 0
|
|
97
|
+
while True:
|
|
98
|
+
response = await fetch_page(url, page=page, size=page_size)
|
|
99
|
+
if not response.items:
|
|
100
|
+
break
|
|
101
|
+
for item in response.items:
|
|
102
|
+
yield item
|
|
103
|
+
page += 1
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Semaphore for Rate Limiting
|
|
107
|
+
|
|
108
|
+
### Bounded Concurrency
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
async def fetch_all(urls: list[str], max_concurrent: int = 10) -> list[Response]:
|
|
112
|
+
semaphore = asyncio.Semaphore(max_concurrent)
|
|
113
|
+
|
|
114
|
+
async def bounded_fetch(url: str) -> Response:
|
|
115
|
+
async with semaphore:
|
|
116
|
+
return await fetch(url)
|
|
117
|
+
|
|
118
|
+
return await asyncio.gather(*[bounded_fetch(url) for url in urls])
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## aiohttp Patterns
|
|
122
|
+
|
|
123
|
+
### Client Session Management
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from contextlib import asynccontextmanager
|
|
127
|
+
from typing import AsyncGenerator
|
|
128
|
+
|
|
129
|
+
@asynccontextmanager
|
|
130
|
+
async def api_client(base_url: str) -> AsyncGenerator[aiohttp.ClientSession, None]:
|
|
131
|
+
timeout = aiohttp.ClientTimeout(total=30)
|
|
132
|
+
async with aiohttp.ClientSession(base_url, timeout=timeout) as session:
|
|
133
|
+
yield session
|
|
134
|
+
|
|
135
|
+
# Usage — session reused for multiple requests
|
|
136
|
+
async def sync_users(user_ids: list[str]) -> list[User]:
|
|
137
|
+
async with api_client("https://api.example.com") as client:
|
|
138
|
+
tasks = [fetch_user_with_session(client, uid) for uid in user_ids]
|
|
139
|
+
return await asyncio.gather(*tasks)
|
|
140
|
+
|
|
141
|
+
async def fetch_user_with_session(
|
|
142
|
+
client: aiohttp.ClientSession, user_id: str
|
|
143
|
+
) -> User:
|
|
144
|
+
async with client.get(f"/users/{user_id}") as response:
|
|
145
|
+
response.raise_for_status()
|
|
146
|
+
data = await response.json()
|
|
147
|
+
return User.model_validate(data)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Timeout Patterns
|
|
151
|
+
|
|
152
|
+
### Per-Operation Timeout
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
async def fetch_with_timeout(url: str, timeout_seconds: float = 5.0) -> dict[str, Any]:
|
|
156
|
+
try:
|
|
157
|
+
async with asyncio.timeout(timeout_seconds):
|
|
158
|
+
return await fetch(url)
|
|
159
|
+
except TimeoutError:
|
|
160
|
+
raise OperationTimeout(f"Request to {url} timed out after {timeout_seconds}s")
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Anti-Patterns
|
|
164
|
+
|
|
165
|
+
### Blocking Calls in Async Code
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
# VIOLATION: Blocks the event loop
|
|
169
|
+
async def bad_fetch(url: str) -> str:
|
|
170
|
+
import requests
|
|
171
|
+
return requests.get(url).text # Blocks entire event loop!
|
|
172
|
+
|
|
173
|
+
# CORRECT: Use async library or run in executor
|
|
174
|
+
async def good_fetch(url: str) -> str:
|
|
175
|
+
async with aiohttp.ClientSession() as session:
|
|
176
|
+
async with session.get(url) as response:
|
|
177
|
+
return await response.text()
|
|
178
|
+
|
|
179
|
+
# CORRECT: Offload blocking call to thread pool
|
|
180
|
+
async def run_blocking(func: Callable[..., R], *args: Any) -> R:
|
|
181
|
+
loop = asyncio.get_running_loop()
|
|
182
|
+
return await loop.run_in_executor(None, func, *args)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Fire-and-Forget Without Error Handling
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
# VIOLATION: Exception silently lost
|
|
189
|
+
async def bad_handler(event: Event) -> None:
|
|
190
|
+
asyncio.create_task(send_notification(event)) # Error vanishes
|
|
191
|
+
|
|
192
|
+
# CORRECT: Track the task and handle errors
|
|
193
|
+
async def good_handler(event: Event) -> None:
|
|
194
|
+
task = asyncio.create_task(send_notification(event))
|
|
195
|
+
task.add_done_callback(handle_task_exception)
|
|
196
|
+
|
|
197
|
+
def handle_task_exception(task: asyncio.Task[Any]) -> None:
|
|
198
|
+
if not task.cancelled() and task.exception() is not None:
|
|
199
|
+
logger.error("Background task failed", exc_info=task.exception())
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Sequential When Parallel Is Safe
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
# VIOLATION: Unnecessarily sequential — 3x slower
|
|
206
|
+
async def slow_load(user_id: str) -> Dashboard:
|
|
207
|
+
user = await fetch_user(user_id)
|
|
208
|
+
orders = await fetch_orders(user_id)
|
|
209
|
+
prefs = await fetch_preferences(user_id)
|
|
210
|
+
return Dashboard(user=user, orders=orders, preferences=prefs)
|
|
211
|
+
|
|
212
|
+
# CORRECT: Concurrent — ~1x latency
|
|
213
|
+
async def fast_load(user_id: str) -> Dashboard:
|
|
214
|
+
user, orders, prefs = await asyncio.gather(
|
|
215
|
+
fetch_user(user_id),
|
|
216
|
+
fetch_orders(user_id),
|
|
217
|
+
fetch_preferences(user_id),
|
|
218
|
+
)
|
|
219
|
+
return Dashboard(user=user, orders=orders, preferences=prefs)
|
|
220
|
+
```
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Python Issue Detection
|
|
2
|
+
|
|
3
|
+
Grep patterns for finding common Python issues. Reference from main SKILL.md.
|
|
4
|
+
|
|
5
|
+
## Type Safety Detection
|
|
6
|
+
|
|
7
|
+
### Missing Type Hints
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Functions without return type annotation
|
|
11
|
+
grep -rn "def .*):$" --include="*.py" | grep -v "->.*:"
|
|
12
|
+
|
|
13
|
+
# Functions without any annotations
|
|
14
|
+
grep -rn "def .*([a-z_][a-z_0-9]*\s*," --include="*.py" | grep -v ":"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Bare Except Clauses
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Bare except (catches everything)
|
|
21
|
+
grep -rn "except:" --include="*.py"
|
|
22
|
+
|
|
23
|
+
# Overly broad except Exception
|
|
24
|
+
grep -rn "except Exception:" --include="*.py" | grep -v "# noqa"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Data Modeling Detection
|
|
28
|
+
|
|
29
|
+
### Mutable Default Arguments
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# List defaults
|
|
33
|
+
grep -rn "def .*=\s*\[\]" --include="*.py"
|
|
34
|
+
|
|
35
|
+
# Dict defaults
|
|
36
|
+
grep -rn "def .*=\s*{}" --include="*.py"
|
|
37
|
+
|
|
38
|
+
# Set defaults
|
|
39
|
+
grep -rn "def .*=\s*set()" --include="*.py"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Raw Dict Usage Instead of Dataclasses
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Dict literals assigned to typed variables
|
|
46
|
+
grep -rn ': dict\[' --include="*.py" | grep "= {"
|
|
47
|
+
|
|
48
|
+
# Nested dict access patterns (fragile)
|
|
49
|
+
grep -rn '\["[a-z].*\]\["' --include="*.py"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Anti-Pattern Detection
|
|
53
|
+
|
|
54
|
+
### Assert in Production Code
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Assert statements outside test files
|
|
58
|
+
grep -rn "^ assert " --include="*.py" | grep -v "test_\|_test\.py\|tests/"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Global Mutable State
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Global variable assignment
|
|
65
|
+
grep -rn "^[a-z_].*= \[\]\|^[a-z_].*= {}\|^[a-z_].*= set()" --include="*.py"
|
|
66
|
+
|
|
67
|
+
# Global keyword usage
|
|
68
|
+
grep -rn "global " --include="*.py"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Old-Style String Formatting
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Percent formatting
|
|
75
|
+
grep -rn '"%.*" %' --include="*.py"
|
|
76
|
+
grep -rn "'%.*' %" --include="*.py"
|
|
77
|
+
|
|
78
|
+
# .format() where f-strings would be cleaner
|
|
79
|
+
grep -rn '\.format(' --include="*.py"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Wildcard Imports
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Star imports
|
|
86
|
+
grep -rn "from .* import \*" --include="*.py"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Async Detection
|
|
90
|
+
|
|
91
|
+
### Missing Await
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Coroutine called without await (common mistake)
|
|
95
|
+
grep -rn "async def" --include="*.py" -l | xargs grep -n "[^await ]fetch\|[^await ]save\|[^await ]send"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Blocking Calls in Async Code
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# time.sleep in async context
|
|
102
|
+
grep -rn "time\.sleep" --include="*.py"
|
|
103
|
+
|
|
104
|
+
# requests library in async files (should use aiohttp/httpx)
|
|
105
|
+
grep -rn "import requests\|from requests" --include="*.py"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Security Detection
|
|
109
|
+
|
|
110
|
+
### Unsafe Deserialization
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# pickle usage (arbitrary code execution risk)
|
|
114
|
+
grep -rn "pickle\.loads\|pickle\.load(" --include="*.py"
|
|
115
|
+
|
|
116
|
+
# yaml.load without SafeLoader
|
|
117
|
+
grep -rn "yaml\.load(" --include="*.py" | grep -v "SafeLoader\|safe_load"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Shell Injection
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# subprocess with shell=True
|
|
124
|
+
grep -rn "shell=True" --include="*.py"
|
|
125
|
+
|
|
126
|
+
# os.system calls
|
|
127
|
+
grep -rn "os\.system(" --include="*.py"
|
|
128
|
+
```
|