data-ops-cli 0.4.4__tar.gz → 0.4.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/PKG-INFO +1 -1
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/data_ops_cli.egg-info/PKG-INFO +1 -1
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/data_ops_cli.egg-info/SOURCES.txt +1 -1
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/__init__.py +1 -1
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/data_table.py +4 -4
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/dataset.py +3 -95
- data_ops_cli-0.4.5/src/dataops_cli/cli/dict_.py +119 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/factory.py +3 -2
- data_ops_cli-0.4.5/src/dataops_cli/cli/ontology.py +291 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/ref.py +20 -5
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/report.py +2 -4
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/skill.py +18 -4
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/workflow.py +6 -10
- data_ops_cli-0.4.4/src/dataops_cli/cli/dict_.py +0 -219
- data_ops_cli-0.4.4/src/dataops_cli/cli/kb.py +0 -234
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/README.md +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/pyproject.toml +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/setup.cfg +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/data_ops_cli.egg-info/dependency_links.txt +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/data_ops_cli.egg-info/entry_points.txt +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/data_ops_cli.egg-info/requires.txt +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/data_ops_cli.egg-info/top_level.txt +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/_compat.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/__init__.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/alarm.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/api.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/auth.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/datasource.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/hazard.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/metrics.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/table.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/tag.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/utils.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/widget.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/exceptions.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/runner.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/schems.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/sdk.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/session.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/utils.py +0 -0
- {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/tests/test_table_relation.py +0 -0
|
@@ -24,8 +24,8 @@ src/dataops_cli/cli/datasource.py
|
|
|
24
24
|
src/dataops_cli/cli/dict_.py
|
|
25
25
|
src/dataops_cli/cli/factory.py
|
|
26
26
|
src/dataops_cli/cli/hazard.py
|
|
27
|
-
src/dataops_cli/cli/kb.py
|
|
28
27
|
src/dataops_cli/cli/metrics.py
|
|
28
|
+
src/dataops_cli/cli/ontology.py
|
|
29
29
|
src/dataops_cli/cli/ref.py
|
|
30
30
|
src/dataops_cli/cli/report.py
|
|
31
31
|
src/dataops_cli/cli/skill.py
|
|
@@ -13,8 +13,8 @@ from dataops_sdk.schemas import MetaType, PageParam, SqlFilter, SqlQueryParam
|
|
|
13
13
|
from dataops_sdk.services import (
|
|
14
14
|
service_get_data_table_by_code,
|
|
15
15
|
service_get_data_table_by_name,
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
service_get_data_table_data_ext_by_code,
|
|
17
|
+
service_get_data_table_data_ext_by_name,
|
|
18
18
|
service_get_data_table_total,
|
|
19
19
|
)
|
|
20
20
|
from dataops_sdk.services.meta import release_list_list
|
|
@@ -285,9 +285,9 @@ def get_datatable_data(
|
|
|
285
285
|
|
|
286
286
|
try:
|
|
287
287
|
datas = (
|
|
288
|
-
|
|
288
|
+
service_get_data_table_data_ext_by_name(arg1, name, pp, qp)
|
|
289
289
|
if name
|
|
290
|
-
else
|
|
290
|
+
else service_get_data_table_data_ext_by_code(arg1, pp, qp)
|
|
291
291
|
)
|
|
292
292
|
except DataOpsError as e:
|
|
293
293
|
typer.echo(f"错误: {e.message}", err=True)
|
|
@@ -1,40 +1,29 @@
|
|
|
1
1
|
"""数据集管理命令组.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
提供数据集的列表查询、详情、数据量统计与实际数据获取能力。
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from pathlib import Path
|
|
7
6
|
from typing import Annotated
|
|
8
7
|
|
|
9
8
|
import typer
|
|
10
|
-
from pydantic_core import to_json
|
|
11
|
-
|
|
12
9
|
from dataops_sdk.exceptions import DataOpsError
|
|
13
|
-
from dataops_sdk.schemas import
|
|
10
|
+
from dataops_sdk.schemas import PageParam, SqlFilter, SqlQueryParam
|
|
14
11
|
from dataops_sdk.services import (
|
|
15
|
-
release_list_get_by_code,
|
|
16
12
|
release_list_update,
|
|
17
13
|
service_count_dataset_by_code,
|
|
18
14
|
service_get_dataset_data_by_code,
|
|
19
15
|
service_get_dataset_detail_by_code,
|
|
20
16
|
service_list_datasets,
|
|
21
17
|
)
|
|
22
|
-
from dataops_sdk.
|
|
23
|
-
from dataops_sdk.utils import encrypt_body, filter_nodes_by_pattern
|
|
18
|
+
from dataops_sdk.utils import filter_nodes_by_pattern
|
|
24
19
|
|
|
25
20
|
from dataops_cli.cli.utils import print_list, print_table_column_detail
|
|
26
21
|
|
|
27
|
-
# 数据集 rawData 中承载 SQL 定义的字段名(subType=sql 的数据集均有此字段)。
|
|
28
|
-
SQL_FIELD = "text"
|
|
29
|
-
|
|
30
|
-
|
|
31
22
|
dataset_typer = typer.Typer(name="dataset", help="Manage dataset.", no_args_is_help=True)
|
|
32
23
|
update_typer = typer.Typer(name="update", help="变更", no_args_is_help=True)
|
|
33
24
|
dataset_typer.add_typer(update_typer)
|
|
34
25
|
column_typer = typer.Typer(name="column", help="列变更", no_args_is_help=True)
|
|
35
26
|
update_typer.add_typer(column_typer)
|
|
36
|
-
sql_typer = typer.Typer(name="sql", help="SQL 编辑", no_args_is_help=True)
|
|
37
|
-
update_typer.add_typer(sql_typer)
|
|
38
27
|
|
|
39
28
|
|
|
40
29
|
@dataset_typer.command("list")
|
|
@@ -252,84 +241,3 @@ def update_dataset_column(
|
|
|
252
241
|
typer.echo(f"错误: {e.message}")
|
|
253
242
|
raise typer.Exit(code=1) from None
|
|
254
243
|
typer.echo(f"修改数据集 {code} 字段 {column_item.name} 完成")
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def _load_sql(code: str):
|
|
258
|
-
"""校验数据集存在 SQL 字段并返回 Meta;失败统一报错退出."""
|
|
259
|
-
try:
|
|
260
|
-
meta = release_list_get_by_code(MetaType.DATASET, code)
|
|
261
|
-
except DataOpsError as e:
|
|
262
|
-
typer.echo(f"错误: {e.message}", err=True)
|
|
263
|
-
raise typer.Exit(code=1) from None
|
|
264
|
-
data = meta.data
|
|
265
|
-
if not isinstance(data, dict) or not isinstance(data.get(SQL_FIELD), str):
|
|
266
|
-
typer.echo(f"数据集 {code} 未找到 SQL 字段({SQL_FIELD})", err=True)
|
|
267
|
-
raise typer.Exit(code=1)
|
|
268
|
-
return meta
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def _save_sql(meta, sql: str) -> None:
|
|
272
|
-
"""整段替换 SQL 并保存:先解析字段回填 columns(失败仅警告),保存失败统一报错."""
|
|
273
|
-
meta.data[SQL_FIELD] = sql
|
|
274
|
-
try:
|
|
275
|
-
meta_payload = meta.model_dump(mode="json", exclude={"data"})
|
|
276
|
-
meta_payload["rawData"] = to_json(meta.data).decode()
|
|
277
|
-
with get_session() as session:
|
|
278
|
-
resp = session.post(
|
|
279
|
-
"/data/dataset/analysis",
|
|
280
|
-
content=to_json(encrypt_body({"meta": meta_payload, "params": []})),
|
|
281
|
-
)
|
|
282
|
-
resp.raise_for_status()
|
|
283
|
-
meta.data["columns"] = resp.json()["data"]["columns"]
|
|
284
|
-
typer.echo(f"字段解析完成({len(meta.data['columns'])} 列)")
|
|
285
|
-
except Exception as e:
|
|
286
|
-
typer.echo(f"⚠️ 字段解析失败,仅保存 SQL: {e}", err=True)
|
|
287
|
-
try:
|
|
288
|
-
release_list_update(meta)
|
|
289
|
-
except DataOpsError as e:
|
|
290
|
-
typer.echo(f"错误: {e.message}", err=True)
|
|
291
|
-
raise typer.Exit(code=1) from None
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
@sql_typer.command("edit", no_args_is_help=True)
|
|
295
|
-
def edit_dataset_sql(
|
|
296
|
-
code: Annotated[str, typer.Argument(help="数据集编码")],
|
|
297
|
-
old_texts: Annotated[
|
|
298
|
-
list[str],
|
|
299
|
-
typer.Option("--old-text", help="待替换文本(可多次传入,与 --new-text 一一对应)"),
|
|
300
|
-
],
|
|
301
|
-
new_texts: Annotated[
|
|
302
|
-
list[str],
|
|
303
|
-
typer.Option("--new-text", help="替换后的文本(与 --old-text 一一对应)"),
|
|
304
|
-
],
|
|
305
|
-
) -> None:
|
|
306
|
-
"""按 --old-text/--new-text 成对精准替换 SQL 文本."""
|
|
307
|
-
if len(old_texts) != len(new_texts):
|
|
308
|
-
typer.echo("参数 --old-text 与 --new-text 数量不一致", err=True)
|
|
309
|
-
raise typer.Exit(code=1)
|
|
310
|
-
meta = _load_sql(code)
|
|
311
|
-
content = meta.data[SQL_FIELD]
|
|
312
|
-
for idx, (old, new) in enumerate(zip(old_texts, new_texts), start=1):
|
|
313
|
-
if old not in content:
|
|
314
|
-
typer.echo(f"[{idx}] 未找到匹配文本 {old}", err=True)
|
|
315
|
-
raise typer.Exit(code=1)
|
|
316
|
-
content = content.replace(old, new, count=1)
|
|
317
|
-
_save_sql(meta, content)
|
|
318
|
-
typer.echo(f"数据集 {code} SQL 更新成功")
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
@sql_typer.command("write", no_args_is_help=True)
|
|
322
|
-
def write_dataset_sql(
|
|
323
|
-
code: Annotated[str, typer.Argument(help="数据集编码")],
|
|
324
|
-
file: Annotated[Path | None, typer.Argument(help="输入文件路径")] = None,
|
|
325
|
-
content: Annotated[str | None, typer.Option("--content", help="SQL 内容")] = None,
|
|
326
|
-
) -> None:
|
|
327
|
-
"""整段写入 SQL,file 与 --content 至少指定一个."""
|
|
328
|
-
if file:
|
|
329
|
-
content = file.read_text(encoding="utf-8")
|
|
330
|
-
elif not content:
|
|
331
|
-
typer.echo("参数 file 或 --content 必须指定一个", err=True)
|
|
332
|
-
raise typer.Exit(code=1)
|
|
333
|
-
meta = _load_sql(code)
|
|
334
|
-
_save_sql(meta, content)
|
|
335
|
-
typer.echo(f"数据集 {code} SQL 写入成功")
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""字典(dict)管理命令组.
|
|
2
|
+
|
|
3
|
+
提供数据源字典的列表查询与详情查看能力。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from dataops_sdk.exceptions import DataOpsError
|
|
10
|
+
from dataops_sdk.services.dict_ import (
|
|
11
|
+
service_get_dict_detail_by_code,
|
|
12
|
+
service_get_dict_detail_by_name,
|
|
13
|
+
service_list_dicts,
|
|
14
|
+
servive_tree_dict,
|
|
15
|
+
)
|
|
16
|
+
from dataops_sdk.utils import filter_nodes_by_pattern
|
|
17
|
+
|
|
18
|
+
from dataops_cli.cli.utils import print_group_tree, print_list
|
|
19
|
+
|
|
20
|
+
dict_typer = typer.Typer(name="dict", help="Manage dict.", no_args_is_help=True)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dict_typer.command("list")
|
|
24
|
+
def list_dict(
|
|
25
|
+
datasource: Annotated[str, typer.Argument(help="数据源")],
|
|
26
|
+
query: Annotated[str | None, typer.Option("-q", "--query", help="字典名称关键字")] = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
"""列出数据源中的字典列表。
|
|
29
|
+
|
|
30
|
+
支持通过关键字过滤字典名称,并支持正则表达式匹配。
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
datasource: 数据源名称。
|
|
34
|
+
query: 可选字典名称关键字(支持正则)。
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
# 列出指定数据源的所有字典
|
|
38
|
+
>>> data-ops-cli dict list <datasource>
|
|
39
|
+
|
|
40
|
+
# 列出数据源中名称包含 "user" 的字典
|
|
41
|
+
>>> data-ops-cli dict list <datasource> -q user
|
|
42
|
+
|
|
43
|
+
# 使用正则过滤字典名
|
|
44
|
+
>>> data-ops-cli dict list <datasource> -q "^user_.*"
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
group_nodes = service_list_dicts(datasource)
|
|
48
|
+
except DataOpsError as e:
|
|
49
|
+
typer.echo(f"错误: {e.message}")
|
|
50
|
+
raise typer.Exit(code=1) from None
|
|
51
|
+
|
|
52
|
+
if not group_nodes:
|
|
53
|
+
print("未查询到匹配字典")
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
if query:
|
|
57
|
+
group_nodes = filter_nodes_by_pattern(group_nodes, query)
|
|
58
|
+
|
|
59
|
+
print_list(
|
|
60
|
+
["code", "name", "alias"],
|
|
61
|
+
[[column.code, column.name, column.title] for column in group_nodes],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dict_typer.command("detail", no_args_is_help=True)
|
|
66
|
+
def get_dict_detail(
|
|
67
|
+
arg1: Annotated[str, typer.Argument(help="编码或数据源")],
|
|
68
|
+
name: Annotated[str | None, typer.Argument(help="字典名称")] = None,
|
|
69
|
+
# full: Annotated[bool, typer.Option("--full", help="是否显示完整信息")] = False,
|
|
70
|
+
) -> None:
|
|
71
|
+
"""获取字典详情信息。
|
|
72
|
+
|
|
73
|
+
支持两种查询方式:
|
|
74
|
+
|
|
75
|
+
1. 通过字典编码(CODE)直接查询
|
|
76
|
+
2. 通过数据源名称和字典名称组合查询
|
|
77
|
+
|
|
78
|
+
默认只显示字典选项的基本信息(label/value)
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
arg1: 字典编码;当同时提供 ``name`` 时为数据源名称。
|
|
82
|
+
name: 可选字典名称。
|
|
83
|
+
full: 是否显示完整信息。
|
|
84
|
+
|
|
85
|
+
Examples:
|
|
86
|
+
# 通过字典编码查询字典结构
|
|
87
|
+
>>> data-ops-cli dict detail dict_code_123
|
|
88
|
+
|
|
89
|
+
# 通过数据源和字典名查询
|
|
90
|
+
>>> data-ops-cli dict detail <datasource> my_dict
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
meta = (
|
|
94
|
+
service_get_dict_detail_by_name(arg1, name)
|
|
95
|
+
if name
|
|
96
|
+
else service_get_dict_detail_by_code(arg1)
|
|
97
|
+
)
|
|
98
|
+
except DataOpsError as e:
|
|
99
|
+
typer.echo(f"错误: {e.message}")
|
|
100
|
+
raise typer.Exit(code=1) from None
|
|
101
|
+
print(f"{meta.name}({meta.title})")
|
|
102
|
+
print_list(["value", "label"], [[option.value, option.label] for option in meta.data.options])
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dict_typer.command("tree")
|
|
106
|
+
def tree_dict(
|
|
107
|
+
query: Annotated[str | None, typer.Option("-q", "--query", help="字典名称关键字")] = None,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""展示字典树结构。
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
query: 字典名称关键字(支持正则)。
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
115
|
+
tree = servive_tree_dict()
|
|
116
|
+
except DataOpsError as e:
|
|
117
|
+
typer.echo(f"错误: {e.message}")
|
|
118
|
+
raise typer.Exit(code=1) from None
|
|
119
|
+
print_group_tree(tree, query)
|
|
@@ -53,8 +53,8 @@ def create_app():
|
|
|
53
53
|
from dataops_cli.cli.datasource import datasource_typer
|
|
54
54
|
from dataops_cli.cli.dict_ import dict_typer
|
|
55
55
|
from dataops_cli.cli.hazard import hazard_typer
|
|
56
|
-
from dataops_cli.cli.kb import kb_typer
|
|
57
56
|
from dataops_cli.cli.metrics import metrics_typer
|
|
57
|
+
from dataops_cli.cli.ontology import ontology_typer
|
|
58
58
|
from dataops_cli.cli.ref import ref_typer
|
|
59
59
|
from dataops_cli.cli.report import report_typer
|
|
60
60
|
from dataops_cli.cli.skill import skill_typer
|
|
@@ -79,11 +79,12 @@ def create_app():
|
|
|
79
79
|
app.add_typer(report_typer)
|
|
80
80
|
app.add_typer(skill_typer)
|
|
81
81
|
app.add_typer(metrics_typer)
|
|
82
|
+
app.add_typer(ontology_typer)
|
|
82
83
|
app.add_typer(data_table_typer)
|
|
83
84
|
app.add_typer(widget_typer)
|
|
84
85
|
app.add_typer(dict_typer)
|
|
85
86
|
app.add_typer(hazard_typer)
|
|
86
|
-
app.add_typer(kb_typer)
|
|
87
|
+
# app.add_typer(kb_typer) # TODO: kb CLI 尚未实现
|
|
87
88
|
app.add_typer(tag_typer)
|
|
88
89
|
|
|
89
90
|
app.registered_callback = TyperInfo(callback=main)
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""本体(Ontology)管理命令组.
|
|
2
|
+
|
|
3
|
+
提供本体的目录树、列表、配置详情与分段更新。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Annotated, Any
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from dataops_sdk.exceptions import DataOpsError
|
|
14
|
+
from dataops_sdk.services.ontology import (
|
|
15
|
+
service_get_ontology_by_code,
|
|
16
|
+
service_get_ontology_by_name,
|
|
17
|
+
service_list_ontologies,
|
|
18
|
+
service_tree_ontology,
|
|
19
|
+
service_update_ontology_basic,
|
|
20
|
+
service_update_ontology_constraints,
|
|
21
|
+
service_update_ontology_notes,
|
|
22
|
+
service_update_ontology_relations,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from dataops_cli.cli.utils import print_group_tree, print_list
|
|
26
|
+
|
|
27
|
+
ontology_typer = typer.Typer(name="ontology", help="本体管理", no_args_is_help=True)
|
|
28
|
+
update_typer = typer.Typer(name="update", help="更新本体配置", no_args_is_help=True)
|
|
29
|
+
ontology_typer.add_typer(update_typer)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _read_json_payload(
|
|
33
|
+
file: Path | None,
|
|
34
|
+
content: str | None,
|
|
35
|
+
) -> Any:
|
|
36
|
+
"""从 ``--file`` 或 ``--content`` 读取 JSON(二选一)."""
|
|
37
|
+
if not file and content is None:
|
|
38
|
+
typer.echo("参数 --file 或 --content 必须指定一个", err=True)
|
|
39
|
+
raise typer.Exit(code=1)
|
|
40
|
+
if file and content is not None:
|
|
41
|
+
typer.echo("参数 --file 与 --content 不能同时指定", err=True)
|
|
42
|
+
raise typer.Exit(code=1)
|
|
43
|
+
if file:
|
|
44
|
+
try:
|
|
45
|
+
text = file.read_text(encoding="utf-8")
|
|
46
|
+
except OSError as e:
|
|
47
|
+
typer.echo(f"错误: 无法读取文件 {file}: {e}", err=True)
|
|
48
|
+
raise typer.Exit(code=1) from None
|
|
49
|
+
else:
|
|
50
|
+
text = content or ""
|
|
51
|
+
try:
|
|
52
|
+
return json.loads(text)
|
|
53
|
+
except json.JSONDecodeError as e:
|
|
54
|
+
typer.echo(f"错误: JSON 解析失败: {e}", err=True)
|
|
55
|
+
raise typer.Exit(code=1) from None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _read_text_payload(
|
|
59
|
+
file: Path | None,
|
|
60
|
+
content: str | None,
|
|
61
|
+
) -> str:
|
|
62
|
+
"""从 ``--file`` 或 ``--content`` 读取文本(二选一)."""
|
|
63
|
+
if not file and content is None:
|
|
64
|
+
typer.echo("参数 --file 或 --content 必须指定一个", err=True)
|
|
65
|
+
raise typer.Exit(code=1)
|
|
66
|
+
if file and content is not None:
|
|
67
|
+
typer.echo("参数 --file 与 --content 不能同时指定", err=True)
|
|
68
|
+
raise typer.Exit(code=1)
|
|
69
|
+
if file:
|
|
70
|
+
try:
|
|
71
|
+
return file.read_text(encoding="utf-8")
|
|
72
|
+
except OSError as e:
|
|
73
|
+
typer.echo(f"错误: 无法读取文件 {file}: {e}", err=True)
|
|
74
|
+
raise typer.Exit(code=1) from None
|
|
75
|
+
return content or ""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@ontology_typer.command("list")
|
|
79
|
+
def list_ontologies(
|
|
80
|
+
query: Annotated[str | None, typer.Option("-q", "--query", help="正则关键词")] = None,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""列出所有本体。
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
>>> data-ops-cli ontology list
|
|
86
|
+
>>> data-ops-cli ontology list -q "^dev_.*"
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
items = service_list_ontologies(query=query)
|
|
90
|
+
except DataOpsError as e:
|
|
91
|
+
typer.echo(f"错误: {e.message}")
|
|
92
|
+
raise typer.Exit(code=1) from None
|
|
93
|
+
|
|
94
|
+
if not items:
|
|
95
|
+
print("未查询到匹配的本体")
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
print_list(
|
|
99
|
+
["code", "name", "title", "remark"],
|
|
100
|
+
[[item.code, item.name, item.title, item.remark or ""] for item in items],
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@ontology_typer.command("detail", no_args_is_help=True)
|
|
105
|
+
def detail_ontology(
|
|
106
|
+
name: Annotated[str, typer.Argument(help="本体编码或业务名")],
|
|
107
|
+
raw: Annotated[bool, typer.Option("--raw", help="仅输出 rawData JSON")] = False,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""查看本体配置详情。
|
|
110
|
+
|
|
111
|
+
默认输出摘要;加 ``--raw`` 时输出完整 ``meta.data`` JSON。
|
|
112
|
+
|
|
113
|
+
Examples:
|
|
114
|
+
>>> data-ops-cli ontology detail dev_ne
|
|
115
|
+
>>> data-ops-cli ontology detail dev_ne --raw
|
|
116
|
+
"""
|
|
117
|
+
try:
|
|
118
|
+
meta = service_get_ontology_by_name(name)
|
|
119
|
+
except DataOpsError:
|
|
120
|
+
try:
|
|
121
|
+
meta = service_get_ontology_by_code(name)
|
|
122
|
+
except DataOpsError as e:
|
|
123
|
+
typer.echo(f"错误: {e.message}")
|
|
124
|
+
raise typer.Exit(code=1) from None
|
|
125
|
+
|
|
126
|
+
if meta.data is None:
|
|
127
|
+
typer.echo(f"错误: 本体({name})不存在")
|
|
128
|
+
raise typer.Exit(code=1) from None
|
|
129
|
+
|
|
130
|
+
if raw:
|
|
131
|
+
print(meta.data.model_dump_json(mode="json"))
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
data = meta.data
|
|
135
|
+
print(f"本体: {meta.name} ({meta.title})")
|
|
136
|
+
print(f"备注: {meta.remark or '-'}")
|
|
137
|
+
print(f"多维表格: {data.data_table or '-'}")
|
|
138
|
+
print(f"场景数: {len(data.scenarios)}")
|
|
139
|
+
print(f"约束数: {len(data.constraints)}")
|
|
140
|
+
print(f"关系数: {len(data.relations)}")
|
|
141
|
+
if data.notes:
|
|
142
|
+
print(f"\n说明:\n{data.notes}")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@ontology_typer.command("tree")
|
|
146
|
+
def tree_ontology(
|
|
147
|
+
query: Annotated[str | None, typer.Option("-q", "--query", help="节点关键字")] = None,
|
|
148
|
+
types: Annotated[
|
|
149
|
+
list[str] | None,
|
|
150
|
+
typer.Option("--type", "-t", help="节点类型过滤 (group/ontology)"),
|
|
151
|
+
] = None,
|
|
152
|
+
) -> None:
|
|
153
|
+
"""展示本体目录树。
|
|
154
|
+
|
|
155
|
+
Examples:
|
|
156
|
+
>>> data-ops-cli ontology tree
|
|
157
|
+
>>> data-ops-cli ontology tree -q dev
|
|
158
|
+
"""
|
|
159
|
+
try:
|
|
160
|
+
tree = service_tree_ontology()
|
|
161
|
+
except DataOpsError as e:
|
|
162
|
+
typer.echo(f"错误: {e.message}")
|
|
163
|
+
raise typer.Exit(code=1) from None
|
|
164
|
+
print_group_tree(tree, query, types=types)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@update_typer.command("basic", no_args_is_help=True)
|
|
168
|
+
def update_basic(
|
|
169
|
+
name: Annotated[str, typer.Argument(help="本体编码或业务名")],
|
|
170
|
+
new_name: Annotated[
|
|
171
|
+
str | None,
|
|
172
|
+
typer.Option("--name", help="新业务名(英文字母开头,仅字母数字下划线)"),
|
|
173
|
+
] = None,
|
|
174
|
+
title: Annotated[str | None, typer.Option("--title", help="中文标题")] = None,
|
|
175
|
+
remark: Annotated[str | None, typer.Option("--remark", help="备注/描述")] = None,
|
|
176
|
+
data_table: Annotated[
|
|
177
|
+
str | None,
|
|
178
|
+
typer.Option("--data-table", help="绑定的多维表格编码"),
|
|
179
|
+
] = None,
|
|
180
|
+
) -> None:
|
|
181
|
+
"""更新本体基础配置(title / remark / name / data_table)。
|
|
182
|
+
|
|
183
|
+
仅覆盖显式传入的字段;至少一个选项必填。
|
|
184
|
+
|
|
185
|
+
Examples:
|
|
186
|
+
>>> data-ops-cli ontology update basic dev_ne --title "网元" --data-table dt_ne
|
|
187
|
+
>>> data-ops-cli ontology update basic dev_ne --name dev_ne_v2 --remark "传输网元"
|
|
188
|
+
"""
|
|
189
|
+
if new_name is None and title is None and remark is None and data_table is None:
|
|
190
|
+
typer.echo("请至少指定一个要更新的字段(--name/--title/--remark/--data-table)", err=True)
|
|
191
|
+
raise typer.Exit(code=1)
|
|
192
|
+
try:
|
|
193
|
+
result = service_update_ontology_basic(
|
|
194
|
+
name,
|
|
195
|
+
title=title,
|
|
196
|
+
remark=remark,
|
|
197
|
+
name=new_name,
|
|
198
|
+
data_table=data_table,
|
|
199
|
+
)
|
|
200
|
+
except DataOpsError as e:
|
|
201
|
+
typer.echo(f"错误: {e.message}", err=True)
|
|
202
|
+
raise typer.Exit(code=1) from None
|
|
203
|
+
typer.echo(f"本体 {result.name} 基础配置更新成功")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@update_typer.command("constraints", no_args_is_help=True)
|
|
207
|
+
def update_constraints(
|
|
208
|
+
name: Annotated[str, typer.Argument(help="本体编码或业务名")],
|
|
209
|
+
file: Annotated[
|
|
210
|
+
Path | None,
|
|
211
|
+
typer.Option("--file", "-f", help="约束 JSON 文件路径(数组)"),
|
|
212
|
+
] = None,
|
|
213
|
+
content: Annotated[
|
|
214
|
+
str | None,
|
|
215
|
+
typer.Option("--content", "-c", help="约束 JSON 内容(数组)"),
|
|
216
|
+
] = None,
|
|
217
|
+
) -> None:
|
|
218
|
+
"""全量替换本体约束列表。
|
|
219
|
+
|
|
220
|
+
Examples:
|
|
221
|
+
>>> data-ops-cli ontology update constraints dev_ne --file constraints.json
|
|
222
|
+
>>> data-ops-cli ontology update constraints dev_ne --content '[{"type":"唯一","rule":"code唯一"}]'
|
|
223
|
+
"""
|
|
224
|
+
payload = _read_json_payload(file, content)
|
|
225
|
+
if not isinstance(payload, list):
|
|
226
|
+
typer.echo("错误: 约束内容必须是 JSON 数组", err=True)
|
|
227
|
+
raise typer.Exit(code=1)
|
|
228
|
+
try:
|
|
229
|
+
result = service_update_ontology_constraints(name, payload)
|
|
230
|
+
except DataOpsError as e:
|
|
231
|
+
typer.echo(f"错误: {e.message}", err=True)
|
|
232
|
+
raise typer.Exit(code=1) from None
|
|
233
|
+
assert result.data is not None
|
|
234
|
+
typer.echo(f"本体 {result.name} 约束已更新(共 {len(result.data.constraints)} 条)")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@update_typer.command("notes", no_args_is_help=True)
|
|
238
|
+
def update_notes(
|
|
239
|
+
name: Annotated[str, typer.Argument(help="本体编码或业务名")],
|
|
240
|
+
file: Annotated[
|
|
241
|
+
Path | None,
|
|
242
|
+
typer.Option("--file", "-f", help="说明文本文件路径"),
|
|
243
|
+
] = None,
|
|
244
|
+
content: Annotated[
|
|
245
|
+
str | None,
|
|
246
|
+
typer.Option("--content", "-c", help="说明文本内容"),
|
|
247
|
+
] = None,
|
|
248
|
+
) -> None:
|
|
249
|
+
"""全量替换本体其他说明。
|
|
250
|
+
|
|
251
|
+
Examples:
|
|
252
|
+
>>> data-ops-cli ontology update notes dev_ne --content "说明文本"
|
|
253
|
+
>>> data-ops-cli ontology update notes dev_ne --file notes.md
|
|
254
|
+
"""
|
|
255
|
+
text = _read_text_payload(file, content)
|
|
256
|
+
try:
|
|
257
|
+
result = service_update_ontology_notes(name, text)
|
|
258
|
+
except DataOpsError as e:
|
|
259
|
+
typer.echo(f"错误: {e.message}", err=True)
|
|
260
|
+
raise typer.Exit(code=1) from None
|
|
261
|
+
typer.echo(f"本体 {result.name} 其他说明已更新")
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@update_typer.command("relations", no_args_is_help=True)
|
|
265
|
+
def update_relations(
|
|
266
|
+
name: Annotated[str, typer.Argument(help="本体编码或业务名")],
|
|
267
|
+
file: Annotated[
|
|
268
|
+
Path | None,
|
|
269
|
+
typer.Option("--file", "-f", help="关系 JSON 文件路径(数组)"),
|
|
270
|
+
] = None,
|
|
271
|
+
content: Annotated[
|
|
272
|
+
str | None,
|
|
273
|
+
typer.Option("--content", "-c", help="关系 JSON 内容(数组)"),
|
|
274
|
+
] = None,
|
|
275
|
+
) -> None:
|
|
276
|
+
"""全量替换本体关联关系列表。
|
|
277
|
+
|
|
278
|
+
Examples:
|
|
279
|
+
>>> data-ops-cli ontology update relations dev_ne --file relations.json
|
|
280
|
+
"""
|
|
281
|
+
payload = _read_json_payload(file, content)
|
|
282
|
+
if not isinstance(payload, list):
|
|
283
|
+
typer.echo("错误: 关系内容必须是 JSON 数组", err=True)
|
|
284
|
+
raise typer.Exit(code=1)
|
|
285
|
+
try:
|
|
286
|
+
result = service_update_ontology_relations(name, payload)
|
|
287
|
+
except DataOpsError as e:
|
|
288
|
+
typer.echo(f"错误: {e.message}", err=True)
|
|
289
|
+
raise typer.Exit(code=1) from None
|
|
290
|
+
assert result.data is not None
|
|
291
|
+
typer.echo(f"本体 {result.name} 关联关系已更新(共 {len(result.data.relations)} 条)")
|