aipmodel 0.2.40__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.
- aipmodel/CephS3Manager.py +1380 -0
- aipmodel/__init__.py +11 -0
- aipmodel/model_registry.py +938 -0
- aipmodel/template.py +67 -0
- aipmodel/update_checker.py +17 -0
- aipmodel-0.2.40.dist-info/METADATA +26 -0
- aipmodel-0.2.40.dist-info/RECORD +9 -0
- aipmodel-0.2.40.dist-info/WHEEL +5 -0
- aipmodel-0.2.40.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,938 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
from base64 import b64encode
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
import logging
|
|
6
|
+
import requests
|
|
7
|
+
import subprocess
|
|
8
|
+
from dotenv import load_dotenv
|
|
9
|
+
from huggingface_hub import snapshot_download
|
|
10
|
+
|
|
11
|
+
load_dotenv()
|
|
12
|
+
|
|
13
|
+
from .CephS3Manager import CephS3Manager
|
|
14
|
+
|
|
15
|
+
# Setup logging
|
|
16
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
# ProjectsAPI manages ClearML project operations
|
|
20
|
+
class ProjectsAPI:
|
|
21
|
+
def __init__(self, post, verbose):
|
|
22
|
+
self.verbose = verbose
|
|
23
|
+
if self.verbose:
|
|
24
|
+
print("Initializing ProjectsAPI...")
|
|
25
|
+
self._post = post
|
|
26
|
+
print("[OK] ProjectsAPI initialized successfully")
|
|
27
|
+
|
|
28
|
+
def create(self, name, description=""):
|
|
29
|
+
if self.verbose:
|
|
30
|
+
print(f"Preparing to create project: name={name}, description={description}")
|
|
31
|
+
print(f"Starting to create project: name={name}, description={description}")
|
|
32
|
+
response = self._post("/projects.create", {"name": name, "description": description})
|
|
33
|
+
if not response or "id" not in response:
|
|
34
|
+
error_msg = "Failed to create project in ClearML"
|
|
35
|
+
print(f"[FAIL] {error_msg}")
|
|
36
|
+
raise ValueError(error_msg)
|
|
37
|
+
if self.verbose:
|
|
38
|
+
print(f"Project creation completed: id={response['id']}")
|
|
39
|
+
print(f"[OK] Project created successfully: id={response['id']}")
|
|
40
|
+
return response
|
|
41
|
+
|
|
42
|
+
def get_all(self):
|
|
43
|
+
if self.verbose:
|
|
44
|
+
print("Preparing to retrieve all projects...")
|
|
45
|
+
print("Starting to get all projects...")
|
|
46
|
+
response = self._post("/projects.get_all")
|
|
47
|
+
if not response or "projects" not in response:
|
|
48
|
+
error_msg = "Failed to retrieve projects from ClearML"
|
|
49
|
+
print(f"[FAIL] {error_msg}")
|
|
50
|
+
raise ValueError(error_msg)
|
|
51
|
+
if self.verbose:
|
|
52
|
+
print(f"Projects retrieval completed: found {len(response['projects'])} projects")
|
|
53
|
+
print(f"[OK] Retrieved {len(response['projects'])} projects successfully")
|
|
54
|
+
return response["projects"]
|
|
55
|
+
|
|
56
|
+
# ModelsAPI manages ClearML model operations
|
|
57
|
+
class ModelsAPI:
|
|
58
|
+
def __init__(self, post, verbose):
|
|
59
|
+
self.verbose = verbose
|
|
60
|
+
if self.verbose:
|
|
61
|
+
print("Initializing ModelsAPI...")
|
|
62
|
+
self._post = post
|
|
63
|
+
print("[OK] ModelsAPI initialized successfully")
|
|
64
|
+
|
|
65
|
+
def get_all(self, project_id=None):
|
|
66
|
+
if self.verbose:
|
|
67
|
+
print(f"Preparing to retrieve models for project_id={project_id}")
|
|
68
|
+
print(f"Starting to get all models for project_id={project_id}")
|
|
69
|
+
payload = {"project": project_id} if project_id else {}
|
|
70
|
+
response = self._post("/models.get_all", payload)
|
|
71
|
+
|
|
72
|
+
# Check expected key in proper format
|
|
73
|
+
if isinstance(response, dict):
|
|
74
|
+
if "models" in response and isinstance(response["models"], list):
|
|
75
|
+
if self.verbose:
|
|
76
|
+
print(f"Models retrieval completed: found {len(response['models'])} models")
|
|
77
|
+
print(f"[OK] Retrieved {len(response['models'])} models successfully")
|
|
78
|
+
return response["models"]
|
|
79
|
+
if "data" in response and isinstance(response["data"], dict) and "models" in response["data"]:
|
|
80
|
+
if self.verbose:
|
|
81
|
+
print(f"Models retrieval completed: found {len(response['data']['models'])} models")
|
|
82
|
+
print(f"[OK] Retrieved {len(response['data']['models'])} models successfully")
|
|
83
|
+
return response["data"]["models"]
|
|
84
|
+
|
|
85
|
+
error_msg = f"'models' not found in response: {response}"
|
|
86
|
+
print(f"[ERROR] {error_msg}")
|
|
87
|
+
raise ValueError("Failed to retrieve models from ClearML")
|
|
88
|
+
return []
|
|
89
|
+
|
|
90
|
+
def create(self, name, project_id, metadata=None, uri=""):
|
|
91
|
+
if self.verbose:
|
|
92
|
+
print(f"Preparing to create model: name={name}, project_id={project_id}, uri={uri}")
|
|
93
|
+
print(f"Starting to create model: name={name}, project_id={project_id}, uri={uri}")
|
|
94
|
+
payload = {
|
|
95
|
+
"name": name,
|
|
96
|
+
"project": project_id,
|
|
97
|
+
"uri": uri
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if isinstance(metadata, dict):
|
|
101
|
+
payload["metadata"] = metadata
|
|
102
|
+
|
|
103
|
+
response = self._post("/models.create", payload)
|
|
104
|
+
if not response or "id" not in response:
|
|
105
|
+
error_msg = "Failed to create model in ClearML"
|
|
106
|
+
print(f"[FAIL] {error_msg}")
|
|
107
|
+
raise ValueError(error_msg)
|
|
108
|
+
if self.verbose:
|
|
109
|
+
print(f"Model creation completed: id={response['id']}")
|
|
110
|
+
print(f"[OK] Model created successfully: id={response['id']}")
|
|
111
|
+
return response
|
|
112
|
+
|
|
113
|
+
def update(self, model_id, uri=None, metadata=None):
|
|
114
|
+
if self.verbose:
|
|
115
|
+
print(f"Preparing to update model: model_id={model_id}, uri={uri}")
|
|
116
|
+
print(f"Starting to update model: model_id={model_id}, uri={uri}")
|
|
117
|
+
payload = {"model": model_id}
|
|
118
|
+
if uri:
|
|
119
|
+
payload["uri"] = uri
|
|
120
|
+
if isinstance(metadata, dict) or isinstance(metadata, list):
|
|
121
|
+
payload["metadata"] = metadata
|
|
122
|
+
|
|
123
|
+
response = self._post("/models.add_or_update_metadata", payload)
|
|
124
|
+
if not response:
|
|
125
|
+
error_msg = "Failed to update model metadata in ClearML"
|
|
126
|
+
print(f"[FAIL] {error_msg}")
|
|
127
|
+
raise ValueError(error_msg)
|
|
128
|
+
if self.verbose:
|
|
129
|
+
print(f"Model metadata update completed for id={model_id}")
|
|
130
|
+
print(f"[OK] Model metadata updated successfully for id={model_id}")
|
|
131
|
+
return response
|
|
132
|
+
|
|
133
|
+
def edit_uri(self, model_id, uri):
|
|
134
|
+
if self.verbose:
|
|
135
|
+
print(f"Preparing to edit URI for model_id={model_id}, uri={uri}")
|
|
136
|
+
print(f"Starting to edit URI for model_id={model_id}, uri={uri}")
|
|
137
|
+
payload = {"model": model_id, "uri": uri}
|
|
138
|
+
response = self._post("/models.edit", payload)
|
|
139
|
+
if not response:
|
|
140
|
+
error_msg = "Failed to edit model URI in ClearML"
|
|
141
|
+
print(f"[FAIL] {error_msg}")
|
|
142
|
+
raise ValueError(error_msg)
|
|
143
|
+
if self.verbose:
|
|
144
|
+
print(f"Model URI edit completed for id={model_id}")
|
|
145
|
+
print(f"[OK] Model URI edited successfully for id={model_id}")
|
|
146
|
+
return response
|
|
147
|
+
|
|
148
|
+
def get_by_id(self, model_id):
|
|
149
|
+
if self.verbose:
|
|
150
|
+
print(f"Preparing to retrieve model by id: {model_id}")
|
|
151
|
+
print(f"Starting to get model by id: {model_id}")
|
|
152
|
+
response = self._post("/models.get_by_id", {"model": model_id})
|
|
153
|
+
if not response:
|
|
154
|
+
error_msg = f"Failed to retrieve model with ID {model_id} from ClearML"
|
|
155
|
+
print(f"[FAIL] {error_msg}")
|
|
156
|
+
raise ValueError(error_msg)
|
|
157
|
+
if self.verbose:
|
|
158
|
+
print(f"Model retrieval completed: id={model_id}")
|
|
159
|
+
print(f"[OK] Model retrieved successfully: id={model_id}")
|
|
160
|
+
return response
|
|
161
|
+
|
|
162
|
+
def delete(self, model_id):
|
|
163
|
+
if self.verbose:
|
|
164
|
+
print(f"Preparing to delete model: id={model_id}")
|
|
165
|
+
print(f"Starting to delete model: id={model_id}")
|
|
166
|
+
response = self._post("/models.delete", {"model": model_id})
|
|
167
|
+
if not response:
|
|
168
|
+
error_msg = f"Failed to delete model with ID {model_id} from ClearML"
|
|
169
|
+
print(f"[FAIL] {error_msg}")
|
|
170
|
+
raise ValueError(error_msg)
|
|
171
|
+
if self.verbose:
|
|
172
|
+
print(f"Model deletion completed: id={model_id}")
|
|
173
|
+
print(f"[OK] Model deleted successfully: id={model_id}")
|
|
174
|
+
return response
|
|
175
|
+
|
|
176
|
+
# MLOpsManager integrates ClearML and Ceph S3 operations
|
|
177
|
+
class MLOpsManager:
|
|
178
|
+
def get_user_info(self):
|
|
179
|
+
return requests.get(
|
|
180
|
+
os.path.join(self.USER_MANAGEMENT_API, f"user/{self.CLEARML_USERNAME}/metadata?token={self.USER_TOKEN}"),
|
|
181
|
+
timeout=10
|
|
182
|
+
).json()["metadata"]
|
|
183
|
+
|
|
184
|
+
def __init__(
|
|
185
|
+
self,
|
|
186
|
+
user_name,
|
|
187
|
+
user_token,
|
|
188
|
+
CLEARML_API_HOST=None,
|
|
189
|
+
CEPH_ENDPOINT_URL=None,
|
|
190
|
+
USER_MANAGEMENT_API=None,
|
|
191
|
+
verbose=False
|
|
192
|
+
):
|
|
193
|
+
self.verbose = verbose
|
|
194
|
+
if self.verbose:
|
|
195
|
+
print("Initializing MLOpsManager...")
|
|
196
|
+
print("Starting MLOpsManager initialization...")
|
|
197
|
+
# Load defaults from environment
|
|
198
|
+
self.CLEARML_USERNAME = user_name
|
|
199
|
+
self.USER_TOKEN = user_token
|
|
200
|
+
self.CLEARML_API_HOST = CLEARML_API_HOST or os.environ.get("CLEARML_API_HOST")
|
|
201
|
+
self.USER_MANAGEMENT_API = USER_MANAGEMENT_API or os.environ.get("USER_MANAGEMENT_API")
|
|
202
|
+
self.CEPH_ENDPOINT_URL = CEPH_ENDPOINT_URL or os.environ.get("CEPH_ENDPOINT_URL")
|
|
203
|
+
|
|
204
|
+
# Validate required ClearML credentials
|
|
205
|
+
if self.verbose:
|
|
206
|
+
print("Validating ClearML credentials...")
|
|
207
|
+
if not all([self.CLEARML_API_HOST, self.CLEARML_USERNAME, self.USER_MANAGEMENT_API, self.CEPH_ENDPOINT_URL]):
|
|
208
|
+
error_msg = "Missing required ClearML configuration parameters"
|
|
209
|
+
print(f"[FAIL] {error_msg}")
|
|
210
|
+
raise ValueError(error_msg)
|
|
211
|
+
if self.verbose:
|
|
212
|
+
print("ClearML credentials validated successfully")
|
|
213
|
+
user_info = self.get_user_info()
|
|
214
|
+
# Ceph configuration from environment
|
|
215
|
+
self.CEPH_ADMIN_ACCESS_KEY = user_info["s3_access_key"]
|
|
216
|
+
self.CEPH_ADMIN_SECRET_KEY = user_info["s3_secret_key"]
|
|
217
|
+
self.CEPH_USER_BUCKET = user_info["s3_bucket"]
|
|
218
|
+
self.CLEARML_ACCESS_KEY = user_info["clearml_access_key"]
|
|
219
|
+
self.CLEARML_SECRET_KEY = user_info["clearml_secret_key"]
|
|
220
|
+
|
|
221
|
+
# Health checks for ClearML services
|
|
222
|
+
if self.verbose:
|
|
223
|
+
print("Performing ClearML service health checks...")
|
|
224
|
+
if not self.check_clearml_service():
|
|
225
|
+
error_msg = "ClearML Server down."
|
|
226
|
+
print(f"[FAIL] {error_msg}")
|
|
227
|
+
raise ValueError(error_msg)
|
|
228
|
+
if not self.check_clearml_auth():
|
|
229
|
+
error_msg = "ClearML Authentication not correct."
|
|
230
|
+
print(f"[FAIL] {error_msg}")
|
|
231
|
+
raise ValueError(error_msg)
|
|
232
|
+
if self.verbose:
|
|
233
|
+
print("ClearML service health checks completed")
|
|
234
|
+
|
|
235
|
+
# Initialize CephS3Manager with user-specific bucket
|
|
236
|
+
if self.verbose:
|
|
237
|
+
print("Initializing CephS3Manager...")
|
|
238
|
+
self.ceph = CephS3Manager(
|
|
239
|
+
self.CEPH_ENDPOINT_URL,
|
|
240
|
+
self.CEPH_ADMIN_ACCESS_KEY,
|
|
241
|
+
self.CEPH_ADMIN_SECRET_KEY,
|
|
242
|
+
self.CEPH_USER_BUCKET,
|
|
243
|
+
verbose=self.verbose
|
|
244
|
+
)
|
|
245
|
+
if self.verbose:
|
|
246
|
+
print("CephS3Manager initialized successfully")
|
|
247
|
+
|
|
248
|
+
# Login to ClearML and extract token
|
|
249
|
+
if self.verbose:
|
|
250
|
+
print("Preparing to login to ClearML...")
|
|
251
|
+
print("Logging in to ClearML...")
|
|
252
|
+
creds = f"{self.CLEARML_ACCESS_KEY}:{self.CLEARML_SECRET_KEY}"
|
|
253
|
+
auth_header = b64encode(creds.encode("utf-8")).decode("utf-8")
|
|
254
|
+
res = requests.post(
|
|
255
|
+
f"{self.CLEARML_API_HOST}/auth.login",
|
|
256
|
+
headers={"Authorization": f"Basic {auth_header}"}
|
|
257
|
+
)
|
|
258
|
+
if res.status_code != 200:
|
|
259
|
+
error_msg = "Failed to authenticate with ClearML"
|
|
260
|
+
print(f"[FAIL] {error_msg}")
|
|
261
|
+
raise ValueError(error_msg)
|
|
262
|
+
self.token = res.json()["data"]["token"]
|
|
263
|
+
if self.verbose:
|
|
264
|
+
print("ClearML login completed successfully")
|
|
265
|
+
print("[OK] Logged in to ClearML successfully")
|
|
266
|
+
|
|
267
|
+
self.projects = ProjectsAPI(self._post, verbose=self.verbose)
|
|
268
|
+
self.models = ModelsAPI(self._post, verbose=self.verbose)
|
|
269
|
+
|
|
270
|
+
# Get or create user-specific project
|
|
271
|
+
if self.verbose:
|
|
272
|
+
print("Checking for user-specific project...")
|
|
273
|
+
print("Getting or creating user-specific project...")
|
|
274
|
+
projects = self.projects.get_all()
|
|
275
|
+
self.project_name = f"project_{self.CLEARML_USERNAME}"
|
|
276
|
+
exists = [p for p in projects if p["name"] == self.project_name]
|
|
277
|
+
self.project_id = exists[0]["id"] if exists else self.projects.create(self.project_name)["id"]
|
|
278
|
+
if self.verbose:
|
|
279
|
+
print(f"User-specific project processing completed: project_id={self.project_id}")
|
|
280
|
+
print(f"[OK] Project ID: {self.project_id}")
|
|
281
|
+
print("[OK] MLOpsManager initialized successfully")
|
|
282
|
+
|
|
283
|
+
def _post(self, path, params=None):
|
|
284
|
+
if self.verbose:
|
|
285
|
+
print(f"Preparing POST request to {path}...")
|
|
286
|
+
print(f"Starting POST request to {path}...")
|
|
287
|
+
headers = {"Authorization": f"Bearer {self.token}"}
|
|
288
|
+
try:
|
|
289
|
+
res = requests.post(f"{self.CLEARML_API_HOST}{path}", headers=headers, json=params)
|
|
290
|
+
res.raise_for_status()
|
|
291
|
+
|
|
292
|
+
data = res.json()
|
|
293
|
+
if "data" not in data:
|
|
294
|
+
error_msg = f"No 'data' key in response: {data}"
|
|
295
|
+
print(f"[ERROR] {error_msg}")
|
|
296
|
+
raise ValueError(f"Request to {path} failed: No data in response")
|
|
297
|
+
if self.verbose:
|
|
298
|
+
print(f"POST request to {path} completed successfully")
|
|
299
|
+
print(f"[OK] POST request to {path} successful")
|
|
300
|
+
return data["data"]
|
|
301
|
+
|
|
302
|
+
except requests.exceptions.RequestException as e:
|
|
303
|
+
error_msg = f"Request to {path} failed: {e}"
|
|
304
|
+
print(f"[ERROR] {error_msg}")
|
|
305
|
+
print(f"[ERROR] Status Code: {res.status_code}, Response: {res.text}")
|
|
306
|
+
raise ValueError(f"Request to {path} failed: {str(e)}")
|
|
307
|
+
|
|
308
|
+
except ValueError as e:
|
|
309
|
+
error_msg = f"Failed to parse JSON from {path}: {e}"
|
|
310
|
+
print(f"[ERROR] {error_msg}")
|
|
311
|
+
print(f"[ERROR] Raw response: {res.text}")
|
|
312
|
+
raise ValueError(f"Failed to parse JSON from {path}: {str(e)}")
|
|
313
|
+
|
|
314
|
+
def check_clearml_service(self):
|
|
315
|
+
"""
|
|
316
|
+
Check if ClearML service is reachable and responding.
|
|
317
|
+
"""
|
|
318
|
+
if self.verbose:
|
|
319
|
+
print("Preparing to check ClearML service...")
|
|
320
|
+
print("Checking ClearML service...")
|
|
321
|
+
try:
|
|
322
|
+
r = requests.get(self.CLEARML_API_HOST + "/auth.login", timeout=5)
|
|
323
|
+
if r.status_code in [200, 401]:
|
|
324
|
+
if self.verbose:
|
|
325
|
+
print("ClearML service check completed")
|
|
326
|
+
print("[OK] ClearML Service")
|
|
327
|
+
return True
|
|
328
|
+
error_msg = f"ClearML Service {r.status_code}"
|
|
329
|
+
print(f"[FAIL] {error_msg}")
|
|
330
|
+
raise ValueError("ClearML Service is not reachable")
|
|
331
|
+
except Exception as e:
|
|
332
|
+
error_msg = f"ClearML Service: {str(e)}"
|
|
333
|
+
print(f"[FAIL] {error_msg}")
|
|
334
|
+
raise ValueError("ClearML Service is not reachable")
|
|
335
|
+
|
|
336
|
+
def check_clearml_auth(self):
|
|
337
|
+
"""
|
|
338
|
+
Check if ClearML credentials are valid by attempting login.
|
|
339
|
+
"""
|
|
340
|
+
if self.verbose:
|
|
341
|
+
print("Preparing to check ClearML authentication...")
|
|
342
|
+
print("Checking ClearML authentication...")
|
|
343
|
+
try:
|
|
344
|
+
creds = f"{self.CLEARML_ACCESS_KEY}:{self.CLEARML_SECRET_KEY}"
|
|
345
|
+
auth_header = b64encode(creds.encode("utf-8")).decode("utf-8")
|
|
346
|
+
r = requests.post(
|
|
347
|
+
self.CLEARML_API_HOST + "/auth.login",
|
|
348
|
+
headers={"Authorization": f"Basic {auth_header}"},
|
|
349
|
+
timeout=5
|
|
350
|
+
)
|
|
351
|
+
if r.status_code == 200:
|
|
352
|
+
if self.verbose:
|
|
353
|
+
print("ClearML authentication check completed")
|
|
354
|
+
print("[OK] ClearML Auth")
|
|
355
|
+
return True
|
|
356
|
+
error_msg = f"ClearML Auth {r.status_code}"
|
|
357
|
+
print(f"[FAIL] {error_msg}")
|
|
358
|
+
raise ValueError("ClearML Authentication failed")
|
|
359
|
+
except Exception as e:
|
|
360
|
+
error_msg = f"ClearML Auth: {str(e)}"
|
|
361
|
+
print(f"[FAIL] {error_msg}")
|
|
362
|
+
raise ValueError("ClearML Authentication failed")
|
|
363
|
+
|
|
364
|
+
def get_model_id_by_name(self, name):
|
|
365
|
+
if self.verbose:
|
|
366
|
+
print(f"Preparing to get model ID for name: {name}")
|
|
367
|
+
print(f"Starting to get model ID by name: {name}")
|
|
368
|
+
models = self.models.get_all(self.project_id)
|
|
369
|
+
if self.verbose:
|
|
370
|
+
print(f"Retrieved {len(models)} models for ID lookup")
|
|
371
|
+
for m in models:
|
|
372
|
+
if m["name"] == name:
|
|
373
|
+
if self.verbose:
|
|
374
|
+
print(f"Model ID lookup completed: id={m['id']}")
|
|
375
|
+
print(f"[OK] Model ID found: {m['id']}")
|
|
376
|
+
return m["id"]
|
|
377
|
+
if self.verbose:
|
|
378
|
+
print("Model ID lookup completed: no model found")
|
|
379
|
+
print("[OK] No model found with given name")
|
|
380
|
+
return None
|
|
381
|
+
|
|
382
|
+
def get_model_name_by_id(self, model_id):
|
|
383
|
+
if self.verbose:
|
|
384
|
+
print(f"Preparing to get model name for ID: {model_id}")
|
|
385
|
+
print(f"Starting to get model name by ID: {model_id}")
|
|
386
|
+
model = self.models.get_by_id(model_id)
|
|
387
|
+
result = model.get("name") if model else None
|
|
388
|
+
if result:
|
|
389
|
+
if self.verbose:
|
|
390
|
+
print(f"Model name lookup completed: name={result}")
|
|
391
|
+
print(f"[OK] Model name found: {result}")
|
|
392
|
+
else:
|
|
393
|
+
if self.verbose:
|
|
394
|
+
print("Model name lookup completed: no model found")
|
|
395
|
+
print("[OK] No model found with given ID")
|
|
396
|
+
return result
|
|
397
|
+
|
|
398
|
+
def generate_random_string(self):
|
|
399
|
+
if self.verbose:
|
|
400
|
+
print("Preparing to generate random string...")
|
|
401
|
+
print("Generating random string...")
|
|
402
|
+
import random
|
|
403
|
+
import string
|
|
404
|
+
result = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))
|
|
405
|
+
if self.verbose:
|
|
406
|
+
print(f"Random string generation completed: {result}")
|
|
407
|
+
print(f"[OK] Random string generated: {result}")
|
|
408
|
+
return result
|
|
409
|
+
|
|
410
|
+
def transfer_from_s3(self, source_endpoint_url, source_access_key, source_secret_key, source_bucket, source_path, dest_prefix, exclude=[".git", ".DS_Store"], overwrite=True):
|
|
411
|
+
"""
|
|
412
|
+
Transfer a model from another S3 bucket to the initialized bucket.
|
|
413
|
+
"""
|
|
414
|
+
if self.verbose:
|
|
415
|
+
print(f"Preparing transfer from S3: source_path={source_path}, dest_prefix={dest_prefix}")
|
|
416
|
+
print(f"Starting transfer from S3: source_path={source_path}, dest_prefix={dest_prefix}")
|
|
417
|
+
tmp_dir = None
|
|
418
|
+
try:
|
|
419
|
+
tmp_dir = f"./tmp_{self.generate_random_string()}"
|
|
420
|
+
if self.verbose:
|
|
421
|
+
print(f"Creating temporary directory: {tmp_dir}")
|
|
422
|
+
print(f"Creating temporary directory: {tmp_dir}")
|
|
423
|
+
os.makedirs(tmp_dir, exist_ok=True)
|
|
424
|
+
|
|
425
|
+
if self.verbose:
|
|
426
|
+
print("Initializing source CephS3Manager...")
|
|
427
|
+
print("Initializing source CephS3Manager...")
|
|
428
|
+
src_ceph = CephS3Manager(source_endpoint_url, source_access_key, source_secret_key, source_bucket)
|
|
429
|
+
if self.verbose:
|
|
430
|
+
print("Source CephS3Manager initialized")
|
|
431
|
+
print("Downloading from source...")
|
|
432
|
+
src_ceph.download(source_path, tmp_dir, keep_folder=True, exclude=exclude, overwrite=overwrite)
|
|
433
|
+
|
|
434
|
+
if self.verbose:
|
|
435
|
+
print("Preparing to delete destination folder if exists...")
|
|
436
|
+
print("Deleting destination folder if exists...")
|
|
437
|
+
self.ceph.delete_folder(dest_prefix) # Ensure clean state
|
|
438
|
+
if self.verbose:
|
|
439
|
+
print("Destination folder deletion completed")
|
|
440
|
+
print("Uploading to destination...")
|
|
441
|
+
self.ceph.upload(tmp_dir, dest_prefix)
|
|
442
|
+
|
|
443
|
+
if self.verbose:
|
|
444
|
+
print("S3 transfer completed successfully")
|
|
445
|
+
print("[OK] Transfer from S3 successful")
|
|
446
|
+
return True
|
|
447
|
+
except Exception as e:
|
|
448
|
+
error_msg = f"Failed to transfer model from S3: {e}"
|
|
449
|
+
print(f"[FAIL] {error_msg}")
|
|
450
|
+
try:
|
|
451
|
+
self.ceph.delete_folder(dest_prefix)
|
|
452
|
+
except Exception as cleanup_error:
|
|
453
|
+
print(f"[ERROR] Failed to clean up destination folder {dest_prefix}: {cleanup_error}")
|
|
454
|
+
raise ValueError(f"Failed to transfer model from S3: {str(e)}")
|
|
455
|
+
finally:
|
|
456
|
+
if tmp_dir and os.path.exists(tmp_dir):
|
|
457
|
+
try:
|
|
458
|
+
shutil.rmtree(tmp_dir)
|
|
459
|
+
if self.verbose:
|
|
460
|
+
print(f"Temporary directory cleanup completed: {tmp_dir}")
|
|
461
|
+
print(f"[OK] Cleaned up temporary directory {tmp_dir}")
|
|
462
|
+
except Exception as cleanup_error:
|
|
463
|
+
print(f"[ERROR] Failed to clean up temporary directory {tmp_dir}: {cleanup_error}")
|
|
464
|
+
|
|
465
|
+
def add_model(self, source_type, model_name=None, source_path=None, code_path=None,
|
|
466
|
+
external_ceph_endpoint_url=None, external_ceph_bucket_name=None, external_ceph_access_key=None, external_ceph_secret_key=None):
|
|
467
|
+
"""
|
|
468
|
+
Add a model from various sources (local, Hugging Face, or S3) and register it in ClearML.
|
|
469
|
+
"""
|
|
470
|
+
if self.verbose:
|
|
471
|
+
print(f"Preparing to add model: source_type={source_type}, model_name={model_name}")
|
|
472
|
+
print(f"Starting to add model: source_type={source_type}, model_name={model_name}")
|
|
473
|
+
# Input validation
|
|
474
|
+
if not model_name or not isinstance(model_name, str):
|
|
475
|
+
error_msg = "Model name is required"
|
|
476
|
+
logger.error(error_msg)
|
|
477
|
+
print(f"[ERROR] model_name must be a non-empty string")
|
|
478
|
+
return None
|
|
479
|
+
if source_type not in ["local", "hf", "s3"]:
|
|
480
|
+
error_msg = f"Unknown source_type: {source_type}"
|
|
481
|
+
logger.error(error_msg)
|
|
482
|
+
print(f"[ERROR] {error_msg}")
|
|
483
|
+
return None
|
|
484
|
+
if source_type == "local":
|
|
485
|
+
if not source_path or not os.path.exists(source_path):
|
|
486
|
+
error_msg = f"Local path {source_path} does not exist"
|
|
487
|
+
logger.error(error_msg)
|
|
488
|
+
print(f"[FAIL] {error_msg}")
|
|
489
|
+
return None
|
|
490
|
+
if not os.access(source_path, os.R_OK):
|
|
491
|
+
error_msg = f"Cannot read source_path: {source_path}"
|
|
492
|
+
logger.error(error_msg)
|
|
493
|
+
print(f"[FAIL] {error_msg}")
|
|
494
|
+
return None
|
|
495
|
+
if source_type == "hf" and (not source_path or not isinstance(source_path, str)):
|
|
496
|
+
error_msg = f"Invalid or missing source_path for Hugging Face: {source_path}"
|
|
497
|
+
logger.error(error_msg)
|
|
498
|
+
print(f"[FAIL] {error_msg}")
|
|
499
|
+
return None
|
|
500
|
+
if source_type == "s3" and (
|
|
501
|
+
not all([source_path, external_ceph_access_key, external_ceph_secret_key, external_ceph_endpoint_url, external_ceph_bucket_name])
|
|
502
|
+
or not all(isinstance(x, str) for x in [source_path, external_ceph_access_key, external_ceph_secret_key, external_ceph_endpoint_url, external_ceph_bucket_name])
|
|
503
|
+
):
|
|
504
|
+
error_msg = "Missing required S3 parameters"
|
|
505
|
+
logger.error(error_msg)
|
|
506
|
+
print(f"[FAIL] {error_msg}")
|
|
507
|
+
return None
|
|
508
|
+
if code_path and (not os.path.isfile(code_path) or not code_path.endswith(".py")):
|
|
509
|
+
error_msg = f"Invalid code_path: {code_path}. Must be a valid .py file"
|
|
510
|
+
logger.error(error_msg)
|
|
511
|
+
print(f"[FAIL] {error_msg}")
|
|
512
|
+
return None
|
|
513
|
+
|
|
514
|
+
if self.verbose:
|
|
515
|
+
print("Checking for existing model...")
|
|
516
|
+
print("Checking if model already exists...")
|
|
517
|
+
if self.get_model_id_by_name(model_name):
|
|
518
|
+
warning_msg = f"Model with name '{model_name}' already exists."
|
|
519
|
+
logger.warning(warning_msg)
|
|
520
|
+
print(f"[WARN] {warning_msg}")
|
|
521
|
+
print("[INFO] Listing existing models:")
|
|
522
|
+
self.list_models(verbose=True)
|
|
523
|
+
return None
|
|
524
|
+
|
|
525
|
+
# Determine model_folder_name according to source_type
|
|
526
|
+
if source_type == "hf":
|
|
527
|
+
model_folder_name = f"hf_{model_name}"
|
|
528
|
+
elif source_type == "local":
|
|
529
|
+
model_folder_name = os.path.basename(source_path)
|
|
530
|
+
elif source_type == "s3":
|
|
531
|
+
model_folder_name = os.path.basename(source_path)
|
|
532
|
+
else:
|
|
533
|
+
model_folder_name = ""
|
|
534
|
+
|
|
535
|
+
if self.verbose:
|
|
536
|
+
print(f"Model folder name set: {model_folder_name}")
|
|
537
|
+
print(f"Model folder name determined: {model_folder_name}")
|
|
538
|
+
have_model_py = False
|
|
539
|
+
temp_model_id = self.generate_random_string()
|
|
540
|
+
dest_prefix = f"models/{temp_model_id}/"
|
|
541
|
+
local_path = None
|
|
542
|
+
temp_local_path = None
|
|
543
|
+
|
|
544
|
+
try:
|
|
545
|
+
if source_type == "local":
|
|
546
|
+
# Create a temporary copy to protect source_path
|
|
547
|
+
temp_local_path = f"./tmp_{self.generate_random_string()}"
|
|
548
|
+
if self.verbose:
|
|
549
|
+
print(f"Preparing to copy local source to temporary path: {temp_local_path}")
|
|
550
|
+
print(f"Copying local source to temporary path: {temp_local_path}")
|
|
551
|
+
shutil.copytree(source_path, temp_local_path, dirs_exist_ok=True)
|
|
552
|
+
if self.verbose:
|
|
553
|
+
print("Local source copy completed")
|
|
554
|
+
print("Deleting destination prefix if exists...")
|
|
555
|
+
self.ceph.delete_folder(dest_prefix) # Ensure clean state
|
|
556
|
+
if self.verbose:
|
|
557
|
+
print("Destination prefix deletion completed")
|
|
558
|
+
print("Uploading temporary path...")
|
|
559
|
+
size_mb = self.ceph.upload(temp_local_path, dest_prefix)
|
|
560
|
+
elif source_type == "hf":
|
|
561
|
+
if self.verbose:
|
|
562
|
+
print("Preparing to download from Hugging Face...")
|
|
563
|
+
print("Downloading from Hugging Face...")
|
|
564
|
+
local_path = snapshot_download(repo_id=source_path)
|
|
565
|
+
if self.verbose:
|
|
566
|
+
print("Hugging Face download completed")
|
|
567
|
+
print("Deleting destination prefix if exists...")
|
|
568
|
+
self.ceph.delete_folder(dest_prefix) # Ensure clean state
|
|
569
|
+
if self.verbose:
|
|
570
|
+
print("Destination prefix deletion completed")
|
|
571
|
+
print("Uploading HF model...")
|
|
572
|
+
size_mb = self.ceph.upload(local_path, os.path.join(dest_prefix, model_folder_name))
|
|
573
|
+
elif source_type == "s3":
|
|
574
|
+
if self.verbose:
|
|
575
|
+
print("Preparing to transfer from S3...")
|
|
576
|
+
print("Transferring from S3...")
|
|
577
|
+
success = self.transfer_from_s3(
|
|
578
|
+
source_endpoint_url=external_ceph_endpoint_url,
|
|
579
|
+
source_access_key=external_ceph_access_key,
|
|
580
|
+
source_secret_key=external_ceph_secret_key,
|
|
581
|
+
source_bucket=external_ceph_bucket_name,
|
|
582
|
+
source_path=source_path,
|
|
583
|
+
dest_prefix=dest_prefix,
|
|
584
|
+
exclude=[".git", ".DS_Store"],
|
|
585
|
+
overwrite=True
|
|
586
|
+
)
|
|
587
|
+
if not success:
|
|
588
|
+
error_msg = "Failed to transfer model from S3"
|
|
589
|
+
print(f"[FAIL] {error_msg}")
|
|
590
|
+
raise ValueError(error_msg)
|
|
591
|
+
uri = f"s3://{self.ceph.bucket_name}/{dest_prefix}"
|
|
592
|
+
if self.verbose:
|
|
593
|
+
print("Calculating size of transferred model...")
|
|
594
|
+
print("Getting size of transferred model...")
|
|
595
|
+
size_mb = self.ceph.get_uri_size(uri)
|
|
596
|
+
else:
|
|
597
|
+
error_msg = f"Unknown source_type: {source_type}"
|
|
598
|
+
print(f"[FAIL] {error_msg}")
|
|
599
|
+
raise ValueError(error_msg)
|
|
600
|
+
|
|
601
|
+
if code_path and os.path.isfile(code_path):
|
|
602
|
+
if self.verbose:
|
|
603
|
+
print("Preparing to upload model.py code...")
|
|
604
|
+
print("Uploading model.py code...")
|
|
605
|
+
self.ceph.upload(code_path, dest_prefix + "model.py")
|
|
606
|
+
have_model_py = True
|
|
607
|
+
if self.verbose:
|
|
608
|
+
print("model.py upload completed")
|
|
609
|
+
|
|
610
|
+
# Create model in ClearML after successful upload
|
|
611
|
+
if self.verbose:
|
|
612
|
+
print("Preparing to create model in ClearML...")
|
|
613
|
+
print("Creating model in ClearML...")
|
|
614
|
+
model = self.models.create(
|
|
615
|
+
name=model_name,
|
|
616
|
+
project_id=self.project_id,
|
|
617
|
+
uri="s3://dummy/uri"
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
model_id = model["id"]
|
|
621
|
+
if model_id != temp_model_id:
|
|
622
|
+
new_dest_prefix = f"models/{model_id}/"
|
|
623
|
+
if self.verbose:
|
|
624
|
+
print(f"Preparing to move folder to new prefix: {new_dest_prefix}")
|
|
625
|
+
print(f"Moving folder to new prefix: {new_dest_prefix}")
|
|
626
|
+
if self.ceph.check_if_exists(new_dest_prefix):
|
|
627
|
+
self.ceph.delete_folder(new_dest_prefix)
|
|
628
|
+
self.ceph.move_folder(dest_prefix, new_dest_prefix)
|
|
629
|
+
self.ceph.delete_folder(dest_prefix)
|
|
630
|
+
dest_prefix = new_dest_prefix
|
|
631
|
+
if self.verbose:
|
|
632
|
+
print("Folder move completed")
|
|
633
|
+
|
|
634
|
+
if self.verbose:
|
|
635
|
+
print("Preparing model metadata...")
|
|
636
|
+
print("Preparing metadata...")
|
|
637
|
+
metadata_list = [
|
|
638
|
+
{"key": "modelFolderName", "type": "str", "value": model_folder_name},
|
|
639
|
+
{"key": "haveModelPy", "type": "str", "value": str(have_model_py).lower()},
|
|
640
|
+
{"key": "modelSize", "type": "float", "value": str(size_mb) if size_mb is not None else "0.0"}
|
|
641
|
+
]
|
|
642
|
+
|
|
643
|
+
uri = f"s3://{self.ceph.bucket_name}/{dest_prefix}"
|
|
644
|
+
if self.verbose:
|
|
645
|
+
print("Preparing to edit model URI...")
|
|
646
|
+
print("Editing model URI...")
|
|
647
|
+
self.models.edit_uri(model_id, uri=uri)
|
|
648
|
+
if self.verbose:
|
|
649
|
+
print("Model URI edit completed")
|
|
650
|
+
print("Updating model metadata...")
|
|
651
|
+
self.models.update(model_id, metadata=metadata_list)
|
|
652
|
+
if self.verbose:
|
|
653
|
+
print("Model metadata update completed")
|
|
654
|
+
|
|
655
|
+
logger.info(f"Model '{model_name}' (ID: {model_id}) added successfully")
|
|
656
|
+
print(f"[SUCCESS] Model '{model_name}' (ID: {model_id}) added successfully")
|
|
657
|
+
return model_id
|
|
658
|
+
|
|
659
|
+
except (Exception, KeyboardInterrupt) as e:
|
|
660
|
+
error_msg = f"Upload or registration failed: {e}"
|
|
661
|
+
logger.error(error_msg)
|
|
662
|
+
print(f"[ERROR] {error_msg}")
|
|
663
|
+
print("[INFO] Cleaning up partially uploaded model...")
|
|
664
|
+
if 'model_id' in locals():
|
|
665
|
+
try:
|
|
666
|
+
self.models.delete(model_id)
|
|
667
|
+
if self.verbose:
|
|
668
|
+
print("ClearML model cleanup completed")
|
|
669
|
+
print("[OK] Cleaned up ClearML model")
|
|
670
|
+
except Exception as cleanup_error:
|
|
671
|
+
error_cleanup = f"Failed to clean up ClearML model {model_id}: {cleanup_error}"
|
|
672
|
+
logger.error(error_cleanup)
|
|
673
|
+
print(f"[ERROR] {error_cleanup}")
|
|
674
|
+
if dest_prefix:
|
|
675
|
+
try:
|
|
676
|
+
self.ceph.delete_folder(dest_prefix)
|
|
677
|
+
if self.verbose:
|
|
678
|
+
print("Ceph folder cleanup completed")
|
|
679
|
+
print("[OK] Cleaned up Ceph folder")
|
|
680
|
+
except Exception as cleanup_error:
|
|
681
|
+
error_cleanup = f"Failed to clean up Ceph folder {dest_prefix}: {cleanup_error}"
|
|
682
|
+
logger.error(error_cleanup)
|
|
683
|
+
print(f"[ERROR] {error_cleanup}")
|
|
684
|
+
return None
|
|
685
|
+
finally:
|
|
686
|
+
# Clean up temporary local paths
|
|
687
|
+
for path in [local_path, temp_local_path]:
|
|
688
|
+
if path and os.path.exists(path):
|
|
689
|
+
try:
|
|
690
|
+
shutil.rmtree(path)
|
|
691
|
+
if self.verbose:
|
|
692
|
+
print(f"Local directory cleanup completed: {path}")
|
|
693
|
+
print(f"[OK] Cleaned up local directory {path}")
|
|
694
|
+
except Exception as cleanup_error:
|
|
695
|
+
error_cleanup = f"Failed to clean up local directory {path}: {cleanup_error}"
|
|
696
|
+
logger.error(error_cleanup)
|
|
697
|
+
print(f"[ERROR] {error_cleanup}")
|
|
698
|
+
|
|
699
|
+
def get_model(self, model_name, local_dest):
|
|
700
|
+
"""
|
|
701
|
+
Download a model by name and return its metadata.
|
|
702
|
+
"""
|
|
703
|
+
logger.info("Starting get_model for name=%r, dest=%r", model_name, local_dest)
|
|
704
|
+
if self.verbose:
|
|
705
|
+
print(f"Preparing to get model: model_name={model_name}, local_dest={local_dest}")
|
|
706
|
+
print(f"Starting get_model: model_name={model_name}, local_dest={local_dest}")
|
|
707
|
+
|
|
708
|
+
# Resolve model ID
|
|
709
|
+
try:
|
|
710
|
+
if self.verbose:
|
|
711
|
+
print("Preparing to resolve model ID...")
|
|
712
|
+
print("Resolving model ID...")
|
|
713
|
+
model_id = self.get_model_id_by_name(model_name)
|
|
714
|
+
logger.debug("Resolved model_id=%r for name=%r", model_id, model_name)
|
|
715
|
+
if self.verbose:
|
|
716
|
+
print(f"Model ID resolution completed: id={model_id}")
|
|
717
|
+
print(f"[OK] Resolved model_id={model_id}")
|
|
718
|
+
except Exception as exc:
|
|
719
|
+
error_msg = f"Failed to resolve model ID for name: {model_name}"
|
|
720
|
+
logger.exception(error_msg)
|
|
721
|
+
print(f"[FAIL] {error_msg}")
|
|
722
|
+
raise ValueError(error_msg) from exc
|
|
723
|
+
|
|
724
|
+
if not model_id:
|
|
725
|
+
warning_msg = f"Model not found: {model_name}"
|
|
726
|
+
logger.warning(warning_msg)
|
|
727
|
+
print(f"[WARN] {warning_msg}")
|
|
728
|
+
raise ValueError(warning_msg)
|
|
729
|
+
|
|
730
|
+
# Fetch model metadata
|
|
731
|
+
try:
|
|
732
|
+
if self.verbose:
|
|
733
|
+
print("Preparing to fetch model metadata...")
|
|
734
|
+
print("Fetching model metadata...")
|
|
735
|
+
model_data = self.models.get_by_id(model_id)
|
|
736
|
+
logger.debug("Fetched model_data keys=%s", list(model_data.keys()))
|
|
737
|
+
if self.verbose:
|
|
738
|
+
print("Model metadata fetch completed")
|
|
739
|
+
print("[OK] Model metadata fetched")
|
|
740
|
+
except Exception as exc:
|
|
741
|
+
error_msg = f"Failed to fetch model data for id: {model_id}"
|
|
742
|
+
logger.exception(error_msg)
|
|
743
|
+
print(f"[FAIL] {error_msg}")
|
|
744
|
+
raise ValueError(error_msg) from exc
|
|
745
|
+
|
|
746
|
+
# Preserve original extraction logic
|
|
747
|
+
model = model_data.get("model") or model_data
|
|
748
|
+
logger.debug("Normalized model payload type=%s keys=%s", type(model).__name__, list(model.keys()))
|
|
749
|
+
if self.verbose:
|
|
750
|
+
print("Normalizing model payload...")
|
|
751
|
+
print("Model payload normalized")
|
|
752
|
+
|
|
753
|
+
# Extract URI
|
|
754
|
+
try:
|
|
755
|
+
uri = model["uri"]
|
|
756
|
+
logger.debug("Model URI: %r", uri)
|
|
757
|
+
if self.verbose:
|
|
758
|
+
print(f"URI extraction completed: {uri}")
|
|
759
|
+
print(f"[OK] Extracted URI: {uri}")
|
|
760
|
+
except Exception as exc:
|
|
761
|
+
error_msg = f"Model metadata missing 'uri' field for id: {model_id}"
|
|
762
|
+
logger.exception(error_msg)
|
|
763
|
+
print(f"[FAIL] {error_msg}")
|
|
764
|
+
raise ValueError(error_msg) from exc
|
|
765
|
+
|
|
766
|
+
# Derive remote path
|
|
767
|
+
try:
|
|
768
|
+
_, remote_path = uri.replace("s3://", "").split("/", 1)
|
|
769
|
+
logger.debug("Derived remote_path=%r from uri=%r", remote_path, uri)
|
|
770
|
+
if self.verbose:
|
|
771
|
+
print(f"Remote path derivation completed: {remote_path}")
|
|
772
|
+
print(f"[OK] Derived remote_path: {remote_path}")
|
|
773
|
+
except Exception as exc:
|
|
774
|
+
error_msg = f"Invalid model URI format: {uri!r}"
|
|
775
|
+
logger.exception(error_msg)
|
|
776
|
+
print(f"[FAIL] {error_msg}")
|
|
777
|
+
raise ValueError(error_msg) from exc
|
|
778
|
+
|
|
779
|
+
# Download via ceph client
|
|
780
|
+
try:
|
|
781
|
+
logger.info("Downloading from remote_path=%r to local_dest=%r", remote_path, local_dest)
|
|
782
|
+
if self.verbose:
|
|
783
|
+
print(f"Preparing to download model from {remote_path} to {local_dest}...")
|
|
784
|
+
print(f"Downloading model from {remote_path} to {local_dest}...")
|
|
785
|
+
self.ceph.download(
|
|
786
|
+
remote_path,
|
|
787
|
+
local_dest,
|
|
788
|
+
keep_folder=True,
|
|
789
|
+
exclude=[".git", ".DS_Store"],
|
|
790
|
+
overwrite=False,
|
|
791
|
+
)
|
|
792
|
+
logger.info("Download complete for model id=%r, name=%r", model_id, model_name)
|
|
793
|
+
if self.verbose:
|
|
794
|
+
print("Model download completed")
|
|
795
|
+
print(f"[OK] Download complete for model: {model_name}")
|
|
796
|
+
except Exception as exc:
|
|
797
|
+
error_msg = f"Failed to download model from {remote_path!r} to {local_dest!r}"
|
|
798
|
+
logger.exception(error_msg)
|
|
799
|
+
print(f"[FAIL] {error_msg}")
|
|
800
|
+
raise ValueError(error_msg) from exc
|
|
801
|
+
|
|
802
|
+
logger.info("Returning model metadata for name=%r", model_name)
|
|
803
|
+
if self.verbose:
|
|
804
|
+
print("Preparing to return model metadata...")
|
|
805
|
+
print("[OK] Returning model metadata")
|
|
806
|
+
return model
|
|
807
|
+
|
|
808
|
+
def get_model_info(self, identifier):
|
|
809
|
+
if self.verbose:
|
|
810
|
+
print(f"Preparing to get model info for identifier: {identifier}")
|
|
811
|
+
print(f"Starting to get model info for identifier: {identifier}")
|
|
812
|
+
all_models = self.models.get_all(self.project_id)
|
|
813
|
+
if self.verbose:
|
|
814
|
+
print(f"Retrieved {len(all_models)} models for info lookup")
|
|
815
|
+
|
|
816
|
+
def extract_model_info(model):
|
|
817
|
+
print("=" * 40)
|
|
818
|
+
print(f"ID: {model.get('id')}")
|
|
819
|
+
print(f"Name: {model.get('name')}")
|
|
820
|
+
print(f"Created: {model.get('created')}")
|
|
821
|
+
print(f"Framework: {model.get('framework')}")
|
|
822
|
+
print(f"URI: {model.get('uri')}")
|
|
823
|
+
|
|
824
|
+
# Extract and show metadata (including modelSize)
|
|
825
|
+
metadata = model.get("metadata", {})
|
|
826
|
+
print("Metadata:")
|
|
827
|
+
for key, value in metadata.items():
|
|
828
|
+
print(f" - {key}: {value}")
|
|
829
|
+
|
|
830
|
+
# Highlight modelSize if available
|
|
831
|
+
model_size = metadata.get("modelSize", {}).get("value")
|
|
832
|
+
if model_size is not None:
|
|
833
|
+
try:
|
|
834
|
+
print(f"\n[Model Size] {float(model_size):.2f} MB")
|
|
835
|
+
except (ValueError, TypeError):
|
|
836
|
+
print(f"\n[Model Size] Invalid value: {model_size}")
|
|
837
|
+
|
|
838
|
+
print(f"Labels: {model.get('labels')}")
|
|
839
|
+
print("=" * 40)
|
|
840
|
+
|
|
841
|
+
# Try match by ID
|
|
842
|
+
if self.verbose:
|
|
843
|
+
print("Attempting to match model by ID...")
|
|
844
|
+
print("Trying to match by ID...")
|
|
845
|
+
matched_by_id = [m for m in all_models if m.get("id") == identifier]
|
|
846
|
+
if matched_by_id:
|
|
847
|
+
extract_model_info(matched_by_id[0])
|
|
848
|
+
if self.verbose:
|
|
849
|
+
print("Model info retrieval by ID completed")
|
|
850
|
+
print("[OK] Model info retrieved by ID")
|
|
851
|
+
return matched_by_id[0]
|
|
852
|
+
|
|
853
|
+
# Try match by name
|
|
854
|
+
if self.verbose:
|
|
855
|
+
print("Attempting to match model by name...")
|
|
856
|
+
print("Trying to match by name...")
|
|
857
|
+
matched_by_name = [m for m in all_models if m.get("name") == identifier]
|
|
858
|
+
if matched_by_name:
|
|
859
|
+
for model in matched_by_name:
|
|
860
|
+
extract_model_info(model)
|
|
861
|
+
if self.verbose:
|
|
862
|
+
print("Model info retrieval by name completed")
|
|
863
|
+
print("[OK] Model info retrieved by name")
|
|
864
|
+
return matched_by_name
|
|
865
|
+
|
|
866
|
+
info_msg = f"No model found with identifier: '{identifier}'"
|
|
867
|
+
print(f"[INFO] {info_msg}")
|
|
868
|
+
raise ValueError(f"No model found with identifier: '{identifier}'")
|
|
869
|
+
|
|
870
|
+
def list_models(self, verbose=True):
|
|
871
|
+
if self.verbose:
|
|
872
|
+
print("Preparing to list models...")
|
|
873
|
+
print("Starting to list models...")
|
|
874
|
+
try:
|
|
875
|
+
models = self.models.get_all(self.project_id)
|
|
876
|
+
if verbose:
|
|
877
|
+
grouped = {}
|
|
878
|
+
for m in models:
|
|
879
|
+
grouped.setdefault(m["name"], []).append(m["id"])
|
|
880
|
+
for name, ids in grouped.items():
|
|
881
|
+
print(f"[Model] Name: {name}, Count: {len(ids)}")
|
|
882
|
+
else:
|
|
883
|
+
for m in models:
|
|
884
|
+
print(f"[Model] {m['name']} (ID: {m['id']})")
|
|
885
|
+
if self.verbose:
|
|
886
|
+
print(f"Model listing completed: found {len(models)} models")
|
|
887
|
+
print(f"[OK] Listed {len(models)} models successfully")
|
|
888
|
+
return [(m["name"], m["id"]) for m in models]
|
|
889
|
+
except Exception as e:
|
|
890
|
+
error_msg = f"Failed to list models: {e}"
|
|
891
|
+
print(f"[FAIL] {error_msg}")
|
|
892
|
+
raise ValueError(f"Failed to list models: {str(e)}")
|
|
893
|
+
|
|
894
|
+
def delete_model(self, model_id=None, model_name=None):
|
|
895
|
+
if self.verbose:
|
|
896
|
+
print(f"Preparing to delete model: model_id={model_id}, model_name={model_name}")
|
|
897
|
+
print(f"Starting to delete model: model_id={model_id}, model_name={model_name}")
|
|
898
|
+
if model_name and not model_id:
|
|
899
|
+
if self.verbose:
|
|
900
|
+
print("Resolving model ID from name...")
|
|
901
|
+
model_id = self.get_model_id_by_name(model_name)
|
|
902
|
+
if not model_id:
|
|
903
|
+
warning_msg = f"No model found with name '{model_name}'"
|
|
904
|
+
print(f"[WARN] {warning_msg}")
|
|
905
|
+
raise ValueError(f"No model found with name '{model_name}'")
|
|
906
|
+
|
|
907
|
+
if self.verbose:
|
|
908
|
+
print(f"Retrieving model data for id: {model_id}")
|
|
909
|
+
model_data = self.models.get_by_id(model_id)
|
|
910
|
+
if not model_data:
|
|
911
|
+
warning_msg = f"Model with ID '{model_id}' not found."
|
|
912
|
+
print(f"[WARN] {warning_msg}")
|
|
913
|
+
raise ValueError(f"Model with ID '{model_id}' not found")
|
|
914
|
+
|
|
915
|
+
model = model_data.get("model") or model_data
|
|
916
|
+
uri = model.get("uri")
|
|
917
|
+
if not uri:
|
|
918
|
+
warning_msg = f"Model '{model_id}' has no 'uri'."
|
|
919
|
+
print(f"[WARN] {warning_msg}")
|
|
920
|
+
raise ValueError(f"Model '{model_id}' has no URI")
|
|
921
|
+
|
|
922
|
+
try:
|
|
923
|
+
_, remote_path = uri.replace("s3://", "").split("/", 1)
|
|
924
|
+
if self.verbose:
|
|
925
|
+
print(f"Preparing to delete Ceph folder: {remote_path}")
|
|
926
|
+
print(f"Deleting Ceph folder: {remote_path}")
|
|
927
|
+
self.ceph.delete_folder(remote_path)
|
|
928
|
+
if self.verbose:
|
|
929
|
+
print("Ceph folder deletion completed")
|
|
930
|
+
print("Deleting model from ClearML...")
|
|
931
|
+
self.models.delete(model_id)
|
|
932
|
+
if self.verbose:
|
|
933
|
+
print("ClearML model deletion completed")
|
|
934
|
+
print(f"[SUCCESS] Model '{model_id}' deleted successfully from ClearML and Ceph")
|
|
935
|
+
except Exception as e:
|
|
936
|
+
error_msg = f"Failed to delete model '{model_id}': {e}"
|
|
937
|
+
print(f"[FAIL] {error_msg}")
|
|
938
|
+
raise ValueError(f"Failed to delete model '{model_id}': {str(e)}")
|