dma-cli 0.1.0__py3-none-any.whl
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.
- dma_cli/__init__.py +3 -0
- dma_cli/__main__.py +6 -0
- dma_cli/calc.py +42 -0
- dma_cli/cli.py +148 -0
- dma_cli/client.py +245 -0
- dma_cli/resources.py +251 -0
- dma_cli/state.py +65 -0
- dma_cli/submit.py +673 -0
- dma_cli-0.1.0.data/data/share/dma-cli/batch.example.json +16 -0
- dma_cli-0.1.0.data/data/share/dma-cli/config.example.yaml +18 -0
- dma_cli-0.1.0.data/data/share/dma-cli/input/calc.py +42 -0
- dma_cli-0.1.0.data/data/share/dma-cli/input/case1/calc.py +42 -0
- dma_cli-0.1.0.data/data/share/dma-cli/input/case2/calc.py +42 -0
- dma_cli-0.1.0.data/data/share/dma-cli/job.example.json +13 -0
- dma_cli-0.1.0.dist-info/METADATA +269 -0
- dma_cli-0.1.0.dist-info/RECORD +19 -0
- dma_cli-0.1.0.dist-info/WHEEL +5 -0
- dma_cli-0.1.0.dist-info/entry_points.txt +2 -0
- dma_cli-0.1.0.dist-info/top_level.txt +1 -0
dma_cli/resources.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""查询 + 切换:project / sku / image(软件) / jobs / job rm。
|
|
2
|
+
|
|
3
|
+
底层接口(HAR/jmx 实测,仅引用接口形态):
|
|
4
|
+
- 项目: GET /dmaapi/szc/v2/project/list?queryType=all&pageSize=1000&bizPlatform=2&page=1
|
|
5
|
+
- 机型: GET /dmaapi/szc/v1/softwares/skus?kind=cpu|gpu&create_source=2
|
|
6
|
+
- 软件: GET /dmaapi/szc/v1/softwares/deep-mining?bizPlatform=2 (image==软件目录)
|
|
7
|
+
- 任务: GET /irpapi/szc/v1/job/list?page=&queryType=all&version=v2&pageSize=&status=&createSource=2[&projectId=]
|
|
8
|
+
- 删除: POST /irpapi/szc/v1/job/del/<bohrId>
|
|
9
|
+
"""
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
14
|
+
|
|
15
|
+
from .client import STATUS_MAP
|
|
16
|
+
from .state import set_current
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _biz_platform(client) -> int:
|
|
20
|
+
return int(client.defaults.get("biz_platform", 2))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _print_table(headers, rows) -> None:
|
|
24
|
+
"""简易对齐表格打印。"""
|
|
25
|
+
if not rows:
|
|
26
|
+
print(" (无数据)")
|
|
27
|
+
return
|
|
28
|
+
widths = [len(h) for h in headers]
|
|
29
|
+
for row in rows:
|
|
30
|
+
for i, cell in enumerate(row):
|
|
31
|
+
widths[i] = max(widths[i], len(str(cell)))
|
|
32
|
+
fmt = " ".join(f"{{:<{w}}}" for w in widths)
|
|
33
|
+
print(fmt.format(*headers))
|
|
34
|
+
print(fmt.format(*["-" * w for w in widths]))
|
|
35
|
+
for row in rows:
|
|
36
|
+
print(fmt.format(*[str(c) for c in row]))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ============ project ============
|
|
40
|
+
def project_list(client) -> list:
|
|
41
|
+
params = {"queryType": "all", "pageSize": 1000, "bizPlatform": _biz_platform(client), "page": 1}
|
|
42
|
+
resp = client.get("/dmaapi/szc/v2/project/list", params=params)
|
|
43
|
+
data = (resp or {}).get("data") or {}
|
|
44
|
+
return data.get("items") or data.get("list") or []
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def project_use(client, project_id) -> dict:
|
|
48
|
+
for it in project_list(client):
|
|
49
|
+
pid = it.get("id") or it.get("projectId")
|
|
50
|
+
if str(pid) == str(project_id):
|
|
51
|
+
value = {"projectId": int(pid), "name": it.get("name", "")}
|
|
52
|
+
set_current("project", value)
|
|
53
|
+
return value
|
|
54
|
+
raise RuntimeError(f"未找到 projectId={project_id}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def handle_project_list(client, args) -> None:
|
|
58
|
+
items = project_list(client)
|
|
59
|
+
rows = [[it.get("id") or it.get("projectId"), it.get("name", "")] for it in items]
|
|
60
|
+
print(f"共 {len(rows)} 个项目")
|
|
61
|
+
_print_table(["projectId", "name"], rows)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def handle_project_use(client, args) -> None:
|
|
65
|
+
value = project_use(client, args.project_id)
|
|
66
|
+
print(f"✅ 当前项目: {value['projectId']} | {value['name']}")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def handle_project_current(client, args) -> None:
|
|
70
|
+
p = client.current_project()
|
|
71
|
+
if p:
|
|
72
|
+
print(f"当前项目: {p['projectId']} | {p['name']}")
|
|
73
|
+
else:
|
|
74
|
+
print("未设置当前项目(用 `project use <projectId>` 设置)")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ============ sku ============
|
|
78
|
+
def sku_list(client, kind="cpu") -> list:
|
|
79
|
+
resp = client.get("/dmaapi/szc/v1/softwares/skus", params={"kind": kind, "create_source": 2})
|
|
80
|
+
return ((resp or {}).get("data") or {}).get("items") or []
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def sku_use(client, sku_id) -> dict:
|
|
84
|
+
found = None
|
|
85
|
+
for kind in ("cpu", "gpu"):
|
|
86
|
+
for it in sku_list(client, kind):
|
|
87
|
+
if str(it.get("skuId")) == str(sku_id):
|
|
88
|
+
found = it
|
|
89
|
+
break
|
|
90
|
+
if found:
|
|
91
|
+
break
|
|
92
|
+
if not found:
|
|
93
|
+
raise RuntimeError(f"未找到 skuId={sku_id}")
|
|
94
|
+
value = {
|
|
95
|
+
"skuId": int(found["skuId"]),
|
|
96
|
+
"skuName": found.get("skuName", ""),
|
|
97
|
+
"cpu": found.get("cpu"),
|
|
98
|
+
"memory": found.get("memory"),
|
|
99
|
+
"gpuModel": found.get("gpuModel", ""),
|
|
100
|
+
"gpuNum": found.get("gpuNum", 0),
|
|
101
|
+
}
|
|
102
|
+
set_current("sku", value)
|
|
103
|
+
return value
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def handle_sku_list(client, args) -> None:
|
|
107
|
+
kind = args.kind or "cpu"
|
|
108
|
+
items = sku_list(client, kind)
|
|
109
|
+
rows = [
|
|
110
|
+
[it.get("skuId"), it.get("skuName", ""), it.get("cpu"), it.get("memory"),
|
|
111
|
+
it.get("gpuModel", ""), it.get("gpuNum", 0), "是" if it.get("hasStock") else "否"]
|
|
112
|
+
for it in items
|
|
113
|
+
]
|
|
114
|
+
print(f"共 {len(rows)} 个机型 (kind={kind})")
|
|
115
|
+
_print_table(["skuId", "skuName", "cpu", "memory", "gpuModel", "gpuNum", "hasStock"], rows)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def handle_sku_use(client, args) -> None:
|
|
119
|
+
value = sku_use(client, args.sku_id)
|
|
120
|
+
print(f"✅ 当前机型: {value['skuId']} | {value['skuName']} "
|
|
121
|
+
f"(cpu={value['cpu']} memory={value['memory']} gpu={value['gpuModel']}x{value['gpuNum']})")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ============ image (软件目录) ============
|
|
125
|
+
def software_list(client) -> list:
|
|
126
|
+
resp = client.get("/dmaapi/szc/v1/softwares/deep-mining", params={"bizPlatform": _biz_platform(client)})
|
|
127
|
+
return ((resp or {}).get("data") or {}).get("items") or []
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def software_use(client, software_id, version_id=None) -> dict:
|
|
131
|
+
items = software_list(client)
|
|
132
|
+
sw = next((it for it in items if str(it.get("softwareId")) == str(software_id)), None)
|
|
133
|
+
if not sw:
|
|
134
|
+
raise RuntimeError(f"未找到 softwareId={software_id}")
|
|
135
|
+
versions = sw.get("versions") or []
|
|
136
|
+
if not versions:
|
|
137
|
+
raise RuntimeError(f"软件 {software_id} 无可用版本")
|
|
138
|
+
ver = None
|
|
139
|
+
if version_id is not None:
|
|
140
|
+
ver = next((v for v in versions if str(v.get("softwareVersionId")) == str(version_id)), None)
|
|
141
|
+
if ver is None:
|
|
142
|
+
ver = versions[0] # 不给 version 取第一个
|
|
143
|
+
value = {
|
|
144
|
+
"softwareId": int(sw["softwareId"]),
|
|
145
|
+
"softwareName": sw.get("softwareName", ""),
|
|
146
|
+
"softwareVersionId": int(ver["softwareVersionId"]),
|
|
147
|
+
"softwareVersion": ver.get("softwareVersion", ""),
|
|
148
|
+
}
|
|
149
|
+
set_current("software", value)
|
|
150
|
+
return value
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def handle_image_list(client, args) -> None:
|
|
154
|
+
"""列出镜像(公共+私有合并,含 url 列)。"""
|
|
155
|
+
all_images = []
|
|
156
|
+
|
|
157
|
+
# 1. 查询软件目录(/softwares/deep-mining)
|
|
158
|
+
resp = client.get("/dmaapi/szc/v1/softwares/deep-mining", params={"bizPlatform": 2})
|
|
159
|
+
if resp.get("code") == 0:
|
|
160
|
+
for item in resp.get("data", {}).get("items", []):
|
|
161
|
+
for version in item.get("versions", []):
|
|
162
|
+
url = version.get("url") or f"{item.get('softwareName')}:{version.get('softwareVersion')}"
|
|
163
|
+
all_images.append({
|
|
164
|
+
"id": item["softwareId"],
|
|
165
|
+
"name": item.get("softwareName", ""),
|
|
166
|
+
"type": "软件目录",
|
|
167
|
+
"url": url,
|
|
168
|
+
"version_id": version.get("softwareVersionId"),
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
# 2. 查询公共镜像(/image/public)
|
|
172
|
+
resp = client.get("/dmaapi/szc/v1/image/public/deep-mining",
|
|
173
|
+
params={"page": 1, "pageSize": 1000, "bizPlatform": 2})
|
|
174
|
+
if resp.get("code") == 0:
|
|
175
|
+
for item in resp.get("data", {}).get("items", []):
|
|
176
|
+
# url 字段可能是 img
|
|
177
|
+
url = item.get("img") or item.get("url") or ""
|
|
178
|
+
all_images.append({
|
|
179
|
+
"id": item.get("id"),
|
|
180
|
+
"name": item.get("name", ""),
|
|
181
|
+
"type": "公共镜像",
|
|
182
|
+
"url": url,
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
# 3. 查询私有镜像(/image/private)
|
|
186
|
+
resp = client.get("/dmaapi/szc/v1/image/private",
|
|
187
|
+
params={"type": "image", "device": "container", "page": 1, "pageSize": 50, "bizPlatform": 2})
|
|
188
|
+
if resp.get("code") == 0:
|
|
189
|
+
for item in resp.get("data", {}).get("items", []):
|
|
190
|
+
url = item.get("url") or ""
|
|
191
|
+
all_images.append({
|
|
192
|
+
"id": item.get("id"),
|
|
193
|
+
"name": item.get("name", ""),
|
|
194
|
+
"type": "私有镜像",
|
|
195
|
+
"url": url,
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
# 合并输出
|
|
199
|
+
print(f"共 {len(all_images)} 个镜像(软件目录 + 公共 + 私有)")
|
|
200
|
+
rows = [[img["id"], img["name"], img["type"], img["url"]] for img in all_images]
|
|
201
|
+
_print_table(["id", "name", "type", "url"], rows)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def handle_image_use(client, args) -> None:
|
|
205
|
+
value = software_use(client, args.software_id, args.version)
|
|
206
|
+
print(f"✅ 当前镜像(软件): {value['softwareId']} | {value['softwareName']} "
|
|
207
|
+
f"-> 版本 {value['softwareVersionId']} | {value['softwareVersion']}")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# ============ jobs ============
|
|
211
|
+
def jobs_list(client, status=-1, project_id=None, page_size=100) -> tuple:
|
|
212
|
+
all_items, page, total = [], 1, 0
|
|
213
|
+
while True:
|
|
214
|
+
params = {"page": page, "queryType": "all", "version": "v2",
|
|
215
|
+
"pageSize": page_size, "status": status, "createSource": 2}
|
|
216
|
+
if project_id:
|
|
217
|
+
params["projectId"] = project_id
|
|
218
|
+
resp = client.get("/irpapi/szc/v1/job/list", params=params)
|
|
219
|
+
inner = ((resp or {}).get("data") or {}) if isinstance(resp, dict) else {}
|
|
220
|
+
items = inner.get("items") or []
|
|
221
|
+
total = inner.get("total", 0)
|
|
222
|
+
all_items.extend(items)
|
|
223
|
+
if len(all_items) >= total or len(items) < page_size:
|
|
224
|
+
break
|
|
225
|
+
page += 1
|
|
226
|
+
return all_items, total
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def job_delete(client, bohr_id) -> dict:
|
|
230
|
+
return client.post_empty(f"/irpapi/szc/v1/job/del/{bohr_id}")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def handle_jobs(client, args) -> None:
|
|
234
|
+
items, total = jobs_list(client, status=args.status, project_id=args.project_id)
|
|
235
|
+
print(f"共 {total} 个任务(拉取 {len(items)} 个,显示前 50)")
|
|
236
|
+
rows = []
|
|
237
|
+
for j in items[:50]:
|
|
238
|
+
jid = j.get("bohrId") or j.get("id")
|
|
239
|
+
s = j.get("status", "?")
|
|
240
|
+
rows.append([jid, STATUS_MAP.get(s, f"?({s})"), j.get("jobName", ""), j.get("createTime", "")])
|
|
241
|
+
_print_table(["bohrId", "status", "jobName", "createTime"], rows)
|
|
242
|
+
if len(items) > 50:
|
|
243
|
+
print(f"... 还有 {len(items) - 50} 个")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def handle_job_rm(client, args) -> None:
|
|
247
|
+
resp = job_delete(client, args.bohr_id)
|
|
248
|
+
if resp.get("code") == 0:
|
|
249
|
+
print(f"✅ 已删除任务 {args.bohr_id}")
|
|
250
|
+
else:
|
|
251
|
+
print(f"❌ 删除失败: code={resp.get('code')} msg={resp.get('msg')}")
|
dma_cli/state.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""本地状态读写:~/.dma_cli/
|
|
2
|
+
|
|
3
|
+
- session.json : 当前选择(project / sku / software),由 `use` 命令写入,`submit` 读取
|
|
4
|
+
- credentials.json: 登录后的 token / user_id / uexid 缓存(不入库)
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
_SESSION_DIR = Path(os.path.expanduser("~")) / ".dma_cli"
|
|
11
|
+
SESSION_FILE = _SESSION_DIR / "session.json"
|
|
12
|
+
CRED_FILE = _SESSION_DIR / "credentials.json"
|
|
13
|
+
|
|
14
|
+
# session.json 的合法键
|
|
15
|
+
_SESSION_KEYS = ("project", "sku", "software")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _ensure_dir() -> None:
|
|
19
|
+
_SESSION_DIR.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def load_session() -> dict:
|
|
23
|
+
"""读取当前选择,缺键补 None。"""
|
|
24
|
+
try:
|
|
25
|
+
with open(SESSION_FILE, "r", encoding="utf-8") as f:
|
|
26
|
+
data = json.load(f)
|
|
27
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
28
|
+
data = {}
|
|
29
|
+
for k in _SESSION_KEYS:
|
|
30
|
+
data.setdefault(k, None)
|
|
31
|
+
return data
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def save_session(session: dict) -> None:
|
|
35
|
+
_ensure_dir()
|
|
36
|
+
with open(SESSION_FILE, "w", encoding="utf-8") as f:
|
|
37
|
+
json.dump(session, f, ensure_ascii=False, indent=2)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def set_current(kind: str, value: dict) -> dict:
|
|
41
|
+
"""设置某一项当前选择并落盘,返回更新后的完整 session。"""
|
|
42
|
+
if kind not in _SESSION_KEYS:
|
|
43
|
+
raise ValueError(f"未知的选择类型: {kind}(合法: {_SESSION_KEYS})")
|
|
44
|
+
session = load_session()
|
|
45
|
+
session[kind] = value
|
|
46
|
+
save_session(session)
|
|
47
|
+
return session
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def load_creds() -> dict:
|
|
51
|
+
try:
|
|
52
|
+
with open(CRED_FILE, "r", encoding="utf-8") as f:
|
|
53
|
+
return json.load(f)
|
|
54
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
55
|
+
return {}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def save_creds(creds: dict) -> None:
|
|
59
|
+
_ensure_dir()
|
|
60
|
+
with open(CRED_FILE, "w", encoding="utf-8") as f:
|
|
61
|
+
json.dump(creds, f, ensure_ascii=False, indent=2)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def creds_path() -> Path:
|
|
65
|
+
return CRED_FILE
|