146 lines
5.9 KiB
Python
146 lines
5.9 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""一次性补丁:向 modules/pbl_common/pbl_common/api.py 注入缺失的 `crud` 工厂符号。
|
||
|
||
背景(Bug YyDumUuSK9R9Q_3Ai23AD / rfJ4V-ynABa7dpfJsaEA2 根因 G):
|
||
deploy_test 第 3 轮实测 import 闭包 15/28,其中 pbl_assessment / pbl_scense_ext
|
||
报 `cannot import name 'crud' from 'pbl_common.api'`。下游模块以
|
||
RUB = crud('pbl_rubric', 'pbl', ['code', ...])
|
||
await RUB['read'](id=..) / await RUB['create'](**payload) / await sc['upsert'](['page_key'], ...)
|
||
的「dict 句柄 + 全 **kw」语义取用 CRUD,但 pbl_common.api 只导出了
|
||
tenant_crud / crud_factory / make_crud(CrudBase 实例,双参 (row, where) 语义),
|
||
从未提供 `crud` 这个适配符号 → 包导入即硬失败。
|
||
|
||
本补丁在 api.py 的 crud_factory 导入块之后插入 `crud()` 适配工厂,并把 'crud'
|
||
登记进 CONTRACT_SYMBOLS 与 __all__,使静态契约面与运行时导出一致。幂等:已存在则跳过。
|
||
"""
|
||
import io
|
||
import os
|
||
import sys
|
||
|
||
API = os.path.join(os.path.dirname(__file__), '..', '..', '..',
|
||
'modules', 'pbl_common', 'pbl_common', 'api.py')
|
||
API = os.path.abspath(API)
|
||
|
||
CRUD_SRC = '''
|
||
|
||
# ==========================================================================
|
||
# crud —— dict 句柄适配工厂(下游 pbl_assessment / pbl_scense_ext 取用入口)
|
||
# ==========================================================================
|
||
def crud(table, module=None, columns=None, pk='id', **factory_kw):
|
||
"""返回 dict 形态的租户安全 CRUD 句柄,把 CrudBase 的 (row, where) 双参语义
|
||
适配为下游模块使用的「全 **kw」单参语义。
|
||
|
||
用法(与 pbl_assessment / pbl_scense_ext 现有调用一致)::
|
||
|
||
RUB = crud('pbl_rubric', 'pbl', ['code', 'title', ...])
|
||
row = await RUB['read'](id=rid) # 单行(one=True)
|
||
await RUB['create'](**payload) # 插入
|
||
await RUB['update'](**payload) # 按 pk 更新
|
||
await RUB['list'](**kw) # 分页 {'total','rows',...}
|
||
await sc['upsert'](['page_key'], page_key=k, ...) # 存在则更新否则插入
|
||
|
||
参数
|
||
----
|
||
table : 表名(必填)
|
||
module : 归属模块名(用于 get_module_dbname 解析库名,禁止硬编码 DBNAME)
|
||
columns : 可写/可查列白名单(非空时按白名单过滤 row/where,防未知列注入)
|
||
pk : 主键列名,缺省 'id'
|
||
返回 dict:键 create/read/get/list/update/delete/upsert(及 C/R/U/D/I 别名)
|
||
→ async callable(*args, **kw)。
|
||
"""
|
||
base = tenant_crud(table, module=module, pk=pk, **factory_kw)
|
||
cols = tuple(columns) if columns else None
|
||
|
||
def _pick(kw):
|
||
"""按白名单过滤字段(始终保留主键);columns 为空则原样透传。"""
|
||
data = dict(kw or {})
|
||
if not cols:
|
||
return data
|
||
return {k: v for k, v in data.items() if (k in cols) or (k == pk)}
|
||
|
||
async def _create(*args, **kw):
|
||
return await base.C(_pick(kw))
|
||
|
||
async def _read(*args, **kw):
|
||
return await base.R(_pick(kw), one=True)
|
||
|
||
async def _get(*args, **kw):
|
||
return await base.get(where=_pick(kw))
|
||
|
||
async def _list(*args, **kw):
|
||
kw = dict(kw or {})
|
||
page = kw.pop('page', 1)
|
||
rows = kw.pop('rows', kw.pop('rows_per_page', 20))
|
||
sort = kw.pop('sort', None)
|
||
order = kw.pop('order', 'asc')
|
||
return await base.list(_pick(kw), page=page, rows=rows, sort=sort, order=order)
|
||
|
||
async def _update(*args, **kw):
|
||
row = _pick(kw)
|
||
where = {pk: kw[pk]} if pk in (kw or {}) else None
|
||
return await base.U(row, where=where)
|
||
|
||
async def _delete(*args, **kw):
|
||
return await base.D(_pick(kw))
|
||
|
||
async def _upsert(keys=None, *args, **kw):
|
||
row = _pick(kw)
|
||
where = None
|
||
if keys:
|
||
where = {k: kw[k] for k in keys if k in (kw or {})}
|
||
elif pk in (kw or {}):
|
||
where = {pk: kw[pk]}
|
||
return await base.upsert(row, where=where)
|
||
|
||
return {
|
||
'create': _create, 'read': _read, 'get': _get, 'list': _list,
|
||
'update': _update, 'delete': _delete, 'upsert': _upsert,
|
||
'C': _create, 'R': _read, 'U': _update, 'D': _delete, 'I': _create,
|
||
}
|
||
|
||
'''
|
||
|
||
ANCHOR = "log = logging.getLogger('pbl_common.api')"
|
||
|
||
|
||
def main():
|
||
with io.open(API, 'r', encoding='utf-8') as f:
|
||
src = f.read()
|
||
|
||
if '\ndef crud(' in src:
|
||
print('[patch] crud() already present, skip injection (idempotent)')
|
||
else:
|
||
if ANCHOR not in src:
|
||
print('[patch][FATAL] anchor not found: %r' % ANCHOR)
|
||
return 2
|
||
src = src.replace(ANCHOR, CRUD_SRC.lstrip('\n') + '\n' + ANCHOR, 1)
|
||
print('[patch] injected crud() factory before logger init')
|
||
|
||
# 登记进 CONTRACT_SYMBOLS(crud 段)
|
||
if "'CrudBase', 'tenant_crud', 'crud_factory', 'make_crud', 'bulk_create'," in src \
|
||
and "'crud'," not in src:
|
||
src = src.replace(
|
||
"'CrudBase', 'tenant_crud', 'crud_factory', 'make_crud', 'bulk_create',",
|
||
"'crud', 'CrudBase', 'tenant_crud', 'crud_factory', 'make_crud', 'bulk_create',",
|
||
1)
|
||
print('[patch] registered crud into CONTRACT_SYMBOLS')
|
||
|
||
# 登记进 __all__ 追加段(确保 from pbl_common.api import crud 与 * 导出都可见)
|
||
if "'clear_registry', 'AUDIT_ACTIONS', 'AUDIT_TABLE_MARKERS'," in src \
|
||
and "'crud', 'clear_registry'" not in src:
|
||
src = src.replace(
|
||
"'clear_registry', 'AUDIT_ACTIONS', 'AUDIT_TABLE_MARKERS',",
|
||
"'crud', 'clear_registry', 'AUDIT_ACTIONS', 'AUDIT_TABLE_MARKERS',",
|
||
1)
|
||
print('[patch] registered crud into __all__ tail')
|
||
|
||
with io.open(API, 'w', encoding='utf-8') as f:
|
||
f.write(src)
|
||
print('[patch] written: %s' % API)
|
||
return 0
|
||
|
||
|
||
if __name__ == '__main__':
|
||
sys.exit(main())
|