laco-dvc 1.0.0__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.
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: laco-dvc
3
+ Version: 1.0.0
4
+ Summary: DVC artifact tracking integration for laco.
5
+ Author-email: Kurt Stolle <kurt@khws.io>
6
+ Requires-Python: >=3.13
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: laco>=1.0.0
9
+ Requires-Dist: dvc>=3.0
10
+
11
+ # Laco-DVC
12
+
13
+ DVC artifact tracking for laco experiments.
14
+
15
+ Part of the [laco](https://github.com/khwstolle/laco) project — see the [root README](../../README.md) for an overview.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install laco-dvc
21
+ ```
22
+
23
+ ## Features
24
+
25
+ `commit_config()`, `add()`, `track()` context manager
26
+
27
+ ## Usage
28
+
29
+ See [`docs/index.md`](docs/index.md) for the full guide.
@@ -0,0 +1,19 @@
1
+ # Laco-DVC
2
+
3
+ DVC artifact tracking for laco experiments.
4
+
5
+ Part of the [laco](https://github.com/khwstolle/laco) project — see the [root README](../../README.md) for an overview.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install laco-dvc
11
+ ```
12
+
13
+ ## Features
14
+
15
+ `commit_config()`, `add()`, `track()` context manager
16
+
17
+ ## Usage
18
+
19
+ See [`docs/index.md`](docs/index.md) for the full guide.
@@ -0,0 +1,19 @@
1
+ [build-system]
2
+ requires = ["setuptools>=75", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "laco-dvc"
7
+ version = "1.0.0"
8
+ description = "DVC artifact tracking integration for laco."
9
+ readme = "README.md"
10
+ requires-python = ">=3.13"
11
+ authors = [{ name = "Kurt Stolle", email = "kurt@khws.io" }]
12
+ dependencies = ["laco>=1.0.0", "dvc>=3.0"]
13
+
14
+ [tool.setuptools.packages.find]
15
+ where = ["sources"]
16
+ namespaces = true
17
+
18
+ [tool.uv.sources]
19
+ laco = { workspace = true }
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,28 @@
1
+ """DVC artifact tracking integration for laco.
2
+
3
+ Tracks laco config files and output directories as DVC artifacts, making
4
+ experiment outputs reproducible and linkable to their exact config.
5
+
6
+ Examples
7
+ --------
8
+ ::
9
+
10
+ import laco
11
+ import laco.integrations.dvc as laco_dvc
12
+
13
+ cfg = laco.load("configs/train.py")
14
+
15
+ # Run training and track outputs with DVC:
16
+ with laco_dvc.track(cfg, output_dir="outputs/run-001") as tracker:
17
+ train(cfg)
18
+ tracker.add("outputs/run-001/checkpoint.pt")
19
+
20
+ # Or just commit the config snapshot after a run:
21
+ laco_dvc.commit_config(cfg, path="outputs/run-001/config.yaml")
22
+ """
23
+
24
+ from laco.integrations.dvc._core import add as add
25
+ from laco.integrations.dvc._core import commit_config as commit_config
26
+ from laco.integrations.dvc._core import track as track
27
+
28
+ __all__ = ["add", "commit_config", "track"]
@@ -0,0 +1,117 @@
1
+ """Implementation for laco-dvc."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import pathlib
7
+ import typing
8
+
9
+
10
+ def commit_config(
11
+ cfg: typing.Any,
12
+ *,
13
+ path: str | pathlib.Path = "config.yaml",
14
+ message: str | None = None,
15
+ ) -> pathlib.Path:
16
+ """Save a resolved laco DictConfig as a DVC-tracked YAML file.
17
+
18
+ Writes *cfg* to *path*, runs ``dvc add`` on it, and optionally commits
19
+ the resulting ``.dvc`` stub to git.
20
+
21
+ Parameters
22
+ ----------
23
+ cfg : DictConfig
24
+ Resolved OmegaConf DictConfig from ``laco.load()``.
25
+ path : str | Path
26
+ Destination file path. Created (with parents) if absent.
27
+ Default: ``"config.yaml"``.
28
+ message : str | None
29
+ If set, also runs ``git commit -m <message>`` on the generated
30
+ ``.dvc`` stub.
31
+
32
+ Returns
33
+ -------
34
+ Path
35
+ Path to the saved YAML file.
36
+ """
37
+ import subprocess
38
+
39
+ from omegaconf import OmegaConf
40
+
41
+ out = pathlib.Path(path)
42
+ out.parent.mkdir(parents=True, exist_ok=True)
43
+ out.write_text(OmegaConf.to_yaml(cfg, resolve=True))
44
+
45
+ subprocess.run(["dvc", "add", str(out)], check=True) # noqa: S603, S607
46
+
47
+ if message:
48
+ dvc_stub = out.with_suffix(out.suffix + ".dvc")
49
+ subprocess.run( # noqa: S603, S607
50
+ ["git", "commit", str(dvc_stub), "-m", message], check=True
51
+ )
52
+
53
+ return out
54
+
55
+
56
+ def add(path: str | pathlib.Path) -> None:
57
+ """Run ``dvc add`` on *path* to start tracking it as a DVC artifact.
58
+
59
+ Parameters
60
+ ----------
61
+ path : str | Path
62
+ File or directory to track with DVC.
63
+ """
64
+ import subprocess
65
+
66
+ subprocess.run(["dvc", "add", str(path)], check=True) # noqa: S603, S607
67
+
68
+
69
+ @contextlib.contextmanager
70
+ def track(
71
+ cfg: typing.Any,
72
+ *,
73
+ output_dir: str | pathlib.Path,
74
+ config_name: str = "config.yaml",
75
+ ) -> typing.Generator[_Tracker]:
76
+ """Context manager that saves the config and tracks outputs with DVC.
77
+
78
+ Saves *cfg* as ``<output_dir>/<config_name>`` on entry, then calls
79
+ ``dvc add`` on any paths registered via ``tracker.add()`` on exit.
80
+
81
+ Parameters
82
+ ----------
83
+ cfg : DictConfig
84
+ Resolved laco DictConfig.
85
+ output_dir : str | Path
86
+ Directory where run artifacts are written.
87
+ config_name : str
88
+ Filename for the config snapshot inside *output_dir*.
89
+ Default: ``"config.yaml"``.
90
+
91
+ Yields
92
+ ------
93
+ _Tracker
94
+ Object with an ``add(path)`` method for registering outputs to be
95
+ tracked by DVC on context exit.
96
+ """
97
+ out = pathlib.Path(output_dir)
98
+ out.mkdir(parents=True, exist_ok=True)
99
+ commit_config(cfg, path=out / config_name)
100
+
101
+ tracker = _Tracker()
102
+ try:
103
+ yield tracker
104
+ finally:
105
+ for p in tracker._paths:
106
+ add(p)
107
+
108
+
109
+ class _Tracker:
110
+ """Collects output paths to be DVC-tracked on context exit."""
111
+
112
+ def __init__(self) -> None:
113
+ self._paths: list[pathlib.Path] = []
114
+
115
+ def add(self, path: str | pathlib.Path) -> None:
116
+ """Register *path* to be tracked by DVC when the context exits."""
117
+ self._paths.append(pathlib.Path(path))
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: laco-dvc
3
+ Version: 1.0.0
4
+ Summary: DVC artifact tracking integration for laco.
5
+ Author-email: Kurt Stolle <kurt@khws.io>
6
+ Requires-Python: >=3.13
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: laco>=1.0.0
9
+ Requires-Dist: dvc>=3.0
10
+
11
+ # Laco-DVC
12
+
13
+ DVC artifact tracking for laco experiments.
14
+
15
+ Part of the [laco](https://github.com/khwstolle/laco) project — see the [root README](../../README.md) for an overview.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install laco-dvc
21
+ ```
22
+
23
+ ## Features
24
+
25
+ `commit_config()`, `add()`, `track()` context manager
26
+
27
+ ## Usage
28
+
29
+ See [`docs/index.md`](docs/index.md) for the full guide.
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ sources/laco/integrations/dvc/__init__.py
4
+ sources/laco/integrations/dvc/_core.py
5
+ sources/laco_dvc.egg-info/PKG-INFO
6
+ sources/laco_dvc.egg-info/SOURCES.txt
7
+ sources/laco_dvc.egg-info/dependency_links.txt
8
+ sources/laco_dvc.egg-info/requires.txt
9
+ sources/laco_dvc.egg-info/top_level.txt
10
+ tests/test_laco_dvc.py
@@ -0,0 +1,2 @@
1
+ laco>=1.0.0
2
+ dvc>=3.0
@@ -0,0 +1,91 @@
1
+ """Tests for laco-dvc."""
2
+
3
+ from unittest.mock import patch
4
+
5
+
6
+ def test_import():
7
+ import laco.integrations.dvc # noqa: F401
8
+
9
+
10
+ def test_public_api():
11
+ import laco.integrations.dvc as laco_dvc
12
+
13
+ assert set(laco_dvc.__all__) == {"add", "commit_config", "track"}
14
+
15
+
16
+ def test_add_calls_dvc(tmp_path):
17
+ import laco.integrations.dvc as laco_dvc
18
+
19
+ target = tmp_path / "artifact.pt"
20
+ target.write_bytes(b"data")
21
+
22
+ with patch("subprocess.run") as mock_run:
23
+ laco_dvc.add(target)
24
+ mock_run.assert_called_once_with(["dvc", "add", str(target)], check=True)
25
+
26
+
27
+ def test_commit_config_writes_yaml_and_calls_dvc(tmp_path):
28
+ import laco.integrations.dvc as laco_dvc
29
+ from omegaconf import OmegaConf
30
+
31
+ cfg = OmegaConf.create({"lr": 0.001, "epochs": 10})
32
+ out = tmp_path / "config.yaml"
33
+
34
+ with patch("subprocess.run") as mock_run:
35
+ result = laco_dvc.commit_config(cfg, path=out)
36
+
37
+ assert result == out
38
+ assert out.exists()
39
+ content = out.read_text()
40
+ assert "lr:" in content
41
+ assert "epochs:" in content
42
+ mock_run.assert_called_once_with(["dvc", "add", str(out)], check=True)
43
+
44
+
45
+ def test_commit_config_with_git_message(tmp_path):
46
+ import laco.integrations.dvc as laco_dvc
47
+ from omegaconf import OmegaConf
48
+
49
+ cfg = OmegaConf.create({"lr": 0.001})
50
+ out = tmp_path / "config.yaml"
51
+ dvc_stub = out.with_suffix(".yaml.dvc")
52
+
53
+ with patch("subprocess.run") as mock_run:
54
+ laco_dvc.commit_config(cfg, path=out, message="track config")
55
+
56
+ assert mock_run.call_count == 2
57
+ mock_run.assert_any_call(["dvc", "add", str(out)], check=True)
58
+ mock_run.assert_any_call(
59
+ ["git", "commit", str(dvc_stub), "-m", "track config"], check=True
60
+ )
61
+
62
+
63
+ def test_track_context_manager(tmp_path):
64
+ import laco.integrations.dvc as laco_dvc
65
+ from omegaconf import OmegaConf
66
+
67
+ cfg = OmegaConf.create({"lr": 0.001})
68
+ output_dir = tmp_path / "run-001"
69
+ artifact = output_dir / "model.pt"
70
+
71
+ with patch("subprocess.run"):
72
+ with laco_dvc.track(cfg, output_dir=output_dir) as tracker:
73
+ artifact.write_bytes(b"weights")
74
+ tracker.add(artifact)
75
+
76
+ assert artifact.exists()
77
+
78
+
79
+ def test_tracker_add_registers_paths(tmp_path):
80
+ from laco.integrations.dvc._core import _Tracker
81
+
82
+ tracker = _Tracker()
83
+ path_a = tmp_path / "a.pt"
84
+ path_b = tmp_path / "b.pt"
85
+
86
+ tracker.add(path_a)
87
+ tracker.add(str(path_b))
88
+
89
+ assert len(tracker._paths) == 2
90
+ assert tracker._paths[0] == path_a
91
+ assert tracker._paths[1] == path_b