qwenpaw-data-cli 0.1.0__py3-none-any.whl
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.
- qwenpaw_data/cli/__init__.py +5 -0
- qwenpaw_data/cli/commands/__init__.py +9 -0
- qwenpaw_data/cli/commands/chat.py +56 -0
- qwenpaw_data/cli/commands/datasource.py +281 -0
- qwenpaw_data/cli/commands/doctor.py +419 -0
- qwenpaw_data/cli/commands/execute.py +54 -0
- qwenpaw_data/cli/commands/mcp.py +59 -0
- qwenpaw_data/cli/commands/plan.py +62 -0
- qwenpaw_data/cli/commands/run.py +55 -0
- qwenpaw_data/cli/commands/semantic.py +594 -0
- qwenpaw_data/cli/env.py +42 -0
- qwenpaw_data/cli/logging_config.py +132 -0
- qwenpaw_data/cli/main.py +83 -0
- qwenpaw_data/cli/util.py +360 -0
- qwenpaw_data_cli-0.1.0.dist-info/METADATA +128 -0
- qwenpaw_data_cli-0.1.0.dist-info/RECORD +20 -0
- qwenpaw_data_cli-0.1.0.dist-info/WHEEL +4 -0
- qwenpaw_data_cli-0.1.0.dist-info/entry_points.txt +2 -0
- qwenpaw_data_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
- qwenpaw_data_cli-0.1.0.dist-info/licenses/NOTICE +36 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Chat command."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from qwenpaw_data.cli.util import (
|
|
9
|
+
add_datasource_id_arg,
|
|
10
|
+
add_permission_mode_arg,
|
|
11
|
+
add_workspace_arg,
|
|
12
|
+
build_cli_confirmation_handler,
|
|
13
|
+
create_qwenpaw_data,
|
|
14
|
+
print_execution_summary,
|
|
15
|
+
print_msg,
|
|
16
|
+
request_context_from_args,
|
|
17
|
+
resolve_permission_mode,
|
|
18
|
+
resolve_workspace_type,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def register(subparsers: argparse._SubParsersAction) -> None:
|
|
23
|
+
parser = subparsers.add_parser("chat", help="Start an interactive chat")
|
|
24
|
+
add_datasource_id_arg(parser)
|
|
25
|
+
add_workspace_arg(parser)
|
|
26
|
+
add_permission_mode_arg(parser)
|
|
27
|
+
parser.set_defaults(handler=handle)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def handle(args: argparse.Namespace) -> int:
|
|
31
|
+
workspace_type = resolve_workspace_type(args)
|
|
32
|
+
dp = create_qwenpaw_data(
|
|
33
|
+
request_context=request_context_from_args(args),
|
|
34
|
+
workspace_type=workspace_type,
|
|
35
|
+
permission_mode=resolve_permission_mode(args, workspace_type),
|
|
36
|
+
confirmation_handler=build_cli_confirmation_handler(),
|
|
37
|
+
)
|
|
38
|
+
print("QwenPaw Data chat. Type 'exit' or 'quit' to quit.", file=sys.stderr)
|
|
39
|
+
try:
|
|
40
|
+
while True:
|
|
41
|
+
try:
|
|
42
|
+
text = input("> ").strip()
|
|
43
|
+
except EOFError:
|
|
44
|
+
print()
|
|
45
|
+
return 0
|
|
46
|
+
|
|
47
|
+
if not text:
|
|
48
|
+
continue
|
|
49
|
+
if text.lower() in {"exit", "quit"}:
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
msg = await dp.run(text)
|
|
53
|
+
print_msg(msg)
|
|
54
|
+
print_execution_summary(msg)
|
|
55
|
+
finally:
|
|
56
|
+
await dp.close()
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""Context Manager datasource discovery and management commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from qwenpaw_data.host.core.cm_client import CMDatasource, ContextManagerClient
|
|
10
|
+
from qwenpaw_data.host.core.semantic_config_client import (
|
|
11
|
+
SEMANTIC_CONFIG_PREFIX,
|
|
12
|
+
SemanticConfigClient,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from qwenpaw_data.cli.util import (
|
|
16
|
+
confirm_deletion,
|
|
17
|
+
load_json_object,
|
|
18
|
+
parse_json_object,
|
|
19
|
+
print_json,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
MASKED_SECRET = "******"
|
|
23
|
+
SENSITIVE_CONFIG_FIELDS = frozenset(
|
|
24
|
+
{
|
|
25
|
+
"password",
|
|
26
|
+
"access_key_id",
|
|
27
|
+
"access_key_secret",
|
|
28
|
+
"sts_token",
|
|
29
|
+
},
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
_DATASOURCE_PATH = f"{SEMANTIC_CONFIG_PREFIX}/datasource"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def register(subparsers: argparse._SubParsersAction) -> None:
|
|
36
|
+
parser = subparsers.add_parser(
|
|
37
|
+
"datasource",
|
|
38
|
+
help="Manage datasources configured in DataBridge",
|
|
39
|
+
)
|
|
40
|
+
datasource_subparsers = parser.add_subparsers(
|
|
41
|
+
dest="datasource_command",
|
|
42
|
+
required=True,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
list_parser = datasource_subparsers.add_parser(
|
|
46
|
+
"list",
|
|
47
|
+
help="List all DataBridge datasources",
|
|
48
|
+
)
|
|
49
|
+
list_parser.set_defaults(handler=handle_list)
|
|
50
|
+
|
|
51
|
+
get_parser = datasource_subparsers.add_parser(
|
|
52
|
+
"get",
|
|
53
|
+
help="Show one datasource",
|
|
54
|
+
)
|
|
55
|
+
get_parser.add_argument("datasource_id", help="Datasource id")
|
|
56
|
+
get_parser.add_argument(
|
|
57
|
+
"--show-config",
|
|
58
|
+
action="store_true",
|
|
59
|
+
help="Include the connection config with sensitive fields masked",
|
|
60
|
+
)
|
|
61
|
+
get_parser.set_defaults(handler=handle_get)
|
|
62
|
+
|
|
63
|
+
create_parser = datasource_subparsers.add_parser(
|
|
64
|
+
"create",
|
|
65
|
+
help="Create a datasource (requires a credentials:manage API key)",
|
|
66
|
+
)
|
|
67
|
+
create_parser.add_argument("--name", required=True, help="Datasource name")
|
|
68
|
+
create_parser.add_argument(
|
|
69
|
+
"--type",
|
|
70
|
+
required=True,
|
|
71
|
+
dest="datasource_type",
|
|
72
|
+
help="Datasource type, e.g. postgresql / mysql / odps",
|
|
73
|
+
)
|
|
74
|
+
_add_config_args(create_parser, required=True)
|
|
75
|
+
create_parser.add_argument(
|
|
76
|
+
"--test",
|
|
77
|
+
action="store_true",
|
|
78
|
+
help="Test the connection first and abort the create on failure",
|
|
79
|
+
)
|
|
80
|
+
create_parser.set_defaults(handler=handle_create)
|
|
81
|
+
|
|
82
|
+
update_parser = datasource_subparsers.add_parser(
|
|
83
|
+
"update",
|
|
84
|
+
help="Update a datasource; a provided config replaces the stored one",
|
|
85
|
+
)
|
|
86
|
+
update_parser.add_argument("datasource_id", help="Datasource id")
|
|
87
|
+
update_parser.add_argument("--name", help="New datasource name")
|
|
88
|
+
update_parser.add_argument(
|
|
89
|
+
"--type",
|
|
90
|
+
dest="datasource_type",
|
|
91
|
+
help="New datasource type",
|
|
92
|
+
)
|
|
93
|
+
_add_config_args(update_parser, required=False)
|
|
94
|
+
update_parser.set_defaults(handler=handle_update)
|
|
95
|
+
|
|
96
|
+
delete_parser = datasource_subparsers.add_parser(
|
|
97
|
+
"delete",
|
|
98
|
+
help="Delete a datasource",
|
|
99
|
+
)
|
|
100
|
+
delete_parser.add_argument("datasource_id", help="Datasource id")
|
|
101
|
+
delete_parser.add_argument(
|
|
102
|
+
"--yes",
|
|
103
|
+
action="store_true",
|
|
104
|
+
help="Skip the interactive confirmation",
|
|
105
|
+
)
|
|
106
|
+
delete_parser.set_defaults(handler=handle_delete)
|
|
107
|
+
|
|
108
|
+
test_parser = datasource_subparsers.add_parser(
|
|
109
|
+
"test",
|
|
110
|
+
help="Test connectivity of a saved datasource or an ad-hoc config",
|
|
111
|
+
)
|
|
112
|
+
test_parser.add_argument(
|
|
113
|
+
"datasource_id",
|
|
114
|
+
nargs="?",
|
|
115
|
+
help="Saved datasource id (omit when testing an ad-hoc config)",
|
|
116
|
+
)
|
|
117
|
+
test_parser.add_argument(
|
|
118
|
+
"--type",
|
|
119
|
+
dest="datasource_type",
|
|
120
|
+
help="Datasource type for an ad-hoc connection test",
|
|
121
|
+
)
|
|
122
|
+
_add_config_args(test_parser, required=False)
|
|
123
|
+
test_parser.set_defaults(handler=handle_test)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _add_config_args(parser: argparse.ArgumentParser, *, required: bool) -> None:
|
|
127
|
+
group = parser.add_mutually_exclusive_group(required=required)
|
|
128
|
+
group.add_argument(
|
|
129
|
+
"--config-file",
|
|
130
|
+
type=Path,
|
|
131
|
+
help="Path to a JSON file with the connection config",
|
|
132
|
+
)
|
|
133
|
+
group.add_argument(
|
|
134
|
+
"--config",
|
|
135
|
+
dest="config_inline",
|
|
136
|
+
help="Inline JSON object with the connection config",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _load_config(args: argparse.Namespace) -> dict[str, Any] | None:
|
|
141
|
+
if getattr(args, "config_file", None) is not None:
|
|
142
|
+
return load_json_object(args.config_file)
|
|
143
|
+
if getattr(args, "config_inline", None) is not None:
|
|
144
|
+
return parse_json_object(args.config_inline, flag="--config")
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _mask_config(config: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
149
|
+
if config is None:
|
|
150
|
+
return None
|
|
151
|
+
return {
|
|
152
|
+
key: MASKED_SECRET
|
|
153
|
+
if key in SENSITIVE_CONFIG_FIELDS and value not in (None, "")
|
|
154
|
+
else value
|
|
155
|
+
for key, value in config.items()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _masked_record(record: dict[str, Any], *, include_config: bool) -> dict[str, Any]:
|
|
160
|
+
payload = {
|
|
161
|
+
"datasource_id": record.get("datasource_id"),
|
|
162
|
+
"datasource_name": record.get("datasource_name"),
|
|
163
|
+
"datasource_type": record.get("datasource_type"),
|
|
164
|
+
}
|
|
165
|
+
if include_config:
|
|
166
|
+
config = record.get("config")
|
|
167
|
+
payload["config"] = _mask_config(config if isinstance(config, dict) else None)
|
|
168
|
+
return payload
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _output_item(item: CMDatasource) -> dict[str, Any]:
|
|
172
|
+
return {
|
|
173
|
+
"datasource_id": item.datasource_id,
|
|
174
|
+
"datasource_name": item.datasource_name,
|
|
175
|
+
"datasource_type": item.datasource_type,
|
|
176
|
+
"config": _mask_config(item.config),
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def handle_list(_: argparse.Namespace) -> int:
|
|
181
|
+
result = ContextManagerClient().list_datasources()
|
|
182
|
+
payload = {
|
|
183
|
+
"items": [_output_item(item) for item in result.items],
|
|
184
|
+
"total": result.total,
|
|
185
|
+
}
|
|
186
|
+
print_json(payload)
|
|
187
|
+
return 0
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def handle_get(args: argparse.Namespace) -> int:
|
|
191
|
+
record = SemanticConfigClient().get(
|
|
192
|
+
f"{_DATASOURCE_PATH}/{args.datasource_id}",
|
|
193
|
+
)
|
|
194
|
+
print_json(_masked_record(record, include_config=args.show_config))
|
|
195
|
+
return 0
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def handle_create(args: argparse.Namespace) -> int:
|
|
199
|
+
config = _load_config(args)
|
|
200
|
+
client = SemanticConfigClient()
|
|
201
|
+
if args.test:
|
|
202
|
+
result = client.post(
|
|
203
|
+
f"{_DATASOURCE_PATH}/test-connection",
|
|
204
|
+
json={"datasource_type": args.datasource_type, "config": config},
|
|
205
|
+
)
|
|
206
|
+
if not result.get("success"):
|
|
207
|
+
raise ValueError(
|
|
208
|
+
f"connection test failed, datasource not created: {result.get('message')}",
|
|
209
|
+
)
|
|
210
|
+
record = client.post(
|
|
211
|
+
_DATASOURCE_PATH,
|
|
212
|
+
json={
|
|
213
|
+
"datasource_name": args.name,
|
|
214
|
+
"datasource_type": args.datasource_type,
|
|
215
|
+
"config": config,
|
|
216
|
+
},
|
|
217
|
+
)
|
|
218
|
+
print_json(_masked_record(record, include_config=True))
|
|
219
|
+
return 0
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def handle_update(args: argparse.Namespace) -> int:
|
|
223
|
+
config = _load_config(args)
|
|
224
|
+
payload: dict[str, Any] = {}
|
|
225
|
+
if args.name is not None:
|
|
226
|
+
payload["datasource_name"] = args.name
|
|
227
|
+
if args.datasource_type is not None:
|
|
228
|
+
payload["datasource_type"] = args.datasource_type
|
|
229
|
+
if config is not None:
|
|
230
|
+
payload["config"] = config
|
|
231
|
+
if not payload:
|
|
232
|
+
raise ValueError("nothing to update: pass --name, --type, or a config")
|
|
233
|
+
record = SemanticConfigClient().put(
|
|
234
|
+
f"{_DATASOURCE_PATH}/{args.datasource_id}",
|
|
235
|
+
json=payload,
|
|
236
|
+
)
|
|
237
|
+
print_json(_masked_record(record, include_config=True))
|
|
238
|
+
return 0
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def handle_delete(args: argparse.Namespace) -> int:
|
|
242
|
+
if not confirm_deletion(f"datasource {args.datasource_id}", assume_yes=args.yes):
|
|
243
|
+
print("aborted")
|
|
244
|
+
return 1
|
|
245
|
+
result = SemanticConfigClient().delete(
|
|
246
|
+
f"{_DATASOURCE_PATH}/{args.datasource_id}",
|
|
247
|
+
)
|
|
248
|
+
print_json(result if result else {"deleted": args.datasource_id})
|
|
249
|
+
return 0
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def handle_test(args: argparse.Namespace) -> int:
|
|
253
|
+
config = _load_config(args)
|
|
254
|
+
ad_hoc = args.datasource_type is not None or config is not None
|
|
255
|
+
if args.datasource_id and ad_hoc:
|
|
256
|
+
raise ValueError("pass either a datasource id or --type with a config, not both")
|
|
257
|
+
client = SemanticConfigClient()
|
|
258
|
+
if args.datasource_id:
|
|
259
|
+
result = client.post(
|
|
260
|
+
f"{_DATASOURCE_PATH}/{args.datasource_id}/test-connection",
|
|
261
|
+
)
|
|
262
|
+
elif args.datasource_type is not None and config is not None:
|
|
263
|
+
result = client.post(
|
|
264
|
+
f"{_DATASOURCE_PATH}/test-connection",
|
|
265
|
+
json={"datasource_type": args.datasource_type, "config": config},
|
|
266
|
+
)
|
|
267
|
+
else:
|
|
268
|
+
raise ValueError("pass a datasource id, or --type together with a config")
|
|
269
|
+
print_json(result)
|
|
270
|
+
return 0 if result.get("success") else 1
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
__all__ = [
|
|
274
|
+
"handle_create",
|
|
275
|
+
"handle_delete",
|
|
276
|
+
"handle_get",
|
|
277
|
+
"handle_list",
|
|
278
|
+
"handle_test",
|
|
279
|
+
"handle_update",
|
|
280
|
+
"register",
|
|
281
|
+
]
|