plugins/languages/python/skills/web/SKILL.md
Python Web 后端开发规范 (FastAPI / Litestar + Pydantic v2 + SQLAlchemy 2.0 async)。涵盖路由组织、依赖注入、请求响应模型、中间件、lifespan、认证、错误处理。在写 REST API、设计 ORM 模型、加中间件、做 OpenAPI 文档、迁移 Flask/Django 时使用。也触发于"FastAPI"、"Litestar"、"REST API"、"Pydantic v2"、"SQLAlchemy 异步"。
npx skillsauth add lazygophers/ccplugin python-webInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
默认 FastAPI 0.115+, 替代选择 Litestar (更现代, 性能更好)。两者 Pydantic v2 + ASGI 都通用, 下文以 FastAPI 为例。
| 框架 | 何时用 | |------|--------| | FastAPI | 默认, 生态最大, 招聘最容易, OpenAPI 一流 | | Litestar | 新项目追求性能 + DI 更强 (channels, msgspec 集成) | | Starlette | 自己拼框架 / 极简 ASGI 中间件 | | Django + DRF | 重度后台 (admin, ORM, auth 套件), 老团队迁移成本 | | Flask | 不推荐用于新项目 (无 async 一等公民, 生态停滞) |
按领域分层, 不按文件类型分:
src/myapp/
├── main.py # FastAPI() 实例 + lifespan
├── api/
│ ├── deps.py # 依赖 (get_db, get_current_user)
│ ├── routers/
│ │ ├── users.py
│ │ └── orders.py
│ └── errors.py # exception_handler
├── domain/
│ ├── users/
│ │ ├── models.py # SQLAlchemy ORM
│ │ ├── schemas.py # Pydantic
│ │ ├── service.py # 业务逻辑
│ │ └── repo.py # 数据访问
│ └── orders/...
├── core/
│ ├── config.py # pydantic-settings
│ ├── db.py # engine, session factory
│ └── logging.py
└── tests/
按文件类型分 (models/, schemas/, services/) 在 ≥10 个领域时混乱。
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
# 启动
app.state.db = await create_db_engine(settings.db_url)
app.state.http = httpx.AsyncClient()
yield
# 关闭
await app.state.http.aclose()
await app.state.db.dispose()
app = FastAPI(title="MyApp", lifespan=lifespan)
app.include_router(users_router, prefix="/api/v1/users", tags=["users"])
不要用 @app.on_event("startup") (已 deprecated)。
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_prefix="APP_")
db_url: str
secret_key: str
debug: bool = False
settings = Settings() # 从环境变量 + .env 加载
不要 os.getenv("DB_URL") 散落到处。
输入输出模型分离, 永远不要把 ORM 对象直接返回:
# schemas.py
from pydantic import BaseModel, ConfigDict, EmailStr
class UserCreate(BaseModel):
username: str
email: EmailStr
password: str # 输入有 password
class UserRead(BaseModel):
model_config = ConfigDict(from_attributes=True) # 从 ORM 转
id: int
username: str
email: EmailStr
# 没有 password
response_model=UserRead 确保 API 不泄漏敏感字段:
@router.post("", response_model=UserRead, status_code=201)
async def create_user(payload: UserCreate, db: DbSession) -> User:
return await user_service.create(db, payload)
Annotated[T, Depends(...)] 替代 Depends() 默认值 (类型检查友好):
from typing import Annotated
from fastapi import Depends
async def get_db(request: Request) -> AsyncIterator[AsyncSession]:
async with AsyncSession(request.app.state.db) as session:
yield session
DbSession = Annotated[AsyncSession, Depends(get_db)]
async def get_current_user(
token: Annotated[str, Depends(oauth2_scheme)],
db: DbSession,
) -> User:
return await auth_service.verify(db, token)
CurrentUser = Annotated[User, Depends(get_current_user)]
@router.get("/me", response_model=UserRead)
async def read_me(user: CurrentUser) -> User:
return user
类型别名 (DbSession, CurrentUser) 让签名清爽。
每个领域一个 router, 路由函数只编排, 业务在 service 层:
# api/routers/users.py
router = APIRouter()
@router.get("/{user_id}", response_model=UserRead)
async def read_user(user_id: int, db: DbSession) -> User:
user = await user_service.get(db, user_id)
if user is None:
raise NotFoundError("user", user_id)
return user
路由函数不超过 20 行, 不写 SQL, 不直接调 httpx。
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import select
class Base(DeclarativeBase): ...
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
username: Mapped[str] = mapped_column(unique=True)
email: Mapped[str]
# 查询用 2.0 风格
async def get_by_email(db: AsyncSession, email: str) -> User | None:
stmt = select(User).where(User.email == email)
return (await db.execute(stmt)).scalar_one_or_none()
不要再用 db.query(User) (1.x legacy API)。
业务层 raise 领域异常 (见 python-error), 在 app 层翻译成 HTTP:
@app.exception_handler(NotFoundError)
async def not_found(req: Request, exc: NotFoundError):
return JSONResponse(404, {"error": "not_found", "resource": exc.resource})
@app.exception_handler(ValidationError)
async def bad_request(req: Request, exc: ValidationError):
return JSONResponse(400, {"error": "validation", "field": exc.field})
不要在 service 里 raise HTTPException (耦合 Web 层)。
from starlette.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_methods=["*"],
allow_headers=["*"],
)
@app.middleware("http")
async def request_id_middleware(request: Request, call_next):
rid = request.headers.get("x-request-id", str(uuid.uuid4()))
structlog.contextvars.bind_contextvars(request_id=rid)
response = await call_next(request)
response.headers["x-request-id"] = rid
return response
JWT + OAuth2 用 python-jose 或 authlib, 密码用 argon2-cffi (不要 bcrypt, 不要 sha256):
from argon2 import PasswordHasher
ph = PasswordHasher()
hashed = ph.hash(password)
ph.verify(hashed, password_attempt) # 抛异常 = 失败
见 python-testing 的"异步测试"章节。FastAPI 用 httpx.AsyncClient + ASGITransport, 不用 TestClient。
/docs 自动生成。提升质量:
summary, descriptiontagsresponses={404: {"model": ErrorResponse}}Field(..., description="...", examples=["..."])response_model= + Pydantic schema)Depends() 当默认值参数 (用 Annotated[T, Depends(...)])@app.on_event("startup") (用 lifespan)requests / 同步 ORM 在 async 路由里 (阻塞 event loop)UserReadTestClient 测异步 app (用 httpx.AsyncClient)development
Go 数据库规范——GORM Model 命名 ModelXxx、表名单数、枚举 uint8 + 常量、索引 idx_ 前缀 + deleted_at leading column、禁 time.Time 统一 int64 unix、禁指针/nullable 字段、TEXT/BLOB/JSON 禁 default、AutoMigrate 禁改主键。设计 DB model、写 GORM tag、建索引、做 migration 审查时触发。
development
Go HTTP API 规范——响应始终 200 + body code 字段、路由 /api/* 全 POST 单段 <Action><Model>、中间件逐路由注册禁 Group(prefix,mw...)、handler 仅返回 (rsp,error)、认证走 header。设计 HTTP API、写路由/handler/中间件时触发。
development
Go 项目结构规范——三层架构(API → Impl → State)、全局状态模式、internal/ 私有包、cmd/ 仅 main.go、go.work 多模块、禁止 Repository 接口和 DI 容器、struct 公共字段开头全 omitempty、handler var rsp 顶声明、禁 legacy migration。设计项目骨架、新建目录、组织包、做架构评审时触发。
development
Go 命名规范——Id/Uid 字段(非 ID)、IsActive/HasMFA 布尔前缀、CreatedAt 时间字段、接收者统一用 p、包名全小写无下划线、泛型类型参数描述性命名、集合字段 xxx_list 禁 xxxs 复数、Enum 0 值 XxxNil 禁 Unknown、禁 Status 统一 State、Set/Update 语义区分。定义结构体字段、函数、变量、包、接收者名、泛型、枚举时触发。