Skip to content

Commit a1dcee0

Browse files
Merge pull request #1 from luk384090-cloud/dev/cli-refactor
Dev/cli refactor
2 parents e6ab85e + 3fdff36 commit a1dcee0

13 files changed

Lines changed: 1156 additions & 889 deletions

File tree

dashscope/cli.py

Lines changed: 0 additions & 843 deletions
This file was deleted.

dashscope/cli/__init__.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# -*- coding: utf-8 -*-
2+
"""DashScope command-line entry point.
3+
4+
This package is intentionally thin — all command-specific logic lives in
5+
sub-modules (generation, fine_tunes, files, etc.).
6+
"""
7+
import sys
8+
import warnings
9+
10+
# Suppress urllib3 NotOpenSSLWarning on systems with LibreSSL
11+
warnings.filterwarnings(
12+
"ignore",
13+
message=".*urllib3.*only supports OpenSSL.*",
14+
category=Warning,
15+
)
16+
17+
import typer
18+
19+
import dashscope
20+
from dashscope.cli import (
21+
deployments,
22+
files,
23+
fine_tunes,
24+
generation,
25+
oss,
26+
)
27+
28+
29+
# ---------------------------------------------------------------------------
30+
# Legacy command compatibility layer
31+
# ---------------------------------------------------------------------------
32+
33+
# Command name mapping: old -> new
34+
_COMMAND_MAP = {
35+
'fine_tunes.call': 'fine-tunes create',
36+
'fine_tunes.get': 'fine-tunes get',
37+
'fine_tunes.list': 'fine-tunes list',
38+
'fine_tunes.stream': 'fine-tunes stream',
39+
'fine_tunes.cancel': 'fine-tunes cancel',
40+
'fine_tunes.delete': 'fine-tunes delete',
41+
'generation.call': 'generation create',
42+
'files.upload': 'files upload',
43+
'files.get': 'files get',
44+
'files.list': 'files list',
45+
'files.delete': 'files delete',
46+
'deployments.call': 'deployments create',
47+
'deployments.get': 'deployments get',
48+
'deployments.list': 'deployments list',
49+
'deployments.scale': 'deployments scale',
50+
'deployments.delete': 'deployments delete',
51+
'oss.upload': 'oss upload',
52+
}
53+
54+
# Parameter name mapping: old -> new (underscore to dash)
55+
_PARAM_MAP = {
56+
'--training_file_ids': '--training-file-ids',
57+
'--validation_file_ids': '--validation-file-ids',
58+
'--n_epochs': '--n-epochs',
59+
'--batch_size': '--batch-size',
60+
'--learning_rate': '--learning-rate',
61+
'--prompt_loss': '--prompt-loss',
62+
'--hyper_parameters': '--hyper-parameters',
63+
'--file_id': '--file-id',
64+
'--deployed_model': '--deployed-model',
65+
'--base_url': '--base-url',
66+
'--api_key': '--api-key',
67+
'--start_page': '--start-page',
68+
'--page_size': '--page-size',
69+
}
70+
71+
72+
def _translate_legacy_args(argv):
73+
"""Translate legacy argparse command format to Typer format.
74+
75+
Legacy format: dashscope fine_tunes.call --training_file_ids ...
76+
New format: dashscope fine-tunes call --training-file-ids ...
77+
78+
Returns modified argv list.
79+
"""
80+
if len(argv) < 2:
81+
return argv
82+
83+
new_argv = [argv[0]] # Keep program name
84+
i = 1
85+
86+
# Check if first arg is a legacy command
87+
if i < len(argv) and argv[i] in _COMMAND_MAP:
88+
# Split "fine_tunes.call" into ["fine-tunes", "call"]
89+
new_cmd = _COMMAND_MAP[argv[i]].split()
90+
new_argv.extend(new_cmd)
91+
i += 1
92+
93+
# Process remaining args
94+
while i < len(argv):
95+
arg = argv[i]
96+
97+
# Translate parameter names
98+
if arg in _PARAM_MAP:
99+
new_argv.append(_PARAM_MAP[arg])
100+
else:
101+
new_argv.append(arg)
102+
103+
i += 1
104+
105+
return new_argv
106+
107+
108+
def _extract_global_api_key(argv):
109+
"""Extract global -k/--api-key from argv and set dashscope.api_key.
110+
111+
Returns modified argv with api-key args removed.
112+
"""
113+
new_argv = []
114+
i = 0
115+
while i < len(argv):
116+
arg = argv[i]
117+
118+
# Check for -k or --api-key
119+
if arg in ('-k', '--api-key'):
120+
# Next arg should be the key value
121+
if i + 1 < len(argv):
122+
dashscope.api_key = argv[i + 1]
123+
i += 2 # Skip both -k and the value
124+
continue
125+
elif arg.startswith('--api-key='):
126+
# Handle --api-key=value format
127+
dashscope.api_key = arg.split('=', 1)[1]
128+
i += 1
129+
continue
130+
131+
new_argv.append(arg)
132+
i += 1
133+
134+
return new_argv
135+
136+
137+
# ---------------------------------------------------------------------------
138+
# Typer app
139+
# ---------------------------------------------------------------------------
140+
141+
app = typer.Typer(
142+
name="dashscope",
143+
help="DashScope command line tools.",
144+
add_completion=False,
145+
no_args_is_help=True,
146+
rich_markup_mode="rich",
147+
)
148+
149+
# Register sub-command groups
150+
app.add_typer(generation.app)
151+
app.add_typer(fine_tunes.app, name="ft") # 主要命令
152+
app.add_typer(fine_tunes.app, name="fine-tunes", hidden=True) # 隐藏,向后兼容
153+
app.add_typer(files.app)
154+
app.add_typer(deployments.app)
155+
app.add_typer(oss.app)
156+
157+
158+
def _register_rl_app():
159+
"""Lazily import and register the Agentic-RL Typer app.
160+
161+
Wrapped in a function so that a missing optional dependency
162+
won't crash the entire CLI at import time.
163+
"""
164+
try:
165+
from dashscope.cli.agentic_rl import app as rl_app
166+
167+
app.add_typer(rl_app, name="rl", help="🚀 Agentic RL fine-tuning commands")
168+
except ImportError:
169+
# reinforcement module not available — skip silently
170+
pass
171+
except Exception:
172+
# Any other issue — skip silently
173+
pass
174+
175+
176+
_register_rl_app()
177+
178+
179+
def main():
180+
"""Entry point for the ``dashscope`` console script."""
181+
# Extract global api-key parameter FIRST (may appear before command name)
182+
argv = _extract_global_api_key(sys.argv)
183+
184+
# Then translate legacy command format
185+
argv = _translate_legacy_args(argv)
186+
187+
# Update sys.argv for Typer
188+
sys.argv = argv
189+
190+
app()

dashscope/cli/__main__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# -*- coding: utf-8 -*-
2+
"""Allow ``python -m dashscope.cli`` invocation."""
3+
import warnings
4+
5+
# Suppress urllib3 NotOpenSSLWarning on systems with LibreSSL
6+
warnings.filterwarnings(
7+
"ignore",
8+
message=".*urllib3.*only supports OpenSSL.*",
9+
category=Warning,
10+
)
11+
12+
import sys
13+
from dashscope.cli import main
14+
15+
main()

dashscope/finetune/reinforcement/common/cli.py renamed to dashscope/cli/agentic_rl.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,20 @@
3030
name="agentic-rl",
3131
help="🚀 Agentic RL Fine-Tuning CLI",
3232
add_completion=False,
33+
invoke_without_command=True,
3334
rich_markup_mode="rich",
3435
)
3536
console = Console()
3637
err_console = Console(stderr=True)
3738

39+
@app.callback()
40+
def callback(ctx: typer.Context):
41+
"""Show help if no subcommand is provided."""
42+
if ctx.invoked_subcommand is None:
43+
typer.echo(ctx.get_help())
44+
45+
46+
3847
_cli_verbose = False
3948

4049

dashscope/cli/common.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# -*- coding: utf-8 -*-
2+
"""Shared utilities, constants, and helpers for the dashscope CLI."""
3+
import logging
4+
from http import HTTPStatus
5+
6+
import typer
7+
from rich.console import Console
8+
9+
logger = logging.getLogger("dashscope.cli")
10+
11+
# ---------------------------------------------------------------------------
12+
# Constants
13+
# ---------------------------------------------------------------------------
14+
POLL_INTERVAL = 30 # seconds between polling requests
15+
LOG_PAGE_SIZE = 1000 # log lines per request
16+
DEFAULT_PAGE_SIZE = 10
17+
DEFAULT_START_PAGE = 1
18+
19+
# ---------------------------------------------------------------------------
20+
# Rich consoles
21+
# ---------------------------------------------------------------------------
22+
console = Console()
23+
err_console = Console(stderr=True)
24+
25+
# ---------------------------------------------------------------------------
26+
# Response helpers
27+
# ---------------------------------------------------------------------------
28+
29+
30+
def print_failed_message(rsp):
31+
"""Print a standardised error message for a failed API response."""
32+
err_console.print(
33+
f"[red]Failed[/red] request_id: {rsp.request_id}, "
34+
f"status_code: {rsp.status_code}, "
35+
f"code: {rsp.code}, message: {rsp.message}",
36+
)
37+
38+
39+
def ensure_ok(rsp):
40+
"""Return *rsp.output* when the response is OK; otherwise print the error
41+
and exit with code 1.
42+
43+
This eliminates the repetitive ``if rsp.status_code == OK … else …``
44+
pattern that appears in every command handler.
45+
"""
46+
if rsp.status_code == HTTPStatus.OK:
47+
return rsp.output
48+
print_failed_message(rsp)
49+
raise typer.Exit(1)
50+
51+
52+
def success(message: str):
53+
"""Print a success message in green."""
54+
console.print(f"[green]✓[/green] {message}")
55+
56+
57+
def info(message: str):
58+
"""Print an info message."""
59+
console.print(message)
60+
61+
62+
def error(message: str, exit_code: int = 1):
63+
"""Print an error message in red and exit."""
64+
err_console.print(f"[red]Error:[/red] {message}")
65+
raise typer.Exit(exit_code)

0 commit comments

Comments
 (0)