Skip to content

Commit ca4cbbd

Browse files
author
luk384090-cloud
committed
feat: add SDK client identifier override and deployment plan parameters
1 parent dfead96 commit ca4cbbd

8 files changed

Lines changed: 111 additions & 7 deletions

File tree

‎dashscope/cli/__init__.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import dashscope # noqa: E402
2323
from dashscope.cli.common import err_console # noqa: E402
2424
from dashscope.common.error import AuthenticationError # noqa: E402
25+
from dashscope.common.utils import set_sdk_client # noqa: E402
2526
from dashscope.cli import ( # noqa: E402
2627
application,
2728
auth,
@@ -43,6 +44,8 @@
4344
video_synthesis,
4445
)
4546

47+
set_sdk_client("python-cli")
48+
4649

4750
# ---------------------------------------------------------------------------
4851
# Legacy command compatibility layer

‎dashscope/cli/deployments.py‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,11 @@ def create(
129129
"--capacity",
130130
help="The target capacity",
131131
),
132-
plan: Optional[str] = typer.Option(
133-
None,
132+
plan: str = typer.Option(
133+
...,
134134
"--plan",
135-
help="Deployment plan or template ID",
135+
help="Required. Deployment plan: ptu (PTU reserved), "
136+
"mu (model unit), or Lora (LoRA deployment).",
136137
),
137138
template_id: Optional[str] = typer.Option(
138139
None,
@@ -145,9 +146,8 @@ def create(
145146
"model": model,
146147
"capacity": capacity,
147148
"suffix": suffix,
149+
"plan": plan,
148150
}
149-
if plan is not None:
150-
kwargs["plan"] = plan
151151
if template_id is not None:
152152
kwargs["template_id"] = template_id
153153

@@ -194,12 +194,19 @@ def call(
194194
"--capacity",
195195
help="The target capacity",
196196
),
197+
plan: str = typer.Option(
198+
...,
199+
"--plan",
200+
help="Required. Deployment plan: ptu (PTU reserved), "
201+
"mu (model unit), or Lora (LoRA deployment).",
202+
),
197203
):
198204
"""(Deprecated: use 'create' instead) Create a model deployment."""
199205
create(
200206
model=model,
201207
suffix=suffix,
202208
capacity=capacity,
209+
plan=plan,
203210
)
204211

205212

‎dashscope/common/utils.py‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,20 @@ def get_user_agent():
163163
return ua
164164

165165

166+
_SDK_CLIENT = "python-sdk"
167+
168+
169+
def set_sdk_client(client: str) -> None:
170+
"""Override the client identifier in the x-dashscope-sdk-client header.
171+
172+
Process-wide, used by entry points that embed the SDK (e.g. the
173+
``dashscope`` CLI marks itself as ``python-cli``).
174+
"""
175+
global _SDK_CLIENT # pylint: disable=global-statement
176+
if client:
177+
_SDK_CLIENT = client
178+
179+
166180
def get_sdk_headers(module: str = "") -> Dict[str, str]:
167181
"""Structured SDK identification headers for backend statistics.
168182
@@ -173,7 +187,7 @@ def get_sdk_headers(module: str = "") -> Dict[str, str]:
173187
"""
174188
if os.environ.get("DASHSCOPE_DISABLE_SDK_HEADERS"):
175189
return {}
176-
parts = ["python-sdk", __version__]
190+
parts = [_SDK_CLIENT, __version__]
177191
if module:
178192
parts.append(module)
179193
return {

‎dashscope/finetune/deployments.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ def call( # pylint: disable=arguments-renamed # type: ignore[override]
3737
capacity: int,
3838
version: str = None,
3939
suffix: Optional[str] = None,
40+
plan: Optional[str] = None,
41+
template_id: Optional[str] = None,
4042
api_key: str = None,
4143
workspace: str = None,
4244
**kwargs,
@@ -51,6 +53,11 @@ def call( # pylint: disable=arguments-renamed # type: ignore[override]
5153
If specified, the final model name will be model_suffix.
5254
Defaults to None.
5355
capacity (int, optional): The model service capacity.
56+
plan (str, optional): The deployment plan required by the
57+
service: "ptu" (PTU reserved), "mu" (model unit) or
58+
"Lora" (LoRA deployment). Defaults to None.
59+
template_id (str, optional): Template ID for deployment
60+
configuration. Defaults to None.
5461
api_key (str, optional): The api-key. Defaults to None.
5562
workspace (str): The dashscope workspace id.
5663
@@ -63,6 +70,10 @@ def call( # pylint: disable=arguments-renamed # type: ignore[override]
6370
req["model_version"] = version
6471
if suffix is not None:
6572
req["suffix"] = suffix
73+
if plan is not None:
74+
req["plan"] = plan
75+
if template_id is not None:
76+
req["template_id"] = template_id
6677
response = super().call(
6778
req,
6879
api_key=api_key,

‎tests/unit/handle_deployment_request.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ async def create_deployment_handler(request: aiohttp.request):
2121
"deployed_model": "deploy123456",
2222
"status": "PENDING",
2323
"model_name": "qwen-turbo-ft-202307121513-5dde",
24+
"plan": body.get("plan"),
25+
"template_id": body.get("template_id"),
2426
},
2527
}
2628

‎tests/unit/test_cli_main.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,7 @@ def mock_call(**kwargs):
648648

649649
result = CliRunner().invoke(
650650
cli_app,
651-
["deployments", "create", "--model", "qwen"],
651+
["deployments", "create", "--model", "qwen", "--plan", "ptu"],
652652
)
653653

654654
assert result.exit_code == 1

‎tests/unit/test_http_deployments_api.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ def test_create_deployment_tune_job(self, http_server):
2121
assert resp.output["deployed_model"] == "deploy123456"
2222
assert resp.output["status"] == "PENDING"
2323

24+
def test_create_deployment_with_plan(self, http_server):
25+
# Regression: plan/template_id must reach the request body (verified
26+
# via mock echo; previously dropped silently, causing "plan is blank")
27+
resp = Deployments.call(
28+
model="gpt",
29+
suffix="1",
30+
capacity=2,
31+
plan="plan-standard",
32+
template_id="tpl-001",
33+
)
34+
assert resp.status_code == HTTPStatus.OK
35+
assert resp.output["deployed_model"] == "deploy123456"
36+
assert resp.output["plan"] == "plan-standard"
37+
assert resp.output["template_id"] == "tpl-001"
38+
2439
def test_list_deployment_job(self, http_server):
2540
rsp = Deployments.list()
2641
assert rsp.status_code == HTTPStatus.OK

‎tests/unit/test_sdk_headers.py‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
SESSION_HEADER = "x-dashscope-sdk-session-id"
1414
DISABLE_ENV = "DASHSCOPE_DISABLE_SDK_HEADERS"
1515

16+
# client 取值来源:
17+
# - "python-sdk": SDK 默认(common/utils.py 的 _SDK_CLIENT)
18+
# - "python-cli": dashscope/cli/__init__.py 在 CLI 进程启动时调用
19+
# set_sdk_client("python-cli") 覆盖
20+
# - "acli": acli/providers/tongyi.py 自建 header
1621
# module 取值在代码库中的实际使用点:
1722
# - "" (无 module): default_headers / base_request / api_request_factory
1823
# - "agentstudio": agentstudio/transport.py
@@ -21,6 +26,12 @@
2126
ACLI_MODULES = ["", "app"]
2227

2328

29+
@pytest.fixture(autouse=True)
30+
def _pin_sdk_client(monkeypatch):
31+
# CLI 测试会在同进程内把 client 改成 python-cli,这里钉住默认值
32+
monkeypatch.setattr("dashscope.common.utils._SDK_CLIENT", "python-sdk")
33+
34+
2435
def _check_client_header(value, client, version, module=""):
2536
parts = value.split("/")
2637
assert parts[0] == client
@@ -68,5 +79,46 @@ def test_sdk_headers_disabled(monkeypatch):
6879
assert SESSION_HEADER not in provider._get_headers()
6980

7081

82+
def test_python_cli_client_header():
83+
from dashscope.common.utils import set_sdk_client
84+
85+
set_sdk_client("python-cli")
86+
try:
87+
headers = get_sdk_headers()
88+
value = headers[CLIENT_HEADER]
89+
print(f"\npython-cli -> {value}")
90+
_check_client_header(value, "python-cli", sdk_version)
91+
# module 段同样适用
92+
value = get_sdk_headers(module="agentstudio")[CLIENT_HEADER]
93+
print(f"python-cli -> {value}")
94+
_check_client_header(value, "python-cli", sdk_version, "agentstudio")
95+
finally:
96+
set_sdk_client("python-sdk")
97+
98+
99+
def test_cli_import_marks_process():
100+
# 子进程隔离验证:加载 dashscope.cli 后,进程内请求的
101+
# client 标识应变为 python-cli(与同进程其他测试的执行顺序无关)
102+
import subprocess
103+
import sys
104+
105+
code = (
106+
"import dashscope.cli; "
107+
"from dashscope.common.utils import get_sdk_headers; "
108+
"h = get_sdk_headers(); "
109+
"c = h['x-dashscope-sdk-client']; "
110+
"assert c.startswith('python-cli/'), c; "
111+
"print(c)"
112+
)
113+
result = subprocess.run(
114+
[sys.executable, "-c", code],
115+
capture_output=True,
116+
text=True,
117+
check=False,
118+
)
119+
assert result.returncode == 0, result.stderr
120+
print(f"\nCLI process -> {result.stdout.strip()}")
121+
122+
71123
if __name__ == "__main__":
72124
pytest.main([__file__, "-v", "-s"])

0 commit comments

Comments
 (0)