tribunal-kit 3.0.0 → 4.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.
Files changed (233) hide show
  1. package/.agent/ARCHITECTURE.md +99 -99
  2. package/.agent/GEMINI.md +52 -52
  3. package/.agent/agents/accessibility-reviewer.md +187 -220
  4. package/.agent/agents/ai-code-reviewer.md +199 -233
  5. package/.agent/agents/backend-specialist.md +215 -238
  6. package/.agent/agents/code-archaeologist.md +161 -181
  7. package/.agent/agents/database-architect.md +184 -207
  8. package/.agent/agents/debugger.md +191 -218
  9. package/.agent/agents/dependency-reviewer.md +103 -136
  10. package/.agent/agents/devops-engineer.md +218 -238
  11. package/.agent/agents/documentation-writer.md +201 -221
  12. package/.agent/agents/explorer-agent.md +160 -180
  13. package/.agent/agents/frontend-reviewer.md +160 -194
  14. package/.agent/agents/frontend-specialist.md +248 -237
  15. package/.agent/agents/game-developer.md +48 -52
  16. package/.agent/agents/logic-reviewer.md +116 -149
  17. package/.agent/agents/mobile-developer.md +200 -223
  18. package/.agent/agents/mobile-reviewer.md +162 -195
  19. package/.agent/agents/orchestrator.md +181 -211
  20. package/.agent/agents/penetration-tester.md +157 -174
  21. package/.agent/agents/performance-optimizer.md +183 -203
  22. package/.agent/agents/performance-reviewer.md +178 -211
  23. package/.agent/agents/precedence-reviewer.md +213 -0
  24. package/.agent/agents/product-manager.md +142 -162
  25. package/.agent/agents/product-owner.md +6 -25
  26. package/.agent/agents/project-planner.md +142 -162
  27. package/.agent/agents/qa-automation-engineer.md +225 -242
  28. package/.agent/agents/security-auditor.md +174 -194
  29. package/.agent/agents/seo-specialist.md +193 -213
  30. package/.agent/agents/sql-reviewer.md +161 -194
  31. package/.agent/agents/supervisor-agent.md +184 -203
  32. package/.agent/agents/swarm-worker-contracts.md +17 -17
  33. package/.agent/agents/swarm-worker-registry.md +46 -46
  34. package/.agent/agents/test-coverage-reviewer.md +160 -193
  35. package/.agent/agents/test-engineer.md +0 -21
  36. package/.agent/agents/type-safety-reviewer.md +175 -208
  37. package/.agent/patterns/generator.md +9 -9
  38. package/.agent/patterns/inversion.md +12 -12
  39. package/.agent/patterns/pipeline.md +9 -9
  40. package/.agent/patterns/reviewer.md +13 -13
  41. package/.agent/patterns/tool-wrapper.md +9 -9
  42. package/.agent/rules/GEMINI.md +63 -63
  43. package/.agent/scripts/append_flow.js +72 -0
  44. package/.agent/scripts/case_law_manager.py +525 -0
  45. package/.agent/scripts/compress_skills.py +167 -0
  46. package/.agent/scripts/consolidate_skills.py +173 -0
  47. package/.agent/scripts/deep_compress.py +202 -0
  48. package/.agent/scripts/minify_context.py +80 -0
  49. package/.agent/scripts/security_scan.py +1 -1
  50. package/.agent/scripts/skill_evolution.py +563 -0
  51. package/.agent/scripts/strip_tribunal.py +41 -0
  52. package/.agent/skills/agent-organizer/SKILL.md +100 -126
  53. package/.agent/skills/agentic-patterns/SKILL.md +0 -70
  54. package/.agent/skills/ai-prompt-injection-defense/SKILL.md +134 -160
  55. package/.agent/skills/api-patterns/SKILL.md +123 -215
  56. package/.agent/skills/api-security-auditor/SKILL.md +143 -177
  57. package/.agent/skills/app-builder/SKILL.md +334 -50
  58. package/.agent/skills/app-builder/templates/SKILL.md +13 -15
  59. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +16 -16
  60. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +22 -22
  61. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +18 -18
  62. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +20 -20
  63. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +17 -17
  64. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +18 -18
  65. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +21 -21
  66. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +19 -19
  67. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +26 -26
  68. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +26 -26
  69. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +19 -19
  70. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +18 -18
  71. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +20 -20
  72. package/.agent/skills/appflow-wireframe/SKILL.md +95 -121
  73. package/.agent/skills/architecture/SKILL.md +169 -331
  74. package/.agent/skills/authentication-best-practices/SKILL.md +139 -173
  75. package/.agent/skills/bash-linux/SKILL.md +129 -154
  76. package/.agent/skills/behavioral-modes/SKILL.md +8 -69
  77. package/.agent/skills/brainstorming/SKILL.md +436 -104
  78. package/.agent/skills/building-native-ui/SKILL.md +152 -174
  79. package/.agent/skills/clean-code/SKILL.md +331 -360
  80. package/.agent/skills/code-review-checklist/SKILL.md +0 -62
  81. package/.agent/skills/config-validator/SKILL.md +115 -141
  82. package/.agent/skills/csharp-developer/SKILL.md +468 -528
  83. package/.agent/skills/database-design/SKILL.md +104 -369
  84. package/.agent/skills/deployment-procedures/SKILL.md +119 -145
  85. package/.agent/skills/devops-engineer/SKILL.md +295 -332
  86. package/.agent/skills/devops-incident-responder/SKILL.md +87 -113
  87. package/.agent/skills/doc.md +5 -5
  88. package/.agent/skills/documentation-templates/SKILL.md +27 -63
  89. package/.agent/skills/edge-computing/SKILL.md +131 -157
  90. package/.agent/skills/extract-design-system/SKILL.md +108 -134
  91. package/.agent/skills/framer-motion-expert/SKILL.md +111 -855
  92. package/.agent/skills/frontend-design/SKILL.md +151 -499
  93. package/.agent/skills/game-design-expert/SKILL.md +79 -105
  94. package/.agent/skills/game-engineering-expert/SKILL.md +96 -122
  95. package/.agent/skills/geo-fundamentals/SKILL.md +97 -124
  96. package/.agent/skills/github-operations/SKILL.md +279 -314
  97. package/.agent/skills/gsap-expert/SKILL.md +119 -826
  98. package/.agent/skills/i18n-localization/SKILL.md +113 -138
  99. package/.agent/skills/intelligent-routing/SKILL.md +167 -127
  100. package/.agent/skills/lint-and-validate/SKILL.md +16 -52
  101. package/.agent/skills/llm-engineering/SKILL.md +344 -357
  102. package/.agent/skills/local-first/SKILL.md +128 -154
  103. package/.agent/skills/mcp-builder/SKILL.md +92 -118
  104. package/.agent/skills/mobile-design/SKILL.md +213 -219
  105. package/.agent/skills/motion-engineering/SKILL.md +184 -0
  106. package/.agent/skills/nextjs-react-expert/SKILL.md +99 -698
  107. package/.agent/skills/nodejs-best-practices/SKILL.md +498 -559
  108. package/.agent/skills/observability/SKILL.md +293 -330
  109. package/.agent/skills/parallel-agents/SKILL.md +96 -122
  110. package/.agent/skills/performance-profiling/SKILL.md +217 -254
  111. package/.agent/skills/plan-writing/SKILL.md +92 -118
  112. package/.agent/skills/platform-engineer/SKILL.md +97 -123
  113. package/.agent/skills/playwright-best-practices/SKILL.md +137 -162
  114. package/.agent/skills/powershell-windows/SKILL.md +112 -146
  115. package/.agent/skills/project-idioms/SKILL.md +87 -0
  116. package/.agent/skills/python-patterns/SKILL.md +15 -35
  117. package/.agent/skills/python-pro/SKILL.md +148 -754
  118. package/.agent/skills/react-specialist/SKILL.md +123 -827
  119. package/.agent/skills/readme-builder/SKILL.md +23 -85
  120. package/.agent/skills/realtime-patterns/SKILL.md +269 -304
  121. package/.agent/skills/red-team-tactics/SKILL.md +18 -51
  122. package/.agent/skills/rust-pro/SKILL.md +623 -701
  123. package/.agent/skills/seo-fundamentals/SKILL.md +129 -154
  124. package/.agent/skills/server-management/SKILL.md +164 -190
  125. package/.agent/skills/shadcn-ui-expert/SKILL.md +181 -206
  126. package/.agent/skills/skill-creator/SKILL.md +24 -56
  127. package/.agent/skills/sql-pro/SKILL.md +579 -633
  128. package/.agent/skills/supabase-postgres-best-practices/SKILL.md +35 -66
  129. package/.agent/skills/swiftui-expert/SKILL.md +151 -176
  130. package/.agent/skills/systematic-debugging/SKILL.md +92 -118
  131. package/.agent/skills/tailwind-patterns/SKILL.md +516 -576
  132. package/.agent/skills/tdd-workflow/SKILL.md +111 -137
  133. package/.agent/skills/test-result-analyzer/SKILL.md +33 -73
  134. package/.agent/skills/testing-patterns/SKILL.md +512 -573
  135. package/.agent/skills/trend-researcher/SKILL.md +30 -71
  136. package/.agent/skills/ui-ux-pro-max/SKILL.md +8 -41
  137. package/.agent/skills/ui-ux-researcher/SKILL.md +51 -91
  138. package/.agent/skills/vue-expert/SKILL.md +127 -866
  139. package/.agent/skills/vulnerability-scanner/SKILL.md +354 -269
  140. package/.agent/skills/web-accessibility-auditor/SKILL.md +168 -193
  141. package/.agent/skills/web-design-guidelines/SKILL.md +25 -61
  142. package/.agent/skills/webapp-testing/SKILL.md +119 -145
  143. package/.agent/skills/whimsy-injector/SKILL.md +58 -132
  144. package/.agent/skills/workflow-optimizer/SKILL.md +28 -68
  145. package/.agent/workflows/api-tester.md +151 -151
  146. package/.agent/workflows/audit.md +127 -138
  147. package/.agent/workflows/brainstorm.md +110 -110
  148. package/.agent/workflows/changelog.md +112 -112
  149. package/.agent/workflows/create.md +124 -124
  150. package/.agent/workflows/debug.md +165 -189
  151. package/.agent/workflows/deploy.md +180 -189
  152. package/.agent/workflows/enhance.md +128 -151
  153. package/.agent/workflows/fix.md +114 -135
  154. package/.agent/workflows/generate.md +13 -4
  155. package/.agent/workflows/migrate.md +160 -160
  156. package/.agent/workflows/orchestrate.md +168 -168
  157. package/.agent/workflows/performance-benchmarker.md +114 -123
  158. package/.agent/workflows/plan.md +173 -173
  159. package/.agent/workflows/preview.md +80 -80
  160. package/.agent/workflows/refactor.md +161 -183
  161. package/.agent/workflows/review-ai.md +101 -129
  162. package/.agent/workflows/review.md +116 -116
  163. package/.agent/workflows/session.md +94 -94
  164. package/.agent/workflows/status.md +79 -79
  165. package/.agent/workflows/strengthen-skills.md +138 -139
  166. package/.agent/workflows/swarm.md +179 -179
  167. package/.agent/workflows/test.md +189 -211
  168. package/.agent/workflows/tribunal-backend.md +94 -113
  169. package/.agent/workflows/tribunal-database.md +95 -115
  170. package/.agent/workflows/tribunal-frontend.md +96 -118
  171. package/.agent/workflows/tribunal-full.md +93 -133
  172. package/.agent/workflows/tribunal-mobile.md +95 -119
  173. package/.agent/workflows/tribunal-performance.md +110 -133
  174. package/.agent/workflows/ui-ux-pro-max.md +122 -143
  175. package/README.md +30 -1
  176. package/bin/tribunal-kit.js +175 -12
  177. package/package.json +25 -4
  178. package/.agent/skills/api-patterns/api-style.md +0 -42
  179. package/.agent/skills/api-patterns/auth.md +0 -24
  180. package/.agent/skills/api-patterns/documentation.md +0 -26
  181. package/.agent/skills/api-patterns/graphql.md +0 -41
  182. package/.agent/skills/api-patterns/rate-limiting.md +0 -31
  183. package/.agent/skills/api-patterns/response.md +0 -37
  184. package/.agent/skills/api-patterns/rest.md +0 -40
  185. package/.agent/skills/api-patterns/security-testing.md +0 -122
  186. package/.agent/skills/api-patterns/trpc.md +0 -41
  187. package/.agent/skills/api-patterns/versioning.md +0 -22
  188. package/.agent/skills/app-builder/agent-coordination.md +0 -71
  189. package/.agent/skills/app-builder/feature-building.md +0 -53
  190. package/.agent/skills/app-builder/project-detection.md +0 -34
  191. package/.agent/skills/app-builder/scaffolding.md +0 -118
  192. package/.agent/skills/app-builder/tech-stack.md +0 -40
  193. package/.agent/skills/architecture/context-discovery.md +0 -43
  194. package/.agent/skills/architecture/examples.md +0 -94
  195. package/.agent/skills/architecture/pattern-selection.md +0 -68
  196. package/.agent/skills/architecture/patterns-reference.md +0 -50
  197. package/.agent/skills/architecture/trade-off-analysis.md +0 -77
  198. package/.agent/skills/brainstorming/dynamic-questioning.md +0 -360
  199. package/.agent/skills/database-design/database-selection.md +0 -43
  200. package/.agent/skills/database-design/indexing.md +0 -39
  201. package/.agent/skills/database-design/migrations.md +0 -48
  202. package/.agent/skills/database-design/optimization.md +0 -36
  203. package/.agent/skills/database-design/orm-selection.md +0 -30
  204. package/.agent/skills/database-design/schema-design.md +0 -56
  205. package/.agent/skills/frontend-design/animation-guide.md +0 -331
  206. package/.agent/skills/frontend-design/color-system.md +0 -329
  207. package/.agent/skills/frontend-design/decision-trees.md +0 -418
  208. package/.agent/skills/frontend-design/motion-graphics.md +0 -306
  209. package/.agent/skills/frontend-design/typography-system.md +0 -363
  210. package/.agent/skills/frontend-design/ux-psychology.md +0 -1116
  211. package/.agent/skills/frontend-design/visual-effects.md +0 -383
  212. package/.agent/skills/intelligent-routing/router-manifest.md +0 -65
  213. package/.agent/skills/mobile-design/decision-trees.md +0 -516
  214. package/.agent/skills/mobile-design/mobile-backend.md +0 -491
  215. package/.agent/skills/mobile-design/mobile-color-system.md +0 -420
  216. package/.agent/skills/mobile-design/mobile-debugging.md +0 -122
  217. package/.agent/skills/mobile-design/mobile-design-thinking.md +0 -357
  218. package/.agent/skills/mobile-design/mobile-navigation.md +0 -458
  219. package/.agent/skills/mobile-design/mobile-performance.md +0 -767
  220. package/.agent/skills/mobile-design/mobile-testing.md +0 -356
  221. package/.agent/skills/mobile-design/mobile-typography.md +0 -433
  222. package/.agent/skills/mobile-design/platform-android.md +0 -666
  223. package/.agent/skills/mobile-design/platform-ios.md +0 -561
  224. package/.agent/skills/mobile-design/touch-psychology.md +0 -537
  225. package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +0 -312
  226. package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +0 -240
  227. package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +0 -490
  228. package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +0 -264
  229. package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +0 -581
  230. package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +0 -432
  231. package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +0 -684
  232. package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +0 -150
  233. package/.agent/skills/vulnerability-scanner/checklists.md +0 -121
@@ -1,169 +1,83 @@
1
1
  ---
2
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.
3
+ description: Python 3.12+ specialist. FastAPI, Pydantic v2, asyncio, modern types, pytest. Use when building Python APIs, data pipelines, automation, or any Python code.
4
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
5
+ version: 3.1.0
6
+ last-updated: 2026-04-06
8
7
  ---
9
8
 
10
- # Python Pro — Python 3.12+ Mastery
9
+ # Python 3.12+ — Dense Reference
11
10
 
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.
11
+ ## Hallucination Traps (Read First)
12
+ - `from typing import List, Dict, Optional, Union` `list[str]`, `dict[k,v]`, `X | None`, `X | Y` (Python 3.10+)
13
+ - ❌ `user.dict()` / `user.json()` / `UserCreate.parse_obj()` → ✅ Pydantic v2: `model_dump()`, `model_dump_json()`, `model_validate()`
14
+ - ❌ Pydantic `class Config: orm_mode = True` → ✅ `model_config = {"from_attributes": True}`
15
+ - ❌ `@validator` / `@root_validator` → ✅ `@field_validator` / `@model_validator`
16
+ - ❌ `@app.on_event("startup")` → ✅ `lifespan` context manager (deprecated)
17
+ - ❌ `import requests` in async code → ✅ `httpx.AsyncClient()` (requests BLOCKS the event loop)
18
+ - ❌ `asyncio.run()` inside running loop → ✅ `await` directly or use `loop.create_task()`
19
+ - ❌ `except Exception as e: pass` → ✅ always log or re-raise
14
20
 
15
21
  ---
16
22
 
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
23
+ ## Type System (3.12+)
37
24
 
38
25
  ```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
26
+ # Built-in generics (3.9+)no typing imports needed for basic types
27
+ def process(items: list[str]) -> dict[str, int]: ...
28
+ def find(user_id: int) -> User | None: ... # 3.10+ union
29
+ def parse(raw: str) -> int | float | None: ...
63
30
 
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)
31
+ # Generic syntax (3.12+)
32
+ def first[T](items: list[T]) -> T | None:
33
+ return items[0] if items else None
34
+ type Point = tuple[float, float] # 3.12+ type alias
79
35
 
80
- ```python
36
+ # Protocol (structural typing — duck typing with types)
81
37
  from typing import Protocol, runtime_checkable
82
-
83
38
  @runtime_checkable
84
39
  class Renderable(Protocol):
85
40
  def render(self) -> str: ...
86
41
 
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
42
+ # TypedDict — typed dict with optional keys
43
+ from typing import TypedDict, NotRequired
44
+ class UserPayload(TypedDict):
45
+ name: str; email: str
46
+ age: NotRequired[int] # optional key
107
47
 
108
- ```python
48
+ # ParamSpec — preserve signatures in decorators
109
49
  from typing import TypeVar, ParamSpec
110
50
  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
51
+ T = TypeVar("T"); P = ParamSpec("P")
120
52
  def with_logging(func: Callable[P, T]) -> Callable[P, T]:
121
53
  def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
122
- print(f"Calling {func.__name__}")
123
54
  result = func(*args, **kwargs)
124
- print(f"Result: {result}")
125
55
  return result
126
56
  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
57
  ```
138
58
 
139
59
  ---
140
60
 
141
- ## Pydantic v2 (Data Validation)
142
-
143
- ### Models
61
+ ## Pydantic v2
144
62
 
145
63
  ```python
146
64
  from pydantic import BaseModel, Field, field_validator, model_validator
147
- from datetime import datetime
148
65
  from enum import Enum
149
66
 
150
67
  class Role(str, Enum):
151
- ADMIN = "admin"
152
- USER = "user"
153
- MODERATOR = "moderator"
68
+ ADMIN = "admin"; USER = "user"
154
69
 
155
70
  class UserCreate(BaseModel):
156
71
  name: str = Field(..., min_length=2, max_length=100)
157
72
  email: str = Field(..., pattern=r"^[\w.-]+@[\w.-]+\.\w+$")
158
73
  age: int = Field(..., ge=13, le=120)
159
74
  role: Role = Role.USER
160
- tags: list[str] = Field(default_factory=list, max_length=10)
75
+ tags: list[str] = Field(default_factory=list)
161
76
 
162
77
  @field_validator("name")
163
78
  @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")
79
+ def name_titlecase(cls, v: str) -> str:
80
+ if not v[0].isupper(): raise ValueError("Name must start with uppercase")
167
81
  return v.strip()
168
82
 
169
83
  @model_validator(mode="after")
@@ -173,526 +87,170 @@ class UserCreate(BaseModel):
173
87
  return self
174
88
 
175
89
  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
90
+ id: int; name: str; email: str
91
+ model_config = {"from_attributes": True} # ORM mode (was orm_mode=True in v1)
92
+
93
+ # Serialization
94
+ user.model_dump() # ✅ (was .dict())
95
+ user.model_dump_json() # (was .json())
96
+ user.model_dump(exclude={"password"}, mode="json")
97
+ UserCreate.model_validate({"name": "Alice", "email": "a@b.com", "age": 30}) # (was parse_obj)
98
+ UserCreate.model_validate_json('{"name": "Bob", ...}')
216
99
  ```
217
100
 
218
101
  ---
219
102
 
220
- ## FastAPI Patterns
221
-
222
- ### Basic Route Structure
103
+ ## FastAPI
223
104
 
224
105
  ```python
225
106
  from fastapi import FastAPI, HTTPException, Depends, Query, Path, status
226
- from fastapi.middleware.cors import CORSMiddleware
107
+ from contextlib import asynccontextmanager
108
+
109
+ @asynccontextmanager
110
+ async def lifespan(app: FastAPI):
111
+ await init_db(); await redis.connect() # startup
112
+ yield
113
+ await redis.close() # shutdown
114
+
115
+ app = FastAPI(title="My API", version="1.0.0", lifespan=lifespan)
227
116
 
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
- )
117
+ # CORS — never "*" in production
118
+ from fastapi.middleware.cors import CORSMiddleware
119
+ app.add_middleware(CORSMiddleware,
120
+ allow_origins=["https://myapp.com"], # NEVER ["*"]
121
+ allow_credentials=True, allow_methods=["GET","POST","PUT","DELETE"], allow_headers=["*"])
242
122
 
123
+ # Routes
243
124
  @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
125
+ async def list_users(skip: int = Query(0, ge=0), limit: int = Query(20, le=100)) -> list[UserResponse]:
126
+ return await db.execute(select(User).offset(skip).limit(limit))
261
127
 
262
128
  @app.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
263
129
  async def create_user(payload: UserCreate) -> UserResponse:
264
130
  user = User(**payload.model_dump())
265
- db.add(user)
266
- await db.commit()
267
- await db.refresh(user)
131
+ db.add(user); await db.commit(); await db.refresh(user)
268
132
  return user
269
- ```
270
133
 
271
- ### Dependency Injection
272
-
273
- ```python
274
- from fastapi import Depends
275
- from sqlalchemy.ext.asyncio import AsyncSession
276
-
277
- # Database session dependency
134
+ # Dependency Injection
278
135
  async def get_db() -> AsyncGenerator[AsyncSession, None]:
279
136
  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:
137
+ try: yield session
138
+ finally: await session.close()
139
+
140
+ async def get_current_user(token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db)) -> User:
290
141
  payload = decode_jwt(token)
291
142
  user = await db.get(User, payload["sub"])
292
- if not user:
293
- raise HTTPException(status_code=401, detail="Invalid credentials")
143
+ if not user: raise HTTPException(status_code=401, detail="Invalid credentials")
294
144
  return user
295
145
 
296
- # Permission dependency
297
146
  def require_role(role: Role):
298
147
  async def checker(user: User = Depends(get_current_user)) -> User:
299
- if user.role != role:
300
- raise HTTPException(status_code=403, detail="Insufficient permissions")
148
+ if user.role != role: raise HTTPException(status_code=403, detail="Forbidden")
301
149
  return user
302
150
  return checker
303
151
 
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
152
+ # Background Tasks
323
153
  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
154
  @app.post("/orders")
344
- async def create_order(
345
- order: OrderCreate,
346
- background_tasks: BackgroundTasks,
347
- ) -> OrderResponse:
155
+ async def create_order(order: OrderCreate, bg: BackgroundTasks) -> OrderResponse:
348
156
  result = await save_order(order)
349
- background_tasks.add_task(send_confirmation_email, result.email)
350
- background_tasks.add_task(update_inventory, result.items)
157
+ bg.add_task(send_email, result.email)
351
158
  return result
352
- ```
353
-
354
- ### Error Handling
355
159
 
356
- ```python
357
- from fastapi import Request
160
+ # Exception handlers
358
161
  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
162
  @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
- )
163
+ async def app_error(request: Request, exc: AppError) -> JSONResponse:
164
+ return JSONResponse(status_code=exc.status_code, content={"error": exc.message})
381
165
  ```
382
166
 
383
167
  ---
384
168
 
385
- ## Async Programming
386
-
387
- ### asyncio Patterns
169
+ ## Async Patterns
388
170
 
389
171
  ```python
390
- import asyncio
391
- import httpx
172
+ import asyncio, httpx
392
173
 
393
- # Parallel async calls
394
- async def fetch_all_data() -> tuple[Users, Posts, Analytics]:
174
+ # Parallel calls — await all simultaneously
175
+ async def fetch_all() -> tuple:
395
176
  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"),
177
+ users, posts = await asyncio.gather(
178
+ client.get("/users"), client.get("/posts")
400
179
  )
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"
180
+ return users.json(), posts.json()
181
+
182
+ # Timeout
183
+ async with asyncio.timeout(5.0): # 3.11+ (was asyncio.wait_for)
184
+ result = await slow_operation()
185
+
186
+ # Semaphore limit concurrent ops
187
+ sem = asyncio.Semaphore(10)
188
+ async def limited_fetch(url: str) -> dict:
189
+ async with sem:
190
+ async with httpx.AsyncClient() as client:
191
+ return (await client.get(url)).json()
192
+
193
+ # Producer-Consumer
194
+ async def producer(q: asyncio.Queue[str]):
195
+ for item in data: await q.put(item)
196
+ await q.put(None) # sentinel
197
+
198
+ async def consumer(q: asyncio.Queue[str]):
199
+ while (item := await q.get()) is not None:
200
+ await process(item)
201
+ q.task_done()
523
202
  ```
524
203
 
525
204
  ---
526
205
 
527
- ## Context Managers
206
+ ## Error Handling
528
207
 
529
208
  ```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
209
+ # NEVER silently swallow exceptions
210
+ try: result = await risky_op()
211
+ except SpecificError as e: logger.error("Failed: %s", e); raise
212
+ except Exception: logger.exception("Unexpected"); raise
213
+
214
+ # Custom exceptions with context
215
+ class ServiceError(Exception):
216
+ def __init__(self, msg: str, code: int = 500, context: dict | None = None):
217
+ super().__init__(msg)
218
+ self.code = code; self.context = context or {}
219
+
220
+ # Context managers for cleanup
221
+ from contextlib import asynccontextmanager
548
222
  @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)
223
+ async def managed_connection():
224
+ conn = await db.connect()
225
+ try: yield conn
226
+ finally: await conn.close()
609
227
  ```
610
228
 
611
229
  ---
612
230
 
613
- ## Testing with pytest
614
-
615
- ### Test Structure
231
+ ## Testing (pytest)
616
232
 
617
233
  ```python
618
234
  import pytest
619
235
  from httpx import AsyncClient, ASGITransport
620
- from unittest.mock import AsyncMock, patch
621
236
 
622
- # Fixtures
623
237
  @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:")
238
+ async def client():
239
+ async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
240
+ yield c
685
241
 
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)
242
+ @pytest.mark.anyio
243
+ async def test_create_user(client: AsyncClient):
244
+ r = await client.post("/users", json={"name": "Alice", "email": "a@b.com", "age": 25})
245
+ assert r.status_code == 201
246
+ assert r.json()["name"] == "Alice"
690
247
 
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)
248
+ # Fixtures with factories (avoid fixtures that return complex data directly)
249
+ @pytest.fixture
250
+ def make_user(db_session):
251
+ async def _make(name="Alice", role="user"):
252
+ return await User.create(db=db_session, name=name, role=role)
253
+ return _make
696
254
  ```
697
255
 
698
256
  ---
@@ -700,180 +258,16 @@ async def db_session(engine) -> AsyncGenerator[AsyncSession, None]:
700
258
  ## Project Structure
701
259
 
702
260
  ```
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
261
+ my-api/
262
+ ├── app/
263
+ ├── main.py # FastAPI app + lifespan
264
+ ├── models/ # SQLAlchemy ORM models
265
+ ├── schemas/ # Pydantic request/response models
266
+ ├── routers/ # APIRouter groups
267
+ ├── services/ # Business logic (no FastAPI imports)
268
+ ├── dependencies.py # Shared Depends() callables
269
+ └── config.py # Settings via pydantic-settings
715
270
  ├── 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)
271
+ ├── alembic/ # Migrations
272
+ └── pyproject.toml
723
273
  ```
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.