tribunal-kit 4.3.0 → 4.4.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/history/architecture-explorer.html +352 -0
- package/.agent/history/architecture-graph.yaml +109 -0
- package/.agent/history/graph-cache.json +215 -0
- package/.agent/history/snapshots/migrate_refs.js.json +11 -0
- package/.agent/history/snapshots/scripts__changelog.js.json +12 -0
- package/.agent/history/snapshots/scripts__sync-version.js.json +11 -0
- package/.agent/history/snapshots/scripts__validate-payload.js.json +11 -0
- package/.agent/history/snapshots/test__integration__bridges.test.js.json +13 -0
- package/.agent/history/snapshots/test__integration__init.test.js.json +13 -0
- package/.agent/history/snapshots/test__integration__routing.test.js.json +11 -0
- package/.agent/history/snapshots/test__integration__swarm_dispatcher.test.js.json +13 -0
- package/.agent/history/snapshots/test__integration__wave2.test.js.json +13 -0
- package/.agent/history/snapshots/test__unit__args.test.js.json +10 -0
- package/.agent/history/snapshots/test__unit__case_law_manager.test.js.json +10 -0
- package/.agent/history/snapshots/test__unit__copyDir.test.js.json +13 -0
- package/.agent/history/snapshots/test__unit__graph_tools.test.js.json +11 -0
- package/.agent/history/snapshots/test__unit__selfInstall.test.js.json +13 -0
- package/.agent/history/snapshots/test__unit__semver.test.js.json +10 -0
- package/.agent/history/snapshots/test__unit__swarm_dispatcher.test.js.json +11 -0
- package/.agent/scripts/case_law_manager.js +684 -684
- package/.agent/scripts/dependency_analyzer.js +1 -1
- package/.agent/scripts/graph_builder.js +311 -0
- package/.agent/scripts/graph_visualizer.js +384 -0
- package/.agent/scripts/graph_zoom.js +154 -0
- package/.agent/scripts/mutation_runner.js +280 -0
- package/.agent/skills/agent-organizer/SKILL.md +9 -1
- package/.agent/skills/agentic-patterns/SKILL.md +9 -1
- package/.agent/skills/ai-prompt-injection-defense/SKILL.md +9 -1
- package/.agent/skills/api-patterns/SKILL.md +206 -198
- package/.agent/skills/api-security-auditor/SKILL.md +9 -1
- package/.agent/skills/app-builder/SKILL.md +9 -1
- package/.agent/skills/app-builder/templates/SKILL.md +77 -69
- package/.agent/skills/appflow-wireframe/SKILL.md +9 -1
- package/.agent/skills/architecture/SKILL.md +9 -1
- package/.agent/skills/authentication-best-practices/SKILL.md +9 -1
- package/.agent/skills/bash-linux/SKILL.md +9 -1
- package/.agent/skills/behavioral-modes/SKILL.md +9 -1
- package/.agent/skills/brainstorming/SKILL.md +9 -1
- package/.agent/skills/building-native-ui/SKILL.md +9 -1
- package/.agent/skills/clean-code/SKILL.md +9 -1
- package/.agent/skills/code-review-checklist/SKILL.md +9 -1
- package/.agent/skills/config-validator/SKILL.md +9 -1
- package/.agent/skills/csharp-developer/SKILL.md +9 -1
- package/.agent/skills/data-validation-schemas/SKILL.md +287 -279
- package/.agent/skills/database-design/SKILL.md +199 -191
- package/.agent/skills/deployment-procedures/SKILL.md +9 -1
- package/.agent/skills/devops-engineer/SKILL.md +9 -1
- package/.agent/skills/devops-incident-responder/SKILL.md +9 -1
- package/.agent/skills/documentation-templates/SKILL.md +9 -1
- package/.agent/skills/edge-computing/SKILL.md +9 -1
- package/.agent/skills/error-resilience/SKILL.md +387 -379
- package/.agent/skills/extract-design-system/SKILL.md +9 -1
- package/.agent/skills/framer-motion-expert/SKILL.md +203 -195
- package/.agent/skills/frontend-design/SKILL.md +160 -152
- package/.agent/skills/game-design-expert/SKILL.md +9 -1
- package/.agent/skills/game-engineering-expert/SKILL.md +9 -1
- package/.agent/skills/geo-fundamentals/SKILL.md +9 -1
- package/.agent/skills/github-operations/SKILL.md +9 -1
- package/.agent/skills/gsap-core/SKILL.md +54 -46
- package/.agent/skills/gsap-frameworks/SKILL.md +54 -46
- package/.agent/skills/gsap-performance/SKILL.md +54 -46
- package/.agent/skills/gsap-plugins/SKILL.md +54 -46
- package/.agent/skills/gsap-react/SKILL.md +54 -46
- package/.agent/skills/gsap-scrolltrigger/SKILL.md +54 -46
- package/.agent/skills/gsap-timeline/SKILL.md +54 -46
- package/.agent/skills/gsap-utils/SKILL.md +54 -46
- package/.agent/skills/i18n-localization/SKILL.md +9 -1
- package/.agent/skills/intelligent-routing/SKILL.md +38 -30
- package/.agent/skills/knowledge-graph/SKILL.md +52 -0
- package/.agent/skills/lint-and-validate/SKILL.md +9 -1
- package/.agent/skills/llm-engineering/SKILL.md +9 -1
- package/.agent/skills/local-first/SKILL.md +9 -1
- package/.agent/skills/mcp-builder/SKILL.md +9 -1
- package/.agent/skills/mobile-design/SKILL.md +222 -214
- package/.agent/skills/monorepo-management/SKILL.md +293 -285
- package/.agent/skills/motion-engineering/SKILL.md +193 -185
- package/.agent/skills/nextjs-react-expert/SKILL.md +193 -185
- package/.agent/skills/nodejs-best-practices/SKILL.md +9 -1
- package/.agent/skills/observability/SKILL.md +9 -1
- package/.agent/skills/parallel-agents/SKILL.md +9 -1
- package/.agent/skills/performance-profiling/SKILL.md +9 -1
- package/.agent/skills/plan-writing/SKILL.md +9 -1
- package/.agent/skills/platform-engineer/SKILL.md +9 -1
- package/.agent/skills/playwright-best-practices/SKILL.md +9 -1
- package/.agent/skills/powershell-windows/SKILL.md +9 -1
- package/.agent/skills/project-idioms/SKILL.md +9 -1
- package/.agent/skills/python-patterns/SKILL.md +9 -1
- package/.agent/skills/python-pro/SKILL.md +282 -274
- package/.agent/skills/react-specialist/SKILL.md +236 -228
- package/.agent/skills/readme-builder/SKILL.md +9 -1
- package/.agent/skills/realtime-patterns/SKILL.md +9 -1
- package/.agent/skills/red-team-tactics/SKILL.md +9 -1
- package/.agent/skills/rust-pro/SKILL.md +9 -1
- package/.agent/skills/seo-fundamentals/SKILL.md +9 -1
- package/.agent/skills/server-management/SKILL.md +9 -1
- package/.agent/skills/shadcn-ui-expert/SKILL.md +9 -1
- package/.agent/skills/skill-creator/SKILL.md +9 -1
- package/.agent/skills/sql-pro/SKILL.md +9 -1
- package/.agent/skills/supabase-postgres-best-practices/SKILL.md +9 -1
- package/.agent/skills/swiftui-expert/SKILL.md +9 -1
- package/.agent/skills/systematic-debugging/SKILL.md +9 -1
- package/.agent/skills/tailwind-patterns/SKILL.md +9 -1
- package/.agent/skills/tdd-workflow/SKILL.md +9 -1
- package/.agent/skills/test-result-analyzer/SKILL.md +9 -1
- package/.agent/skills/testing-patterns/SKILL.md +28 -3
- package/.agent/skills/trend-researcher/SKILL.md +9 -1
- package/.agent/skills/typescript-advanced/SKILL.md +294 -286
- package/.agent/skills/ui-ux-pro-max/SKILL.md +561 -116
- package/.agent/skills/ui-ux-researcher/SKILL.md +9 -1
- package/.agent/skills/vue-expert/SKILL.md +234 -226
- package/.agent/skills/vulnerability-scanner/SKILL.md +9 -1
- package/.agent/skills/web-accessibility-auditor/SKILL.md +9 -1
- package/.agent/skills/web-design-guidelines/SKILL.md +9 -1
- package/.agent/skills/webapp-testing/SKILL.md +9 -1
- package/.agent/skills/whimsy-injector/SKILL.md +9 -1
- package/.agent/skills/workflow-optimizer/SKILL.md +9 -1
- package/README.md +242 -242
- package/bin/tribunal-kit.js +157 -21
- package/package.json +81 -80
- package/scripts/validate-payload.js +73 -0
|
@@ -1,276 +1,276 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: python-pro
|
|
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
|
-
allowed-tools: Read, Write, Edit, Glob, Grep
|
|
5
|
-
version: 3.1.0
|
|
6
|
-
last-updated: 2026-04-06
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Python 3.12+ — Dense Reference
|
|
10
|
-
|
|
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
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Type System (3.12+)
|
|
24
|
-
|
|
25
|
-
```python
|
|
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: ...
|
|
30
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
# Protocol (structural typing — duck typing with types)
|
|
37
|
-
from typing import Protocol, runtime_checkable
|
|
38
|
-
@runtime_checkable
|
|
39
|
-
class Renderable(Protocol):
|
|
40
|
-
def render(self) -> str: ...
|
|
41
|
-
|
|
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
|
|
47
|
-
|
|
48
|
-
# ParamSpec — preserve signatures in decorators
|
|
49
|
-
from typing import TypeVar, ParamSpec
|
|
50
|
-
from collections.abc import Callable
|
|
51
|
-
T = TypeVar("T"); P = ParamSpec("P")
|
|
52
|
-
def with_logging(func: Callable[P, T]) -> Callable[P, T]:
|
|
53
|
-
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
54
|
-
result = func(*args, **kwargs)
|
|
55
|
-
return result
|
|
56
|
-
return wrapper
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
## Pydantic v2
|
|
62
|
-
|
|
63
|
-
```python
|
|
64
|
-
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
65
|
-
from enum import Enum
|
|
66
|
-
|
|
67
|
-
class Role(str, Enum):
|
|
68
|
-
ADMIN = "admin"; USER = "user"
|
|
69
|
-
|
|
70
|
-
class UserCreate(BaseModel):
|
|
71
|
-
name: str = Field(..., min_length=2, max_length=100)
|
|
72
|
-
email: str = Field(..., pattern=r"^[\w.-]+@[\w.-]+\.\w+$")
|
|
73
|
-
age: int = Field(..., ge=13, le=120)
|
|
74
|
-
role: Role = Role.USER
|
|
75
|
-
tags: list[str] = Field(default_factory=list)
|
|
76
|
-
|
|
77
|
-
@field_validator("name")
|
|
78
|
-
@classmethod
|
|
79
|
-
def name_titlecase(cls, v: str) -> str:
|
|
80
|
-
if not v[0].isupper(): raise ValueError("Name must start with uppercase")
|
|
81
|
-
return v.strip()
|
|
82
|
-
|
|
83
|
-
@model_validator(mode="after")
|
|
84
|
-
def check_admin_age(self) -> "UserCreate":
|
|
85
|
-
if self.role == Role.ADMIN and self.age < 18:
|
|
86
|
-
raise ValueError("Admins must be 18+")
|
|
87
|
-
return self
|
|
88
|
-
|
|
89
|
-
class UserResponse(BaseModel):
|
|
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", ...}')
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## FastAPI
|
|
104
|
-
|
|
105
|
-
```python
|
|
106
|
-
from fastapi import FastAPI, HTTPException, Depends, Query, Path, status
|
|
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)
|
|
116
|
-
|
|
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=["*"])
|
|
122
|
-
|
|
123
|
-
# Routes
|
|
124
|
-
@app.get("/users", response_model=list[UserResponse])
|
|
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))
|
|
127
|
-
|
|
128
|
-
@app.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
|
129
|
-
async def create_user(payload: UserCreate) -> UserResponse:
|
|
130
|
-
user = User(**payload.model_dump())
|
|
131
|
-
db.add(user); await db.commit(); await db.refresh(user)
|
|
132
|
-
return user
|
|
133
|
-
|
|
134
|
-
# Dependency Injection
|
|
135
|
-
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
136
|
-
async with async_session() as session:
|
|
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:
|
|
141
|
-
payload = decode_jwt(token)
|
|
142
|
-
user = await db.get(User, payload["sub"])
|
|
143
|
-
if not user: raise HTTPException(status_code=401, detail="Invalid credentials")
|
|
144
|
-
return user
|
|
145
|
-
|
|
146
|
-
def require_role(role: Role):
|
|
147
|
-
async def checker(user: User = Depends(get_current_user)) -> User:
|
|
148
|
-
if user.role != role: raise HTTPException(status_code=403, detail="Forbidden")
|
|
149
|
-
return user
|
|
150
|
-
return checker
|
|
151
|
-
|
|
152
|
-
# Background Tasks
|
|
153
|
-
from fastapi import BackgroundTasks
|
|
154
|
-
@app.post("/orders")
|
|
155
|
-
async def create_order(order: OrderCreate, bg: BackgroundTasks) -> OrderResponse:
|
|
156
|
-
result = await save_order(order)
|
|
157
|
-
bg.add_task(send_email, result.email)
|
|
158
|
-
return result
|
|
159
|
-
|
|
160
|
-
# Exception handlers
|
|
161
|
-
from fastapi.responses import JSONResponse
|
|
162
|
-
@app.exception_handler(AppError)
|
|
163
|
-
async def app_error(request: Request, exc: AppError) -> JSONResponse:
|
|
164
|
-
return JSONResponse(status_code=exc.status_code, content={"error": exc.message})
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## Async Patterns
|
|
170
|
-
|
|
171
|
-
```python
|
|
172
|
-
import asyncio, httpx
|
|
173
|
-
|
|
174
|
-
# Parallel calls — await all simultaneously
|
|
175
|
-
async def fetch_all() -> tuple:
|
|
176
|
-
async with httpx.AsyncClient() as client:
|
|
177
|
-
users, posts = await asyncio.gather(
|
|
178
|
-
client.get("/users"), client.get("/posts")
|
|
179
|
-
)
|
|
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()
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
## Error Handling
|
|
207
|
-
|
|
208
|
-
```python
|
|
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
|
|
222
|
-
@asynccontextmanager
|
|
223
|
-
async def managed_connection():
|
|
224
|
-
conn = await db.connect()
|
|
225
|
-
try: yield conn
|
|
226
|
-
finally: await conn.close()
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
---
|
|
230
|
-
|
|
231
|
-
## Testing (pytest)
|
|
232
|
-
|
|
233
|
-
```python
|
|
234
|
-
import pytest
|
|
235
|
-
from httpx import AsyncClient, ASGITransport
|
|
236
|
-
|
|
237
|
-
@pytest.fixture
|
|
238
|
-
async def client():
|
|
239
|
-
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
|
|
240
|
-
yield c
|
|
241
|
-
|
|
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"
|
|
247
|
-
|
|
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
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
---
|
|
257
|
-
|
|
258
|
-
## Project Structure
|
|
259
|
-
|
|
260
|
-
```
|
|
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
|
|
270
|
-
├── tests/
|
|
271
|
-
├── alembic/ # Migrations
|
|
272
|
-
└── pyproject.toml
|
|
273
|
-
```
|
|
1
|
+
---
|
|
2
|
+
name: python-pro
|
|
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
|
+
allowed-tools: Read, Write, Edit, Glob, Grep
|
|
5
|
+
version: 3.1.0
|
|
6
|
+
last-updated: 2026-04-06
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Python 3.12+ — Dense Reference
|
|
10
|
+
|
|
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
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Type System (3.12+)
|
|
24
|
+
|
|
25
|
+
```python
|
|
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: ...
|
|
30
|
+
|
|
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
|
|
35
|
+
|
|
36
|
+
# Protocol (structural typing — duck typing with types)
|
|
37
|
+
from typing import Protocol, runtime_checkable
|
|
38
|
+
@runtime_checkable
|
|
39
|
+
class Renderable(Protocol):
|
|
40
|
+
def render(self) -> str: ...
|
|
41
|
+
|
|
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
|
|
47
|
+
|
|
48
|
+
# ParamSpec — preserve signatures in decorators
|
|
49
|
+
from typing import TypeVar, ParamSpec
|
|
50
|
+
from collections.abc import Callable
|
|
51
|
+
T = TypeVar("T"); P = ParamSpec("P")
|
|
52
|
+
def with_logging(func: Callable[P, T]) -> Callable[P, T]:
|
|
53
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
54
|
+
result = func(*args, **kwargs)
|
|
55
|
+
return result
|
|
56
|
+
return wrapper
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Pydantic v2
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
65
|
+
from enum import Enum
|
|
66
|
+
|
|
67
|
+
class Role(str, Enum):
|
|
68
|
+
ADMIN = "admin"; USER = "user"
|
|
69
|
+
|
|
70
|
+
class UserCreate(BaseModel):
|
|
71
|
+
name: str = Field(..., min_length=2, max_length=100)
|
|
72
|
+
email: str = Field(..., pattern=r"^[\w.-]+@[\w.-]+\.\w+$")
|
|
73
|
+
age: int = Field(..., ge=13, le=120)
|
|
74
|
+
role: Role = Role.USER
|
|
75
|
+
tags: list[str] = Field(default_factory=list)
|
|
76
|
+
|
|
77
|
+
@field_validator("name")
|
|
78
|
+
@classmethod
|
|
79
|
+
def name_titlecase(cls, v: str) -> str:
|
|
80
|
+
if not v[0].isupper(): raise ValueError("Name must start with uppercase")
|
|
81
|
+
return v.strip()
|
|
82
|
+
|
|
83
|
+
@model_validator(mode="after")
|
|
84
|
+
def check_admin_age(self) -> "UserCreate":
|
|
85
|
+
if self.role == Role.ADMIN and self.age < 18:
|
|
86
|
+
raise ValueError("Admins must be 18+")
|
|
87
|
+
return self
|
|
88
|
+
|
|
89
|
+
class UserResponse(BaseModel):
|
|
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", ...}')
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## FastAPI
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from fastapi import FastAPI, HTTPException, Depends, Query, Path, status
|
|
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)
|
|
116
|
+
|
|
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=["*"])
|
|
122
|
+
|
|
123
|
+
# Routes
|
|
124
|
+
@app.get("/users", response_model=list[UserResponse])
|
|
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))
|
|
127
|
+
|
|
128
|
+
@app.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
|
129
|
+
async def create_user(payload: UserCreate) -> UserResponse:
|
|
130
|
+
user = User(**payload.model_dump())
|
|
131
|
+
db.add(user); await db.commit(); await db.refresh(user)
|
|
132
|
+
return user
|
|
133
|
+
|
|
134
|
+
# Dependency Injection
|
|
135
|
+
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
136
|
+
async with async_session() as session:
|
|
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:
|
|
141
|
+
payload = decode_jwt(token)
|
|
142
|
+
user = await db.get(User, payload["sub"])
|
|
143
|
+
if not user: raise HTTPException(status_code=401, detail="Invalid credentials")
|
|
144
|
+
return user
|
|
145
|
+
|
|
146
|
+
def require_role(role: Role):
|
|
147
|
+
async def checker(user: User = Depends(get_current_user)) -> User:
|
|
148
|
+
if user.role != role: raise HTTPException(status_code=403, detail="Forbidden")
|
|
149
|
+
return user
|
|
150
|
+
return checker
|
|
151
|
+
|
|
152
|
+
# Background Tasks
|
|
153
|
+
from fastapi import BackgroundTasks
|
|
154
|
+
@app.post("/orders")
|
|
155
|
+
async def create_order(order: OrderCreate, bg: BackgroundTasks) -> OrderResponse:
|
|
156
|
+
result = await save_order(order)
|
|
157
|
+
bg.add_task(send_email, result.email)
|
|
158
|
+
return result
|
|
159
|
+
|
|
160
|
+
# Exception handlers
|
|
161
|
+
from fastapi.responses import JSONResponse
|
|
162
|
+
@app.exception_handler(AppError)
|
|
163
|
+
async def app_error(request: Request, exc: AppError) -> JSONResponse:
|
|
164
|
+
return JSONResponse(status_code=exc.status_code, content={"error": exc.message})
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Async Patterns
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
import asyncio, httpx
|
|
173
|
+
|
|
174
|
+
# Parallel calls — await all simultaneously
|
|
175
|
+
async def fetch_all() -> tuple:
|
|
176
|
+
async with httpx.AsyncClient() as client:
|
|
177
|
+
users, posts = await asyncio.gather(
|
|
178
|
+
client.get("/users"), client.get("/posts")
|
|
179
|
+
)
|
|
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()
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Error Handling
|
|
207
|
+
|
|
208
|
+
```python
|
|
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
|
|
222
|
+
@asynccontextmanager
|
|
223
|
+
async def managed_connection():
|
|
224
|
+
conn = await db.connect()
|
|
225
|
+
try: yield conn
|
|
226
|
+
finally: await conn.close()
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Testing (pytest)
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
import pytest
|
|
235
|
+
from httpx import AsyncClient, ASGITransport
|
|
236
|
+
|
|
237
|
+
@pytest.fixture
|
|
238
|
+
async def client():
|
|
239
|
+
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
|
|
240
|
+
yield c
|
|
241
|
+
|
|
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"
|
|
247
|
+
|
|
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
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Project Structure
|
|
259
|
+
|
|
260
|
+
```
|
|
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
|
|
270
|
+
├── tests/
|
|
271
|
+
├── alembic/ # Migrations
|
|
272
|
+
└── pyproject.toml
|
|
273
|
+
```
|
|
274
274
|
|
|
275
275
|
|
|
276
276
|
---
|
|
@@ -312,4 +312,12 @@ Review these questions before confirming output:
|
|
|
312
312
|
|
|
313
313
|
**CRITICAL:** You must follow a strict "evidence-based closeout" state machine.
|
|
314
314
|
- ❌ **Forbidden:** Declaring a task complete because the output "looks correct."
|
|
315
|
-
- ✅ **Required:** You are explicitly forbidden from finalizing any task without providing **concrete evidence** (terminal output, passing tests, compile success, or equivalent proof) that your output works as intended.
|
|
315
|
+
- ✅ **Required:** You are explicitly forbidden from finalizing any task without providing **concrete evidence** (terminal output, passing tests, compile success, or equivalent proof) that your output works as intended.
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
## Pre-Flight Checklist
|
|
319
|
+
- [ ] Have I reviewed the user's specific constraints and requests?
|
|
320
|
+
- [ ] Have I checked the environment for relevant existing implementations?
|
|
321
|
+
|
|
322
|
+
## VBC Protocol (Verification-Before-Completion)
|
|
323
|
+
You MUST verify existing code signatures and variables before attempting to modify or call them. No hallucination is permitted.
|