claudify-agent 1.0.1 → 1.0.2
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/.claude/QUICK-REFERENCE.md +213 -213
- package/.claude/cost-dashboard.md +162 -162
- package/.claude/docs/COST-OPTIMIZATION.md +402 -403
- package/.claude/docs/MCP-INTEGRATION-GUIDE.md +348 -348
- package/.claude/docs/PLAN-MODE-OPTIMIZATION.md +835 -836
- package/.claude/hooks/auto-learning-review.ts +200 -195
- package/.claude/hooks/auto-lint-fix.ts +60 -104
- package/.claude/hooks/auto-security-review.ts +314 -354
- package/.claude/hooks/auto-validate.ts +249 -348
- package/.claude/hooks/lib/claude-hook.ts +168 -0
- package/.claude/hooks/plan-quality-tracker.ts +598 -719
- package/.claude/rules/anti-patterns.md +22 -22
- package/.claude/rules/context-tier-1.md +41 -41
- package/.claude/rules/context-tier-2.md +285 -285
- package/.claude/rules/general.md +25 -25
- package/.claude/rules/mcp-auto-use.md +46 -46
- package/.claude/rules/parallel-exploration-auto.md +30 -31
- package/.claude/rules/plan-mode-execution.md +289 -289
- package/.claude/rules/plan-mode-research.md +34 -34
- package/.claude/rules/plan-mode-structure.md +166 -166
- package/.claude/rules/plan-mode-validation.md +129 -130
- package/.claude/rules/security-critical.md +19 -19
- package/.claude/rules/tool-auto-selection.md +45 -45
- package/.claude/rules/tool-strategy.md +22 -22
- package/.claude/scripts/security-scan.js +1 -1
- package/.claude/security-dashboard.md +142 -142
- package/.claude/settings.json +50 -0
- package/.claude/skills/debug-optimizer/SKILL.md +420 -399
- package/.claude/skills/learning-loop/SKILL.md +313 -308
- package/.claude/skills/mcp-mastery/SKILL.md +218 -214
- package/.claude/skills/nextjs-guide/SKILL.md +687 -687
- package/.claude/skills/parallel-exploration/SKILL.md +197 -193
- package/.claude/skills/plan-mode-mastery/SKILL.md +268 -263
- package/.claude/skills/python-guide/SKILL.md +822 -822
- package/.claude/skills/react-guide/SKILL.md +762 -762
- package/.claude/skills/{security.md → security-guide/SKILL.md} +10 -0
- package/.claude/skills/solidity-guide/SKILL.md +5 -0
- package/.claude/skills/tool-selection/SKILL.md +433 -428
- package/.claude/skills/vite-guide/SKILL.md +445 -445
- package/CLAUDE.md +1 -0
- package/README.md +74 -35
- package/README.vi.md +106 -0
- package/bin/cli.js +13 -4
- package/package.json +9 -1
- package/.claude/skills/debug-optimizer.md +0 -12
- package/.claude/skills/nextjs.md +0 -13
- package/.claude/skills/react.md +0 -12
|
@@ -1,822 +1,822 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: python-guide
|
|
3
|
-
description: Apply Python best practices including PEP 8, clean code, testing, and project structure. Use when writing, reviewing, or refactoring Python code.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Python Best Practices
|
|
7
|
-
|
|
8
|
-
## Quick Start
|
|
9
|
-
|
|
10
|
-
When working with Python code, follow these core principles:
|
|
11
|
-
|
|
12
|
-
1. **Follow PEP 8**: Use proper indentation, naming, and formatting
|
|
13
|
-
2. **Write tests**: Use pytest with >80% coverage
|
|
14
|
-
3. **Document**: Add docstrings and type hints
|
|
15
|
-
4. **Structure properly**: Separate concerns (routers, services, models)
|
|
16
|
-
5. **Validate early**: Use configuration validation at startup
|
|
17
|
-
6. **Automate quality**: Use Ruff, Black, mypy, pytest
|
|
18
|
-
|
|
19
|
-
## Code Style & Formatting
|
|
20
|
-
|
|
21
|
-
### PEP 8 Essentials
|
|
22
|
-
|
|
23
|
-
**Indentation**:
|
|
24
|
-
```python
|
|
25
|
-
# ✅ Good: 4 spaces
|
|
26
|
-
def calculate_total(items):
|
|
27
|
-
total = 0
|
|
28
|
-
for item in items:
|
|
29
|
-
total += item.price
|
|
30
|
-
return total
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
**Line Length**:
|
|
34
|
-
```python
|
|
35
|
-
# ✅ Good: Max 79 characters (code), 72 (docstrings)
|
|
36
|
-
result = (
|
|
37
|
-
some_long_variable_name
|
|
38
|
-
+ another_long_variable_name
|
|
39
|
-
+ third_long_variable_name
|
|
40
|
-
)
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
**Blank Lines**:
|
|
44
|
-
```python
|
|
45
|
-
# ✅ Good: 2 blank lines between top-level, 1 between methods
|
|
46
|
-
class UserService:
|
|
47
|
-
def __init__(self):
|
|
48
|
-
pass
|
|
49
|
-
|
|
50
|
-
def create_user(self):
|
|
51
|
-
pass
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def helper_function():
|
|
55
|
-
pass
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Naming Conventions
|
|
59
|
-
|
|
60
|
-
```python
|
|
61
|
-
# ✅ Good: snake_case for functions/variables
|
|
62
|
-
def calculate_total():
|
|
63
|
-
pass
|
|
64
|
-
|
|
65
|
-
user_count = 0
|
|
66
|
-
|
|
67
|
-
# ✅ Good: PascalCase for classes
|
|
68
|
-
class UserProfile:
|
|
69
|
-
pass
|
|
70
|
-
|
|
71
|
-
# ✅ Good: UPPERCASE for constants
|
|
72
|
-
MAX_RETRIES = 3
|
|
73
|
-
DEFAULT_TIMEOUT = 30
|
|
74
|
-
|
|
75
|
-
# ✅ Good: _prefix for private, __prefix for name mangling
|
|
76
|
-
class DataProcessor:
|
|
77
|
-
def _internal_method(self):
|
|
78
|
-
pass
|
|
79
|
-
|
|
80
|
-
def __private_method(self):
|
|
81
|
-
pass
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### Imports
|
|
85
|
-
|
|
86
|
-
```python
|
|
87
|
-
# ✅ Good: Standard library first, then third-party, then local
|
|
88
|
-
import os
|
|
89
|
-
import sys
|
|
90
|
-
from typing import List, Optional
|
|
91
|
-
|
|
92
|
-
import requests
|
|
93
|
-
from fastapi import FastAPI
|
|
94
|
-
|
|
95
|
-
from app.models import User
|
|
96
|
-
from app.services import UserService
|
|
97
|
-
|
|
98
|
-
# ✅ Good: Use explicit imports
|
|
99
|
-
from collections import OrderedDict # Not: from collections import *
|
|
100
|
-
|
|
101
|
-
# ✅ Good: Handle circular imports with TYPE_CHECKING
|
|
102
|
-
from typing import TYPE_CHECKING
|
|
103
|
-
|
|
104
|
-
if TYPE_CHECKING:
|
|
105
|
-
from app.models import User
|
|
106
|
-
|
|
107
|
-
def process_user(user: 'User'):
|
|
108
|
-
pass
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## Type Hints
|
|
112
|
-
|
|
113
|
-
### Basic Types
|
|
114
|
-
|
|
115
|
-
```python
|
|
116
|
-
# ✅ Good: Use type hints for all functions
|
|
117
|
-
def greet(name: str, age: int) -> str:
|
|
118
|
-
return f"Hello {name}, age {age}"
|
|
119
|
-
|
|
120
|
-
# ✅ Good: Use Optional for nullable
|
|
121
|
-
from typing import Optional
|
|
122
|
-
|
|
123
|
-
def find_user(user_id: int) -> Optional[User]:
|
|
124
|
-
pass
|
|
125
|
-
|
|
126
|
-
# ✅ Good: Use Union for multiple types
|
|
127
|
-
from typing import Union
|
|
128
|
-
|
|
129
|
-
def parse_value(value: Union[str, int, float]) -> Any:
|
|
130
|
-
pass
|
|
131
|
-
|
|
132
|
-
# ✅ Good: Use List, Dict, etc. from typing
|
|
133
|
-
from typing import List, Dict
|
|
134
|
-
|
|
135
|
-
def process_items(items: List[str]) -> Dict[str, int]:
|
|
136
|
-
pass
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### Advanced Types
|
|
140
|
-
|
|
141
|
-
```python
|
|
142
|
-
# ✅ Good: Use Protocol for structural typing
|
|
143
|
-
from typing import Protocol
|
|
144
|
-
|
|
145
|
-
class Drawable(Protocol):
|
|
146
|
-
def draw(self) -> None:
|
|
147
|
-
...
|
|
148
|
-
|
|
149
|
-
def render(shape: Drawable):
|
|
150
|
-
shape.draw()
|
|
151
|
-
|
|
152
|
-
# ✅ Good: Use TypedDict for dictionaries
|
|
153
|
-
from typing import TypedDict
|
|
154
|
-
|
|
155
|
-
class UserConfig(TypedDict):
|
|
156
|
-
username: str
|
|
157
|
-
email: str
|
|
158
|
-
age: int
|
|
159
|
-
|
|
160
|
-
# ✅ Good: Use Literal for specific values
|
|
161
|
-
from typing import Literal
|
|
162
|
-
|
|
163
|
-
def set_status(status: Literal["active", "inactive", "pending"]):
|
|
164
|
-
pass
|
|
165
|
-
|
|
166
|
-
# ✅ Good: Use Callable for function types
|
|
167
|
-
from typing import Callable
|
|
168
|
-
|
|
169
|
-
def process_data(
|
|
170
|
-
data: List[int],
|
|
171
|
-
transformer: Callable[[int], int]
|
|
172
|
-
) -> List[int]:
|
|
173
|
-
return [transformer(x) for x in data]
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## Project Structure
|
|
177
|
-
|
|
178
|
-
### Production-Ready Layout
|
|
179
|
-
|
|
180
|
-
```
|
|
181
|
-
project/
|
|
182
|
-
├── app/
|
|
183
|
-
│ ├── __init__.py
|
|
184
|
-
│ ├── main.py # Application entry point
|
|
185
|
-
│ ├── api/ # API routes
|
|
186
|
-
│ │ ├── __init__.py
|
|
187
|
-
│ │ ├── routes/
|
|
188
|
-
│ │ │ ├── users.py
|
|
189
|
-
│ │ │ └── products.py
|
|
190
|
-
│ │ └── deps.py # Dependencies
|
|
191
|
-
│ ├── core/ # Core configuration
|
|
192
|
-
│ │ ├── __init__.py
|
|
193
|
-
│ │ ├── config.py # Settings
|
|
194
|
-
│ │ ├── security.py # Auth/security
|
|
195
|
-
│ │ └── logging.py # Logging config
|
|
196
|
-
│ ├── models/ # Database models
|
|
197
|
-
│ │ ├── __init__.py
|
|
198
|
-
│ │ ├── user.py
|
|
199
|
-
│ │ └── product.py
|
|
200
|
-
│ ├── schemas/ # Pydantic schemas
|
|
201
|
-
│ │ ├── __init__.py
|
|
202
|
-
│ │ ├── user.py
|
|
203
|
-
│ │ └── product.py
|
|
204
|
-
│ ├── services/ # Business logic
|
|
205
|
-
│ │ ├── __init__.py
|
|
206
|
-
│ │ ├── user_service.py
|
|
207
|
-
│ │ └── product_service.py
|
|
208
|
-
│ ├── repositories/ # Data access
|
|
209
|
-
│ │ ├── __init__.py
|
|
210
|
-
│ │ ├── user_repo.py
|
|
211
|
-
│ │ └── product_repo.py
|
|
212
|
-
│ └── utils/ # Utilities
|
|
213
|
-
│ ├── __init__.py
|
|
214
|
-
│ └── helpers.py
|
|
215
|
-
├── tests/ # Test files
|
|
216
|
-
│ ├── __init__.py
|
|
217
|
-
│ ├── conftest.py # Shared fixtures
|
|
218
|
-
│ ├── test_users.py
|
|
219
|
-
│ └── test_products.py
|
|
220
|
-
├── scripts/ # Utility scripts
|
|
221
|
-
│ ├── setup_db.py
|
|
222
|
-
│ └── migrate.py
|
|
223
|
-
├── .env # Environment variables
|
|
224
|
-
├── .env.example # Template
|
|
225
|
-
├── pyproject.toml # Dependencies
|
|
226
|
-
├── pytest.ini # Test config
|
|
227
|
-
├── mypy.ini # Type checking
|
|
228
|
-
├── ruff.toml # Linting
|
|
229
|
-
└── README.md
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
### Configuration Management
|
|
233
|
-
|
|
234
|
-
```python
|
|
235
|
-
# ✅ Good: Use Pydantic Settings
|
|
236
|
-
from pydantic_settings import BaseSettings
|
|
237
|
-
from functools import lru_cache
|
|
238
|
-
|
|
239
|
-
class Settings(BaseSettings):
|
|
240
|
-
app_name: str = "My App"
|
|
241
|
-
debug: bool = False
|
|
242
|
-
database_url: str
|
|
243
|
-
secret_key: str
|
|
244
|
-
max_connections: int = 100
|
|
245
|
-
|
|
246
|
-
class Config:
|
|
247
|
-
env_file = ".env"
|
|
248
|
-
|
|
249
|
-
@lru_cache()
|
|
250
|
-
def get_settings() -> Settings:
|
|
251
|
-
return Settings()
|
|
252
|
-
|
|
253
|
-
# Usage
|
|
254
|
-
settings = get_settings()
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
## Documentation
|
|
258
|
-
|
|
259
|
-
### Docstrings
|
|
260
|
-
|
|
261
|
-
```python
|
|
262
|
-
# ✅ Good: Google style docstrings
|
|
263
|
-
def calculate_total(
|
|
264
|
-
items: List[Item],
|
|
265
|
-
tax_rate: float = 0.1
|
|
266
|
-
) -> float:
|
|
267
|
-
"""
|
|
268
|
-
Calculate total price including tax.
|
|
269
|
-
|
|
270
|
-
Args:
|
|
271
|
-
items: List of items to calculate total for
|
|
272
|
-
tax_rate: Tax rate as decimal (default: 0.1)
|
|
273
|
-
|
|
274
|
-
Returns:
|
|
275
|
-
Total price including tax
|
|
276
|
-
|
|
277
|
-
Raises:
|
|
278
|
-
ValueError: If items list is empty or tax_rate is negative
|
|
279
|
-
|
|
280
|
-
Example:
|
|
281
|
-
>>> items = [Item(price=100), Item(price=200)]
|
|
282
|
-
>>> calculate_total(items, 0.1)
|
|
283
|
-
330.0
|
|
284
|
-
"""
|
|
285
|
-
if not items:
|
|
286
|
-
raise ValueError("Items list cannot be empty")
|
|
287
|
-
if tax_rate < 0:
|
|
288
|
-
raise ValueError("Tax rate must be non-negative")
|
|
289
|
-
|
|
290
|
-
subtotal = sum(item.price for item in items)
|
|
291
|
-
return subtotal * (1 + tax_rate)
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
### Module Docstrings
|
|
295
|
-
|
|
296
|
-
```python
|
|
297
|
-
"""
|
|
298
|
-
User management module.
|
|
299
|
-
|
|
300
|
-
This module provides user-related business logic including:
|
|
301
|
-
- User creation and validation
|
|
302
|
-
- Authentication and authorization
|
|
303
|
-
- Profile management
|
|
304
|
-
|
|
305
|
-
Example:
|
|
306
|
-
from app.services import UserService
|
|
307
|
-
|
|
308
|
-
service = UserService()
|
|
309
|
-
user = service.create_user("john", "john@example.com")
|
|
310
|
-
"""
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
## Testing
|
|
314
|
-
|
|
315
|
-
### Test Structure
|
|
316
|
-
|
|
317
|
-
```python
|
|
318
|
-
# tests/test_users.py
|
|
319
|
-
import pytest
|
|
320
|
-
from app.services import UserService
|
|
321
|
-
from app.models import User
|
|
322
|
-
|
|
323
|
-
class TestUserService:
|
|
324
|
-
"""Test cases for UserService."""
|
|
325
|
-
|
|
326
|
-
@pytest.fixture
|
|
327
|
-
def user_service(self):
|
|
328
|
-
"""Create UserService instance."""
|
|
329
|
-
return UserService()
|
|
330
|
-
|
|
331
|
-
def test_create_user_success(self, user_service):
|
|
332
|
-
"""Should create user with valid data."""
|
|
333
|
-
user = user_service.create_user(
|
|
334
|
-
username="john",
|
|
335
|
-
email="john@example.com"
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
assert user.username == "john"
|
|
339
|
-
assert user.email == "john@example.com"
|
|
340
|
-
assert user.id is not None
|
|
341
|
-
|
|
342
|
-
def test_create_user_duplicate_email(self, user_service):
|
|
343
|
-
"""Should raise error for duplicate email."""
|
|
344
|
-
user_service.create_user(
|
|
345
|
-
username="john",
|
|
346
|
-
email="john@example.com"
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
with pytest.raises(ValueError, match="Email already exists"):
|
|
350
|
-
user_service.create_user(
|
|
351
|
-
username="jane",
|
|
352
|
-
email="john@example.com"
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
-
def test_create_user_invalid_email(self, user_service):
|
|
356
|
-
"""Should raise error for invalid email."""
|
|
357
|
-
with pytest.raises(ValueError, match="Invalid email"):
|
|
358
|
-
user_service.create_user(
|
|
359
|
-
username="john",
|
|
360
|
-
email="invalid-email"
|
|
361
|
-
)
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
### Test Fixtures (conftest.py)
|
|
365
|
-
|
|
366
|
-
```python
|
|
367
|
-
# tests/conftest.py
|
|
368
|
-
import pytest
|
|
369
|
-
from app.models import User
|
|
370
|
-
from app.services import UserService
|
|
371
|
-
|
|
372
|
-
@pytest.fixture
|
|
373
|
-
def sample_user():
|
|
374
|
-
"""Create sample user for testing."""
|
|
375
|
-
return User(
|
|
376
|
-
id=1,
|
|
377
|
-
username="testuser",
|
|
378
|
-
email="test@example.com"
|
|
379
|
-
)
|
|
380
|
-
|
|
381
|
-
@pytest.fixture
|
|
382
|
-
def user_service():
|
|
383
|
-
"""Create UserService with test configuration."""
|
|
384
|
-
return UserService(test_mode=True)
|
|
385
|
-
|
|
386
|
-
@pytest.fixture
|
|
387
|
-
def mock_database(mocker):
|
|
388
|
-
"""Mock database operations."""
|
|
389
|
-
mock = mocker.patch('app.db.database')
|
|
390
|
-
mock.execute.return_value = []
|
|
391
|
-
return mock
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
### Test Coverage
|
|
395
|
-
|
|
396
|
-
```bash
|
|
397
|
-
# Run tests with coverage
|
|
398
|
-
pytest --cov=app --cov-report=html
|
|
399
|
-
|
|
400
|
-
# Minimum coverage requirement
|
|
401
|
-
pytest --cov=app --cov-fail-under=80
|
|
402
|
-
|
|
403
|
-
# Run specific test file
|
|
404
|
-
pytest tests/test_users.py -v
|
|
405
|
-
|
|
406
|
-
# Run with markers
|
|
407
|
-
pytest -m slow # or -m fast, -m integration
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
## Error Handling
|
|
411
|
-
|
|
412
|
-
### Custom Exceptions
|
|
413
|
-
|
|
414
|
-
```python
|
|
415
|
-
# app/exceptions.py
|
|
416
|
-
class AppException(Exception):
|
|
417
|
-
"""Base exception for application."""
|
|
418
|
-
def __init__(self, message: str, code: str = None):
|
|
419
|
-
self.message = message
|
|
420
|
-
self.code = code or self.__class__.__name__
|
|
421
|
-
super().__init__(self.message)
|
|
422
|
-
|
|
423
|
-
class UserNotFoundException(AppException):
|
|
424
|
-
"""Raised when user is not found."""
|
|
425
|
-
def __init__(self, user_id: int):
|
|
426
|
-
super().__init__(
|
|
427
|
-
message=f"User with id {user_id} not found",
|
|
428
|
-
code="USER_NOT_FOUND"
|
|
429
|
-
)
|
|
430
|
-
|
|
431
|
-
class ValidationError(AppException):
|
|
432
|
-
"""Raised when validation fails."""
|
|
433
|
-
def __init__(self, message: str, field: str = None):
|
|
434
|
-
super().__init__(message, "VALIDATION_ERROR")
|
|
435
|
-
self.field = field
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
### Error Handling Patterns
|
|
439
|
-
|
|
440
|
-
```python
|
|
441
|
-
# ✅ Good: Specific exception handling
|
|
442
|
-
from app.exceptions import UserNotFoundException, ValidationError
|
|
443
|
-
|
|
444
|
-
def get_user(user_id: int) -> User:
|
|
445
|
-
try:
|
|
446
|
-
user = user_repo.find_by_id(user_id)
|
|
447
|
-
if not user:
|
|
448
|
-
raise UserNotFoundException(user_id)
|
|
449
|
-
return user
|
|
450
|
-
except UserNotFoundException:
|
|
451
|
-
raise
|
|
452
|
-
except Exception as e:
|
|
453
|
-
logger.error(f"Failed to get user {user_id}: {e}")
|
|
454
|
-
raise AppException("Failed to retrieve user")
|
|
455
|
-
|
|
456
|
-
# ✅ Good: Context managers for resources
|
|
457
|
-
from contextlib import contextmanager
|
|
458
|
-
|
|
459
|
-
@contextmanager
|
|
460
|
-
def database_transaction():
|
|
461
|
-
"""Manage database transaction."""
|
|
462
|
-
conn = get_connection()
|
|
463
|
-
try:
|
|
464
|
-
conn.begin()
|
|
465
|
-
yield conn
|
|
466
|
-
conn.commit()
|
|
467
|
-
except Exception:
|
|
468
|
-
conn.rollback()
|
|
469
|
-
raise
|
|
470
|
-
finally:
|
|
471
|
-
conn.close()
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
## Design Patterns
|
|
475
|
-
|
|
476
|
-
### Repository Pattern
|
|
477
|
-
|
|
478
|
-
```python
|
|
479
|
-
# app/repositories/base.py
|
|
480
|
-
from abc import ABC, abstractmethod
|
|
481
|
-
from typing import Generic, TypeVar, List, Optional
|
|
482
|
-
|
|
483
|
-
T = TypeVar('T')
|
|
484
|
-
|
|
485
|
-
class BaseRepository(ABC, Generic[T]):
|
|
486
|
-
"""Base repository with CRUD operations."""
|
|
487
|
-
|
|
488
|
-
@abstractmethod
|
|
489
|
-
def find_by_id(self, id: int) -> Optional[T]:
|
|
490
|
-
pass
|
|
491
|
-
|
|
492
|
-
@abstractmethod
|
|
493
|
-
def find_all(self) -> List[T]:
|
|
494
|
-
pass
|
|
495
|
-
|
|
496
|
-
@abstractmethod
|
|
497
|
-
def create(self, entity: T) -> T:
|
|
498
|
-
pass
|
|
499
|
-
|
|
500
|
-
@abstractmethod
|
|
501
|
-
def update(self, id: int, entity: T) -> T:
|
|
502
|
-
pass
|
|
503
|
-
|
|
504
|
-
@abstractmethod
|
|
505
|
-
def delete(self, id: int) -> bool:
|
|
506
|
-
pass
|
|
507
|
-
|
|
508
|
-
# app/repositories/user_repository.py
|
|
509
|
-
class UserRepository(BaseRepository[User]):
|
|
510
|
-
"""User-specific repository."""
|
|
511
|
-
|
|
512
|
-
def find_by_id(self, id: int) -> Optional[User]:
|
|
513
|
-
# Implementation
|
|
514
|
-
pass
|
|
515
|
-
|
|
516
|
-
def find_by_email(self, email: str) -> Optional[User]:
|
|
517
|
-
# Implementation
|
|
518
|
-
pass
|
|
519
|
-
|
|
520
|
-
def find_active_users(self) -> List[User]:
|
|
521
|
-
# Implementation
|
|
522
|
-
pass
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
### Service Layer Pattern
|
|
526
|
-
|
|
527
|
-
```python
|
|
528
|
-
# app/services/user_service.py
|
|
529
|
-
from app.repositories import UserRepository
|
|
530
|
-
from app.schemas import UserCreate, UserUpdate
|
|
531
|
-
from app.models import User
|
|
532
|
-
|
|
533
|
-
class UserService:
|
|
534
|
-
"""Business logic for user operations."""
|
|
535
|
-
|
|
536
|
-
def __init__(self, user_repo: UserRepository):
|
|
537
|
-
self.user_repo = user_repo
|
|
538
|
-
|
|
539
|
-
def create_user(self, data: UserCreate) -> User:
|
|
540
|
-
"""Create new user with validation."""
|
|
541
|
-
# Validate email uniqueness
|
|
542
|
-
existing = self.user_repo.find_by_email(data.email)
|
|
543
|
-
if existing:
|
|
544
|
-
raise ValidationError("Email already exists", "email")
|
|
545
|
-
|
|
546
|
-
# Create user
|
|
547
|
-
user = User(
|
|
548
|
-
username=data.username,
|
|
549
|
-
email=data.email,
|
|
550
|
-
hashed_password=hash_password(data.password)
|
|
551
|
-
)
|
|
552
|
-
|
|
553
|
-
return self.user_repo.create(user)
|
|
554
|
-
|
|
555
|
-
def update_user(self, user_id: int, data: UserUpdate) -> User:
|
|
556
|
-
"""Update existing user."""
|
|
557
|
-
user = self.user_repo.find_by_id(user_id)
|
|
558
|
-
if not user:
|
|
559
|
-
raise UserNotFoundException(user_id)
|
|
560
|
-
|
|
561
|
-
# Update fields
|
|
562
|
-
if data.username:
|
|
563
|
-
user.username = data.username
|
|
564
|
-
if data.email:
|
|
565
|
-
user.email = data.email
|
|
566
|
-
|
|
567
|
-
return self.user_repo.update(user_id, user)
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
### Dependency Injection
|
|
571
|
-
|
|
572
|
-
```python
|
|
573
|
-
# app/api/deps.py
|
|
574
|
-
from functools import lru_cache
|
|
575
|
-
from app.repositories import UserRepository
|
|
576
|
-
from app.services import UserService
|
|
577
|
-
from app.db import get_db
|
|
578
|
-
|
|
579
|
-
@lru_cache()
|
|
580
|
-
def get_user_repository() -> UserRepository:
|
|
581
|
-
"""Get UserRepository instance."""
|
|
582
|
-
return UserRepository(get_db())
|
|
583
|
-
|
|
584
|
-
@lru_cache()
|
|
585
|
-
def get_user_service() -> UserService:
|
|
586
|
-
"""Get UserService instance."""
|
|
587
|
-
return UserService(get_user_repository())
|
|
588
|
-
|
|
589
|
-
# Usage in API routes
|
|
590
|
-
from fastapi import Depends
|
|
591
|
-
|
|
592
|
-
@app.get("/users/{user_id}")
|
|
593
|
-
def get_user(
|
|
594
|
-
user_id: int,
|
|
595
|
-
service: UserService = Depends(get_user_service)
|
|
596
|
-
):
|
|
597
|
-
user = service.get_user(user_id)
|
|
598
|
-
return user
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
## Code Quality Tools
|
|
602
|
-
|
|
603
|
-
### Ruff Configuration (ruff.toml)
|
|
604
|
-
|
|
605
|
-
```toml
|
|
606
|
-
[lint]
|
|
607
|
-
select = [
|
|
608
|
-
"E", # pycodestyle errors
|
|
609
|
-
"W", # pycodestyle warnings
|
|
610
|
-
"F", # Pyflakes
|
|
611
|
-
"I", # isort
|
|
612
|
-
"B", # flake8-bugbear
|
|
613
|
-
"C4", # flake8-comprehensions
|
|
614
|
-
"UP", # pyupgrade
|
|
615
|
-
]
|
|
616
|
-
|
|
617
|
-
ignore = [
|
|
618
|
-
"E501", # Line too long (handled by formatter)
|
|
619
|
-
]
|
|
620
|
-
|
|
621
|
-
[lint.per-file-ignores]
|
|
622
|
-
"__init__.py" = ["F401"] # Unused imports OK in __init__
|
|
623
|
-
"tests/*" = ["S101"] # Assert OK in tests
|
|
624
|
-
|
|
625
|
-
[format]
|
|
626
|
-
quote-style = "double"
|
|
627
|
-
indent-style = "space"
|
|
628
|
-
line-ending = "auto"
|
|
629
|
-
```
|
|
630
|
-
|
|
631
|
-
### Pytest Configuration (pytest.ini)
|
|
632
|
-
|
|
633
|
-
```ini
|
|
634
|
-
[pytest]
|
|
635
|
-
testpaths = tests
|
|
636
|
-
python_files = test_*.py
|
|
637
|
-
python_classes = Test*
|
|
638
|
-
python_functions = test_*
|
|
639
|
-
addopts =
|
|
640
|
-
-v
|
|
641
|
-
--strict-markers
|
|
642
|
-
--tb=short
|
|
643
|
-
--cov=app
|
|
644
|
-
--cov-report=term-missing
|
|
645
|
-
markers =
|
|
646
|
-
slow: marks tests as slow
|
|
647
|
-
integration: marks tests as integration tests
|
|
648
|
-
unit: marks tests as unit tests
|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
### Mypy Configuration (mypy.ini)
|
|
652
|
-
|
|
653
|
-
```ini
|
|
654
|
-
[mypy]
|
|
655
|
-
python_version = 3.11
|
|
656
|
-
warn_return_any = True
|
|
657
|
-
warn_unused_configs = True
|
|
658
|
-
disallow_untyped_defs = True
|
|
659
|
-
disallow_incomplete_defs = True
|
|
660
|
-
check_untyped_defs = True
|
|
661
|
-
no_implicit_optional = True
|
|
662
|
-
warn_redundant_casts = True
|
|
663
|
-
warn_unused_ignores = True
|
|
664
|
-
|
|
665
|
-
[mypy-tests.*]
|
|
666
|
-
disallow_untyped_defs = False
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
## Anti-Patterns to Avoid
|
|
670
|
-
|
|
671
|
-
### ❌ Bad: Mutable Default Arguments
|
|
672
|
-
|
|
673
|
-
```python
|
|
674
|
-
# ❌ Bad
|
|
675
|
-
def add_item(item, items=[]):
|
|
676
|
-
items.append(item)
|
|
677
|
-
return items
|
|
678
|
-
|
|
679
|
-
# ✅ Good
|
|
680
|
-
def add_item(item, items=None):
|
|
681
|
-
if items is None:
|
|
682
|
-
items = []
|
|
683
|
-
items.append(item)
|
|
684
|
-
return items
|
|
685
|
-
```
|
|
686
|
-
|
|
687
|
-
### ❌ Bad: Bare Except
|
|
688
|
-
|
|
689
|
-
```python
|
|
690
|
-
# ❌ Bad
|
|
691
|
-
try:
|
|
692
|
-
risky_operation()
|
|
693
|
-
except:
|
|
694
|
-
pass
|
|
695
|
-
|
|
696
|
-
# ✅ Good
|
|
697
|
-
try:
|
|
698
|
-
risky_operation()
|
|
699
|
-
except SpecificException as e:
|
|
700
|
-
logger.error(f"Operation failed: {e}")
|
|
701
|
-
raise
|
|
702
|
-
```
|
|
703
|
-
|
|
704
|
-
### ❌ Bad: Deep Nesting
|
|
705
|
-
|
|
706
|
-
```python
|
|
707
|
-
# ❌ Bad
|
|
708
|
-
def process_data(data):
|
|
709
|
-
if data:
|
|
710
|
-
for item in data:
|
|
711
|
-
if item.valid:
|
|
712
|
-
for subitem in item.subitems:
|
|
713
|
-
if subitem.active:
|
|
714
|
-
process(subitem)
|
|
715
|
-
|
|
716
|
-
# ✅ Good
|
|
717
|
-
def process_data(data):
|
|
718
|
-
if not data:
|
|
719
|
-
return
|
|
720
|
-
|
|
721
|
-
for item in data:
|
|
722
|
-
if not item.valid:
|
|
723
|
-
continue
|
|
724
|
-
|
|
725
|
-
for subitem in item.subitems:
|
|
726
|
-
if subitem.active:
|
|
727
|
-
process(subitem)
|
|
728
|
-
```
|
|
729
|
-
|
|
730
|
-
### ❌ Bad: Magic Numbers
|
|
731
|
-
|
|
732
|
-
```python
|
|
733
|
-
# ❌ Bad
|
|
734
|
-
def calculate_price(price):
|
|
735
|
-
return price * 1.1 * 0.95
|
|
736
|
-
|
|
737
|
-
# ✅ Good
|
|
738
|
-
TAX_RATE = 0.1
|
|
739
|
-
DISCOUNT_RATE = 0.05
|
|
740
|
-
|
|
741
|
-
def calculate_price(price):
|
|
742
|
-
return price * (1 + TAX_RATE) * (1 - DISCOUNT_RATE)
|
|
743
|
-
```
|
|
744
|
-
|
|
745
|
-
### ❌ Bad: God Classes
|
|
746
|
-
|
|
747
|
-
```python
|
|
748
|
-
# ❌ Bad: Does everything
|
|
749
|
-
class UserManager:
|
|
750
|
-
def create_user(self): pass
|
|
751
|
-
def delete_user(self): pass
|
|
752
|
-
def send_email(self): pass
|
|
753
|
-
def generate_report(self): pass
|
|
754
|
-
def connect_db(self): pass
|
|
755
|
-
|
|
756
|
-
# ✅ Good: Single responsibility
|
|
757
|
-
class UserService:
|
|
758
|
-
def create_user(self): pass
|
|
759
|
-
def delete_user(self): pass
|
|
760
|
-
|
|
761
|
-
class EmailService:
|
|
762
|
-
def send_email(self): pass
|
|
763
|
-
|
|
764
|
-
class ReportGenerator:
|
|
765
|
-
def generate_report(self): pass
|
|
766
|
-
```
|
|
767
|
-
|
|
768
|
-
## Commands
|
|
769
|
-
|
|
770
|
-
/python-guide-lint: Run Ruff linter and formatter
|
|
771
|
-
/python-guide-test: Run pytest with coverage
|
|
772
|
-
/python-guide-typecheck: Run mypy type checking
|
|
773
|
-
/python-guide-format: Format code with Black/Ruff
|
|
774
|
-
|
|
775
|
-
## Examples
|
|
776
|
-
|
|
777
|
-
### Example 1: Create New Feature
|
|
778
|
-
|
|
779
|
-
User: "Add user registration endpoint"
|
|
780
|
-
|
|
781
|
-
Agent:
|
|
782
|
-
1. Create schema in `app/schemas/user.py`
|
|
783
|
-
2. Create service in `app/services/user_service.py`
|
|
784
|
-
3. Create repository in `app/repositories/user_repo.py`
|
|
785
|
-
4. Create route in `app/api/routes/users.py`
|
|
786
|
-
5. Write tests in `tests/test_users.py`
|
|
787
|
-
6. Run: `ruff check app && pytest tests/test_users.py`
|
|
788
|
-
|
|
789
|
-
Follow patterns in existing code. Use type hints, docstrings, and proper error handling.
|
|
790
|
-
|
|
791
|
-
### Example 2: Refactor Legacy Code
|
|
792
|
-
|
|
793
|
-
User: "Refactor this function"
|
|
794
|
-
|
|
795
|
-
Agent:
|
|
796
|
-
1. Write tests for current behavior
|
|
797
|
-
2. Add type hints
|
|
798
|
-
3. Extract helper functions
|
|
799
|
-
4. Improve naming
|
|
800
|
-
5. Add docstrings
|
|
801
|
-
6. Run: `ruff check && mypy && pytest`
|
|
802
|
-
|
|
803
|
-
Ensure tests pass before and after. Maintain backward compatibility.
|
|
804
|
-
|
|
805
|
-
### Example 3: Fix Bug
|
|
806
|
-
|
|
807
|
-
User: "Fix null pointer exception"
|
|
808
|
-
|
|
809
|
-
Agent:
|
|
810
|
-
1. Reproduce the bug with test
|
|
811
|
-
2. Identify root cause (missing None check)
|
|
812
|
-
3. Add proper None handling
|
|
813
|
-
4. Add type hints (Optional)
|
|
814
|
-
5. Run tests to verify fix
|
|
815
|
-
6. Run: `ruff check && mypy`
|
|
816
|
-
|
|
817
|
-
## Additional Resources
|
|
818
|
-
|
|
819
|
-
- [PEP 8 Style Guide](https://peps.python.org/pep-0008/)
|
|
820
|
-
- [Real Python Best Practices](https://realpython.com/tutorials/best-practices/)
|
|
821
|
-
- [Python Code Quality Tools](https://realpython.com/python-code-quality/)
|
|
822
|
-
- [FastAPI Best Practices](https://dev.to/building-production-ready-apis-with-fastapi-in-2026)
|
|
1
|
+
---
|
|
2
|
+
name: python-guide
|
|
3
|
+
description: Apply Python best practices including PEP 8, clean code, testing, and project structure. Use when writing, reviewing, or refactoring Python code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Python Best Practices
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
When working with Python code, follow these core principles:
|
|
11
|
+
|
|
12
|
+
1. **Follow PEP 8**: Use proper indentation, naming, and formatting
|
|
13
|
+
2. **Write tests**: Use pytest with >80% coverage
|
|
14
|
+
3. **Document**: Add docstrings and type hints
|
|
15
|
+
4. **Structure properly**: Separate concerns (routers, services, models)
|
|
16
|
+
5. **Validate early**: Use configuration validation at startup
|
|
17
|
+
6. **Automate quality**: Use Ruff, Black, mypy, pytest
|
|
18
|
+
|
|
19
|
+
## Code Style & Formatting
|
|
20
|
+
|
|
21
|
+
### PEP 8 Essentials
|
|
22
|
+
|
|
23
|
+
**Indentation**:
|
|
24
|
+
```python
|
|
25
|
+
# ✅ Good: 4 spaces
|
|
26
|
+
def calculate_total(items):
|
|
27
|
+
total = 0
|
|
28
|
+
for item in items:
|
|
29
|
+
total += item.price
|
|
30
|
+
return total
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Line Length**:
|
|
34
|
+
```python
|
|
35
|
+
# ✅ Good: Max 79 characters (code), 72 (docstrings)
|
|
36
|
+
result = (
|
|
37
|
+
some_long_variable_name
|
|
38
|
+
+ another_long_variable_name
|
|
39
|
+
+ third_long_variable_name
|
|
40
|
+
)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Blank Lines**:
|
|
44
|
+
```python
|
|
45
|
+
# ✅ Good: 2 blank lines between top-level, 1 between methods
|
|
46
|
+
class UserService:
|
|
47
|
+
def __init__(self):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
def create_user(self):
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def helper_function():
|
|
55
|
+
pass
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Naming Conventions
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
# ✅ Good: snake_case for functions/variables
|
|
62
|
+
def calculate_total():
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
user_count = 0
|
|
66
|
+
|
|
67
|
+
# ✅ Good: PascalCase for classes
|
|
68
|
+
class UserProfile:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
# ✅ Good: UPPERCASE for constants
|
|
72
|
+
MAX_RETRIES = 3
|
|
73
|
+
DEFAULT_TIMEOUT = 30
|
|
74
|
+
|
|
75
|
+
# ✅ Good: _prefix for private, __prefix for name mangling
|
|
76
|
+
class DataProcessor:
|
|
77
|
+
def _internal_method(self):
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
def __private_method(self):
|
|
81
|
+
pass
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Imports
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# ✅ Good: Standard library first, then third-party, then local
|
|
88
|
+
import os
|
|
89
|
+
import sys
|
|
90
|
+
from typing import List, Optional
|
|
91
|
+
|
|
92
|
+
import requests
|
|
93
|
+
from fastapi import FastAPI
|
|
94
|
+
|
|
95
|
+
from app.models import User
|
|
96
|
+
from app.services import UserService
|
|
97
|
+
|
|
98
|
+
# ✅ Good: Use explicit imports
|
|
99
|
+
from collections import OrderedDict # Not: from collections import *
|
|
100
|
+
|
|
101
|
+
# ✅ Good: Handle circular imports with TYPE_CHECKING
|
|
102
|
+
from typing import TYPE_CHECKING
|
|
103
|
+
|
|
104
|
+
if TYPE_CHECKING:
|
|
105
|
+
from app.models import User
|
|
106
|
+
|
|
107
|
+
def process_user(user: 'User'):
|
|
108
|
+
pass
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Type Hints
|
|
112
|
+
|
|
113
|
+
### Basic Types
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
# ✅ Good: Use type hints for all functions
|
|
117
|
+
def greet(name: str, age: int) -> str:
|
|
118
|
+
return f"Hello {name}, age {age}"
|
|
119
|
+
|
|
120
|
+
# ✅ Good: Use Optional for nullable
|
|
121
|
+
from typing import Optional
|
|
122
|
+
|
|
123
|
+
def find_user(user_id: int) -> Optional[User]:
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
# ✅ Good: Use Union for multiple types
|
|
127
|
+
from typing import Union
|
|
128
|
+
|
|
129
|
+
def parse_value(value: Union[str, int, float]) -> Any:
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
# ✅ Good: Use List, Dict, etc. from typing
|
|
133
|
+
from typing import List, Dict
|
|
134
|
+
|
|
135
|
+
def process_items(items: List[str]) -> Dict[str, int]:
|
|
136
|
+
pass
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Advanced Types
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
# ✅ Good: Use Protocol for structural typing
|
|
143
|
+
from typing import Protocol
|
|
144
|
+
|
|
145
|
+
class Drawable(Protocol):
|
|
146
|
+
def draw(self) -> None:
|
|
147
|
+
...
|
|
148
|
+
|
|
149
|
+
def render(shape: Drawable):
|
|
150
|
+
shape.draw()
|
|
151
|
+
|
|
152
|
+
# ✅ Good: Use TypedDict for dictionaries
|
|
153
|
+
from typing import TypedDict
|
|
154
|
+
|
|
155
|
+
class UserConfig(TypedDict):
|
|
156
|
+
username: str
|
|
157
|
+
email: str
|
|
158
|
+
age: int
|
|
159
|
+
|
|
160
|
+
# ✅ Good: Use Literal for specific values
|
|
161
|
+
from typing import Literal
|
|
162
|
+
|
|
163
|
+
def set_status(status: Literal["active", "inactive", "pending"]):
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
# ✅ Good: Use Callable for function types
|
|
167
|
+
from typing import Callable
|
|
168
|
+
|
|
169
|
+
def process_data(
|
|
170
|
+
data: List[int],
|
|
171
|
+
transformer: Callable[[int], int]
|
|
172
|
+
) -> List[int]:
|
|
173
|
+
return [transformer(x) for x in data]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Project Structure
|
|
177
|
+
|
|
178
|
+
### Production-Ready Layout
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
project/
|
|
182
|
+
├── app/
|
|
183
|
+
│ ├── __init__.py
|
|
184
|
+
│ ├── main.py # Application entry point
|
|
185
|
+
│ ├── api/ # API routes
|
|
186
|
+
│ │ ├── __init__.py
|
|
187
|
+
│ │ ├── routes/
|
|
188
|
+
│ │ │ ├── users.py
|
|
189
|
+
│ │ │ └── products.py
|
|
190
|
+
│ │ └── deps.py # Dependencies
|
|
191
|
+
│ ├── core/ # Core configuration
|
|
192
|
+
│ │ ├── __init__.py
|
|
193
|
+
│ │ ├── config.py # Settings
|
|
194
|
+
│ │ ├── security.py # Auth/security
|
|
195
|
+
│ │ └── logging.py # Logging config
|
|
196
|
+
│ ├── models/ # Database models
|
|
197
|
+
│ │ ├── __init__.py
|
|
198
|
+
│ │ ├── user.py
|
|
199
|
+
│ │ └── product.py
|
|
200
|
+
│ ├── schemas/ # Pydantic schemas
|
|
201
|
+
│ │ ├── __init__.py
|
|
202
|
+
│ │ ├── user.py
|
|
203
|
+
│ │ └── product.py
|
|
204
|
+
│ ├── services/ # Business logic
|
|
205
|
+
│ │ ├── __init__.py
|
|
206
|
+
│ │ ├── user_service.py
|
|
207
|
+
│ │ └── product_service.py
|
|
208
|
+
│ ├── repositories/ # Data access
|
|
209
|
+
│ │ ├── __init__.py
|
|
210
|
+
│ │ ├── user_repo.py
|
|
211
|
+
│ │ └── product_repo.py
|
|
212
|
+
│ └── utils/ # Utilities
|
|
213
|
+
│ ├── __init__.py
|
|
214
|
+
│ └── helpers.py
|
|
215
|
+
├── tests/ # Test files
|
|
216
|
+
│ ├── __init__.py
|
|
217
|
+
│ ├── conftest.py # Shared fixtures
|
|
218
|
+
│ ├── test_users.py
|
|
219
|
+
│ └── test_products.py
|
|
220
|
+
├── scripts/ # Utility scripts
|
|
221
|
+
│ ├── setup_db.py
|
|
222
|
+
│ └── migrate.py
|
|
223
|
+
├── .env # Environment variables
|
|
224
|
+
├── .env.example # Template
|
|
225
|
+
├── pyproject.toml # Dependencies
|
|
226
|
+
├── pytest.ini # Test config
|
|
227
|
+
├── mypy.ini # Type checking
|
|
228
|
+
├── ruff.toml # Linting
|
|
229
|
+
└── README.md
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Configuration Management
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
# ✅ Good: Use Pydantic Settings
|
|
236
|
+
from pydantic_settings import BaseSettings
|
|
237
|
+
from functools import lru_cache
|
|
238
|
+
|
|
239
|
+
class Settings(BaseSettings):
|
|
240
|
+
app_name: str = "My App"
|
|
241
|
+
debug: bool = False
|
|
242
|
+
database_url: str
|
|
243
|
+
secret_key: str
|
|
244
|
+
max_connections: int = 100
|
|
245
|
+
|
|
246
|
+
class Config:
|
|
247
|
+
env_file = ".env"
|
|
248
|
+
|
|
249
|
+
@lru_cache()
|
|
250
|
+
def get_settings() -> Settings:
|
|
251
|
+
return Settings()
|
|
252
|
+
|
|
253
|
+
# Usage
|
|
254
|
+
settings = get_settings()
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Documentation
|
|
258
|
+
|
|
259
|
+
### Docstrings
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
# ✅ Good: Google style docstrings
|
|
263
|
+
def calculate_total(
|
|
264
|
+
items: List[Item],
|
|
265
|
+
tax_rate: float = 0.1
|
|
266
|
+
) -> float:
|
|
267
|
+
"""
|
|
268
|
+
Calculate total price including tax.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
items: List of items to calculate total for
|
|
272
|
+
tax_rate: Tax rate as decimal (default: 0.1)
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Total price including tax
|
|
276
|
+
|
|
277
|
+
Raises:
|
|
278
|
+
ValueError: If items list is empty or tax_rate is negative
|
|
279
|
+
|
|
280
|
+
Example:
|
|
281
|
+
>>> items = [Item(price=100), Item(price=200)]
|
|
282
|
+
>>> calculate_total(items, 0.1)
|
|
283
|
+
330.0
|
|
284
|
+
"""
|
|
285
|
+
if not items:
|
|
286
|
+
raise ValueError("Items list cannot be empty")
|
|
287
|
+
if tax_rate < 0:
|
|
288
|
+
raise ValueError("Tax rate must be non-negative")
|
|
289
|
+
|
|
290
|
+
subtotal = sum(item.price for item in items)
|
|
291
|
+
return subtotal * (1 + tax_rate)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Module Docstrings
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
"""
|
|
298
|
+
User management module.
|
|
299
|
+
|
|
300
|
+
This module provides user-related business logic including:
|
|
301
|
+
- User creation and validation
|
|
302
|
+
- Authentication and authorization
|
|
303
|
+
- Profile management
|
|
304
|
+
|
|
305
|
+
Example:
|
|
306
|
+
from app.services import UserService
|
|
307
|
+
|
|
308
|
+
service = UserService()
|
|
309
|
+
user = service.create_user("john", "john@example.com")
|
|
310
|
+
"""
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Testing
|
|
314
|
+
|
|
315
|
+
### Test Structure
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
# tests/test_users.py
|
|
319
|
+
import pytest
|
|
320
|
+
from app.services import UserService
|
|
321
|
+
from app.models import User
|
|
322
|
+
|
|
323
|
+
class TestUserService:
|
|
324
|
+
"""Test cases for UserService."""
|
|
325
|
+
|
|
326
|
+
@pytest.fixture
|
|
327
|
+
def user_service(self):
|
|
328
|
+
"""Create UserService instance."""
|
|
329
|
+
return UserService()
|
|
330
|
+
|
|
331
|
+
def test_create_user_success(self, user_service):
|
|
332
|
+
"""Should create user with valid data."""
|
|
333
|
+
user = user_service.create_user(
|
|
334
|
+
username="john",
|
|
335
|
+
email="john@example.com"
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
assert user.username == "john"
|
|
339
|
+
assert user.email == "john@example.com"
|
|
340
|
+
assert user.id is not None
|
|
341
|
+
|
|
342
|
+
def test_create_user_duplicate_email(self, user_service):
|
|
343
|
+
"""Should raise error for duplicate email."""
|
|
344
|
+
user_service.create_user(
|
|
345
|
+
username="john",
|
|
346
|
+
email="john@example.com"
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
with pytest.raises(ValueError, match="Email already exists"):
|
|
350
|
+
user_service.create_user(
|
|
351
|
+
username="jane",
|
|
352
|
+
email="john@example.com"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def test_create_user_invalid_email(self, user_service):
|
|
356
|
+
"""Should raise error for invalid email."""
|
|
357
|
+
with pytest.raises(ValueError, match="Invalid email"):
|
|
358
|
+
user_service.create_user(
|
|
359
|
+
username="john",
|
|
360
|
+
email="invalid-email"
|
|
361
|
+
)
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Test Fixtures (conftest.py)
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
# tests/conftest.py
|
|
368
|
+
import pytest
|
|
369
|
+
from app.models import User
|
|
370
|
+
from app.services import UserService
|
|
371
|
+
|
|
372
|
+
@pytest.fixture
|
|
373
|
+
def sample_user():
|
|
374
|
+
"""Create sample user for testing."""
|
|
375
|
+
return User(
|
|
376
|
+
id=1,
|
|
377
|
+
username="testuser",
|
|
378
|
+
email="test@example.com"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
@pytest.fixture
|
|
382
|
+
def user_service():
|
|
383
|
+
"""Create UserService with test configuration."""
|
|
384
|
+
return UserService(test_mode=True)
|
|
385
|
+
|
|
386
|
+
@pytest.fixture
|
|
387
|
+
def mock_database(mocker):
|
|
388
|
+
"""Mock database operations."""
|
|
389
|
+
mock = mocker.patch('app.db.database')
|
|
390
|
+
mock.execute.return_value = []
|
|
391
|
+
return mock
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Test Coverage
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
# Run tests with coverage
|
|
398
|
+
pytest --cov=app --cov-report=html
|
|
399
|
+
|
|
400
|
+
# Minimum coverage requirement
|
|
401
|
+
pytest --cov=app --cov-fail-under=80
|
|
402
|
+
|
|
403
|
+
# Run specific test file
|
|
404
|
+
pytest tests/test_users.py -v
|
|
405
|
+
|
|
406
|
+
# Run with markers
|
|
407
|
+
pytest -m slow # or -m fast, -m integration
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Error Handling
|
|
411
|
+
|
|
412
|
+
### Custom Exceptions
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
# app/exceptions.py
|
|
416
|
+
class AppException(Exception):
|
|
417
|
+
"""Base exception for application."""
|
|
418
|
+
def __init__(self, message: str, code: str = None):
|
|
419
|
+
self.message = message
|
|
420
|
+
self.code = code or self.__class__.__name__
|
|
421
|
+
super().__init__(self.message)
|
|
422
|
+
|
|
423
|
+
class UserNotFoundException(AppException):
|
|
424
|
+
"""Raised when user is not found."""
|
|
425
|
+
def __init__(self, user_id: int):
|
|
426
|
+
super().__init__(
|
|
427
|
+
message=f"User with id {user_id} not found",
|
|
428
|
+
code="USER_NOT_FOUND"
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
class ValidationError(AppException):
|
|
432
|
+
"""Raised when validation fails."""
|
|
433
|
+
def __init__(self, message: str, field: str = None):
|
|
434
|
+
super().__init__(message, "VALIDATION_ERROR")
|
|
435
|
+
self.field = field
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Error Handling Patterns
|
|
439
|
+
|
|
440
|
+
```python
|
|
441
|
+
# ✅ Good: Specific exception handling
|
|
442
|
+
from app.exceptions import UserNotFoundException, ValidationError
|
|
443
|
+
|
|
444
|
+
def get_user(user_id: int) -> User:
|
|
445
|
+
try:
|
|
446
|
+
user = user_repo.find_by_id(user_id)
|
|
447
|
+
if not user:
|
|
448
|
+
raise UserNotFoundException(user_id)
|
|
449
|
+
return user
|
|
450
|
+
except UserNotFoundException:
|
|
451
|
+
raise
|
|
452
|
+
except Exception as e:
|
|
453
|
+
logger.error(f"Failed to get user {user_id}: {e}")
|
|
454
|
+
raise AppException("Failed to retrieve user")
|
|
455
|
+
|
|
456
|
+
# ✅ Good: Context managers for resources
|
|
457
|
+
from contextlib import contextmanager
|
|
458
|
+
|
|
459
|
+
@contextmanager
|
|
460
|
+
def database_transaction():
|
|
461
|
+
"""Manage database transaction."""
|
|
462
|
+
conn = get_connection()
|
|
463
|
+
try:
|
|
464
|
+
conn.begin()
|
|
465
|
+
yield conn
|
|
466
|
+
conn.commit()
|
|
467
|
+
except Exception:
|
|
468
|
+
conn.rollback()
|
|
469
|
+
raise
|
|
470
|
+
finally:
|
|
471
|
+
conn.close()
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
## Design Patterns
|
|
475
|
+
|
|
476
|
+
### Repository Pattern
|
|
477
|
+
|
|
478
|
+
```python
|
|
479
|
+
# app/repositories/base.py
|
|
480
|
+
from abc import ABC, abstractmethod
|
|
481
|
+
from typing import Generic, TypeVar, List, Optional
|
|
482
|
+
|
|
483
|
+
T = TypeVar('T')
|
|
484
|
+
|
|
485
|
+
class BaseRepository(ABC, Generic[T]):
|
|
486
|
+
"""Base repository with CRUD operations."""
|
|
487
|
+
|
|
488
|
+
@abstractmethod
|
|
489
|
+
def find_by_id(self, id: int) -> Optional[T]:
|
|
490
|
+
pass
|
|
491
|
+
|
|
492
|
+
@abstractmethod
|
|
493
|
+
def find_all(self) -> List[T]:
|
|
494
|
+
pass
|
|
495
|
+
|
|
496
|
+
@abstractmethod
|
|
497
|
+
def create(self, entity: T) -> T:
|
|
498
|
+
pass
|
|
499
|
+
|
|
500
|
+
@abstractmethod
|
|
501
|
+
def update(self, id: int, entity: T) -> T:
|
|
502
|
+
pass
|
|
503
|
+
|
|
504
|
+
@abstractmethod
|
|
505
|
+
def delete(self, id: int) -> bool:
|
|
506
|
+
pass
|
|
507
|
+
|
|
508
|
+
# app/repositories/user_repository.py
|
|
509
|
+
class UserRepository(BaseRepository[User]):
|
|
510
|
+
"""User-specific repository."""
|
|
511
|
+
|
|
512
|
+
def find_by_id(self, id: int) -> Optional[User]:
|
|
513
|
+
# Implementation
|
|
514
|
+
pass
|
|
515
|
+
|
|
516
|
+
def find_by_email(self, email: str) -> Optional[User]:
|
|
517
|
+
# Implementation
|
|
518
|
+
pass
|
|
519
|
+
|
|
520
|
+
def find_active_users(self) -> List[User]:
|
|
521
|
+
# Implementation
|
|
522
|
+
pass
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Service Layer Pattern
|
|
526
|
+
|
|
527
|
+
```python
|
|
528
|
+
# app/services/user_service.py
|
|
529
|
+
from app.repositories import UserRepository
|
|
530
|
+
from app.schemas import UserCreate, UserUpdate
|
|
531
|
+
from app.models import User
|
|
532
|
+
|
|
533
|
+
class UserService:
|
|
534
|
+
"""Business logic for user operations."""
|
|
535
|
+
|
|
536
|
+
def __init__(self, user_repo: UserRepository):
|
|
537
|
+
self.user_repo = user_repo
|
|
538
|
+
|
|
539
|
+
def create_user(self, data: UserCreate) -> User:
|
|
540
|
+
"""Create new user with validation."""
|
|
541
|
+
# Validate email uniqueness
|
|
542
|
+
existing = self.user_repo.find_by_email(data.email)
|
|
543
|
+
if existing:
|
|
544
|
+
raise ValidationError("Email already exists", "email")
|
|
545
|
+
|
|
546
|
+
# Create user
|
|
547
|
+
user = User(
|
|
548
|
+
username=data.username,
|
|
549
|
+
email=data.email,
|
|
550
|
+
hashed_password=hash_password(data.password)
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
return self.user_repo.create(user)
|
|
554
|
+
|
|
555
|
+
def update_user(self, user_id: int, data: UserUpdate) -> User:
|
|
556
|
+
"""Update existing user."""
|
|
557
|
+
user = self.user_repo.find_by_id(user_id)
|
|
558
|
+
if not user:
|
|
559
|
+
raise UserNotFoundException(user_id)
|
|
560
|
+
|
|
561
|
+
# Update fields
|
|
562
|
+
if data.username:
|
|
563
|
+
user.username = data.username
|
|
564
|
+
if data.email:
|
|
565
|
+
user.email = data.email
|
|
566
|
+
|
|
567
|
+
return self.user_repo.update(user_id, user)
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### Dependency Injection
|
|
571
|
+
|
|
572
|
+
```python
|
|
573
|
+
# app/api/deps.py
|
|
574
|
+
from functools import lru_cache
|
|
575
|
+
from app.repositories import UserRepository
|
|
576
|
+
from app.services import UserService
|
|
577
|
+
from app.db import get_db
|
|
578
|
+
|
|
579
|
+
@lru_cache()
|
|
580
|
+
def get_user_repository() -> UserRepository:
|
|
581
|
+
"""Get UserRepository instance."""
|
|
582
|
+
return UserRepository(get_db())
|
|
583
|
+
|
|
584
|
+
@lru_cache()
|
|
585
|
+
def get_user_service() -> UserService:
|
|
586
|
+
"""Get UserService instance."""
|
|
587
|
+
return UserService(get_user_repository())
|
|
588
|
+
|
|
589
|
+
# Usage in API routes
|
|
590
|
+
from fastapi import Depends
|
|
591
|
+
|
|
592
|
+
@app.get("/users/{user_id}")
|
|
593
|
+
def get_user(
|
|
594
|
+
user_id: int,
|
|
595
|
+
service: UserService = Depends(get_user_service)
|
|
596
|
+
):
|
|
597
|
+
user = service.get_user(user_id)
|
|
598
|
+
return user
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
## Code Quality Tools
|
|
602
|
+
|
|
603
|
+
### Ruff Configuration (ruff.toml)
|
|
604
|
+
|
|
605
|
+
```toml
|
|
606
|
+
[lint]
|
|
607
|
+
select = [
|
|
608
|
+
"E", # pycodestyle errors
|
|
609
|
+
"W", # pycodestyle warnings
|
|
610
|
+
"F", # Pyflakes
|
|
611
|
+
"I", # isort
|
|
612
|
+
"B", # flake8-bugbear
|
|
613
|
+
"C4", # flake8-comprehensions
|
|
614
|
+
"UP", # pyupgrade
|
|
615
|
+
]
|
|
616
|
+
|
|
617
|
+
ignore = [
|
|
618
|
+
"E501", # Line too long (handled by formatter)
|
|
619
|
+
]
|
|
620
|
+
|
|
621
|
+
[lint.per-file-ignores]
|
|
622
|
+
"__init__.py" = ["F401"] # Unused imports OK in __init__
|
|
623
|
+
"tests/*" = ["S101"] # Assert OK in tests
|
|
624
|
+
|
|
625
|
+
[format]
|
|
626
|
+
quote-style = "double"
|
|
627
|
+
indent-style = "space"
|
|
628
|
+
line-ending = "auto"
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
### Pytest Configuration (pytest.ini)
|
|
632
|
+
|
|
633
|
+
```ini
|
|
634
|
+
[pytest]
|
|
635
|
+
testpaths = tests
|
|
636
|
+
python_files = test_*.py
|
|
637
|
+
python_classes = Test*
|
|
638
|
+
python_functions = test_*
|
|
639
|
+
addopts =
|
|
640
|
+
-v
|
|
641
|
+
--strict-markers
|
|
642
|
+
--tb=short
|
|
643
|
+
--cov=app
|
|
644
|
+
--cov-report=term-missing
|
|
645
|
+
markers =
|
|
646
|
+
slow: marks tests as slow
|
|
647
|
+
integration: marks tests as integration tests
|
|
648
|
+
unit: marks tests as unit tests
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### Mypy Configuration (mypy.ini)
|
|
652
|
+
|
|
653
|
+
```ini
|
|
654
|
+
[mypy]
|
|
655
|
+
python_version = 3.11
|
|
656
|
+
warn_return_any = True
|
|
657
|
+
warn_unused_configs = True
|
|
658
|
+
disallow_untyped_defs = True
|
|
659
|
+
disallow_incomplete_defs = True
|
|
660
|
+
check_untyped_defs = True
|
|
661
|
+
no_implicit_optional = True
|
|
662
|
+
warn_redundant_casts = True
|
|
663
|
+
warn_unused_ignores = True
|
|
664
|
+
|
|
665
|
+
[mypy-tests.*]
|
|
666
|
+
disallow_untyped_defs = False
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
## Anti-Patterns to Avoid
|
|
670
|
+
|
|
671
|
+
### ❌ Bad: Mutable Default Arguments
|
|
672
|
+
|
|
673
|
+
```python
|
|
674
|
+
# ❌ Bad
|
|
675
|
+
def add_item(item, items=[]):
|
|
676
|
+
items.append(item)
|
|
677
|
+
return items
|
|
678
|
+
|
|
679
|
+
# ✅ Good
|
|
680
|
+
def add_item(item, items=None):
|
|
681
|
+
if items is None:
|
|
682
|
+
items = []
|
|
683
|
+
items.append(item)
|
|
684
|
+
return items
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
### ❌ Bad: Bare Except
|
|
688
|
+
|
|
689
|
+
```python
|
|
690
|
+
# ❌ Bad
|
|
691
|
+
try:
|
|
692
|
+
risky_operation()
|
|
693
|
+
except:
|
|
694
|
+
pass
|
|
695
|
+
|
|
696
|
+
# ✅ Good
|
|
697
|
+
try:
|
|
698
|
+
risky_operation()
|
|
699
|
+
except SpecificException as e:
|
|
700
|
+
logger.error(f"Operation failed: {e}")
|
|
701
|
+
raise
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
### ❌ Bad: Deep Nesting
|
|
705
|
+
|
|
706
|
+
```python
|
|
707
|
+
# ❌ Bad
|
|
708
|
+
def process_data(data):
|
|
709
|
+
if data:
|
|
710
|
+
for item in data:
|
|
711
|
+
if item.valid:
|
|
712
|
+
for subitem in item.subitems:
|
|
713
|
+
if subitem.active:
|
|
714
|
+
process(subitem)
|
|
715
|
+
|
|
716
|
+
# ✅ Good
|
|
717
|
+
def process_data(data):
|
|
718
|
+
if not data:
|
|
719
|
+
return
|
|
720
|
+
|
|
721
|
+
for item in data:
|
|
722
|
+
if not item.valid:
|
|
723
|
+
continue
|
|
724
|
+
|
|
725
|
+
for subitem in item.subitems:
|
|
726
|
+
if subitem.active:
|
|
727
|
+
process(subitem)
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### ❌ Bad: Magic Numbers
|
|
731
|
+
|
|
732
|
+
```python
|
|
733
|
+
# ❌ Bad
|
|
734
|
+
def calculate_price(price):
|
|
735
|
+
return price * 1.1 * 0.95
|
|
736
|
+
|
|
737
|
+
# ✅ Good
|
|
738
|
+
TAX_RATE = 0.1
|
|
739
|
+
DISCOUNT_RATE = 0.05
|
|
740
|
+
|
|
741
|
+
def calculate_price(price):
|
|
742
|
+
return price * (1 + TAX_RATE) * (1 - DISCOUNT_RATE)
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
### ❌ Bad: God Classes
|
|
746
|
+
|
|
747
|
+
```python
|
|
748
|
+
# ❌ Bad: Does everything
|
|
749
|
+
class UserManager:
|
|
750
|
+
def create_user(self): pass
|
|
751
|
+
def delete_user(self): pass
|
|
752
|
+
def send_email(self): pass
|
|
753
|
+
def generate_report(self): pass
|
|
754
|
+
def connect_db(self): pass
|
|
755
|
+
|
|
756
|
+
# ✅ Good: Single responsibility
|
|
757
|
+
class UserService:
|
|
758
|
+
def create_user(self): pass
|
|
759
|
+
def delete_user(self): pass
|
|
760
|
+
|
|
761
|
+
class EmailService:
|
|
762
|
+
def send_email(self): pass
|
|
763
|
+
|
|
764
|
+
class ReportGenerator:
|
|
765
|
+
def generate_report(self): pass
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
## Commands
|
|
769
|
+
|
|
770
|
+
/python-guide-lint: Run Ruff linter and formatter
|
|
771
|
+
/python-guide-test: Run pytest with coverage
|
|
772
|
+
/python-guide-typecheck: Run mypy type checking
|
|
773
|
+
/python-guide-format: Format code with Black/Ruff
|
|
774
|
+
|
|
775
|
+
## Examples
|
|
776
|
+
|
|
777
|
+
### Example 1: Create New Feature
|
|
778
|
+
|
|
779
|
+
User: "Add user registration endpoint"
|
|
780
|
+
|
|
781
|
+
Agent:
|
|
782
|
+
1. Create schema in `app/schemas/user.py`
|
|
783
|
+
2. Create service in `app/services/user_service.py`
|
|
784
|
+
3. Create repository in `app/repositories/user_repo.py`
|
|
785
|
+
4. Create route in `app/api/routes/users.py`
|
|
786
|
+
5. Write tests in `tests/test_users.py`
|
|
787
|
+
6. Run: `ruff check app && pytest tests/test_users.py`
|
|
788
|
+
|
|
789
|
+
Follow patterns in existing code. Use type hints, docstrings, and proper error handling.
|
|
790
|
+
|
|
791
|
+
### Example 2: Refactor Legacy Code
|
|
792
|
+
|
|
793
|
+
User: "Refactor this function"
|
|
794
|
+
|
|
795
|
+
Agent:
|
|
796
|
+
1. Write tests for current behavior
|
|
797
|
+
2. Add type hints
|
|
798
|
+
3. Extract helper functions
|
|
799
|
+
4. Improve naming
|
|
800
|
+
5. Add docstrings
|
|
801
|
+
6. Run: `ruff check && mypy && pytest`
|
|
802
|
+
|
|
803
|
+
Ensure tests pass before and after. Maintain backward compatibility.
|
|
804
|
+
|
|
805
|
+
### Example 3: Fix Bug
|
|
806
|
+
|
|
807
|
+
User: "Fix null pointer exception"
|
|
808
|
+
|
|
809
|
+
Agent:
|
|
810
|
+
1. Reproduce the bug with test
|
|
811
|
+
2. Identify root cause (missing None check)
|
|
812
|
+
3. Add proper None handling
|
|
813
|
+
4. Add type hints (Optional)
|
|
814
|
+
5. Run tests to verify fix
|
|
815
|
+
6. Run: `ruff check && mypy`
|
|
816
|
+
|
|
817
|
+
## Additional Resources
|
|
818
|
+
|
|
819
|
+
- [PEP 8 Style Guide](https://peps.python.org/pep-0008/)
|
|
820
|
+
- [Real Python Best Practices](https://realpython.com/tutorials/best-practices/)
|
|
821
|
+
- [Python Code Quality Tools](https://realpython.com/python-code-quality/)
|
|
822
|
+
- [FastAPI Best Practices](https://dev.to/building-production-ready-apis-with-fastapi-in-2026)
|