# -*- coding: utf-8 -*- """M1b 交付补丁脚本(一次性执行): 1) m1b_init.py:表模型路径改指 models/m1b/*.json(四段式),CRUD 定义指 json/m1b/*.json, DDL 路径改指仓库根 sql/m1b_ddl.sql,并接入真实库执行通道(m1b_db.SorDB)。 2) __init__.py:补 M1b 导出(三处同步之「__init__ 导出」环节)。 3) init.py:补 M1b env 注册(三处同步之「init.py env 注册」环节)。 执行:python3 tools/patch_m1b.py """ import io import os import sys REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PKG = os.path.join(REPO, "pbl_blueprint") def read(p): with io.open(p, "r", encoding="utf-8") as f: return f.read() def write(p, s): with io.open(p, "w", encoding="utf-8") as f: f.write(s) print("WROTE %s (%d chars)" % (os.path.relpath(p, REPO), len(s))) def sub_once(text, old, new, tag): if old not in text: raise SystemExit("PATCH FAIL [%s]: anchor not found" % tag) if text.count(old) != 1: raise SystemExit("PATCH FAIL [%s]: anchor not unique (%d)" % (tag, text.count(old))) return text.replace(old, new) # ---------------------------------------------------------------- 1) m1b_init.py p = os.path.join(PKG, "m1b_init.py") t = read(p) old_head = '''_HERE = os.path.dirname(os.path.abspath(__file__)) M1B_MODEL_FILES = [ os.path.join(_HERE, "json", "m1b", "pbl_blueprint_template.json"), os.path.join(_HERE, "json", "m1b", "pbl_ext_field_def.json"), os.path.join(_HERE, "json", "m1b", "pbl_subobject_ext.json"), os.path.join(_HERE, "json", "m1b", "pbl_blueprint_ref.json"), ] M1B_TABLES = ["pbl_blueprint_template", "pbl_ext_field_def", "pbl_subobject_ext", "pbl_blueprint_ref"] M1B_DDL = os.path.join(_HERE, "sql", "m1b_ddl.sql") ''' new_head = '''# 目录规约(module-development-spec / database-table-definition-spec): # * 表定义(四段式 summary/fields/indexes/codes)→ pbl_blueprint/models/m1b/*.json # * CRUD 定义(tblname + params.editable) → pbl_blueprint/json/m1b/*.json # * 幂等 DDL(4 表 CREATE TABLE IF NOT EXISTS) → /sql/m1b_ddl.sql _HERE = os.path.dirname(os.path.abspath(__file__)) # .../pbl_blueprint/pbl_blueprint _REPO = os.path.dirname(_HERE) # .../pbl_blueprint(仓库根) M1B_TABLES = ["pbl_blueprint_template", "pbl_ext_field_def", "pbl_subobject_ext", "pbl_blueprint_ref"] # 表模型(四段式)——建表/字段校验的权威来源 M1B_MODEL_FILES = [ os.path.join(_HERE, "models", "m1b", "pbl_blueprint_template.json"), os.path.join(_HERE, "models", "m1b", "pbl_ext_field_def.json"), os.path.join(_HERE, "models", "m1b", "pbl_subobject_ext.json"), os.path.join(_HERE, "models", "m1b", "pbl_blueprint_ref.json"), ] # CRUD 定义(tblname + params.editable)——供 sqlor/前端 CRUD 装配 M1B_CRUD_FILES = [ os.path.join(_HERE, "json", "m1b", "pbl_blueprint_template.json"), os.path.join(_HERE, "json", "m1b", "pbl_ext_field_def.json"), os.path.join(_HERE, "json", "m1b", "pbl_subobject_ext.json"), os.path.join(_HERE, "json", "m1b", "pbl_blueprint_ref.json"), ] M1B_DDL = os.path.join(_REPO, "sql", "m1b_ddl.sql") ''' t = sub_once(t, old_head, new_head, "m1b_init head") t = sub_once( t, '__all__ = ["M1B_TABLES", "M1B_MODEL_FILES", "init_m1b", "ensure_m1b_tables",\n' ' "ensure_platform_ext_defs", "seed_platform_data"]', '__all__ = ["M1B_TABLES", "M1B_MODEL_FILES", "M1B_CRUD_FILES", "M1B_DDL",\n' ' "PLATFORM_EXT_DEFS", "PLATFORM_TEMPLATES",\n' ' "init_m1b", "ensure_m1b_tables", "ensure_platform_ext_defs",\n' ' "seed_platform_data", "load_m1b_models", "load_m1b_crud_defs"]', "m1b_init __all__") # 建表:接入真实库通道(m1b_db.SorDB.sqlExe),并做 Q-OPEN-3 闸门保护 old_ensure = ''' result = {"ok": True, "models": [m.get("table") for m in models], "ddl": None, "executed": False} ''' new_ensure = ''' result = {"ok": True, "models": [m.get("table") for m in models], "ddl": None, "executed": 0, "statements": 0, "errors": []} ''' t = sub_once(t, old_ensure, new_ensure, "ensure result init") old_ddl = ''' # 2) DDL 兜底 if os.path.exists(M1B_DDL): try: with open(M1B_DDL, "r", encoding="utf-8") as fp: ddl = fp.read() result["ddl"] = M1B_DDL if callable(executor): for stmt in [s.strip() for s in ddl.split(";") if s.strip() and not s.strip().startswith("--")]: try: executor(stmt) except Exception: continue result["executed"] = True except Exception: pass return result ''' new_ddl = ''' # 2) 幂等 DDL(sql/m1b_ddl.sql,4 表 CREATE TABLE IF NOT EXISTS) if os.path.exists(M1B_DDL): try: with open(M1B_DDL, "r", encoding="utf-8") as fp: ddl = fp.read() result["ddl"] = M1B_DDL from .m1b_db import split_ddl, guard_sql stmts = split_ddl(ddl) result["statements"] = len(stmts) # 执行通道优先级:显式 executor > 真实库 SorDB(sqlor 标准 API) runner = None if callable(executor): runner = executor else: try: from .m1b_db import make_db real = make_db() if real is not None: runner = real.sqlExe result["channel"] = "sqlor(SorDB.sqlExe)" except Exception: runner = None if runner is not None: for stmt in stmts: try: guard_sql(stmt) # Q-OPEN-3:拦截任何复用域基表写/改 runner(stmt) result["executed"] += 1 except Exception as e: # 单条失败不阻断其余(幂等重跑可补) result["errors"].append("%s: %s" % (type(e).__name__, e)) result["ok"] = not result["errors"] else: result["channel"] = "none(内存适配器:表按需自动创建)" except Exception as e: result["ok"] = False result["errors"].append("ddl_load: %s" % e) return result def load_m1b_models(): """读取 4 张表的四段式表模型(models/m1b/*.json)。""" out = [] for f in M1B_MODEL_FILES: if not os.path.exists(f): continue try: with open(f, "r", encoding="utf-8") as fp: d = json.load(fp) d["_file"] = os.path.relpath(f, _REPO) out.append(d) except Exception: continue return out def load_m1b_crud_defs(): """读取 4 张表的 CRUD 定义(json/m1b/*.json,tblname + params.editable)。""" out = [] for f in M1B_CRUD_FILES: if not os.path.exists(f): continue try: with open(f, "r", encoding="utf-8") as fp: d = json.load(fp) d["_file"] = os.path.relpath(f, _REPO) out.append(d) except Exception: continue return out ''' t = sub_once(t, old_ddl, new_ddl, "ensure ddl block") # init_m1b 返回值补 CRUD 定义装载证据 t = sub_once( t, ''' return {"ok": True, "milestone": "M1b", "tables": tables, "routes": routes, "seed": seeded, "q_open_3": "world/scene/entity 基表零改动"}''', ''' return {"ok": True, "milestone": "M1b", "tables": tables, "routes": routes, "seed": seeded, "models_loaded": [m.get("_file") for m in load_m1b_models()], "crud_defs_loaded": [c.get("tblname") for c in load_m1b_crud_defs()], "q_open_3": "world/scene/entity/script 基表零改动、零加列、零回写"}''', "init_m1b return") write(p, t) # ---------------------------------------------------------------- 2) __init__.py p = os.path.join(PKG, "__init__.py") t = read(p) old = '''# ------------------------------------------------------------------ 挂载入口 from .init import load_pbl_blueprint # noqa: F401 ''' new = '''# ------------------------------------------------------------------ 挂载入口 from .init import load_pbl_blueprint # noqa: F401 # ------------------------------------------------------------------ M1b(模板平台公共部分 / 子对象扩展 / 关联表判定) # 三处同步之「__init__ 导出」环节:函数定义(m1b_*.py) + 本文件导出 + init.py env 注册 from .m1b_init import ( # noqa: F401 M1B_TABLES, M1B_MODEL_FILES, M1B_CRUD_FILES, M1B_DDL, PLATFORM_EXT_DEFS, PLATFORM_TEMPLATES, init_m1b, ensure_m1b_tables, ensure_platform_ext_defs, seed_platform_data, load_m1b_models, load_m1b_crud_defs, ) from .m1b_api import M1B_ROUTES, dispatch as m1b_dispatch, register_m1b_routes # noqa: F401 from .m1b_db import ( # noqa: F401 PLATFORM_TENANT_KEY, WRITE_PROTECTED_TABLES, PBL_DB_WRITE_PROTECTED, WriteProtectedError, SorDB, tenant_key as m1b_tenant_key, assert_writable as m1b_assert_writable, make_db as m1b_make_db, ) from . import m1b_template # noqa: F401 from . import m1b_subobject # noqa: F401 from . import m1b_ref # noqa: F401 ''' t = sub_once(t, old, new, "__init__ imports") t = sub_once( t, ''' # 挂载入口 "load_pbl_blueprint", ]''', ''' # 挂载入口 "load_pbl_blueprint", # ---- M1b:挂载/建表/种子 "M1B_TABLES", "M1B_MODEL_FILES", "M1B_CRUD_FILES", "M1B_DDL", "PLATFORM_EXT_DEFS", "PLATFORM_TEMPLATES", "init_m1b", "ensure_m1b_tables", "ensure_platform_ext_defs", "seed_platform_data", "load_m1b_models", "load_m1b_crud_defs", # ---- M1b:路由 "M1B_ROUTES", "m1b_dispatch", "register_m1b_routes", # ---- M1b:真实库适配 + Q-OPEN-3 写保护闸门 "PLATFORM_TENANT_KEY", "WRITE_PROTECTED_TABLES", "PBL_DB_WRITE_PROTECTED", "WriteProtectedError", "SorDB", "m1b_tenant_key", "m1b_assert_writable", "m1b_make_db", # ---- M1b:业务子模块 "m1b_template", "m1b_subobject", "m1b_ref", ]''', "__init__ __all__") t = sub_once( t, '__version__ = "1.0.0"', '__version__ = "1.1.0" # M1b:模板平台公共部分/子对象扩展/关联表', "__init__ version") write(p, t) # ---------------------------------------------------------------- 3) init.py p = os.path.join(PKG, "init.py") t = read(p) old = '''MODULE_NAME = "pbl_blueprint" TABLE_NAMES = list(_tables.TABLES.keys()) # 11 表 SUBOBJECT_TYPES = _tables.SUBOBJECT_TYPES # 7 类 ''' new = '''from . import m1b_init as _m1b_init from .m1b_api import M1B_ROUTES, register_m1b_routes from .m1b_init import (M1B_CRUD_FILES, M1B_DDL, M1B_MODEL_FILES, M1B_TABLES, ensure_m1b_tables, init_m1b, load_m1b_crud_defs, load_m1b_models, seed_platform_data) MODULE_NAME = "pbl_blueprint" TABLE_NAMES = list(_tables.TABLES.keys()) # M1a 11 表 SUBOBJECT_TYPES = _tables.SUBOBJECT_TYPES # 7 类 M1B_TABLE_NAMES = list(M1B_TABLES) # M1b 4 表 ALL_TABLE_NAMES = TABLE_NAMES + [t for t in M1B_TABLE_NAMES if t not in TABLE_NAMES] ''' t = sub_once(t, old, new, "init.py imports") t = sub_once( t, '''__all__ = [ "load_pbl_blueprint", "ensure_tables", "MODULE_NAME", "TABLE_NAMES", "SUBOBJECT_TYPES", "BUILTIN_SCHEMA",''', '''__all__ = [ "load_pbl_blueprint", "ensure_tables", "MODULE_NAME", "TABLE_NAMES", "SUBOBJECT_TYPES", "BUILTIN_SCHEMA", # ---- M1b 三处同步之「init.py env 注册」环节 "M1B_TABLES", "M1B_TABLE_NAMES", "ALL_TABLE_NAMES", "M1B_MODEL_FILES", "M1B_CRUD_FILES", "M1B_DDL", "M1B_ROUTES", "init_m1b", "ensure_m1b_tables", "register_m1b_routes", "load_m1b_models", "load_m1b_crud_defs", "seed_platform_data",''', "init.py __all__") old_load = '''def load_pbl_blueprint(app=None, sor=None, ensure=False, **kw): dbname = get_module_dbname(_MODULE_NAME) # noqa: F841 库名由应用注入 if ensure: ensure_tables(sor) if app is not None: try: app.register_module(MODULE_NAME, { "tables": TABLE_NAMES,''' new_load = '''def _m1b_register_callback(app): """解析应用层路由注册回调:register_route / register / route 三选一。""" if app is None: return None for name in ("register_route", "register", "route", "add_route"): fn = getattr(app, name, None) if callable(fn): return fn return None def load_pbl_blueprint(app=None, sor=None, ensure=False, **kw): dbname = get_module_dbname(_MODULE_NAME) # noqa: F841 库名由应用注入 if ensure: ensure_tables(sor) # ------------------------------------------------------------ M1b 挂载 # 三处同步:m1b_api.register_m1b_routes(定义)+ __init__.py(导出)+ 此处(env 注册) # Q-OPEN-3:init_m1b 内部只建 M1b 自有 4 表,绝不触碰 world/scene/entity/script 基表。 m1b_result = {"ok": False, "skipped": True} if kw.get("m1b", True): try: m1b_result = init_m1b( db=kw.get("m1b_db"), env=kw.get("env"), register=_m1b_register_callback(app), executor=kw.get("m1b_executor"), seed=bool(kw.get("m1b_seed", ensure)), ) except Exception as e: # M1b 失败不拖垮 M1a 挂载 m1b_result = {"ok": False, "error": "%s: %s" % (type(e).__name__, e)} if app is not None: try: app.register_module(MODULE_NAME, { "tables": ALL_TABLE_NAMES, "m1b": { "tables": M1B_TABLE_NAMES, "model_files": M1B_MODEL_FILES, "crud_files": M1B_CRUD_FILES, "ddl": M1B_DDL, "routes": [(m, p) for m, p, _ in M1B_ROUTES], "init": m1b_result, },''', t = sub_once(t, old_load, new_load, "init.py load_pbl_blueprint") t = sub_once( t, ''' return {"module": MODULE_NAME, "tables": TABLE_NAMES, "subobject_types": list(SUBOBJECT_TYPES), "loaded": True}''', ''' return {"module": MODULE_NAME, "tables": TABLE_NAMES, "m1b_tables": M1B_TABLE_NAMES, "m1b": m1b_result, "subobject_types": list(SUBOBJECT_TYPES), "loaded": True}''', "init.py return") write(p, t) print("PATCH OK")