sima-cli 0.0.36__py3-none-any.whl → 0.0.38__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.
sima_cli/__version__.py CHANGED
@@ -1,2 +1,2 @@
1
1
  # sima_cli/__version__.py
2
- __version__ = "0.0.36"
2
+ __version__ = "0.0.38"
sima_cli/app_zoo/app.py CHANGED
@@ -0,0 +1,435 @@
1
+ # model_zoo/models.py
2
+
3
+ import requests
4
+ import click
5
+ import os
6
+ import yaml
7
+ import zipfile
8
+ import shutil
9
+ from urllib.parse import urlparse
10
+ from rich import print
11
+ from rich.text import Text
12
+ from rich.table import Table
13
+ from rich.panel import Panel
14
+ from rich.console import Console
15
+ from sima_cli.utils.config import get_auth_token
16
+ from sima_cli.utils.config_loader import artifactory_url
17
+ from sima_cli.download import download_file_from_url
18
+
19
+ ARTIFACTORY_BASE_URL = artifactory_url() + '/artifactory'
20
+
21
+ def _is_valid_url(url: str) -> bool:
22
+ try:
23
+ result = urlparse(url)
24
+ return all([result.scheme, result.netloc])
25
+ except:
26
+ return False
27
+
28
+ def _describe_app_internal(ver: str, app_name: str):
29
+ repo = "vdp"
30
+ base_path = f"vdp-app-config-default/{ver}/{app_name}"
31
+ aql_query = f"""
32
+ items.find({{
33
+ "repo": "{repo}",
34
+ "path": "{base_path}",
35
+ "$or": [
36
+ {{ "name": {{ "$match": "*.yaml" }} }},
37
+ {{ "name": {{ "$match": "*.yml" }} }}
38
+ ],
39
+ "type": "file"
40
+ }}).include("repo","path","name")
41
+ """.strip()
42
+
43
+ headers = {
44
+ "Content-Type": "text/plain",
45
+ "Authorization": f"Bearer {get_auth_token(internal=True)}"
46
+ }
47
+ aql_url = f"{ARTIFACTORY_BASE_URL}/api/search/aql"
48
+ response = requests.post(aql_url, data=aql_query, headers=headers)
49
+ if response.status_code != 200:
50
+ click.echo(f"❌ Failed to list app files. Status: {response.status_code}")
51
+ click.echo(response.text)
52
+ return
53
+
54
+ results = response.json().get("results", [])
55
+ yaml_file = next((f for f in results if f["name"].endswith((".yaml", ".yml"))), None)
56
+
57
+ if not yaml_file:
58
+ click.echo(f"⚠️ No .yaml or .yml file found under: {base_path}")
59
+ return
60
+
61
+ # Download the YAML
62
+ yaml_url = f"{ARTIFACTORY_BASE_URL}/{repo}/{yaml_file['path']}/{yaml_file['name']}"
63
+ response = requests.get(yaml_url, headers={"Authorization": f"Bearer {get_auth_token(internal=True)}"})
64
+ if response.status_code != 200:
65
+ click.echo(f"❌ Failed to fetch YAML: {response.status_code}")
66
+ return
67
+
68
+ try:
69
+ data = yaml.safe_load(response.text)
70
+ except yaml.YAMLError as e:
71
+ click.echo(f"❌ Failed to parse YAML: {e}")
72
+ return
73
+
74
+ # ---- PIPELINE (new app YAML) ----
75
+ if "pipeline" in data:
76
+ pipeline = data.get("pipeline", {})
77
+
78
+ header = f"[bold green]{pipeline.get('name', app_name)}[/bold green] - {pipeline.get('category', 'Unknown')}"
79
+ desc = pipeline.get("short_description", "")
80
+
81
+ console = Console()
82
+ # Panel body holds description so it wraps
83
+ body = Text(desc, style="yellow", no_wrap=False)
84
+ panel_width = min(console.width, 100)
85
+
86
+ print(Panel(body, title=header, expand=False, width=80))
87
+
88
+ p_table = Table(title="Pipeline", header_style="bold magenta")
89
+ p_table.add_column("Field")
90
+ p_table.add_column("Value")
91
+ p_table.add_row("Input Format", pipeline.get("in_format", "-"))
92
+ p_table.add_row("Output Format", pipeline.get("out_format", "-"))
93
+
94
+ perf = pipeline.get("performance", {})
95
+ p_table.add_row("Davinci FPS", str(perf.get("davinci_fps", "-")))
96
+ p_table.add_row("Modalix FPS", str(perf.get("modalix_fps", "-")))
97
+
98
+ print(p_table)
99
+ # ---- MODEL(S) ----
100
+ if "model" in data:
101
+ models = data["model"]
102
+ if isinstance(models, dict):
103
+ models = [models]
104
+ elif not isinstance(models, list):
105
+ models = []
106
+
107
+ for idx, model in enumerate(models, start=1):
108
+ title = f"Model #{idx}" if len(models) > 1 else "Model"
109
+ m_table = Table(title=title, header_style="bold cyan")
110
+ m_table.add_column("Field")
111
+ m_table.add_column("Value")
112
+
113
+ m_table.add_row("Name", model.get("name", "-"))
114
+
115
+ if inp := model.get("input_description"):
116
+ m_table.add_row("Resolution", str(inp.get("resolution", "-")))
117
+ m_table.add_row("Format", inp.get("format", "-"))
118
+
119
+ if resize := model.get("resize_configuration"):
120
+ m_table.add_row("Resize Format", resize.get("input_image_format", "-"))
121
+ m_table.add_row("Input Shape", str(resize.get("input_shape", "-")))
122
+ m_table.add_row("Scaling Type", resize.get("scaling_type", "-"))
123
+ m_table.add_row("Padding Type", resize.get("padding_type", "-"))
124
+ m_table.add_row("Aspect Ratio", str(resize.get("aspect_ratio", "-")))
125
+
126
+ if norm := model.get("normalization_configuration"):
127
+ m_table.add_row("Channel Mean", str(norm.get("channel_mean", "-")))
128
+ m_table.add_row("Channel Stddev", str(norm.get("channel_stddev", "-")))
129
+ if "accuracy" in norm:
130
+ m_table.add_row("Accuracy", str(norm.get("accuracy", "-")))
131
+
132
+ # Legacy model-centric fields
133
+ if "dataset" in model:
134
+ ds = model["dataset"]
135
+ m_table.add_row("Dataset", ds.get("name", "-"))
136
+ for k, v in (ds.get("params") or {}).items():
137
+ m_table.add_row(k, str(v))
138
+ m_table.add_row("Accuracy", ds.get("accuracy", "-"))
139
+ m_table.add_row("Calibration", ds.get("calibration", "-"))
140
+
141
+ if "quantization_settings" in model:
142
+ q = model["quantization_settings"]
143
+ m_table.add_row("Calibration Samples", str(q.get("calibration_num_samples", "-")))
144
+ m_table.add_row("Calibration Method", q.get("calibration_method", "-"))
145
+ m_table.add_row("Requantization Mode", q.get("requantization_mode", "-"))
146
+ m_table.add_row("Bias Correction", str(q.get("bias_correction", "-")))
147
+
148
+ print(m_table)
149
+
150
+ # ---- PIPELINE TRANSFORMS (legacy YAML) ----
151
+ if "pipeline" in data and "transforms" in data["pipeline"]:
152
+ transforms = data["pipeline"]["transforms"]
153
+ if isinstance(transforms, list):
154
+ t_table = Table(title="Pipeline Transforms", header_style="bold green")
155
+ t_table.add_column("Name")
156
+ t_table.add_column("Params")
157
+
158
+ for step in transforms:
159
+ name = step.get("name")
160
+ params = step.get("params", {})
161
+ param_str = ", ".join(f"{k}={v}" for k, v in params.items()) if params else "-"
162
+ t_table.add_row(name, param_str)
163
+
164
+ print(t_table)
165
+
166
+ # If nothing useful parsed
167
+ if not any(k in data for k in ("pipeline", "model")):
168
+ click.echo("⚠️ YAML parsed, but no recognizable `pipeline` or `model` sections found.")
169
+
170
+ def _download_app_internal(ver: str, app_name: str):
171
+ """
172
+ Download a specific app (.zip file) from Artifactory App Zoo, unzip it,
173
+ flatten the extracted content into the app folder, and clean up.
174
+ """
175
+ repo = "vdp"
176
+ base_path = f"vdp-app-config-default/{ver}/{app_name}"
177
+ aql_query = f"""
178
+ items.find({{
179
+ "repo": "{repo}",
180
+ "path": "{base_path}",
181
+ "name": {{"$match": "*.zip"}},
182
+ "type": "file"
183
+ }}).include("repo","path","name")
184
+ """.strip()
185
+
186
+ aql_url = f"{ARTIFACTORY_BASE_URL}/api/search/aql"
187
+ headers = {
188
+ "Content-Type": "text/plain",
189
+ "Authorization": f"Bearer {get_auth_token(internal=True)}"
190
+ }
191
+
192
+ response = requests.post(aql_url, data=aql_query, headers=headers)
193
+ if response.status_code != 200:
194
+ click.echo(f"❌ Failed to list app files. Status: {response.status_code}, path: {aql_url}")
195
+ click.echo(response.text)
196
+ return None
197
+
198
+ results = response.json().get("results", [])
199
+ if not results:
200
+ click.echo(f"⚠️ No .zip file found for app: {app_name}")
201
+ return None
202
+
203
+ # Expect only one .zip file per app
204
+ file_info = results[0]
205
+ file_path = file_info["path"]
206
+ file_name = file_info["name"]
207
+ download_url = f"{ARTIFACTORY_BASE_URL}/{repo}/{file_path}/{file_name}"
208
+
209
+ dest_dir = os.path.join(os.getcwd(), app_name)
210
+ os.makedirs(dest_dir, exist_ok=True)
211
+
212
+ click.echo(f"⬇️ Downloading app '{app_name}' to '{dest_dir}'...")
213
+
214
+ try:
215
+ local_zip = download_file_from_url(download_url, dest_folder=dest_dir, internal=True)
216
+ click.echo(f"✅ {file_name} -> {local_zip}")
217
+ except Exception as e:
218
+ click.echo(f"❌ Failed to download {file_name}: {e}")
219
+ return None
220
+
221
+ # Unzip into the destination folder
222
+ try:
223
+ with zipfile.ZipFile(local_zip, "r") as zip_ref:
224
+ zip_ref.extractall(dest_dir)
225
+ click.echo(f"📦 Extracted {file_name} into {dest_dir}")
226
+ except Exception as e:
227
+ click.echo(f"❌ Failed to unzip {file_name}: {e}")
228
+ return None
229
+
230
+ # Move contents up if unzipped into a nested folder
231
+ extracted_root = os.path.join(dest_dir, file_name.replace(".zip", ""))
232
+ if os.path.isdir(extracted_root):
233
+ for item in os.listdir(extracted_root):
234
+ src = os.path.join(extracted_root, item)
235
+ dst = os.path.join(dest_dir, item)
236
+ if os.path.isdir(src):
237
+ if os.path.exists(dst):
238
+ shutil.rmtree(dst)
239
+ shutil.move(src, dst)
240
+ else:
241
+ shutil.move(src, dst)
242
+ shutil.rmtree(extracted_root)
243
+
244
+ # Remove the original zip file
245
+ try:
246
+ os.remove(local_zip)
247
+ except OSError:
248
+ pass
249
+
250
+ click.echo(f"✅ App '{app_name}' ready at {dest_dir}")
251
+ return dest_dir
252
+
253
+ def _list_available_app_versions_internal(match_keyword: str = None):
254
+ """
255
+ List all available App Zoo versions from Artifactory (vdp-app-config-default).
256
+ If match_keyword is provided, only versions containing the keyword (case-insensitive) are returned.
257
+ """
258
+ repo = "vdp"
259
+ base_path = "vdp-app-config-default"
260
+ aql_query = f"""
261
+ items.find({{
262
+ "repo": "{repo}",
263
+ "path": {{"$match": "{base_path}/*"}},
264
+ "type": "folder"
265
+ }}).include("repo","path","name")
266
+ """.strip()
267
+
268
+ aql_url = f"{ARTIFACTORY_BASE_URL}/api/search/aql"
269
+ headers = {
270
+ "Content-Type": "text/plain",
271
+ "Authorization": f"Bearer {get_auth_token(internal=True)}"
272
+ }
273
+
274
+ response = requests.post(aql_url, data=aql_query, headers=headers)
275
+
276
+ if response.status_code == 401:
277
+ print('❌ You are not authorized to access Artifactory. Use `sima-cli -i login` with your Artifactory identity token to authenticate, then try again.')
278
+ return []
279
+
280
+ if response.status_code != 200:
281
+ print(f"❌ Failed to retrieve app versions (status {response.status_code})")
282
+ print(response.text)
283
+ return []
284
+
285
+ results = response.json().get("results", [])
286
+
287
+ # Versions = folder names under vdp-app-config-default/
288
+ versions = sorted({
289
+ item["path"].replace(base_path + "/", "").split("/")[0]
290
+ for item in results
291
+ if item["path"].startswith(base_path + "/")
292
+ })
293
+
294
+ if match_keyword:
295
+ mk = match_keyword.lower()
296
+ versions = [v for v in versions if mk in v.lower()]
297
+
298
+ return versions
299
+
300
+
301
+ def _list_available_apps_internal(version: str):
302
+ """
303
+ List available app folders from Artifactory for the given SDK version.
304
+ Returns distinct top-level folders under vdp-app-config-default/{version}/.
305
+ After selecting an app, shows its description and action menu.
306
+ """
307
+ repo = "vdp"
308
+ base_prefix = f"vdp-app-config-default/{version}"
309
+ aql_query = f"""
310
+ items.find({{
311
+ "repo": "{repo}",
312
+ "path": {{"$match": "{base_prefix}/*"}}
313
+ }}).include("repo","path","name")
314
+ """.strip()
315
+
316
+ aql_url = f"{ARTIFACTORY_BASE_URL}/api/search/aql"
317
+ headers = {
318
+ "Content-Type": "text/plain",
319
+ "Authorization": f"Bearer {get_auth_token(internal=True)}"
320
+ }
321
+
322
+ response = requests.post(aql_url, data=aql_query, headers=headers)
323
+ if response.status_code != 200:
324
+ click.echo(f"❌ Failed to retrieve app list (status {response.status_code})")
325
+ click.echo(response.text)
326
+ return None
327
+
328
+ results = response.json().get("results", [])
329
+
330
+ # Collect only top-level folder names under {version}/
331
+ app_names = sorted({
332
+ item["path"].replace(base_prefix + "/", "").split("/")[0]
333
+ for item in results
334
+ if item["path"].startswith(base_prefix + "/")
335
+ })
336
+
337
+ if not app_names:
338
+ click.echo("⚠️ No apps found.")
339
+ return None
340
+
341
+ # Add Exit option at the bottom
342
+ app_names_with_exit = app_names + ["Exit"]
343
+
344
+ from InquirerPy import inquirer
345
+
346
+ while True:
347
+ # Step 1: Select app
348
+ selected_app = inquirer.fuzzy(
349
+ message=f"Select an app from version {version}:",
350
+ choices=app_names_with_exit,
351
+ max_height="70%",
352
+ instruction="(Use ↑↓ to navigate, / to search, Enter to select)"
353
+ ).execute()
354
+
355
+ if not selected_app or selected_app == "Exit":
356
+ click.echo("👋 Exiting.")
357
+ break
358
+
359
+ # Step 2: Describe the app
360
+ _describe_app_internal(version, selected_app)
361
+
362
+ # Step 3: Action menu
363
+ action = inquirer.select(
364
+ message=f"What would you like to do with {selected_app}?",
365
+ choices=["Download", "Back", "Exit"],
366
+ default="Back"
367
+ ).execute()
368
+
369
+ if action == "Download":
370
+ _download_app_internal(version, selected_app)
371
+ click.echo(f"✅ Downloaded {selected_app}")
372
+ # loop back to menu again after download
373
+ elif action == "Back":
374
+ # back to app list (continue loop)
375
+ continue
376
+ elif action == "Exit":
377
+ click.echo("👋 Exiting.")
378
+ break
379
+
380
+
381
+ def list_apps(internal, ver):
382
+ if internal:
383
+ click.echo("App Zoo Source : SiMa Artifactory...")
384
+ versions = _list_available_app_versions_internal(ver)
385
+
386
+ if len(versions) == 1:
387
+ # Exactly one match → go straight to listing apps
388
+ return _list_available_apps_internal(versions[0])
389
+ elif len(versions) == 0:
390
+ print(f'❌ No version match found in Artifactory for [{ver}]')
391
+ return []
392
+ else:
393
+ # Multiple matches → prompt user to choose
394
+ click.echo("Multiple app zoo versions found matching your input:")
395
+
396
+ from InquirerPy import inquirer
397
+ selected_version = inquirer.fuzzy(
398
+ message="Select a version:",
399
+ choices=versions,
400
+ max_height="70%", # scrollable
401
+ instruction="(Use ↑↓ to navigate, / to search, Enter to select)"
402
+ ).execute()
403
+
404
+ if not selected_version:
405
+ click.echo("No selection made. Exiting.", err=True)
406
+ raise SystemExit(1)
407
+
408
+ return _list_available_apps_internal(selected_version)
409
+
410
+ else:
411
+ print('External app zoo not supported yet')
412
+ return []
413
+
414
+ def download_app(internal, ver, model_name):
415
+ if internal:
416
+ click.echo("App Zoo Source : SiMa Artifactory...")
417
+ return _download_app_internal(ver, model_name)
418
+ else:
419
+ print('External app zoo not supported yet')
420
+
421
+ def describe_app(internal, ver, model_name):
422
+ if internal:
423
+ click.echo("App Zoo Source : SiMa Artifactory...")
424
+ return _describe_app_internal(ver, model_name)
425
+ else:
426
+ print('External app zoo not supported yet')
427
+
428
+ # Module CLI tests
429
+ if __name__ == "__main__":
430
+ import sys
431
+ if len(sys.argv) < 2:
432
+ print("Usage: python models.py <version>")
433
+ else:
434
+ version_arg = sys.argv[1]
435
+ _list_available_apps_internal(version_arg)
sima_cli/cli.py CHANGED
@@ -3,6 +3,7 @@ import click
3
3
  from sima_cli.utils.env import get_environment_type
4
4
  from sima_cli.update.updater import perform_update
5
5
  from sima_cli.model_zoo.model import list_models, download_model, describe_model
6
+ from sima_cli.app_zoo.app import list_apps, download_app, describe_app
6
7
  from sima_cli.utils.config_loader import internal_resource_exists
7
8
  from sima_cli.mla.meminfo import monitor_simaai_mem_chart
8
9
  from sima_cli.__version__ import __version__
@@ -167,12 +168,25 @@ def update(ctx, version_or_url, ip, yes, passwd, flavor):
167
168
  # Model Zoo Subcommands
168
169
  # ----------------------
169
170
  @main.group()
170
- @click.option('--ver', default="1.6.0", show_default=True, help="SDK version, minimum and default is 1.6.0")
171
+ @click.option(
172
+ "-v", "--ver", "--version",
173
+ "ver",
174
+ required=False,
175
+ help="SDK version (e.g. 1.7.0, 2.0.0). If not provided, you can select from available versions.",
176
+ )
177
+ @click.option(
178
+ "--boardtype",
179
+ type=click.Choice(["mlsoc", "modalix"], case_sensitive=False),
180
+ default='modalix',
181
+ required=False,
182
+ help="Target board type (mlsoc or modalix).",
183
+ )
171
184
  @click.pass_context
172
- def modelzoo(ctx, ver):
185
+ def modelzoo(ctx, ver, boardtype):
173
186
  """Access models from the Model Zoo."""
174
187
  ctx.ensure_object(dict)
175
188
  ctx.obj['ver'] = ver
189
+ ctx.obj["boardtype"] = boardtype
176
190
  internal = ctx.obj.get("internal", False)
177
191
  if not internal:
178
192
  click.echo(f"Public developer portal environment is not supported yet..")
@@ -186,8 +200,9 @@ def list_models_cmd(ctx):
186
200
  """List available models."""
187
201
  internal = ctx.obj.get("internal", False)
188
202
  version = ctx.obj.get("ver")
203
+ boardtype = ctx.obj.get('boardtype')
189
204
  click.echo(f"Listing models for version: {version}")
190
- list_models(internal, version)
205
+ list_models(internal, version, boardtype)
191
206
 
192
207
  @modelzoo.command("get")
193
208
  @click.argument('model_name')
@@ -209,6 +224,57 @@ def get_model(ctx, model_name):
209
224
  click.echo(f"Getting model '{model_name}' for version: {ver}")
210
225
  describe_model(internal, ver, model_name)
211
226
 
227
+ # ----------------------
228
+ # App Zoo Subcommands
229
+ # ----------------------
230
+ @main.group()
231
+ @click.option(
232
+ "-v", "--ver", "--version",
233
+ "ver",
234
+ required=False,
235
+ help="SDK version (e.g. 1.7.0, 2.0.0). If not provided, you can select from available versions.",
236
+ )
237
+ @click.pass_context
238
+ def appzoo(ctx, ver):
239
+ """Access models from the Model Zoo."""
240
+ ctx.ensure_object(dict)
241
+ ctx.obj['ver'] = ver
242
+ internal = ctx.obj.get("internal", False)
243
+ if not internal:
244
+ click.echo(f"Public developer portal environment is not supported yet..")
245
+ exit(0)
246
+
247
+ pass
248
+
249
+ @appzoo.command("list")
250
+ @click.pass_context
251
+ def list_apps_cmd(ctx):
252
+ """List available models."""
253
+ internal = ctx.obj.get("internal", False)
254
+ version = ctx.obj.get("ver")
255
+ click.echo(f"Listing apps for version: {version}")
256
+ list_apps(internal, version)
257
+
258
+ @appzoo.command("get")
259
+ @click.argument('app_name')
260
+ @click.pass_context
261
+ def get_app(ctx, app_name):
262
+ """Download a specific model."""
263
+ ver = ctx.obj.get("ver")
264
+ internal = ctx.obj.get("internal", False)
265
+ click.echo(f"Getting app '{app_name}' for version: {ver}")
266
+ download_app(internal, ver, app_name)
267
+
268
+ @appzoo.command("describe")
269
+ @click.argument('app_name')
270
+ @click.pass_context
271
+ def get_model(ctx, app_name):
272
+ """Download a specific model."""
273
+ ver = ctx.obj.get("ver")
274
+ internal = ctx.obj.get("internal", False)
275
+ click.echo(f"Getting model '{app_name}' for version: {ver}")
276
+ describe_app(internal, ver, app_name)
277
+
212
278
  # ----------------------
213
279
  # Authentication Command
214
280
  # ----------------------
@@ -269,7 +335,7 @@ def bootimg_cmd(ctx, version, boardtype, netboot, autoflash, fwtype, rootfs):
269
335
  try:
270
336
  boardtype = boardtype if boardtype != 'mlsoc' else 'davinci'
271
337
  if netboot or autoflash:
272
- setup_netboot(version, boardtype, internal, autoflash, flavor='headless', rootfs=rootfs)
338
+ setup_netboot(version, boardtype, internal, autoflash, flavor='headless', rootfs=rootfs, swtype=fwtype)
273
339
  click.echo("✅ Netboot image prepared and TFTP server is running.")
274
340
  else:
275
341
  write_image(version, boardtype, fwtype, internal, flavor='headless')