提交
This commit is contained in:
1 parent
c3be9d1dce
commit
299609706c
328 files changed
+144899
-13873
No files matched your search
@@ -0,0 +1,105 @@
|
||||
#!/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())
|
||||
Reference in new issue
Block a user