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/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
- folder: Optional[str] = None,
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
- folder: Optional folder path
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, folder, and namespace data
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 typing import Optional, Dict, Any
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
- Manages ML-Dash CLI configuration file.
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
- DEFAULT_CONFIG_DIR = Path.home() / ".ml-dash"
21
- CONFIG_FILE = "config.json"
22
-
23
- def __init__(self, config_dir: Optional[Path] = None):
24
- """
25
- Initialize config manager.
26
-
27
- Args:
28
- config_dir: Config directory path (defaults to ~/.ml-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
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
- 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.
54
-
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)
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