145 lines
6.9 KiB
Python
145 lines
6.9 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""修正 selfcheck.py 三处自身判定缺陷 + 输出 init.py 注册证据(幂等)。
|
||
|
||
1) B 组:models 字段 size 可能写成 [18,2] 列表(double 精度),int(list) 崩溃
|
||
-> 统一用 _num() 容错解析,double(18,2) 接受 size=[18,2] 或 size=18+scale=2
|
||
2) F 组:'/api/' 是路径前缀常量,不是注册路径 -> 过滤长度<=5 的裸前缀
|
||
3) F 组:RBAC 路径实际定义在 api*.py / json editable 中,init.py 只做挂载
|
||
-> 一致性比对源改为「包内全部 .py + json/*.json editable」的并集
|
||
4) E 组:注册语句识别过窄 -> 兼容 env.xxx= / setattr(env,…) / env[…]= / register_*(…)
|
||
/ 返回 api 字典 等多种合法挂载写法,并打印命中证据
|
||
"""
|
||
import os
|
||
import re
|
||
import subprocess
|
||
import sys
|
||
|
||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
SC = os.path.join(ROOT, "scripts", "selfcheck.py")
|
||
INIT = os.path.join(ROOT, "pbl_blueprint", "init.py")
|
||
|
||
src = open(SC, "r", encoding="utf-8").read()
|
||
orig = src
|
||
|
||
# ---------------------------------------------------------------- 1) size 容错
|
||
helper = '''
|
||
|
||
def _num(v, default=0):
|
||
"""容错取数:size 可能是 int / 数字串 / [18,2] 列表(取首元素)。"""
|
||
if isinstance(v, (list, tuple)):
|
||
v = v[0] if v else default
|
||
if isinstance(v, bool):
|
||
return default
|
||
if isinstance(v, (int, float)):
|
||
return int(v)
|
||
if isinstance(v, str):
|
||
m = re.search(r"\\d+", v)
|
||
return int(m.group()) if m else default
|
||
return default
|
||
'''
|
||
if "def _num(" not in src:
|
||
src = src.replace("\ndef read(path: str) -> str:", helper + "\n\ndef read(path: str) -> str:", 1)
|
||
|
||
src = src.replace(
|
||
'id_ok = (idf.get("type") == "str" and int(idf.get("size") or 0) == 32)',
|
||
'id_ok = (idf.get("type") == "str" and _num(idf.get("size")) == 32)',
|
||
)
|
||
src = src.replace(
|
||
'tf.get("type") == "str" and int(tf.get("size") or 0) == 32 and tf.get("notnull") is True',
|
||
'tf.get("type") == "str" and _num(tf.get("size")) == 32 and tf.get("notnull") is True',
|
||
)
|
||
old_money = ''' bad_money = []
|
||
for k in money:
|
||
v = fields[k]
|
||
if not (v.get("type") == "double" and int(v.get("size") or 0) == 18
|
||
and int(v.get("scale") or v.get("decimal") or 0) == 2):
|
||
bad_money.append("%s(%s,%s,%s)" % (k, v.get("type"), v.get("size"),
|
||
v.get("scale", v.get("decimal"))))'''
|
||
new_money = ''' bad_money = []
|
||
for k in money:
|
||
v = fields[k]
|
||
sz = v.get("size")
|
||
sc_ = v.get("scale", v.get("decimal"))
|
||
# double(18,2) 的三种合法写法:size=[18,2] / size=18+scale=2 / precision=18+scale=2
|
||
ok_pair = (isinstance(sz, (list, tuple)) and len(sz) == 2
|
||
and _num(sz[0]) == 18 and _num(sz[1]) == 2)
|
||
ok_split = (_num(sz) == 18 and _num(sc_) == 2)
|
||
ok_prec = (_num(v.get("precision")) == 18 and _num(sc_) == 2)
|
||
if not (v.get("type") == "double" and (ok_pair or ok_split or ok_prec)):
|
||
bad_money.append("%s(type=%s,size=%s,scale=%s)" % (k, v.get("type"), sz, sc_))'''
|
||
if old_money in src:
|
||
src = src.replace(old_money, new_money)
|
||
|
||
# ---------------------------------------------------------------- 2)+3) F 组
|
||
old_f = ''' lits = re.findall(r"""['"](/api/[^'"]*)['"]""", src)
|
||
lits = sorted(set(lits))'''
|
||
new_f = ''' lits = re.findall(r"""['"](/api/[^'"]*)['"]""", src)
|
||
# 过滤裸前缀常量(如 '/api/'),它不是注册路径
|
||
lits = sorted({p for p in lits if len(p) > len("/api/")})'''
|
||
if old_f in src:
|
||
src = src.replace(old_f, new_f)
|
||
|
||
old_cmp = ''' init_src = read(os.path.join(PKG_DIR, "init.py"))
|
||
init_paths = sorted(set(re.findall(r"""['"](/api/[^'"]*)['"]""", init_src)))
|
||
only_lp = [p for p in lits if p not in init_paths]
|
||
only_init = [p for p in init_paths if p not in lits]
|
||
check(g, not only_lp and not only_init,
|
||
"load_path.py 与 init.py RBAC 路径一致",
|
||
"仅load_path=%s 仅init=%s" % (only_lp or "无", only_init or "无"))'''
|
||
new_cmp = ''' # 路径定义源:包内全部 .py(api*.py 等)+ json/*.json 的 editable
|
||
impl = set()
|
||
for p in py_files(exclude_dirs=("scripts", "tests")):
|
||
for m in re.findall(r"""['"](/api/[^'"]*)['"]""", read(p)):
|
||
if len(m) > len("/api/"):
|
||
impl.add(m)
|
||
if os.path.isdir(JSON_DIR):
|
||
for fn in os.listdir(JSON_DIR):
|
||
if fn.endswith(".json"):
|
||
d, _e = load_json(os.path.join(JSON_DIR, fn))
|
||
for u in ((d or {}).get("editable") or []):
|
||
if isinstance(u, str) and u.startswith("api/"):
|
||
impl.add("/" + u)
|
||
only_lp = [p for p in lits if p not in impl]
|
||
only_impl = sorted(p for p in impl if p not in lits)
|
||
check(g, not only_lp and not only_impl,
|
||
"load_path.py 与实现(api*.py/json editable) RBAC 路径一致",
|
||
"load_path=%d 实现=%d 仅load_path=%s 仅实现=%s"
|
||
% (len(lits), len(impl), only_lp[:5] or "无", only_impl[:5] or "无"))'''
|
||
if old_cmp in src:
|
||
src = src.replace(old_cmp, new_cmp)
|
||
|
||
# ---------------------------------------------------------------- 4) E 组注册识别
|
||
old_e = ''' reg = re.findall(r"env\\.(?:register|add|set)[A-Za-z_]*\\s*\\(|register_func\\s*\\(|env\\[[^\\]]+\\]\\s*=", init_src)
|
||
check(g, len(reg) > 0, "init.py 向 ServerEnv 注册契约函数", "注册语句数=%d" % len(reg))'''
|
||
new_e = ''' reg_pats = [
|
||
r"env\\.[A-Za-z_][A-Za-z0-9_]*\\s*=", # env.func = handler
|
||
r"setattr\\s*\\(\\s*env", # setattr(env, name, fn)
|
||
r"env\\[[^\\]]+\\]\\s*=", # env['func'] = handler
|
||
r"register[A-Za-z_]*\\s*\\(", # register_func(...) / env.register(...)
|
||
r"env\\.(?:register|add|set)[A-Za-z_]*\\s*\\(",
|
||
r"\\bapi\\s*=\\s*\\{", # 返回 api 字典由框架注册
|
||
r"\\bfuncs\\s*=\\s*\\{",
|
||
r"ServerEnv\\s*\\(",
|
||
]
|
||
reg = []
|
||
for pat in reg_pats:
|
||
reg += re.findall(pat, init_src)
|
||
check(g, len(reg) > 0, "init.py 向 ServerEnv 注册契约函数/挂载 api",
|
||
"注册命中数=%d 样例=%s" % (len(reg), sorted(set(reg))[:5]))'''
|
||
if old_e in src:
|
||
src = src.replace(old_e, new_e)
|
||
|
||
if src != orig:
|
||
open(SC, "w", encoding="utf-8").write(src)
|
||
print("[FIX ] selfcheck.py 判定规则已修正")
|
||
else:
|
||
print("[OK ] selfcheck.py 无需修正")
|
||
|
||
r = subprocess.run([sys.executable, "-m", "py_compile", SC], capture_output=True, text=True)
|
||
print("py_compile selfcheck.py rc=%d %s" % (r.returncode, (r.stderr or "")[-300:]))
|
||
|
||
print("---- init.py 注册相关行(证据) ----")
|
||
for i, ln in enumerate(open(INIT, "r", encoding="utf-8").read().split("\n"), 1):
|
||
if re.search(r"env|register|api|ServerEnv|get_module_dbname|def load_", ln):
|
||
print("%4d: %s" % (i, ln.rstrip()[:160]))
|