ml-dash 0.5.0__py3-none-any.whl → 0.5.1__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.
ml_dash/client.py CHANGED
@@ -35,6 +35,7 @@ class RemoteClient:
35
35
  name: str,
36
36
  description: Optional[str] = None,
37
37
  tags: Optional[List[str]] = None,
38
+ bindrs: Optional[List[str]] = None,
38
39
  folder: Optional[str] = None,
39
40
  write_protected: bool = False,
40
41
  metadata: Optional[Dict[str, Any]] = None,
@@ -47,6 +48,7 @@ class RemoteClient:
47
48
  name: Experiment name
48
49
  description: Optional description
49
50
  tags: Optional list of tags
51
+ bindrs: Optional list of bindrs
50
52
  folder: Optional folder path
51
53
  write_protected: If True, experiment becomes immutable
52
54
  metadata: Optional metadata dict
@@ -65,6 +67,8 @@ class RemoteClient:
65
67
  payload["description"] = description
66
68
  if tags is not None:
67
69
  payload["tags"] = tags
70
+ if bindrs is not None:
71
+ payload["bindrs"] = bindrs
68
72
  if folder is not None:
69
73
  payload["folder"] = folder
70
74
  if write_protected:
@@ -79,6 +83,35 @@ class RemoteClient:
79
83
  response.raise_for_status()
80
84
  return response.json()
81
85
 
86
+ def update_experiment_status(
87
+ self,
88
+ experiment_id: str,
89
+ status: str,
90
+ ) -> Dict[str, Any]:
91
+ """
92
+ Update experiment status.
93
+
94
+ Args:
95
+ experiment_id: Experiment ID
96
+ status: Status value - "RUNNING" | "COMPLETED" | "FAILED" | "CANCELLED"
97
+
98
+ Returns:
99
+ Response dict with updated experiment data
100
+
101
+ Raises:
102
+ httpx.HTTPStatusError: If request fails
103
+ """
104
+ payload = {
105
+ "status": status,
106
+ }
107
+
108
+ response = self._client.patch(
109
+ f"/experiments/{experiment_id}/status",
110
+ json=payload,
111
+ )
112
+ response.raise_for_status()
113
+ return response.json()
114
+
82
115
  def create_log_entries(
83
116
  self,
84
117
  experiment_id: str,
ml_dash/experiment.py CHANGED
@@ -65,6 +65,7 @@ class Experiment:
65
65
  *,
66
66
  description: Optional[str] = None,
67
67
  tags: Optional[List[str]] = None,
68
+ bindrs: Optional[List[str]] = None,
68
69
  folder: Optional[str] = None,
69
70
  write_protected: bool = False,
70
71
  metadata: Optional[Dict[str, Any]] = None,
@@ -82,6 +83,7 @@ class Experiment:
82
83
  project: Project name
83
84
  description: Optional experiment description
84
85
  tags: Optional list of tags
86
+ bindrs: Optional list of bindrs
85
87
  folder: Optional folder path (e.g., "/experiments/baseline")
86
88
  write_protected: If True, experiment becomes immutable after creation
87
89
  metadata: Optional metadata dict
@@ -94,6 +96,7 @@ class Experiment:
94
96
  self.project = project
95
97
  self.description = description
96
98
  self.tags = tags
99
+ self.bindrs = bindrs
97
100
  self.folder = folder
98
101
  self.write_protected = write_protected
99
102
  self.metadata = metadata
@@ -185,6 +188,7 @@ class Experiment:
185
188
  name=self.name,
186
189
  description=self.description,
187
190
  tags=self.tags,
191
+ bindrs=self.bindrs,
188
192
  folder=self.folder,
189
193
  write_protected=self.write_protected,
190
194
  metadata=self.metadata,
@@ -199,6 +203,7 @@ class Experiment:
199
203
  name=self.name,
200
204
  description=self.description,
201
205
  tags=self.tags,
206
+ bindrs=self.bindrs,
202
207
  folder=self.folder,
203
208
  metadata=self.metadata,
204
209
  )
@@ -206,8 +211,13 @@ class Experiment:
206
211
  self._is_open = True
207
212
  return self
208
213
 
209
- def close(self):
210
- """Close the experiment."""
214
+ def close(self, status: str = "COMPLETED"):
215
+ """
216
+ Close the experiment and update status.
217
+
218
+ Args:
219
+ status: Status to set - "COMPLETED" (default), "FAILED", or "CANCELLED"
220
+ """
211
221
  if not self._is_open:
212
222
  return
213
223
 
@@ -215,6 +225,17 @@ class Experiment:
215
225
  if self._storage:
216
226
  self._storage.flush()
217
227
 
228
+ # Update experiment status in remote mode
229
+ if self._client and self._experiment_id:
230
+ try:
231
+ self._client.update_experiment_status(
232
+ experiment_id=self._experiment_id,
233
+ status=status
234
+ )
235
+ except Exception as e:
236
+ # Log error but don't fail the close operation
237
+ print(f"Warning: Failed to update experiment status: {e}")
238
+
218
239
  self._is_open = False
219
240
 
220
241
  def __enter__(self) -> "Experiment":
@@ -222,8 +243,10 @@ class Experiment:
222
243
  return self.open()
223
244
 
224
245
  def __exit__(self, exc_type, exc_val, exc_tb):
225
- """Context manager exit."""
226
- self.close()
246
+ """Context manager exit. Sets status to FAILED if exception occurred."""
247
+ # Determine status based on whether an exception occurred
248
+ status = "FAILED" if exc_type is not None else "COMPLETED"
249
+ self.close(status=status)
227
250
  return False
228
251
 
229
252
  def log(
ml_dash/storage.py CHANGED
@@ -43,6 +43,7 @@ class LocalStorage:
43
43
  name: str,
44
44
  description: Optional[str] = None,
45
45
  tags: Optional[List[str]] = None,
46
+ bindrs: Optional[List[str]] = None,
46
47
  folder: Optional[str] = None,
47
48
  metadata: Optional[Dict[str, Any]] = None,
48
49
  ) -> Path:
@@ -54,6 +55,7 @@ class LocalStorage:
54
55
  name: Experiment name
55
56
  description: Optional description
56
57
  tags: Optional tags
58
+ bindrs: Optional bindrs
57
59
  folder: Optional folder path (used for organization)
58
60
  metadata: Optional metadata
59
61
 
@@ -79,6 +81,7 @@ class LocalStorage:
79
81
  "project": project,
80
82
  "description": description,
81
83
  "tags": tags or [],
84
+ "bindrs": bindrs or [],
82
85
  "folder": folder,
83
86
  "metadata": metadata,
84
87
  "created_at": datetime.utcnow().isoformat() + "Z",
@@ -99,6 +102,8 @@ class LocalStorage:
99
102
  existing["description"] = description
100
103
  if tags is not None:
101
104
  existing["tags"] = tags
105
+ if bindrs is not None:
106
+ existing["bindrs"] = bindrs
102
107
  if folder is not None:
103
108
  existing["folder"] = folder
104
109
  if metadata is not None:
@@ -288,7 +293,7 @@ class LocalStorage:
288
293
  """
289
294
  Write file to local storage.
290
295
 
291
- Copies file to: files/<file_id>/<filename>
296
+ Copies file to: files/<prefix>/<file_id>/<filename>
292
297
  Updates .files_metadata.json with file metadata
293
298
 
294
299
  Args:
@@ -317,8 +322,14 @@ class LocalStorage:
317
322
  # Generate Snowflake ID for file
318
323
  file_id = generate_snowflake_id()
319
324
 
320
- # Create file directory
321
- file_dir = files_dir / file_id
325
+ # Normalize prefix (remove leading slashes to avoid absolute paths)
326
+ normalized_prefix = prefix.lstrip("/") if prefix else ""
327
+
328
+ # Create prefix directory, then file directory
329
+ prefix_dir = files_dir / normalized_prefix if normalized_prefix else files_dir
330
+ prefix_dir.mkdir(parents=True, exist_ok=True)
331
+
332
+ file_dir = prefix_dir / file_id
322
333
  file_dir.mkdir(parents=True, exist_ok=True)
323
334
 
324
335
  # Copy file
@@ -363,7 +374,11 @@ class LocalStorage:
363
374
  if existing_index is not None:
364
375
  # Overwrite: remove old file and update metadata
365
376
  old_file = files_metadata["files"][existing_index]
366
- old_file_dir = files_dir / old_file["id"]
377
+ old_prefix = old_file["path"].lstrip("/") if old_file["path"] else ""
378
+ if old_prefix:
379
+ old_file_dir = files_dir / old_prefix / old_file["id"]
380
+ else:
381
+ old_file_dir = files_dir / old_file["id"]
367
382
  if old_file_dir.exists():
368
383
  shutil.rmtree(old_file_dir)
369
384
  files_metadata["files"][existing_index] = file_metadata
@@ -470,7 +485,11 @@ class LocalStorage:
470
485
  raise FileNotFoundError(f"File {file_id} not found")
471
486
 
472
487
  # Get source file
473
- source_file = files_dir / file_id / file_metadata["filename"]
488
+ file_prefix = file_metadata["path"].lstrip("/") if file_metadata["path"] else ""
489
+ if file_prefix:
490
+ source_file = files_dir / file_prefix / file_id / file_metadata["filename"]
491
+ else:
492
+ source_file = files_dir / file_id / file_metadata["filename"]
474
493
  if not source_file.exists():
475
494
  raise FileNotFoundError(f"File {file_id} not found on disk")
476
495
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ml-dash
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: ML experiment metricing and data storage
5
5
  Keywords: machine-learning,experiment-metricing,mlops,data-storage
6
6
  Author: Ge Yang, Tom Tao
@@ -67,6 +67,9 @@ A simple and flexible SDK for ML experiment metricing and data storage.
67
67
  - **Dual Operation Modes**: Remote (API server) or local (filesystem)
68
68
  - **Auto-creation**: Automatically creates namespace, project, and folder hierarchy
69
69
  - **Upsert Behavior**: Updates existing experiments or creates new ones
70
+ - **Experiment Lifecycle**: Automatic status tracking (RUNNING, COMPLETED, FAILED, CANCELLED)
71
+ - **Organized File Storage**: Prefix-based file organization with unique snowflake IDs
72
+ - **Rich Metadata**: Tags, bindrs, descriptions, and custom metadata support
70
73
  - **Simple API**: Minimal configuration, maximum flexibility
71
74
 
72
75
  ## Installation
@@ -1,12 +1,12 @@
1
1
  ml_dash/__init__.py,sha256=5tT0Lmf0SS3J7BOwJGVai8FOjdpjKGBJCEYL5nXnkLA,1384
2
- ml_dash/client.py,sha256=1T85L-YOCVRakzZlKCKaW6-kSDpo8gx_pdG8wvvi9Tc,16391
3
- ml_dash/experiment.py,sha256=MKQsEs1MQ07xbPHvWRvatyvLOXuiDKxJx7QWPfpmygM,27658
2
+ ml_dash/client.py,sha256=vhWcS5o2n3o4apEjVeLmu7flCEzxBbBOoLSQNcAx_ew,17267
3
+ ml_dash/experiment.py,sha256=x1jtQD1QroNjNULOxZiGtX5oFLi3ZXDaFbGHWt0yMJU,28652
4
4
  ml_dash/files.py,sha256=WKWbcug6XADwZruYQio1EdSstmfTsty9-2-t2KPWz38,9719
5
5
  ml_dash/log.py,sha256=0yXaNnFwYeBI3tRLHX3kkqWRpg0MbSGwmgjnOfsElCk,5350
6
6
  ml_dash/metric.py,sha256=PcEd6_HTLDpf-kBIDeQq2LlTRAS7xDx6EvSBpin5iuY,6456
7
7
  ml_dash/params.py,sha256=W-JkY1Mz7KdmvDjQ0HFV2QnpBov7Gf4dl70fuBnXTdo,5974
8
8
  ml_dash/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- ml_dash/storage.py,sha256=iVGPgRJnsUzxfTh12QCUKyPC-SiK4QNDWHsUBLxP0I0,29538
10
- ml_dash-0.5.0.dist-info/WHEEL,sha256=X16MKk8bp2DRsAuyteHJ-9qOjzmnY0x1aj0P1ftqqWA,78
11
- ml_dash-0.5.0.dist-info/METADATA,sha256=j1GGNmDvmp8REzN8AIsdiLRimiZqPz7OuEpAhf71Kys,5809
12
- ml_dash-0.5.0.dist-info/RECORD,,
9
+ ml_dash/storage.py,sha256=UTuux2nfclLrrtlkC6TsOvDB_wIbSDvYGg8Gtbvk6mc,30471
10
+ ml_dash-0.5.1.dist-info/WHEEL,sha256=X16MKk8bp2DRsAuyteHJ-9qOjzmnY0x1aj0P1ftqqWA,78
11
+ ml_dash-0.5.1.dist-info/METADATA,sha256=TBTqi4lJNoFO2eyztYJZptQypUSVTDVUdbGS0EnQZ2k,6067
12
+ ml_dash-0.5.1.dist-info/RECORD,,