Skip to content

Commit 84e065e

Browse files
authored
Merge pull request #155 from dashscope/dev/sdk-cli
Dev/sdk cli
2 parents 78d8959 + 93e3d6b commit 84e065e

43 files changed

Lines changed: 3825 additions & 85 deletions

Some content is hidden

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

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ repos:
5353
--disable-error-code=attr-defined,
5454
--disable-error-code=import-untyped,
5555
--disable-error-code=truthy-function,
56+
--disable-error-code=arg-type,
5657
--follow-imports=skip,
5758
--explicit-package-bases,
5859
]

dashscope/__init__.py

Lines changed: 12 additions & 2 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
@@ -14,8 +24,6 @@
1424
)
1525
from dashscope.aigc.video_synthesis import VideoSynthesis
1626
from dashscope.app.application import Application
17-
from dashscope.assistants import Assistant, AssistantList, Assistants
18-
from dashscope.assistants.assistant_types import AssistantFile, DeleteResponse
1927
from dashscope.audio.asr.transcription import Transcription
2028
from dashscope.audio.http_tts.http_speech_synthesizer import (
2129
HttpSpeechSynthesizer,
@@ -48,6 +56,8 @@
4856
from dashscope.models import Models
4957
from dashscope.nlp.understanding import Understanding
5058
from dashscope.rerank import AioTextReRank, TextReRank
59+
from dashscope.assistants import Assistant, AssistantList, Assistants
60+
from dashscope.assistants.assistant_types import AssistantFile, DeleteResponse
5161
from dashscope.threads import (
5262
MessageFile,
5363
Messages,

dashscope/cli/__init__.py

Lines changed: 185 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,28 @@
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+
code_generation,
2227
deployments,
28+
embeddings,
2329
files,
2430
fine_tunes,
2531
generation,
32+
image_generation,
33+
image_synthesis,
34+
models,
35+
multimodal_conversation,
36+
multimodal_embedding,
2637
oss,
38+
rerank,
39+
speech_synthesis,
40+
tokenization,
41+
transcription,
42+
understanding,
43+
video_synthesis,
2744
)
2845

2946

@@ -69,6 +86,50 @@
6986
"--page_size": "--page-size",
7087
}
7188

89+
_TOP_LEVEL_COMMANDS = {
90+
"generation",
91+
"ft",
92+
"fine-tunes",
93+
"files",
94+
"deployments",
95+
"oss",
96+
"rerank",
97+
"embeddings",
98+
"tokenization",
99+
"models",
100+
"understanding",
101+
"application",
102+
"code-generation",
103+
"image-synthesis",
104+
"video-synthesis",
105+
"image-generation",
106+
"multimodal-conversation",
107+
"multimodal-embedding",
108+
"transcription",
109+
"speech-synthesis",
110+
"rl",
111+
"agentic-rl",
112+
}
113+
114+
115+
_COMMANDS_WITH_LOCAL_API_KEY = {"oss", "rl", "agentic-rl"}
116+
_LEGACY_COMMANDS_WITH_SIZE_OPTION = {
117+
"files.list",
118+
"fine_tunes.list",
119+
"deployments.list",
120+
}
121+
122+
123+
def _translate_param_name(arg):
124+
"""Translate legacy underscore option names to Typer hyphen names."""
125+
if arg in _PARAM_MAP:
126+
return _PARAM_MAP[arg]
127+
if arg.startswith("--") and "=" in arg:
128+
option_name, option_value = arg.split("=", 1)
129+
if option_name in _PARAM_MAP:
130+
return f"{_PARAM_MAP[option_name]}={option_value}"
131+
return arg
132+
72133

73134
def _translate_legacy_args(argv):
74135
"""Translate legacy argparse command format to Typer format.
@@ -81,59 +142,95 @@ def _translate_legacy_args(argv):
81142
if len(argv) < 2:
82143
return argv
83144

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
145+
is_legacy_command = argv[1] in _COMMAND_MAP
146+
if not is_legacy_command:
147+
return [argv[0]] + [_translate_param_name(arg) for arg in argv[1:]]
93148

94-
# Process remaining args
149+
command_args = _COMMAND_MAP[argv[1]].split()
150+
top_level_args = []
151+
translated_args = []
152+
i = 2
95153
while i < len(argv):
96154
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}")
155+
translated_arg = _translate_param_name(arg)
156+
if argv[1] in _LEGACY_COMMANDS_WITH_SIZE_OPTION:
157+
if translated_arg == "--page-size":
158+
translated_arg = "--size"
159+
elif translated_arg.startswith("--page-size="):
160+
translated_arg = translated_arg.replace(
161+
"--page-size=",
162+
"--size=",
163+
1,
164+
)
165+
166+
if translated_arg == "--api-key":
167+
top_level_args.append("--api-key")
168+
if i + 1 < len(argv):
169+
top_level_args.append(argv[i + 1])
170+
i += 2
105171
else:
106-
new_argv.append(arg)
107-
else:
108-
new_argv.append(arg)
172+
i += 1
173+
continue
109174

175+
if translated_arg.startswith("--api-key="):
176+
top_level_args.append(translated_arg)
177+
i += 1
178+
continue
179+
180+
translated_args.append(translated_arg)
110181
i += 1
111182

112-
return new_argv
183+
return [argv[0]] + top_level_args + command_args + translated_args
184+
185+
186+
def _exit_missing_api_key_value(option_name):
187+
err_console.print(
188+
f"[red]Error:[/red] Option '{option_name}' requires an argument.",
189+
)
190+
sys.exit(2)
113191

114192

115193
def _extract_global_api_key(argv):
116194
"""Extract global -k/--api-key from argv and set dashscope.api_key.
117195
118-
Returns modified argv with api-key args removed.
196+
For backward compatibility, global api-key can appear before or after most
197+
command names. Commands that define their own local api-key option are left
198+
untouched once their command name has been reached.
119199
"""
120200
new_argv = []
121201
i = 0
202+
current_command = None
122203
while i < len(argv):
123204
arg = argv[i]
124205

125-
# Check for -k or --api-key
206+
if i > 0 and current_command is None and arg in _TOP_LEVEL_COMMANDS:
207+
current_command = arg
208+
209+
should_parse_global_api_key = (
210+
current_command not in _COMMANDS_WITH_LOCAL_API_KEY
211+
)
212+
126213
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
214+
if i + 1 >= len(argv):
215+
_exit_missing_api_key_value(arg)
216+
next_arg = argv[i + 1]
217+
if not next_arg or next_arg.startswith("-"):
218+
_exit_missing_api_key_value(arg)
219+
if should_parse_global_api_key:
220+
if next_arg in _TOP_LEVEL_COMMANDS:
221+
_exit_missing_api_key_value(arg)
222+
dashscope.api_key = next_arg
223+
i += 2
224+
continue
225+
226+
if arg.startswith("--api-key="):
227+
api_key = arg.split("=", 1)[1]
228+
if not api_key:
229+
_exit_missing_api_key_value("--api-key")
230+
if should_parse_global_api_key:
231+
dashscope.api_key = api_key
232+
i += 1
131233
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
137234

138235
new_argv.append(arg)
139236
i += 1
@@ -153,13 +250,42 @@ def _extract_global_api_key(argv):
153250
rich_markup_mode="rich",
154251
)
155252

253+
254+
@app.callback()
255+
def callback(
256+
api_key: Optional[str] = typer.Option(
257+
None,
258+
"-k",
259+
"--api-key",
260+
help="DashScope API Key.",
261+
),
262+
):
263+
"""Configure global CLI options."""
264+
if api_key:
265+
dashscope.api_key = api_key
266+
267+
156268
# Register sub-command groups
157269
app.add_typer(generation.app)
158270
app.add_typer(fine_tunes.app, name="ft")
159271
app.add_typer(fine_tunes.app, name="fine-tunes", hidden=True)
160272
app.add_typer(files.app)
161273
app.add_typer(deployments.app)
162274
app.add_typer(oss.app)
275+
app.add_typer(rerank.app)
276+
app.add_typer(embeddings.app)
277+
app.add_typer(tokenization.app)
278+
app.add_typer(models.app)
279+
app.add_typer(understanding.app)
280+
app.add_typer(application.app)
281+
app.add_typer(code_generation.app)
282+
app.add_typer(image_synthesis.app)
283+
app.add_typer(video_synthesis.app)
284+
app.add_typer(image_generation.app)
285+
app.add_typer(multimodal_conversation.app)
286+
app.add_typer(multimodal_embedding.app)
287+
app.add_typer(transcription.app)
288+
app.add_typer(speech_synthesis.app)
163289

164290

165291
def _register_rl_app():
@@ -176,24 +302,41 @@ def _register_rl_app():
176302
name="rl",
177303
help="🚀 Agentic RL fine-tuning commands",
178304
)
179-
except ImportError:
180-
pass
181-
except Exception:
182-
pass
305+
app.add_typer(
306+
rl_app,
307+
name="agentic-rl",
308+
help="🚀 Agentic RL fine-tuning commands",
309+
hidden=True,
310+
)
311+
except ImportError as exception:
312+
err_console.print(
313+
"[yellow]Warning:[/yellow] Failed to register rl command: "
314+
f"{exception}",
315+
)
316+
except Exception as exception:
317+
err_console.print(
318+
"[yellow]Warning:[/yellow] Failed to register rl command: "
319+
f"{exception}",
320+
)
183321

184322

185323
_register_rl_app()
186324

187325

188326
def main():
189327
"""Entry point for the ``dashscope`` console script."""
190-
# Extract global api-key parameter FIRST
191-
argv = _extract_global_api_key(sys.argv)
328+
# Translate legacy command format first so legacy --api_key can be treated
329+
# as the global --api-key option.
330+
argv = _translate_legacy_args(sys.argv)
192331

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

196335
# Update sys.argv for Typer
197336
sys.argv = argv
198337

199-
app()
338+
try:
339+
app()
340+
except AuthenticationError as exception:
341+
err_console.print(f"[red]Error:[/red] {exception}")
342+
sys.exit(1)

0 commit comments

Comments
 (0)