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/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