ml-dash 0.6.1__py3-none-any.whl → 0.6.2__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 +37 -63
- ml_dash/auth/token_storage.py +267 -226
- ml_dash/auto_start.py +28 -15
- ml_dash/cli.py +16 -2
- ml_dash/cli_commands/api.py +165 -0
- ml_dash/cli_commands/download.py +757 -667
- ml_dash/cli_commands/list.py +146 -13
- ml_dash/cli_commands/login.py +190 -183
- ml_dash/cli_commands/profile.py +92 -0
- ml_dash/cli_commands/upload.py +1291 -1141
- ml_dash/client.py +79 -6
- ml_dash/config.py +119 -119
- ml_dash/experiment.py +1242 -995
- ml_dash/files.py +1051 -340
- ml_dash/log.py +7 -7
- ml_dash/metric.py +359 -100
- ml_dash/params.py +6 -6
- ml_dash/remote_auto_start.py +20 -17
- ml_dash/run.py +231 -0
- ml_dash/snowflake.py +173 -0
- ml_dash/storage.py +1051 -1079
- {ml_dash-0.6.1.dist-info → ml_dash-0.6.2.dist-info}/METADATA +45 -20
- ml_dash-0.6.2.dist-info/RECORD +33 -0
- ml_dash-0.6.1.dist-info/RECORD +0 -29
- {ml_dash-0.6.1.dist-info → ml_dash-0.6.2.dist-info}/WHEEL +0 -0
- {ml_dash-0.6.1.dist-info → ml_dash-0.6.2.dist-info}/entry_points.txt +0 -0
ml_dash/client.py
CHANGED
|
@@ -84,7 +84,7 @@ class RemoteClient:
|
|
|
84
84
|
description: Optional[str] = None,
|
|
85
85
|
tags: Optional[List[str]] = None,
|
|
86
86
|
bindrs: Optional[List[str]] = None,
|
|
87
|
-
|
|
87
|
+
prefix: Optional[str] = None,
|
|
88
88
|
write_protected: bool = False,
|
|
89
89
|
metadata: Optional[Dict[str, Any]] = None,
|
|
90
90
|
) -> Dict[str, Any]:
|
|
@@ -93,16 +93,16 @@ class RemoteClient:
|
|
|
93
93
|
|
|
94
94
|
Args:
|
|
95
95
|
project: Project name
|
|
96
|
-
name: Experiment name
|
|
96
|
+
name: Experiment name (last segment of prefix)
|
|
97
97
|
description: Optional description
|
|
98
98
|
tags: Optional list of tags
|
|
99
99
|
bindrs: Optional list of bindrs
|
|
100
|
-
|
|
100
|
+
prefix: Full prefix path sent to backend for folder hierarchy creation
|
|
101
101
|
write_protected: If True, experiment becomes immutable
|
|
102
102
|
metadata: Optional metadata dict
|
|
103
103
|
|
|
104
104
|
Returns:
|
|
105
|
-
Response dict with experiment, project,
|
|
105
|
+
Response dict with experiment, project, and namespace data
|
|
106
106
|
|
|
107
107
|
Raises:
|
|
108
108
|
httpx.HTTPStatusError: If request fails
|
|
@@ -117,12 +117,12 @@ class RemoteClient:
|
|
|
117
117
|
payload["tags"] = tags
|
|
118
118
|
if bindrs is not None:
|
|
119
119
|
payload["bindrs"] = bindrs
|
|
120
|
-
if folder is not None:
|
|
121
|
-
payload["folder"] = folder
|
|
122
120
|
if write_protected:
|
|
123
121
|
payload["writeProtected"] = write_protected
|
|
124
122
|
if metadata is not None:
|
|
125
123
|
payload["metadata"] = metadata
|
|
124
|
+
if prefix is not None:
|
|
125
|
+
payload["prefix"] = prefix
|
|
126
126
|
|
|
127
127
|
response = self._client.post(
|
|
128
128
|
f"/projects/{project}/experiments",
|
|
@@ -728,6 +728,9 @@ class RemoteClient:
|
|
|
728
728
|
metadata
|
|
729
729
|
project {
|
|
730
730
|
slug
|
|
731
|
+
namespace {
|
|
732
|
+
slug
|
|
733
|
+
}
|
|
731
734
|
}
|
|
732
735
|
logMetadata {
|
|
733
736
|
totalLogs
|
|
@@ -792,6 +795,9 @@ class RemoteClient:
|
|
|
792
795
|
metadata
|
|
793
796
|
project {
|
|
794
797
|
slug
|
|
798
|
+
namespace {
|
|
799
|
+
slug
|
|
800
|
+
}
|
|
795
801
|
}
|
|
796
802
|
logMetadata {
|
|
797
803
|
totalLogs
|
|
@@ -828,6 +834,73 @@ class RemoteClient:
|
|
|
828
834
|
result = self.graphql_query(query, variables)
|
|
829
835
|
return result.get("experiment")
|
|
830
836
|
|
|
837
|
+
def search_experiments_graphql(self, pattern: str) -> List[Dict[str, Any]]:
|
|
838
|
+
"""
|
|
839
|
+
Search experiments using glob pattern via GraphQL.
|
|
840
|
+
|
|
841
|
+
Pattern format: namespace/project/experiment
|
|
842
|
+
Supports wildcards: *, ?, [0-9], [a-z], etc.
|
|
843
|
+
|
|
844
|
+
Args:
|
|
845
|
+
pattern: Glob pattern (e.g., "tom*/tutorials/*", "*/project-?/exp*")
|
|
846
|
+
|
|
847
|
+
Returns:
|
|
848
|
+
List of experiment dicts matching the pattern
|
|
849
|
+
|
|
850
|
+
Raises:
|
|
851
|
+
httpx.HTTPStatusError: If request fails
|
|
852
|
+
|
|
853
|
+
Examples:
|
|
854
|
+
>>> client.search_experiments_graphql("tom*/tutorials/*")
|
|
855
|
+
>>> client.search_experiments_graphql("*/my-project/baseline*")
|
|
856
|
+
"""
|
|
857
|
+
query = """
|
|
858
|
+
query SearchExperiments($pattern: String!) {
|
|
859
|
+
searchExperiments(pattern: $pattern) {
|
|
860
|
+
id
|
|
861
|
+
name
|
|
862
|
+
description
|
|
863
|
+
tags
|
|
864
|
+
status
|
|
865
|
+
startedAt
|
|
866
|
+
endedAt
|
|
867
|
+
metadata
|
|
868
|
+
project {
|
|
869
|
+
id
|
|
870
|
+
slug
|
|
871
|
+
name
|
|
872
|
+
namespace {
|
|
873
|
+
id
|
|
874
|
+
slug
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
logMetadata {
|
|
878
|
+
totalLogs
|
|
879
|
+
}
|
|
880
|
+
metrics {
|
|
881
|
+
name
|
|
882
|
+
metricMetadata {
|
|
883
|
+
totalDataPoints
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
files {
|
|
887
|
+
id
|
|
888
|
+
filename
|
|
889
|
+
path
|
|
890
|
+
contentType
|
|
891
|
+
sizeBytes
|
|
892
|
+
checksum
|
|
893
|
+
description
|
|
894
|
+
tags
|
|
895
|
+
metadata
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
"""
|
|
900
|
+
variables = {"pattern": pattern}
|
|
901
|
+
result = self.graphql_query(query, variables)
|
|
902
|
+
return result.get("searchExperiments", [])
|
|
903
|
+
|
|
831
904
|
def download_file_streaming(
|
|
832
905
|
self, experiment_id: str, file_id: str, dest_path: str
|
|
833
906
|
) -> str:
|
ml_dash/config.py
CHANGED
|
@@ -1,132 +1,132 @@
|
|
|
1
1
|
"""Configuration file management for ML-Dash CLI."""
|
|
2
2
|
|
|
3
|
-
from pathlib import Path
|
|
4
3
|
import json
|
|
5
|
-
from
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class Config:
|
|
9
|
+
"""
|
|
10
|
+
Manages ML-Dash CLI configuration file.
|
|
11
|
+
|
|
12
|
+
Configuration is stored in ~/.dash/config.json with structure:
|
|
13
|
+
{
|
|
14
|
+
"remote_url": "https://api.dash.ml",
|
|
15
|
+
"api_key": "token",
|
|
16
|
+
"default_batch_size": 100
|
|
17
|
+
}
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
DEFAULT_CONFIG_DIR = Path.home() / ".dash"
|
|
21
|
+
CONFIG_FILE = "config.json"
|
|
22
|
+
|
|
23
|
+
def __init__(self, config_dir: Optional[Path] = None):
|
|
9
24
|
"""
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Configuration is stored in ~/.ml-dash/config.json with structure:
|
|
13
|
-
{
|
|
14
|
-
"remote_url": "https://api.dash.ml",
|
|
15
|
-
"api_key": "token",
|
|
16
|
-
"default_batch_size": 100
|
|
17
|
-
}
|
|
18
|
-
"""
|
|
25
|
+
Initialize config manager.
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
self.config_path
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"""Load config from file."""
|
|
36
|
-
if self.config_path.exists():
|
|
37
|
-
try:
|
|
38
|
-
with open(self.config_path, "r") as f:
|
|
39
|
-
return json.load(f)
|
|
40
|
-
except (json.JSONDecodeError, IOError):
|
|
41
|
-
# If config is corrupted, return empty dict
|
|
42
|
-
return {}
|
|
27
|
+
Args:
|
|
28
|
+
config_dir: Config directory path (defaults to ~/.dash)
|
|
29
|
+
"""
|
|
30
|
+
self.config_dir = config_dir or self.DEFAULT_CONFIG_DIR
|
|
31
|
+
self.config_path = self.config_dir / self.CONFIG_FILE
|
|
32
|
+
self._data = self._load()
|
|
33
|
+
|
|
34
|
+
def _load(self) -> Dict[str, Any]:
|
|
35
|
+
"""Load config from file."""
|
|
36
|
+
if self.config_path.exists():
|
|
37
|
+
try:
|
|
38
|
+
with open(self.config_path, "r") as f:
|
|
39
|
+
return json.load(f)
|
|
40
|
+
except (json.JSONDecodeError, IOError):
|
|
41
|
+
# If config is corrupted, return empty dict
|
|
43
42
|
return {}
|
|
43
|
+
return {}
|
|
44
|
+
|
|
45
|
+
def save(self):
|
|
46
|
+
"""Save config to file."""
|
|
47
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
with open(self.config_path, "w") as f:
|
|
49
|
+
json.dump(self._data, f, indent=2)
|
|
50
|
+
|
|
51
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
52
|
+
"""
|
|
53
|
+
Get config value.
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
@property
|
|
122
|
-
def device_secret(self) -> Optional[str]:
|
|
123
|
-
"""Get device secret for OAuth device flow."""
|
|
124
|
-
return self.get("device_secret")
|
|
125
|
-
|
|
126
|
-
@device_secret.setter
|
|
127
|
-
def device_secret(self, secret: str):
|
|
128
|
-
"""Set device secret."""
|
|
129
|
-
self.set("device_secret", secret)
|
|
55
|
+
Args:
|
|
56
|
+
key: Config key
|
|
57
|
+
default: Default value if key not found
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Config value or default
|
|
61
|
+
"""
|
|
62
|
+
return self._data.get(key, default)
|
|
63
|
+
|
|
64
|
+
def set(self, key: str, value: Any):
|
|
65
|
+
"""
|
|
66
|
+
Set config value and save.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
key: Config key
|
|
70
|
+
value: Config value
|
|
71
|
+
"""
|
|
72
|
+
self._data[key] = value
|
|
73
|
+
self.save()
|
|
74
|
+
|
|
75
|
+
def delete(self, key: str):
|
|
76
|
+
"""
|
|
77
|
+
Delete config key and save.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
key: Config key to delete
|
|
81
|
+
"""
|
|
82
|
+
if key in self._data:
|
|
83
|
+
del self._data[key]
|
|
84
|
+
self.save()
|
|
85
|
+
|
|
86
|
+
def clear(self):
|
|
87
|
+
"""Clear all config and save."""
|
|
88
|
+
self._data = {}
|
|
89
|
+
self.save()
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def remote_url(self) -> Optional[str]:
|
|
93
|
+
"""Get default remote URL."""
|
|
94
|
+
return self.get("remote_url", "https://api.dash.ml")
|
|
95
|
+
|
|
96
|
+
@remote_url.setter
|
|
97
|
+
def remote_url(self, url: str):
|
|
98
|
+
"""Set default remote URL."""
|
|
99
|
+
self.set("remote_url", url)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def api_key(self) -> Optional[str]:
|
|
103
|
+
"""Get default API key."""
|
|
104
|
+
return self.get("api_key")
|
|
105
|
+
|
|
106
|
+
@api_key.setter
|
|
107
|
+
def api_key(self, key: str):
|
|
108
|
+
"""Set default API key."""
|
|
109
|
+
self.set("api_key", key)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def batch_size(self) -> int:
|
|
113
|
+
"""Get default batch size for uploads."""
|
|
114
|
+
return self.get("default_batch_size", 100)
|
|
115
|
+
|
|
116
|
+
@batch_size.setter
|
|
117
|
+
def batch_size(self, size: int):
|
|
118
|
+
"""Set default batch size."""
|
|
119
|
+
self.set("default_batch_size", size)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def device_secret(self) -> Optional[str]:
|
|
123
|
+
"""Get device secret for OAuth device flow."""
|
|
124
|
+
return self.get("device_secret")
|
|
125
|
+
|
|
126
|
+
@device_secret.setter
|
|
127
|
+
def device_secret(self, secret: str):
|
|
128
|
+
"""Set device secret."""
|
|
129
|
+
self.set("device_secret", secret)
|
|
130
130
|
|
|
131
131
|
|
|
132
132
|
# Global config instance
|