ml-dash 0.0.17__py3-none-any.whl → 0.2.1__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.
Files changed (48) hide show
  1. ml_dash/__init__.py +58 -1
  2. ml_dash/client.py +562 -0
  3. ml_dash/experiment.py +916 -0
  4. ml_dash/files.py +313 -0
  5. ml_dash/log.py +181 -0
  6. ml_dash/metric.py +186 -0
  7. ml_dash/params.py +188 -0
  8. ml_dash/storage.py +922 -0
  9. ml_dash-0.2.1.dist-info/METADATA +237 -0
  10. ml_dash-0.2.1.dist-info/RECORD +12 -0
  11. ml_dash-0.2.1.dist-info/WHEEL +4 -0
  12. app-build/asset-manifest.json +0 -15
  13. app-build/favicon.ico +0 -0
  14. app-build/github-markdown.css +0 -957
  15. app-build/index.html +0 -1
  16. app-build/manifest.json +0 -15
  17. app-build/monaco-editor-worker-loader-proxy.js +0 -6
  18. app-build/precache-manifest.ffc09f8a591c529a1bd5c6f21f49815f.js +0 -26
  19. app-build/service-worker.js +0 -34
  20. ml_dash/app.py +0 -60
  21. ml_dash/config.py +0 -16
  22. ml_dash/file_events.py +0 -71
  23. ml_dash/file_handlers.py +0 -141
  24. ml_dash/file_utils.py +0 -5
  25. ml_dash/file_watcher.py +0 -30
  26. ml_dash/main.py +0 -60
  27. ml_dash/mime_types.py +0 -20
  28. ml_dash/schema/__init__.py +0 -110
  29. ml_dash/schema/archive.py +0 -165
  30. ml_dash/schema/directories.py +0 -59
  31. ml_dash/schema/experiments.py +0 -65
  32. ml_dash/schema/files/__init__.py +0 -204
  33. ml_dash/schema/files/file_helpers.py +0 -79
  34. ml_dash/schema/files/images.py +0 -27
  35. ml_dash/schema/files/metrics.py +0 -64
  36. ml_dash/schema/files/parameters.py +0 -50
  37. ml_dash/schema/files/series.py +0 -235
  38. ml_dash/schema/files/videos.py +0 -27
  39. ml_dash/schema/helpers.py +0 -66
  40. ml_dash/schema/projects.py +0 -65
  41. ml_dash/schema/schema_helpers.py +0 -19
  42. ml_dash/schema/users.py +0 -33
  43. ml_dash/sse.py +0 -18
  44. ml_dash-0.0.17.dist-info/METADATA +0 -67
  45. ml_dash-0.0.17.dist-info/RECORD +0 -38
  46. ml_dash-0.0.17.dist-info/WHEEL +0 -5
  47. ml_dash-0.0.17.dist-info/top_level.txt +0 -2
  48. /ml_dash/{example.py → py.typed} +0 -0
ml_dash/files.py ADDED
@@ -0,0 +1,313 @@
1
+ """
2
+ Files module for ML-Dash SDK.
3
+
4
+ Provides fluent API for file upload, download, list, and delete operations.
5
+ """
6
+
7
+ import hashlib
8
+ import mimetypes
9
+ from typing import Dict, Any, List, Optional, TYPE_CHECKING
10
+ from pathlib import Path
11
+
12
+ if TYPE_CHECKING:
13
+ from .experiment import Experiment
14
+
15
+
16
+ class FileBuilder:
17
+ """
18
+ Fluent interface for file operations.
19
+
20
+ Usage:
21
+ # Upload file
22
+ experiment.file(file_path="./model.pt", prefix="/models").save()
23
+
24
+ # List files
25
+ files = experiment.file().list()
26
+ files = experiment.file(prefix="/models").list()
27
+
28
+ # Download file
29
+ experiment.file(file_id="123").download()
30
+ experiment.file(file_id="123", dest_path="./model.pt").download()
31
+
32
+ # Delete file
33
+ experiment.file(file_id="123").delete()
34
+ """
35
+
36
+ def __init__(self, experiment: 'Experiment', **kwargs):
37
+ """
38
+ Initialize file builder.
39
+
40
+ Args:
41
+ experiment: Parent experiment instance
42
+ **kwargs: File operation parameters
43
+ - file_path: Path to file to upload
44
+ - prefix: Logical path prefix (default: "/")
45
+ - description: Optional description
46
+ - tags: Optional list of tags
47
+ - metadata: Optional metadata dict
48
+ - file_id: File ID for download/delete/update operations
49
+ - dest_path: Destination path for download
50
+ """
51
+ self._experiment = experiment
52
+ self._file_path = kwargs.get('file_path')
53
+ self._prefix = kwargs.get('prefix', '/')
54
+ self._description = kwargs.get('description')
55
+ self._tags = kwargs.get('tags', [])
56
+ self._metadata = kwargs.get('metadata')
57
+ self._file_id = kwargs.get('file_id')
58
+ self._dest_path = kwargs.get('dest_path')
59
+
60
+ def save(self) -> Dict[str, Any]:
61
+ """
62
+ Upload and save the file.
63
+
64
+ Returns:
65
+ File metadata dict with id, path, filename, checksum, etc.
66
+
67
+ Raises:
68
+ RuntimeError: If experiment is not open or write-protected
69
+ ValueError: If file_path not provided or file doesn't exist
70
+ ValueError: If file size exceeds 5GB limit
71
+
72
+ Examples:
73
+ result = experiment.file(file_path="./model.pt", prefix="/models").save()
74
+ # Returns: {"id": "123", "path": "/models", "filename": "model.pt", ...}
75
+ """
76
+ if not self._experiment._is_open:
77
+ raise RuntimeError("Experiment not open. Use experiment.open() or context manager.")
78
+
79
+ if self._experiment.write_protected:
80
+ raise RuntimeError("Experiment is write-protected and cannot be modified.")
81
+
82
+ if not self._file_path:
83
+ raise ValueError("file_path is required for save() operation")
84
+
85
+ file_path = Path(self._file_path)
86
+ if not file_path.exists():
87
+ raise ValueError(f"File not found: {self._file_path}")
88
+
89
+ if not file_path.is_file():
90
+ raise ValueError(f"Path is not a file: {self._file_path}")
91
+
92
+ # Check file size (max 5GB)
93
+ file_size = file_path.stat().st_size
94
+ MAX_FILE_SIZE = 5 * 1024 * 1024 * 1024 # 5GB in bytes
95
+ if file_size > MAX_FILE_SIZE:
96
+ raise ValueError(f"File size ({file_size} bytes) exceeds 5GB limit")
97
+
98
+ # Compute checksum
99
+ checksum = compute_sha256(str(file_path))
100
+
101
+ # Detect MIME type
102
+ content_type = get_mime_type(str(file_path))
103
+
104
+ # Get filename
105
+ filename = file_path.name
106
+
107
+ # Upload through experiment
108
+ return self._experiment._upload_file(
109
+ file_path=str(file_path),
110
+ prefix=self._prefix,
111
+ filename=filename,
112
+ description=self._description,
113
+ tags=self._tags,
114
+ metadata=self._metadata,
115
+ checksum=checksum,
116
+ content_type=content_type,
117
+ size_bytes=file_size
118
+ )
119
+
120
+ def list(self) -> List[Dict[str, Any]]:
121
+ """
122
+ List files with optional filters.
123
+
124
+ Returns:
125
+ List of file metadata dicts
126
+
127
+ Raises:
128
+ RuntimeError: If experiment is not open
129
+
130
+ Examples:
131
+ files = experiment.file().list() # All files
132
+ files = experiment.file(prefix="/models").list() # Filter by prefix
133
+ files = experiment.file(tags=["checkpoint"]).list() # Filter by tags
134
+ """
135
+ if not self._experiment._is_open:
136
+ raise RuntimeError("Experiment not open. Use experiment.open() or context manager.")
137
+
138
+ return self._experiment._list_files(
139
+ prefix=self._prefix if self._prefix != '/' else None,
140
+ tags=self._tags if self._tags else None
141
+ )
142
+
143
+ def download(self) -> str:
144
+ """
145
+ Download file with automatic checksum verification.
146
+
147
+ If dest_path not provided, downloads to current directory with original filename.
148
+
149
+ Returns:
150
+ Path to downloaded file
151
+
152
+ Raises:
153
+ RuntimeError: If experiment is not open
154
+ ValueError: If file_id not provided
155
+ ValueError: If checksum verification fails
156
+
157
+ Examples:
158
+ # Download to current directory with original filename
159
+ path = experiment.file(file_id="123").download()
160
+
161
+ # Download to custom path
162
+ path = experiment.file(file_id="123", dest_path="./model.pt").download()
163
+ """
164
+ if not self._experiment._is_open:
165
+ raise RuntimeError("Experiment not open. Use experiment.open() or context manager.")
166
+
167
+ if not self._file_id:
168
+ raise ValueError("file_id is required for download() operation")
169
+
170
+ return self._experiment._download_file(
171
+ file_id=self._file_id,
172
+ dest_path=self._dest_path
173
+ )
174
+
175
+ def delete(self) -> Dict[str, Any]:
176
+ """
177
+ Delete file (soft delete).
178
+
179
+ Returns:
180
+ Dict with id and deletedAt timestamp
181
+
182
+ Raises:
183
+ RuntimeError: If experiment is not open or write-protected
184
+ ValueError: If file_id not provided
185
+
186
+ Examples:
187
+ result = experiment.file(file_id="123").delete()
188
+ """
189
+ if not self._experiment._is_open:
190
+ raise RuntimeError("Experiment not open. Use experiment.open() or context manager.")
191
+
192
+ if self._experiment.write_protected:
193
+ raise RuntimeError("Experiment is write-protected and cannot be modified.")
194
+
195
+ if not self._file_id:
196
+ raise ValueError("file_id is required for delete() operation")
197
+
198
+ return self._experiment._delete_file(file_id=self._file_id)
199
+
200
+ def update(self) -> Dict[str, Any]:
201
+ """
202
+ Update file metadata (description, tags, metadata).
203
+
204
+ Returns:
205
+ Updated file metadata dict
206
+
207
+ Raises:
208
+ RuntimeError: If experiment is not open or write-protected
209
+ ValueError: If file_id not provided
210
+
211
+ Examples:
212
+ result = experiment.file(
213
+ file_id="123",
214
+ description="Updated description",
215
+ tags=["new", "tags"],
216
+ metadata={"updated": True}
217
+ ).update()
218
+ """
219
+ if not self._experiment._is_open:
220
+ raise RuntimeError("Experiment not open. Use experiment.open() or context manager.")
221
+
222
+ if self._experiment.write_protected:
223
+ raise RuntimeError("Experiment is write-protected and cannot be modified.")
224
+
225
+ if not self._file_id:
226
+ raise ValueError("file_id is required for update() operation")
227
+
228
+ return self._experiment._update_file(
229
+ file_id=self._file_id,
230
+ description=self._description,
231
+ tags=self._tags,
232
+ metadata=self._metadata
233
+ )
234
+
235
+
236
+ def compute_sha256(file_path: str) -> str:
237
+ """
238
+ Compute SHA256 checksum of a file.
239
+
240
+ Args:
241
+ file_path: Path to file
242
+
243
+ Returns:
244
+ Hex-encoded SHA256 checksum
245
+
246
+ Examples:
247
+ checksum = compute_sha256("./model.pt")
248
+ # Returns: "abc123def456..."
249
+ """
250
+ sha256_hash = hashlib.sha256()
251
+
252
+ with open(file_path, "rb") as f:
253
+ # Read file in chunks to handle large files
254
+ for byte_block in iter(lambda: f.read(8192), b""):
255
+ sha256_hash.update(byte_block)
256
+
257
+ return sha256_hash.hexdigest()
258
+
259
+
260
+ def get_mime_type(file_path: str) -> str:
261
+ """
262
+ Detect MIME type of a file.
263
+
264
+ Args:
265
+ file_path: Path to file
266
+
267
+ Returns:
268
+ MIME type string (default: "application/octet-stream")
269
+
270
+ Examples:
271
+ mime_type = get_mime_type("./model.pt")
272
+ # Returns: "application/octet-stream"
273
+
274
+ mime_type = get_mime_type("./image.png")
275
+ # Returns: "image/png"
276
+ """
277
+ mime_type, _ = mimetypes.guess_type(file_path)
278
+ return mime_type or "application/octet-stream"
279
+
280
+
281
+ def verify_checksum(file_path: str, expected_checksum: str) -> bool:
282
+ """
283
+ Verify SHA256 checksum of a file.
284
+
285
+ Args:
286
+ file_path: Path to file
287
+ expected_checksum: Expected SHA256 checksum (hex-encoded)
288
+
289
+ Returns:
290
+ True if checksum matches, False otherwise
291
+
292
+ Examples:
293
+ is_valid = verify_checksum("./model.pt", "abc123...")
294
+ """
295
+ actual_checksum = compute_sha256(file_path)
296
+ return actual_checksum == expected_checksum
297
+
298
+
299
+ def generate_snowflake_id() -> str:
300
+ """
301
+ Generate a simple Snowflake-like ID for local mode.
302
+
303
+ Not a true Snowflake ID, but provides unique IDs for local storage.
304
+
305
+ Returns:
306
+ String representation of generated ID
307
+ """
308
+ import time
309
+ import random
310
+
311
+ timestamp = int(time.time() * 1000)
312
+ random_bits = random.randint(0, 4095)
313
+ return str((timestamp << 12) | random_bits)
ml_dash/log.py ADDED
@@ -0,0 +1,181 @@
1
+ """
2
+ Log API for ML-Dash SDK.
3
+
4
+ Provides fluent interface for structured logging with validation.
5
+ """
6
+
7
+ from typing import Optional, Dict, Any, TYPE_CHECKING
8
+ from datetime import datetime
9
+ from enum import Enum
10
+
11
+ if TYPE_CHECKING:
12
+ from .experiment import Experiment
13
+
14
+
15
+ class LogLevel(Enum):
16
+ """
17
+ Standard log levels for ML-Dash.
18
+
19
+ Supported levels:
20
+ - INFO: Informational messages
21
+ - WARN: Warning messages
22
+ - ERROR: Error messages
23
+ - DEBUG: Debug messages
24
+ - FATAL: Fatal error messages
25
+ """
26
+ INFO = "info"
27
+ WARN = "warn"
28
+ ERROR = "error"
29
+ DEBUG = "debug"
30
+ FATAL = "fatal"
31
+
32
+ @classmethod
33
+ def validate(cls, level: str) -> str:
34
+ """
35
+ Validate and normalize log level.
36
+
37
+ Args:
38
+ level: Log level string (case-insensitive)
39
+
40
+ Returns:
41
+ Normalized log level string (lowercase)
42
+
43
+ Raises:
44
+ ValueError: If level is not one of the 5 standard levels
45
+
46
+ Example:
47
+ >>> LogLevel.validate("INFO")
48
+ "info"
49
+ >>> LogLevel.validate("invalid")
50
+ ValueError: Invalid log level: 'invalid'. Must be one of: info, warn, error, debug, fatal
51
+ """
52
+ level_lower = level.lower()
53
+ try:
54
+ return cls[level_lower.upper()].value
55
+ except KeyError:
56
+ valid_levels = ", ".join([l.value for l in cls])
57
+ raise ValueError(
58
+ f"Invalid log level: '{level}'. "
59
+ f"Must be one of: {valid_levels}"
60
+ )
61
+
62
+
63
+ class LogBuilder:
64
+ """
65
+ Fluent builder for creating log entries.
66
+
67
+ This class is returned by Experiment.log() when no message is provided.
68
+ It allows for a fluent API style where metadata is set first, then
69
+ the log level method is called to write the log.
70
+
71
+ Example:
72
+ experiment.log(metadata={"epoch": 1}).info("Training started")
73
+ experiment.log().error("Failed", error_code=500)
74
+ """
75
+
76
+ def __init__(self, experiment: 'Experiment', metadata: Optional[Dict[str, Any]] = None):
77
+ """
78
+ Initialize LogBuilder.
79
+
80
+ Args:
81
+ experiment: Parent Experiment instance
82
+ metadata: Optional metadata dict from log() call
83
+ """
84
+ self._experiment = experiment
85
+ self._metadata = metadata
86
+
87
+ def info(self, message: str, **extra_metadata) -> None:
88
+ """
89
+ Write info level log.
90
+
91
+ Args:
92
+ message: Log message
93
+ **extra_metadata: Additional metadata as keyword arguments
94
+
95
+ Example:
96
+ experiment.log().info("Training started")
97
+ experiment.log().info("Epoch complete", epoch=1, loss=0.5)
98
+ """
99
+ self._write(LogLevel.INFO.value, message, extra_metadata)
100
+
101
+ def warn(self, message: str, **extra_metadata) -> None:
102
+ """
103
+ Write warning level log.
104
+
105
+ Args:
106
+ message: Log message
107
+ **extra_metadata: Additional metadata as keyword arguments
108
+
109
+ Example:
110
+ experiment.log().warn("High loss detected", loss=1.5)
111
+ """
112
+ self._write(LogLevel.WARN.value, message, extra_metadata)
113
+
114
+ def error(self, message: str, **extra_metadata) -> None:
115
+ """
116
+ Write error level log.
117
+
118
+ Args:
119
+ message: Log message
120
+ **extra_metadata: Additional metadata as keyword arguments
121
+
122
+ Example:
123
+ experiment.log().error("Failed to save", path="/models/checkpoint.pth")
124
+ """
125
+ self._write(LogLevel.ERROR.value, message, extra_metadata)
126
+
127
+ def debug(self, message: str, **extra_metadata) -> None:
128
+ """
129
+ Write debug level log.
130
+
131
+ Args:
132
+ message: Log message
133
+ **extra_metadata: Additional metadata as keyword arguments
134
+
135
+ Example:
136
+ experiment.log().debug("Memory usage", memory_mb=2500)
137
+ """
138
+ self._write(LogLevel.DEBUG.value, message, extra_metadata)
139
+
140
+ def fatal(self, message: str, **extra_metadata) -> None:
141
+ """
142
+ Write fatal level log.
143
+
144
+ Args:
145
+ message: Log message
146
+ **extra_metadata: Additional metadata as keyword arguments
147
+
148
+ Example:
149
+ experiment.log().fatal("Unrecoverable error", exit_code=1)
150
+ """
151
+ self._write(LogLevel.FATAL.value, message, extra_metadata)
152
+
153
+ def _write(self, level: str, message: str, extra_metadata: Dict[str, Any]) -> None:
154
+ """
155
+ Internal: Execute the actual log write.
156
+
157
+ Merges metadata from log() call with metadata from level method,
158
+ then writes immediately (no buffering).
159
+
160
+ Args:
161
+ level: Log level (already validated)
162
+ message: Log message
163
+ extra_metadata: Additional metadata from level method kwargs
164
+ """
165
+ # Merge metadata from log() call with metadata from level method
166
+ if self._metadata and extra_metadata:
167
+ final_metadata = {**self._metadata, **extra_metadata}
168
+ elif self._metadata:
169
+ final_metadata = self._metadata
170
+ elif extra_metadata:
171
+ final_metadata = extra_metadata
172
+ else:
173
+ final_metadata = None
174
+
175
+ # Write immediately (no buffering)
176
+ self._experiment._write_log(
177
+ message=message,
178
+ level=level,
179
+ metadata=final_metadata,
180
+ timestamp=None
181
+ )
ml_dash/metric.py ADDED
@@ -0,0 +1,186 @@
1
+ """
2
+ Metric API - Time-series data metricing for ML experiments.
3
+
4
+ Metrics are used for storing continuous data series like training metrics,
5
+ validation losses, system measurements, etc.
6
+ """
7
+
8
+ from typing import Dict, Any, List, Optional, TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from .experiment import Experiment
12
+
13
+
14
+ class MetricBuilder:
15
+ """
16
+ Builder for metric operations.
17
+
18
+ Provides fluent API for appending, reading, and querying metric data.
19
+
20
+ Usage:
21
+ # Append single data point
22
+ experiment.metric(name="train_loss").append(value=0.5, step=100)
23
+
24
+ # Append batch
25
+ experiment.metric(name="train_loss").append_batch([
26
+ {"value": 0.5, "step": 100},
27
+ {"value": 0.45, "step": 101}
28
+ ])
29
+
30
+ # Read data
31
+ data = experiment.metric(name="train_loss").read(start_index=0, limit=100)
32
+
33
+ # Get statistics
34
+ stats = experiment.metric(name="train_loss").stats()
35
+ """
36
+
37
+ def __init__(self, experiment: 'Experiment', name: str, description: Optional[str] = None,
38
+ tags: Optional[List[str]] = None, metadata: Optional[Dict[str, Any]] = None):
39
+ """
40
+ Initialize MetricBuilder.
41
+
42
+ Args:
43
+ experiment: Parent Experiment instance
44
+ name: Metric name (unique within experiment)
45
+ description: Optional metric description
46
+ tags: Optional tags for categorization
47
+ metadata: Optional structured metadata (units, type, etc.)
48
+ """
49
+ self._experiment = experiment
50
+ self._name = name
51
+ self._description = description
52
+ self._tags = tags
53
+ self._metadata = metadata
54
+
55
+ def append(self, **kwargs) -> 'MetricBuilder':
56
+ """
57
+ Append a single data point to the metric.
58
+
59
+ The data point can have any structure - common patterns:
60
+ - {value: 0.5, step: 100}
61
+ - {loss: 0.3, accuracy: 0.92, epoch: 5}
62
+ - {timestamp: "...", temperature: 25.5, humidity: 60}
63
+
64
+ Args:
65
+ **kwargs: Data point fields (flexible schema)
66
+
67
+ Returns:
68
+ Dict with metricId, index, bufferedDataPoints, chunkSize
69
+
70
+ Example:
71
+ result = experiment.metric(name="train_loss").append(value=0.5, step=100, epoch=1)
72
+ print(f"Appended at index {result['index']}")
73
+ """
74
+ result = self._experiment._append_to_metric(
75
+ name=self._name,
76
+ data=kwargs,
77
+ description=self._description,
78
+ tags=self._tags,
79
+ metadata=self._metadata
80
+ )
81
+ return result
82
+
83
+ def append_batch(self, data_points: List[Dict[str, Any]]) -> Dict[str, Any]:
84
+ """
85
+ Append multiple data points in batch (more efficient than multiple append calls).
86
+
87
+ Args:
88
+ data_points: List of data point dicts
89
+
90
+ Returns:
91
+ Dict with metricId, startIndex, endIndex, count, bufferedDataPoints, chunkSize
92
+
93
+ Example:
94
+ result = experiment.metric(name="metrics").append_batch([
95
+ {"loss": 0.5, "acc": 0.8, "step": 1},
96
+ {"loss": 0.4, "acc": 0.85, "step": 2},
97
+ {"loss": 0.3, "acc": 0.9, "step": 3}
98
+ ])
99
+ print(f"Appended {result['count']} points")
100
+ """
101
+ if not data_points:
102
+ raise ValueError("data_points cannot be empty")
103
+
104
+ result = self._experiment._append_batch_to_metric(
105
+ name=self._name,
106
+ data_points=data_points,
107
+ description=self._description,
108
+ tags=self._tags,
109
+ metadata=self._metadata
110
+ )
111
+ return result
112
+
113
+ def read(self, start_index: int = 0, limit: int = 1000) -> Dict[str, Any]:
114
+ """
115
+ Read data points from the metric by index range.
116
+
117
+ Args:
118
+ start_index: Starting index (inclusive, default 0)
119
+ limit: Maximum number of points to read (default 1000, max 10000)
120
+
121
+ Returns:
122
+ Dict with keys:
123
+ - data: List of {index: str, data: dict, createdAt: str}
124
+ - startIndex: Starting index
125
+ - endIndex: Ending index
126
+ - total: Number of points returned
127
+ - hasMore: Whether more data exists beyond this range
128
+
129
+ Example:
130
+ result = experiment.metric(name="train_loss").read(start_index=0, limit=100)
131
+ for point in result['data']:
132
+ print(f"Index {point['index']}: {point['data']}")
133
+ """
134
+ return self._experiment._read_metric_data(
135
+ name=self._name,
136
+ start_index=start_index,
137
+ limit=limit
138
+ )
139
+
140
+ def stats(self) -> Dict[str, Any]:
141
+ """
142
+ Get metric statistics and metadata.
143
+
144
+ Returns:
145
+ Dict with metric info:
146
+ - metricId: Unique metric ID
147
+ - name: Metric name
148
+ - description: Metric description (if set)
149
+ - tags: Tags list
150
+ - metadata: User metadata
151
+ - totalDataPoints: Total points (buffered + chunked)
152
+ - bufferedDataPoints: Points in MongoDB (hot storage)
153
+ - chunkedDataPoints: Points in S3 (cold storage)
154
+ - totalChunks: Number of chunks in S3
155
+ - chunkSize: Chunking threshold
156
+ - firstDataAt: Timestamp of first point (if data has timestamp)
157
+ - lastDataAt: Timestamp of last point (if data has timestamp)
158
+ - createdAt: Metric creation time
159
+ - updatedAt: Last update time
160
+
161
+ Example:
162
+ stats = experiment.metric(name="train_loss").stats()
163
+ print(f"Total points: {stats['totalDataPoints']}")
164
+ print(f"Buffered: {stats['bufferedDataPoints']}, Chunked: {stats['chunkedDataPoints']}")
165
+ """
166
+ return self._experiment._get_metric_stats(name=self._name)
167
+
168
+ def list_all(self) -> List[Dict[str, Any]]:
169
+ """
170
+ List all metrics in the experiment.
171
+
172
+ Returns:
173
+ List of metric summaries with keys:
174
+ - metricId: Unique metric ID
175
+ - name: Metric name
176
+ - description: Metric description
177
+ - tags: Tags list
178
+ - totalDataPoints: Total data points
179
+ - createdAt: Creation timestamp
180
+
181
+ Example:
182
+ metrics = experiment.metric().list_all()
183
+ for metric in metrics:
184
+ print(f"{metric['name']}: {metric['totalDataPoints']} points")
185
+ """
186
+ return self._experiment._list_metrics()