ml-dash 0.5.1__py3-none-any.whl → 0.5.3__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/__init__.py CHANGED
@@ -38,7 +38,7 @@ Usage:
38
38
  experiment.log("Training started")
39
39
  """
40
40
 
41
- from .experiment import Experiment, ml_dash_experiment, OperationMode
41
+ from .experiment import Experiment, ml_dash_experiment, OperationMode, RunManager
42
42
  from .client import RemoteClient
43
43
  from .storage import LocalStorage
44
44
  from .log import LogLevel, LogBuilder
@@ -50,6 +50,7 @@ __all__ = [
50
50
  "Experiment",
51
51
  "ml_dash_experiment",
52
52
  "OperationMode",
53
+ "RunManager",
53
54
  "RemoteClient",
54
55
  "LocalStorage",
55
56
  "LogLevel",
ml_dash/auto_start.py ADDED
@@ -0,0 +1,42 @@
1
+ """
2
+ Auto-start module for ML-Dash SDK.
3
+
4
+ Provides a pre-configured, auto-started experiment singleton named 'dxp'.
5
+
6
+ Usage:
7
+ from ml_dash.auto_start import dxp
8
+
9
+ # Ready to use immediately - no need to open/start
10
+ dxp.log("Hello from dxp!")
11
+ dxp.params.set(lr=0.001)
12
+ dxp.metrics("loss").append(step=0, value=0.5)
13
+
14
+ # Automatically closed on Python exit
15
+ """
16
+
17
+ import atexit
18
+ from .experiment import Experiment
19
+
20
+ # Create pre-configured singleton experiment
21
+ dxp = Experiment(
22
+ name="dxp",
23
+ project="scratch",
24
+ local_path=".ml-dash"
25
+ )
26
+
27
+ # Auto-start the experiment on import
28
+ dxp.run.start()
29
+
30
+ # Register cleanup handler to complete experiment on Python exit
31
+ def _cleanup():
32
+ """Complete the dxp experiment on exit if still open."""
33
+ if dxp._is_open:
34
+ try:
35
+ dxp.run.complete()
36
+ except Exception:
37
+ # Silently ignore errors during cleanup
38
+ pass
39
+
40
+ atexit.register(_cleanup)
41
+
42
+ __all__ = ["dxp"]
ml_dash/experiment.py CHANGED
@@ -27,6 +27,74 @@ class OperationMode(Enum):
27
27
  HYBRID = "hybrid" # Future: sync local to remote
28
28
 
29
29
 
30
+ class RunManager:
31
+ """
32
+ Lifecycle manager for experiments.
33
+
34
+ Supports three usage patterns:
35
+ 1. Method calls: experiment.run.start(), experiment.run.complete()
36
+ 2. Context manager: with Experiment(...).run as exp:
37
+ 3. Decorator: @exp.run or @Experiment(...).run
38
+ """
39
+
40
+ def __init__(self, experiment: "Experiment"):
41
+ """
42
+ Initialize RunManager.
43
+
44
+ Args:
45
+ experiment: Parent Experiment instance
46
+ """
47
+ self._experiment = experiment
48
+
49
+ def start(self) -> "Experiment":
50
+ """
51
+ Start the experiment (sets status to RUNNING).
52
+
53
+ Returns:
54
+ The experiment instance for chaining
55
+ """
56
+ return self._experiment._open()
57
+
58
+ def complete(self) -> None:
59
+ """Mark experiment as completed (status: COMPLETED)."""
60
+ self._experiment._close(status="COMPLETED")
61
+
62
+ def fail(self) -> None:
63
+ """Mark experiment as failed (status: FAILED)."""
64
+ self._experiment._close(status="FAILED")
65
+
66
+ def cancel(self) -> None:
67
+ """Mark experiment as cancelled (status: CANCELLED)."""
68
+ self._experiment._close(status="CANCELLED")
69
+
70
+ def __enter__(self) -> "Experiment":
71
+ """Context manager entry - starts the experiment."""
72
+ return self.start()
73
+
74
+ def __exit__(self, exc_type, exc_val, exc_tb):
75
+ """Context manager exit - completes or fails the experiment."""
76
+ if exc_type is not None:
77
+ self.fail()
78
+ else:
79
+ self.complete()
80
+ return False
81
+
82
+ def __call__(self, func: Callable) -> Callable:
83
+ """
84
+ Decorator support for wrapping functions with experiment lifecycle.
85
+
86
+ Usage:
87
+ @exp.run
88
+ def train(exp):
89
+ exp.log("Training...")
90
+ """
91
+ @functools.wraps(func)
92
+ def wrapper(*args, **kwargs):
93
+ with self as exp:
94
+ return func(exp, *args, **kwargs)
95
+ return wrapper
96
+
97
+
30
98
  class Experiment:
31
99
  """
32
100
  ML-Dash experiment for metricing experiments.
@@ -67,13 +135,14 @@ class Experiment:
67
135
  tags: Optional[List[str]] = None,
68
136
  bindrs: Optional[List[str]] = None,
69
137
  folder: Optional[str] = None,
70
- write_protected: bool = False,
71
138
  metadata: Optional[Dict[str, Any]] = None,
72
139
  # Mode configuration
73
140
  remote: Optional[str] = None,
74
141
  api_key: Optional[str] = None,
75
142
  user_name: Optional[str] = None,
76
143
  local_path: Optional[str] = None,
144
+ # Internal parameters
145
+ _write_protected: bool = False,
77
146
  ):
78
147
  """
79
148
  Initialize an ML-Dash experiment.
@@ -85,12 +154,12 @@ class Experiment:
85
154
  tags: Optional list of tags
86
155
  bindrs: Optional list of bindrs
87
156
  folder: Optional folder path (e.g., "/experiments/baseline")
88
- write_protected: If True, experiment becomes immutable after creation
89
157
  metadata: Optional metadata dict
90
158
  remote: Remote API URL (e.g., "http://localhost:3000")
91
159
  api_key: JWT token for authentication (if not provided, will be generated from user_name)
92
160
  user_name: Username for authentication (generates API key if api_key not provided)
93
161
  local_path: Local storage root path (for local mode)
162
+ _write_protected: Internal parameter - if True, experiment becomes immutable after creation
94
163
  """
95
164
  self.name = name
96
165
  self.project = project
@@ -98,7 +167,7 @@ class Experiment:
98
167
  self.tags = tags
99
168
  self.bindrs = bindrs
100
169
  self.folder = folder
101
- self.write_protected = write_protected
170
+ self._write_protected = _write_protected
102
171
  self.metadata = metadata
103
172
 
104
173
  # Generate API key from username if not provided
@@ -171,9 +240,9 @@ class Experiment:
171
240
 
172
241
  return token
173
242
 
174
- def open(self) -> "Experiment":
243
+ def _open(self) -> "Experiment":
175
244
  """
176
- Open the experiment (create or update on server/filesystem).
245
+ Internal method to open the experiment (create or update on server/filesystem).
177
246
 
178
247
  Returns:
179
248
  self for chaining
@@ -190,7 +259,7 @@ class Experiment:
190
259
  tags=self.tags,
191
260
  bindrs=self.bindrs,
192
261
  folder=self.folder,
193
- write_protected=self.write_protected,
262
+ write_protected=self._write_protected,
194
263
  metadata=self.metadata,
195
264
  )
196
265
  self._experiment_data = response
@@ -211,9 +280,9 @@ class Experiment:
211
280
  self._is_open = True
212
281
  return self
213
282
 
214
- def close(self, status: str = "COMPLETED"):
283
+ def _close(self, status: str = "COMPLETED"):
215
284
  """
216
- Close the experiment and update status.
285
+ Internal method to close the experiment and update status.
217
286
 
218
287
  Args:
219
288
  status: Status to set - "COMPLETED" (default), "FAILED", or "CANCELLED"
@@ -238,16 +307,52 @@ class Experiment:
238
307
 
239
308
  self._is_open = False
240
309
 
241
- def __enter__(self) -> "Experiment":
242
- """Context manager entry."""
243
- return self.open()
310
+ @property
311
+ def run(self) -> RunManager:
312
+ """
313
+ Get the RunManager for lifecycle operations.
244
314
 
245
- def __exit__(self, exc_type, exc_val, exc_tb):
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)
250
- return False
315
+ Usage:
316
+ # Method calls
317
+ experiment.run.start()
318
+ experiment.run.complete()
319
+
320
+ # Context manager
321
+ with Experiment(...).run as exp:
322
+ exp.log("Training...")
323
+
324
+ # Decorator
325
+ @experiment.run
326
+ def train(exp):
327
+ exp.log("Training...")
328
+
329
+ Returns:
330
+ RunManager instance
331
+ """
332
+ return RunManager(self)
333
+
334
+ @property
335
+ def params(self) -> ParametersBuilder:
336
+ """
337
+ Get a ParametersBuilder for parameter operations.
338
+
339
+ Usage:
340
+ # Set parameters
341
+ experiment.params.set(lr=0.001, batch_size=32)
342
+
343
+ # Get parameters
344
+ params = experiment.params.get()
345
+
346
+ Returns:
347
+ ParametersBuilder instance
348
+
349
+ Raises:
350
+ RuntimeError: If experiment is not open
351
+ """
352
+ if not self._is_open:
353
+ raise RuntimeError("Experiment not open. Use experiment.run.start() or context manager.")
354
+
355
+ return ParametersBuilder(self)
251
356
 
252
357
  def log(
253
358
  self,
@@ -591,31 +696,6 @@ class Experiment:
591
696
 
592
697
  return result
593
698
 
594
- def parameters(self) -> ParametersBuilder:
595
- """
596
- Get a ParametersBuilder for fluent parameter operations.
597
-
598
- Returns:
599
- ParametersBuilder instance for chaining
600
-
601
- Raises:
602
- RuntimeError: If experiment is not open
603
-
604
- Examples:
605
- # Set parameters
606
- experiment.parameters().set(
607
- model={"lr": 0.001, "batch_size": 32},
608
- optimizer="adam"
609
- )
610
-
611
- # Get parameters
612
- params = experiment.parameters().get() # Flattened
613
- params = experiment.parameters().get(flatten=False) # Nested
614
- """
615
- if not self._is_open:
616
- raise RuntimeError("Experiment not open. Use experiment.open() or context manager.")
617
-
618
- return ParametersBuilder(self)
619
699
 
620
700
  def _write_params(self, flattened_params: Dict[str, Any]) -> None:
621
701
  """
@@ -665,48 +745,49 @@ class Experiment:
665
745
 
666
746
  return params
667
747
 
668
- def metric(self, name: str, description: Optional[str] = None,
669
- tags: Optional[List[str]] = None, metadata: Optional[Dict[str, Any]] = None) -> 'MetricBuilder':
748
+ @property
749
+ def metrics(self) -> 'MetricsManager':
670
750
  """
671
- Get a MetricBuilder for fluent metric operations.
751
+ Get a MetricsManager for metric operations.
672
752
 
673
- Args:
674
- name: Metric name (unique within experiment)
675
- description: Optional metric description
676
- tags: Optional tags for categorization
677
- metadata: Optional structured metadata
753
+ Supports two usage patterns:
754
+ 1. Named: experiment.metrics("loss").append(value=0.5, step=1)
755
+ 2. Unnamed: experiment.metrics.append(name="loss", value=0.5, step=1)
678
756
 
679
757
  Returns:
680
- MetricBuilder instance for chaining
758
+ MetricsManager instance
681
759
 
682
760
  Raises:
683
761
  RuntimeError: If experiment is not open
684
762
 
685
763
  Examples:
686
- # Append single data point
687
- experiment.metric(name="train_loss").append(value=0.5, step=100)
764
+ # Named metric
765
+ experiment.metrics("train_loss").append(value=0.5, step=100)
766
+
767
+ # Unnamed (name in append call)
768
+ experiment.metrics.append(name="train_loss", value=0.5, step=100)
688
769
 
689
770
  # Append batch
690
- experiment.metric(name="metrics").append_batch([
771
+ experiment.metrics("metrics").append_batch([
691
772
  {"loss": 0.5, "acc": 0.8, "step": 1},
692
773
  {"loss": 0.4, "acc": 0.85, "step": 2}
693
774
  ])
694
775
 
695
776
  # Read data
696
- data = experiment.metric(name="train_loss").read(start_index=0, limit=100)
777
+ data = experiment.metrics("train_loss").read(start_index=0, limit=100)
697
778
 
698
779
  # Get statistics
699
- stats = experiment.metric(name="train_loss").stats()
780
+ stats = experiment.metrics("train_loss").stats()
700
781
  """
701
- from .metric import MetricBuilder
782
+ from .metric import MetricsManager
702
783
 
703
784
  if not self._is_open:
704
785
  raise RuntimeError(
705
- "Cannot use metric on closed experiment. "
706
- "Use 'with Experiment(...) as experiment:' or call experiment.open() first."
786
+ "Cannot use metrics on closed experiment. "
787
+ "Use 'with Experiment(...).run as experiment:' or call experiment.run.start() first."
707
788
  )
708
789
 
709
- return MetricBuilder(self, name, description, tags, metadata)
790
+ return MetricsManager(self)
710
791
 
711
792
  def _append_to_metric(
712
793
  self,
@@ -931,7 +1012,7 @@ def ml_dash_experiment(
931
1012
  def decorator(func: Callable) -> Callable:
932
1013
  @functools.wraps(func)
933
1014
  def wrapper(*args, **func_kwargs):
934
- with Experiment(name=name, project=project, **kwargs) as experiment:
1015
+ with Experiment(name=name, project=project, **kwargs).run as experiment:
935
1016
  # Inject experiment into function kwargs
936
1017
  func_kwargs['experiment'] = experiment
937
1018
  return func(*args, **func_kwargs)
ml_dash/files.py CHANGED
@@ -44,6 +44,7 @@ class FileBuilder:
44
44
  - prefix: Logical path prefix (default: "/")
45
45
  - description: Optional description
46
46
  - tags: Optional list of tags
47
+ - bindrs: Optional list of bindrs
47
48
  - metadata: Optional metadata dict
48
49
  - file_id: File ID for download/delete/update operations
49
50
  - dest_path: Destination path for download
@@ -53,6 +54,7 @@ class FileBuilder:
53
54
  self._prefix = kwargs.get('prefix', '/')
54
55
  self._description = kwargs.get('description')
55
56
  self._tags = kwargs.get('tags', [])
57
+ self._bindrs = kwargs.get('bindrs', [])
56
58
  self._metadata = kwargs.get('metadata')
57
59
  self._file_id = kwargs.get('file_id')
58
60
  self._dest_path = kwargs.get('dest_path')
@@ -76,7 +78,7 @@ class FileBuilder:
76
78
  if not self._experiment._is_open:
77
79
  raise RuntimeError("Experiment not open. Use experiment.open() or context manager.")
78
80
 
79
- if self._experiment.write_protected:
81
+ if self._experiment._write_protected:
80
82
  raise RuntimeError("Experiment is write-protected and cannot be modified.")
81
83
 
82
84
  if not self._file_path:
@@ -189,7 +191,7 @@ class FileBuilder:
189
191
  if not self._experiment._is_open:
190
192
  raise RuntimeError("Experiment not open. Use experiment.open() or context manager.")
191
193
 
192
- if self._experiment.write_protected:
194
+ if self._experiment._write_protected:
193
195
  raise RuntimeError("Experiment is write-protected and cannot be modified.")
194
196
 
195
197
  if not self._file_id:
@@ -219,7 +221,7 @@ class FileBuilder:
219
221
  if not self._experiment._is_open:
220
222
  raise RuntimeError("Experiment not open. Use experiment.open() or context manager.")
221
223
 
222
- if self._experiment.write_protected:
224
+ if self._experiment._write_protected:
223
225
  raise RuntimeError("Experiment is write-protected and cannot be modified.")
224
226
 
225
227
  if not self._file_id:
@@ -232,6 +234,186 @@ class FileBuilder:
232
234
  metadata=self._metadata
233
235
  )
234
236
 
237
+ def save_json(self, content: Any, file_name: str) -> Dict[str, Any]:
238
+ """
239
+ Save JSON content to a file.
240
+
241
+ Args:
242
+ content: Content to save as JSON (dict, list, or any JSON-serializable object)
243
+ file_name: Name of the file to create
244
+
245
+ Returns:
246
+ File metadata dict with id, path, filename, checksum, etc.
247
+
248
+ Raises:
249
+ RuntimeError: If experiment is not open or write-protected
250
+ ValueError: If content is not JSON-serializable
251
+
252
+ Examples:
253
+ config = {"model": "resnet50", "lr": 0.001}
254
+ result = experiment.file(prefix="/configs").save_json(config, "config.json")
255
+ """
256
+ import json
257
+ import tempfile
258
+ import os
259
+
260
+ if not self._experiment._is_open:
261
+ raise RuntimeError("Experiment not open. Use experiment.run.start() or context manager.")
262
+
263
+ if self._experiment._write_protected:
264
+ raise RuntimeError("Experiment is write-protected and cannot be modified.")
265
+
266
+ # Create temporary file with desired filename
267
+ temp_dir = tempfile.mkdtemp()
268
+ temp_path = os.path.join(temp_dir, file_name)
269
+ try:
270
+ # Write JSON content to temp file
271
+ with open(temp_path, 'w') as f:
272
+ json.dump(content, f, indent=2)
273
+
274
+ # Save using existing save() method
275
+ original_file_path = self._file_path
276
+ self._file_path = temp_path
277
+
278
+ # Upload and get result
279
+ result = self.save()
280
+
281
+ # Restore original file_path
282
+ self._file_path = original_file_path
283
+
284
+ return result
285
+ finally:
286
+ # Clean up temp file and directory
287
+ try:
288
+ os.unlink(temp_path)
289
+ os.rmdir(temp_dir)
290
+ except Exception:
291
+ pass
292
+
293
+ def save_torch(self, model: Any, file_name: str) -> Dict[str, Any]:
294
+ """
295
+ Save PyTorch model to a file.
296
+
297
+ Args:
298
+ model: PyTorch model or state dict to save
299
+ file_name: Name of the file to create (should end with .pt or .pth)
300
+
301
+ Returns:
302
+ File metadata dict with id, path, filename, checksum, etc.
303
+
304
+ Raises:
305
+ RuntimeError: If experiment is not open or write-protected
306
+ ImportError: If torch is not installed
307
+ ValueError: If model cannot be saved
308
+
309
+ Examples:
310
+ import torch
311
+ model = torch.nn.Linear(10, 5)
312
+ result = experiment.file(prefix="/models").save_torch(model, "model.pt")
313
+
314
+ # Or save state dict
315
+ result = experiment.file(prefix="/models").save_torch(model.state_dict(), "model.pth")
316
+ """
317
+ import tempfile
318
+ import os
319
+
320
+ try:
321
+ import torch
322
+ except ImportError:
323
+ raise ImportError("PyTorch is not installed. Install it with: pip install torch")
324
+
325
+ if not self._experiment._is_open:
326
+ raise RuntimeError("Experiment not open. Use experiment.run.start() or context manager.")
327
+
328
+ if self._experiment._write_protected:
329
+ raise RuntimeError("Experiment is write-protected and cannot be modified.")
330
+
331
+ # Create temporary file with desired filename
332
+ temp_dir = tempfile.mkdtemp()
333
+ temp_path = os.path.join(temp_dir, file_name)
334
+
335
+ try:
336
+ # Save model to temp file
337
+ torch.save(model, temp_path)
338
+
339
+ # Save using existing save() method
340
+ original_file_path = self._file_path
341
+ self._file_path = temp_path
342
+
343
+ # Upload and get result
344
+ result = self.save()
345
+
346
+ # Restore original file_path
347
+ self._file_path = original_file_path
348
+
349
+ return result
350
+ finally:
351
+ # Clean up temp file and directory
352
+ try:
353
+ os.unlink(temp_path)
354
+ os.rmdir(temp_dir)
355
+ except Exception:
356
+ pass
357
+
358
+ def save_pkl(self, content: Any, file_name: str) -> Dict[str, Any]:
359
+ """
360
+ Save Python object to a pickle file.
361
+
362
+ Args:
363
+ content: Python object to pickle (must be pickle-serializable)
364
+ file_name: Name of the file to create (should end with .pkl or .pickle)
365
+
366
+ Returns:
367
+ File metadata dict with id, path, filename, checksum, etc.
368
+
369
+ Raises:
370
+ RuntimeError: If experiment is not open or write-protected
371
+ ValueError: If content cannot be pickled
372
+
373
+ Examples:
374
+ data = {"model": "resnet50", "weights": np.array([1, 2, 3])}
375
+ result = experiment.file(prefix="/data").save_pkl(data, "data.pkl")
376
+
377
+ # Or save any Python object
378
+ result = experiment.file(prefix="/models").save_pkl(trained_model, "model.pickle")
379
+ """
380
+ import pickle
381
+ import tempfile
382
+ import os
383
+
384
+ if not self._experiment._is_open:
385
+ raise RuntimeError("Experiment not open. Use experiment.run.start() or context manager.")
386
+
387
+ if self._experiment._write_protected:
388
+ raise RuntimeError("Experiment is write-protected and cannot be modified.")
389
+
390
+ # Create temporary file with desired filename
391
+ temp_dir = tempfile.mkdtemp()
392
+ temp_path = os.path.join(temp_dir, file_name)
393
+ try:
394
+ # Write pickled content to temp file
395
+ with open(temp_path, 'wb') as f:
396
+ pickle.dump(content, f)
397
+
398
+ # Save using existing save() method
399
+ original_file_path = self._file_path
400
+ self._file_path = temp_path
401
+
402
+ # Upload and get result
403
+ result = self.save()
404
+
405
+ # Restore original file_path
406
+ self._file_path = original_file_path
407
+
408
+ return result
409
+ finally:
410
+ # Clean up temp file and directory
411
+ try:
412
+ os.unlink(temp_path)
413
+ os.rmdir(temp_dir)
414
+ except Exception:
415
+ pass
416
+
235
417
 
236
418
  def compute_sha256(file_path: str) -> str:
237
419
  """
ml_dash/metric.py CHANGED
@@ -11,6 +11,99 @@ if TYPE_CHECKING:
11
11
  from .experiment import Experiment
12
12
 
13
13
 
14
+ class MetricsManager:
15
+ """
16
+ Manager for metric operations that supports both named and unnamed usage.
17
+
18
+ Supports two usage patterns:
19
+ 1. Named: experiment.metrics("loss").append(value=0.5, step=1)
20
+ 2. Unnamed: experiment.metrics.append(name="loss", value=0.5, step=1)
21
+
22
+ Usage:
23
+ # With explicit metric name
24
+ experiment.metrics("train_loss").append(value=0.5, step=100)
25
+
26
+ # Without specifying name upfront (name in append call)
27
+ experiment.metrics.append(name="train_loss", value=0.5, step=100)
28
+ """
29
+
30
+ def __init__(self, experiment: 'Experiment'):
31
+ """
32
+ Initialize MetricsManager.
33
+
34
+ Args:
35
+ experiment: Parent Experiment instance
36
+ """
37
+ self._experiment = experiment
38
+
39
+ def __call__(self, name: str, description: Optional[str] = None,
40
+ tags: Optional[List[str]] = None, metadata: Optional[Dict[str, Any]] = None) -> 'MetricBuilder':
41
+ """
42
+ Get a MetricBuilder for a specific metric name.
43
+
44
+ Args:
45
+ name: Metric name (unique within experiment)
46
+ description: Optional metric description
47
+ tags: Optional tags for categorization
48
+ metadata: Optional structured metadata
49
+
50
+ Returns:
51
+ MetricBuilder instance for the named metric
52
+
53
+ Examples:
54
+ experiment.metrics("loss").append(value=0.5, step=1)
55
+ """
56
+ return MetricBuilder(self._experiment, name, description, tags, metadata)
57
+
58
+ def append(self, name: str, data: Optional[Dict[str, Any]] = None, **kwargs) -> Dict[str, Any]:
59
+ """
60
+ Append a data point to a metric (name specified in call).
61
+
62
+ Args:
63
+ name: Metric name
64
+ data: Data dict (alternative to kwargs)
65
+ **kwargs: Data as keyword arguments
66
+
67
+ Returns:
68
+ Response dict with metric metadata
69
+
70
+ Examples:
71
+ experiment.metrics.append(name="loss", value=0.5, step=1)
72
+ experiment.metrics.append(name="loss", data={"value": 0.5, "step": 1})
73
+ """
74
+ if data is None:
75
+ data = kwargs
76
+ return self._experiment._append_to_metric(name, data, None, None, None)
77
+
78
+ def append_batch(self, name: str, data_points: List[Dict[str, Any]],
79
+ description: Optional[str] = None,
80
+ tags: Optional[List[str]] = None,
81
+ metadata: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
82
+ """
83
+ Append multiple data points to a metric.
84
+
85
+ Args:
86
+ name: Metric name
87
+ data_points: List of data point dicts
88
+ description: Optional metric description
89
+ tags: Optional tags for categorization
90
+ metadata: Optional structured metadata
91
+
92
+ Returns:
93
+ Response dict with metric metadata
94
+
95
+ Examples:
96
+ experiment.metrics.append_batch(
97
+ name="loss",
98
+ data_points=[
99
+ {"value": 0.5, "step": 1},
100
+ {"value": 0.4, "step": 2}
101
+ ]
102
+ )
103
+ """
104
+ return self._experiment._append_batch_to_metric(name, data_points, description, tags, metadata)
105
+
106
+
14
107
  class MetricBuilder:
15
108
  """
16
109
  Builder for metric operations.
ml_dash/params.py CHANGED
@@ -62,9 +62,9 @@ class ParametersBuilder:
62
62
  experiment.parameters().set(**{"model.lr": 0.001, "model.batch_size": 32})
63
63
  """
64
64
  if not self._experiment._is_open:
65
- raise RuntimeError("Experiment not open. Use experiment.open() or context manager.")
65
+ raise RuntimeError("Experiment not open. Use experiment.run.start() or context manager.")
66
66
 
67
- if self._experiment.write_protected:
67
+ if self._experiment._write_protected:
68
68
  raise RuntimeError("Experiment is write-protected and cannot be modified.")
69
69
 
70
70
  # Flatten the kwargs
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ml-dash
3
- Version: 0.5.1
4
- Summary: ML experiment metricing and data storage
5
- Keywords: machine-learning,experiment-metricing,mlops,data-storage
3
+ Version: 0.5.3
4
+ Summary: ML experiment tracking and data storage
5
+ Keywords: machine-learning,experiment-tracking,mlops,data-storage
6
6
  Author: Ge Yang, Tom Tao
7
7
  License: MIT License
8
8
 
@@ -97,7 +97,7 @@ pip install ml-dash
97
97
  </tr>
98
98
  </table>
99
99
 
100
- ## Quick Start
100
+ ## Getting Started
101
101
 
102
102
  ### Remote Mode (with API Server)
103
103
 
@@ -107,7 +107,7 @@ from ml_dash import Experiment
107
107
  with Experiment(
108
108
  name="my-experiment",
109
109
  project="my-project",
110
- remote="https://cu3thurmv3.us-east-1.awsapprunner.com",
110
+ remote="https://api.dash.ml",
111
111
  api_key="your-jwt-token"
112
112
  ) as experiment:
113
113
  print(f"Experiment ID: {experiment.id}")
@@ -0,0 +1,13 @@
1
+ ml_dash/__init__.py,sha256=o_LrWVJBY_VkUGhSBs5wdb_NqEsHD1AK9HGsjZGxHxQ,1414
2
+ ml_dash/auto_start.py,sha256=c3XcXFpZdvjtWauEoK5043Gw9k0L_5IDq4fdiB2ha88,959
3
+ ml_dash/client.py,sha256=vhWcS5o2n3o4apEjVeLmu7flCEzxBbBOoLSQNcAx_ew,17267
4
+ ml_dash/experiment.py,sha256=zdGB3oZsFNFyg9olRazWk7dTO7tfy-vTa4neFq5i2CY,30552
5
+ ml_dash/files.py,sha256=Nx7f0n9zoQ7XqPtiHu_SBEZK0cT2b3F6-EhJBNs3C6k,15824
6
+ ml_dash/log.py,sha256=0yXaNnFwYeBI3tRLHX3kkqWRpg0MbSGwmgjnOfsElCk,5350
7
+ ml_dash/metric.py,sha256=LMb6-T08VAl6UBAv6FlZee6LleVLjFaBNc19b17NlfI,9662
8
+ ml_dash/params.py,sha256=xaByDSVar4D1pZqxTANkMPeZTL5-V7ewJe5TXfPLhMQ,5980
9
+ ml_dash/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ ml_dash/storage.py,sha256=UTuux2nfclLrrtlkC6TsOvDB_wIbSDvYGg8Gtbvk6mc,30471
11
+ ml_dash-0.5.3.dist-info/WHEEL,sha256=X16MKk8bp2DRsAuyteHJ-9qOjzmnY0x1aj0P1ftqqWA,78
12
+ ml_dash-0.5.3.dist-info/METADATA,sha256=AO80zIp9TG2-3S-zZEPQUOwFBowKCddCfQarQem096M,6043
13
+ ml_dash-0.5.3.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- ml_dash/__init__.py,sha256=5tT0Lmf0SS3J7BOwJGVai8FOjdpjKGBJCEYL5nXnkLA,1384
2
- ml_dash/client.py,sha256=vhWcS5o2n3o4apEjVeLmu7flCEzxBbBOoLSQNcAx_ew,17267
3
- ml_dash/experiment.py,sha256=x1jtQD1QroNjNULOxZiGtX5oFLi3ZXDaFbGHWt0yMJU,28652
4
- ml_dash/files.py,sha256=WKWbcug6XADwZruYQio1EdSstmfTsty9-2-t2KPWz38,9719
5
- ml_dash/log.py,sha256=0yXaNnFwYeBI3tRLHX3kkqWRpg0MbSGwmgjnOfsElCk,5350
6
- ml_dash/metric.py,sha256=PcEd6_HTLDpf-kBIDeQq2LlTRAS7xDx6EvSBpin5iuY,6456
7
- ml_dash/params.py,sha256=W-JkY1Mz7KdmvDjQ0HFV2QnpBov7Gf4dl70fuBnXTdo,5974
8
- ml_dash/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
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,,