ml-dash 0.6.2__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 (43) hide show
  1. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/PKG-INFO +14 -12
  2. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/README.md +12 -10
  3. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/pyproject.toml +2 -2
  4. ml_dash-0.6.2rc1/src/ml_dash/__init__.py +87 -0
  5. ml_dash-0.6.2rc1/src/ml_dash/auth/token_storage.py +262 -0
  6. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/auto_start.py +15 -28
  7. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/cli.py +2 -16
  8. ml_dash-0.6.2rc1/src/ml_dash/cli_commands/download.py +769 -0
  9. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/cli_commands/list.py +13 -146
  10. ml_dash-0.6.2rc1/src/ml_dash/cli_commands/login.py +225 -0
  11. ml_dash-0.6.2rc1/src/ml_dash/cli_commands/upload.py +1248 -0
  12. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/client.py +6 -79
  13. ml_dash-0.6.2rc1/src/ml_dash/config.py +133 -0
  14. ml_dash-0.6.2rc1/src/ml_dash/experiment.py +1163 -0
  15. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/files.py +224 -339
  16. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/log.py +7 -7
  17. ml_dash-0.6.2rc1/src/ml_dash/metric.py +481 -0
  18. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/params.py +6 -6
  19. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/remote_auto_start.py +17 -20
  20. ml_dash-0.6.2rc1/src/ml_dash/run.py +85 -0
  21. ml_dash-0.6.2rc1/src/ml_dash/storage.py +1129 -0
  22. ml_dash-0.6.2/src/ml_dash/__init__.py +0 -59
  23. ml_dash-0.6.2/src/ml_dash/auth/token_storage.py +0 -303
  24. ml_dash-0.6.2/src/ml_dash/cli_commands/api.py +0 -165
  25. ml_dash-0.6.2/src/ml_dash/cli_commands/download.py +0 -859
  26. ml_dash-0.6.2/src/ml_dash/cli_commands/login.py +0 -232
  27. ml_dash-0.6.2/src/ml_dash/cli_commands/profile.py +0 -92
  28. ml_dash-0.6.2/src/ml_dash/cli_commands/upload.py +0 -1398
  29. ml_dash-0.6.2/src/ml_dash/config.py +0 -133
  30. ml_dash-0.6.2/src/ml_dash/experiment.py +0 -1363
  31. ml_dash-0.6.2/src/ml_dash/metric.py +0 -740
  32. ml_dash-0.6.2/src/ml_dash/run.py +0 -231
  33. ml_dash-0.6.2/src/ml_dash/snowflake.py +0 -173
  34. ml_dash-0.6.2/src/ml_dash/storage.py +0 -1099
  35. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/LICENSE +0 -0
  36. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/auth/__init__.py +0 -0
  37. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/auth/constants.py +0 -0
  38. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/auth/device_flow.py +0 -0
  39. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/auth/device_secret.py +0 -0
  40. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/auth/exceptions.py +0 -0
  41. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/cli_commands/__init__.py +0 -0
  42. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/cli_commands/logout.py +0 -0
  43. {ml_dash-0.6.2 → ml_dash-0.6.2rc1}/src/ml_dash/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ml-dash
3
- Version: 0.6.2
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,7 +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.0rc27
46
+ Requires-Dist: params-proto>=3.0.0rc12
47
47
  Requires-Dist: keyring>=25.0.0 ; extra == 'auth'
48
48
  Requires-Dist: qrcode>=8.0.0 ; extra == 'auth'
49
49
  Requires-Dist: pytest>=8.0.0 ; extra == 'dev'
@@ -122,16 +122,16 @@ This opens your browser for secure OAuth2 authentication. Your credentials are s
122
122
  #### Option A: Use the Pre-configured Singleton (Easiest)
123
123
 
124
124
  ```python
125
- from ml_dash.auto_start import dxp
125
+ from ml_dash import dxp
126
126
 
127
127
  # Start experiment (uploads to https://api.dash.ml by default)
128
128
  with dxp.run:
129
- dxp.log("Training started", level="info")
129
+ dxp.log().info("Training started")
130
130
  dxp.params.set(learning_rate=0.001, batch_size=32)
131
131
 
132
132
  for epoch in range(10):
133
133
  loss = train_one_epoch()
134
- dxp.metrics("train").log(loss=loss, epoch=epoch)
134
+ dxp.metrics("loss").append(value=loss, epoch=epoch)
135
135
  ```
136
136
 
137
137
  #### Option B: Create Your Own Experiment
@@ -140,11 +140,12 @@ with dxp.run:
140
140
  from ml_dash import Experiment
141
141
 
142
142
  with Experiment(
143
- prefix="alice/my-project/my-experiment",
144
- dash_url="https://api.dash.ml", # token auto-loaded
143
+ name="my-experiment",
144
+ project="my-project",
145
+ remote="https://api.dash.ml" # token auto-loaded
145
146
  ).run as experiment:
146
- experiment.log("Hello!", level="info")
147
- experiment.params.set(lr=0.001)
147
+ experiment.log().info("Hello!")
148
+ experiment.params.set(lr=0.001)
148
149
  ```
149
150
 
150
151
  #### Option C: Local Mode (No Authentication Required)
@@ -153,10 +154,11 @@ with Experiment(
153
154
  from ml_dash import Experiment
154
155
 
155
156
  with Experiment(
156
- project="my-project", prefix="my-experiment", dash_root=".dash"
157
+ name="my-experiment",
158
+ project="my-project",
159
+ local_path=".ml-dash"
157
160
  ).run as experiment:
158
- experiment.log("Running locally", level="info")
159
-
161
+ experiment.log().info("Running locally")
160
162
  ```
161
163
 
162
164
  See [docs/getting-started.md](docs/getting-started.md) for more examples.
@@ -54,16 +54,16 @@ This opens your browser for secure OAuth2 authentication. Your credentials are s
54
54
  #### Option A: Use the Pre-configured Singleton (Easiest)
55
55
 
56
56
  ```python
57
- from ml_dash.auto_start import dxp
57
+ from ml_dash import dxp
58
58
 
59
59
  # Start experiment (uploads to https://api.dash.ml by default)
60
60
  with dxp.run:
61
- dxp.log("Training started", level="info")
61
+ dxp.log().info("Training started")
62
62
  dxp.params.set(learning_rate=0.001, batch_size=32)
63
63
 
64
64
  for epoch in range(10):
65
65
  loss = train_one_epoch()
66
- dxp.metrics("train").log(loss=loss, epoch=epoch)
66
+ dxp.metrics("loss").append(value=loss, epoch=epoch)
67
67
  ```
68
68
 
69
69
  #### Option B: Create Your Own Experiment
@@ -72,11 +72,12 @@ with dxp.run:
72
72
  from ml_dash import Experiment
73
73
 
74
74
  with Experiment(
75
- prefix="alice/my-project/my-experiment",
76
- dash_url="https://api.dash.ml", # token auto-loaded
75
+ name="my-experiment",
76
+ project="my-project",
77
+ remote="https://api.dash.ml" # token auto-loaded
77
78
  ).run as experiment:
78
- experiment.log("Hello!", level="info")
79
- experiment.params.set(lr=0.001)
79
+ experiment.log().info("Hello!")
80
+ experiment.params.set(lr=0.001)
80
81
  ```
81
82
 
82
83
  #### Option C: Local Mode (No Authentication Required)
@@ -85,10 +86,11 @@ with Experiment(
85
86
  from ml_dash import Experiment
86
87
 
87
88
  with Experiment(
88
- project="my-project", prefix="my-experiment", dash_root=".dash"
89
+ name="my-experiment",
90
+ project="my-project",
91
+ local_path=".ml-dash"
89
92
  ).run as experiment:
90
- experiment.log("Running locally", level="info")
91
-
93
+ experiment.log().info("Running locally")
92
94
  ```
93
95
 
94
96
  See [docs/getting-started.md](docs/getting-started.md) for more examples.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ml-dash"
3
- version = "0.6.2"
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,7 +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.0rc27",
34
+ "params-proto>=3.0.0rc12",
35
35
  ]
36
36
 
37
37
  [project.scripts]
@@ -0,0 +1,87 @@
1
+ """
2
+ ML-Dash Python SDK
3
+
4
+ A simple and flexible SDK for ML experiment metricing and data storage.
5
+
6
+ Usage:
7
+
8
+ # Quickest - dxp (pre-configured remote singleton)
9
+ # Requires: ml-dash login
10
+ from ml_dash import dxp
11
+
12
+ with dxp.run:
13
+ dxp.params.set(lr=0.001)
14
+ dxp.log().info("Training started")
15
+ # Auto-completes on exit from with block
16
+
17
+ # Local mode - explicit configuration
18
+ from ml_dash import Experiment
19
+
20
+ with Experiment(
21
+ name="my-experiment",
22
+ project="my-project",
23
+ local_path=".ml-dash"
24
+ ) as experiment:
25
+ experiment.log("Training started")
26
+ experiment.params.set(lr=0.001)
27
+ experiment.metrics("loss").append(step=0, value=0.5)
28
+
29
+ # Remote mode - explicit configuration
30
+ with Experiment(
31
+ name="my-experiment",
32
+ project="my-project",
33
+ remote="https://api.dash.ml",
34
+ api_key="your-jwt-token"
35
+ ) as experiment:
36
+ experiment.log("Training started")
37
+
38
+ # Decorator style
39
+ from ml_dash import ml_dash_experiment
40
+
41
+ @ml_dash_experiment(
42
+ name="my-experiment",
43
+ project="my-project"
44
+ )
45
+ def train_model(experiment):
46
+ experiment.log("Training started")
47
+ """
48
+
49
+ from .experiment import Experiment, ml_dash_experiment, OperationMode, RunManager
50
+ from .client import RemoteClient
51
+ from .storage import LocalStorage
52
+ from .log import LogLevel, LogBuilder
53
+ from .params import ParametersBuilder
54
+ from .run import RUN
55
+ from .auto_start import dxp
56
+
57
+ __version__ = "0.1.0"
58
+
59
+ __all__ = [
60
+ "Experiment",
61
+ "ml_dash_experiment",
62
+ "OperationMode",
63
+ "RunManager",
64
+ "RemoteClient",
65
+ "LocalStorage",
66
+ "LogLevel",
67
+ "LogBuilder",
68
+ "ParametersBuilder",
69
+ "RUN",
70
+ "dxp",
71
+ ]
72
+
73
+ # Hidden for now - rdxp (remote auto-start singleton)
74
+ # Will be exposed in a future release
75
+ #
76
+ # # Lazy-load rdxp to avoid auto-connecting to server on package import
77
+ # _rdxp = None
78
+ #
79
+ # def __getattr__(name):
80
+ # """Lazy-load rdxp only when accessed."""
81
+ # if name == "rdxp":
82
+ # global _rdxp
83
+ # if _rdxp is None:
84
+ # from .remote_auto_start import rdxp as _loaded_rdxp
85
+ # _rdxp = _loaded_rdxp
86
+ # return _rdxp
87
+ # raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
@@ -0,0 +1,262 @@
1
+ """Token storage backends for ml-dash authentication."""
2
+
3
+ import json
4
+ from abc import ABC, abstractmethod
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ from .exceptions import StorageError
9
+
10
+
11
+ class TokenStorage(ABC):
12
+ """Abstract base class for token storage backends."""
13
+
14
+ @abstractmethod
15
+ def store(self, key: str, value: str) -> None:
16
+ """Store a token.
17
+
18
+ Args:
19
+ key: Storage key
20
+ value: Token string to store
21
+ """
22
+ pass
23
+
24
+ @abstractmethod
25
+ def load(self, key: str) -> Optional[str]:
26
+ """Load a token.
27
+
28
+ Args:
29
+ key: Storage key
30
+
31
+ Returns:
32
+ Token string or None if not found
33
+ """
34
+ pass
35
+
36
+ @abstractmethod
37
+ def delete(self, key: str) -> None:
38
+ """Delete a token.
39
+
40
+ Args:
41
+ key: Storage key
42
+ """
43
+ pass
44
+
45
+
46
+ class KeyringStorage(TokenStorage):
47
+ """OS keyring storage backend (macOS Keychain, Windows Credential Manager, Linux Secret Service)."""
48
+
49
+ SERVICE_NAME = "ml-dash"
50
+
51
+ def __init__(self):
52
+ """Initialize keyring storage."""
53
+ try:
54
+ import keyring
55
+ self.keyring = keyring
56
+ except ImportError:
57
+ raise StorageError(
58
+ "keyring library not installed. "
59
+ "Install with: pip install keyring"
60
+ )
61
+
62
+ def store(self, key: str, value: str) -> None:
63
+ """Store token in OS keyring."""
64
+ try:
65
+ self.keyring.set_password(self.SERVICE_NAME, key, value)
66
+ except Exception as e:
67
+ raise StorageError(f"Failed to store token in keyring: {e}")
68
+
69
+ def load(self, key: str) -> Optional[str]:
70
+ """Load token from OS keyring."""
71
+ try:
72
+ return self.keyring.get_password(self.SERVICE_NAME, key)
73
+ except Exception as e:
74
+ raise StorageError(f"Failed to load token from keyring: {e}")
75
+
76
+ def delete(self, key: str) -> None:
77
+ """Delete token from OS keyring."""
78
+ try:
79
+ self.keyring.delete_password(self.SERVICE_NAME, key)
80
+ except Exception:
81
+ # Silently ignore if key doesn't exist
82
+ pass
83
+
84
+
85
+ class EncryptedFileStorage(TokenStorage):
86
+ """Encrypted file storage backend using Fernet symmetric encryption."""
87
+
88
+ def __init__(self, config_dir: Path):
89
+ """Initialize encrypted file storage.
90
+
91
+ Args:
92
+ config_dir: Configuration directory path
93
+ """
94
+ self.config_dir = Path(config_dir)
95
+ self.tokens_file = self.config_dir / "tokens.encrypted"
96
+ self.key_file = self.config_dir / "encryption.key"
97
+
98
+ try:
99
+ from cryptography.fernet import Fernet
100
+ self.Fernet = Fernet
101
+ except ImportError:
102
+ raise StorageError(
103
+ "cryptography library not installed. "
104
+ "Install with: pip install cryptography"
105
+ )
106
+
107
+ # Ensure config directory exists
108
+ self.config_dir.mkdir(parents=True, exist_ok=True)
109
+
110
+ # Generate or load encryption key
111
+ if not self.key_file.exists():
112
+ key = self.Fernet.generate_key()
113
+ self.key_file.write_bytes(key)
114
+ self.key_file.chmod(0o600) # User read/write only
115
+ else:
116
+ key = self.key_file.read_bytes()
117
+
118
+ self.cipher = self.Fernet(key)
119
+
120
+ def _load_all(self) -> dict:
121
+ """Load all tokens from encrypted file."""
122
+ if not self.tokens_file.exists():
123
+ return {}
124
+
125
+ try:
126
+ encrypted = self.tokens_file.read_bytes()
127
+ decrypted = self.cipher.decrypt(encrypted)
128
+ return json.loads(decrypted)
129
+ except Exception as e:
130
+ raise StorageError(f"Failed to decrypt tokens file: {e}")
131
+
132
+ def _save_all(self, data: dict) -> None:
133
+ """Save all tokens to encrypted file."""
134
+ try:
135
+ plaintext = json.dumps(data).encode()
136
+ encrypted = self.cipher.encrypt(plaintext)
137
+ self.tokens_file.write_bytes(encrypted)
138
+ self.tokens_file.chmod(0o600) # User read/write only
139
+ except Exception as e:
140
+ raise StorageError(f"Failed to encrypt tokens file: {e}")
141
+
142
+ def store(self, key: str, value: str) -> None:
143
+ """Store token in encrypted file."""
144
+ all_tokens = self._load_all()
145
+ all_tokens[key] = value
146
+ self._save_all(all_tokens)
147
+
148
+ def load(self, key: str) -> Optional[str]:
149
+ """Load token from encrypted file."""
150
+ all_tokens = self._load_all()
151
+ return all_tokens.get(key)
152
+
153
+ def delete(self, key: str) -> None:
154
+ """Delete token from encrypted file."""
155
+ all_tokens = self._load_all()
156
+ if key in all_tokens:
157
+ del all_tokens[key]
158
+ self._save_all(all_tokens)
159
+
160
+
161
+ class PlaintextFileStorage(TokenStorage):
162
+ """Plaintext file storage backend (INSECURE - only for testing/fallback)."""
163
+
164
+ _warning_shown = False
165
+
166
+ def __init__(self, config_dir: Path):
167
+ """Initialize plaintext file storage.
168
+
169
+ Args:
170
+ config_dir: Configuration directory path
171
+ """
172
+ self.config_dir = Path(config_dir)
173
+ self.tokens_file = self.config_dir / "tokens.json"
174
+
175
+ # Ensure config directory exists
176
+ self.config_dir.mkdir(parents=True, exist_ok=True)
177
+
178
+ # Show security warning on first use
179
+ if not PlaintextFileStorage._warning_shown:
180
+ try:
181
+ from rich.console import Console
182
+ console = Console()
183
+ console.print(
184
+ "\n[bold red]WARNING: Storing tokens in plaintext![/bold red]\n"
185
+ "[yellow]Your authentication tokens are being stored unencrypted.[/yellow]\n"
186
+ "[yellow]This is insecure and only recommended for testing.[/yellow]\n\n"
187
+ "To use secure storage:\n"
188
+ " • Install keyring: pip install keyring\n"
189
+ " • Or encrypted storage will be used automatically\n"
190
+ )
191
+ except ImportError:
192
+ print("WARNING: Storing tokens in plaintext! This is insecure.")
193
+
194
+ PlaintextFileStorage._warning_shown = True
195
+
196
+ def _load_all(self) -> dict:
197
+ """Load all tokens from file."""
198
+ if not self.tokens_file.exists():
199
+ return {}
200
+
201
+ try:
202
+ with open(self.tokens_file, "r") as f:
203
+ return json.load(f)
204
+ except (json.JSONDecodeError, IOError):
205
+ return {}
206
+
207
+ def _save_all(self, data: dict) -> None:
208
+ """Save all tokens to file."""
209
+ with open(self.tokens_file, "w") as f:
210
+ json.dump(data, f, indent=2)
211
+ self.tokens_file.chmod(0o600) # User read/write only
212
+
213
+ def store(self, key: str, value: str) -> None:
214
+ """Store token in plaintext file."""
215
+ all_tokens = self._load_all()
216
+ all_tokens[key] = value
217
+ self._save_all(all_tokens)
218
+
219
+ def load(self, key: str) -> Optional[str]:
220
+ """Load token from plaintext file."""
221
+ all_tokens = self._load_all()
222
+ return all_tokens.get(key)
223
+
224
+ def delete(self, key: str) -> None:
225
+ """Delete token from plaintext file."""
226
+ all_tokens = self._load_all()
227
+ if key in all_tokens:
228
+ del all_tokens[key]
229
+ self._save_all(all_tokens)
230
+
231
+
232
+ def get_token_storage(config_dir: Optional[Path] = None) -> TokenStorage:
233
+ """Auto-detect and return appropriate storage backend.
234
+
235
+ Tries backends in order of security:
236
+ 1. KeyringStorage (OS keyring)
237
+ 2. EncryptedFileStorage (encrypted file)
238
+ 3. PlaintextFileStorage (plaintext file with warning)
239
+
240
+ Args:
241
+ config_dir: Configuration directory (defaults to ~/.ml-dash)
242
+
243
+ Returns:
244
+ TokenStorage instance
245
+ """
246
+ if config_dir is None:
247
+ config_dir = Path.home() / ".ml-dash"
248
+
249
+ # Try keyring first
250
+ try:
251
+ return KeyringStorage()
252
+ except (ImportError, StorageError):
253
+ pass
254
+
255
+ # Try encrypted file storage
256
+ try:
257
+ return EncryptedFileStorage(config_dir)
258
+ except (ImportError, StorageError):
259
+ pass
260
+
261
+ # Fallback to plaintext (with warning)
262
+ return PlaintextFileStorage(config_dir)
@@ -9,56 +9,43 @@ Usage:
9
9
  # First, authenticate
10
10
  # $ ml-dash login
11
11
 
12
- from ml_dash.auto_start import dxp
12
+ from ml_dash import dxp
13
13
 
14
14
  # Use with statement (recommended)
15
15
  with dxp.run:
16
- dxp.log("Hello from dxp!", level="info")
16
+ dxp.log().info("Hello from dxp!")
17
17
  dxp.params.set(lr=0.001)
18
- dxp.metrics("train").log(loss=0.5, step=0)
18
+ dxp.metrics("loss").append(step=0, value=0.5)
19
19
  # Automatically completes on exit from with block
20
20
 
21
21
  # Or start/complete manually
22
22
  dxp.run.start()
23
- dxp.log("Training...", level="info")
23
+ dxp.log().info("Training...")
24
24
  dxp.run.complete()
25
25
  """
26
26
 
27
27
  import atexit
28
+ from .experiment import Experiment
28
29
 
29
30
  # Create pre-configured singleton experiment in remote mode
30
31
  # Uses default remote server (https://api.dash.ml)
31
32
  # Token is auto-loaded from storage when first used
32
33
  # If not authenticated, operations will fail with AuthenticationError
33
- # Prefix format: {owner}/{project}/path...
34
- # Using getpass to get current user as owner for local convenience
35
- import getpass
36
- from datetime import datetime
37
-
38
- from .auth.token_storage import get_jwt_user
39
- from .experiment import Experiment
40
-
41
- _user = get_jwt_user()
42
- # Fallback to system username if not authenticated
43
- _username = _user["username"] if _user else getpass.getuser()
44
- _now = datetime.now()
45
-
46
34
  dxp = Experiment(
47
- prefix=f"{_username}/scratch/{_now:%Y-%m-%d/%H%M%S}",
48
- dash_url="https://api.dash.ml",
35
+ name="dxp",
36
+ project="scratch",
37
+ remote="https://api.dash.ml",
49
38
  )
50
39
 
51
-
52
40
  # Register cleanup handler to complete experiment on Python exit (if still open)
53
41
  def _cleanup():
54
- """Complete the dxp experiment on exit if still open."""
55
- if dxp._is_open:
56
- try:
57
- dxp.run.complete()
58
- except Exception:
59
- # Silently ignore errors during cleanup
60
- pass
61
-
42
+ """Complete the dxp experiment on exit if still open."""
43
+ if dxp._is_open:
44
+ try:
45
+ dxp.run.complete()
46
+ except Exception:
47
+ # Silently ignore errors during cleanup
48
+ pass
62
49
 
63
50
  atexit.register(_cleanup)
64
51
 
@@ -9,11 +9,7 @@ def create_parser() -> argparse.ArgumentParser:
9
9
  """Create the main CLI argument parser."""
10
10
  parser = argparse.ArgumentParser(
11
11
  prog="ml-dash",
12
- description=(
13
- "ML-Dash: ML experiment tracking and data storage CLI\n\n"
14
- "View your experiments, statistics, and plots online at:\n"
15
- " https://dash.ml\n"
16
- ),
12
+ description="ML-Dash: ML experiment tracking and data storage CLI",
17
13
  formatter_class=argparse.RawDescriptionHelpFormatter,
18
14
  )
19
15
 
@@ -25,15 +21,11 @@ def create_parser() -> argparse.ArgumentParser:
25
21
  )
26
22
 
27
23
  # Import and add command parsers
28
- from .cli_commands import upload, download, list as list_cmd, login, logout, profile, api
24
+ from .cli_commands import upload, download, list as list_cmd, login, logout
29
25
 
30
26
  # Authentication commands
31
27
  login.add_parser(subparsers)
32
28
  logout.add_parser(subparsers)
33
- profile.add_parser(subparsers)
34
-
35
- # API commands
36
- api.add_parser(subparsers)
37
29
 
38
30
  # Data commands
39
31
  upload.add_parser(subparsers)
@@ -68,9 +60,6 @@ def main(argv: Optional[List[str]] = None) -> int:
68
60
  elif args.command == "logout":
69
61
  from .cli_commands import logout
70
62
  return logout.cmd_logout(args)
71
- elif args.command == "profile":
72
- from .cli_commands import profile
73
- return profile.cmd_profile(args)
74
63
  elif args.command == "upload":
75
64
  from .cli_commands import upload
76
65
  return upload.cmd_upload(args)
@@ -80,9 +69,6 @@ def main(argv: Optional[List[str]] = None) -> int:
80
69
  elif args.command == "list":
81
70
  from .cli_commands import list as list_cmd
82
71
  return list_cmd.cmd_list(args)
83
- elif args.command == "api":
84
- from .cli_commands import api
85
- return api.cmd_api(args)
86
72
 
87
73
  # Unknown command (shouldn't happen due to subparsers)
88
74
  parser.print_help()