Skip to content

Commit 36ea72d

Browse files
author
kevin
committed
feat(cli): expand CLI commands and harden compatibility
1 parent 3aa4053 commit 36ea72d

51 files changed

Lines changed: 6110 additions & 59 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

dashscope/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
# -*- coding: utf-8 -*-
22
# Copyright (c) Alibaba, Inc. and its affiliates.
3+
# pylint: disable=wrong-import-position
34

45
import logging
6+
import warnings
57
from logging import NullHandler
68

9+
# Suppress urllib3 NotOpenSSLWarning on systems with LibreSSL before any SDK
10+
# submodule imports urllib3.
11+
warnings.filterwarnings(
12+
"ignore",
13+
message=".*urllib3.*only supports OpenSSL.*",
14+
category=Warning,
15+
)
16+
717
from dashscope.aigc.code_generation import CodeGeneration
818
from dashscope.aigc.conversation import Conversation, History, HistoryItem
919
from dashscope.aigc.generation import AioGeneration, Generation

dashscope/cli/__init__.py

Lines changed: 203 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"""
88
import sys
99
import warnings
10+
from typing import Optional
1011

1112
# Suppress urllib3 NotOpenSSLWarning on systems with LibreSSL
1213
warnings.filterwarnings(
@@ -18,12 +19,34 @@
1819
import typer # noqa: E402
1920

2021
import dashscope # noqa: E402
22+
from dashscope.cli.common import err_console # noqa: E402
23+
from dashscope.common.error import AuthenticationError # noqa: E402
2124
from dashscope.cli import ( # noqa: E402
25+
application,
26+
assistant_files,
27+
assistants,
28+
code_generation,
2229
deployments,
30+
embeddings,
2331
files,
2432
fine_tunes,
2533
generation,
34+
image_generation,
35+
image_synthesis,
36+
messages,
37+
models,
38+
multimodal_conversation,
39+
multimodal_embedding,
2640
oss,
41+
rerank,
42+
runs,
43+
speech_synthesis,
44+
steps,
45+
threads,
46+
tokenization,
47+
transcription,
48+
understanding,
49+
video_synthesis,
2750
)
2851

2952

@@ -69,6 +92,56 @@
6992
"--page_size": "--page-size",
7093
}
7194

95+
_TOP_LEVEL_COMMANDS = {
96+
"generation",
97+
"ft",
98+
"fine-tunes",
99+
"files",
100+
"deployments",
101+
"oss",
102+
"rerank",
103+
"embeddings",
104+
"tokenization",
105+
"models",
106+
"understanding",
107+
"application",
108+
"code-generation",
109+
"image-synthesis",
110+
"video-synthesis",
111+
"image-generation",
112+
"multimodal-conversation",
113+
"multimodal-embedding",
114+
"transcription",
115+
"speech-synthesis",
116+
"assistants",
117+
"assistant-files",
118+
"threads",
119+
"messages",
120+
"runs",
121+
"steps",
122+
"rl",
123+
"agentic-rl",
124+
}
125+
126+
127+
_COMMANDS_WITH_LOCAL_API_KEY = {"oss", "rl", "agentic-rl"}
128+
_LEGACY_COMMANDS_WITH_SIZE_OPTION = {
129+
"files.list",
130+
"fine_tunes.list",
131+
"deployments.list",
132+
}
133+
134+
135+
def _translate_param_name(arg):
136+
"""Translate legacy underscore option names to Typer hyphen names."""
137+
if arg in _PARAM_MAP:
138+
return _PARAM_MAP[arg]
139+
if arg.startswith("--") and "=" in arg:
140+
option_name, option_value = arg.split("=", 1)
141+
if option_name in _PARAM_MAP:
142+
return f"{_PARAM_MAP[option_name]}={option_value}"
143+
return arg
144+
72145

73146
def _translate_legacy_args(argv):
74147
"""Translate legacy argparse command format to Typer format.
@@ -81,59 +154,95 @@ def _translate_legacy_args(argv):
81154
if len(argv) < 2:
82155
return argv
83156

84-
new_argv = [argv[0]] # Keep program name
85-
i = 1
86-
87-
# Check if first arg is a legacy command
88-
if i < len(argv) and argv[i] in _COMMAND_MAP:
89-
# Split "fine_tunes.call" into ["fine-tunes", "call"]
90-
new_cmd = _COMMAND_MAP[argv[i]].split()
91-
new_argv.extend(new_cmd)
92-
i += 1
157+
is_legacy_command = argv[1] in _COMMAND_MAP
158+
if not is_legacy_command:
159+
return [argv[0]] + [_translate_param_name(arg) for arg in argv[1:]]
93160

94-
# Process remaining args
161+
command_args = _COMMAND_MAP[argv[1]].split()
162+
top_level_args = []
163+
translated_args = []
164+
i = 2
95165
while i < len(argv):
96166
arg = argv[i]
97-
98-
# Translate parameter names
99-
if arg in _PARAM_MAP:
100-
new_argv.append(_PARAM_MAP[arg])
101-
elif arg.startswith("--") and "=" in arg:
102-
opt, val = arg.split("=", 1)
103-
if opt in _PARAM_MAP:
104-
new_argv.append(f"{_PARAM_MAP[opt]}={val}")
167+
translated_arg = _translate_param_name(arg)
168+
if argv[1] in _LEGACY_COMMANDS_WITH_SIZE_OPTION:
169+
if translated_arg == "--page-size":
170+
translated_arg = "--size"
171+
elif translated_arg.startswith("--page-size="):
172+
translated_arg = translated_arg.replace(
173+
"--page-size=",
174+
"--size=",
175+
1,
176+
)
177+
178+
if translated_arg == "--api-key":
179+
top_level_args.append("--api-key")
180+
if i + 1 < len(argv):
181+
top_level_args.append(argv[i + 1])
182+
i += 2
105183
else:
106-
new_argv.append(arg)
107-
else:
108-
new_argv.append(arg)
184+
i += 1
185+
continue
109186

187+
if translated_arg.startswith("--api-key="):
188+
top_level_args.append(translated_arg)
189+
i += 1
190+
continue
191+
192+
translated_args.append(translated_arg)
110193
i += 1
111194

112-
return new_argv
195+
return [argv[0]] + top_level_args + command_args + translated_args
196+
197+
198+
def _exit_missing_api_key_value(option_name):
199+
err_console.print(
200+
f"[red]Error:[/red] Option '{option_name}' requires an argument.",
201+
)
202+
sys.exit(2)
113203

114204

115205
def _extract_global_api_key(argv):
116206
"""Extract global -k/--api-key from argv and set dashscope.api_key.
117207
118-
Returns modified argv with api-key args removed.
208+
For backward compatibility, global api-key can appear before or after most
209+
command names. Commands that define their own local api-key option are left
210+
untouched once their command name has been reached.
119211
"""
120212
new_argv = []
121213
i = 0
214+
current_command = None
122215
while i < len(argv):
123216
arg = argv[i]
124217

125-
# Check for -k or --api-key
218+
if i > 0 and current_command is None and arg in _TOP_LEVEL_COMMANDS:
219+
current_command = arg
220+
221+
should_parse_global_api_key = (
222+
current_command not in _COMMANDS_WITH_LOCAL_API_KEY
223+
)
224+
126225
if arg in ("-k", "--api-key"):
127-
# Next arg should be the key value
128-
if i + 1 < len(argv):
129-
dashscope.api_key = argv[i + 1]
130-
i += 2 # Skip both -k and the value
226+
if i + 1 >= len(argv):
227+
_exit_missing_api_key_value(arg)
228+
next_arg = argv[i + 1]
229+
if not next_arg or next_arg.startswith("-"):
230+
_exit_missing_api_key_value(arg)
231+
if should_parse_global_api_key:
232+
if next_arg in _TOP_LEVEL_COMMANDS:
233+
_exit_missing_api_key_value(arg)
234+
dashscope.api_key = next_arg
235+
i += 2
236+
continue
237+
238+
if arg.startswith("--api-key="):
239+
api_key = arg.split("=", 1)[1]
240+
if not api_key:
241+
_exit_missing_api_key_value("--api-key")
242+
if should_parse_global_api_key:
243+
dashscope.api_key = api_key
244+
i += 1
131245
continue
132-
elif arg.startswith("--api-key="):
133-
# Handle --api-key=value format
134-
dashscope.api_key = arg.split("=", 1)[1]
135-
i += 1
136-
continue
137246

138247
new_argv.append(arg)
139248
i += 1
@@ -153,13 +262,48 @@ def _extract_global_api_key(argv):
153262
rich_markup_mode="rich",
154263
)
155264

265+
266+
@app.callback()
267+
def callback(
268+
api_key: Optional[str] = typer.Option(
269+
None,
270+
"-k",
271+
"--api-key",
272+
help="DashScope API Key.",
273+
),
274+
):
275+
"""Configure global CLI options."""
276+
if api_key:
277+
dashscope.api_key = api_key
278+
279+
156280
# Register sub-command groups
157281
app.add_typer(generation.app)
158282
app.add_typer(fine_tunes.app, name="ft")
159283
app.add_typer(fine_tunes.app, name="fine-tunes", hidden=True)
160284
app.add_typer(files.app)
161285
app.add_typer(deployments.app)
162286
app.add_typer(oss.app)
287+
app.add_typer(rerank.app)
288+
app.add_typer(embeddings.app)
289+
app.add_typer(tokenization.app)
290+
app.add_typer(models.app)
291+
app.add_typer(understanding.app)
292+
app.add_typer(application.app)
293+
app.add_typer(code_generation.app)
294+
app.add_typer(image_synthesis.app)
295+
app.add_typer(video_synthesis.app)
296+
app.add_typer(image_generation.app)
297+
app.add_typer(multimodal_conversation.app)
298+
app.add_typer(multimodal_embedding.app)
299+
app.add_typer(transcription.app)
300+
app.add_typer(speech_synthesis.app)
301+
app.add_typer(assistants.app)
302+
app.add_typer(assistant_files.app)
303+
app.add_typer(threads.app)
304+
app.add_typer(messages.app)
305+
app.add_typer(runs.app)
306+
app.add_typer(steps.app)
163307

164308

165309
def _register_rl_app():
@@ -176,24 +320,41 @@ def _register_rl_app():
176320
name="rl",
177321
help="🚀 Agentic RL fine-tuning commands",
178322
)
179-
except ImportError:
180-
pass
181-
except Exception:
182-
pass
323+
app.add_typer(
324+
rl_app,
325+
name="agentic-rl",
326+
help="🚀 Agentic RL fine-tuning commands",
327+
hidden=True,
328+
)
329+
except ImportError as exception:
330+
err_console.print(
331+
"[yellow]Warning:[/yellow] Failed to register rl command: "
332+
f"{exception}",
333+
)
334+
except Exception as exception:
335+
err_console.print(
336+
"[yellow]Warning:[/yellow] Failed to register rl command: "
337+
f"{exception}",
338+
)
183339

184340

185341
_register_rl_app()
186342

187343

188344
def main():
189345
"""Entry point for the ``dashscope`` console script."""
190-
# Extract global api-key parameter FIRST
191-
argv = _extract_global_api_key(sys.argv)
346+
# Translate legacy command format first so legacy --api_key can be treated
347+
# as the global --api-key option.
348+
argv = _translate_legacy_args(sys.argv)
192349

193-
# Then translate legacy command format
194-
argv = _translate_legacy_args(argv)
350+
# Extract global api-key parameter after legacy argument normalization.
351+
argv = _extract_global_api_key(argv)
195352

196353
# Update sys.argv for Typer
197354
sys.argv = argv
198355

199-
app()
356+
try:
357+
app()
358+
except AuthenticationError as exception:
359+
err_console.print(f"[red]Error:[/red] {exception}")
360+
sys.exit(1)

0 commit comments

Comments
 (0)