| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105 |
- #!/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())
|