sima-cli 0.0.1__py3-none-any.whl → 0.0.11__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 +1 -1
- sima_cli/auth/login.py +103 -0
- sima_cli/cli.py +105 -42
- sima_cli/download/downloader.py +30 -19
- sima_cli/model_zoo/model.py +148 -0
- sima_cli/update/local.py +94 -0
- sima_cli/update/remote.py +238 -0
- sima_cli/update/updater.py +323 -39
- sima_cli/utils/artifactory.py +63 -0
- sima_cli/utils/config.py +25 -19
- sima_cli/utils/config_loader.py +30 -0
- sima_cli-0.0.11.dist-info/METADATA +182 -0
- {sima_cli-0.0.1.dist-info → sima_cli-0.0.11.dist-info}/RECORD +17 -13
- {sima_cli-0.0.1.dist-info → sima_cli-0.0.11.dist-info}/WHEEL +1 -1
- sima_cli-0.0.1.dist-info/METADATA +0 -112
- {sima_cli-0.0.1.dist-info → sima_cli-0.0.11.dist-info}/entry_points.txt +0 -0
- {sima_cli-0.0.1.dist-info → sima_cli-0.0.11.dist-info}/licenses/LICENSE +0 -0
- {sima_cli-0.0.1.dist-info → sima_cli-0.0.11.dist-info}/top_level.txt +0 -0
sima_cli/__version__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# sima_cli/__version__.py
|
2
|
-
__version__ = "0.0.
|
2
|
+
__version__ = "0.0.11"
|
sima_cli/auth/login.py
CHANGED
@@ -0,0 +1,103 @@
|
|
1
|
+
import click
|
2
|
+
import getpass
|
3
|
+
import requests
|
4
|
+
from sima_cli.utils.config import set_auth_token, get_auth_token
|
5
|
+
from sima_cli.utils.config_loader import load_resource_config
|
6
|
+
from sima_cli.utils.artifactory import exchange_identity_token, validate_token
|
7
|
+
|
8
|
+
def login(method: str = "external"):
|
9
|
+
"""
|
10
|
+
Dispatch login based on the specified method.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
method (str): 'external' (public developer portal) or 'internal' (Artifactory).
|
14
|
+
"""
|
15
|
+
if method == "internal":
|
16
|
+
return login_internal()
|
17
|
+
else:
|
18
|
+
return login_external()
|
19
|
+
|
20
|
+
def login_internal():
|
21
|
+
"""
|
22
|
+
Internal login using a manually provided identity token.
|
23
|
+
|
24
|
+
Flow:
|
25
|
+
1. Prompt for identity token.
|
26
|
+
2. Validate the token using the configured validation URL.
|
27
|
+
3. If valid, exchange it for a short-lived access token.
|
28
|
+
4. Save the short-lived token to local config.
|
29
|
+
"""
|
30
|
+
|
31
|
+
cfg = load_resource_config()
|
32
|
+
auth_cfg = cfg.get("internal", {}).get("auth", {})
|
33
|
+
base_url = cfg.get("internal", {}).get("artifactory", {}).get("url", {})
|
34
|
+
validate_url = f"{base_url}/{auth_cfg.get("validate_url")}"
|
35
|
+
exchange_url = f"{base_url}/{auth_cfg.get("internal_url")}"
|
36
|
+
|
37
|
+
# Check for required config values
|
38
|
+
if not validate_url or not exchange_url:
|
39
|
+
click.echo("❌ Missing 'validate_url' or 'internal_url' in internal auth config.")
|
40
|
+
click.echo("👉 Please check ~/.sima-cli/resources_internal.yaml")
|
41
|
+
return
|
42
|
+
|
43
|
+
# Prompt for identity token
|
44
|
+
click.echo("🔐 Paste your Artifactory identity token below.")
|
45
|
+
identity_token = click.prompt("Identity Token", hide_input=True)
|
46
|
+
|
47
|
+
if not identity_token or len(identity_token.strip()) < 10:
|
48
|
+
return click.echo("❌ Invalid or empty token.")
|
49
|
+
|
50
|
+
# Step 1: Validate the identity token
|
51
|
+
is_valid, username = validate_token(identity_token, validate_url)
|
52
|
+
if not is_valid:
|
53
|
+
return click.echo("❌ Token validation failed. Please check your identity token.")
|
54
|
+
|
55
|
+
click.echo(f"✅ Identity token is valid")
|
56
|
+
|
57
|
+
# Step 2: Exchange for a short-lived access token (default: 7 days)
|
58
|
+
access_token, user_name = exchange_identity_token(identity_token, exchange_url, expires_in=604800)
|
59
|
+
|
60
|
+
if not access_token:
|
61
|
+
return click.echo("❌ Failed to acquire short-lived access token.")
|
62
|
+
|
63
|
+
# Step 3: Save token to internal auth config
|
64
|
+
set_auth_token(access_token, internal=True)
|
65
|
+
click.echo(f"💾 Short-lived access token saved successfully for {user_name} (valid for 7 days).")
|
66
|
+
|
67
|
+
|
68
|
+
def login_external():
|
69
|
+
"""
|
70
|
+
External login using Developer Portal endpoint defined in the 'public' section of YAML config.
|
71
|
+
Prompts for username/password and retrieves access token.
|
72
|
+
"""
|
73
|
+
cfg = load_resource_config()
|
74
|
+
auth_url = cfg.get("public", {}).get("auth", {}).get("external_url")
|
75
|
+
|
76
|
+
if not auth_url:
|
77
|
+
click.echo("❌ External auth URL not configured in YAML.")
|
78
|
+
return
|
79
|
+
|
80
|
+
click.echo("🌐 Logging in using external Developer Portal...")
|
81
|
+
|
82
|
+
# Prompt for credentials
|
83
|
+
username = click.prompt("Email or Username")
|
84
|
+
password = getpass.getpass("Password: ")
|
85
|
+
|
86
|
+
data = {
|
87
|
+
"username": username,
|
88
|
+
"password": password
|
89
|
+
}
|
90
|
+
|
91
|
+
try:
|
92
|
+
response = requests.post(auth_url, json=data)
|
93
|
+
response.raise_for_status()
|
94
|
+
|
95
|
+
token = response.json().get("access_token")
|
96
|
+
if not token:
|
97
|
+
return click.echo("❌ Failed to retrieve access token.")
|
98
|
+
|
99
|
+
set_auth_token(token)
|
100
|
+
click.echo("✅ External login successful.")
|
101
|
+
|
102
|
+
except requests.RequestException as e:
|
103
|
+
click.echo(f"❌ External login failed: {e}")
|
sima_cli/cli.py
CHANGED
@@ -1,22 +1,52 @@
|
|
1
|
+
import os
|
1
2
|
import click
|
2
3
|
from sima_cli.utils.env import get_environment_type
|
3
4
|
from sima_cli.update.updater import perform_update
|
5
|
+
from sima_cli.model_zoo.model import list_models, download_model
|
4
6
|
|
5
7
|
# Entry point for the CLI tool using Click's command group decorator
|
6
8
|
@click.group()
|
7
|
-
|
8
|
-
|
9
|
+
@click.option('-i', '--internal', is_flag=True, help="Use internal Artifactory resources.")
|
10
|
+
@click.pass_context
|
11
|
+
def main(ctx, internal):
|
12
|
+
"""
|
13
|
+
sima-cli – SiMa Developer Portal CLI Tool
|
14
|
+
|
15
|
+
Global Options:
|
16
|
+
--internal Use internal Artifactory resources (can also be set via env variable SIMA_CLI_INTERNAL=1)
|
17
|
+
"""
|
18
|
+
ctx.ensure_object(dict)
|
19
|
+
|
20
|
+
# Allow env override if --internal not explicitly passed
|
21
|
+
if not internal:
|
22
|
+
internal = os.getenv("SIMA_CLI_INTERNAL", "0") in ("1", "true", "yes")
|
23
|
+
|
24
|
+
ctx.obj["internal"] = internal
|
25
|
+
|
26
|
+
from sima_cli.utils.env import get_environment_type
|
9
27
|
env_type, env_subtype = get_environment_type()
|
10
|
-
|
28
|
+
|
29
|
+
if internal:
|
30
|
+
click.echo(f"🔧 Environment: {env_type} ({env_subtype}) | Internal: {internal}")
|
31
|
+
else:
|
32
|
+
click.echo(f"🔧 Environment: {env_type} ({env_subtype})")
|
33
|
+
|
34
|
+
if not internal:
|
35
|
+
click.echo(f"external environment is not supported yet..")
|
36
|
+
exit(0)
|
11
37
|
|
12
38
|
# ----------------------
|
13
39
|
# Authentication Command
|
14
40
|
# ----------------------
|
15
41
|
@main.command()
|
16
|
-
|
42
|
+
@click.pass_context
|
43
|
+
def login(ctx):
|
17
44
|
"""Authenticate with the SiMa Developer Portal."""
|
18
|
-
|
19
|
-
|
45
|
+
|
46
|
+
from sima_cli.auth import login as perform_login
|
47
|
+
|
48
|
+
internal = ctx.obj.get("internal", False)
|
49
|
+
perform_login.login("internal" if internal else "external")
|
20
50
|
|
21
51
|
# ----------------------
|
22
52
|
# Download Command
|
@@ -24,14 +54,17 @@ def login():
|
|
24
54
|
@main.command(name="download")
|
25
55
|
@click.argument('url') # Accept both file and folder URLs
|
26
56
|
@click.option('-d', '--dest', type=click.Path(), default='.', help="Target download directory")
|
27
|
-
|
57
|
+
@click.pass_context
|
58
|
+
def download(ctx, url, dest):
|
28
59
|
"""Download a file or a whole folder from a given URL."""
|
29
60
|
from sima_cli.download.downloader import download_file_from_url, download_folder_from_url
|
30
61
|
|
62
|
+
internal = ctx.obj.get("internal", False)
|
63
|
+
|
31
64
|
# First, try to download as a file
|
32
65
|
try:
|
33
66
|
click.echo("🔍 Checking if URL is a direct file...")
|
34
|
-
path = download_file_from_url(url, dest)
|
67
|
+
path = download_file_from_url(url, dest, internal)
|
35
68
|
click.echo(f"\n✅ File downloaded successfully to: {path}")
|
36
69
|
return
|
37
70
|
except Exception as e:
|
@@ -40,7 +73,7 @@ def download(url, dest):
|
|
40
73
|
# If that fails, try to treat as a folder and download all files
|
41
74
|
try:
|
42
75
|
click.echo("🔍 Attempting folder download...")
|
43
|
-
paths = download_folder_from_url(url, dest)
|
76
|
+
paths = download_folder_from_url(url, dest, internal)
|
44
77
|
if not paths:
|
45
78
|
raise RuntimeError("No files were downloaded.")
|
46
79
|
click.echo(f"\n✅ Folder download completed. {len(paths)} files saved to: {dest}")
|
@@ -53,55 +86,85 @@ def download(url, dest):
|
|
53
86
|
@main.command(name="update")
|
54
87
|
@click.argument('version_or_url')
|
55
88
|
@click.option('--ip', help="Target device IP address for remote firmware update.")
|
56
|
-
|
57
|
-
|
58
|
-
|
89
|
+
@click.option(
|
90
|
+
'--board',
|
91
|
+
default='davinci',
|
92
|
+
type=click.Choice(['davinci', 'modalix'], case_sensitive=False),
|
93
|
+
show_default=True,
|
94
|
+
help="Target board type (davinci or modalix)."
|
95
|
+
)
|
96
|
+
@click.option(
|
97
|
+
'--passwd',
|
98
|
+
default='edgeai',
|
99
|
+
help="Optional SSH password for remote board (default is 'edgeai')."
|
100
|
+
)
|
101
|
+
@click.pass_context
|
102
|
+
def update(ctx, version_or_url, ip, board, passwd):
|
103
|
+
"""
|
104
|
+
Run system update across different environments.
|
105
|
+
Downloads and applies firmware updates for PCIe host or SiMa board.
|
106
|
+
|
107
|
+
version_or_url: The version string (e.g. '1.5.0') or a direct URL to the firmware package.
|
108
|
+
"""
|
109
|
+
internal = ctx.obj.get("internal", False)
|
110
|
+
perform_update(version_or_url, ip, board.lower(), internal, passwd=passwd)
|
59
111
|
|
60
112
|
# ----------------------
|
61
113
|
# Model Zoo Subcommands
|
62
114
|
# ----------------------
|
63
115
|
@main.group()
|
64
|
-
|
116
|
+
@click.option('--ver', default="1.6.0", show_default=True, help="SDK version, minimum and default is 1.6.0")
|
117
|
+
@click.pass_context
|
118
|
+
def modelzoo(ctx, ver):
|
65
119
|
"""Access models from the Model Zoo."""
|
120
|
+
ctx.ensure_object(dict)
|
121
|
+
ctx.obj['ver'] = ver
|
66
122
|
pass
|
67
123
|
|
68
|
-
@
|
69
|
-
@click.
|
70
|
-
def
|
124
|
+
@modelzoo.command("list")
|
125
|
+
@click.pass_context
|
126
|
+
def list_models_cmd(ctx):
|
71
127
|
"""List available models."""
|
72
|
-
|
73
|
-
|
128
|
+
internal = ctx.obj.get("internal", False)
|
129
|
+
version = ctx.obj.get("ver")
|
130
|
+
click.echo(f"Listing models for version: {version}")
|
131
|
+
list_models(internal, version)
|
74
132
|
|
75
|
-
@
|
76
|
-
@click.argument('model_name')
|
77
|
-
@click.
|
78
|
-
def get_model(
|
133
|
+
@modelzoo.command("get")
|
134
|
+
@click.argument('model_name')
|
135
|
+
@click.pass_context
|
136
|
+
def get_model(ctx, model_name):
|
79
137
|
"""Download a specific model."""
|
80
|
-
|
81
|
-
|
138
|
+
ver = ctx.obj.get("ver")
|
139
|
+
internal = ctx.obj.get("internal", False)
|
140
|
+
click.echo(f"Getting model '{model_name}' for version: {ver}")
|
141
|
+
download_model(internal, ver, model_name)
|
82
142
|
|
83
143
|
# ----------------------
|
84
144
|
# App Zoo Subcommands
|
85
145
|
# ----------------------
|
86
|
-
@main.group()
|
87
|
-
|
88
|
-
|
89
|
-
|
146
|
+
# @main.group()
|
147
|
+
# @click.pass_context
|
148
|
+
# def app_zoo(ctx):
|
149
|
+
# """Access apps from the App Zoo."""
|
150
|
+
# pass
|
151
|
+
|
152
|
+
# @app_zoo.command("list")
|
153
|
+
# @click.option('--ver', help="SDK version")
|
154
|
+
# @click.pass_context
|
155
|
+
# def list_apps(ctx, ver):
|
156
|
+
# """List available apps."""
|
157
|
+
# # Placeholder: Call API to list apps
|
158
|
+
# click.echo(f"Listing apps for version: {ver or 'latest'}")
|
90
159
|
|
91
|
-
@app_zoo.command("
|
92
|
-
@click.
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
@click.argument('app_name') # Required: app name
|
100
|
-
@click.option('--ver', help="SDK version")
|
101
|
-
def get_app(app_name, ver):
|
102
|
-
"""Download a specific app."""
|
103
|
-
# Placeholder: Download and validate app
|
104
|
-
click.echo(f"Getting app '{app_name}' for version: {ver or 'latest'}")
|
160
|
+
# @app_zoo.command("get")
|
161
|
+
# @click.argument('app_name') # Required: app name
|
162
|
+
# @click.option('--ver', help="SDK version")
|
163
|
+
# @click.pass_context
|
164
|
+
# def get_app(ctx, app_name, ver):
|
165
|
+
# """Download a specific app."""
|
166
|
+
# # Placeholder: Download and validate app
|
167
|
+
# click.echo(f"Getting app '{app_name}' for version: {ver or 'latest'}")
|
105
168
|
|
106
169
|
# ----------------------
|
107
170
|
# Entry point for direct execution
|
sima_cli/download/downloader.py
CHANGED
@@ -3,13 +3,15 @@ import requests
|
|
3
3
|
from urllib.parse import urlparse
|
4
4
|
from tqdm import tqdm
|
5
5
|
from typing import List
|
6
|
+
from sima_cli.utils.config import get_auth_token
|
6
7
|
|
7
|
-
def _list_directory_files(url: str) -> List[str]:
|
8
|
+
def _list_directory_files(url: str, internal: bool = False) -> List[str]:
|
8
9
|
"""
|
9
10
|
Attempt to list files in a server-hosted directory with index browsing enabled.
|
10
11
|
|
11
12
|
Args:
|
12
13
|
url (str): Base URL to the folder.
|
14
|
+
internal (bool): Whether the resource is internal (requires token).
|
13
15
|
|
14
16
|
Returns:
|
15
17
|
List[str]: List of full file URLs.
|
@@ -18,14 +20,18 @@ def _list_directory_files(url: str) -> List[str]:
|
|
18
20
|
RuntimeError: If listing fails or HTML cannot be parsed.
|
19
21
|
"""
|
20
22
|
try:
|
21
|
-
|
23
|
+
headers = {}
|
24
|
+
token = get_auth_token(internal)
|
25
|
+
if token:
|
26
|
+
headers["Authorization"] = f"Bearer {token}"
|
27
|
+
|
28
|
+
response = requests.get(url, headers=headers, timeout=10)
|
22
29
|
response.raise_for_status()
|
23
30
|
|
24
31
|
if "text/html" not in response.headers.get("Content-Type", ""):
|
25
32
|
raise RuntimeError("Directory listing not supported (non-HTML response).")
|
26
33
|
|
27
34
|
import re
|
28
|
-
|
29
35
|
hrefs = re.findall(r'href="([^"?/][^"?]*)"', response.text)
|
30
36
|
files = [href for href in hrefs if href not in ("../", "") and not href.endswith("/")]
|
31
37
|
|
@@ -38,21 +44,21 @@ def _list_directory_files(url: str) -> List[str]:
|
|
38
44
|
raise RuntimeError(f"Failed to list folder '{url}': {e}")
|
39
45
|
|
40
46
|
|
41
|
-
def download_file_from_url(url: str, dest_folder: str = ".") -> str:
|
47
|
+
def download_file_from_url(url: str, dest_folder: str = ".", internal: bool = False) -> str:
|
42
48
|
"""
|
43
49
|
Download a file from a direct URL with resume and skip support.
|
44
|
-
|
50
|
+
|
45
51
|
Args:
|
46
52
|
url (str): The full URL to download.
|
47
53
|
dest_folder (str): The folder to save the downloaded file.
|
48
|
-
|
54
|
+
internal (bool): Whether this is internal resource on Artifactory
|
55
|
+
|
49
56
|
Returns:
|
50
57
|
str: Path to the downloaded file.
|
51
|
-
|
58
|
+
|
52
59
|
Raises:
|
53
60
|
Exception: if download fails.
|
54
61
|
"""
|
55
|
-
# Extract file name
|
56
62
|
parsed_url = urlparse(url)
|
57
63
|
file_name = os.path.basename(parsed_url.path)
|
58
64
|
if not file_name:
|
@@ -62,12 +68,17 @@ def download_file_from_url(url: str, dest_folder: str = ".") -> str:
|
|
62
68
|
dest_path = os.path.join(dest_folder, file_name)
|
63
69
|
|
64
70
|
resume_header = {}
|
71
|
+
headers = {}
|
65
72
|
mode = 'wb'
|
66
73
|
existing_size = 0
|
67
74
|
|
68
75
|
try:
|
69
|
-
|
70
|
-
|
76
|
+
auth_token = get_auth_token(internal)
|
77
|
+
if auth_token:
|
78
|
+
headers["Authorization"] = f"Bearer {auth_token}"
|
79
|
+
|
80
|
+
# HEAD request to get total file size
|
81
|
+
head = requests.head(url, headers=headers, timeout=10)
|
71
82
|
head.raise_for_status()
|
72
83
|
total_size = int(head.headers.get('content-length', 0))
|
73
84
|
|
@@ -79,19 +90,17 @@ def download_file_from_url(url: str, dest_folder: str = ".") -> str:
|
|
79
90
|
print(f"✔ File already exists and is complete: {file_name}")
|
80
91
|
return dest_path
|
81
92
|
elif existing_size < total_size:
|
82
|
-
# Set up resume
|
83
93
|
resume_header['Range'] = f'bytes={existing_size}-'
|
84
94
|
mode = 'ab'
|
95
|
+
headers['Range'] = resume_header['Range']
|
85
96
|
else:
|
86
|
-
# Local file is bigger than expected — reset
|
87
97
|
existing_size = 0
|
88
98
|
mode = 'wb'
|
89
99
|
|
90
|
-
#
|
91
|
-
with requests.get(url, stream=True, headers=
|
100
|
+
# Begin download with headers
|
101
|
+
with requests.get(url, stream=True, headers=headers, timeout=30) as r:
|
92
102
|
r.raise_for_status()
|
93
103
|
|
94
|
-
# If resuming, adjust total to show correct progress bar
|
95
104
|
content_length = int(r.headers.get('content-length', 0))
|
96
105
|
final_total = existing_size + content_length
|
97
106
|
|
@@ -113,25 +122,27 @@ def download_file_from_url(url: str, dest_folder: str = ".") -> str:
|
|
113
122
|
|
114
123
|
return dest_path
|
115
124
|
|
116
|
-
|
125
|
+
|
126
|
+
def download_folder_from_url(url: str, dest_folder: str = ".", internal: bool = False) -> List[str]:
|
117
127
|
"""
|
118
128
|
Download all files listed in a remote folder (server must support listing).
|
119
129
|
|
120
130
|
Args:
|
121
131
|
url (str): Folder URL.
|
122
132
|
dest_folder (str): Local folder to save downloads.
|
133
|
+
internal (bool): Whether this is internal resource on Artifactory
|
123
134
|
|
124
135
|
Returns:
|
125
136
|
List[str]: Paths to all downloaded files.
|
126
137
|
"""
|
127
|
-
file_urls = _list_directory_files(url)
|
138
|
+
file_urls = _list_directory_files(url, internal=internal)
|
128
139
|
downloaded_paths = []
|
129
140
|
|
130
141
|
for file_url in file_urls:
|
131
142
|
try:
|
132
|
-
downloaded_path = download_file_from_url(file_url, dest_folder)
|
143
|
+
downloaded_path = download_file_from_url(file_url, dest_folder, internal=internal)
|
133
144
|
downloaded_paths.append(downloaded_path)
|
134
145
|
except Exception as e:
|
135
146
|
print(f"⚠ Skipped {file_url}: {e}")
|
136
147
|
|
137
|
-
return downloaded_paths
|
148
|
+
return downloaded_paths
|
sima_cli/model_zoo/model.py
CHANGED
@@ -0,0 +1,148 @@
|
|
1
|
+
# model_zoo/models.py
|
2
|
+
|
3
|
+
import requests
|
4
|
+
import click
|
5
|
+
import os
|
6
|
+
from urllib.parse import urlparse
|
7
|
+
|
8
|
+
from sima_cli.utils.config import get_auth_token
|
9
|
+
from sima_cli.download import download_file_from_url
|
10
|
+
|
11
|
+
ARTIFACTORY_BASE_URL = "https://artifacts.eng.sima.ai:443/artifactory"
|
12
|
+
|
13
|
+
def _is_valid_url(url: str) -> bool:
|
14
|
+
try:
|
15
|
+
result = urlparse(url)
|
16
|
+
return all([result.scheme, result.netloc])
|
17
|
+
except:
|
18
|
+
return False
|
19
|
+
|
20
|
+
def _download_model_internal(ver: str, model_name: str):
|
21
|
+
repo = "sima-qa-releases"
|
22
|
+
base_path = f"SiMaCLI-SDK-Releases/{ver}-Release/modelzoo_edgematic/{model_name}"
|
23
|
+
aql_query = f"""
|
24
|
+
items.find({{
|
25
|
+
"repo": "{repo}",
|
26
|
+
"path": {{
|
27
|
+
"$match": "{base_path}*"
|
28
|
+
}},
|
29
|
+
"type": "file"
|
30
|
+
}}).include("repo", "path", "name")
|
31
|
+
""".strip()
|
32
|
+
|
33
|
+
aql_url = f"{ARTIFACTORY_BASE_URL}/api/search/aql"
|
34
|
+
headers = {
|
35
|
+
"Content-Type": "text/plain",
|
36
|
+
"Authorization": f"Bearer {get_auth_token(internal=True)}"
|
37
|
+
}
|
38
|
+
|
39
|
+
response = requests.post(aql_url, data=aql_query, headers=headers)
|
40
|
+
if response.status_code != 200:
|
41
|
+
click.echo(f"Failed to list model files. Status: {response.status_code}, path: {aql_url}")
|
42
|
+
click.echo(response.text)
|
43
|
+
return
|
44
|
+
|
45
|
+
results = response.json().get("results", [])
|
46
|
+
if not results:
|
47
|
+
click.echo(f"No files found for model: {model_name}")
|
48
|
+
return
|
49
|
+
|
50
|
+
dest_dir = os.path.join(os.getcwd(), model_name)
|
51
|
+
os.makedirs(dest_dir, exist_ok=True)
|
52
|
+
|
53
|
+
click.echo(f"Downloading files for model '{model_name}' to '{dest_dir}'...")
|
54
|
+
|
55
|
+
for item in results:
|
56
|
+
file_path = item["path"]
|
57
|
+
file_name = item["name"]
|
58
|
+
download_url = f"{ARTIFACTORY_BASE_URL}/{repo}/{file_path}/{file_name}"
|
59
|
+
|
60
|
+
try:
|
61
|
+
local_path = download_file_from_url(download_url, dest_folder=dest_dir, internal=True)
|
62
|
+
click.echo(f"✅ {file_name} -> {local_path}")
|
63
|
+
except Exception as e:
|
64
|
+
click.echo(f"❌ Failed to download {file_name}: {e}")
|
65
|
+
|
66
|
+
# Check for model_path.txt and optionally download external ONNX model
|
67
|
+
model_path_file = os.path.join(dest_dir, "model_path.txt")
|
68
|
+
if os.path.exists(model_path_file):
|
69
|
+
with open(model_path_file, "r") as f:
|
70
|
+
first_line = f.readline().strip()
|
71
|
+
if _is_valid_url(first_line):
|
72
|
+
click.echo(f"\n🔍 model_path.txt contains external model link:\n{first_line}")
|
73
|
+
if click.confirm("Do you want to download the FP32 ONNX model from this link?", default=True):
|
74
|
+
try:
|
75
|
+
external_model_path = download_file_from_url(first_line, dest_folder=dest_dir, internal=True)
|
76
|
+
click.echo(f"✅ External model downloaded to: {external_model_path}")
|
77
|
+
except Exception as e:
|
78
|
+
click.echo(f"❌ Failed to download external model: {e}")
|
79
|
+
else:
|
80
|
+
click.echo("⚠️ model_path.txt exists but does not contain a valid URL.")
|
81
|
+
|
82
|
+
def _list_available_models_internal(version: str):
|
83
|
+
repo_path = f"SiMaCLI-SDK-Releases/{version}-Release/modelzoo_edgematic"
|
84
|
+
aql_query = f"""
|
85
|
+
items.find({{
|
86
|
+
"repo": "sima-qa-releases",
|
87
|
+
"path": {{
|
88
|
+
"$match": "{repo_path}/*"
|
89
|
+
}},
|
90
|
+
"type": "folder"
|
91
|
+
}}).include("repo", "path", "name")
|
92
|
+
""".strip()
|
93
|
+
|
94
|
+
aql_url = f"{ARTIFACTORY_BASE_URL}/api/search/aql"
|
95
|
+
headers = {
|
96
|
+
"Content-Type": "text/plain",
|
97
|
+
"Authorization": f"Bearer {get_auth_token(internal=True)}"
|
98
|
+
}
|
99
|
+
|
100
|
+
response = requests.post(aql_url, data=aql_query, headers=headers)
|
101
|
+
|
102
|
+
if response.status_code != 200:
|
103
|
+
click.echo(f"Failed to retrieve model list. Status: {response.status_code}")
|
104
|
+
click.echo(response.text)
|
105
|
+
return
|
106
|
+
|
107
|
+
results = response.json().get("results", [])
|
108
|
+
|
109
|
+
base_prefix = f"SiMaCLI-SDK-Releases/{version}-Release/modelzoo_edgematic/"
|
110
|
+
model_paths = sorted({
|
111
|
+
item["path"].replace(base_prefix, "").rstrip("/") + "/" + item["name"]
|
112
|
+
for item in results
|
113
|
+
})
|
114
|
+
|
115
|
+
if not model_paths:
|
116
|
+
click.echo("No models found.")
|
117
|
+
return
|
118
|
+
|
119
|
+
# Pretty print table
|
120
|
+
max_len = max(len(name) for name in model_paths)
|
121
|
+
click.echo(f"{'-' * max_len}")
|
122
|
+
for path in model_paths:
|
123
|
+
click.echo(path.ljust(max_len))
|
124
|
+
|
125
|
+
return model_paths
|
126
|
+
|
127
|
+
def list_models(internal, ver):
|
128
|
+
if internal:
|
129
|
+
click.echo("Model Zoo Source : SiMa Artifactory...")
|
130
|
+
return _list_available_models_internal(ver)
|
131
|
+
else:
|
132
|
+
print('External model zoo not supported yet')
|
133
|
+
|
134
|
+
def download_model(internal, ver, model_name):
|
135
|
+
if internal:
|
136
|
+
click.echo("Model Zoo Source : SiMa Artifactory...")
|
137
|
+
return _download_model_internal(ver, model_name)
|
138
|
+
else:
|
139
|
+
print('External model zoo not supported yet')
|
140
|
+
|
141
|
+
# Module CLI tests
|
142
|
+
if __name__ == "__main__":
|
143
|
+
import sys
|
144
|
+
if len(sys.argv) < 2:
|
145
|
+
print("Usage: python models.py <version>")
|
146
|
+
else:
|
147
|
+
version_arg = sys.argv[1]
|
148
|
+
_list_available_models_internal(version_arg)
|