380 lines
14 KiB
Python
380 lines
14 KiB
Python
# -*- 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) → <repo>/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")
|