ml-dash 0.6.1__tar.gz → 0.6.2rc1__tar.gz

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.
Files changed (31) hide show
  1. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/PKG-INFO +42 -15
  2. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/README.md +40 -14
  3. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/pyproject.toml +2 -1
  4. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/__init__.py +2 -0
  5. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/experiment.py +83 -36
  6. ml_dash-0.6.2rc1/src/ml_dash/files.py +1381 -0
  7. ml_dash-0.6.2rc1/src/ml_dash/run.py +85 -0
  8. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/storage.py +4 -2
  9. ml_dash-0.6.1/src/ml_dash/files.py +0 -785
  10. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/LICENSE +0 -0
  11. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/auth/__init__.py +0 -0
  12. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/auth/constants.py +0 -0
  13. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/auth/device_flow.py +0 -0
  14. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/auth/device_secret.py +0 -0
  15. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/auth/exceptions.py +0 -0
  16. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/auth/token_storage.py +0 -0
  17. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/auto_start.py +0 -0
  18. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/cli.py +0 -0
  19. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/cli_commands/__init__.py +0 -0
  20. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/cli_commands/download.py +0 -0
  21. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/cli_commands/list.py +0 -0
  22. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/cli_commands/login.py +0 -0
  23. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/cli_commands/logout.py +0 -0
  24. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/cli_commands/upload.py +0 -0
  25. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/client.py +0 -0
  26. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/config.py +0 -0
  27. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/log.py +0 -0
  28. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/metric.py +0 -0
  29. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/params.py +0 -0
  30. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/py.typed +0 -0
  31. {ml_dash-0.6.1 → ml_dash-0.6.2rc1}/src/ml_dash/remote_auto_start.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ml-dash
3
- Version: 0.6.1
3
+ Version: 0.6.2rc1
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
@@ -43,6 +43,7 @@ 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
45
  Requires-Dist: cryptography>=42.0.0
46
+ Requires-Dist: params-proto>=3.0.0rc12
46
47
  Requires-Dist: keyring>=25.0.0 ; extra == 'auth'
47
48
  Requires-Dist: qrcode>=8.0.0 ; extra == 'auth'
48
49
  Requires-Dist: pytest>=8.0.0 ; extra == 'dev'
@@ -67,12 +68,13 @@ Description-Content-Type: text/markdown
67
68
 
68
69
  # ML-Dash
69
70
 
70
- A simple and flexible SDK for ML experiment metricing and data storage.
71
+ A simple and flexible SDK for ML experiment tracking and data storage.
71
72
 
72
73
  ## Features
73
74
 
74
- - **Three Usage Styles**: Decorator, context manager, or direct instantiation
75
+ - **Three Usage Styles**: Pre-configured singleton (dxp), context manager, or direct instantiation
75
76
  - **Dual Operation Modes**: Remote (API server) or local (filesystem)
77
+ - **OAuth2 Authentication**: Secure device flow authentication for CLI and SDK
76
78
  - **Auto-creation**: Automatically creates namespace, project, and folder hierarchy
77
79
  - **Upsert Behavior**: Updates existing experiments or creates new ones
78
80
  - **Experiment Lifecycle**: Automatic status tracking (RUNNING, COMPLETED, FAILED, CANCELLED)
@@ -91,23 +93,48 @@ A simple and flexible SDK for ML experiment metricing and data storage.
91
93
  <td>
92
94
 
93
95
  ```bash
94
- uv add ml-dash
96
+ uv add ml-dash==0.6.2rc1
95
97
  ```
96
98
 
97
99
  </td>
98
100
  <td>
99
101
 
100
102
  ```bash
101
- pip install ml-dash
103
+ pip install ml-dash==0.6.2rc1
102
104
  ```
103
105
 
104
106
  </td>
105
107
  </tr>
106
108
  </table>
107
109
 
108
- ## Getting Started
110
+ ## Quick Start
109
111
 
110
- ### Remote Mode (with API Server)
112
+ ### 1. Authenticate (Required for Remote Mode)
113
+
114
+ ```bash
115
+ ml-dash login
116
+ ```
117
+
118
+ This opens your browser for secure OAuth2 authentication. Your credentials are stored securely in your system keychain.
119
+
120
+ ### 2. Start Tracking Experiments
121
+
122
+ #### Option A: Use the Pre-configured Singleton (Easiest)
123
+
124
+ ```python
125
+ from ml_dash import dxp
126
+
127
+ # Start experiment (uploads to https://api.dash.ml by default)
128
+ with dxp.run:
129
+ dxp.log().info("Training started")
130
+ dxp.params.set(learning_rate=0.001, batch_size=32)
131
+
132
+ for epoch in range(10):
133
+ loss = train_one_epoch()
134
+ dxp.metrics("loss").append(value=loss, epoch=epoch)
135
+ ```
136
+
137
+ #### Option B: Create Your Own Experiment
111
138
 
112
139
  ```python
113
140
  from ml_dash import Experiment
@@ -115,13 +142,13 @@ from ml_dash import Experiment
115
142
  with Experiment(
116
143
  name="my-experiment",
117
144
  project="my-project",
118
- remote="https://api.dash.ml",
119
- api_key="your-jwt-token"
120
- ) as experiment:
121
- print(f"Experiment ID: {experiment.id}")
145
+ remote="https://api.dash.ml" # token auto-loaded
146
+ ).run as experiment:
147
+ experiment.log().info("Hello!")
148
+ experiment.params.set(lr=0.001)
122
149
  ```
123
150
 
124
- ### Local Mode (Filesystem)
151
+ #### Option C: Local Mode (No Authentication Required)
125
152
 
126
153
  ```python
127
154
  from ml_dash import Experiment
@@ -130,11 +157,11 @@ with Experiment(
130
157
  name="my-experiment",
131
158
  project="my-project",
132
159
  local_path=".ml-dash"
133
- ) as experiment:
134
- pass # Your code here
160
+ ).run as experiment:
161
+ experiment.log().info("Running locally")
135
162
  ```
136
163
 
137
- See [examples/](examples/) for more complete examples.
164
+ See [docs/getting-started.md](docs/getting-started.md) for more examples.
138
165
 
139
166
  ## Development Setup
140
167
 
@@ -1,11 +1,12 @@
1
1
  # ML-Dash
2
2
 
3
- A simple and flexible SDK for ML experiment metricing and data storage.
3
+ A simple and flexible SDK for ML experiment tracking and data storage.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Three Usage Styles**: Decorator, context manager, or direct instantiation
7
+ - **Three Usage Styles**: Pre-configured singleton (dxp), context manager, or direct instantiation
8
8
  - **Dual Operation Modes**: Remote (API server) or local (filesystem)
9
+ - **OAuth2 Authentication**: Secure device flow authentication for CLI and SDK
9
10
  - **Auto-creation**: Automatically creates namespace, project, and folder hierarchy
10
11
  - **Upsert Behavior**: Updates existing experiments or creates new ones
11
12
  - **Experiment Lifecycle**: Automatic status tracking (RUNNING, COMPLETED, FAILED, CANCELLED)
@@ -24,23 +25,48 @@ A simple and flexible SDK for ML experiment metricing and data storage.
24
25
  <td>
25
26
 
26
27
  ```bash
27
- uv add ml-dash
28
+ uv add ml-dash==0.6.2rc1
28
29
  ```
29
30
 
30
31
  </td>
31
32
  <td>
32
33
 
33
34
  ```bash
34
- pip install ml-dash
35
+ pip install ml-dash==0.6.2rc1
35
36
  ```
36
37
 
37
38
  </td>
38
39
  </tr>
39
40
  </table>
40
41
 
41
- ## Getting Started
42
+ ## Quick Start
42
43
 
43
- ### Remote Mode (with API Server)
44
+ ### 1. Authenticate (Required for Remote Mode)
45
+
46
+ ```bash
47
+ ml-dash login
48
+ ```
49
+
50
+ This opens your browser for secure OAuth2 authentication. Your credentials are stored securely in your system keychain.
51
+
52
+ ### 2. Start Tracking Experiments
53
+
54
+ #### Option A: Use the Pre-configured Singleton (Easiest)
55
+
56
+ ```python
57
+ from ml_dash import dxp
58
+
59
+ # Start experiment (uploads to https://api.dash.ml by default)
60
+ with dxp.run:
61
+ dxp.log().info("Training started")
62
+ dxp.params.set(learning_rate=0.001, batch_size=32)
63
+
64
+ for epoch in range(10):
65
+ loss = train_one_epoch()
66
+ dxp.metrics("loss").append(value=loss, epoch=epoch)
67
+ ```
68
+
69
+ #### Option B: Create Your Own Experiment
44
70
 
45
71
  ```python
46
72
  from ml_dash import Experiment
@@ -48,13 +74,13 @@ from ml_dash import Experiment
48
74
  with Experiment(
49
75
  name="my-experiment",
50
76
  project="my-project",
51
- remote="https://api.dash.ml",
52
- api_key="your-jwt-token"
53
- ) as experiment:
54
- print(f"Experiment ID: {experiment.id}")
77
+ remote="https://api.dash.ml" # token auto-loaded
78
+ ).run as experiment:
79
+ experiment.log().info("Hello!")
80
+ experiment.params.set(lr=0.001)
55
81
  ```
56
82
 
57
- ### Local Mode (Filesystem)
83
+ #### Option C: Local Mode (No Authentication Required)
58
84
 
59
85
  ```python
60
86
  from ml_dash import Experiment
@@ -63,11 +89,11 @@ with Experiment(
63
89
  name="my-experiment",
64
90
  project="my-project",
65
91
  local_path=".ml-dash"
66
- ) as experiment:
67
- pass # Your code here
92
+ ).run as experiment:
93
+ experiment.log().info("Running locally")
68
94
  ```
69
95
 
70
- See [examples/](examples/) for more complete examples.
96
+ See [docs/getting-started.md](docs/getting-started.md) for more examples.
71
97
 
72
98
  ## Development Setup
73
99
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ml-dash"
3
- version = "0.6.1"
3
+ version = "0.6.2rc1"
4
4
  description = "ML experiment tracking and data storage"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -31,6 +31,7 @@ dependencies = [
31
31
  "scikit-image>=0.21.0",
32
32
  "rich>=13.0.0",
33
33
  "cryptography>=42.0.0",
34
+ "params-proto>=3.0.0rc12",
34
35
  ]
35
36
 
36
37
  [project.scripts]
@@ -51,6 +51,7 @@ from .client import RemoteClient
51
51
  from .storage import LocalStorage
52
52
  from .log import LogLevel, LogBuilder
53
53
  from .params import ParametersBuilder
54
+ from .run import RUN
54
55
  from .auto_start import dxp
55
56
 
56
57
  __version__ = "0.1.0"
@@ -65,6 +66,7 @@ __all__ = [
65
66
  "LogLevel",
66
67
  "LogBuilder",
67
68
  "ParametersBuilder",
69
+ "RUN",
68
70
  "dxp",
69
71
  ]
70
72
 
@@ -17,7 +17,8 @@ from .client import RemoteClient
17
17
  from .storage import LocalStorage
18
18
  from .log import LogLevel, LogBuilder
19
19
  from .params import ParametersBuilder
20
- from .files import FileBuilder
20
+ from .files import FilesAccessor, BindrsBuilder
21
+ from .run import RUN
21
22
 
22
23
 
23
24
  class OperationMode(Enum):
@@ -121,28 +122,13 @@ class RunManager:
121
122
  "Set folder before calling start() or entering 'with' block."
122
123
  )
123
124
 
124
- # Process template variables if present
125
+ # Check if this is a template (contains {RUN.) or static folder
125
126
  if value and '{RUN.' in value:
126
- # Generate unique run ID (timestamp-based)
127
- from datetime import datetime
128
- run_timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
129
-
130
- # Simple string replacement for template variables
131
- # Supports: {RUN.name}, {RUN.project}, {RUN.id}, {RUN.timestamp}
132
- replacements = {
133
- '{RUN.name}': f"{self._experiment.name}_{run_timestamp}", # Unique name with timestamp
134
- '{RUN.project}': self._experiment.project,
135
- '{RUN.id}': run_timestamp, # Just the timestamp
136
- '{RUN.timestamp}': run_timestamp, # Alias for id
137
- }
138
-
139
- # Replace all template variables
140
- for template, replacement in replacements.items():
141
- if template in value:
142
- value = value.replace(template, replacement)
143
-
144
- # Update the folder on the experiment
145
- self._experiment.folder = value
127
+ # Store the template - it will be formatted when the run starts
128
+ self._experiment._folder_template = value
129
+ else:
130
+ # Static folder - set directly
131
+ self._experiment.folder = value
146
132
 
147
133
  def __enter__(self) -> "Experiment":
148
134
  """Context manager entry - starts the experiment."""
@@ -240,7 +226,7 @@ class Experiment:
240
226
  self.project = project
241
227
  self.description = description
242
228
  self.tags = tags
243
- self.bindrs = bindrs
229
+ self._bindrs_list = bindrs
244
230
  self.folder = folder
245
231
  self._write_protected = _write_protected
246
232
  self.metadata = metadata
@@ -264,6 +250,7 @@ class Experiment:
264
250
  self._experiment_data: Optional[Dict[str, Any]] = None
265
251
  self._is_open = False
266
252
  self._metrics_manager: Optional['MetricsManager'] = None # Cached metrics manager
253
+ self._folder_template: Optional[str] = None # Template for folder path
267
254
 
268
255
  if self.mode in (OperationMode.REMOTE, OperationMode.HYBRID):
269
256
  # api_key can be None - RemoteClient will auto-load from storage
@@ -284,6 +271,16 @@ class Experiment:
284
271
  if self._is_open:
285
272
  return self
286
273
 
274
+ # Initialize RUN with experiment values
275
+ RUN.name = self.name
276
+ RUN.project = self.project
277
+ RUN.description = self.description
278
+ RUN._init_run() # Generate id and timestamp
279
+
280
+ # Format folder template if present
281
+ if self._folder_template:
282
+ self.folder = RUN._format(self._folder_template)
283
+
287
284
  if self._client:
288
285
  # Remote mode: create/update experiment via API
289
286
  response = self._client.create_or_update_experiment(
@@ -291,7 +288,7 @@ class Experiment:
291
288
  name=self.name,
292
289
  description=self.description,
293
290
  tags=self.tags,
294
- bindrs=self.bindrs,
291
+ bindrs=self._bindrs_list,
295
292
  folder=self.folder,
296
293
  write_protected=self._write_protected,
297
294
  metadata=self.metadata,
@@ -306,7 +303,7 @@ class Experiment:
306
303
  name=self.name,
307
304
  description=self.description,
308
305
  tags=self.tags,
309
- bindrs=self.bindrs,
306
+ bindrs=self._bindrs_list,
310
307
  folder=self.folder,
311
308
  metadata=self.metadata,
312
309
  )
@@ -341,6 +338,9 @@ class Experiment:
341
338
 
342
339
  self._is_open = False
343
340
 
341
+ # Reset RUN for next experiment
342
+ RUN._reset()
343
+
344
344
  @property
345
345
  def run(self) -> RunManager:
346
346
  """
@@ -545,39 +545,86 @@ class Experiment:
545
545
  else:
546
546
  print(formatted_message, file=sys.stdout)
547
547
 
548
- def files(self, **kwargs) -> FileBuilder:
548
+ @property
549
+ def files(self) -> FilesAccessor:
549
550
  """
550
- Get a FileBuilder for fluent file operations.
551
+ Get a FilesAccessor for fluent file operations.
551
552
 
552
553
  Returns:
553
- FileBuilder instance for chaining
554
+ FilesAccessor instance for chaining
554
555
 
555
556
  Raises:
556
557
  RuntimeError: If experiment is not open
557
558
 
558
559
  Examples:
559
560
  # Upload file
560
- experiment.files(file_path="./model.pt", prefix="/models").save()
561
+ experiment.files("checkpoints").save(net, to="checkpoint.pt")
561
562
 
562
563
  # List files
563
- files = experiment.files().list()
564
- files = experiment.files(prefix="/models").list()
564
+ files = experiment.files("/some/location").list()
565
+ files = experiment.files("/models").list()
565
566
 
566
567
  # Download file
567
- experiment.files(file_id="123").download()
568
+ experiment.files("some.text").download()
569
+ experiment.files("some.text").download(to="./model.pt")
570
+
571
+ # Download Files via Glob Pattern
572
+ file_paths = experiment.files("images").list("*.png")
573
+ experiment.files("images").download("*.png")
574
+
575
+ # This is equivalent to downloading to a directory
576
+ experiment.files.download("images/*.png", to="local_images")
577
+
578
+ # Delete files
579
+ experiment.files("some.text").delete()
580
+ experiment.files.delete("some.text")
581
+
582
+ # Specific File Types
583
+ dxp.files.save_text("content", to="view.yaml")
584
+ dxp.files.save_json(dict(hey="yo"), to="config.json")
585
+ dxp.files.save_blob(b"xxx", to="data.bin")
586
+ """
587
+ if not self._is_open:
588
+ raise RuntimeError(
589
+ "Experiment not started. Use 'with experiment.run:' or call experiment.run.start() first.\n"
590
+ "Example:\n"
591
+ " with dxp.run:\n"
592
+ " dxp.files('path').save()"
593
+ )
594
+
595
+ return FilesAccessor(self)
596
+
597
+ def bindrs(self, bindr_name: str) -> BindrsBuilder:
598
+ """
599
+ Get a BindrsBuilder for working with file collections (bindrs).
600
+
601
+ Bindrs are collections of files that can span multiple prefixes.
602
+
603
+ Args:
604
+ bindr_name: Name of the bindr (collection)
605
+
606
+ Returns:
607
+ BindrsBuilder instance for chaining
608
+
609
+ Raises:
610
+ RuntimeError: If experiment is not open
611
+
612
+ Examples:
613
+ # List files in a bindr
614
+ file_paths = experiment.bindrs("some-bindr").list()
568
615
 
569
- # Delete file
570
- experiment.files(file_id="123").delete()
616
+ Note:
617
+ This is a placeholder for future bindr functionality.
571
618
  """
572
619
  if not self._is_open:
573
620
  raise RuntimeError(
574
621
  "Experiment not started. Use 'with experiment.run:' or call experiment.run.start() first.\n"
575
622
  "Example:\n"
576
623
  " with dxp.run:\n"
577
- " dxp.files().save()"
624
+ " files = dxp.bindrs('my-bindr').list()"
578
625
  )
579
626
 
580
- return FileBuilder(self, **kwargs)
627
+ return BindrsBuilder(self, bindr_name)
581
628
 
582
629
  def _upload_file(
583
630
  self,