ml-dash 0.5.9__py3-none-any.whl → 0.6.0__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/params.py CHANGED
@@ -6,6 +6,7 @@ Nested dicts are flattened to dot-notation: {"model": {"lr": 0.001}} → {"model
6
6
  """
7
7
 
8
8
  from typing import Dict, Any, Optional, TYPE_CHECKING
9
+ import inspect
9
10
 
10
11
  if TYPE_CHECKING:
11
12
  from .experiment import Experiment
@@ -62,13 +63,21 @@ class ParametersBuilder:
62
63
  experiment.parameters().set(**{"model.lr": 0.001, "model.batch_size": 32})
63
64
  """
64
65
  if not self._experiment._is_open:
65
- raise RuntimeError("Experiment not open. Use experiment.run.start() or context manager.")
66
+ raise RuntimeError(
67
+ "Experiment not started. Use 'with experiment.run:' or call experiment.run.start() first.\n"
68
+ "Example:\n"
69
+ " with dxp.run:\n"
70
+ " dxp.params.set(lr=0.001)"
71
+ )
66
72
 
67
73
  if self._experiment._write_protected:
68
74
  raise RuntimeError("Experiment is write-protected and cannot be modified.")
69
75
 
76
+ # Convert class objects to dicts (for params_proto support)
77
+ processed_kwargs = self._process_class_objects(kwargs)
78
+
70
79
  # Flatten the kwargs
71
- flattened = self.flatten_dict(kwargs)
80
+ flattened = self.flatten_dict(processed_kwargs)
72
81
 
73
82
  if not flattened:
74
83
  # No parameters to set, just return
@@ -79,6 +88,43 @@ class ParametersBuilder:
79
88
 
80
89
  return self
81
90
 
91
+ def log(self, **kwargs) -> 'ParametersBuilder':
92
+ """
93
+ Alias for set(). Sets/merges parameters.
94
+
95
+ This method exists for better parameter organization and semantic clarity.
96
+ It behaves exactly the same as set().
97
+
98
+ Nested dicts are automatically flattened:
99
+ log(model={"lr": 0.001, "batch_size": 32})
100
+ → {"model.lr": 0.001, "model.batch_size": 32}
101
+
102
+ Args:
103
+ **kwargs: Parameters to set (can be nested dicts)
104
+
105
+ Returns:
106
+ Self for potential chaining
107
+
108
+ Raises:
109
+ RuntimeError: If experiment is not open
110
+ RuntimeError: If experiment is write-protected
111
+
112
+ Examples:
113
+ # Set parameters using log() - same as set()
114
+ experiment.params.log(
115
+ learning_rate=0.001,
116
+ batch_size=32,
117
+ model="resnet50"
118
+ )
119
+
120
+ # Track parameter changes during training
121
+ for epoch in range(10):
122
+ if epoch == 5:
123
+ experiment.params.log(learning_rate=0.0001) # Log LR decay
124
+ """
125
+ # Just call set() - they behave exactly the same
126
+ return self.set(**kwargs)
127
+
82
128
  def get(self, flatten: bool = True) -> Dict[str, Any]:
83
129
  """
84
130
  Get parameters from the experiment.
@@ -103,7 +149,12 @@ class ParametersBuilder:
103
149
  # → {"model": {"lr": 0.001, "batch_size": 32}, "optimizer": "adam"}
104
150
  """
105
151
  if not self._experiment._is_open:
106
- raise RuntimeError("Experiment not open. Use experiment.open() or context manager.")
152
+ raise RuntimeError(
153
+ "Experiment not started. Use 'with experiment.run:' or call experiment.run.start() first.\n"
154
+ "Example:\n"
155
+ " with dxp.run:\n"
156
+ " dxp.params.get()"
157
+ )
107
158
 
108
159
  # Read parameters through experiment
109
160
  params = self._experiment._read_params()
@@ -186,3 +237,41 @@ class ParametersBuilder:
186
237
  current[parts[-1]] = value
187
238
 
188
239
  return result
240
+
241
+ @staticmethod
242
+ def _process_class_objects(d: Dict[str, Any]) -> Dict[str, Any]:
243
+ """
244
+ Convert class objects to dicts by extracting their attributes.
245
+
246
+ This enables passing configuration classes directly:
247
+ dxp.params.log(Args=Args) # Args is a class
248
+ → {"Args": {"batch_size": 64, "lr": 0.001, ...}}
249
+
250
+ Args:
251
+ d: Dictionary that may contain class objects as values
252
+
253
+ Returns:
254
+ Dictionary with class objects converted to attribute dicts
255
+
256
+ Examples:
257
+ >>> class Args:
258
+ ... batch_size = 64
259
+ ... lr = 0.001
260
+ >>> _process_class_objects({"Args": Args})
261
+ {"Args": {"batch_size": 64, "lr": 0.001}}
262
+ """
263
+ result = {}
264
+ for key, value in d.items():
265
+ if inspect.isclass(value):
266
+ # Extract class attributes (skip private/magic and callables)
267
+ attrs = {}
268
+ for attr_name, attr_value in vars(value).items():
269
+ if not attr_name.startswith('_') and not callable(attr_value):
270
+ # Recursively handle nested types
271
+ if isinstance(attr_value, type):
272
+ continue # Skip type annotations
273
+ attrs[attr_name] = attr_value
274
+ result[key] = attrs
275
+ else:
276
+ result[key] = value
277
+ return result
@@ -0,0 +1,55 @@
1
+ """
2
+ Pre-configured remote experiment singleton for ML-Dash SDK.
3
+
4
+ Provides a pre-configured experiment singleton named 'rdxp' that uses remote mode.
5
+ Requires manual start using 'with' statement or explicit start() call.
6
+
7
+ IMPORTANT: Before using rdxp, you must authenticate with the ML-Dash server:
8
+ # First time setup - authenticate with the server
9
+ python -m ml_dash.cli login
10
+
11
+ Usage:
12
+ from ml_dash import rdxp
13
+
14
+ # Use with statement (recommended)
15
+ with rdxp.run:
16
+ rdxp.log().info("Hello from rdxp!")
17
+ rdxp.params.set(lr=0.001)
18
+ rdxp.metrics("loss").append(step=0, value=0.5)
19
+ # Automatically completes on exit from with block
20
+
21
+ # Or start/complete manually
22
+ rdxp.run.start()
23
+ rdxp.log().info("Training...")
24
+ rdxp.run.complete()
25
+
26
+ Configuration:
27
+ - Default server: https://api.dash.ml
28
+ - To use a different server, set MLDASH_API_URL environment variable
29
+ - Authentication token is auto-loaded from secure storage
30
+ """
31
+
32
+ import atexit
33
+ from .experiment import Experiment
34
+
35
+ # Create pre-configured singleton experiment for remote mode
36
+ # Uses remote API server - token auto-loaded from storage
37
+ rdxp = Experiment(
38
+ name="rdxp",
39
+ project="scratch",
40
+ remote="https://api.dash.ml"
41
+ )
42
+
43
+ # Register cleanup handler to complete experiment on Python exit (if still open)
44
+ def _cleanup():
45
+ """Complete the rdxp experiment on exit if still open."""
46
+ if rdxp._is_open:
47
+ try:
48
+ rdxp.run.complete()
49
+ except Exception:
50
+ # Silently ignore errors during cleanup
51
+ pass
52
+
53
+ atexit.register(_cleanup)
54
+
55
+ __all__ = ["rdxp"]
ml_dash/storage.py CHANGED
@@ -189,9 +189,10 @@ class LocalStorage:
189
189
  self,
190
190
  project: str,
191
191
  experiment: str,
192
- message: str,
193
- level: str,
194
- timestamp: str,
192
+ folder: Optional[str] = None,
193
+ message: str = "",
194
+ level: str = "info",
195
+ timestamp: str = "",
195
196
  metadata: Optional[Dict[str, Any]] = None,
196
197
  ):
197
198
  """
@@ -200,12 +201,13 @@ class LocalStorage:
200
201
  Args:
201
202
  project: Project name
202
203
  experiment: Experiment name
204
+ folder: Optional folder path
203
205
  message: Log message
204
206
  level: Log level
205
207
  timestamp: ISO timestamp string
206
208
  metadata: Optional metadata
207
209
  """
208
- experiment_dir = self._get_experiment_dir(project, experiment)
210
+ experiment_dir = self._get_experiment_dir(project, experiment, folder)
209
211
  logs_dir = experiment_dir / "logs"
210
212
  logs_file = logs_dir / "logs.jsonl"
211
213
  seq_file = logs_dir / ".log_sequence"
@@ -269,7 +271,8 @@ class LocalStorage:
269
271
  self,
270
272
  project: str,
271
273
  experiment: str,
272
- data: Dict[str, Any],
274
+ folder: Optional[str] = None,
275
+ data: Optional[Dict[str, Any]] = None,
273
276
  ):
274
277
  """
275
278
  Write/merge parameters. Always merges with existing parameters.
@@ -284,9 +287,12 @@ class LocalStorage:
284
287
  Args:
285
288
  project: Project name
286
289
  experiment: Experiment name
290
+ folder: Optional folder path
287
291
  data: Flattened parameter dict with dot notation (already flattened)
288
292
  """
289
- experiment_dir = self._get_experiment_dir(project, experiment)
293
+ if data is None:
294
+ data = {}
295
+ experiment_dir = self._get_experiment_dir(project, experiment, folder)
290
296
  params_file = experiment_dir / "parameters.json"
291
297
 
292
298
  # File-based lock for concurrent parameter writes (prevents data loss and version conflicts)
@@ -366,15 +372,16 @@ class LocalStorage:
366
372
  self,
367
373
  project: str,
368
374
  experiment: str,
369
- file_path: str,
370
- prefix: str,
371
- filename: str,
372
- description: Optional[str],
373
- tags: Optional[List[str]],
374
- metadata: Optional[Dict[str, Any]],
375
- checksum: str,
376
- content_type: str,
377
- size_bytes: int
375
+ folder: Optional[str] = None,
376
+ file_path: str = "",
377
+ prefix: str = "",
378
+ filename: str = "",
379
+ description: Optional[str] = None,
380
+ tags: Optional[List[str]] = None,
381
+ metadata: Optional[Dict[str, Any]] = None,
382
+ checksum: str = "",
383
+ content_type: str = "",
384
+ size_bytes: int = 0
378
385
  ) -> Dict[str, Any]:
379
386
  """
380
387
  Write file to local storage.
@@ -385,6 +392,7 @@ class LocalStorage:
385
392
  Args:
386
393
  project: Project name
387
394
  experiment: Experiment name
395
+ folder: Optional folder path
388
396
  file_path: Source file path
389
397
  prefix: Logical path prefix
390
398
  filename: Original filename
@@ -401,7 +409,7 @@ class LocalStorage:
401
409
  import shutil
402
410
  from .files import generate_snowflake_id
403
411
 
404
- experiment_dir = self._get_experiment_dir(project, experiment)
412
+ experiment_dir = self._get_experiment_dir(project, experiment, folder)
405
413
  files_dir = experiment_dir / "files"
406
414
  metadata_file = files_dir / ".files_metadata.json"
407
415
 
@@ -774,8 +782,9 @@ class LocalStorage:
774
782
  self,
775
783
  project: str,
776
784
  experiment: str,
777
- metric_name: Optional[str],
778
- data: Dict[str, Any],
785
+ folder: Optional[str] = None,
786
+ metric_name: Optional[str] = None,
787
+ data: Optional[Dict[str, Any]] = None,
779
788
  description: Optional[str] = None,
780
789
  tags: Optional[List[str]] = None,
781
790
  metadata: Optional[Dict[str, Any]] = None
@@ -791,6 +800,7 @@ class LocalStorage:
791
800
  Args:
792
801
  project: Project name
793
802
  experiment: Experiment name
803
+ folder: Optional folder path
794
804
  metric_name: Metric name (None for unnamed metrics)
795
805
  data: Data point (flexible schema)
796
806
  description: Optional metric description
@@ -800,7 +810,9 @@ class LocalStorage:
800
810
  Returns:
801
811
  Dict with metricId, index, bufferedDataPoints, chunkSize
802
812
  """
803
- experiment_dir = self._get_experiment_dir(project, experiment)
813
+ if data is None:
814
+ data = {}
815
+ experiment_dir = self._get_experiment_dir(project, experiment, folder)
804
816
  metrics_dir = experiment_dir / "metrics"
805
817
  metrics_dir.mkdir(parents=True, exist_ok=True)
806
818
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ml-dash
3
- Version: 0.5.9
3
+ Version: 0.6.0
4
4
  Summary: ML experiment tracking and data storage
5
5
  Keywords: machine-learning,experiment-tracking,mlops,data-storage
6
6
  Author: Ge Yang, Tom Tao
@@ -42,6 +42,9 @@ Requires-Dist: imageio>=2.31.0
42
42
  Requires-Dist: imageio-ffmpeg>=0.4.9
43
43
  Requires-Dist: scikit-image>=0.21.0
44
44
  Requires-Dist: rich>=13.0.0
45
+ Requires-Dist: cryptography>=42.0.0
46
+ Requires-Dist: keyring>=25.0.0 ; extra == 'auth'
47
+ Requires-Dist: qrcode>=8.0.0 ; extra == 'auth'
45
48
  Requires-Dist: pytest>=8.0.0 ; extra == 'dev'
46
49
  Requires-Dist: pytest-asyncio>=0.23.0 ; extra == 'dev'
47
50
  Requires-Dist: sphinx>=7.2.0 ; extra == 'dev'
@@ -58,6 +61,7 @@ Requires-Dist: linkify-it-py>=2.0.0 ; extra == 'dev'
58
61
  Requires-Dist: ruff>=0.3.0 ; extra == 'dev'
59
62
  Requires-Dist: mypy>=1.9.0 ; extra == 'dev'
60
63
  Requires-Python: >=3.9
64
+ Provides-Extra: auth
61
65
  Provides-Extra: dev
62
66
  Description-Content-Type: text/markdown
63
67
 
@@ -0,0 +1,29 @@
1
+ ml_dash/__init__.py,sha256=eO526uX_T_Y5tySTVw2ZN-QCR46120gYPD6DWhenMUw,2228
2
+ ml_dash/auth/__init__.py,sha256=3lwM-Y8UBHPU1gFW2JNpmXlPVTnkGudWLKNFFKulQfo,1200
3
+ ml_dash/auth/constants.py,sha256=ku4QzQUMNjvyJwjy7AUdywMAZd59jXSxNHZxDiagUWU,280
4
+ ml_dash/auth/device_flow.py,sha256=DQOdPNlZCuU1umZOA_A6WXdRM3zWphnyo9IntToBl_A,7921
5
+ ml_dash/auth/device_secret.py,sha256=qUsz6M9S1GEIukvmz57eJEp57srSx74O4MU9mZEeDlE,1158
6
+ ml_dash/auth/exceptions.py,sha256=IeBwUzoaTyFtPwd4quFOIel49inIzuabe_ChEeEXEWI,725
7
+ ml_dash/auth/token_storage.py,sha256=vuWDAhQPdjUwNTJAdoWP-EUDudIJVTNdWkWnvgS4Qgc,8217
8
+ ml_dash/auto_start.py,sha256=u1dSzP7S_ZL2w81Yvft_fS6JysU2z6pfNyMvjtYQe-8,1820
9
+ ml_dash/cli.py,sha256=r55bNa3T_guMnSl2dwsj0WF9Ln-vOGsBmbrooMKYhRI,2103
10
+ ml_dash/cli_commands/__init__.py,sha256=bjAmV7MsW-bhtW_4SnLJ0Cfkt9h82vMDC8ebW1Ke8KE,38
11
+ ml_dash/cli_commands/download.py,sha256=jOUx2aejA_Hz4ttuL8ubFY3pI4j39fYtdn-sZyrYCrE,28674
12
+ ml_dash/cli_commands/list.py,sha256=0G0XMJ8cH8_k37IEjwq20t6obT-tKxZPTxr221UF8KA,9757
13
+ ml_dash/cli_commands/login.py,sha256=0v8UPj_Y-uYmNDfc8Zv6xddFJPx3_VgVNeESYO700b0,7173
14
+ ml_dash/cli_commands/logout.py,sha256=lTUUNyRXqvo61qNkCd4KBrPUujDAHnNqsHkU6bHie0U,1332
15
+ ml_dash/cli_commands/upload.py,sha256=4O9iZOQDOiiBBsoDG1Cr8BqeBGSZZSfc3i-5FERjFtU,46439
16
+ ml_dash/client.py,sha256=TBIPY0ti31hcyWHGW8SexBUNcSzo-aJ4hLlZtMViPD8,28902
17
+ ml_dash/config.py,sha256=aNqX_HT2Yo-BzjFexQ97gQK9xOiBUwuJJ65dKM3oJjs,3481
18
+ ml_dash/experiment.py,sha256=qEwPq8U6YXw-bmEBOZX0yL-4Y8TTZYuFsKtMarq8duA,33913
19
+ ml_dash/files.py,sha256=p9s15eqgGAf-6bZntXkd_YicS6GjSGzEKAN_YoMuAXQ,26541
20
+ ml_dash/log.py,sha256=0yXaNnFwYeBI3tRLHX3kkqWRpg0MbSGwmgjnOfsElCk,5350
21
+ ml_dash/metric.py,sha256=vz3YwO1YBWZ797l7TzD5m9WwNBBXlrATyRRzSti8QS0,17194
22
+ ml_dash/params.py,sha256=S4wHQfv0EQRXRrFbKLuEKTQOLp5eqnALcIk1pZNroBM,9124
23
+ ml_dash/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ ml_dash/remote_auto_start.py,sha256=BAsTu41e9SboeSCn9N2vn4ftoYeUVklcAcNM45HJVGA,1610
25
+ ml_dash/storage.py,sha256=OTMMHSLYHk_MBfo8PyMQBHD8AZ2TFTS8KSMhVhpG-hg,39493
26
+ ml_dash-0.6.0.dist-info/WHEEL,sha256=z-mOpxbJHqy3cq6SvUThBZdaLGFZzdZPtgWLcP2NKjQ,79
27
+ ml_dash-0.6.0.dist-info/entry_points.txt,sha256=dYs2EHX1uRNO7AQGNnVaJJpgiy0Z9q7tiy4fHSyaf3Q,46
28
+ ml_dash-0.6.0.dist-info/METADATA,sha256=UCpvpQfajtLaLvsRQQ84Ci0uNXgsRnNnyIE6NxAASL0,6328
29
+ ml_dash-0.6.0.dist-info/RECORD,,
@@ -1,20 +0,0 @@
1
- ml_dash/__init__.py,sha256=o_LrWVJBY_VkUGhSBs5wdb_NqEsHD1AK9HGsjZGxHxQ,1414
2
- ml_dash/auto_start.py,sha256=c3XcXFpZdvjtWauEoK5043Gw9k0L_5IDq4fdiB2ha88,959
3
- ml_dash/cli.py,sha256=lyWVVhmsflSXQt2UCDb8IqC-mSRQwwlB2l1qEIYBUb8,1743
4
- ml_dash/cli_commands/__init__.py,sha256=bjAmV7MsW-bhtW_4SnLJ0Cfkt9h82vMDC8ebW1Ke8KE,38
5
- ml_dash/cli_commands/download.py,sha256=TomyUFwelqfQHfh60K7rCyCwEZVp1CkMToogprgC64Q,29614
6
- ml_dash/cli_commands/list.py,sha256=Cx9yWsTV5HPaevYpQ6BugCEr5z_4bhxQ0T51OXExuTU,10900
7
- ml_dash/cli_commands/upload.py,sha256=jo6FVdbuokTz64rjvOEWWhLBzlh2gM0Ru4TRNv9hX60,47943
8
- ml_dash/client.py,sha256=31C2Kb3KULwhrb3UlpCFY7HDA3-kvj3XVmWUvXEvQHY,27993
9
- ml_dash/config.py,sha256=iQbHCu4lM_Sg8YadyEXSJ6Ht9yKIJHN26L7L-rMH4gE,3112
10
- ml_dash/experiment.py,sha256=K36HkHJb_O2-vdaPPOCq74_2nZtfiLaS0o7qhTntD8Q,30646
11
- ml_dash/files.py,sha256=JptjoxGJiXJ-nkj6C7vDhw-cgJRCB0cHt_SIUJG665o,23024
12
- ml_dash/log.py,sha256=0yXaNnFwYeBI3tRLHX3kkqWRpg0MbSGwmgjnOfsElCk,5350
13
- ml_dash/metric.py,sha256=c0Zl0wEufmQuVfwIMvrORLwqe92Iaf0PfKRgmlgQWzQ,10343
14
- ml_dash/params.py,sha256=xaByDSVar4D1pZqxTANkMPeZTL5-V7ewJe5TXfPLhMQ,5980
15
- ml_dash/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- ml_dash/storage.py,sha256=1NLkIOZeFv0e97zieEX0tJT3qHTuM8UN6hvsFRQ6TTo,38941
17
- ml_dash-0.5.9.dist-info/WHEEL,sha256=z-mOpxbJHqy3cq6SvUThBZdaLGFZzdZPtgWLcP2NKjQ,79
18
- ml_dash-0.5.9.dist-info/entry_points.txt,sha256=dYs2EHX1uRNO7AQGNnVaJJpgiy0Z9q7tiy4fHSyaf3Q,46
19
- ml_dash-0.5.9.dist-info/METADATA,sha256=Acfle-Q8_jExDsli8yuZ8EMP1knWSkPtONo1FYB1sM4,6175
20
- ml_dash-0.5.9.dist-info/RECORD,,