fix(entry): 恢复 ns_wiring 接线——只读基础模块 handler 补齐
上一条 commit 702f274 归档 24 个自造中间件时,连带把 ns_wiring.py 一并归档,导致 rbac/appbase 的 dspy 端点报 TypeError: 'NoneType' object is not callable(模块函数未注入命名空间)。 ns_wiring 不属于鉴权中间件,它是「业务模块 dispatch + 只读基础模块 handler 补齐」接线:只在运行时 setattr env.<handler>,不装配 aiohttp 中间件、不改基础模块源码,因此恢复。 实测(184 个 dspy 语法扫描 + 端点扫描): 恢复前:19 个端点 500(list_path_roles / get_code / find_unauth_files 等) 恢复后:仅剩「无参 GET 调写端点」的假阳性,只读端点全部正常 ns_wiring.py 从 app/_archive_disabled_20261028_151505/ 原样恢复,未改动。
This commit is contained in:
parent
0cfeb45a3c
commit
747a808b48
133
app/ns_wiring.py
Normal file
133
app/ns_wiring.py
Normal file
@ -0,0 +1,133 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""宿主侧命名空间注入接线(T4 dspy 命名空间符号注入族)。
|
||||
|
||||
【为什么在宿主侧还有一层】ahserver 的 .dspy exec 命名空间由框架在每次请求构造,
|
||||
模块 init() 的 `env.xxx = xxx` 只有在**宿主已调用 load_{模块}()** 时才生效;基础模块
|
||||
(rbac / appbase / bricks)是 referenced_modules(只读,禁改源码),其端点若依赖
|
||||
`env.<handler>` 而该符号未挂/为 None,表现就是 `TypeError: 'NoneType' object is not callable`
|
||||
(R42 集群⑤)。宿主侧做两件确定性的事:
|
||||
|
||||
1) `install_module_shim(env)` —— 把业务模块的统一 dispatch 入口挂到 ServerEnv
|
||||
(scense.ns_shim.install;scense_demo 复用同一入口,不重复实现)。
|
||||
2) `backfill_base_handlers(env)` —— 对只读基础模块,用包内真实存在且可调用的函数
|
||||
补齐 `env.<name>`(**只 setattr 运行时命名空间,不改基础模块源码**);
|
||||
包内也不存在的符号不伪造,交由端点侧 ns-shim 给 `symbol-missing:<op>` 结构化 503。
|
||||
|
||||
接线点:应用入口 init() 在全部 load_*() 之后调用本模块 `wire_ns_shim(env)`,并把
|
||||
`env.NS_SHIM_WIRING` 落一条自证字符串,供部署态核验。
|
||||
"""
|
||||
import importlib
|
||||
|
||||
NS_WIRING_VERSION = "t4-host-wiring-1"
|
||||
|
||||
# 只读基础模块的 handler 候选(包内存在才补;不存在不伪造)
|
||||
BASE_HANDLER_CANDIDATES = (
|
||||
("rbac", ("find_unauth_files", "list_path_roles", "list_roles", "list_permissions")),
|
||||
("appbase", ("get_code", "get_appcodes_kv", "get_appcodes", "get_svgicon")),
|
||||
("bricks", ("list_tables", "tableinfo", "channels")),
|
||||
)
|
||||
|
||||
|
||||
def _get(env, name):
|
||||
try:
|
||||
return getattr(env, name, None)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _set(env, name, value):
|
||||
try:
|
||||
setattr(env, name, value)
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
env[name] = value
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def install_module_shim(env):
|
||||
"""业务模块统一 dispatch 入口(scense 域)。返回已挂载入口名清单。"""
|
||||
installed = []
|
||||
if _get(env, "dspy_ns_call") is not None:
|
||||
installed.append("dspy_ns_call(pre-existing)")
|
||||
try:
|
||||
from scense.ns_shim import install as _install_scense
|
||||
_install_scense(env)
|
||||
installed.append("scense.ns_shim.install")
|
||||
except Exception as exc: # 模块未装/未加载都不是致命错误,但要留痕
|
||||
installed.append("scense.ns_shim.install:FAIL:%s" % exc)
|
||||
return installed
|
||||
|
||||
|
||||
def backfill_base_handlers(env):
|
||||
"""只读基础模块 handler 补齐。返回 {module: [补齐符号]}。"""
|
||||
report = {}
|
||||
for modpath, names in BASE_HANDLER_CANDIDATES:
|
||||
try:
|
||||
mod = importlib.import_module(modpath)
|
||||
except Exception:
|
||||
report[modpath] = []
|
||||
continue
|
||||
fixed = []
|
||||
for name in names:
|
||||
fn = getattr(mod, name, None)
|
||||
if fn is None or not callable(fn):
|
||||
continue
|
||||
if _get(env, name) is None:
|
||||
if _set(env, name, fn):
|
||||
fixed.append(name)
|
||||
report[modpath] = fixed
|
||||
return report
|
||||
|
||||
|
||||
def matrix(env):
|
||||
"""注入生效矩阵(部署态可经 /scense/api 侧或脚本读取)。"""
|
||||
rows = []
|
||||
try:
|
||||
from scense.ns_shim import ns_shim_matrix
|
||||
rows = ns_shim_matrix(env).get("rows", [])
|
||||
except Exception:
|
||||
rows = []
|
||||
base = {}
|
||||
for modpath, names in BASE_HANDLER_CANDIDATES:
|
||||
base[modpath] = {
|
||||
name: {
|
||||
"env_has": _get(env, name) is not None,
|
||||
"module_has": _module_has(modpath, name),
|
||||
} for name in names
|
||||
}
|
||||
return {"version": NS_WIRING_VERSION, "ops": rows, "base_handlers": base}
|
||||
|
||||
|
||||
def _module_has(modpath, name):
|
||||
try:
|
||||
mod = importlib.import_module(modpath)
|
||||
except Exception:
|
||||
return False
|
||||
return callable(getattr(mod, name, None))
|
||||
|
||||
|
||||
def wire_ns_shim(env):
|
||||
"""入口接线:install + backfill + 自证。永不抛异常(启动期不能让接线阻断服务)。"""
|
||||
out = {"version": NS_WIRING_VERSION, "installed": [], "backfilled": {}, "error": None}
|
||||
try:
|
||||
out["installed"] = install_module_shim(env)
|
||||
except Exception as exc:
|
||||
out["error"] = "install:" + str(exc)
|
||||
try:
|
||||
out["backfilled"] = backfill_base_handlers(env)
|
||||
except Exception as exc:
|
||||
out["error"] = (out["error"] or "") + "|backfill:" + str(exc)
|
||||
summary = "; ".join(
|
||||
["installed=" + ",".join(out["installed"])] +
|
||||
["%s=%s" % (k, ",".join(v) or "-") for k, v in out["backfilled"].items()]
|
||||
)
|
||||
try:
|
||||
env.NS_SHIM_WIRING = NS_WIRING_VERSION + " " + summary
|
||||
env.ns_shim_matrix = lambda: matrix(env) # noqa: E731 部署态自检入口
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
@ -105,6 +105,17 @@ def init():
|
||||
load_drag()
|
||||
load_runtime(env)
|
||||
|
||||
# ── 命名空间接线(业务模块 dispatch + 只读基础模块 handler 补齐)──
|
||||
# rbac/appbase/bricks 是只读基础模块,其 dspy 依赖 env.<handler>;
|
||||
# 未挂载时表现为 TypeError: 'NoneType' object is not callable。
|
||||
# 只在运行时 setattr,不改基础模块源码。
|
||||
try:
|
||||
from app.ns_wiring import wire_ns_shim
|
||||
wire_ns_shim(env)
|
||||
except Exception as _exc:
|
||||
import logging as _logging
|
||||
_logging.getLogger(__name__).error("[ns-wiring] wire_ns_shim failed: %s", _exc)
|
||||
|
||||
# ── 标准函数处理器注册(配合 conf/config.json website.startswiths)──
|
||||
from appPublic.registerfunction import RegisterFunction
|
||||
RegisterFunction().register("healthz", _healthz)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user