scense/app/ns_wiring.py
ymq 747a808b48 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/ 原样恢复,未改动。
2026-10-28 16:39:56 +08:00

134 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- 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