Files
iAOP/core/data-bus/postgres_schema.py

155 lines
5.7 KiB
Python
Raw Permalink 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 -*-
"""PostgreSQL 关系表 schema(模板 / 模型 / 用户 / 权限)—— 子任务 #30。
按模板自动生成隔离 schema(`tpl_{tpl}`),内含四张基础关系表:
- templates 模板注册表(行业模板配置资产);
- models 模型注册表(模型版本 / 算法 / 超参包);
- users 用户表(角色 + 细粒度权限 JSONB);
- permissions 角色-资源-动作权限矩阵。
另生成角色授权语句(GRANT),按模板配置声明 databus_ro / databus_rw 等角色。
不依赖 psycopg2:仅产出 DDL 文本,供模板配置台预览 / 运维执行。
"""
from __future__ import annotations
from typing import List, Optional
from .templating import TemplateNaming
# 默认基础表清单(对应子任务 #30「模板/模型/用户/权限」)
DEFAULT_TABLES: List[str] = ["templates", "models", "users", "permissions"]
# 表 → 列定义(可被模板 YAML 的 schema_tables 覆盖为子集/扩展)
TABLE_COLUMNS: dict = {
"templates": [
"id BIGSERIAL PRIMARY KEY",
"template VARCHAR(64) NOT NULL UNIQUE",
"version VARCHAR(32) NOT NULL DEFAULT '0.1.0'",
"config JSONB NOT NULL DEFAULT '{{}}'::jsonb", # format() 转义 {}
"status VARCHAR(16) NOT NULL DEFAULT 'draft'",
"created_at TIMESTAMPTZ NOT NULL DEFAULT now()",
],
"models": [
"id BIGSERIAL PRIMARY KEY",
"model_id VARCHAR(128) NOT NULL UNIQUE",
"template VARCHAR(64) REFERENCES {schema}.templates(template) ON DELETE CASCADE",
"algorithm VARCHAR(32) NOT NULL DEFAULT 'xgboost'",
"hyperparams JSONB NOT NULL DEFAULT '{{}}'::jsonb", # format() 转义 {}
"version VARCHAR(32) NOT NULL DEFAULT '0.1.0'",
"status VARCHAR(16) NOT NULL DEFAULT 'staging'",
"created_at TIMESTAMPTZ NOT NULL DEFAULT now()",
],
"users": [
"id BIGSERIAL PRIMARY KEY",
"username VARCHAR(64) NOT NULL UNIQUE",
"role VARCHAR(32) NOT NULL DEFAULT 'viewer'",
"permissions JSONB NOT NULL DEFAULT '[]'::jsonb",
"created_at TIMESTAMPTZ NOT NULL DEFAULT now()",
],
"permissions": [
"id BIGSERIAL PRIMARY KEY",
"role VARCHAR(32) NOT NULL",
"resource VARCHAR(64) NOT NULL",
"action VARCHAR(16) NOT NULL",
"UNIQUE (role, resource, action)",
],
}
def generate_schema_ddl(
naming: TemplateNaming,
tables: Optional[List[str]] = None,
table_columns: Optional[dict] = None,
) -> str:
"""生成 schema 与基础关系表的完整 DDL(幂等 IF NOT EXISTS)。
Args:
naming: 模板命名器(决定 schema 名)。
tables: 表清单(默认 DEFAULT_TABLES)。
table_columns: 表→列定义覆盖(默认 TABLE_COLUMNS)。
Returns:
多语句 DDL 文本(含 CREATE SCHEMA / CREATE TABLE)。
"""
names = tables or DEFAULT_TABLES
cols = table_columns or TABLE_COLUMNS
schema = naming.pg_schema()
lines = [f"CREATE SCHEMA IF NOT EXISTS {schema};", ""]
for table in names:
columns = cols.get(table)
if not columns:
continue
lines.append(f"CREATE TABLE IF NOT EXISTS {schema}.{table} (")
lines.append(" " + ",\n ".join(c.format(schema=schema) for c in columns))
lines.append(");")
lines.append("")
return "\n".join(lines).rstrip() + "\n"
def generate_grant_ddl(
naming: TemplateNaming,
roles: Optional[List[str]] = None,
) -> List[str]:
"""按模板声明的关系角色生成授权语句。
角色约定:`{role}_ro` 只读、`{role}_rw` 读写(可扩展)。
Args:
naming: 模板命名器。
roles: 角色清单(如 ["databus"] → databus_ro / databus_rw)。
Returns:
GRANT 语句列表。
"""
schema = naming.pg_schema()
grants: List[str] = []
for role in (roles or ["databus"]):
for suffix, privileges in (("ro", "SELECT"), ("rw", "SELECT, INSERT, UPDATE, DELETE")):
role_name = f"{role}_{suffix}"
grants.append(f"GRANT USAGE ON SCHEMA {schema} TO {role_name};")
grants.append(
f"GRANT {privileges} ON ALL TABLES IN SCHEMA {schema} TO {role_name};"
)
if suffix == "rw":
grants.append(
f"GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA {schema} TO {role_name};"
)
return grants
# ---------------------------------------------------------------------------
# 模板配置资产加载(issue #30:换行业只改 config/postgres.template.yaml)
# ---------------------------------------------------------------------------
def load_postgres_config(path: str) -> dict:
"""加载模板配置资产(config/postgres.template.yaml)。"""
import yaml
with open(path, "r", encoding="utf-8") as fh:
return yaml.safe_load(fh) or {}
def generate_from_config(
path: str,
naming: Optional[TemplateNaming] = None,
) -> tuple:
"""按模板配置生成 PostgreSQL schema DDL 与授权语句。
Args:
path: 模板配置资产路径(config/postgres.template.yaml)。
naming: 模板命名器(缺省按资产 template 字段构造)。
Returns:
(schema_ddl, grant_statements):DDL 文本与 GRANT 语句列表。
"""
raw = load_postgres_config(path)
pg = raw.get("postgres", {}) or {}
naming = naming or TemplateNaming(template=str(raw.get("template", "default")))
ddl = generate_schema_ddl(
naming,
tables=pg.get("schema_tables") or None,
table_columns=None, # 列定义用内核默认,避免与实现漂移
)
grants = generate_grant_ddl(naming, roles=pg.get("roles") or None)
return ddl, grants