mllogs 0.1.3__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.
mllogs/__init__.py ADDED
File without changes
mllogs/client.py ADDED
@@ -0,0 +1,59 @@
1
+ from typing import Any
2
+ from datetime import datetime, UTC
3
+ from uuid import uuid4
4
+
5
+ from .run import Run, RunStatus
6
+
7
+
8
+ class MLLogsClient:
9
+ def __init__(self):
10
+ self._active_run: Run | None = None
11
+
12
+ def start_run(
13
+ self,
14
+ name: str | None = None,
15
+ run_type: str | None = None,
16
+ ) -> None:
17
+ """
18
+ Start a new MLLogs run.
19
+
20
+ Args:
21
+ name: Optional name for the run.
22
+ run_type: Optional type used to categorize the run.
23
+ """
24
+ # generate run id
25
+ started_at = datetime.now(UTC)
26
+ timestamp = started_at.strftime("%Y%m%d%H%M%S")
27
+ random_suffix = uuid4().hex[:8]
28
+ run_id = f"{timestamp}-{random_suffix}"
29
+
30
+ self._active_run = Run(
31
+ id=run_id,
32
+ name=name,
33
+ run_type=run_type,
34
+ status = RunStatus.RUNNING,
35
+ started_at=started_at,
36
+ )
37
+ # END
38
+
39
+
40
+ def log_param(self, key: str, value: Any) -> None:
41
+ self._active_run.params[key] = value
42
+
43
+
44
+ def log_metric(self, key: str, value: float) -> None:
45
+ self._active_run.metrics[key] = value
46
+
47
+
48
+ def set_tag(self, key: str, value: str) -> None:
49
+ self._active_run.tags[key] = value
50
+
51
+
52
+ def end_run(self) -> None:
53
+ self._active_run.ended_at = datetime.now(UTC)
54
+
55
+ self._active_run.status = RunStatus.COMPLETE
56
+
57
+ # clear run
58
+ self._active_run = None
59
+ # END
mllogs/run.py ADDED
@@ -0,0 +1,88 @@
1
+ from datetime import datetime
2
+ from dataclasses import dataclass, field
3
+ from typing import Any
4
+ from enum import Enum
5
+
6
+
7
+ class RunStatus(Enum):
8
+ RUNNING = "running"
9
+ COMPLETE = "complete"
10
+ FAILED = "failed"
11
+
12
+
13
+ @dataclass
14
+ class Artifact:
15
+ name: str
16
+ uri: str
17
+ artifact_type: str
18
+
19
+ def to_dict(self) -> dict[str, str]:
20
+ return {
21
+ "name": self.name,
22
+ "uri": self.uri,
23
+ "artifact_type": self.artifact_type,
24
+ }
25
+
26
+
27
+ @classmethod
28
+ def from_dict(cls, d: dict[str, str]) -> "Artifact":
29
+ return cls(
30
+ name = d["name"],
31
+ uri = d["uri"],
32
+ artifact_type = d["artifact_type"],
33
+ )
34
+
35
+
36
+ @dataclass
37
+ class Run:
38
+ id: str
39
+ started_at: datetime
40
+ status: RunStatus
41
+
42
+ name: str | None = None
43
+ run_type: str | None = None
44
+ ended_at: datetime | None = None
45
+
46
+ params: dict[str, Any] = field(default_factory=dict)
47
+ metrics: dict[str, float] = field(default_factory=dict)
48
+ tags: dict[str, str] = field(default_factory=dict)
49
+ artifacts: list[Artifact] = field(default_factory=list)
50
+
51
+
52
+ def to_dict(self) -> dict[str, Any]:
53
+ return {
54
+ "id": self.id,
55
+ "started_at": self.started_at.isoformat(),
56
+ "status": self.status.value,
57
+ "name": self.name,
58
+ "run_type": self.run_type,
59
+ "ended_at": (
60
+ self.ended_at.isoformat()
61
+ if self.ended_at is not None
62
+ else None
63
+ ),
64
+ "params": self.params,
65
+ "metrics": self.metrics,
66
+ "tags": self.tags,
67
+ "artifacts": [a.to_dict() for a in self.artifacts],
68
+ }
69
+
70
+
71
+ @classmethod
72
+ def from_dict(cls, d: dict[str, Any]) -> "Run":
73
+ return cls(
74
+ id = d["id"],
75
+ started_at = datetime.fromisoformat(d["started_at"]),
76
+ status = RunStatus(d["status"]),
77
+ name = d["name"],
78
+ run_type = d["run_type"],
79
+ ended_at = (
80
+ datetime.fromisoformat(d["ended_at"])
81
+ if d["ended_at"] is not None
82
+ else None
83
+ ),
84
+ params = d["params"],
85
+ metrics = d["metrics"],
86
+ tags = d["tags"],
87
+ artifacts = [Artifact.from_dict(a) for a in d["artifacts"]],
88
+ )
mllogs/storage.py ADDED
@@ -0,0 +1,72 @@
1
+ from pathlib import Path
2
+ import json
3
+
4
+ from .run import Run
5
+
6
+
7
+ class LocalFileStore:
8
+ def __init__(self, root_dir: str | Path = ".mllogs") -> None:
9
+ """
10
+ Initializes LocalFileStore and creates runs directory if it
11
+ does not already exist.
12
+ """
13
+ self._root_dir = Path(root_dir)
14
+ self._runs_dir = self._root_dir / "runs"
15
+
16
+ self._runs_dir.mkdir(parents=True, exist_ok=True)
17
+
18
+ def save_run(self, run: Run) -> None:
19
+ """
20
+ Writes Run to storage as JSON file.
21
+ """
22
+ path = self._runs_dir / f"{run.id}.json"
23
+
24
+ with path.open("w") as f:
25
+ json.dump(run.to_dict(), f, indent=4)
26
+
27
+
28
+ def load_run(self, run_id: str) -> Run:
29
+ """
30
+ Reads JSON run file from storage and returns a Run.
31
+ """
32
+ path = self._runs_dir / f"{run_id}.json"
33
+
34
+ with path.open("r") as f:
35
+ run_dict = json.load(f)
36
+
37
+ return Run.from_dict(run_dict)
38
+
39
+
40
+ def list_runs(self, limit: int | None = None) -> list[Run]:
41
+ """
42
+ Returns the most recent runs.
43
+
44
+ If limit is None, returns all runs.
45
+ """
46
+ if limit is not None and limit <= 0:
47
+ raise ValueError("limit must be greater than 0")
48
+
49
+ paths = sorted(
50
+ self._runs_dir.glob("*.json"),
51
+ reverse=True, # latest first
52
+ )
53
+
54
+ if limit is not None:
55
+ paths = paths[:limit]
56
+
57
+ runs = []
58
+
59
+ for path in paths:
60
+ run = self.load_run(path.stem)
61
+ runs.append(run)
62
+
63
+ return runs
64
+
65
+
66
+ def delete_run(self, run_id: str) -> None:
67
+ """
68
+ Deletes a run from storage by run ID.
69
+ """
70
+ path = self._runs_dir / f"{run_id}.json"
71
+
72
+ path.unlink()
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: mllogs
3
+ Version: 0.1.3
4
+ Summary: Local experiment logging for machine learning
5
+ Author: Finn Walsh
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=8.0; extra == "dev"
12
+ Dynamic: license-file
13
+
14
+ # mllogs
15
+
16
+ Local experiment logging for machine learning.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install mllogs
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```python
27
+ from mllogs import MLLogsClient
28
+
29
+ client = MLLogsClient()
30
+
31
+ client.start_run(
32
+ name="baseline",
33
+ run_type="training",
34
+ )
35
+
36
+ client.log_param("learning_rate", 0.01)
37
+ client.log_metric("accuracy", 0.92)
38
+ client.set_tag("model", "logistic_regression")
39
+
40
+ client.end_run()
41
+ ```
42
+
43
+ ## Features
44
+
45
+ - Start and end experiment runs
46
+ - Log parameters, metrics, and tags
47
+ - Save runs to local storage
48
+ - Load, list, and delete saved runs
49
+
50
+ ## Development
51
+
52
+ Install from source with development dependencies:
53
+
54
+ ```bash
55
+ pip install -e ".[dev]"
56
+ ```
57
+
58
+ Run the test suite:
59
+
60
+ ```bash
61
+ pytest
62
+ ```
63
+
64
+ ## License
65
+
66
+ MIT
@@ -0,0 +1,9 @@
1
+ mllogs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ mllogs/client.py,sha256=o1gjE9NT4UqQfEgJf4FfK8MZhaAOiSdYZm9vPtMOzrY,1447
3
+ mllogs/run.py,sha256=gsZnG-hWhN6fXDie73xz6H8X3ts_jBV0MmjcZj3i2DY,2324
4
+ mllogs/storage.py,sha256=uILR8SYsZL6IjC6vxl-tlABo7LRcSHkFIRwuj10FuSo,1753
5
+ mllogs-0.1.3.dist-info/licenses/LICENSE,sha256=JlLb6swOXBl0b_vjSfh-Cd0m6XWAAnAgmzIivu6GJkc,1067
6
+ mllogs-0.1.3.dist-info/METADATA,sha256=KQIyG1TfN5vWeTxOmQ7b6iV5lhYyO5a8yFJ6t5oA_mQ,1027
7
+ mllogs-0.1.3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
8
+ mllogs-0.1.3.dist-info/top_level.txt,sha256=CHao4JCO6OOKT-wDwz8xEDDdTRYC5zL8JBroggHTyWY,7
9
+ mllogs-0.1.3.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Finn Walsh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ mllogs