hydraflow 0.9.0__tar.gz → 0.10.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.
- {hydraflow-0.9.0 → hydraflow-0.10.0}/PKG-INFO +2 -2
- {hydraflow-0.9.0 → hydraflow-0.10.0}/pyproject.toml +2 -2
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/__init__.py +8 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/core/io.py +72 -1
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/executor/job.py +1 -1
- hydraflow-0.10.0/tests/core/io/test_iter_dirs.py +82 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/test_mlflow.py +7 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/executor/test_job.py +1 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/.devcontainer/devcontainer.json +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/.devcontainer/postCreate.sh +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/.devcontainer/starship.toml +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/.gitattributes +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/.github/workflows/ci.yaml +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/.github/workflows/docs.yaml +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/.gitignore +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/LICENSE +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/README.md +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/apps/quickstart.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/docs/index.md +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/docs/usage/quickstart.md +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/mkdocs.yaml +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/cli.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/core/__init__.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/core/config.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/core/context.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/core/main.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/core/mlflow.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/core/param.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/entities/__init__.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/entities/run_collection.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/entities/run_data.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/entities/run_info.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/executor/__init__.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/executor/conf.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/executor/io.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/executor/parser.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/src/hydraflow/py.typed +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/__init__.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/cli/__init__.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/cli/app.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/cli/conftest.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/cli/hydraflow.yaml +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/cli/test_run.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/cli/test_setup.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/cli/test_show.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/cli/test_version.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/conftest.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/__init__.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/config/__init__.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/config/test_config.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/config/test_params.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/context/__init__.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/context/chdir.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/context/log_run.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/context/start_run.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/context/test_chdir.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/context/test_log_run.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/context/test_start_run.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/io/__init__.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/io/hydra_dir.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/io/test_hydra_dir.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/io/test_run.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/main/__init__.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/main/default.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/main/force_new_run.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/main/match_overrides.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/main/rerun_finished.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/main/skip_finished.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/main/test_default.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/main/test_force_new_run.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/main/test_match_overrides.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/main/test_rerun_finished.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/main/test_skip_finished.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/param/__init__.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/param/params.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/param/test_param.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/core/param/test_params.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/entities/__init__.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/entities/filter.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/entities/test_collection.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/entities/test_data.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/entities/test_filter.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/entities/test_info.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/entities/test_values.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/entities/values.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/executor/__init__.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/executor/conftest.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/executor/echo.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/executor/test_args.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/executor/test_conf.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/executor/test_io.py +0 -0
- {hydraflow-0.9.0 → hydraflow-0.10.0}/tests/executor/test_parser.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hydraflow
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.10.0
|
4
4
|
Summary: Hydraflow integrates Hydra and MLflow to manage and track machine learning experiments.
|
5
5
|
Project-URL: Documentation, https://daizutabi.github.io/hydraflow/
|
6
6
|
Project-URL: Source, https://github.com/daizutabi/hydraflow
|
@@ -39,9 +39,9 @@ Requires-Python: >=3.10
|
|
39
39
|
Requires-Dist: hydra-core>=1.3
|
40
40
|
Requires-Dist: mlflow>=2.15
|
41
41
|
Requires-Dist: omegaconf
|
42
|
+
Requires-Dist: python-ulid>=3.0.0
|
42
43
|
Requires-Dist: rich
|
43
44
|
Requires-Dist: typer
|
44
|
-
Requires-Dist: ulid
|
45
45
|
Description-Content-Type: text/markdown
|
46
46
|
|
47
47
|
# Hydraflow
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "hydraflow"
|
7
|
-
version = "0.
|
7
|
+
version = "0.10.0"
|
8
8
|
description = "Hydraflow integrates Hydra and MLflow to manage and track machine learning experiments."
|
9
9
|
readme = "README.md"
|
10
10
|
license = { file = "LICENSE" }
|
@@ -23,9 +23,9 @@ dependencies = [
|
|
23
23
|
"hydra-core>=1.3",
|
24
24
|
"mlflow>=2.15",
|
25
25
|
"omegaconf",
|
26
|
+
"python-ulid>=3.0.0",
|
26
27
|
"rich",
|
27
28
|
"typer",
|
28
|
-
"ulid",
|
29
29
|
]
|
30
30
|
|
31
31
|
[project.urls]
|
@@ -5,6 +5,10 @@ from hydraflow.core.io import (
|
|
5
5
|
get_artifact_dir,
|
6
6
|
get_artifact_path,
|
7
7
|
get_hydra_output_dir,
|
8
|
+
iter_artifact_paths,
|
9
|
+
iter_artifacts_dirs,
|
10
|
+
iter_experiment_dirs,
|
11
|
+
iter_run_dirs,
|
8
12
|
load_config,
|
9
13
|
remove_run,
|
10
14
|
)
|
@@ -18,6 +22,10 @@ __all__ = [
|
|
18
22
|
"get_artifact_dir",
|
19
23
|
"get_artifact_path",
|
20
24
|
"get_hydra_output_dir",
|
25
|
+
"iter_artifact_paths",
|
26
|
+
"iter_artifacts_dirs",
|
27
|
+
"iter_experiment_dirs",
|
28
|
+
"iter_run_dirs",
|
21
29
|
"list_run_ids",
|
22
30
|
"list_run_paths",
|
23
31
|
"list_runs",
|
@@ -15,7 +15,7 @@ from mlflow.entities import Run
|
|
15
15
|
from omegaconf import DictConfig, ListConfig, OmegaConf
|
16
16
|
|
17
17
|
if TYPE_CHECKING:
|
18
|
-
from collections.abc import Iterable
|
18
|
+
from collections.abc import Iterable, Iterator
|
19
19
|
|
20
20
|
|
21
21
|
def file_uri_to_path(uri: str) -> Path:
|
@@ -147,3 +147,74 @@ def remove_run(run: Run | Iterable[Run]) -> None:
|
|
147
147
|
return
|
148
148
|
|
149
149
|
shutil.rmtree(get_artifact_dir(run).parent)
|
150
|
+
|
151
|
+
|
152
|
+
def get_root_dir(uri: str | Path | None = None) -> Path:
|
153
|
+
"""Get the root directory for the MLflow tracking server."""
|
154
|
+
if uri is not None:
|
155
|
+
return Path(uri).absolute()
|
156
|
+
|
157
|
+
uri = mlflow.get_tracking_uri()
|
158
|
+
|
159
|
+
if uri.startswith("file:"):
|
160
|
+
return file_uri_to_path(uri)
|
161
|
+
|
162
|
+
return Path(uri).absolute()
|
163
|
+
|
164
|
+
|
165
|
+
def get_experiment_name(path: Path) -> str | None:
|
166
|
+
"""Get the experiment name from the meta file."""
|
167
|
+
metafile = path / "meta.yaml"
|
168
|
+
if not metafile.exists():
|
169
|
+
return None
|
170
|
+
lines = metafile.read_text().splitlines()
|
171
|
+
for line in lines:
|
172
|
+
if line.startswith("name:"):
|
173
|
+
return line.split(":")[1].strip()
|
174
|
+
return None
|
175
|
+
|
176
|
+
|
177
|
+
def iter_experiment_dirs(
|
178
|
+
experiment_names: str | list[str] | None = None,
|
179
|
+
root_dir: str | Path | None = None,
|
180
|
+
) -> Iterator[Path]:
|
181
|
+
"""Iterate over the experiment directories in the root directory."""
|
182
|
+
if isinstance(experiment_names, str):
|
183
|
+
experiment_names = [experiment_names]
|
184
|
+
|
185
|
+
root_dir = get_root_dir(root_dir)
|
186
|
+
for path in root_dir.iterdir():
|
187
|
+
if path.is_dir() and path.name not in [".trash", "0"]:
|
188
|
+
if name := get_experiment_name(path):
|
189
|
+
if experiment_names is None or name in experiment_names:
|
190
|
+
yield path
|
191
|
+
|
192
|
+
|
193
|
+
def iter_run_dirs(
|
194
|
+
experiment_names: str | list[str] | None = None,
|
195
|
+
root_dir: str | Path | None = None,
|
196
|
+
) -> Iterator[Path]:
|
197
|
+
"""Iterate over the run directories in the root directory."""
|
198
|
+
for experiment_dir in iter_experiment_dirs(experiment_names, root_dir):
|
199
|
+
for path in experiment_dir.iterdir():
|
200
|
+
if path.is_dir() and (path / "artifacts").exists():
|
201
|
+
yield path
|
202
|
+
|
203
|
+
|
204
|
+
def iter_artifacts_dirs(
|
205
|
+
experiment_names: str | list[str] | None = None,
|
206
|
+
root_dir: str | Path | None = None,
|
207
|
+
) -> Iterator[Path]:
|
208
|
+
"""Iterate over the artifacts directories in the root directory."""
|
209
|
+
for path in iter_run_dirs(experiment_names, root_dir):
|
210
|
+
yield path / "artifacts"
|
211
|
+
|
212
|
+
|
213
|
+
def iter_artifact_paths(
|
214
|
+
artifact_path: str | Path,
|
215
|
+
experiment_names: str | list[str] | None = None,
|
216
|
+
root_dir: str | Path | None = None,
|
217
|
+
) -> Iterator[Path]:
|
218
|
+
"""Iterate over the artifact paths in the root directory."""
|
219
|
+
for path in iter_artifacts_dirs(experiment_names, root_dir):
|
220
|
+
yield path / artifact_path
|
@@ -71,7 +71,7 @@ def iter_batches(job: Job) -> Iterator[list[str]]:
|
|
71
71
|
|
72
72
|
for step in job.steps:
|
73
73
|
for args in iter_args(step):
|
74
|
-
sweep_dir = f"hydra.sweep.dir=multirun/{ulid.
|
74
|
+
sweep_dir = f"hydra.sweep.dir=multirun/{ulid.ULID()}"
|
75
75
|
yield ["--multirun", sweep_dir, job_name, *args]
|
76
76
|
|
77
77
|
|
@@ -0,0 +1,82 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
import mlflow
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
pytestmark = pytest.mark.xdist_group(name="group1")
|
7
|
+
|
8
|
+
|
9
|
+
@pytest.fixture(scope="module")
|
10
|
+
def root_dir(chdir):
|
11
|
+
return Path("mlruns").absolute()
|
12
|
+
|
13
|
+
|
14
|
+
@pytest.fixture(scope="module", autouse=True)
|
15
|
+
def setup(chdir):
|
16
|
+
mlflow.set_experiment("e1")
|
17
|
+
with mlflow.start_run():
|
18
|
+
mlflow.log_text("1", "text.txt")
|
19
|
+
with mlflow.start_run():
|
20
|
+
mlflow.log_text("2", "text.txt")
|
21
|
+
mlflow.set_experiment("e2")
|
22
|
+
with mlflow.start_run():
|
23
|
+
mlflow.log_text("3", "text.txt")
|
24
|
+
with mlflow.start_run():
|
25
|
+
mlflow.log_text("4", "text.txt")
|
26
|
+
with mlflow.start_run():
|
27
|
+
mlflow.log_text("5", "text.txt")
|
28
|
+
|
29
|
+
|
30
|
+
def test_root_dir(root_dir: Path):
|
31
|
+
from hydraflow.core.io import get_root_dir
|
32
|
+
|
33
|
+
assert get_root_dir(root_dir) == root_dir
|
34
|
+
assert get_root_dir(root_dir.name) == root_dir
|
35
|
+
assert get_root_dir() == root_dir
|
36
|
+
|
37
|
+
|
38
|
+
def test_iter_experiment_dirs():
|
39
|
+
from hydraflow.core.io import get_experiment_name, iter_experiment_dirs
|
40
|
+
|
41
|
+
names = [get_experiment_name(p) for p in iter_experiment_dirs()]
|
42
|
+
assert sorted(names) == ["e1", "e2"] # type: ignore
|
43
|
+
|
44
|
+
|
45
|
+
def test_iter_experiment_dirs_filter():
|
46
|
+
from hydraflow.core.io import get_experiment_name, iter_experiment_dirs
|
47
|
+
|
48
|
+
it = iter_experiment_dirs(experiment_names="e1")
|
49
|
+
assert [get_experiment_name(p) for p in it] == ["e1"]
|
50
|
+
|
51
|
+
|
52
|
+
def test_get_experiment_name_none(root_dir: Path):
|
53
|
+
from hydraflow.core.io import get_experiment_name
|
54
|
+
|
55
|
+
assert get_experiment_name(root_dir.parent) is None
|
56
|
+
|
57
|
+
|
58
|
+
def test_get_experiment_name_metafile_none(root_dir: Path):
|
59
|
+
from hydraflow.core.io import get_experiment_name
|
60
|
+
|
61
|
+
(root_dir / "meta.yaml").touch()
|
62
|
+
assert get_experiment_name(root_dir) is None
|
63
|
+
|
64
|
+
|
65
|
+
def test_iter_run_dirs():
|
66
|
+
from hydraflow.core.io import iter_run_dirs
|
67
|
+
|
68
|
+
assert len(list(iter_run_dirs())) == 5
|
69
|
+
|
70
|
+
|
71
|
+
def test_iter_artifacts_dirs():
|
72
|
+
from hydraflow.core.io import iter_artifacts_dirs
|
73
|
+
|
74
|
+
assert len(list(iter_artifacts_dirs())) == 5
|
75
|
+
|
76
|
+
|
77
|
+
def test_iter_artifact_paths():
|
78
|
+
from hydraflow.core.io import iter_artifact_paths
|
79
|
+
|
80
|
+
it = iter_artifact_paths("text.txt")
|
81
|
+
text = sorted("".join(p.read_text() for p in it))
|
82
|
+
assert text == ["1", "2", "3", "4", "5"]
|
@@ -81,3 +81,10 @@ def test_list_run_paths(experiment: Experiment):
|
|
81
81
|
|
82
82
|
dirs = list_run_paths(experiment.name, "artifacts")
|
83
83
|
assert all(d.is_dir() for d in dirs)
|
84
|
+
|
85
|
+
|
86
|
+
@pytest.mark.parametrize("uri", [None, "test_mlflow"])
|
87
|
+
def test_root_dir(experiment, uri):
|
88
|
+
from hydraflow.core.io import get_root_dir
|
89
|
+
|
90
|
+
assert get_root_dir(uri) == Path.cwd() / "test_mlflow"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|