test
This commit is contained in:
0
examples/todo_skill/scripts/db/__init__.py
Normal file
0
examples/todo_skill/scripts/db/__init__.py
Normal file
96
examples/todo_skill/scripts/db/connection.py
Normal file
96
examples/todo_skill/scripts/db/connection.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""SQLite 连接与幂等初始化。
|
||||
|
||||
``init_db`` 建两张表:
|
||||
1. ``todos`` — 业务表(demo:待办事项)
|
||||
2. ``_jiangchang_tables`` / ``_jiangchang_columns`` — 宿主数据管理展示元数据
|
||||
|
||||
业务表字段顺序就是宿主列展示顺序(cid 顺序)。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
|
||||
from db.display_metadata import (
|
||||
init_display_metadata_tables,
|
||||
seed_column_metadata_batch,
|
||||
upsert_table_metadata,
|
||||
)
|
||||
from util.runtime_paths import get_db_path
|
||||
|
||||
# ---- 业务表 DDL(待办事项)----
|
||||
# 字段顺序 = 数据管理列展示顺序;状态/优先级用 options_json 给宿主做下拉筛选
|
||||
_TODOS_DDL = """
|
||||
CREATE TABLE IF NOT EXISTS todos (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
priority TEXT NOT NULL DEFAULT 'normal',
|
||||
due_at INTEGER,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
)
|
||||
"""
|
||||
|
||||
_TODOS_INDEX = """
|
||||
CREATE INDEX IF NOT EXISTS idx_todos_status ON todos(status)
|
||||
"""
|
||||
|
||||
|
||||
def get_conn() -> sqlite3.Connection:
|
||||
return sqlite3.connect(get_db_path())
|
||||
|
||||
|
||||
def _seed_todos_display_metadata(cursor) -> None:
|
||||
"""写 ``todos`` 表与字段的中文展示元数据。"""
|
||||
upsert_table_metadata(
|
||||
cursor,
|
||||
table_name="todos",
|
||||
display_name="待办事项",
|
||||
description="本技能的核心业务表:每条待办事项一行。",
|
||||
grid_help="状态变更走技能 CLI(todo done / todo delete),不要在表里直接改。",
|
||||
visible=1,
|
||||
readonly=1, # 整表只读:增删改通过技能 Action 走,宿主不直写
|
||||
default_sort_json='[{"column":"created_at","direction":"desc"}]',
|
||||
default_visible_columns_json='["title","status","priority","due_at","created_at"]',
|
||||
)
|
||||
seed_column_metadata_batch(
|
||||
cursor,
|
||||
table_name="todos",
|
||||
columns=[
|
||||
{"column_name": "id", "display_name": "编号", "description": "主键",
|
||||
"visible": 1, "searchable": 1, "editable": 0, "display_type": "number"},
|
||||
{"column_name": "title", "display_name": "标题", "description": "待办事项内容",
|
||||
"grid_help": "简明描述要做的事", "visible": 1, "searchable": 1, "editable": 0,
|
||||
"display_type": "text"},
|
||||
{"column_name": "status", "display_name": "状态", "description": "pending/done",
|
||||
"grid_help": "pending=待办,done=完成", "visible": 1, "searchable": 1, "editable": 0,
|
||||
"display_type": "enum",
|
||||
"options_json": '[{"value":"pending","label":"待办"},{"value":"done","label":"完成"}]'},
|
||||
{"column_name": "priority", "display_name": "优先级", "description": "high/normal/low",
|
||||
"visible": 1, "searchable": 1, "editable": 0, "display_type": "enum",
|
||||
"options_json": '[{"value":"high","label":"高"},{"value":"normal","label":"中"},{"value":"low","label":"低"}]'},
|
||||
{"column_name": "due_at", "display_name": "截止时间", "description": "Unix 秒;可空",
|
||||
"visible": 1, "searchable": 1, "editable": 0, "display_type": "datetime_unix_seconds"},
|
||||
{"column_name": "created_at", "display_name": "创建时间", "description": "Unix 秒",
|
||||
"visible": 1, "searchable": 0, "editable": 0, "display_type": "datetime_unix_seconds"},
|
||||
{"column_name": "updated_at", "display_name": "更新时间", "description": "Unix 秒",
|
||||
"visible": 0, "searchable": 0, "editable": 0, "display_type": "datetime_unix_seconds"},
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def init_db() -> None:
|
||||
"""幂等创建/迁移本地 SQLite 与数据管理元数据。"""
|
||||
conn = get_conn()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
# 1. 元数据表(先建,方便其它步骤 upsert)
|
||||
init_display_metadata_tables(cur)
|
||||
# 2. 业务表
|
||||
cur.execute(_TODOS_DDL)
|
||||
cur.execute(_TODOS_INDEX)
|
||||
# 3. 业务表展示元数据
|
||||
_seed_todos_display_metadata(cur)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
160
examples/todo_skill/scripts/db/display_metadata.py
Normal file
160
examples/todo_skill/scripts/db/display_metadata.py
Normal file
@@ -0,0 +1,160 @@
|
||||
"""匠厂数据管理展示元数据(``_jiangchang_tables`` / ``_jiangchang_columns``)。
|
||||
|
||||
SQLite 没有原生表/字段 COMMENT,宿主「数据管理」页面靠这两张元数据表
|
||||
拿到中文表名/列名/可编辑性等。字段顺序由 ``CREATE TABLE`` + ``PRAGMA table_info``
|
||||
的 ``cid`` 决定,不读 ``display_order``。
|
||||
|
||||
本文件是模板 ``scripts/db/display_metadata.py`` 的最小可用子集:
|
||||
- ``init_display_metadata_tables``:建元数据表
|
||||
- ``upsert_table_metadata`` / ``upsert_column_metadata``:写入展示元数据
|
||||
- ``list_physical_column_names``:按 cid 列出物理字段名
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Iterable
|
||||
|
||||
# 宿主隐藏这些表,不作为业务表展示
|
||||
INTERNAL_METADATA_TABLES = frozenset({"_jiangchang_tables", "_jiangchang_columns"})
|
||||
|
||||
_JIANGCHANG_TABLES_DDL = """
|
||||
CREATE TABLE IF NOT EXISTS _jiangchang_tables (
|
||||
table_name TEXT PRIMARY KEY,
|
||||
display_name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
grid_help TEXT,
|
||||
visible INTEGER NOT NULL DEFAULT 1,
|
||||
readonly INTEGER NOT NULL DEFAULT 0,
|
||||
default_sort_json TEXT,
|
||||
default_visible_columns_json TEXT
|
||||
)
|
||||
"""
|
||||
|
||||
_JIANGCHANG_COLUMNS_DDL = """
|
||||
CREATE TABLE IF NOT EXISTS _jiangchang_columns (
|
||||
table_name TEXT NOT NULL,
|
||||
column_name TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
grid_help TEXT,
|
||||
display_order INTEGER,
|
||||
visible INTEGER NOT NULL DEFAULT 1,
|
||||
searchable INTEGER NOT NULL DEFAULT 1,
|
||||
editable INTEGER NOT NULL DEFAULT 1,
|
||||
display_type TEXT,
|
||||
options_json TEXT,
|
||||
PRIMARY KEY (table_name, column_name)
|
||||
)
|
||||
"""
|
||||
|
||||
|
||||
def init_display_metadata_tables(cursor) -> None:
|
||||
cursor.execute(_JIANGCHANG_TABLES_DDL)
|
||||
cursor.execute(_JIANGCHANG_COLUMNS_DDL)
|
||||
|
||||
|
||||
def upsert_table_metadata(
|
||||
cursor,
|
||||
*,
|
||||
table_name: str,
|
||||
display_name: str,
|
||||
description: str | None = None,
|
||||
grid_help: str | None = None,
|
||||
visible: int = 1,
|
||||
readonly: int = 0,
|
||||
default_sort_json: str | None = None,
|
||||
default_visible_columns_json: str | None = None,
|
||||
) -> None:
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO _jiangchang_tables (
|
||||
table_name, display_name, description, grid_help, visible, readonly,
|
||||
default_sort_json, default_visible_columns_json
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(table_name) DO UPDATE SET
|
||||
display_name = excluded.display_name,
|
||||
description = excluded.description,
|
||||
grid_help = excluded.grid_help,
|
||||
visible = excluded.visible,
|
||||
readonly = excluded.readonly,
|
||||
default_sort_json = excluded.default_sort_json,
|
||||
default_visible_columns_json = excluded.default_visible_columns_json
|
||||
""",
|
||||
(
|
||||
table_name, display_name, description, grid_help, visible, readonly,
|
||||
default_sort_json, default_visible_columns_json,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def upsert_column_metadata(
|
||||
cursor,
|
||||
*,
|
||||
table_name: str,
|
||||
column_name: str,
|
||||
display_name: str,
|
||||
description: str | None = None,
|
||||
grid_help: str | None = None,
|
||||
visible: int = 1,
|
||||
searchable: int = 1,
|
||||
editable: int = 1,
|
||||
display_type: str | None = None,
|
||||
options_json: str | None = None,
|
||||
) -> None:
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO _jiangchang_columns (
|
||||
table_name, column_name, display_name, description, grid_help,
|
||||
display_order, visible, searchable, editable, display_type, options_json
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(table_name, column_name) DO UPDATE SET
|
||||
display_name = excluded.display_name,
|
||||
description = excluded.description,
|
||||
grid_help = excluded.grid_help,
|
||||
display_order = NULL,
|
||||
visible = excluded.visible,
|
||||
searchable = excluded.searchable,
|
||||
editable = excluded.editable,
|
||||
display_type = excluded.display_type,
|
||||
options_json = excluded.options_json
|
||||
""",
|
||||
(
|
||||
table_name, column_name, display_name, description, grid_help,
|
||||
visible, searchable, editable, display_type, options_json,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def seed_column_metadata_batch(
|
||||
cursor,
|
||||
*,
|
||||
table_name: str,
|
||||
columns: Iterable[dict[str, object]],
|
||||
) -> None:
|
||||
"""批量 upsert 字段元数据。"""
|
||||
for column in columns:
|
||||
upsert_column_metadata(
|
||||
cursor,
|
||||
table_name=table_name,
|
||||
column_name=str(column["column_name"]),
|
||||
display_name=str(column["display_name"]),
|
||||
description=str(column["description"]) if column.get("description") else None,
|
||||
grid_help=str(column["grid_help"]) if column.get("grid_help") else None,
|
||||
visible=int(column.get("visible", 1)),
|
||||
searchable=int(column.get("searchable", 1)),
|
||||
editable=int(column.get("editable", 1)),
|
||||
display_type=str(column["display_type"]) if column.get("display_type") else None,
|
||||
options_json=str(column["options_json"]) if column.get("options_json") else None,
|
||||
)
|
||||
|
||||
|
||||
def _quote_identifier(name: str) -> str:
|
||||
return '"' + name.replace('"', '""') + '"'
|
||||
|
||||
|
||||
def list_physical_column_names(cursor, table_name: str) -> list[str]:
|
||||
cursor.execute(f"PRAGMA table_info({_quote_identifier(table_name)})")
|
||||
rows = cursor.fetchall()
|
||||
rows.sort(key=lambda row: int(row[0]))
|
||||
return [str(row[1]) for row in rows]
|
||||
Reference in New Issue
Block a user