tribunal-kit 2.4.6 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/agents/accessibility-reviewer.md +220 -134
- package/.agent/agents/ai-code-reviewer.md +233 -129
- package/.agent/agents/backend-specialist.md +238 -178
- package/.agent/agents/code-archaeologist.md +181 -119
- package/.agent/agents/database-architect.md +207 -164
- package/.agent/agents/debugger.md +218 -151
- package/.agent/agents/dependency-reviewer.md +136 -55
- package/.agent/agents/devops-engineer.md +238 -175
- package/.agent/agents/documentation-writer.md +221 -137
- package/.agent/agents/explorer-agent.md +180 -142
- package/.agent/agents/frontend-reviewer.md +194 -80
- package/.agent/agents/frontend-specialist.md +237 -188
- package/.agent/agents/game-developer.md +52 -184
- package/.agent/agents/logic-reviewer.md +149 -78
- package/.agent/agents/mobile-developer.md +223 -152
- package/.agent/agents/mobile-reviewer.md +195 -79
- package/.agent/agents/orchestrator.md +211 -170
- package/.agent/agents/penetration-tester.md +174 -131
- package/.agent/agents/performance-optimizer.md +203 -139
- package/.agent/agents/performance-reviewer.md +211 -108
- package/.agent/agents/product-manager.md +162 -108
- package/.agent/agents/project-planner.md +162 -142
- package/.agent/agents/qa-automation-engineer.md +242 -138
- package/.agent/agents/security-auditor.md +194 -170
- package/.agent/agents/seo-specialist.md +213 -132
- package/.agent/agents/sql-reviewer.md +194 -73
- package/.agent/agents/supervisor-agent.md +203 -156
- package/.agent/agents/test-coverage-reviewer.md +193 -81
- package/.agent/agents/type-safety-reviewer.md +208 -65
- package/.agent/scripts/__pycache__/auto_preview.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/bundle_analyzer.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/checklist.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/dependency_analyzer.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/security_scan.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/session_manager.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/skill_integrator.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/swarm_dispatcher.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/test_runner.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/verify_all.cpython-311.pyc +0 -0
- package/.agent/skills/agent-organizer/SKILL.md +126 -132
- package/.agent/skills/ai-prompt-injection-defense/SKILL.md +155 -66
- package/.agent/skills/api-patterns/SKILL.md +289 -257
- package/.agent/skills/api-security-auditor/SKILL.md +172 -70
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +1 -1
- package/.agent/skills/appflow-wireframe/SKILL.md +107 -100
- package/.agent/skills/architecture/SKILL.md +331 -200
- package/.agent/skills/authentication-best-practices/SKILL.md +168 -67
- package/.agent/skills/bash-linux/SKILL.md +154 -215
- package/.agent/skills/brainstorming/SKILL.md +104 -210
- package/.agent/skills/building-native-ui/SKILL.md +169 -70
- package/.agent/skills/clean-code/SKILL.md +360 -206
- package/.agent/skills/config-validator/SKILL.md +141 -165
- package/.agent/skills/csharp-developer/SKILL.md +528 -107
- package/.agent/skills/database-design/SKILL.md +455 -275
- package/.agent/skills/deployment-procedures/SKILL.md +145 -188
- package/.agent/skills/devops-engineer/SKILL.md +332 -134
- package/.agent/skills/devops-incident-responder/SKILL.md +113 -98
- package/.agent/skills/edge-computing/SKILL.md +157 -213
- package/.agent/skills/extract-design-system/SKILL.md +129 -69
- package/.agent/skills/framer-motion-expert/SKILL.md +939 -0
- package/.agent/skills/game-design-expert/SKILL.md +105 -0
- package/.agent/skills/game-engineering-expert/SKILL.md +122 -0
- package/.agent/skills/geo-fundamentals/SKILL.md +124 -215
- package/.agent/skills/github-operations/SKILL.md +314 -354
- package/.agent/skills/gsap-expert/SKILL.md +901 -0
- package/.agent/skills/i18n-localization/SKILL.md +138 -216
- package/.agent/skills/intelligent-routing/SKILL.md +127 -139
- package/.agent/skills/llm-engineering/SKILL.md +357 -258
- package/.agent/skills/local-first/SKILL.md +154 -203
- package/.agent/skills/mcp-builder/SKILL.md +118 -224
- package/.agent/skills/nextjs-react-expert/SKILL.md +783 -203
- package/.agent/skills/nodejs-best-practices/SKILL.md +559 -280
- package/.agent/skills/observability/SKILL.md +330 -285
- package/.agent/skills/parallel-agents/SKILL.md +122 -181
- package/.agent/skills/performance-profiling/SKILL.md +254 -197
- package/.agent/skills/plan-writing/SKILL.md +118 -188
- package/.agent/skills/platform-engineer/SKILL.md +123 -135
- package/.agent/skills/playwright-best-practices/SKILL.md +157 -76
- package/.agent/skills/powershell-windows/SKILL.md +146 -230
- package/.agent/skills/python-pro/SKILL.md +879 -114
- package/.agent/skills/react-specialist/SKILL.md +931 -108
- package/.agent/skills/realtime-patterns/SKILL.md +304 -296
- package/.agent/skills/rust-pro/SKILL.md +701 -240
- package/.agent/skills/seo-fundamentals/SKILL.md +154 -181
- package/.agent/skills/server-management/SKILL.md +190 -212
- package/.agent/skills/shadcn-ui-expert/SKILL.md +201 -68
- package/.agent/skills/sql-pro/SKILL.md +633 -104
- package/.agent/skills/swiftui-expert/SKILL.md +171 -70
- package/.agent/skills/systematic-debugging/SKILL.md +118 -186
- package/.agent/skills/tailwind-patterns/SKILL.md +576 -232
- package/.agent/skills/tdd-workflow/SKILL.md +137 -209
- package/.agent/skills/testing-patterns/SKILL.md +573 -205
- package/.agent/skills/vue-expert/SKILL.md +964 -119
- package/.agent/skills/vulnerability-scanner/SKILL.md +269 -316
- package/.agent/skills/web-accessibility-auditor/SKILL.md +188 -71
- package/.agent/skills/webapp-testing/SKILL.md +145 -236
- package/.agent/workflows/api-tester.md +151 -279
- package/.agent/workflows/audit.md +138 -168
- package/.agent/workflows/brainstorm.md +110 -146
- package/.agent/workflows/changelog.md +112 -144
- package/.agent/workflows/create.md +124 -139
- package/.agent/workflows/debug.md +189 -196
- package/.agent/workflows/deploy.md +189 -153
- package/.agent/workflows/enhance.md +151 -139
- package/.agent/workflows/fix.md +135 -143
- package/.agent/workflows/generate.md +157 -164
- package/.agent/workflows/migrate.md +160 -163
- package/.agent/workflows/orchestrate.md +168 -151
- package/.agent/workflows/performance-benchmarker.md +123 -305
- package/.agent/workflows/plan.md +173 -151
- package/.agent/workflows/preview.md +80 -137
- package/.agent/workflows/refactor.md +183 -153
- package/.agent/workflows/review-ai.md +129 -140
- package/.agent/workflows/review.md +116 -155
- package/.agent/workflows/session.md +94 -154
- package/.agent/workflows/status.md +79 -125
- package/.agent/workflows/strengthen-skills.md +139 -99
- package/.agent/workflows/swarm.md +179 -194
- package/.agent/workflows/test.md +211 -166
- package/.agent/workflows/tribunal-backend.md +113 -111
- package/.agent/workflows/tribunal-database.md +115 -132
- package/.agent/workflows/tribunal-frontend.md +118 -115
- package/.agent/workflows/tribunal-full.md +133 -136
- package/.agent/workflows/tribunal-mobile.md +119 -123
- package/.agent/workflows/tribunal-performance.md +133 -152
- package/.agent/workflows/ui-ux-pro-max.md +143 -171
- package/README.md +11 -15
- package/package.json +1 -1
- package/.agent/skills/dotnet-core-expert/SKILL.md +0 -103
- package/.agent/skills/framer-motion-animations/SKILL.md +0 -74
- package/.agent/skills/game-development/2d-games/SKILL.md +0 -119
- package/.agent/skills/game-development/3d-games/SKILL.md +0 -135
- package/.agent/skills/game-development/SKILL.md +0 -236
- package/.agent/skills/game-development/game-art/SKILL.md +0 -185
- package/.agent/skills/game-development/game-audio/SKILL.md +0 -190
- package/.agent/skills/game-development/game-design/SKILL.md +0 -129
- package/.agent/skills/game-development/mobile-games/SKILL.md +0 -108
- package/.agent/skills/game-development/multiplayer/SKILL.md +0 -132
- package/.agent/skills/game-development/pc-games/SKILL.md +0 -144
- package/.agent/skills/game-development/vr-ar/SKILL.md +0 -123
- package/.agent/skills/game-development/web-games/SKILL.md +0 -150
|
@@ -1,114 +1,879 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: python-pro
|
|
3
|
-
description: Senior Python developer (3.
|
|
4
|
-
allowed-tools: Read, Write, Edit, Glob, Grep
|
|
5
|
-
version:
|
|
6
|
-
last-updated: 2026-03-
|
|
7
|
-
applies-to-model: gemini-2.5-pro, claude-3-7-sonnet
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Python Pro
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
1
|
+
---
|
|
2
|
+
name: python-pro
|
|
3
|
+
description: Senior Python developer (3.12+) specializing in idiomatic, type-safe, and performant Python. FastAPI/Django mastery, Pydantic v2 data validation, asyncio concurrency, modern type system (TypeVar, ParamSpec, TypedDict, Protocol), testing with pytest, and production patterns. Use when building Python APIs, data pipelines, automation, or any Python code.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Glob, Grep
|
|
5
|
+
version: 2.0.0
|
|
6
|
+
last-updated: 2026-03-30
|
|
7
|
+
applies-to-model: gemini-2.5-pro, claude-3-7-sonnet
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Python Pro — Python 3.12+ Mastery
|
|
11
|
+
|
|
12
|
+
> Python is not a scripting language you hack together. It is a typed, async-capable, production-grade platform.
|
|
13
|
+
> Every function gets type hints. Every I/O call gets `async`. Every exception gets caught specifically. No shortcuts.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Modern Python Type System
|
|
18
|
+
|
|
19
|
+
### Function Signatures (Mandatory)
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
# ✅ Every public function MUST have full type hints
|
|
23
|
+
def calculate_tax(amount: float, rate: float = 0.08) -> float:
|
|
24
|
+
"""Calculate tax for a given amount."""
|
|
25
|
+
return amount * rate
|
|
26
|
+
|
|
27
|
+
# ✅ Collections use built-in generics (Python 3.9+)
|
|
28
|
+
def process_items(items: list[str]) -> dict[str, int]:
|
|
29
|
+
return {item: len(item) for item in items}
|
|
30
|
+
|
|
31
|
+
# ❌ HALLUCINATION TRAP: Do NOT import from typing for built-in generics
|
|
32
|
+
# ❌ from typing import List, Dict, Tuple, Set ← LEGACY (Python 3.8)
|
|
33
|
+
# ✅ Use list[str], dict[str, int], tuple[int, ...], set[str] directly
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Union Types & Optional
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
# Python 3.10+ — use | instead of Union
|
|
40
|
+
def find_user(user_id: int) -> User | None:
|
|
41
|
+
"""Return user or None if not found."""
|
|
42
|
+
return db.get(user_id)
|
|
43
|
+
|
|
44
|
+
# ❌ HALLUCINATION TRAP: Do NOT use Optional[X] or Union[X, Y]
|
|
45
|
+
# ❌ from typing import Optional, Union ← LEGACY
|
|
46
|
+
# ✅ Use X | None and X | Y directly (Python 3.10+)
|
|
47
|
+
|
|
48
|
+
# Multiple return types
|
|
49
|
+
def parse_value(raw: str) -> int | float | None:
|
|
50
|
+
try:
|
|
51
|
+
return int(raw)
|
|
52
|
+
except ValueError:
|
|
53
|
+
try:
|
|
54
|
+
return float(raw)
|
|
55
|
+
except ValueError:
|
|
56
|
+
return None
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### TypedDict
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from typing import TypedDict, NotRequired
|
|
63
|
+
|
|
64
|
+
class UserPayload(TypedDict):
|
|
65
|
+
name: str
|
|
66
|
+
email: str
|
|
67
|
+
age: NotRequired[int] # optional key
|
|
68
|
+
role: NotRequired[str]
|
|
69
|
+
|
|
70
|
+
def create_user(data: UserPayload) -> User:
|
|
71
|
+
# TypedDict gives you autocomplete and type checking on dict keys
|
|
72
|
+
return User(name=data["name"], email=data["email"])
|
|
73
|
+
|
|
74
|
+
# Usage:
|
|
75
|
+
payload: UserPayload = {"name": "Alice", "email": "alice@example.com"}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Protocol (Structural Typing)
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from typing import Protocol, runtime_checkable
|
|
82
|
+
|
|
83
|
+
@runtime_checkable
|
|
84
|
+
class Renderable(Protocol):
|
|
85
|
+
def render(self) -> str: ...
|
|
86
|
+
|
|
87
|
+
class HTMLWidget:
|
|
88
|
+
def render(self) -> str:
|
|
89
|
+
return "<div>Widget</div>"
|
|
90
|
+
|
|
91
|
+
class JSONExporter:
|
|
92
|
+
def render(self) -> str:
|
|
93
|
+
return '{"type": "export"}'
|
|
94
|
+
|
|
95
|
+
# Both satisfy Renderable WITHOUT inheriting from it
|
|
96
|
+
def display(item: Renderable) -> None:
|
|
97
|
+
print(item.render())
|
|
98
|
+
|
|
99
|
+
display(HTMLWidget()) # ✅ works — has render() -> str
|
|
100
|
+
display(JSONExporter()) # ✅ works — has render() -> str
|
|
101
|
+
|
|
102
|
+
# Protocol is Duck Typing with type safety
|
|
103
|
+
# Use Protocol INSTEAD of ABC when you want structural (not nominal) typing
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Generic Functions & Classes
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from typing import TypeVar, ParamSpec
|
|
110
|
+
from collections.abc import Callable
|
|
111
|
+
|
|
112
|
+
T = TypeVar("T")
|
|
113
|
+
P = ParamSpec("P")
|
|
114
|
+
|
|
115
|
+
# Generic function
|
|
116
|
+
def first(items: list[T]) -> T | None:
|
|
117
|
+
return items[0] if items else None
|
|
118
|
+
|
|
119
|
+
# ParamSpec — preserve function signatures in decorators
|
|
120
|
+
def with_logging(func: Callable[P, T]) -> Callable[P, T]:
|
|
121
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
122
|
+
print(f"Calling {func.__name__}")
|
|
123
|
+
result = func(*args, **kwargs)
|
|
124
|
+
print(f"Result: {result}")
|
|
125
|
+
return result
|
|
126
|
+
return wrapper
|
|
127
|
+
|
|
128
|
+
@with_logging
|
|
129
|
+
def add(a: int, b: int) -> int:
|
|
130
|
+
return a + b
|
|
131
|
+
|
|
132
|
+
# Python 3.12+ — Simplified generic syntax (PEP 695)
|
|
133
|
+
def first_item[T](items: list[T]) -> T | None: # 3.12+ syntax
|
|
134
|
+
return items[0] if items else None
|
|
135
|
+
|
|
136
|
+
type Point = tuple[float, float] # 3.12+ type alias syntax
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Pydantic v2 (Data Validation)
|
|
142
|
+
|
|
143
|
+
### Models
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
147
|
+
from datetime import datetime
|
|
148
|
+
from enum import Enum
|
|
149
|
+
|
|
150
|
+
class Role(str, Enum):
|
|
151
|
+
ADMIN = "admin"
|
|
152
|
+
USER = "user"
|
|
153
|
+
MODERATOR = "moderator"
|
|
154
|
+
|
|
155
|
+
class UserCreate(BaseModel):
|
|
156
|
+
name: str = Field(..., min_length=2, max_length=100)
|
|
157
|
+
email: str = Field(..., pattern=r"^[\w.-]+@[\w.-]+\.\w+$")
|
|
158
|
+
age: int = Field(..., ge=13, le=120)
|
|
159
|
+
role: Role = Role.USER
|
|
160
|
+
tags: list[str] = Field(default_factory=list, max_length=10)
|
|
161
|
+
|
|
162
|
+
@field_validator("name")
|
|
163
|
+
@classmethod
|
|
164
|
+
def name_must_be_titlecase(cls, v: str) -> str:
|
|
165
|
+
if not v[0].isupper():
|
|
166
|
+
raise ValueError("Name must start with uppercase")
|
|
167
|
+
return v.strip()
|
|
168
|
+
|
|
169
|
+
@model_validator(mode="after")
|
|
170
|
+
def check_admin_age(self) -> "UserCreate":
|
|
171
|
+
if self.role == Role.ADMIN and self.age < 18:
|
|
172
|
+
raise ValueError("Admins must be 18+")
|
|
173
|
+
return self
|
|
174
|
+
|
|
175
|
+
class UserResponse(BaseModel):
|
|
176
|
+
id: int
|
|
177
|
+
name: str
|
|
178
|
+
email: str
|
|
179
|
+
created_at: datetime
|
|
180
|
+
|
|
181
|
+
model_config = {"from_attributes": True} # ORM mode
|
|
182
|
+
|
|
183
|
+
# ❌ HALLUCINATION TRAP: Pydantic v2 uses model_config dict
|
|
184
|
+
# NOT the inner class Config (Pydantic v1 pattern)
|
|
185
|
+
# ❌ class Config:
|
|
186
|
+
# ❌ orm_mode = True ← REMOVED in v2
|
|
187
|
+
# ✅ model_config = {"from_attributes": True}
|
|
188
|
+
|
|
189
|
+
# ❌ HALLUCINATION TRAP: Pydantic v2 validators use @field_validator
|
|
190
|
+
# NOT @validator (v1) or @root_validator (v1)
|
|
191
|
+
# @field_validator replaces @validator
|
|
192
|
+
# @model_validator replaces @root_validator
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Serialization
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
user = UserCreate(name="Alice", email="alice@test.com", age=30)
|
|
199
|
+
|
|
200
|
+
# Serialize to dict
|
|
201
|
+
data = user.model_dump() # ✅ v2
|
|
202
|
+
# ❌ user.dict() ← REMOVED in v2
|
|
203
|
+
|
|
204
|
+
# Serialize to JSON
|
|
205
|
+
json_str = user.model_json_schema() # schema
|
|
206
|
+
json_str = user.model_dump_json() # data as JSON string
|
|
207
|
+
# ❌ user.json() ← REMOVED in v2
|
|
208
|
+
|
|
209
|
+
# Parse from dict
|
|
210
|
+
user = UserCreate.model_validate({"name": "Bob", "email": "bob@test.com", "age": 25})
|
|
211
|
+
# ❌ UserCreate.parse_obj({...}) ← REMOVED in v2
|
|
212
|
+
|
|
213
|
+
# Parse from JSON
|
|
214
|
+
user = UserCreate.model_validate_json('{"name": "Bob", ...}')
|
|
215
|
+
# ❌ UserCreate.parse_raw(...) ← REMOVED in v2
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## FastAPI Patterns
|
|
221
|
+
|
|
222
|
+
### Basic Route Structure
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
from fastapi import FastAPI, HTTPException, Depends, Query, Path, status
|
|
226
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
227
|
+
|
|
228
|
+
app = FastAPI(
|
|
229
|
+
title="My API",
|
|
230
|
+
version="1.0.0",
|
|
231
|
+
docs_url="/docs", # Swagger UI
|
|
232
|
+
redoc_url="/redoc", # ReDoc
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
app.add_middleware(
|
|
236
|
+
CORSMiddleware,
|
|
237
|
+
allow_origins=["https://myapp.com"], # ❌ NEVER use ["*"] in production
|
|
238
|
+
allow_credentials=True,
|
|
239
|
+
allow_methods=["GET", "POST", "PUT", "DELETE"],
|
|
240
|
+
allow_headers=["*"],
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
@app.get("/users", response_model=list[UserResponse])
|
|
244
|
+
async def list_users(
|
|
245
|
+
skip: int = Query(0, ge=0),
|
|
246
|
+
limit: int = Query(20, ge=1, le=100),
|
|
247
|
+
role: Role | None = Query(None),
|
|
248
|
+
) -> list[UserResponse]:
|
|
249
|
+
query = select(User)
|
|
250
|
+
if role:
|
|
251
|
+
query = query.where(User.role == role)
|
|
252
|
+
users = await db.execute(query.offset(skip).limit(limit))
|
|
253
|
+
return users.scalars().all()
|
|
254
|
+
|
|
255
|
+
@app.get("/users/{user_id}", response_model=UserResponse)
|
|
256
|
+
async def get_user(user_id: int = Path(..., ge=1)) -> UserResponse:
|
|
257
|
+
user = await db.get(User, user_id)
|
|
258
|
+
if not user:
|
|
259
|
+
raise HTTPException(status_code=404, detail="User not found")
|
|
260
|
+
return user
|
|
261
|
+
|
|
262
|
+
@app.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
|
263
|
+
async def create_user(payload: UserCreate) -> UserResponse:
|
|
264
|
+
user = User(**payload.model_dump())
|
|
265
|
+
db.add(user)
|
|
266
|
+
await db.commit()
|
|
267
|
+
await db.refresh(user)
|
|
268
|
+
return user
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Dependency Injection
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
from fastapi import Depends
|
|
275
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
276
|
+
|
|
277
|
+
# Database session dependency
|
|
278
|
+
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
279
|
+
async with async_session() as session:
|
|
280
|
+
try:
|
|
281
|
+
yield session
|
|
282
|
+
finally:
|
|
283
|
+
await session.close()
|
|
284
|
+
|
|
285
|
+
# Auth dependency
|
|
286
|
+
async def get_current_user(
|
|
287
|
+
token: str = Depends(oauth2_scheme),
|
|
288
|
+
db: AsyncSession = Depends(get_db),
|
|
289
|
+
) -> User:
|
|
290
|
+
payload = decode_jwt(token)
|
|
291
|
+
user = await db.get(User, payload["sub"])
|
|
292
|
+
if not user:
|
|
293
|
+
raise HTTPException(status_code=401, detail="Invalid credentials")
|
|
294
|
+
return user
|
|
295
|
+
|
|
296
|
+
# Permission dependency
|
|
297
|
+
def require_role(role: Role):
|
|
298
|
+
async def checker(user: User = Depends(get_current_user)) -> User:
|
|
299
|
+
if user.role != role:
|
|
300
|
+
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
|
301
|
+
return user
|
|
302
|
+
return checker
|
|
303
|
+
|
|
304
|
+
# Usage:
|
|
305
|
+
@app.delete("/users/{user_id}")
|
|
306
|
+
async def delete_user(
|
|
307
|
+
user_id: int,
|
|
308
|
+
db: AsyncSession = Depends(get_db),
|
|
309
|
+
admin: User = Depends(require_role(Role.ADMIN)),
|
|
310
|
+
) -> dict:
|
|
311
|
+
user = await db.get(User, user_id)
|
|
312
|
+
if not user:
|
|
313
|
+
raise HTTPException(status_code=404, detail="Not found")
|
|
314
|
+
await db.delete(user)
|
|
315
|
+
await db.commit()
|
|
316
|
+
return {"deleted": user_id}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Background Tasks & Lifespan
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
from contextlib import asynccontextmanager
|
|
323
|
+
from fastapi import BackgroundTasks
|
|
324
|
+
|
|
325
|
+
# Lifespan (replaces @app.on_event — which is deprecated)
|
|
326
|
+
@asynccontextmanager
|
|
327
|
+
async def lifespan(app: FastAPI):
|
|
328
|
+
# Startup
|
|
329
|
+
await init_db()
|
|
330
|
+
await redis.connect()
|
|
331
|
+
print("App started")
|
|
332
|
+
yield
|
|
333
|
+
# Shutdown
|
|
334
|
+
await redis.close()
|
|
335
|
+
print("App stopped")
|
|
336
|
+
|
|
337
|
+
app = FastAPI(lifespan=lifespan)
|
|
338
|
+
|
|
339
|
+
# ❌ HALLUCINATION TRAP: @app.on_event("startup") is DEPRECATED in FastAPI
|
|
340
|
+
# ✅ Use the lifespan context manager instead
|
|
341
|
+
|
|
342
|
+
# Background tasks
|
|
343
|
+
@app.post("/orders")
|
|
344
|
+
async def create_order(
|
|
345
|
+
order: OrderCreate,
|
|
346
|
+
background_tasks: BackgroundTasks,
|
|
347
|
+
) -> OrderResponse:
|
|
348
|
+
result = await save_order(order)
|
|
349
|
+
background_tasks.add_task(send_confirmation_email, result.email)
|
|
350
|
+
background_tasks.add_task(update_inventory, result.items)
|
|
351
|
+
return result
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Error Handling
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
from fastapi import Request
|
|
358
|
+
from fastapi.responses import JSONResponse
|
|
359
|
+
|
|
360
|
+
class AppError(Exception):
|
|
361
|
+
def __init__(self, message: str, status_code: int = 400):
|
|
362
|
+
self.message = message
|
|
363
|
+
self.status_code = status_code
|
|
364
|
+
|
|
365
|
+
@app.exception_handler(AppError)
|
|
366
|
+
async def app_error_handler(request: Request, exc: AppError) -> JSONResponse:
|
|
367
|
+
return JSONResponse(
|
|
368
|
+
status_code=exc.status_code,
|
|
369
|
+
content={"error": exc.message, "path": str(request.url)},
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
@app.exception_handler(Exception)
|
|
373
|
+
async def unhandled_error_handler(request: Request, exc: Exception) -> JSONResponse:
|
|
374
|
+
# Log the full traceback — critical for debugging
|
|
375
|
+
import traceback
|
|
376
|
+
traceback.print_exc()
|
|
377
|
+
return JSONResponse(
|
|
378
|
+
status_code=500,
|
|
379
|
+
content={"error": "Internal server error"},
|
|
380
|
+
)
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Async Programming
|
|
386
|
+
|
|
387
|
+
### asyncio Patterns
|
|
388
|
+
|
|
389
|
+
```python
|
|
390
|
+
import asyncio
|
|
391
|
+
import httpx
|
|
392
|
+
|
|
393
|
+
# ✅ Parallel async calls
|
|
394
|
+
async def fetch_all_data() -> tuple[Users, Posts, Analytics]:
|
|
395
|
+
async with httpx.AsyncClient() as client:
|
|
396
|
+
users, posts, analytics = await asyncio.gather(
|
|
397
|
+
client.get("https://api.example.com/users"),
|
|
398
|
+
client.get("https://api.example.com/posts"),
|
|
399
|
+
client.get("https://api.example.com/analytics"),
|
|
400
|
+
)
|
|
401
|
+
return users.json(), posts.json(), analytics.json()
|
|
402
|
+
|
|
403
|
+
# ❌ HALLUCINATION TRAP: Do NOT use `requests` in async code
|
|
404
|
+
# `requests` is synchronous and blocks the event loop
|
|
405
|
+
# ✅ Use httpx (async) or aiohttp
|
|
406
|
+
# ❌ import requests ← blocks async event loop
|
|
407
|
+
# ✅ import httpx ← async-native HTTP client
|
|
408
|
+
|
|
409
|
+
# Semaphore — limit concurrent operations
|
|
410
|
+
async def fetch_with_limit(urls: list[str], max_concurrent: int = 10):
|
|
411
|
+
semaphore = asyncio.Semaphore(max_concurrent)
|
|
412
|
+
|
|
413
|
+
async def fetch_one(url: str) -> dict:
|
|
414
|
+
async with semaphore:
|
|
415
|
+
async with httpx.AsyncClient() as client:
|
|
416
|
+
response = await client.get(url)
|
|
417
|
+
return response.json()
|
|
418
|
+
|
|
419
|
+
return await asyncio.gather(*[fetch_one(url) for url in urls])
|
|
420
|
+
|
|
421
|
+
# TaskGroup (Python 3.11+) — structured concurrency
|
|
422
|
+
async def process_batch():
|
|
423
|
+
results = []
|
|
424
|
+
async with asyncio.TaskGroup() as tg:
|
|
425
|
+
task1 = tg.create_task(fetch_users())
|
|
426
|
+
task2 = tg.create_task(fetch_posts())
|
|
427
|
+
# Both tasks guaranteed to complete or all cancel on error
|
|
428
|
+
return task1.result(), task2.result()
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Async Files & Databases
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
import aiofiles
|
|
435
|
+
|
|
436
|
+
# ✅ Async file I/O
|
|
437
|
+
async def read_config(path: str) -> dict:
|
|
438
|
+
async with aiofiles.open(path, "r") as f:
|
|
439
|
+
content = await f.read()
|
|
440
|
+
return json.loads(content)
|
|
441
|
+
|
|
442
|
+
# ❌ HALLUCINATION TRAP: Do NOT use open() in async contexts
|
|
443
|
+
# open() blocks the event loop — use aiofiles instead
|
|
444
|
+
|
|
445
|
+
# Blocking code in async context — use to_thread
|
|
446
|
+
import asyncio
|
|
447
|
+
|
|
448
|
+
async def process_image(path: str) -> bytes:
|
|
449
|
+
# CPU-bound work — offload to thread pool
|
|
450
|
+
return await asyncio.to_thread(heavy_image_processing, path)
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## Dataclasses & Patterns
|
|
456
|
+
|
|
457
|
+
### Modern Dataclasses
|
|
458
|
+
|
|
459
|
+
```python
|
|
460
|
+
from dataclasses import dataclass, field
|
|
461
|
+
from datetime import datetime
|
|
462
|
+
|
|
463
|
+
@dataclass(frozen=True, slots=True) # immutable + memory efficient
|
|
464
|
+
class Point:
|
|
465
|
+
x: float
|
|
466
|
+
y: float
|
|
467
|
+
|
|
468
|
+
def distance_to(self, other: "Point") -> float:
|
|
469
|
+
return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
|
|
470
|
+
|
|
471
|
+
@dataclass(slots=True)
|
|
472
|
+
class Task:
|
|
473
|
+
title: str
|
|
474
|
+
description: str = ""
|
|
475
|
+
tags: list[str] = field(default_factory=list)
|
|
476
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
477
|
+
_internal: str = field(default="", repr=False, init=False)
|
|
478
|
+
|
|
479
|
+
# ✅ slots=True — faster attribute access, less memory
|
|
480
|
+
# ✅ frozen=True — immutable (hashable, safe for sets/dicts)
|
|
481
|
+
# ✅ field(default_factory=list) — never use mutable defaults
|
|
482
|
+
|
|
483
|
+
# ❌ HALLUCINATION TRAP: NEVER use mutable default arguments
|
|
484
|
+
# ❌ def __init__(self, tags: list[str] = []): ← BUG (shared reference)
|
|
485
|
+
# ✅ def __init__(self, tags: list[str] | None = None):
|
|
486
|
+
# self.tags = tags or []
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Pattern Matching (3.10+)
|
|
490
|
+
|
|
491
|
+
```python
|
|
492
|
+
# Structural pattern matching — NOT just a switch statement
|
|
493
|
+
def handle_command(command: dict) -> str:
|
|
494
|
+
match command:
|
|
495
|
+
case {"action": "create", "name": str(name), "type": str(kind)}:
|
|
496
|
+
return f"Creating {kind}: {name}"
|
|
497
|
+
|
|
498
|
+
case {"action": "delete", "id": int(item_id)}:
|
|
499
|
+
return f"Deleting item {item_id}"
|
|
500
|
+
|
|
501
|
+
case {"action": "list", "filter": {"status": str(status)}}:
|
|
502
|
+
return f"Listing items with status: {status}"
|
|
503
|
+
|
|
504
|
+
case {"action": "list"}:
|
|
505
|
+
return "Listing all items"
|
|
506
|
+
|
|
507
|
+
case _:
|
|
508
|
+
raise ValueError(f"Unknown command: {command}")
|
|
509
|
+
|
|
510
|
+
# With guards
|
|
511
|
+
def classify_response(status_code: int) -> str:
|
|
512
|
+
match status_code:
|
|
513
|
+
case code if 200 <= code < 300:
|
|
514
|
+
return "success"
|
|
515
|
+
case 404:
|
|
516
|
+
return "not_found"
|
|
517
|
+
case code if 400 <= code < 500:
|
|
518
|
+
return "client_error"
|
|
519
|
+
case code if 500 <= code < 600:
|
|
520
|
+
return "server_error"
|
|
521
|
+
case _:
|
|
522
|
+
return "unknown"
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## Context Managers
|
|
528
|
+
|
|
529
|
+
```python
|
|
530
|
+
from contextlib import contextmanager, asynccontextmanager
|
|
531
|
+
import time
|
|
532
|
+
|
|
533
|
+
# Sync context manager
|
|
534
|
+
@contextmanager
|
|
535
|
+
def timer(label: str):
|
|
536
|
+
start = time.perf_counter()
|
|
537
|
+
try:
|
|
538
|
+
yield
|
|
539
|
+
finally:
|
|
540
|
+
elapsed = time.perf_counter() - start
|
|
541
|
+
print(f"{label}: {elapsed:.3f}s")
|
|
542
|
+
|
|
543
|
+
# Usage:
|
|
544
|
+
with timer("Database query"):
|
|
545
|
+
results = db.execute(query)
|
|
546
|
+
|
|
547
|
+
# Async context manager
|
|
548
|
+
@asynccontextmanager
|
|
549
|
+
async def managed_transaction(db: AsyncSession):
|
|
550
|
+
try:
|
|
551
|
+
yield db
|
|
552
|
+
await db.commit()
|
|
553
|
+
except Exception:
|
|
554
|
+
await db.rollback()
|
|
555
|
+
raise
|
|
556
|
+
finally:
|
|
557
|
+
await db.close()
|
|
558
|
+
|
|
559
|
+
# Usage:
|
|
560
|
+
async with managed_transaction(session) as db:
|
|
561
|
+
db.add(user)
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## Decorators
|
|
567
|
+
|
|
568
|
+
```python
|
|
569
|
+
import functools
|
|
570
|
+
import time
|
|
571
|
+
from collections.abc import Callable
|
|
572
|
+
from typing import ParamSpec, TypeVar
|
|
573
|
+
|
|
574
|
+
P = ParamSpec("P")
|
|
575
|
+
T = TypeVar("T")
|
|
576
|
+
|
|
577
|
+
# Retry decorator with exponential backoff
|
|
578
|
+
def retry(max_attempts: int = 3, delay: float = 1.0):
|
|
579
|
+
def decorator(func: Callable[P, T]) -> Callable[P, T]:
|
|
580
|
+
@functools.wraps(func)
|
|
581
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
582
|
+
last_error: Exception | None = None
|
|
583
|
+
for attempt in range(max_attempts):
|
|
584
|
+
try:
|
|
585
|
+
return func(*args, **kwargs)
|
|
586
|
+
except Exception as e:
|
|
587
|
+
last_error = e
|
|
588
|
+
if attempt < max_attempts - 1:
|
|
589
|
+
sleep_time = delay * (2 ** attempt)
|
|
590
|
+
time.sleep(sleep_time)
|
|
591
|
+
raise last_error # type: ignore[misc]
|
|
592
|
+
return wrapper
|
|
593
|
+
return decorator
|
|
594
|
+
|
|
595
|
+
@retry(max_attempts=3, delay=0.5)
|
|
596
|
+
def flaky_api_call(url: str) -> dict:
|
|
597
|
+
response = httpx.get(url)
|
|
598
|
+
response.raise_for_status()
|
|
599
|
+
return response.json()
|
|
600
|
+
|
|
601
|
+
# Cache decorator (Python 3.9+ — replaces lru_cache for methods)
|
|
602
|
+
from functools import cache
|
|
603
|
+
|
|
604
|
+
@cache # unbounded cache (use lru_cache(maxsize=N) to limit)
|
|
605
|
+
def fibonacci(n: int) -> int:
|
|
606
|
+
if n < 2:
|
|
607
|
+
return n
|
|
608
|
+
return fibonacci(n - 1) + fibonacci(n - 2)
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
613
|
+
## Testing with pytest
|
|
614
|
+
|
|
615
|
+
### Test Structure
|
|
616
|
+
|
|
617
|
+
```python
|
|
618
|
+
import pytest
|
|
619
|
+
from httpx import AsyncClient, ASGITransport
|
|
620
|
+
from unittest.mock import AsyncMock, patch
|
|
621
|
+
|
|
622
|
+
# Fixtures
|
|
623
|
+
@pytest.fixture
|
|
624
|
+
def sample_user() -> UserCreate:
|
|
625
|
+
return UserCreate(name="Alice", email="alice@test.com", age=30)
|
|
626
|
+
|
|
627
|
+
@pytest.fixture
|
|
628
|
+
async def async_client(app: FastAPI) -> AsyncGenerator[AsyncClient, None]:
|
|
629
|
+
transport = ASGITransport(app=app)
|
|
630
|
+
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
|
631
|
+
yield client
|
|
632
|
+
|
|
633
|
+
# ❌ HALLUCINATION TRAP: Do NOT use TestClient for async FastAPI tests
|
|
634
|
+
# TestClient is synchronous. Use httpx.AsyncClient with ASGITransport.
|
|
635
|
+
|
|
636
|
+
# Basic test
|
|
637
|
+
def test_calculate_tax():
|
|
638
|
+
assert calculate_tax(100, 0.08) == pytest.approx(8.0)
|
|
639
|
+
|
|
640
|
+
# Async test
|
|
641
|
+
@pytest.mark.anyio # or @pytest.mark.asyncio
|
|
642
|
+
async def test_create_user(async_client: AsyncClient):
|
|
643
|
+
response = await async_client.post("/users", json={
|
|
644
|
+
"name": "Alice",
|
|
645
|
+
"email": "alice@test.com",
|
|
646
|
+
"age": 30,
|
|
647
|
+
})
|
|
648
|
+
assert response.status_code == 201
|
|
649
|
+
data = response.json()
|
|
650
|
+
assert data["name"] == "Alice"
|
|
651
|
+
|
|
652
|
+
# Parametrize
|
|
653
|
+
@pytest.mark.parametrize("input_val,expected", [
|
|
654
|
+
(0, "zero"),
|
|
655
|
+
(1, "positive"),
|
|
656
|
+
(-1, "negative"),
|
|
657
|
+
])
|
|
658
|
+
def test_classify(input_val: int, expected: str):
|
|
659
|
+
assert classify(input_val) == expected
|
|
660
|
+
|
|
661
|
+
# Mocking
|
|
662
|
+
async def test_external_api_call():
|
|
663
|
+
with patch("myapp.services.httpx.AsyncClient.get", new_callable=AsyncMock) as mock:
|
|
664
|
+
mock.return_value = httpx.Response(200, json={"data": "mocked"})
|
|
665
|
+
result = await fetch_external_data()
|
|
666
|
+
assert result == {"data": "mocked"}
|
|
667
|
+
mock.assert_called_once()
|
|
668
|
+
|
|
669
|
+
# Exception testing
|
|
670
|
+
def test_invalid_age_raises():
|
|
671
|
+
with pytest.raises(ValueError, match="Admins must be 18"):
|
|
672
|
+
UserCreate(name="Kid", email="kid@test.com", age=15, role=Role.ADMIN)
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### conftest.py Patterns
|
|
676
|
+
|
|
677
|
+
```python
|
|
678
|
+
# conftest.py — shared fixtures across all tests
|
|
679
|
+
import pytest
|
|
680
|
+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
|
681
|
+
|
|
682
|
+
@pytest.fixture(scope="session")
|
|
683
|
+
def engine():
|
|
684
|
+
return create_async_engine("sqlite+aiosqlite:///:memory:")
|
|
685
|
+
|
|
686
|
+
@pytest.fixture(autouse=True)
|
|
687
|
+
async def db_session(engine) -> AsyncGenerator[AsyncSession, None]:
|
|
688
|
+
async with engine.begin() as conn:
|
|
689
|
+
await conn.run_sync(Base.metadata.create_all)
|
|
690
|
+
|
|
691
|
+
async with AsyncSession(engine) as session:
|
|
692
|
+
yield session
|
|
693
|
+
|
|
694
|
+
async with engine.begin() as conn:
|
|
695
|
+
await conn.run_sync(Base.metadata.drop_all)
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
---
|
|
699
|
+
|
|
700
|
+
## Project Structure
|
|
701
|
+
|
|
702
|
+
```
|
|
703
|
+
myproject/
|
|
704
|
+
├── pyproject.toml ← project config (replaces setup.py + setup.cfg)
|
|
705
|
+
├── src/
|
|
706
|
+
│ └── myproject/
|
|
707
|
+
│ ├── __init__.py
|
|
708
|
+
│ ├── main.py ← FastAPI app factory
|
|
709
|
+
│ ├── config.py ← Settings via pydantic-settings
|
|
710
|
+
│ ├── models/ ← SQLAlchemy / Pydantic models
|
|
711
|
+
│ ├── routes/ ← API route modules
|
|
712
|
+
│ ├── services/ ← Business logic
|
|
713
|
+
│ ├── repositories/ ← Database access layer
|
|
714
|
+
│ └── utils/ ← Shared utilities
|
|
715
|
+
├── tests/
|
|
716
|
+
│ ├── conftest.py
|
|
717
|
+
│ ├── test_routes/
|
|
718
|
+
│ ├── test_services/
|
|
719
|
+
│ └── test_models/
|
|
720
|
+
├── alembic/ ← Database migrations
|
|
721
|
+
│ └── versions/
|
|
722
|
+
└── .env ← Environment variables (git-ignored)
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### Settings with pydantic-settings
|
|
726
|
+
|
|
727
|
+
```python
|
|
728
|
+
from pydantic_settings import BaseSettings
|
|
729
|
+
from pydantic import Field
|
|
730
|
+
|
|
731
|
+
class Settings(BaseSettings):
|
|
732
|
+
database_url: str = Field(..., alias="DATABASE_URL")
|
|
733
|
+
redis_url: str = Field("redis://localhost:6379", alias="REDIS_URL")
|
|
734
|
+
jwt_secret: str = Field(..., alias="JWT_SECRET")
|
|
735
|
+
debug: bool = False
|
|
736
|
+
environment: str = "development"
|
|
737
|
+
|
|
738
|
+
model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}
|
|
739
|
+
|
|
740
|
+
# ❌ HALLUCINATION TRAP: pydantic-settings is a SEPARATE package in v2
|
|
741
|
+
# pip install pydantic-settings
|
|
742
|
+
# ❌ from pydantic import BaseSettings ← REMOVED from pydantic v2 core
|
|
743
|
+
# ✅ from pydantic_settings import BaseSettings
|
|
744
|
+
|
|
745
|
+
settings = Settings() # reads from .env and environment variables
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
## Common Idioms
|
|
751
|
+
|
|
752
|
+
### Comprehensions & Generators
|
|
753
|
+
|
|
754
|
+
```python
|
|
755
|
+
# Dict comprehension with filtering
|
|
756
|
+
active_users = {u.id: u.name for u in users if u.is_active}
|
|
757
|
+
|
|
758
|
+
# Set comprehension
|
|
759
|
+
unique_domains = {email.split("@")[1] for email in emails}
|
|
760
|
+
|
|
761
|
+
# Generator (lazy — doesn't load all into memory)
|
|
762
|
+
def read_large_file(path: str):
|
|
763
|
+
with open(path) as f:
|
|
764
|
+
for line in f:
|
|
765
|
+
yield line.strip()
|
|
766
|
+
|
|
767
|
+
# Walrus operator (:=) — assign and test
|
|
768
|
+
while chunk := f.read(8192):
|
|
769
|
+
process(chunk)
|
|
770
|
+
|
|
771
|
+
# zip with strict (Python 3.10+)
|
|
772
|
+
for name, score in zip(names, scores, strict=True):
|
|
773
|
+
# Raises ValueError if lengths differ
|
|
774
|
+
print(f"{name}: {score}")
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
### Exception Handling
|
|
778
|
+
|
|
779
|
+
```python
|
|
780
|
+
# ✅ CORRECT: Specific exceptions with context
|
|
781
|
+
import logging
|
|
782
|
+
|
|
783
|
+
logger = logging.getLogger(__name__)
|
|
784
|
+
|
|
785
|
+
async def fetch_user(user_id: int) -> User:
|
|
786
|
+
try:
|
|
787
|
+
response = await client.get(f"/users/{user_id}")
|
|
788
|
+
response.raise_for_status()
|
|
789
|
+
return User.model_validate(response.json())
|
|
790
|
+
except httpx.HTTPStatusError as e:
|
|
791
|
+
if e.response.status_code == 404:
|
|
792
|
+
raise UserNotFoundError(f"User {user_id} not found") from e
|
|
793
|
+
logger.error("API error: %s", e.response.text)
|
|
794
|
+
raise
|
|
795
|
+
except httpx.RequestError as e:
|
|
796
|
+
logger.error("Network error fetching user %d: %s", user_id, e)
|
|
797
|
+
raise ServiceUnavailableError("User service unreachable") from e
|
|
798
|
+
|
|
799
|
+
# ❌ NEVER: Bare except or broad Exception catch
|
|
800
|
+
# ❌ except: ← catches SystemExit, KeyboardInterrupt
|
|
801
|
+
# ❌ except Exception: ← too broad
|
|
802
|
+
# ❌ except Exception as e: pass ← silent failure (WORST)
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
---
|
|
806
|
+
|
|
807
|
+
## Output Format
|
|
808
|
+
|
|
809
|
+
When this skill produces or reviews code, structure your output as follows:
|
|
810
|
+
|
|
811
|
+
```
|
|
812
|
+
━━━ Python Pro Report ━━━━━━━━━━━━━━━━━━━━━━━━
|
|
813
|
+
Skill: Python Pro
|
|
814
|
+
Python Ver: 3.12+
|
|
815
|
+
Scope: [N files · N functions]
|
|
816
|
+
─────────────────────────────────────────────────
|
|
817
|
+
✅ Passed: [checks that passed, or "All clean"]
|
|
818
|
+
⚠️ Warnings: [non-blocking issues, or "None"]
|
|
819
|
+
❌ Blocked: [blocking issues requiring fix, or "None"]
|
|
820
|
+
─────────────────────────────────────────────────
|
|
821
|
+
VBC status: PENDING → VERIFIED
|
|
822
|
+
Evidence: [test output / lint pass / compile success]
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
**VBC (Verification-Before-Completion) is mandatory.**
|
|
826
|
+
Do not mark status as VERIFIED until concrete terminal evidence is provided.
|
|
827
|
+
|
|
828
|
+
---
|
|
829
|
+
|
|
830
|
+
## 🤖 LLM-Specific Traps
|
|
831
|
+
|
|
832
|
+
AI coding assistants often fall into specific bad habits when generating Python code. These are strictly forbidden:
|
|
833
|
+
|
|
834
|
+
1. **Legacy Type Imports:** Do NOT import `List`, `Dict`, `Tuple`, `Set`, `Optional`, `Union` from `typing`. Use `list[str]`, `dict[str, int]`, `tuple[int, ...]`, `X | None`, `X | Y` directly (Python 3.10+).
|
|
835
|
+
2. **Pydantic v1 Syntax:** Do NOT use `.dict()`, `.json()`, `.parse_obj()`, `@validator`, `@root_validator`, or `class Config:`. Use `.model_dump()`, `.model_dump_json()`, `.model_validate()`, `@field_validator`, `@model_validator`, and `model_config = {}`.
|
|
836
|
+
3. **`BaseSettings` from Pydantic Core:** `BaseSettings` was moved to the `pydantic-settings` package in v2. Import from `pydantic_settings`, not `pydantic`.
|
|
837
|
+
4. **Synchronous I/O in Async:** Never use `requests`, `open()`, or blocking `time.sleep()` inside `async` functions. Use `httpx`, `aiofiles`, and `asyncio.sleep()`.
|
|
838
|
+
5. **Mutable Default Arguments:** Never use `def func(items=[])` or `def func(data={})`. Use `None` and initialize inside the function body.
|
|
839
|
+
6. **Bare `except:`:** Never catch all exceptions. Always catch specific exception types. Never silence errors with `except: pass`.
|
|
840
|
+
7. **`@app.on_event("startup")`:** This is deprecated in FastAPI. Use the `lifespan` context manager.
|
|
841
|
+
8. **`TestClient` for Async Tests:** Use `httpx.AsyncClient` with `ASGITransport` for async FastAPI tests, not the synchronous `TestClient`.
|
|
842
|
+
9. **String-Interpolated SQL:** Never use f-strings or `.format()` to build SQL queries. Always use parameterized queries or ORM methods.
|
|
843
|
+
10. **Missing `from __future__ import annotations`:** If targeting Python 3.9 with 3.10+ type syntax, you need this import. For 3.10+, it's not needed.
|
|
844
|
+
|
|
845
|
+
---
|
|
846
|
+
|
|
847
|
+
## 🏛️ Tribunal Integration (Anti-Hallucination)
|
|
848
|
+
|
|
849
|
+
**Slash command: `/tribunal-backend`**
|
|
850
|
+
**Active reviewers: `logic` · `security` · `dependency` · `type-safety`**
|
|
851
|
+
|
|
852
|
+
### ❌ Forbidden AI Tropes
|
|
853
|
+
|
|
854
|
+
1. **Blind Assumptions:** Never make an assumption without documenting it clearly with `# VERIFY: [reason]`.
|
|
855
|
+
2. **Silent Degradation:** Catching and suppressing exceptions without logging.
|
|
856
|
+
3. **Context Amnesia:** Forgetting the user's Python version, framework, or package manager constraints.
|
|
857
|
+
4. **Over-Engineering:** Creating abstractions, factory patterns, or metaclasses where a simple function suffices.
|
|
858
|
+
|
|
859
|
+
### ✅ Pre-Flight Self-Audit
|
|
860
|
+
|
|
861
|
+
Review these questions before confirming output:
|
|
862
|
+
```
|
|
863
|
+
✅ Are all function signatures fully typed (params + return)?
|
|
864
|
+
✅ Did I use modern syntax (X | None, not Optional[X])?
|
|
865
|
+
✅ Did I use Pydantic v2 methods (.model_dump, not .dict)?
|
|
866
|
+
✅ Is I/O properly awaited or offloaded with asyncio.to_thread?
|
|
867
|
+
✅ Did I catch specific exceptions (not bare except)?
|
|
868
|
+
✅ Did I use parameterized queries (not f-string SQL)?
|
|
869
|
+
✅ Are mutable defaults avoided (no list/dict as default args)?
|
|
870
|
+
✅ Did I use the lifespan pattern (not @app.on_event)?
|
|
871
|
+
✅ Is the code PEP 8 / black / ruff compliant?
|
|
872
|
+
✅ Did I write pytest tests with proper fixtures?
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
### 🛑 Verification-Before-Completion (VBC) Protocol
|
|
876
|
+
|
|
877
|
+
**CRITICAL:** You must follow a strict "evidence-based closeout" state machine.
|
|
878
|
+
- ❌ **Forbidden:** Ending your task because the code "looks pythonic" or lacks syntax errors.
|
|
879
|
+
- ✅ **Required:** You are explicitly forbidden from completing your task without providing **concrete terminal/test evidence** (e.g., passing `pytest` logs, `mypy --strict` success, or `ruff check` pass) proving the code actually runs correctly.
|