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.
Files changed (41) hide show
  1. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/PKG-INFO +1 -1
  2. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/data_ops_cli.egg-info/PKG-INFO +1 -1
  3. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/data_ops_cli.egg-info/SOURCES.txt +1 -1
  4. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/__init__.py +1 -1
  5. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/data_table.py +4 -4
  6. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/dataset.py +3 -95
  7. data_ops_cli-0.4.5/src/dataops_cli/cli/dict_.py +119 -0
  8. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/factory.py +3 -2
  9. data_ops_cli-0.4.5/src/dataops_cli/cli/ontology.py +291 -0
  10. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/ref.py +20 -5
  11. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/report.py +2 -4
  12. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/skill.py +18 -4
  13. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/workflow.py +6 -10
  14. data_ops_cli-0.4.4/src/dataops_cli/cli/dict_.py +0 -219
  15. data_ops_cli-0.4.4/src/dataops_cli/cli/kb.py +0 -234
  16. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/README.md +0 -0
  17. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/pyproject.toml +0 -0
  18. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/setup.cfg +0 -0
  19. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/data_ops_cli.egg-info/dependency_links.txt +0 -0
  20. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/data_ops_cli.egg-info/entry_points.txt +0 -0
  21. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/data_ops_cli.egg-info/requires.txt +0 -0
  22. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/data_ops_cli.egg-info/top_level.txt +0 -0
  23. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/_compat.py +0 -0
  24. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/__init__.py +0 -0
  25. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/alarm.py +0 -0
  26. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/api.py +0 -0
  27. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/auth.py +0 -0
  28. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/datasource.py +0 -0
  29. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/hazard.py +0 -0
  30. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/metrics.py +0 -0
  31. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/table.py +0 -0
  32. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/tag.py +0 -0
  33. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/utils.py +0 -0
  34. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/cli/widget.py +0 -0
  35. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/exceptions.py +0 -0
  36. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/runner.py +0 -0
  37. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/schems.py +0 -0
  38. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/sdk.py +0 -0
  39. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/session.py +0 -0
  40. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/src/dataops_cli/utils.py +0 -0
  41. {data_ops_cli-0.4.4 → data_ops_cli-0.4.5}/tests/test_table_relation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: data-ops-cli
3
- Version: 0.4.4
3
+ Version: 0.4.5
4
4
  Summary: Command-line wrapper around dataops_sdk for the DataOps platform.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: data-ops-cli
3
- Version: 0.4.4
3
+ Version: 0.4.5
4
4
  Summary: Command-line wrapper around dataops_sdk for the DataOps platform.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -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
@@ -14,4 +14,4 @@ groups command modules under :mod:`dataops_cli.cli` and re-exports the package
14
14
  removed in ``dataops_cli`` 1.0.
15
15
  """
16
16
 
17
- __version__ = "0.4.4"
17
+ __version__ = "0.4.5"
@@ -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
- service_get_data_table_data_by_code,
17
- service_get_data_table_data_by_name,
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
- service_get_data_table_data_by_name(arg1, name, pp, qp)
288
+ service_get_data_table_data_ext_by_name(arg1, name, pp, qp)
289
289
  if name
290
- else service_get_data_table_data_by_code(arg1, pp, qp)
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 MetaType, PageParam, SqlFilter, SqlQueryParam
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.session import get_session
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)} 条)")