agentic-team-templates 0.12.0 → 0.13.1
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/README.md +19 -14
- package/package.json +1 -1
- package/src/index.js +9 -5
- package/src/index.test.js +2 -1
- package/templates/python-expert/.cursorrules/async-python.md +214 -0
- package/templates/python-expert/.cursorrules/overview.md +174 -0
- package/templates/python-expert/.cursorrules/patterns-and-idioms.md +251 -0
- package/templates/python-expert/.cursorrules/performance.md +208 -0
- package/templates/python-expert/.cursorrules/testing.md +238 -0
- package/templates/python-expert/.cursorrules/tooling.md +240 -0
- package/templates/python-expert/.cursorrules/type-system.md +203 -0
- package/templates/python-expert/.cursorrules/web-and-apis.md +231 -0
- package/templates/python-expert/CLAUDE.md +264 -0
package/README.md
CHANGED
|
@@ -147,20 +147,25 @@ npx agentic-team-templates --reset --force
|
|
|
147
147
|
|
|
148
148
|
| Template | Description |
|
|
149
149
|
|----------|-------------|
|
|
150
|
-
| `
|
|
151
|
-
| `
|
|
152
|
-
| `
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
157
|
-
| `
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
150
|
+
| `blockchain` | Smart contracts, DeFi protocols, and Web3 applications (Solidity, Foundry, Viem) |
|
|
151
|
+
| `cli-tools` | Command-line applications and developer tools (Cobra, Commander, Click) |
|
|
152
|
+
| `data-engineering` | Data platforms and pipelines (ETL, data modeling, data quality) |
|
|
153
|
+
| `devops-sre` | DevOps and SRE practices (incident management, observability, SLOs, chaos engineering) |
|
|
154
|
+
| `documentation` | Technical documentation standards (READMEs, API docs, ADRs, code comments) |
|
|
155
|
+
| `fullstack` | Full-stack web applications (Next.js, Nuxt, SvelteKit, Remix) |
|
|
156
|
+
| `golang-expert` | Principal-level Go engineering (concurrency, stdlib, production patterns, testing) |
|
|
157
|
+
| `javascript-expert` | Principal-level JavaScript engineering across Node.js, React, vanilla JS, and testing |
|
|
158
|
+
| `ml-ai` | Machine learning and AI systems (model development, deployment, monitoring) |
|
|
159
|
+
| `mobile` | Mobile applications (React Native, Flutter, native iOS/Android) |
|
|
160
|
+
| `platform-engineering` | Internal developer platforms, infrastructure automation, reliability engineering |
|
|
161
|
+
| `product-manager` | Product management with customer-centric discovery, prioritization, and execution |
|
|
162
|
+
| `python-expert` | Principal-level Python engineering (type system, async, testing, FastAPI, Django) |
|
|
163
|
+
| `qa-engineering` | Quality assurance programs for confident, rapid software delivery |
|
|
164
|
+
| `rust-expert` | Principal-level Rust engineering (ownership, concurrency, unsafe, traits, async) |
|
|
165
|
+
| `testing` | Comprehensive testing practices (TDD, test design, CI/CD integration, performance testing) |
|
|
166
|
+
| `utility-agent` | AI agent utilities with context management and hallucination prevention |
|
|
167
|
+
| `web-backend` | Backend APIs and services (REST, GraphQL, microservices) |
|
|
168
|
+
| `web-frontend` | Frontend web applications (SPAs, SSR, static sites, PWAs) |
|
|
164
169
|
|
|
165
170
|
## What Gets Installed
|
|
166
171
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-team-templates",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "AI coding assistant templates for Cursor IDE. Pre-configured rules and guidelines that help AI assistants write better code. - use at your own risk",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cursor",
|
package/src/index.js
CHANGED
|
@@ -60,10 +60,6 @@ const TEMPLATES = {
|
|
|
60
60
|
description: 'Mobile applications (React Native, Flutter, native iOS/Android)',
|
|
61
61
|
rules: ['navigation.md', 'offline-first.md', 'overview.md', 'performance.md', 'testing.md']
|
|
62
62
|
},
|
|
63
|
-
'rust-expert': {
|
|
64
|
-
description: 'Principal-level Rust engineering (ownership, concurrency, unsafe, traits, async)',
|
|
65
|
-
rules: ['concurrency.md', 'ecosystem-and-tooling.md', 'error-handling.md', 'overview.md', 'ownership-and-borrowing.md', 'performance-and-unsafe.md', 'testing.md', 'traits-and-generics.md']
|
|
66
|
-
},
|
|
67
63
|
'platform-engineering': {
|
|
68
64
|
description: 'Internal developer platforms, infrastructure automation, and reliability engineering',
|
|
69
65
|
rules: ['ci-cd.md', 'developer-experience.md', 'infrastructure-as-code.md', 'kubernetes.md', 'observability.md', 'overview.md', 'security.md', 'testing.md']
|
|
@@ -72,10 +68,18 @@ const TEMPLATES = {
|
|
|
72
68
|
description: 'Product management with customer-centric discovery, prioritization, and execution',
|
|
73
69
|
rules: ['communication.md', 'discovery.md', 'metrics.md', 'overview.md', 'prioritization.md', 'requirements.md']
|
|
74
70
|
},
|
|
71
|
+
'python-expert': {
|
|
72
|
+
description: 'Principal-level Python engineering (type system, async, testing, FastAPI, Django)',
|
|
73
|
+
rules: ['async-python.md', 'overview.md', 'patterns-and-idioms.md', 'performance.md', 'testing.md', 'tooling.md', 'type-system.md', 'web-and-apis.md']
|
|
74
|
+
},
|
|
75
75
|
'qa-engineering': {
|
|
76
76
|
description: 'Quality assurance programs for confident, rapid software delivery',
|
|
77
77
|
rules: ['automation.md', 'metrics.md', 'overview.md', 'quality-gates.md', 'test-design.md', 'test-strategy.md']
|
|
78
78
|
},
|
|
79
|
+
'rust-expert': {
|
|
80
|
+
description: 'Principal-level Rust engineering (ownership, concurrency, unsafe, traits, async)',
|
|
81
|
+
rules: ['concurrency.md', 'ecosystem-and-tooling.md', 'error-handling.md', 'overview.md', 'ownership-and-borrowing.md', 'performance-and-unsafe.md', 'testing.md', 'traits-and-generics.md']
|
|
82
|
+
},
|
|
79
83
|
'testing': {
|
|
80
84
|
description: 'Comprehensive testing practices (TDD, test design, CI/CD integration, performance testing)',
|
|
81
85
|
rules: ['advanced-techniques.md', 'ci-cd-integration.md', 'overview.md', 'performance-testing.md', 'quality-metrics.md', 'reliability.md', 'tdd-methodology.md', 'test-data.md', 'test-design.md', 'test-types.md']
|
|
@@ -160,7 +164,7 @@ async function checkForUpdates() {
|
|
|
160
164
|
function printBanner() {
|
|
161
165
|
console.log(colors.blue(`
|
|
162
166
|
╔═══════════════════════════════════════════════════════════╗
|
|
163
|
-
║
|
|
167
|
+
║ Agentic Team Templates Installer ║
|
|
164
168
|
╚═══════════════════════════════════════════════════════════╝
|
|
165
169
|
`));
|
|
166
170
|
}
|
package/src/index.test.js
CHANGED
|
@@ -83,10 +83,11 @@ describe('Constants', () => {
|
|
|
83
83
|
'javascript-expert',
|
|
84
84
|
'ml-ai',
|
|
85
85
|
'mobile',
|
|
86
|
-
'rust-expert',
|
|
87
86
|
'platform-engineering',
|
|
88
87
|
'product-manager',
|
|
88
|
+
'python-expert',
|
|
89
89
|
'qa-engineering',
|
|
90
|
+
'rust-expert',
|
|
90
91
|
'testing',
|
|
91
92
|
'utility-agent',
|
|
92
93
|
'web-backend',
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Async Python
|
|
2
|
+
|
|
3
|
+
Python's `asyncio` enables concurrent I/O-bound operations on a single thread. Understanding the event loop, coroutines, and task lifecycle is essential for building high-performance async services.
|
|
4
|
+
|
|
5
|
+
## Fundamentals
|
|
6
|
+
|
|
7
|
+
### Coroutines and Tasks
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import asyncio
|
|
11
|
+
|
|
12
|
+
# A coroutine is defined with async def
|
|
13
|
+
async def fetch_data(url: str) -> bytes:
|
|
14
|
+
async with aiohttp.ClientSession() as session:
|
|
15
|
+
async with session.get(url) as response:
|
|
16
|
+
return await response.read()
|
|
17
|
+
|
|
18
|
+
# Coroutines don't run until awaited or wrapped in a task
|
|
19
|
+
coro = fetch_data("https://example.com") # Nothing happens yet
|
|
20
|
+
data = await coro # Now it runs
|
|
21
|
+
|
|
22
|
+
# Tasks run concurrently on the event loop
|
|
23
|
+
async def fetch_all(urls: list[str]) -> list[bytes]:
|
|
24
|
+
tasks = [asyncio.create_task(fetch_data(url)) for url in urls]
|
|
25
|
+
return await asyncio.gather(*tasks)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### asyncio.gather vs TaskGroup
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
# gather — returns results in order, continues on individual failures (by default)
|
|
32
|
+
results = await asyncio.gather(
|
|
33
|
+
fetch_users(),
|
|
34
|
+
fetch_posts(),
|
|
35
|
+
fetch_comments(),
|
|
36
|
+
)
|
|
37
|
+
users, posts, comments = results
|
|
38
|
+
|
|
39
|
+
# TaskGroup (3.11+) — structured concurrency, cancels all on first failure
|
|
40
|
+
async with asyncio.TaskGroup() as tg:
|
|
41
|
+
user_task = tg.create_task(fetch_users())
|
|
42
|
+
post_task = tg.create_task(fetch_posts())
|
|
43
|
+
|
|
44
|
+
# All tasks complete or all cancelled if one fails
|
|
45
|
+
users = user_task.result()
|
|
46
|
+
posts = post_task.result()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Timeouts
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
# asyncio.timeout (3.11+)
|
|
53
|
+
async with asyncio.timeout(5.0):
|
|
54
|
+
data = await fetch_data(url)
|
|
55
|
+
|
|
56
|
+
# asyncio.wait_for (older approach)
|
|
57
|
+
try:
|
|
58
|
+
data = await asyncio.wait_for(fetch_data(url), timeout=5.0)
|
|
59
|
+
except asyncio.TimeoutError:
|
|
60
|
+
logger.warning("fetch timed out", extra={"url": url})
|
|
61
|
+
raise
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Bounded Concurrency
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
# Semaphore for limiting concurrent operations
|
|
68
|
+
async def fetch_all(urls: list[str], max_concurrent: int = 10) -> list[Response]:
|
|
69
|
+
semaphore = asyncio.Semaphore(max_concurrent)
|
|
70
|
+
|
|
71
|
+
async def bounded_fetch(url: str) -> Response:
|
|
72
|
+
async with semaphore:
|
|
73
|
+
return await fetch(url)
|
|
74
|
+
|
|
75
|
+
async with asyncio.TaskGroup() as tg:
|
|
76
|
+
tasks = [tg.create_task(bounded_fetch(url)) for url in urls]
|
|
77
|
+
|
|
78
|
+
return [task.result() for task in tasks]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Async Generators and Iteration
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from collections.abc import AsyncIterator
|
|
85
|
+
|
|
86
|
+
# Async generator
|
|
87
|
+
async def stream_records(query: str) -> AsyncIterator[Record]:
|
|
88
|
+
async with db.execute(query) as cursor:
|
|
89
|
+
async for row in cursor:
|
|
90
|
+
yield Record.from_row(row)
|
|
91
|
+
|
|
92
|
+
# Async for loop
|
|
93
|
+
async for record in stream_records("SELECT * FROM events"):
|
|
94
|
+
await process(record)
|
|
95
|
+
|
|
96
|
+
# Async comprehension
|
|
97
|
+
results = [item async for item in stream_records(query) if item.is_valid()]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Async Context Managers
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from contextlib import asynccontextmanager
|
|
104
|
+
from collections.abc import AsyncIterator
|
|
105
|
+
|
|
106
|
+
@asynccontextmanager
|
|
107
|
+
async def managed_pool(url: str, size: int = 10) -> AsyncIterator[Pool]:
|
|
108
|
+
pool = await create_pool(url, max_size=size)
|
|
109
|
+
try:
|
|
110
|
+
yield pool
|
|
111
|
+
finally:
|
|
112
|
+
await pool.close()
|
|
113
|
+
|
|
114
|
+
async def main() -> None:
|
|
115
|
+
async with managed_pool("postgresql://localhost/db") as pool:
|
|
116
|
+
async with pool.acquire() as conn:
|
|
117
|
+
result = await conn.fetch("SELECT 1")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Event Loop Patterns
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
# Main entry point
|
|
124
|
+
async def main() -> None:
|
|
125
|
+
...
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
asyncio.run(main())
|
|
129
|
+
|
|
130
|
+
# Running blocking code in async context
|
|
131
|
+
import functools
|
|
132
|
+
|
|
133
|
+
async def process_file(path: Path) -> Data:
|
|
134
|
+
# Run CPU-bound or blocking I/O in a thread pool
|
|
135
|
+
loop = asyncio.get_running_loop()
|
|
136
|
+
data = await loop.run_in_executor(
|
|
137
|
+
None, # Default ThreadPoolExecutor
|
|
138
|
+
functools.partial(parse_file, path),
|
|
139
|
+
)
|
|
140
|
+
return data
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Graceful Shutdown
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
import signal
|
|
147
|
+
|
|
148
|
+
async def main() -> None:
|
|
149
|
+
loop = asyncio.get_running_loop()
|
|
150
|
+
|
|
151
|
+
shutdown_event = asyncio.Event()
|
|
152
|
+
|
|
153
|
+
for sig in (signal.SIGTERM, signal.SIGINT):
|
|
154
|
+
loop.add_signal_handler(sig, shutdown_event.set)
|
|
155
|
+
|
|
156
|
+
server = await start_server()
|
|
157
|
+
|
|
158
|
+
await shutdown_event.wait()
|
|
159
|
+
|
|
160
|
+
logger.info("shutting down")
|
|
161
|
+
await server.stop()
|
|
162
|
+
# Clean up resources
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Async Testing
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
import pytest
|
|
169
|
+
|
|
170
|
+
@pytest.mark.asyncio
|
|
171
|
+
async def test_fetch_user() -> None:
|
|
172
|
+
service = UserService(fake_repo)
|
|
173
|
+
user = await service.get_by_id("123")
|
|
174
|
+
assert user.name == "Alice"
|
|
175
|
+
|
|
176
|
+
# Fixtures
|
|
177
|
+
@pytest.fixture
|
|
178
|
+
async def db_pool():
|
|
179
|
+
pool = await create_pool(TEST_DATABASE_URL)
|
|
180
|
+
yield pool
|
|
181
|
+
await pool.close()
|
|
182
|
+
|
|
183
|
+
@pytest.fixture
|
|
184
|
+
async def service(db_pool):
|
|
185
|
+
return UserService(UserRepo(db_pool))
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Anti-Patterns
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
# Never: Blocking calls in async code
|
|
192
|
+
async def bad_handler(request: Request) -> Response:
|
|
193
|
+
data = requests.get(url) # Blocks the event loop!
|
|
194
|
+
time.sleep(1) # Blocks the event loop!
|
|
195
|
+
# Use aiohttp and asyncio.sleep() instead
|
|
196
|
+
|
|
197
|
+
# Never: Creating event loops manually (unless you're a framework author)
|
|
198
|
+
loop = asyncio.new_event_loop() # Use asyncio.run() instead
|
|
199
|
+
|
|
200
|
+
# Never: Fire-and-forget tasks without error handling
|
|
201
|
+
asyncio.create_task(background_work()) # Who catches the exception?
|
|
202
|
+
# Better:
|
|
203
|
+
task = asyncio.create_task(background_work())
|
|
204
|
+
task.add_done_callback(handle_task_exception)
|
|
205
|
+
|
|
206
|
+
# Never: Mixing sync and async without run_in_executor
|
|
207
|
+
async def handler():
|
|
208
|
+
result = cpu_bound_work() # Blocks event loop
|
|
209
|
+
# Use: result = await loop.run_in_executor(None, cpu_bound_work)
|
|
210
|
+
|
|
211
|
+
# Never: Unbounded task spawning
|
|
212
|
+
for item in million_items:
|
|
213
|
+
asyncio.create_task(process(item)) # OOM risk — use semaphore
|
|
214
|
+
```
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Python Expert
|
|
2
|
+
|
|
3
|
+
Guidelines for principal-level Python engineering. Idiomatic Python, production systems, and deep interpreter knowledge.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
This ruleset applies to:
|
|
8
|
+
- Web services and APIs (Django, FastAPI, Flask)
|
|
9
|
+
- CLI tools and automation scripts
|
|
10
|
+
- Data processing and ETL pipelines
|
|
11
|
+
- Libraries and packages published to PyPI
|
|
12
|
+
- Machine learning infrastructure
|
|
13
|
+
- System administration and DevOps tooling
|
|
14
|
+
- Async services and event-driven architectures
|
|
15
|
+
|
|
16
|
+
## Core Philosophy
|
|
17
|
+
|
|
18
|
+
Python's power is in its clarity. The best Python code reads like well-written prose.
|
|
19
|
+
|
|
20
|
+
- **Readability counts.** The Zen of Python isn't decoration — it's the engineering standard. `import this` and mean it.
|
|
21
|
+
- **Explicit is better than implicit.** Type hints, clear names, no magic. If you need a comment to explain what a variable holds, rename the variable.
|
|
22
|
+
- **Simple is better than complex.** A straightforward `for` loop that's obvious beats a clever one-liner that requires a decoder ring.
|
|
23
|
+
- **Errors should never pass silently.** Bare `except:` is a bug. Swallowing exceptions hides the failures that will wake you up at 3 AM.
|
|
24
|
+
- **The standard library is rich — use it.** `collections`, `itertools`, `pathlib`, `dataclasses`, `contextlib`, `functools` — know them deeply before reaching for PyPI.
|
|
25
|
+
- **If you don't know, say so.** Python's ecosystem is vast. Admitting uncertainty about a niche library or CPython internal is honest and professional.
|
|
26
|
+
|
|
27
|
+
## Key Principles
|
|
28
|
+
|
|
29
|
+
### 1. Type Hints Are Not Optional
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
# Modern Python is typed Python. Type hints are documentation,
|
|
33
|
+
# tooling enablement, and bug prevention.
|
|
34
|
+
|
|
35
|
+
# Good: Fully typed function signatures
|
|
36
|
+
def fetch_users(
|
|
37
|
+
db: Database,
|
|
38
|
+
*,
|
|
39
|
+
limit: int = 100,
|
|
40
|
+
active_only: bool = True,
|
|
41
|
+
) -> list[User]:
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
# Good: Complex types with type aliases
|
|
45
|
+
type UserMap = dict[UserId, User]
|
|
46
|
+
type Handler = Callable[[Request], Awaitable[Response]]
|
|
47
|
+
|
|
48
|
+
# Bad: No type information
|
|
49
|
+
def fetch_users(db, limit=100, active_only=True):
|
|
50
|
+
... # What does this return? What's db? Nobody knows.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Dataclasses and Pydantic Over Raw Dicts
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
# Good: Structured data with validation
|
|
57
|
+
from dataclasses import dataclass, field
|
|
58
|
+
from datetime import datetime
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True, slots=True)
|
|
61
|
+
class User:
|
|
62
|
+
id: str
|
|
63
|
+
email: str
|
|
64
|
+
name: str
|
|
65
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
66
|
+
|
|
67
|
+
# Good: Pydantic for external data validation
|
|
68
|
+
from pydantic import BaseModel, EmailStr
|
|
69
|
+
|
|
70
|
+
class CreateUserRequest(BaseModel):
|
|
71
|
+
email: EmailStr
|
|
72
|
+
name: str = Field(min_length=1, max_length=200)
|
|
73
|
+
|
|
74
|
+
# Bad: Dict soup
|
|
75
|
+
user = {"id": "123", "email": "a@b.com"} # No validation, no autocomplete, no safety
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 3. EAFP Over LBYL
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
# Easier to Ask Forgiveness than Permission — Pythonic
|
|
82
|
+
# Good: Try it and handle the exception
|
|
83
|
+
try:
|
|
84
|
+
value = mapping[key]
|
|
85
|
+
except KeyError:
|
|
86
|
+
value = default
|
|
87
|
+
|
|
88
|
+
# Also good: Use .get() when it's clearer
|
|
89
|
+
value = mapping.get(key, default)
|
|
90
|
+
|
|
91
|
+
# Bad: Look Before You Leap (extra lookup, race conditions)
|
|
92
|
+
if key in mapping:
|
|
93
|
+
value = mapping[key] # What if it was deleted between check and access?
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 4. Context Managers for Resource Management
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
# Always use context managers for resources
|
|
100
|
+
# Good:
|
|
101
|
+
with open("data.json") as f:
|
|
102
|
+
data = json.load(f)
|
|
103
|
+
|
|
104
|
+
async with aiohttp.ClientSession() as session:
|
|
105
|
+
response = await session.get(url)
|
|
106
|
+
|
|
107
|
+
# Good: Custom context managers
|
|
108
|
+
from contextlib import contextmanager
|
|
109
|
+
|
|
110
|
+
@contextmanager
|
|
111
|
+
def temporary_directory():
|
|
112
|
+
path = Path(tempfile.mkdtemp())
|
|
113
|
+
try:
|
|
114
|
+
yield path
|
|
115
|
+
finally:
|
|
116
|
+
shutil.rmtree(path)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Project Structure
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
project/
|
|
123
|
+
├── src/
|
|
124
|
+
│ └── mypackage/
|
|
125
|
+
│ ├── __init__.py
|
|
126
|
+
│ ├── py.typed # PEP 561 marker
|
|
127
|
+
│ ├── config.py # Configuration loading
|
|
128
|
+
│ ├── exceptions.py # Custom exceptions
|
|
129
|
+
│ ├── models/ # Domain models
|
|
130
|
+
│ │ ├── __init__.py
|
|
131
|
+
│ │ └── user.py
|
|
132
|
+
│ ├── services/ # Business logic
|
|
133
|
+
│ │ ├── __init__.py
|
|
134
|
+
│ │ └── user_service.py
|
|
135
|
+
│ ├── repositories/ # Data access
|
|
136
|
+
│ │ ├── __init__.py
|
|
137
|
+
│ │ └── user_repo.py
|
|
138
|
+
│ └── api/ # HTTP/CLI entry points
|
|
139
|
+
│ ├── __init__.py
|
|
140
|
+
│ └── routes.py
|
|
141
|
+
├── tests/
|
|
142
|
+
│ ├── conftest.py # Shared fixtures
|
|
143
|
+
│ ├── unit/
|
|
144
|
+
│ │ └── test_user_service.py
|
|
145
|
+
│ ├── integration/
|
|
146
|
+
│ │ └── test_user_repo.py
|
|
147
|
+
│ └── e2e/
|
|
148
|
+
│ └── test_api.py
|
|
149
|
+
├── pyproject.toml # Single source of truth for project config
|
|
150
|
+
├── Makefile
|
|
151
|
+
└── .python-version # Pin Python version
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Layout Rules
|
|
155
|
+
|
|
156
|
+
- Use `src/` layout — it prevents accidental imports of uninstalled code
|
|
157
|
+
- `pyproject.toml` is the single config file — no `setup.py`, no `setup.cfg`, no `requirements.txt` for deps
|
|
158
|
+
- Tests mirror source structure but live outside `src/`
|
|
159
|
+
- One concern per module — don't dump everything in `utils.py`
|
|
160
|
+
- `__init__.py` defines the public API for each package
|
|
161
|
+
|
|
162
|
+
## Definition of Done
|
|
163
|
+
|
|
164
|
+
A Python feature is complete when:
|
|
165
|
+
- [ ] `mypy --strict` passes with zero errors
|
|
166
|
+
- [ ] `ruff check` passes with zero warnings
|
|
167
|
+
- [ ] `ruff format --check` passes
|
|
168
|
+
- [ ] `pytest` passes with no failures
|
|
169
|
+
- [ ] Test coverage covers meaningful behavior (not just lines)
|
|
170
|
+
- [ ] Error cases are tested, not just happy paths
|
|
171
|
+
- [ ] Docstrings on all public functions and classes
|
|
172
|
+
- [ ] No bare `except:` or `except Exception:` without re-raise
|
|
173
|
+
- [ ] No mutable default arguments
|
|
174
|
+
- [ ] No `# type: ignore` without an inline explanation
|