data-ops-cli 0.4.2__tar.gz → 0.4.4__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.2 → data_ops_cli-0.4.4}/PKG-INFO +1 -1
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/data_ops_cli.egg-info/PKG-INFO +1 -1
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/data_ops_cli.egg-info/SOURCES.txt +2 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/__init__.py +1 -1
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/factory.py +4 -0
- data_ops_cli-0.4.4/src/dataops_cli/cli/kb.py +234 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/skill.py +68 -0
- data_ops_cli-0.4.4/src/dataops_cli/cli/tag.py +297 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/workflow.py +10 -6
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/README.md +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/pyproject.toml +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/setup.cfg +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/data_ops_cli.egg-info/dependency_links.txt +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/data_ops_cli.egg-info/entry_points.txt +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/data_ops_cli.egg-info/requires.txt +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/data_ops_cli.egg-info/top_level.txt +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/_compat.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/__init__.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/alarm.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/api.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/auth.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/data_table.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/dataset.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/datasource.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/dict_.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/hazard.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/metrics.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/ref.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/report.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/table.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/utils.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/cli/widget.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/exceptions.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/runner.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/schems.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/sdk.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/session.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/src/dataops_cli/utils.py +0 -0
- {data_ops_cli-0.4.2 → data_ops_cli-0.4.4}/tests/test_table_relation.py +0 -0
|
@@ -24,11 +24,13 @@ 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
|
|
27
28
|
src/dataops_cli/cli/metrics.py
|
|
28
29
|
src/dataops_cli/cli/ref.py
|
|
29
30
|
src/dataops_cli/cli/report.py
|
|
30
31
|
src/dataops_cli/cli/skill.py
|
|
31
32
|
src/dataops_cli/cli/table.py
|
|
33
|
+
src/dataops_cli/cli/tag.py
|
|
32
34
|
src/dataops_cli/cli/utils.py
|
|
33
35
|
src/dataops_cli/cli/widget.py
|
|
34
36
|
src/dataops_cli/cli/workflow.py
|
|
@@ -53,11 +53,13 @@ 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
|
|
56
57
|
from dataops_cli.cli.metrics import metrics_typer
|
|
57
58
|
from dataops_cli.cli.ref import ref_typer
|
|
58
59
|
from dataops_cli.cli.report import report_typer
|
|
59
60
|
from dataops_cli.cli.skill import skill_typer
|
|
60
61
|
from dataops_cli.cli.table import table_typer
|
|
62
|
+
from dataops_cli.cli.tag import tag_typer
|
|
61
63
|
from dataops_cli.cli.widget import widget_typer
|
|
62
64
|
from dataops_cli.cli.workflow import workflow_typer
|
|
63
65
|
|
|
@@ -81,6 +83,8 @@ def create_app():
|
|
|
81
83
|
app.add_typer(widget_typer)
|
|
82
84
|
app.add_typer(dict_typer)
|
|
83
85
|
app.add_typer(hazard_typer)
|
|
86
|
+
app.add_typer(kb_typer)
|
|
87
|
+
app.add_typer(tag_typer)
|
|
84
88
|
|
|
85
89
|
app.registered_callback = TyperInfo(callback=main)
|
|
86
90
|
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""Knowledge Base(知识库)管理命令组.
|
|
2
|
+
|
|
3
|
+
- 服务层函数 (service_*):纯 Python 接口,返回原始数据
|
|
4
|
+
- CLI 命令:基于服务层的薄包装,负责格式化输出
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from dataops_cli.cli.utils import print_list
|
|
14
|
+
from dataops_sdk.exceptions import DataOpsError
|
|
15
|
+
from dataops_sdk.schemas.wiki import KbType
|
|
16
|
+
from dataops_sdk.services.wiki import (
|
|
17
|
+
service_get_knowledge_base,
|
|
18
|
+
service_list_knowledge_bases,
|
|
19
|
+
service_list_knowledge_documents,
|
|
20
|
+
service_search_knowledge,
|
|
21
|
+
service_search_wiki_pages,
|
|
22
|
+
service_upload_knowledge_file,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
kb_typer = typer.Typer(
|
|
26
|
+
name="kb",
|
|
27
|
+
help="知识库管理",
|
|
28
|
+
no_args_is_help=True,
|
|
29
|
+
)
|
|
30
|
+
doc_typer = typer.Typer(name="doc", help="知识库文档", no_args_is_help=True)
|
|
31
|
+
page_typer = typer.Typer(name="page", help="Wiki 页面", no_args_is_help=True)
|
|
32
|
+
kb_typer.add_typer(doc_typer)
|
|
33
|
+
kb_typer.add_typer(page_typer)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@kb_typer.command("list")
|
|
37
|
+
def kb_list_cmd(
|
|
38
|
+
kb_type: Annotated[
|
|
39
|
+
KbType | None,
|
|
40
|
+
typer.Option("--type", "-t", help="知识库类型:document / faq / wiki"),
|
|
41
|
+
] = None,
|
|
42
|
+
page: Annotated[int, typer.Option("--page", help="页码", min=1)] = 1,
|
|
43
|
+
page_size: Annotated[int, typer.Option("--page-size", help="每页条数", min=1)] = 20,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""列出知识库。
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
>>> data-ops-cli kb list
|
|
49
|
+
>>> data-ops-cli kb list --type wiki
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
result = service_list_knowledge_bases(page=page, page_size=page_size, kb_type=kb_type)
|
|
53
|
+
except DataOpsError as e:
|
|
54
|
+
typer.echo(f"错误: {e.message}")
|
|
55
|
+
raise typer.Exit(code=1) from None
|
|
56
|
+
|
|
57
|
+
if not result.items:
|
|
58
|
+
print("未查询到知识库")
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
print_list(
|
|
62
|
+
("id", "name", "type", "knowledge_count"),
|
|
63
|
+
[
|
|
64
|
+
[it.id, it.name, it.type or "", it.knowledge_count if it.knowledge_count is not None else ""]
|
|
65
|
+
for it in result.items
|
|
66
|
+
],
|
|
67
|
+
)
|
|
68
|
+
print(f"total={result.total} page={result.page} page_size={result.page_size}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@kb_typer.command("detail", no_args_is_help=True)
|
|
72
|
+
def kb_detail_cmd(
|
|
73
|
+
kb_id: Annotated[str, typer.Argument(help="知识库 ID")],
|
|
74
|
+
) -> None:
|
|
75
|
+
"""查看知识库详情。
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
>>> data-ops-cli kb detail <kb_id>
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
kb = service_get_knowledge_base(kb_id)
|
|
82
|
+
except DataOpsError as e:
|
|
83
|
+
typer.echo(f"错误: {e.message}")
|
|
84
|
+
raise typer.Exit(code=1) from None
|
|
85
|
+
|
|
86
|
+
print_list(
|
|
87
|
+
("field", "value"),
|
|
88
|
+
[
|
|
89
|
+
["id", kb.id],
|
|
90
|
+
["name", kb.name],
|
|
91
|
+
["type", kb.type or ""],
|
|
92
|
+
["description", kb.description or ""],
|
|
93
|
+
["knowledge_count", kb.knowledge_count if kb.knowledge_count is not None else ""],
|
|
94
|
+
["chunk_count", kb.chunk_count if kb.chunk_count is not None else ""],
|
|
95
|
+
],
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@doc_typer.command("list", no_args_is_help=True)
|
|
100
|
+
def doc_list_cmd(
|
|
101
|
+
kb_id: Annotated[str, typer.Argument(help="知识库 ID")],
|
|
102
|
+
keyword: Annotated[
|
|
103
|
+
str | None,
|
|
104
|
+
typer.Option("--keyword", "-k", help="关键词"),
|
|
105
|
+
] = None,
|
|
106
|
+
page: Annotated[int, typer.Option("--page", help="页码", min=1)] = 1,
|
|
107
|
+
page_size: Annotated[int, typer.Option("--page-size", help="每页条数", min=1)] = 20,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""列出知识库文档。
|
|
110
|
+
|
|
111
|
+
Examples:
|
|
112
|
+
>>> data-ops-cli kb doc list <kb_id>
|
|
113
|
+
>>> data-ops-cli kb doc list <kb_id> --keyword "故障处理"
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
result = service_list_knowledge_documents(
|
|
117
|
+
kb_id, page=page, page_size=page_size, keyword=keyword
|
|
118
|
+
)
|
|
119
|
+
except DataOpsError as e:
|
|
120
|
+
typer.echo(f"错误: {e.message}")
|
|
121
|
+
raise typer.Exit(code=1) from None
|
|
122
|
+
|
|
123
|
+
if not result.items:
|
|
124
|
+
print("未查询到文档")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
print_list(
|
|
128
|
+
("id", "title", "file_name", "parse_status"),
|
|
129
|
+
[
|
|
130
|
+
[
|
|
131
|
+
it.id,
|
|
132
|
+
it.title or "",
|
|
133
|
+
it.file_name or "",
|
|
134
|
+
it.parse_status or "",
|
|
135
|
+
]
|
|
136
|
+
for it in result.items
|
|
137
|
+
],
|
|
138
|
+
)
|
|
139
|
+
print(f"total={result.total} page={result.page} page_size={result.page_size}")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@doc_typer.command("upload", no_args_is_help=True)
|
|
143
|
+
def doc_upload_cmd(
|
|
144
|
+
kb_id: Annotated[str, typer.Argument(help="知识库 ID")],
|
|
145
|
+
file: Annotated[Path, typer.Argument(help="本地文件路径", exists=True, readable=True)],
|
|
146
|
+
name: Annotated[
|
|
147
|
+
str | None,
|
|
148
|
+
typer.Option("--name", help="覆盖上传文件名"),
|
|
149
|
+
] = None,
|
|
150
|
+
) -> None:
|
|
151
|
+
"""上传文件到知识库。
|
|
152
|
+
|
|
153
|
+
Examples:
|
|
154
|
+
>>> data-ops-cli kb doc upload <kb_id> ./manual.pdf
|
|
155
|
+
"""
|
|
156
|
+
try:
|
|
157
|
+
doc = service_upload_knowledge_file(kb_id, file, file_name=name)
|
|
158
|
+
except (DataOpsError, FileNotFoundError) as e:
|
|
159
|
+
msg = getattr(e, "message", None) or str(e)
|
|
160
|
+
typer.echo(f"错误: {msg}")
|
|
161
|
+
raise typer.Exit(code=1) from None
|
|
162
|
+
|
|
163
|
+
print(f"上传成功: id={doc.id} title={doc.title or ''} file_name={doc.file_name or ''}")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@kb_typer.command("search", no_args_is_help=True)
|
|
167
|
+
def search_cmd(
|
|
168
|
+
keyword: Annotated[str, typer.Argument(help="检索关键词")],
|
|
169
|
+
limit: Annotated[int, typer.Option("--limit", help="返回条数", min=1)] = 20,
|
|
170
|
+
offset: Annotated[int, typer.Option("--offset", help="偏移量", min=0)] = 0,
|
|
171
|
+
) -> None:
|
|
172
|
+
"""全局关键词检索知识内容。
|
|
173
|
+
|
|
174
|
+
Examples:
|
|
175
|
+
>>> data-ops-cli kb search "网络割接"
|
|
176
|
+
"""
|
|
177
|
+
try:
|
|
178
|
+
result = service_search_knowledge(keyword, limit=limit, offset=offset)
|
|
179
|
+
except DataOpsError as e:
|
|
180
|
+
typer.echo(f"错误: {e.message}")
|
|
181
|
+
raise typer.Exit(code=1) from None
|
|
182
|
+
|
|
183
|
+
if not result.items:
|
|
184
|
+
print("未检索到内容")
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
print_list(
|
|
188
|
+
("title", "kb_name", "score", "content"),
|
|
189
|
+
[
|
|
190
|
+
[
|
|
191
|
+
it.title or it.knowledge_title or "",
|
|
192
|
+
it.kb_name or "",
|
|
193
|
+
f"{it.score:.4f}" if it.score is not None else "",
|
|
194
|
+
(it.content or "")[:80],
|
|
195
|
+
]
|
|
196
|
+
for it in result.items
|
|
197
|
+
],
|
|
198
|
+
)
|
|
199
|
+
print(f"total={result.total}")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@page_typer.command("search", no_args_is_help=True)
|
|
203
|
+
def page_search_cmd(
|
|
204
|
+
kb_id: Annotated[str, typer.Argument(help="知识库 ID")],
|
|
205
|
+
query: Annotated[str, typer.Argument(help="检索词")],
|
|
206
|
+
limit: Annotated[int, typer.Option("--limit", help="返回条数", min=1)] = 20,
|
|
207
|
+
) -> None:
|
|
208
|
+
"""检索知识库 Wiki 页面。
|
|
209
|
+
|
|
210
|
+
Examples:
|
|
211
|
+
>>> data-ops-cli kb page search <kb_id> "运维手册"
|
|
212
|
+
"""
|
|
213
|
+
try:
|
|
214
|
+
pages = service_search_wiki_pages(kb_id, query, limit=limit)
|
|
215
|
+
except DataOpsError as e:
|
|
216
|
+
typer.echo(f"错误: {e.message}")
|
|
217
|
+
raise typer.Exit(code=1) from None
|
|
218
|
+
|
|
219
|
+
if not pages:
|
|
220
|
+
print("未检索到 Wiki 页面")
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
print_list(
|
|
224
|
+
("slug", "title", "page_type", "summary"),
|
|
225
|
+
[
|
|
226
|
+
[
|
|
227
|
+
it.slug,
|
|
228
|
+
it.title or "",
|
|
229
|
+
it.page_type or "",
|
|
230
|
+
(it.summary or "")[:60],
|
|
231
|
+
]
|
|
232
|
+
for it in pages
|
|
233
|
+
],
|
|
234
|
+
)
|
|
@@ -23,6 +23,8 @@ from dataops_sdk.services import (
|
|
|
23
23
|
service_pack_skill_zip,
|
|
24
24
|
service_skill_call_by_name,
|
|
25
25
|
service_skill_delete_sub_file,
|
|
26
|
+
service_skill_download,
|
|
27
|
+
service_skill_update,
|
|
26
28
|
service_skill_upload,
|
|
27
29
|
service_skill_upload_sub_file,
|
|
28
30
|
)
|
|
@@ -151,6 +153,72 @@ def upload_cmd(
|
|
|
151
153
|
print("上传成功")
|
|
152
154
|
|
|
153
155
|
|
|
156
|
+
@skill_typer.command("update", no_args_is_help=True)
|
|
157
|
+
def update_cmd(
|
|
158
|
+
name: Annotated[str, typer.Argument(help="技能业务名")],
|
|
159
|
+
file: Annotated[Path, typer.Argument(help="技能 ZIP 文件路径")],
|
|
160
|
+
dtype: Annotated[
|
|
161
|
+
MetaSkillSubType | None,
|
|
162
|
+
typer.Option("--type", help="技能子类型(可选)"),
|
|
163
|
+
] = None,
|
|
164
|
+
title: Annotated[str | None, typer.Option("--title", help="技能简体中文名称")] = None,
|
|
165
|
+
) -> None:
|
|
166
|
+
"""全量覆盖更新已有技能。
|
|
167
|
+
|
|
168
|
+
Examples:
|
|
169
|
+
>>> data-ops-cli skill update my-skill ./my-skill.zip
|
|
170
|
+
>>> data-ops-cli skill update my-skill ./pkg.zip --type api --title "Foo Skill"
|
|
171
|
+
"""
|
|
172
|
+
if not file.exists():
|
|
173
|
+
typer.echo(f"错误: 文件不存在 {file}")
|
|
174
|
+
raise typer.Exit(code=2)
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
service_skill_update(
|
|
178
|
+
name=name,
|
|
179
|
+
file_path=str(file),
|
|
180
|
+
sub_type=dtype,
|
|
181
|
+
title=title,
|
|
182
|
+
)
|
|
183
|
+
except DataOpsError as e:
|
|
184
|
+
typer.echo(f"错误: {e.message}")
|
|
185
|
+
raise typer.Exit(code=1) from None
|
|
186
|
+
print("更新成功")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@skill_typer.command("download", no_args_is_help=True)
|
|
190
|
+
def download_cmd(
|
|
191
|
+
name: Annotated[str, typer.Argument(help="技能业务名")],
|
|
192
|
+
output: Annotated[
|
|
193
|
+
Path | None,
|
|
194
|
+
typer.Option("--output", "-o", help="输出 ZIP 路径"),
|
|
195
|
+
] = None,
|
|
196
|
+
) -> None:
|
|
197
|
+
"""从平台下载技能 ZIP 包。
|
|
198
|
+
|
|
199
|
+
Examples:
|
|
200
|
+
>>> data-ops-cli skill download my-skill
|
|
201
|
+
>>> data-ops-cli skill download my-skill -o /tmp/my-skill.zip
|
|
202
|
+
>>> data-ops-cli skill download my-skill --output ./out/
|
|
203
|
+
"""
|
|
204
|
+
output_path: str | None = None
|
|
205
|
+
if output is not None:
|
|
206
|
+
# Typer Path 会吞掉末尾分隔符;无 .zip 后缀时视为目录
|
|
207
|
+
output_path = (
|
|
208
|
+
str(output) if output.suffix.lower() == ".zip" else f"{output}/"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
zip_path = service_skill_download(name=name, output_path=output_path)
|
|
213
|
+
except DataOpsError as e:
|
|
214
|
+
typer.echo(f"错误: {e.message}")
|
|
215
|
+
raise typer.Exit(code=1) from None
|
|
216
|
+
except OSError as e:
|
|
217
|
+
typer.echo(f"错误: 写入失败 {e}")
|
|
218
|
+
raise typer.Exit(code=1) from None
|
|
219
|
+
print(f"已下载技能包: {zip_path}")
|
|
220
|
+
|
|
221
|
+
|
|
154
222
|
@skill_typer.command("zip", no_args_is_help=True)
|
|
155
223
|
def zip_cmd(
|
|
156
224
|
folder: Annotated[Path, typer.Argument(help="技能源文件夹路径")],
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""元标签管理命令组.
|
|
2
|
+
|
|
3
|
+
提供标签树 list/detail,以及实体标签关联的 list/add/remove/set。
|
|
4
|
+
标签作用域由 ``type`` 指定(如 skill / agent),非 meta-release 实体。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from dataops_sdk.exceptions import DataOpsError
|
|
14
|
+
from dataops_sdk.schemas.tag import TagTreeNode
|
|
15
|
+
from dataops_sdk.services.meta import release_list_list
|
|
16
|
+
from dataops_sdk.services.tag import (
|
|
17
|
+
flatten_tag_tree,
|
|
18
|
+
service_add_entity_tags,
|
|
19
|
+
service_get_tag_detail,
|
|
20
|
+
service_list_entity_tags,
|
|
21
|
+
service_list_entity_tags_by_codes,
|
|
22
|
+
service_list_tags,
|
|
23
|
+
service_remove_entity_tags,
|
|
24
|
+
service_set_entity_tags,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from dataops_cli.cli.utils import print_list
|
|
28
|
+
|
|
29
|
+
tag_typer = typer.Typer(name="tag", help="Manage meta tags.", no_args_is_help=True)
|
|
30
|
+
rel_typer = typer.Typer(name="rel", help="实体与标签关联", no_args_is_help=True)
|
|
31
|
+
tag_typer.add_typer(rel_typer)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _print_tag_tree(
|
|
35
|
+
nodes: list[TagTreeNode], query: str | None = None, *, indent: int = 0
|
|
36
|
+
) -> list[str]:
|
|
37
|
+
"""缩进打印标签树;有 query 时仅保留匹配子树."""
|
|
38
|
+
pattern = re.compile(query) if query else None
|
|
39
|
+
lines: list[str] = []
|
|
40
|
+
for node in nodes:
|
|
41
|
+
child_lines = _print_tag_tree(node.children or [], query, indent=indent + 1)
|
|
42
|
+
title = node.title or ""
|
|
43
|
+
code = node.code or ""
|
|
44
|
+
self_match = pattern is None or bool(pattern.search(title) or pattern.search(code))
|
|
45
|
+
if not self_match and not child_lines:
|
|
46
|
+
continue
|
|
47
|
+
prefix = " " * indent
|
|
48
|
+
lines.append(f"{prefix}- {title}[{code}]({node.cuid})")
|
|
49
|
+
lines.extend(child_lines)
|
|
50
|
+
return lines
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _echo_final_tags(tag_type: str, entity_code: str, cuids: list[str]) -> None:
|
|
54
|
+
typer.echo(f"已更新 {tag_type}/{entity_code} 标签: {', '.join(cuids) if cuids else '(空)'}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@tag_typer.command("list", no_args_is_help=True)
|
|
58
|
+
def list_tags(
|
|
59
|
+
tag_type: Annotated[str, typer.Argument(help="实体类型作用域,如 skill / agent")],
|
|
60
|
+
query: Annotated[
|
|
61
|
+
str | None, typer.Option("-q", "--query", help="标题/编码关键字(支持正则)")
|
|
62
|
+
] = None,
|
|
63
|
+
as_tree: Annotated[bool, typer.Option("--tree", help="以树形缩进输出")] = False,
|
|
64
|
+
) -> None:
|
|
65
|
+
"""列出指定类型下的标签定义.
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
>>> data-ops-cli tag list skill
|
|
69
|
+
>>> data-ops-cli tag list skill --tree
|
|
70
|
+
>>> data-ops-cli tag list skill -q "运维"
|
|
71
|
+
"""
|
|
72
|
+
try:
|
|
73
|
+
tree = service_list_tags(tag_type)
|
|
74
|
+
except DataOpsError as e:
|
|
75
|
+
typer.echo(f"错误: {e.message}")
|
|
76
|
+
raise typer.Exit(code=1) from None
|
|
77
|
+
|
|
78
|
+
if as_tree:
|
|
79
|
+
lines = _print_tag_tree(tree, query)
|
|
80
|
+
if not lines:
|
|
81
|
+
typer.echo("未查询到匹配标签")
|
|
82
|
+
return
|
|
83
|
+
typer.echo("\n".join(lines))
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
flat = flatten_tag_tree(tree)
|
|
87
|
+
if query:
|
|
88
|
+
pattern = re.compile(query)
|
|
89
|
+
flat = [n for n in flat if pattern.search(n.title or "") or pattern.search(n.code or "")]
|
|
90
|
+
|
|
91
|
+
if not flat:
|
|
92
|
+
typer.echo("未查询到匹配标签")
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
print_list(
|
|
96
|
+
["code", "title", "cuid", "parentId"],
|
|
97
|
+
[[n.code, n.title, n.cuid, n.parentId] for n in flat],
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@tag_typer.command("detail", no_args_is_help=True)
|
|
102
|
+
def get_tag_detail(
|
|
103
|
+
tag_type: Annotated[str, typer.Argument(help="实体类型作用域,如 skill")],
|
|
104
|
+
key: Annotated[str, typer.Argument(help="标签 cuid 或 code")],
|
|
105
|
+
full: Annotated[bool, typer.Option("--full", help="显示完整字段")] = False,
|
|
106
|
+
) -> None:
|
|
107
|
+
"""获取标签详情(从标签树中按 cuid/code 查找).
|
|
108
|
+
|
|
109
|
+
Examples:
|
|
110
|
+
>>> data-ops-cli tag detail skill my-tag-code
|
|
111
|
+
>>> data-ops-cli tag detail skill <cuid> --full
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
node = service_get_tag_detail(tag_type, key)
|
|
115
|
+
except DataOpsError as e:
|
|
116
|
+
typer.echo(f"错误: {e.message}")
|
|
117
|
+
raise typer.Exit(code=1) from None
|
|
118
|
+
|
|
119
|
+
if not full:
|
|
120
|
+
print_list(
|
|
121
|
+
["code", "title", "cuid", "parentId", "color"],
|
|
122
|
+
[[node.code, node.title, node.cuid, node.parentId, node.color]],
|
|
123
|
+
)
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
print_list(
|
|
127
|
+
[
|
|
128
|
+
"code",
|
|
129
|
+
"title",
|
|
130
|
+
"cuid",
|
|
131
|
+
"parentId",
|
|
132
|
+
"type",
|
|
133
|
+
"icon",
|
|
134
|
+
"color",
|
|
135
|
+
"sort",
|
|
136
|
+
"remark",
|
|
137
|
+
"createUser",
|
|
138
|
+
"createTime",
|
|
139
|
+
"lastModifyUser",
|
|
140
|
+
"lastModifyTime",
|
|
141
|
+
],
|
|
142
|
+
[
|
|
143
|
+
[
|
|
144
|
+
node.code,
|
|
145
|
+
node.title,
|
|
146
|
+
node.cuid,
|
|
147
|
+
node.parentId,
|
|
148
|
+
node.type,
|
|
149
|
+
node.icon,
|
|
150
|
+
node.color,
|
|
151
|
+
node.sort,
|
|
152
|
+
node.remark,
|
|
153
|
+
node.createUser,
|
|
154
|
+
node.createTime,
|
|
155
|
+
node.lastModifyUser,
|
|
156
|
+
node.lastModifyTime,
|
|
157
|
+
]
|
|
158
|
+
],
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@rel_typer.command("list", no_args_is_help=True)
|
|
163
|
+
def list_entity_tags(
|
|
164
|
+
tag_type: Annotated[str, typer.Argument(help="实体类型作用域,如 skill")],
|
|
165
|
+
entity_code: Annotated[str, typer.Argument(help="实体编码")],
|
|
166
|
+
) -> None:
|
|
167
|
+
"""列出实体已绑定的标签.
|
|
168
|
+
|
|
169
|
+
Examples:
|
|
170
|
+
>>> data-ops-cli tag rel list skill my-skill-code
|
|
171
|
+
"""
|
|
172
|
+
try:
|
|
173
|
+
tags = service_list_entity_tags(tag_type, entity_code)
|
|
174
|
+
except DataOpsError as e:
|
|
175
|
+
typer.echo(f"错误: {e.message}")
|
|
176
|
+
raise typer.Exit(code=1) from None
|
|
177
|
+
|
|
178
|
+
if not tags:
|
|
179
|
+
typer.echo("未绑定标签")
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
print_list(
|
|
183
|
+
["cuid", "title", "color", "icon"],
|
|
184
|
+
[[t.cuid, t.title, t.color, t.icon] for t in tags],
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@rel_typer.command("map", no_args_is_help=True)
|
|
189
|
+
def map_entity_tags(
|
|
190
|
+
tag_type: Annotated[str, typer.Argument(help="实体类型作用域,如 skill")],
|
|
191
|
+
codes: Annotated[
|
|
192
|
+
list[str] | None,
|
|
193
|
+
typer.Option("--code", help="实体编码(可多次;省略则拉取该类型全部实体)"),
|
|
194
|
+
] = None,
|
|
195
|
+
) -> None:
|
|
196
|
+
"""批量展示「实体 → 已绑定标签」映射.
|
|
197
|
+
|
|
198
|
+
未传 ``--code`` 时,通过 meta-release list 拉取 ``tag_type`` 对应的全部实体,
|
|
199
|
+
再调用 ``listByCodes`` 批量查标签。未绑定的实体 ``tags`` 列为空,便于盘点补标。
|
|
200
|
+
|
|
201
|
+
Examples:
|
|
202
|
+
>>> data-ops-cli tag rel map skill
|
|
203
|
+
>>> data-ops-cli tag rel map skill --code sk-a --code sk-b
|
|
204
|
+
"""
|
|
205
|
+
try:
|
|
206
|
+
if codes:
|
|
207
|
+
entity_codes = list(dict.fromkeys(codes))
|
|
208
|
+
titles: dict[str, str] = {}
|
|
209
|
+
else:
|
|
210
|
+
metas = release_list_list(tag_type)
|
|
211
|
+
entity_codes = [m.code for m in metas if m.code]
|
|
212
|
+
titles = {m.code: m.title or m.name or "" for m in metas if m.code}
|
|
213
|
+
mapping = service_list_entity_tags_by_codes(tag_type, entity_codes)
|
|
214
|
+
except DataOpsError as e:
|
|
215
|
+
typer.echo(f"错误: {e.message}")
|
|
216
|
+
raise typer.Exit(code=1) from None
|
|
217
|
+
|
|
218
|
+
if not entity_codes:
|
|
219
|
+
typer.echo("未查询到实体")
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
rows = []
|
|
223
|
+
for code in entity_codes:
|
|
224
|
+
tags = mapping.get(code) or []
|
|
225
|
+
tag_titles = ",".join(t.title or t.cuid for t in tags)
|
|
226
|
+
tag_cuids = ",".join(t.cuid for t in tags)
|
|
227
|
+
rows.append([code, titles.get(code, ""), tag_titles, tag_cuids])
|
|
228
|
+
|
|
229
|
+
print_list(["code", "title", "tags", "tag_cuids"], rows)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@rel_typer.command("add", no_args_is_help=True)
|
|
233
|
+
def add_entity_tags(
|
|
234
|
+
tag_type: Annotated[str, typer.Argument(help="实体类型作用域,如 skill")],
|
|
235
|
+
entity_code: Annotated[str, typer.Argument(help="实体编码")],
|
|
236
|
+
tags: Annotated[
|
|
237
|
+
list[str],
|
|
238
|
+
typer.Option("--tag", help="标签 cuid 或 code(可多次传入)"),
|
|
239
|
+
],
|
|
240
|
+
) -> None:
|
|
241
|
+
"""给实体追加标签(读-改-写;自动补齐根标签).
|
|
242
|
+
|
|
243
|
+
Examples:
|
|
244
|
+
>>> data-ops-cli tag rel add skill my-skill-code --tag my-tag-code
|
|
245
|
+
>>> data-ops-cli tag rel add skill my-skill-code --tag root --tag child
|
|
246
|
+
"""
|
|
247
|
+
try:
|
|
248
|
+
final = service_add_entity_tags(tag_type, entity_code, tags)
|
|
249
|
+
except DataOpsError as e:
|
|
250
|
+
typer.echo(f"错误: {e.message}")
|
|
251
|
+
raise typer.Exit(code=1) from None
|
|
252
|
+
_echo_final_tags(tag_type, entity_code, final)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@rel_typer.command("remove", no_args_is_help=True)
|
|
256
|
+
def remove_entity_tags(
|
|
257
|
+
tag_type: Annotated[str, typer.Argument(help="实体类型作用域,如 skill")],
|
|
258
|
+
entity_code: Annotated[str, typer.Argument(help="实体编码")],
|
|
259
|
+
tags: Annotated[
|
|
260
|
+
list[str],
|
|
261
|
+
typer.Option("--tag", help="标签 cuid 或 code(可多次传入)"),
|
|
262
|
+
],
|
|
263
|
+
) -> None:
|
|
264
|
+
"""从实体移除指定标签.
|
|
265
|
+
|
|
266
|
+
Examples:
|
|
267
|
+
>>> data-ops-cli tag rel remove skill my-skill-code --tag my-tag-code
|
|
268
|
+
"""
|
|
269
|
+
try:
|
|
270
|
+
final = service_remove_entity_tags(tag_type, entity_code, tags)
|
|
271
|
+
except DataOpsError as e:
|
|
272
|
+
typer.echo(f"错误: {e.message}")
|
|
273
|
+
raise typer.Exit(code=1) from None
|
|
274
|
+
_echo_final_tags(tag_type, entity_code, final)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@rel_typer.command("set", no_args_is_help=True)
|
|
278
|
+
def set_entity_tags(
|
|
279
|
+
tag_type: Annotated[str, typer.Argument(help="实体类型作用域,如 skill")],
|
|
280
|
+
entity_code: Annotated[str, typer.Argument(help="实体编码")],
|
|
281
|
+
tags: Annotated[
|
|
282
|
+
list[str] | None,
|
|
283
|
+
typer.Option("--tag", help="标签 cuid 或 code(可多次;省略则清空)"),
|
|
284
|
+
] = None,
|
|
285
|
+
) -> None:
|
|
286
|
+
"""全量设置实体标签;不传 ``--tag`` 则清空.
|
|
287
|
+
|
|
288
|
+
Examples:
|
|
289
|
+
>>> data-ops-cli tag rel set skill my-skill-code --tag root --tag child
|
|
290
|
+
>>> data-ops-cli tag rel set skill my-skill-code
|
|
291
|
+
"""
|
|
292
|
+
try:
|
|
293
|
+
final = service_set_entity_tags(tag_type, entity_code, tags or [])
|
|
294
|
+
except DataOpsError as e:
|
|
295
|
+
typer.echo(f"错误: {e.message}")
|
|
296
|
+
raise typer.Exit(code=1) from None
|
|
297
|
+
_echo_final_tags(tag_type, entity_code, final)
|
|
@@ -160,6 +160,7 @@ def get_workflow_template(
|
|
|
160
160
|
@workflow_typer.command("run", no_args_is_help=True)
|
|
161
161
|
def run_workflow(
|
|
162
162
|
code: Annotated[str, typer.Argument(help="工作流编码")],
|
|
163
|
+
start_node_id: Annotated[str, typer.Argument(help="启动节点ID")],
|
|
163
164
|
params: Annotated[
|
|
164
165
|
str | None,
|
|
165
166
|
typer.Option(help='启动参数 JSON 字符串,例如 \'{"key":"value"}\''),
|
|
@@ -172,14 +173,15 @@ def run_workflow(
|
|
|
172
173
|
|
|
173
174
|
Args:
|
|
174
175
|
code: 工作流编码。
|
|
176
|
+
start_node_id: 启动节点ID。
|
|
175
177
|
params: 可选启动参数 JSON 字符串。
|
|
176
178
|
|
|
177
179
|
Examples:
|
|
178
180
|
无参启动
|
|
179
|
-
>>> data-ops-cli workflow run wf_code_001
|
|
181
|
+
>>> data-ops-cli workflow run wf_code_001 manually-trigger
|
|
180
182
|
|
|
181
183
|
带参数启动
|
|
182
|
-
>>> data-ops-cli workflow run wf_code_001 --params '{"date":"2026-07-16"}'
|
|
184
|
+
>>> data-ops-cli workflow run wf_code_001 manually-trigger --params '{"date":"2026-07-16"}'
|
|
183
185
|
"""
|
|
184
186
|
try:
|
|
185
187
|
params_dict = json.loads(params) if params else None
|
|
@@ -188,7 +190,7 @@ def run_workflow(
|
|
|
188
190
|
raise typer.Exit(code=1) from None
|
|
189
191
|
|
|
190
192
|
try:
|
|
191
|
-
result = service_start_workflow(code, params_dict)
|
|
193
|
+
result = service_start_workflow(code, start_node_id, params_dict)
|
|
192
194
|
except DataOpsError as e:
|
|
193
195
|
typer.echo(f"错误: {e.message}")
|
|
194
196
|
raise typer.Exit(code=1) from None
|
|
@@ -199,6 +201,7 @@ def run_workflow(
|
|
|
199
201
|
@workflow_typer.command("run-async", no_args_is_help=True)
|
|
200
202
|
def run_workflow_async(
|
|
201
203
|
code: Annotated[str, typer.Argument(help="工作流编码")],
|
|
204
|
+
start_node_id: Annotated[str, typer.Argument(help="启动节点ID")],
|
|
202
205
|
params: Annotated[
|
|
203
206
|
str | None,
|
|
204
207
|
typer.Option(help='启动参数 JSON 字符串,例如 \'{"key":"value"}\''),
|
|
@@ -211,14 +214,15 @@ def run_workflow_async(
|
|
|
211
214
|
|
|
212
215
|
Args:
|
|
213
216
|
code: 工作流编码。
|
|
217
|
+
start_node_id: 启动节点ID。
|
|
214
218
|
params: 可选启动参数 JSON 字符串。
|
|
215
219
|
|
|
216
220
|
Examples:
|
|
217
221
|
无参异步启动
|
|
218
|
-
>>> data-ops-cli workflow run-async wf_code_001
|
|
222
|
+
>>> data-ops-cli workflow run-async wf_code_001 manually-trigger
|
|
219
223
|
|
|
220
224
|
带参数异步启动
|
|
221
|
-
>>> data-ops-cli workflow run-async wf_code_001 --params '{"date":"2026-07-16"}'
|
|
225
|
+
>>> data-ops-cli workflow run-async wf_code_001 manually-trigger --params '{"date":"2026-07-16"}'
|
|
222
226
|
"""
|
|
223
227
|
try:
|
|
224
228
|
params_dict = json.loads(params) if params else None
|
|
@@ -227,7 +231,7 @@ def run_workflow_async(
|
|
|
227
231
|
raise typer.Exit(code=1) from None
|
|
228
232
|
|
|
229
233
|
try:
|
|
230
|
-
result = service_start_workflow_async(code, params_dict)
|
|
234
|
+
result = service_start_workflow_async(code, start_node_id, params_dict)
|
|
231
235
|
except DataOpsError as e:
|
|
232
236
|
typer.echo(f"错误: {e.message}")
|
|
233
237
|
raise typer.Exit(code=1) from None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|