#!/usr/bin/env python3 """校验运输计划模块 Pydantic Schema 是否具备 camelCase alias(防 422)。 用法(在 backend 目录): ./venv/bin/python scripts/check_pl_json_aliases.py """ from __future__ import annotations import ast import re import sys from pathlib import Path SCHEMA_FILES = [ "app/schemas/pl/base_data.py", "app/schemas/pl/plan.py", "app/schemas/pl/transport.py", "app/schemas/pl/freight.py", "app/schemas/pl/inventory.py", ] # 单段 snake_case 公共字段可不加 alias(JSON 与 Python 同名) SINGLE_WORD_OK = { "id", "q", "page", "status", "remark", "model", "unit", "action", "comment", "total", "list", "products", "transports", "period", "qty", "amount", } def snake_to_camel(name: str) -> str: parts = name.split("_") return parts[0] + "".join(p.capitalize() for p in parts[1:]) def iter_field_assignments(tree: ast.AST): for node in ast.walk(tree): if not isinstance(node, ast.ClassDef): continue for stmt in node.body: if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name): yield node.name, stmt.target.id, stmt.value elif isinstance(stmt, ast.Assign): for target in stmt.targets: if isinstance(target, ast.Name): yield node.name, target.id, stmt.value def field_has_alias(value: ast.expr | None) -> bool: if value is None: return False if isinstance(value, ast.Call): for kw in value.keywords: if kw.arg == "alias": return True return False def main() -> int: root = Path(__file__).resolve().parents[1] errors: list[str] = [] for rel in SCHEMA_FILES: path = root / rel if not path.exists(): errors.append(f"缺少文件: {rel}") continue source = path.read_text(encoding="utf-8") tree = ast.parse(source, filename=str(path)) for class_name, field_name, value in iter_field_assignments(tree): if "_" not in field_name: continue if field_name in SINGLE_WORD_OK or field_name == "model_config": continue if not field_has_alias(value): expected = snake_to_camel(field_name) errors.append( f"{rel}: {class_name}.{field_name} 缺少 Field(alias=\"{expected}\")" ) if errors: print("PL JSON alias 校验失败(可能导致前端 camelCase 请求 422):\n") for e in errors: print(f" - {e}") print("\n规范: openspec/changes/transport-plan-management/specs/pl-api-json/spec.md") return 1 print("PL JSON alias 校验通过。") return 0 if __name__ == "__main__": sys.exit(main())