dma-cli 0.1.0__tar.gz → 0.1.2__tar.gz

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.
Files changed (26) hide show
  1. {dma_cli-0.1.0 → dma_cli-0.1.2}/PKG-INFO +28 -52
  2. {dma_cli-0.1.0 → dma_cli-0.1.2}/README.md +27 -51
  3. {dma_cli-0.1.0 → dma_cli-0.1.2}/config.example.yaml +1 -2
  4. {dma_cli-0.1.0 → dma_cli-0.1.2}/dma_cli/__init__.py +1 -1
  5. {dma_cli-0.1.0 → dma_cli-0.1.2}/dma_cli/client.py +11 -3
  6. {dma_cli-0.1.0 → dma_cli-0.1.2}/dma_cli/resources.py +6 -10
  7. {dma_cli-0.1.0 → dma_cli-0.1.2}/dma_cli/submit.py +11 -7
  8. {dma_cli-0.1.0 → dma_cli-0.1.2}/dma_cli.egg-info/PKG-INFO +28 -52
  9. {dma_cli-0.1.0 → dma_cli-0.1.2}/dma_cli.egg-info/SOURCES.txt +3 -1
  10. {dma_cli-0.1.0 → dma_cli-0.1.2}/job.example.json +1 -1
  11. {dma_cli-0.1.0 → dma_cli-0.1.2}/pyproject.toml +1 -1
  12. dma_cli-0.1.2/tests/test_biz_platform.py +27 -0
  13. dma_cli-0.1.2/tests/test_login.py +55 -0
  14. {dma_cli-0.1.0 → dma_cli-0.1.2}/batch.example.json +0 -0
  15. {dma_cli-0.1.0 → dma_cli-0.1.2}/dma_cli/__main__.py +0 -0
  16. {dma_cli-0.1.0 → dma_cli-0.1.2}/dma_cli/calc.py +0 -0
  17. {dma_cli-0.1.0 → dma_cli-0.1.2}/dma_cli/cli.py +0 -0
  18. {dma_cli-0.1.0 → dma_cli-0.1.2}/dma_cli/state.py +0 -0
  19. {dma_cli-0.1.0 → dma_cli-0.1.2}/dma_cli.egg-info/dependency_links.txt +0 -0
  20. {dma_cli-0.1.0 → dma_cli-0.1.2}/dma_cli.egg-info/entry_points.txt +0 -0
  21. {dma_cli-0.1.0 → dma_cli-0.1.2}/dma_cli.egg-info/requires.txt +0 -0
  22. {dma_cli-0.1.0 → dma_cli-0.1.2}/dma_cli.egg-info/top_level.txt +0 -0
  23. {dma_cli-0.1.0 → dma_cli-0.1.2}/input/calc.py +0 -0
  24. {dma_cli-0.1.0 → dma_cli-0.1.2}/input/case1/calc.py +0 -0
  25. {dma_cli-0.1.0 → dma_cli-0.1.2}/input/case2/calc.py +0 -0
  26. {dma_cli-0.1.0 → dma_cli-0.1.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dma-cli
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: DMA 任务提交命令行工具
5
5
  Requires-Python: >=3.8
6
6
  Description-Content-Type: text/markdown
@@ -9,11 +9,7 @@ Requires-Dist: pyyaml
9
9
 
10
10
  # DMA CLI —— 任务提交工具
11
11
 
12
- 深度挖掘(DMA,`https://szc.test.dp.tech`)网页外的命令行提交工具,用于提交单个或批量任务。
13
- 与压测无关,只借用压测脚本里已验证的接口形态,不改动任何压测内容。
14
-
15
- > **术语**:本工具的 `image`(镜像)即网页上的「软件目录」,底层接口为 `/softwares/deep-mining`,
16
- > snapshot 用 `softwareId`/`softwareVersionId`。容器镜像接口(image/public|private)不参与 job 提交,但 `image list` 合并展示供参考。
12
+ 深度挖掘算法(DMA)网页外的命令行提交工具,用于提交单个或批量任务。
17
13
 
18
14
  ## 安装
19
15
 
@@ -22,14 +18,6 @@ Requires-Dist: pyyaml
22
18
  pip install dma-cli
23
19
  dma --help
24
20
 
25
- # 从源码安装
26
- cd tools/dma-cli
27
- pip install .
28
-
29
- # 方式二:本地开发模式(可直接修改代码调试)
30
- cd tools/dma-cli
31
- pip install -e .
32
-
33
21
  # 安装后 dma 命令即可全局使用
34
22
  dma --help
35
23
  ```
@@ -64,12 +52,11 @@ cp config.example.yaml config.yaml
64
52
  base_url: https://szc.test.dp.tech
65
53
  auth:
66
54
  email: "" # 验证码登录邮箱(留空则必须填 token)
67
- captcha: "952754" # 固定验证码(测试环境)
55
+ captcha: "" # 留空,执行 dma login 时交互输入验证码
68
56
  token: "" # 直接配 token(优先于 email 登录)
69
57
  uexid: "" # 留空从 JWT 自动解析为 fe_<userId>
70
58
  user_id: "" # 留空从 JWT 自动解析
71
59
  defaults:
72
- biz_platform: 2
73
60
  name_prefix: "cli-job"
74
61
  max_run_time: 15
75
62
  max_run_time_unit: d
@@ -108,6 +95,11 @@ dma login
108
95
  dma login --email you@example.com
109
96
  ```
110
97
 
98
+ `auth.captcha` 留空时,登录会提示 `请输入邮箱验证码:`,输入本次验证码后提交。
99
+ 验证码不会保存到配置文件或登录缓存。若配置中已填写 `auth.captcha`,则使用该值;
100
+ 已有配置中的旧验证码请清空,以启用交互输入。输入为空或取消输入时,不发送登录请求。
101
+ 此命令只校验输入的验证码,不会自动发送验证码邮件。
102
+
111
103
  解析 JWT 缓存 `token` / `user_id` / `uexid` 到 `~/.dma_cli/credentials.json`。也可跳过登录,直接在 `config.yaml` 填 `token`。
112
104
 
113
105
  ### whoami —— 查看当前状态
@@ -120,12 +112,19 @@ dma whoami
120
112
 
121
113
  ### project —— 项目管理
122
114
 
115
+ 先查询当前账号可访问的项目,根据项目名称找到自己的 `projectId`,再设为当前项目:
116
+
123
117
  ```bash
124
- dma project list # 列 projectId | name
125
- dma project use 3948 # 设为当前项目
126
- dma project current # 显示当前项目
118
+ dma project list # 列出 projectId | name
119
+ dma project use <你的项目ID> # 替换为上一步查到的 projectId
120
+ dma project current # 确认当前项目 ID 和名称
127
121
  ```
128
122
 
123
+ `<你的项目ID>` 是占位符,执行前请替换为列表中你要使用的数字 ID,不要输入尖括号。
124
+ 当前项目会保存在本机,后续提交任务时省略 `--project_id` 或设置为 `0` 即使用该项目;
125
+ JSON 配置中的 `"project_id": 0` 含义相同。`0` 表示使用当前项目,不是真实项目 ID,首次提交前需先完成项目选择。
126
+ 如需单次提交到其他项目,可传入 `--project_id <你的项目ID>`,不会改变已保存的当前项目。
127
+
129
128
  ### sku —— 机型查询
130
129
 
131
130
  ```bash
@@ -158,7 +157,7 @@ dma job submit -i job.example.json
158
157
  dma job submit \
159
158
  --machine_type c2_m2_cpu \
160
159
  --image_address ubuntu:22.04-py3.10-terminal-20260916 \
161
- --project_id 3283 \
160
+ --project_id 0 \
162
161
  --command "python calc.py" \
163
162
  --job_name my-job
164
163
  ```
@@ -175,7 +174,6 @@ dma job submit \
175
174
  | `--machine_type` | `-t` | 机器类型名(自动匹配) |
176
175
  | `--command` | `-c` | 执行命令 |
177
176
  | `--max_run_time` | — | 最大运行时间(分钟) |
178
- | `--nnode` | — | 节点数 |
179
177
  | `--count` | — | 批量提交任务数 |
180
178
  | `--concurrency` | — | 批量并发数 |
181
179
  | `--batch_file` | — | 批量任务 JSON 数组文件 |
@@ -191,7 +189,8 @@ dma job submit \
191
189
  dma job submit \
192
190
  --machine_type c2_m2_cpu \
193
191
  --image_address ubuntu:22.04-py3.10-terminal-20260916 \
194
- --count 10 \
192
+ --command "python calc.py" \
193
+ --count 5 \
195
194
  --concurrency 5
196
195
  ```
197
196
 
@@ -204,7 +203,7 @@ dma job submit \
204
203
  # {"job_name": "job-002", "machine_type": "c4_m8_cpu", ...}
205
204
  # ]
206
205
 
207
- dma job submit --batch_file batch.json --concurrency 3
206
+ dma job submit --batch_file batch.example.json --concurrency 3
208
207
  ```
209
208
 
210
209
  ### 方式三:--batch_input_dir(目录扫描)
@@ -212,9 +211,10 @@ dma job submit --batch_file batch.json --concurrency 3
212
211
  ```bash
213
212
  # 每个子目录作为一个任务的输入
214
213
  dma job submit \
215
- --batch_input_dir ./inputs \
214
+ --batch_input_dir ./input \
216
215
  --machine_type c2_m2_cpu \
217
216
  --image_address ubuntu:22.04-py3.10-terminal-20260916 \
217
+ --command "python calc.py" \
218
218
  --concurrency 5
219
219
  ```
220
220
 
@@ -228,9 +228,10 @@ cp config.example.yaml config.yaml
228
228
  # 2. 查看状态
229
229
  dma whoami
230
230
 
231
- # 3. 切换项目
231
+ # 3. 查询并选择自己的项目(将占位符替换为列表中的 projectId,不保留尖括号)
232
232
  dma project list
233
- dma project use 3948
233
+ dma project use <你的项目ID>
234
+ dma project current
234
235
 
235
236
  # 4. 查询机型和镜像
236
237
  dma sku list --kind cpu
@@ -240,30 +241,5 @@ dma image list
240
241
  dma job submit -i job.example.json
241
242
 
242
243
  # 6. 批量提交
243
- dma job submit --batch_file batch.json
244
- ```
245
-
246
- ## 文件结构
247
-
244
+ dma job submit --batch_file batch.example.json
248
245
  ```
249
- tools/dma-cli/
250
- ├── README.md # 本文档
251
- ├── pyproject.toml # 打包配置
252
- ├── requirements.txt # requests, pyyaml
253
- ├── config.example.yaml # 配置模板
254
- ├── job.example.json # 任务配置示例
255
- ├── batch.example.json # 批量任务示例
256
- └── dma_cli/ # Python 包目录
257
- ├── __init__.py
258
- ├── cli.py # 命令行入口
259
- ├── client.py # API 客户端
260
- ├── state.py # 状态管理
261
- ├── resources.py # 资源查询
262
- ├── submit.py # 任务提交
263
- └── calc.py # 默认任务脚本
264
- ```
265
-
266
- ## 不在范围
267
-
268
- - 不做多用户 / 不改压测脚本
269
- - 不做分布式压测
@@ -1,10 +1,6 @@
1
1
  # DMA CLI —— 任务提交工具
2
2
 
3
- 深度挖掘(DMA,`https://szc.test.dp.tech`)网页外的命令行提交工具,用于提交单个或批量任务。
4
- 与压测无关,只借用压测脚本里已验证的接口形态,不改动任何压测内容。
5
-
6
- > **术语**:本工具的 `image`(镜像)即网页上的「软件目录」,底层接口为 `/softwares/deep-mining`,
7
- > snapshot 用 `softwareId`/`softwareVersionId`。容器镜像接口(image/public|private)不参与 job 提交,但 `image list` 合并展示供参考。
3
+ 深度挖掘算法(DMA)网页外的命令行提交工具,用于提交单个或批量任务。
8
4
 
9
5
  ## 安装
10
6
 
@@ -13,14 +9,6 @@
13
9
  pip install dma-cli
14
10
  dma --help
15
11
 
16
- # 从源码安装
17
- cd tools/dma-cli
18
- pip install .
19
-
20
- # 方式二:本地开发模式(可直接修改代码调试)
21
- cd tools/dma-cli
22
- pip install -e .
23
-
24
12
  # 安装后 dma 命令即可全局使用
25
13
  dma --help
26
14
  ```
@@ -55,12 +43,11 @@ cp config.example.yaml config.yaml
55
43
  base_url: https://szc.test.dp.tech
56
44
  auth:
57
45
  email: "" # 验证码登录邮箱(留空则必须填 token)
58
- captcha: "952754" # 固定验证码(测试环境)
46
+ captcha: "" # 留空,执行 dma login 时交互输入验证码
59
47
  token: "" # 直接配 token(优先于 email 登录)
60
48
  uexid: "" # 留空从 JWT 自动解析为 fe_<userId>
61
49
  user_id: "" # 留空从 JWT 自动解析
62
50
  defaults:
63
- biz_platform: 2
64
51
  name_prefix: "cli-job"
65
52
  max_run_time: 15
66
53
  max_run_time_unit: d
@@ -99,6 +86,11 @@ dma login
99
86
  dma login --email you@example.com
100
87
  ```
101
88
 
89
+ `auth.captcha` 留空时,登录会提示 `请输入邮箱验证码:`,输入本次验证码后提交。
90
+ 验证码不会保存到配置文件或登录缓存。若配置中已填写 `auth.captcha`,则使用该值;
91
+ 已有配置中的旧验证码请清空,以启用交互输入。输入为空或取消输入时,不发送登录请求。
92
+ 此命令只校验输入的验证码,不会自动发送验证码邮件。
93
+
102
94
  解析 JWT 缓存 `token` / `user_id` / `uexid` 到 `~/.dma_cli/credentials.json`。也可跳过登录,直接在 `config.yaml` 填 `token`。
103
95
 
104
96
  ### whoami —— 查看当前状态
@@ -111,12 +103,19 @@ dma whoami
111
103
 
112
104
  ### project —— 项目管理
113
105
 
106
+ 先查询当前账号可访问的项目,根据项目名称找到自己的 `projectId`,再设为当前项目:
107
+
114
108
  ```bash
115
- dma project list # 列 projectId | name
116
- dma project use 3948 # 设为当前项目
117
- dma project current # 显示当前项目
109
+ dma project list # 列出 projectId | name
110
+ dma project use <你的项目ID> # 替换为上一步查到的 projectId
111
+ dma project current # 确认当前项目 ID 和名称
118
112
  ```
119
113
 
114
+ `<你的项目ID>` 是占位符,执行前请替换为列表中你要使用的数字 ID,不要输入尖括号。
115
+ 当前项目会保存在本机,后续提交任务时省略 `--project_id` 或设置为 `0` 即使用该项目;
116
+ JSON 配置中的 `"project_id": 0` 含义相同。`0` 表示使用当前项目,不是真实项目 ID,首次提交前需先完成项目选择。
117
+ 如需单次提交到其他项目,可传入 `--project_id <你的项目ID>`,不会改变已保存的当前项目。
118
+
120
119
  ### sku —— 机型查询
121
120
 
122
121
  ```bash
@@ -149,7 +148,7 @@ dma job submit -i job.example.json
149
148
  dma job submit \
150
149
  --machine_type c2_m2_cpu \
151
150
  --image_address ubuntu:22.04-py3.10-terminal-20260916 \
152
- --project_id 3283 \
151
+ --project_id 0 \
153
152
  --command "python calc.py" \
154
153
  --job_name my-job
155
154
  ```
@@ -166,7 +165,6 @@ dma job submit \
166
165
  | `--machine_type` | `-t` | 机器类型名(自动匹配) |
167
166
  | `--command` | `-c` | 执行命令 |
168
167
  | `--max_run_time` | — | 最大运行时间(分钟) |
169
- | `--nnode` | — | 节点数 |
170
168
  | `--count` | — | 批量提交任务数 |
171
169
  | `--concurrency` | — | 批量并发数 |
172
170
  | `--batch_file` | — | 批量任务 JSON 数组文件 |
@@ -182,7 +180,8 @@ dma job submit \
182
180
  dma job submit \
183
181
  --machine_type c2_m2_cpu \
184
182
  --image_address ubuntu:22.04-py3.10-terminal-20260916 \
185
- --count 10 \
183
+ --command "python calc.py" \
184
+ --count 5 \
186
185
  --concurrency 5
187
186
  ```
188
187
 
@@ -195,7 +194,7 @@ dma job submit \
195
194
  # {"job_name": "job-002", "machine_type": "c4_m8_cpu", ...}
196
195
  # ]
197
196
 
198
- dma job submit --batch_file batch.json --concurrency 3
197
+ dma job submit --batch_file batch.example.json --concurrency 3
199
198
  ```
200
199
 
201
200
  ### 方式三:--batch_input_dir(目录扫描)
@@ -203,9 +202,10 @@ dma job submit --batch_file batch.json --concurrency 3
203
202
  ```bash
204
203
  # 每个子目录作为一个任务的输入
205
204
  dma job submit \
206
- --batch_input_dir ./inputs \
205
+ --batch_input_dir ./input \
207
206
  --machine_type c2_m2_cpu \
208
207
  --image_address ubuntu:22.04-py3.10-terminal-20260916 \
208
+ --command "python calc.py" \
209
209
  --concurrency 5
210
210
  ```
211
211
 
@@ -219,9 +219,10 @@ cp config.example.yaml config.yaml
219
219
  # 2. 查看状态
220
220
  dma whoami
221
221
 
222
- # 3. 切换项目
222
+ # 3. 查询并选择自己的项目(将占位符替换为列表中的 projectId,不保留尖括号)
223
223
  dma project list
224
- dma project use 3948
224
+ dma project use <你的项目ID>
225
+ dma project current
225
226
 
226
227
  # 4. 查询机型和镜像
227
228
  dma sku list --kind cpu
@@ -231,30 +232,5 @@ dma image list
231
232
  dma job submit -i job.example.json
232
233
 
233
234
  # 6. 批量提交
234
- dma job submit --batch_file batch.json
235
- ```
236
-
237
- ## 文件结构
238
-
235
+ dma job submit --batch_file batch.example.json
239
236
  ```
240
- tools/dma-cli/
241
- ├── README.md # 本文档
242
- ├── pyproject.toml # 打包配置
243
- ├── requirements.txt # requests, pyyaml
244
- ├── config.example.yaml # 配置模板
245
- ├── job.example.json # 任务配置示例
246
- ├── batch.example.json # 批量任务示例
247
- └── dma_cli/ # Python 包目录
248
- ├── __init__.py
249
- ├── cli.py # 命令行入口
250
- ├── client.py # API 客户端
251
- ├── state.py # 状态管理
252
- ├── resources.py # 资源查询
253
- ├── submit.py # 任务提交
254
- └── calc.py # 默认任务脚本
255
- ```
256
-
257
- ## 不在范围
258
-
259
- - 不做多用户 / 不改压测脚本
260
- - 不做分布式压测
@@ -4,13 +4,12 @@ base_url: https://szc.test.dp.tech
4
4
 
5
5
  auth:
6
6
  email: "" # 验证码登录邮箱(留空则必须填 token)
7
- captcha: "952754" # 固定验证码(测试环境)
7
+ captcha: "" # 留空,执行 dma login 时交互输入验证码
8
8
  token: "" # 直接配 token(优先于 email 登录,留空走 email)
9
9
  uexid: "" # 留空则从 JWT 自动解析为 fe_<userId>
10
10
  user_id: "" # 留空则从 JWT 自动解析
11
11
 
12
12
  defaults:
13
- biz_platform: 2
14
13
  name_prefix: "cli-job"
15
14
  cmd: "python calc.py"
16
15
  max_run_time: 15
@@ -1,3 +1,3 @@
1
1
  """DMA CLI —— 任务提交命令行工具"""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.1.2"
@@ -24,11 +24,12 @@ _PKG_DIR = Path(__file__).parent
24
24
  CONFIG_FILE = Path.cwd() / "config.yaml" if (Path.cwd() / "config.yaml").exists() else _PKG_DIR / "config.yaml"
25
25
  EXAMPLE_CONFIG = _PKG_DIR / "config.example.yaml"
26
26
 
27
+ BIZ_PLATFORM = 2
28
+
27
29
  DEFAULT_CONFIG = {
28
30
  "base_url": "https://szc.test.dp.tech",
29
- "auth": {"email": "", "captcha": "952754", "token": "", "uexid": "", "user_id": ""},
31
+ "auth": {"email": "", "captcha": "", "token": "", "uexid": "", "user_id": ""},
30
32
  "defaults": {
31
- "biz_platform": 2,
32
33
  "name_prefix": "cli-job",
33
34
  "cmd": "python calc.py",
34
35
  "max_run_time": 15,
@@ -153,7 +154,14 @@ class DmaClient:
153
154
  email = email or self.config["auth"].get("email")
154
155
  if not email:
155
156
  raise RuntimeError("未配置 auth.email,无法验证码登录(请在 config.yaml 填写或改用 token)")
156
- captcha = self.config["auth"].get("captcha") or "952754"
157
+ captcha = str(self.config["auth"].get("captcha") or "").strip()
158
+ if not captcha:
159
+ try:
160
+ captcha = input("请输入邮箱验证码: ").strip()
161
+ except (EOFError, KeyboardInterrupt):
162
+ raise RuntimeError("已取消验证码输入,未发送登录请求") from None
163
+ if not captcha:
164
+ raise RuntimeError("验证码不能为空,请重新执行 dma login")
157
165
  url = self.base_url + LOGIN_PATH
158
166
  payload = {
159
167
  "channel": "gkx-talent-universe",
@@ -12,14 +12,10 @@ import sys
12
12
 
13
13
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
14
14
 
15
- from .client import STATUS_MAP
15
+ from .client import BIZ_PLATFORM, STATUS_MAP
16
16
  from .state import set_current
17
17
 
18
18
 
19
- def _biz_platform(client) -> int:
20
- return int(client.defaults.get("biz_platform", 2))
21
-
22
-
23
19
  def _print_table(headers, rows) -> None:
24
20
  """简易对齐表格打印。"""
25
21
  if not rows:
@@ -38,7 +34,7 @@ def _print_table(headers, rows) -> None:
38
34
 
39
35
  # ============ project ============
40
36
  def project_list(client) -> list:
41
- params = {"queryType": "all", "pageSize": 1000, "bizPlatform": _biz_platform(client), "page": 1}
37
+ params = {"queryType": "all", "pageSize": 1000, "bizPlatform": BIZ_PLATFORM, "page": 1}
42
38
  resp = client.get("/dmaapi/szc/v2/project/list", params=params)
43
39
  data = (resp or {}).get("data") or {}
44
40
  return data.get("items") or data.get("list") or []
@@ -123,7 +119,7 @@ def handle_sku_use(client, args) -> None:
123
119
 
124
120
  # ============ image (软件目录) ============
125
121
  def software_list(client) -> list:
126
- resp = client.get("/dmaapi/szc/v1/softwares/deep-mining", params={"bizPlatform": _biz_platform(client)})
122
+ resp = client.get("/dmaapi/szc/v1/softwares/deep-mining", params={"bizPlatform": BIZ_PLATFORM})
127
123
  return ((resp or {}).get("data") or {}).get("items") or []
128
124
 
129
125
 
@@ -155,7 +151,7 @@ def handle_image_list(client, args) -> None:
155
151
  all_images = []
156
152
 
157
153
  # 1. 查询软件目录(/softwares/deep-mining)
158
- resp = client.get("/dmaapi/szc/v1/softwares/deep-mining", params={"bizPlatform": 2})
154
+ resp = client.get("/dmaapi/szc/v1/softwares/deep-mining", params={"bizPlatform": BIZ_PLATFORM})
159
155
  if resp.get("code") == 0:
160
156
  for item in resp.get("data", {}).get("items", []):
161
157
  for version in item.get("versions", []):
@@ -170,7 +166,7 @@ def handle_image_list(client, args) -> None:
170
166
 
171
167
  # 2. 查询公共镜像(/image/public)
172
168
  resp = client.get("/dmaapi/szc/v1/image/public/deep-mining",
173
- params={"page": 1, "pageSize": 1000, "bizPlatform": 2})
169
+ params={"page": 1, "pageSize": 1000, "bizPlatform": BIZ_PLATFORM})
174
170
  if resp.get("code") == 0:
175
171
  for item in resp.get("data", {}).get("items", []):
176
172
  # url 字段可能是 img
@@ -184,7 +180,7 @@ def handle_image_list(client, args) -> None:
184
180
 
185
181
  # 3. 查询私有镜像(/image/private)
186
182
  resp = client.get("/dmaapi/szc/v1/image/private",
187
- params={"type": "image", "device": "container", "page": 1, "pageSize": 50, "bizPlatform": 2})
183
+ params={"type": "image", "device": "container", "page": 1, "pageSize": 50, "bizPlatform": BIZ_PLATFORM})
188
184
  if resp.get("code") == 0:
189
185
  for item in resp.get("data", {}).get("items", []):
190
186
  url = item.get("url") or ""
@@ -24,7 +24,7 @@ import requests
24
24
 
25
25
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
26
26
 
27
- from .client import DmaClient, STATUS_MAP
27
+ from .client import BIZ_PLATFORM, DmaClient, STATUS_MAP
28
28
 
29
29
  # 内置默认任务脚本(无 --input_directory 时上传它)
30
30
  DEFAULT_INPUT = Path(__file__).parent / "calc.py"
@@ -57,7 +57,7 @@ def find_software_by_image_address(client: DmaClient, image_address: str) -> dic
57
57
  2. softwareVersion 字段精确匹配
58
58
  3. softwareName 模糊匹配
59
59
  """
60
- resp = client.get("/dmaapi/szc/v1/softwares/deep-mining", params={"bizPlatform": 2})
60
+ resp = client.get("/dmaapi/szc/v1/softwares/deep-mining", params={"bizPlatform": BIZ_PLATFORM})
61
61
  if resp.get("code") != 0:
62
62
  raise RuntimeError(f"查询软件目录失败: code={resp.get('code')}")
63
63
 
@@ -407,7 +407,7 @@ def handle_submit(client, args):
407
407
  json_config = json_config["_batch_data"][0] if json_config["_batch_data"] else {}
408
408
 
409
409
  # 映射 JSON 字段到 overrides
410
- if json_config.get("project_id"):
410
+ if json_config.get("project_id") is not None and json_config.get("project_id") != 0:
411
411
  overrides["project_id"] = json_config["project_id"]
412
412
  if json_config.get("job_name"):
413
413
  args.job_name = json_config["job_name"]
@@ -435,7 +435,7 @@ def handle_submit(client, args):
435
435
  overrides["dataset_path"] = json_config["dataset_path"]
436
436
 
437
437
  # CLI 参数覆盖 JSON 配置
438
- if args.project_id:
438
+ if args.project_id is not None and args.project_id != 0:
439
439
  overrides["project_id"] = args.project_id
440
440
  if args.machine_type:
441
441
  overrides["machine_type"] = args.machine_type
@@ -542,8 +542,12 @@ def handle_submit(client, args):
542
542
  for key in ["project_id", "job_name", "machine_type", "image_address", "command",
543
543
  "input_directory", "max_run_time", "max_reschedule_times", "nnode",
544
544
  "log_file", "out_files", "result_path", "dataset_path"]:
545
- if config.get(key):
546
- item_overrides[key] = config[key]
545
+ val = config.get(key)
546
+ if val is not None:
547
+ # 如果 project_id 显式为 0,则应使用默认当前项目
548
+ if key == "project_id" and val == 0:
549
+ continue
550
+ item_overrides[key] = val
547
551
 
548
552
  # 自动查找
549
553
  if item_overrides.get("machine_type"):
@@ -670,4 +674,4 @@ def handle_submit(client, args):
670
674
  print("📊 轮询任务状态...")
671
675
  poll_task_status(client, result['job_id'])
672
676
  else:
673
- print(f"❌ 提交失败: {result['error']}")
677
+ print(f"❌ 提交失败: {result['error']}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dma-cli
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: DMA 任务提交命令行工具
5
5
  Requires-Python: >=3.8
6
6
  Description-Content-Type: text/markdown
@@ -9,11 +9,7 @@ Requires-Dist: pyyaml
9
9
 
10
10
  # DMA CLI —— 任务提交工具
11
11
 
12
- 深度挖掘(DMA,`https://szc.test.dp.tech`)网页外的命令行提交工具,用于提交单个或批量任务。
13
- 与压测无关,只借用压测脚本里已验证的接口形态,不改动任何压测内容。
14
-
15
- > **术语**:本工具的 `image`(镜像)即网页上的「软件目录」,底层接口为 `/softwares/deep-mining`,
16
- > snapshot 用 `softwareId`/`softwareVersionId`。容器镜像接口(image/public|private)不参与 job 提交,但 `image list` 合并展示供参考。
12
+ 深度挖掘算法(DMA)网页外的命令行提交工具,用于提交单个或批量任务。
17
13
 
18
14
  ## 安装
19
15
 
@@ -22,14 +18,6 @@ Requires-Dist: pyyaml
22
18
  pip install dma-cli
23
19
  dma --help
24
20
 
25
- # 从源码安装
26
- cd tools/dma-cli
27
- pip install .
28
-
29
- # 方式二:本地开发模式(可直接修改代码调试)
30
- cd tools/dma-cli
31
- pip install -e .
32
-
33
21
  # 安装后 dma 命令即可全局使用
34
22
  dma --help
35
23
  ```
@@ -64,12 +52,11 @@ cp config.example.yaml config.yaml
64
52
  base_url: https://szc.test.dp.tech
65
53
  auth:
66
54
  email: "" # 验证码登录邮箱(留空则必须填 token)
67
- captcha: "952754" # 固定验证码(测试环境)
55
+ captcha: "" # 留空,执行 dma login 时交互输入验证码
68
56
  token: "" # 直接配 token(优先于 email 登录)
69
57
  uexid: "" # 留空从 JWT 自动解析为 fe_<userId>
70
58
  user_id: "" # 留空从 JWT 自动解析
71
59
  defaults:
72
- biz_platform: 2
73
60
  name_prefix: "cli-job"
74
61
  max_run_time: 15
75
62
  max_run_time_unit: d
@@ -108,6 +95,11 @@ dma login
108
95
  dma login --email you@example.com
109
96
  ```
110
97
 
98
+ `auth.captcha` 留空时,登录会提示 `请输入邮箱验证码:`,输入本次验证码后提交。
99
+ 验证码不会保存到配置文件或登录缓存。若配置中已填写 `auth.captcha`,则使用该值;
100
+ 已有配置中的旧验证码请清空,以启用交互输入。输入为空或取消输入时,不发送登录请求。
101
+ 此命令只校验输入的验证码,不会自动发送验证码邮件。
102
+
111
103
  解析 JWT 缓存 `token` / `user_id` / `uexid` 到 `~/.dma_cli/credentials.json`。也可跳过登录,直接在 `config.yaml` 填 `token`。
112
104
 
113
105
  ### whoami —— 查看当前状态
@@ -120,12 +112,19 @@ dma whoami
120
112
 
121
113
  ### project —— 项目管理
122
114
 
115
+ 先查询当前账号可访问的项目,根据项目名称找到自己的 `projectId`,再设为当前项目:
116
+
123
117
  ```bash
124
- dma project list # 列 projectId | name
125
- dma project use 3948 # 设为当前项目
126
- dma project current # 显示当前项目
118
+ dma project list # 列出 projectId | name
119
+ dma project use <你的项目ID> # 替换为上一步查到的 projectId
120
+ dma project current # 确认当前项目 ID 和名称
127
121
  ```
128
122
 
123
+ `<你的项目ID>` 是占位符,执行前请替换为列表中你要使用的数字 ID,不要输入尖括号。
124
+ 当前项目会保存在本机,后续提交任务时省略 `--project_id` 或设置为 `0` 即使用该项目;
125
+ JSON 配置中的 `"project_id": 0` 含义相同。`0` 表示使用当前项目,不是真实项目 ID,首次提交前需先完成项目选择。
126
+ 如需单次提交到其他项目,可传入 `--project_id <你的项目ID>`,不会改变已保存的当前项目。
127
+
129
128
  ### sku —— 机型查询
130
129
 
131
130
  ```bash
@@ -158,7 +157,7 @@ dma job submit -i job.example.json
158
157
  dma job submit \
159
158
  --machine_type c2_m2_cpu \
160
159
  --image_address ubuntu:22.04-py3.10-terminal-20260916 \
161
- --project_id 3283 \
160
+ --project_id 0 \
162
161
  --command "python calc.py" \
163
162
  --job_name my-job
164
163
  ```
@@ -175,7 +174,6 @@ dma job submit \
175
174
  | `--machine_type` | `-t` | 机器类型名(自动匹配) |
176
175
  | `--command` | `-c` | 执行命令 |
177
176
  | `--max_run_time` | — | 最大运行时间(分钟) |
178
- | `--nnode` | — | 节点数 |
179
177
  | `--count` | — | 批量提交任务数 |
180
178
  | `--concurrency` | — | 批量并发数 |
181
179
  | `--batch_file` | — | 批量任务 JSON 数组文件 |
@@ -191,7 +189,8 @@ dma job submit \
191
189
  dma job submit \
192
190
  --machine_type c2_m2_cpu \
193
191
  --image_address ubuntu:22.04-py3.10-terminal-20260916 \
194
- --count 10 \
192
+ --command "python calc.py" \
193
+ --count 5 \
195
194
  --concurrency 5
196
195
  ```
197
196
 
@@ -204,7 +203,7 @@ dma job submit \
204
203
  # {"job_name": "job-002", "machine_type": "c4_m8_cpu", ...}
205
204
  # ]
206
205
 
207
- dma job submit --batch_file batch.json --concurrency 3
206
+ dma job submit --batch_file batch.example.json --concurrency 3
208
207
  ```
209
208
 
210
209
  ### 方式三:--batch_input_dir(目录扫描)
@@ -212,9 +211,10 @@ dma job submit --batch_file batch.json --concurrency 3
212
211
  ```bash
213
212
  # 每个子目录作为一个任务的输入
214
213
  dma job submit \
215
- --batch_input_dir ./inputs \
214
+ --batch_input_dir ./input \
216
215
  --machine_type c2_m2_cpu \
217
216
  --image_address ubuntu:22.04-py3.10-terminal-20260916 \
217
+ --command "python calc.py" \
218
218
  --concurrency 5
219
219
  ```
220
220
 
@@ -228,9 +228,10 @@ cp config.example.yaml config.yaml
228
228
  # 2. 查看状态
229
229
  dma whoami
230
230
 
231
- # 3. 切换项目
231
+ # 3. 查询并选择自己的项目(将占位符替换为列表中的 projectId,不保留尖括号)
232
232
  dma project list
233
- dma project use 3948
233
+ dma project use <你的项目ID>
234
+ dma project current
234
235
 
235
236
  # 4. 查询机型和镜像
236
237
  dma sku list --kind cpu
@@ -240,30 +241,5 @@ dma image list
240
241
  dma job submit -i job.example.json
241
242
 
242
243
  # 6. 批量提交
243
- dma job submit --batch_file batch.json
244
- ```
245
-
246
- ## 文件结构
247
-
244
+ dma job submit --batch_file batch.example.json
248
245
  ```
249
- tools/dma-cli/
250
- ├── README.md # 本文档
251
- ├── pyproject.toml # 打包配置
252
- ├── requirements.txt # requests, pyyaml
253
- ├── config.example.yaml # 配置模板
254
- ├── job.example.json # 任务配置示例
255
- ├── batch.example.json # 批量任务示例
256
- └── dma_cli/ # Python 包目录
257
- ├── __init__.py
258
- ├── cli.py # 命令行入口
259
- ├── client.py # API 客户端
260
- ├── state.py # 状态管理
261
- ├── resources.py # 资源查询
262
- ├── submit.py # 任务提交
263
- └── calc.py # 默认任务脚本
264
- ```
265
-
266
- ## 不在范围
267
-
268
- - 不做多用户 / 不改压测脚本
269
- - 不做分布式压测
@@ -19,4 +19,6 @@ dma_cli.egg-info/requires.txt
19
19
  dma_cli.egg-info/top_level.txt
20
20
  input/calc.py
21
21
  input/case1/calc.py
22
- input/case2/calc.py
22
+ input/case2/calc.py
23
+ tests/test_biz_platform.py
24
+ tests/test_login.py
@@ -1,5 +1,5 @@
1
1
  {
2
- "project_id": 3948,
2
+ "project_id": 0,
3
3
  "job_name": "cli-job-001",
4
4
  "image_address": "ubuntu:22.04-py3.10-terminal-20260916",
5
5
  "machine_type": "c2_m2_cpu",
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dma-cli"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "DMA 任务提交命令行工具"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -0,0 +1,27 @@
1
+ import unittest
2
+ from unittest.mock import Mock
3
+
4
+ from dma_cli.resources import project_list, software_list
5
+ from dma_cli.submit import find_software_by_image_address
6
+
7
+
8
+ class BizPlatformTests(unittest.TestCase):
9
+ def test_requests_ignore_legacy_platform_config(self):
10
+ for operation, args in (
11
+ (project_list, ()),
12
+ (software_list, ()),
13
+ (find_software_by_image_address, ("test-image",)),
14
+ ):
15
+ with self.subTest(operation=operation.__name__):
16
+ client = Mock()
17
+ client.defaults = {"biz_platform": 99}
18
+ client.get.return_value = {"code": 0, "data": {"items": []}}
19
+ try:
20
+ operation(client, *args)
21
+ except RuntimeError as error:
22
+ self.assertIn("未找到镜像", str(error))
23
+ self.assertEqual(client.get.call_args.kwargs["params"]["bizPlatform"], 2)
24
+
25
+
26
+ if __name__ == "__main__":
27
+ unittest.main()
@@ -0,0 +1,55 @@
1
+ import unittest
2
+ from unittest.mock import Mock, patch
3
+
4
+ from dma_cli.client import DmaClient
5
+
6
+
7
+ class LoginCaptchaTests(unittest.TestCase):
8
+ def setUp(self):
9
+ save = patch("dma_cli.client.save_creds")
10
+ save.start()
11
+ self.addCleanup(save.stop)
12
+ self.client = DmaClient.__new__(DmaClient)
13
+ self.client.config = {
14
+ "base_url": "https://example.invalid",
15
+ "auth": {"email": "user@example.invalid", "captcha": ""},
16
+ }
17
+ self.client._http = Mock()
18
+ self.client._http.post.return_value.json.return_value = {
19
+ "code": 0, "data": {"token": "test-token"}
20
+ }
21
+
22
+ def test_prompts_for_missing_captcha_and_sends_input(self):
23
+ with patch("builtins.input", return_value=" 123456 ") as prompt, \
24
+ patch("dma_cli.client.save_creds"):
25
+ self.client.login()
26
+ prompt.assert_called_once()
27
+ payload = self.client._http.post.call_args.kwargs["json"]
28
+ self.assertEqual(payload["emailCode"], "123456")
29
+ self.assertEqual(payload["code"], "123456")
30
+ self.assertEqual(self.client.config["auth"]["captcha"], "")
31
+ self.assertNotIn("captcha", self.client.creds)
32
+
33
+ def test_configured_captcha_does_not_prompt(self):
34
+ self.client.config["auth"]["captcha"] = "654321"
35
+ with patch("builtins.input") as prompt, patch("dma_cli.client.save_creds"):
36
+ self.client.login()
37
+ prompt.assert_not_called()
38
+ self.assertEqual(self.client._http.post.call_args.kwargs["json"]["code"], "654321")
39
+
40
+ def test_empty_input_does_not_send_login_request(self):
41
+ with patch("builtins.input", return_value=" "):
42
+ with self.assertRaisesRegex(RuntimeError, "验证码不能为空"):
43
+ self.client.login()
44
+ self.client._http.post.assert_not_called()
45
+
46
+ def test_cancelled_input_does_not_send_login_request(self):
47
+ for error in (EOFError, KeyboardInterrupt):
48
+ with self.subTest(error=error), patch("builtins.input", side_effect=error):
49
+ with self.assertRaisesRegex(RuntimeError, "取消验证码输入"):
50
+ self.client.login()
51
+ self.client._http.post.assert_not_called()
52
+
53
+
54
+ if __name__ == "__main__":
55
+ unittest.main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes