hydraflow 0.8.0__tar.gz → 0.9.1__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.8.0 → hydraflow-0.9.1}/PKG-INFO +18 -19
- {hydraflow-0.8.0 → hydraflow-0.9.1}/README.md +16 -18
- {hydraflow-0.8.0 → hydraflow-0.9.1}/docs/usage/quickstart.md +1 -23
- {hydraflow-0.8.0 → hydraflow-0.9.1}/pyproject.toml +15 -4
- hydraflow-0.9.1/src/hydraflow/__init__.py +37 -0
- hydraflow-0.9.1/src/hydraflow/cli.py +67 -0
- {hydraflow-0.8.0/src/hydraflow → hydraflow-0.9.1/src/hydraflow/core}/context.py +3 -2
- hydraflow-0.8.0/src/hydraflow/utils.py → hydraflow-0.9.1/src/hydraflow/core/io.py +72 -1
- {hydraflow-0.8.0/src/hydraflow → hydraflow-0.9.1/src/hydraflow/core}/main.py +5 -3
- {hydraflow-0.8.0/src/hydraflow → hydraflow-0.9.1/src/hydraflow/core}/mlflow.py +4 -3
- {hydraflow-0.8.0/src/hydraflow → hydraflow-0.9.1/src/hydraflow/entities}/run_collection.py +8 -7
- {hydraflow-0.8.0/src/hydraflow → hydraflow-0.9.1/src/hydraflow/entities}/run_data.py +3 -3
- {hydraflow-0.8.0/src/hydraflow → hydraflow-0.9.1/src/hydraflow/entities}/run_info.py +2 -2
- hydraflow-0.9.1/src/hydraflow/executor/conf.py +23 -0
- hydraflow-0.9.1/src/hydraflow/executor/io.py +34 -0
- hydraflow-0.9.1/src/hydraflow/executor/job.py +152 -0
- hydraflow-0.9.1/src/hydraflow/executor/parser.py +397 -0
- hydraflow-0.9.1/tests/cli/app.py +24 -0
- hydraflow-0.9.1/tests/cli/conftest.py +12 -0
- hydraflow-0.9.1/tests/cli/hydraflow.yaml +13 -0
- hydraflow-0.9.1/tests/cli/test_run.py +33 -0
- hydraflow-0.9.1/tests/cli/test_setup.py +7 -0
- hydraflow-0.9.1/tests/cli/test_show.py +23 -0
- {hydraflow-0.8.0 → hydraflow-0.9.1}/tests/conftest.py +10 -5
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/config/test_config.py +2 -2
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/config/test_params.py +1 -1
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/context/test_chdir.py +6 -3
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/context/test_log_run.py +6 -3
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/context/test_start_run.py +6 -3
- hydraflow-0.9.1/tests/core/io/__init__.py +0 -0
- hydraflow-0.8.0/tests/utils/test_utils.py → hydraflow-0.9.1/tests/core/io/test_hydra_dir.py +10 -12
- hydraflow-0.9.1/tests/core/io/test_iter_dirs.py +82 -0
- {hydraflow-0.8.0/tests/utils → hydraflow-0.9.1/tests/core/io}/test_run.py +4 -4
- hydraflow-0.9.1/tests/core/main/__init__.py +0 -0
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/main/test_default.py +7 -6
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/main/test_force_new_run.py +6 -3
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/main/test_match_overrides.py +7 -4
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/main/test_rerun_finished.py +6 -3
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/main/test_skip_finished.py +8 -6
- hydraflow-0.9.1/tests/core/param/__init__.py +0 -0
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/param/test_param.py +6 -6
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/param/test_params.py +10 -7
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/test_mlflow.py +12 -5
- hydraflow-0.9.1/tests/entities/__init__.py +0 -0
- {hydraflow-0.8.0/tests/run → hydraflow-0.9.1/tests/entities}/test_collection.py +5 -5
- {hydraflow-0.8.0/tests/run → hydraflow-0.9.1/tests/entities}/test_data.py +2 -2
- {hydraflow-0.8.0/tests/run → hydraflow-0.9.1/tests/entities}/test_filter.py +6 -3
- {hydraflow-0.8.0/tests/run → hydraflow-0.9.1/tests/entities}/test_info.py +2 -2
- {hydraflow-0.8.0/tests/run → hydraflow-0.9.1/tests/entities}/test_values.py +5 -2
- hydraflow-0.9.1/tests/executor/__init__.py +0 -0
- hydraflow-0.9.1/tests/executor/conftest.py +30 -0
- hydraflow-0.9.1/tests/executor/echo.py +17 -0
- hydraflow-0.9.1/tests/executor/test_args.py +19 -0
- hydraflow-0.9.1/tests/executor/test_conf.py +34 -0
- hydraflow-0.9.1/tests/executor/test_io.py +18 -0
- hydraflow-0.9.1/tests/executor/test_job.py +127 -0
- hydraflow-0.9.1/tests/executor/test_parser.py +220 -0
- hydraflow-0.8.0/hydraflow.yaml +0 -5
- hydraflow-0.8.0/src/hydraflow/__init__.py +0 -29
- hydraflow-0.8.0/src/hydraflow/cli.py +0 -75
- hydraflow-0.8.0/tests/cli/conftest.py +0 -9
- hydraflow-0.8.0/tests/cli/test_run.py +0 -18
- hydraflow-0.8.0/tests/cli/test_show.py +0 -52
- {hydraflow-0.8.0 → hydraflow-0.9.1}/.devcontainer/devcontainer.json +0 -0
- {hydraflow-0.8.0 → hydraflow-0.9.1}/.devcontainer/postCreate.sh +0 -0
- {hydraflow-0.8.0 → hydraflow-0.9.1}/.devcontainer/starship.toml +0 -0
- {hydraflow-0.8.0 → hydraflow-0.9.1}/.gitattributes +0 -0
- {hydraflow-0.8.0 → hydraflow-0.9.1}/.github/workflows/ci.yaml +0 -0
- {hydraflow-0.8.0 → hydraflow-0.9.1}/.github/workflows/docs.yaml +0 -0
- {hydraflow-0.8.0 → hydraflow-0.9.1}/.gitignore +0 -0
- {hydraflow-0.8.0 → hydraflow-0.9.1}/LICENSE +0 -0
- {hydraflow-0.8.0 → hydraflow-0.9.1}/apps/quickstart.py +0 -0
- {hydraflow-0.8.0 → hydraflow-0.9.1}/docs/index.md +0 -0
- {hydraflow-0.8.0 → hydraflow-0.9.1}/mkdocs.yaml +0 -0
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/src/hydraflow/core}/__init__.py +0 -0
- {hydraflow-0.8.0/src/hydraflow → hydraflow-0.9.1/src/hydraflow/core}/config.py +0 -0
- {hydraflow-0.8.0/src/hydraflow → hydraflow-0.9.1/src/hydraflow/core}/param.py +0 -0
- {hydraflow-0.8.0/tests/cli → hydraflow-0.9.1/src/hydraflow/entities}/__init__.py +0 -0
- {hydraflow-0.8.0/tests/config → hydraflow-0.9.1/src/hydraflow/executor}/__init__.py +0 -0
- {hydraflow-0.8.0 → hydraflow-0.9.1}/src/hydraflow/py.typed +0 -0
- {hydraflow-0.8.0/tests/context → hydraflow-0.9.1/tests}/__init__.py +0 -0
- {hydraflow-0.8.0/tests/main → hydraflow-0.9.1/tests/cli}/__init__.py +0 -0
- {hydraflow-0.8.0 → hydraflow-0.9.1}/tests/cli/test_version.py +0 -0
- {hydraflow-0.8.0/tests/param → hydraflow-0.9.1/tests/core}/__init__.py +0 -0
- {hydraflow-0.8.0/tests/run → hydraflow-0.9.1/tests/core/config}/__init__.py +0 -0
- {hydraflow-0.8.0/tests/utils → hydraflow-0.9.1/tests/core/context}/__init__.py +0 -0
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/context/chdir.py +0 -0
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/context/log_run.py +0 -0
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/context/start_run.py +0 -0
- /hydraflow-0.8.0/tests/utils/utils.py → /hydraflow-0.9.1/tests/core/io/hydra_dir.py +0 -0
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/main/default.py +0 -0
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/main/force_new_run.py +0 -0
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/main/match_overrides.py +0 -0
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/main/rerun_finished.py +0 -0
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/main/skip_finished.py +0 -0
- {hydraflow-0.8.0/tests → hydraflow-0.9.1/tests/core}/param/params.py +0 -0
- {hydraflow-0.8.0/tests/run → hydraflow-0.9.1/tests/entities}/filter.py +0 -0
- {hydraflow-0.8.0/tests/run → hydraflow-0.9.1/tests/entities}/values.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hydraflow
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.9.1
|
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
|
@@ -41,6 +41,7 @@ Requires-Dist: mlflow>=2.15
|
|
41
41
|
Requires-Dist: omegaconf
|
42
42
|
Requires-Dist: rich
|
43
43
|
Requires-Dist: typer
|
44
|
+
Requires-Dist: ulid
|
44
45
|
Description-Content-Type: text/markdown
|
45
46
|
|
46
47
|
# Hydraflow
|
@@ -93,31 +94,29 @@ pip install hydraflow
|
|
93
94
|
Here is a simple example to get you started with Hydraflow:
|
94
95
|
|
95
96
|
```python
|
96
|
-
import
|
97
|
-
|
98
|
-
import mlflow
|
97
|
+
from __future__ import annotations
|
98
|
+
|
99
99
|
from dataclasses import dataclass
|
100
|
-
from hydra.core.config_store import ConfigStore
|
101
100
|
from pathlib import Path
|
101
|
+
from typing import TYPE_CHECKING
|
102
102
|
|
103
|
-
|
104
|
-
class MySQLConfig:
|
105
|
-
host: str = "localhost"
|
106
|
-
port: int = 3306
|
103
|
+
import hydraflow
|
107
104
|
|
108
|
-
|
109
|
-
|
105
|
+
if TYPE_CHECKING:
|
106
|
+
from mlflow.entities import Run
|
107
|
+
|
108
|
+
|
109
|
+
@dataclass
|
110
|
+
class Config:
|
111
|
+
count: int = 1
|
112
|
+
name: str = "a"
|
110
113
|
|
111
|
-
@hydra.main(config_name="config", version_base=None)
|
112
|
-
def my_app(cfg: MySQLConfig) -> None:
|
113
|
-
# Set experiment by Hydra job name.
|
114
|
-
hydraflow.set_experiment()
|
115
114
|
|
116
|
-
|
117
|
-
|
118
|
-
|
115
|
+
@hydraflow.main(Config)
|
116
|
+
def app(run: Run, cfg: Config):
|
117
|
+
"""Your app code here."""
|
119
118
|
|
120
119
|
|
121
120
|
if __name__ == "__main__":
|
122
|
-
|
121
|
+
app()
|
123
122
|
```
|
@@ -48,31 +48,29 @@ pip install hydraflow
|
|
48
48
|
Here is a simple example to get you started with Hydraflow:
|
49
49
|
|
50
50
|
```python
|
51
|
-
import
|
52
|
-
|
53
|
-
import mlflow
|
51
|
+
from __future__ import annotations
|
52
|
+
|
54
53
|
from dataclasses import dataclass
|
55
|
-
from hydra.core.config_store import ConfigStore
|
56
54
|
from pathlib import Path
|
55
|
+
from typing import TYPE_CHECKING
|
57
56
|
|
58
|
-
|
59
|
-
class MySQLConfig:
|
60
|
-
host: str = "localhost"
|
61
|
-
port: int = 3306
|
57
|
+
import hydraflow
|
62
58
|
|
63
|
-
|
64
|
-
|
59
|
+
if TYPE_CHECKING:
|
60
|
+
from mlflow.entities import Run
|
61
|
+
|
62
|
+
|
63
|
+
@dataclass
|
64
|
+
class Config:
|
65
|
+
count: int = 1
|
66
|
+
name: str = "a"
|
65
67
|
|
66
|
-
@hydra.main(config_name="config", version_base=None)
|
67
|
-
def my_app(cfg: MySQLConfig) -> None:
|
68
|
-
# Set experiment by Hydra job name.
|
69
|
-
hydraflow.set_experiment()
|
70
68
|
|
71
|
-
|
72
|
-
|
73
|
-
|
69
|
+
@hydraflow.main(Config)
|
70
|
+
def app(run: Run, cfg: Config):
|
71
|
+
"""Your app code here."""
|
74
72
|
|
75
73
|
|
76
74
|
if __name__ == "__main__":
|
77
|
-
|
75
|
+
app()
|
78
76
|
```
|
@@ -12,16 +12,6 @@ There are two main steps to using Hydraflow:
|
|
12
12
|
--8<-- "apps/quickstart.py"
|
13
13
|
```
|
14
14
|
|
15
|
-
### Set the MLflow experiment
|
16
|
-
|
17
|
-
[`hydraflow.set_experiment`][] sets the MLflow experiment using the Hydra job name.
|
18
|
-
Optionally, it can also set the tracking URI with `uri` argument.
|
19
|
-
For example,
|
20
|
-
|
21
|
-
```python
|
22
|
-
hydraflow.set_experiment(uri="sqlite:///mlruns.db")
|
23
|
-
```
|
24
|
-
|
25
15
|
### Start a new MLflow run
|
26
16
|
|
27
17
|
[`hydraflow.start_run`][] starts a new MLflow run that logs the Hydra configuration.
|
@@ -64,10 +54,8 @@ $ python apps/quickstart.py -m width=400,600 height=100,200,300
|
|
64
54
|
### Run collection
|
65
55
|
|
66
56
|
```pycon exec="1" source="console" session="quickstart"
|
67
|
-
>>> import mlflow
|
68
|
-
>>> mlflow.set_experiment("quickstart")
|
69
57
|
>>> import hydraflow
|
70
|
-
>>> rc = hydraflow.list_runs()
|
58
|
+
>>> rc = hydraflow.list_runs("quickstart")
|
71
59
|
>>> print(rc)
|
72
60
|
```
|
73
61
|
|
@@ -107,16 +95,6 @@ $ python apps/quickstart.py -m width=400,600 height=100,200,300
|
|
107
95
|
>>> print(filtered)
|
108
96
|
```
|
109
97
|
|
110
|
-
```pycon exec="1" source="console" session="quickstart"
|
111
|
-
>>> run = rc.find(height=100)
|
112
|
-
>>> print(run.data.params)
|
113
|
-
```
|
114
|
-
|
115
|
-
```pycon exec="1" source="console" session="quickstart"
|
116
|
-
>>> run = rc.find_last(height=100)
|
117
|
-
>>> print(run.data.params)
|
118
|
-
```
|
119
|
-
|
120
98
|
### Group runs
|
121
99
|
|
122
100
|
```pycon exec="1" source="console" session="quickstart"
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "hydraflow"
|
7
|
-
version = "0.
|
7
|
+
version = "0.9.1"
|
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" }
|
@@ -19,7 +19,14 @@ classifiers = [
|
|
19
19
|
"Programming Language :: Python :: 3.13",
|
20
20
|
]
|
21
21
|
requires-python = ">=3.10"
|
22
|
-
dependencies = [
|
22
|
+
dependencies = [
|
23
|
+
"hydra-core>=1.3",
|
24
|
+
"mlflow>=2.15",
|
25
|
+
"omegaconf",
|
26
|
+
"rich",
|
27
|
+
"typer",
|
28
|
+
"ulid",
|
29
|
+
]
|
23
30
|
|
24
31
|
[project.urls]
|
25
32
|
Documentation = "https://daizutabi.github.io/hydraflow/"
|
@@ -44,6 +51,7 @@ addopts = [
|
|
44
51
|
"--cov=hydraflow",
|
45
52
|
"--cov-report=lcov:lcov.info",
|
46
53
|
"--dist=loadgroup",
|
54
|
+
"--doctest-modules",
|
47
55
|
"-n8",
|
48
56
|
]
|
49
57
|
filterwarnings = [
|
@@ -67,6 +75,7 @@ ignore = [
|
|
67
75
|
"ANN003",
|
68
76
|
"ANN401",
|
69
77
|
"B904",
|
78
|
+
"D104",
|
70
79
|
"D105",
|
71
80
|
"D107",
|
72
81
|
"D203",
|
@@ -79,13 +88,15 @@ ignore = [
|
|
79
88
|
"PLR0913",
|
80
89
|
"PLR1704",
|
81
90
|
"PLR2004",
|
91
|
+
"S603",
|
82
92
|
"SIM102",
|
83
93
|
"SIM108",
|
84
94
|
"TRY003",
|
85
95
|
]
|
86
96
|
|
87
97
|
[tool.ruff.lint.per-file-ignores]
|
88
|
-
"tests/*" = ["A001", "ANN", "ARG", "D", "FBT", "PD", "PLR", "PT", "S", "SLF"]
|
89
98
|
"apps/*.py" = ["D", "G", "INP"]
|
90
|
-
"src/hydraflow/main.py" = ["ANN201", "D401"]
|
91
99
|
"src/hydraflow/cli.py" = ["ANN", "D"]
|
100
|
+
"src/hydraflow/core/main.py" = ["ANN201", "D401"]
|
101
|
+
"src/hydraflow/executor/conf.py" = ["ANN", "D"]
|
102
|
+
"tests/*" = ["A001", "ANN", "ARG", "D", "FBT", "PD", "PLR", "PT", "S", "SLF"]
|
@@ -0,0 +1,37 @@
|
|
1
|
+
"""Integrate Hydra and MLflow to manage and track machine learning experiments."""
|
2
|
+
|
3
|
+
from hydraflow.core.context import chdir_artifact, log_run, start_run
|
4
|
+
from hydraflow.core.io import (
|
5
|
+
get_artifact_dir,
|
6
|
+
get_artifact_path,
|
7
|
+
get_hydra_output_dir,
|
8
|
+
iter_artifact_paths,
|
9
|
+
iter_artifacts_dirs,
|
10
|
+
iter_experiment_dirs,
|
11
|
+
iter_run_dirs,
|
12
|
+
load_config,
|
13
|
+
remove_run,
|
14
|
+
)
|
15
|
+
from hydraflow.core.main import main
|
16
|
+
from hydraflow.core.mlflow import list_run_ids, list_run_paths, list_runs
|
17
|
+
from hydraflow.entities.run_collection import RunCollection
|
18
|
+
|
19
|
+
__all__ = [
|
20
|
+
"RunCollection",
|
21
|
+
"chdir_artifact",
|
22
|
+
"get_artifact_dir",
|
23
|
+
"get_artifact_path",
|
24
|
+
"get_hydra_output_dir",
|
25
|
+
"iter_artifact_paths",
|
26
|
+
"iter_artifacts_dirs",
|
27
|
+
"iter_experiment_dirs",
|
28
|
+
"iter_run_dirs",
|
29
|
+
"list_run_ids",
|
30
|
+
"list_run_paths",
|
31
|
+
"list_runs",
|
32
|
+
"load_config",
|
33
|
+
"log_run",
|
34
|
+
"main",
|
35
|
+
"remove_run",
|
36
|
+
"start_run",
|
37
|
+
]
|
@@ -0,0 +1,67 @@
|
|
1
|
+
"""Hydraflow CLI."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from typing import TYPE_CHECKING, Annotated
|
6
|
+
|
7
|
+
import typer
|
8
|
+
from rich.console import Console
|
9
|
+
from typer import Argument, Option
|
10
|
+
|
11
|
+
from hydraflow.executor.io import load_config
|
12
|
+
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from hydraflow.executor.job import Job
|
15
|
+
|
16
|
+
app = typer.Typer(add_completion=False)
|
17
|
+
console = Console()
|
18
|
+
|
19
|
+
|
20
|
+
def get_job(name: str) -> Job:
|
21
|
+
cfg = load_config()
|
22
|
+
job = cfg.jobs[name]
|
23
|
+
|
24
|
+
if not job.name:
|
25
|
+
job.name = name
|
26
|
+
|
27
|
+
return job
|
28
|
+
|
29
|
+
|
30
|
+
@app.command()
|
31
|
+
def run(
|
32
|
+
name: Annotated[str, Argument(help="Job name.", show_default=False)],
|
33
|
+
) -> None:
|
34
|
+
"""Run a job."""
|
35
|
+
import mlflow
|
36
|
+
|
37
|
+
from hydraflow.executor.job import multirun
|
38
|
+
|
39
|
+
job = get_job(name)
|
40
|
+
mlflow.set_experiment(job.name)
|
41
|
+
multirun(job)
|
42
|
+
|
43
|
+
|
44
|
+
@app.command()
|
45
|
+
def show(
|
46
|
+
name: Annotated[str, Argument(help="Job name.", show_default=False)],
|
47
|
+
) -> None:
|
48
|
+
"""Show a job."""
|
49
|
+
from hydraflow.executor.job import show
|
50
|
+
|
51
|
+
job = get_job(name)
|
52
|
+
show(job)
|
53
|
+
|
54
|
+
|
55
|
+
@app.callback(invoke_without_command=True)
|
56
|
+
def callback(
|
57
|
+
*,
|
58
|
+
version: Annotated[
|
59
|
+
bool,
|
60
|
+
Option("--version", help="Show the version and exit."),
|
61
|
+
] = False,
|
62
|
+
) -> None:
|
63
|
+
if version:
|
64
|
+
import importlib.metadata
|
65
|
+
|
66
|
+
typer.echo(f"hydraflow {importlib.metadata.version('hydraflow')}")
|
67
|
+
raise typer.Exit
|
@@ -12,8 +12,9 @@ import mlflow
|
|
12
12
|
import mlflow.artifacts
|
13
13
|
from hydra.core.hydra_config import HydraConfig
|
14
14
|
|
15
|
-
from hydraflow.
|
16
|
-
|
15
|
+
from hydraflow.core.io import get_artifact_dir
|
16
|
+
|
17
|
+
from .mlflow import log_params, log_text
|
17
18
|
|
18
19
|
if TYPE_CHECKING:
|
19
20
|
from collections.abc import Iterator
|
@@ -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
|
@@ -7,6 +7,7 @@ management.
|
|
7
7
|
|
8
8
|
The main functionality is provided through the `main` decorator, which can be
|
9
9
|
used to wrap experiment entry points. This decorator handles:
|
10
|
+
|
10
11
|
- Configuration management via Hydra
|
11
12
|
- Experiment tracking via MLflow
|
12
13
|
- Run deduplication based on configurations
|
@@ -44,11 +45,12 @@ from mlflow.entities import RunStatus
|
|
44
45
|
from omegaconf import OmegaConf
|
45
46
|
|
46
47
|
import hydraflow
|
47
|
-
from hydraflow.
|
48
|
+
from hydraflow.core.io import file_uri_to_path
|
48
49
|
|
49
50
|
if TYPE_CHECKING:
|
50
51
|
from collections.abc import Callable
|
51
52
|
from pathlib import Path
|
53
|
+
from typing import Any
|
52
54
|
|
53
55
|
from mlflow.entities import Run
|
54
56
|
|
@@ -115,7 +117,7 @@ def main(
|
|
115
117
|
return decorator
|
116
118
|
|
117
119
|
|
118
|
-
def get_run_id(uri: str, config:
|
120
|
+
def get_run_id(uri: str, config: Any, overrides: list[str] | None) -> str | None:
|
119
121
|
"""Try to get the run ID for the given configuration.
|
120
122
|
|
121
123
|
If the run is not found, the function will return None.
|
@@ -137,7 +139,7 @@ def get_run_id(uri: str, config: object, overrides: list[str] | None) -> str | N
|
|
137
139
|
return None
|
138
140
|
|
139
141
|
|
140
|
-
def equals(run_dir: Path, config:
|
142
|
+
def equals(run_dir: Path, config: Any, overrides: list[str] | None) -> bool:
|
141
143
|
"""Check if the run directory matches the given configuration or overrides.
|
142
144
|
|
143
145
|
Args:
|
@@ -13,9 +13,10 @@ import joblib
|
|
13
13
|
import mlflow
|
14
14
|
import mlflow.artifacts
|
15
15
|
|
16
|
-
from hydraflow.
|
17
|
-
from hydraflow.run_collection import RunCollection
|
18
|
-
|
16
|
+
from hydraflow.core.io import file_uri_to_path, get_artifact_dir
|
17
|
+
from hydraflow.entities.run_collection import RunCollection
|
18
|
+
|
19
|
+
from .config import iter_params
|
19
20
|
|
20
21
|
if TYPE_CHECKING:
|
21
22
|
from pathlib import Path
|
@@ -25,12 +25,13 @@ from typing import TYPE_CHECKING, Any, overload
|
|
25
25
|
|
26
26
|
from mlflow.entities import RunStatus
|
27
27
|
|
28
|
-
import hydraflow.param
|
29
|
-
from hydraflow.config import iter_params, select_config, select_overrides
|
30
|
-
from hydraflow.
|
31
|
-
from hydraflow.
|
32
|
-
|
33
|
-
from
|
28
|
+
import hydraflow.core.param
|
29
|
+
from hydraflow.core.config import iter_params, select_config, select_overrides
|
30
|
+
from hydraflow.core.io import load_config
|
31
|
+
from hydraflow.core.param import get_params, get_values
|
32
|
+
|
33
|
+
from .run_data import RunCollectionData
|
34
|
+
from .run_info import RunCollectionInfo
|
34
35
|
|
35
36
|
if TYPE_CHECKING:
|
36
37
|
from collections.abc import Callable, Iterator
|
@@ -478,7 +479,7 @@ def _param_matches(run: Run, key: str, value: Any) -> bool:
|
|
478
479
|
if param == "None":
|
479
480
|
return value is None or value == "None"
|
480
481
|
|
481
|
-
return hydraflow.param.match(param, value)
|
482
|
+
return hydraflow.core.param.match(param, value)
|
482
483
|
|
483
484
|
|
484
485
|
def filter_runs(
|
@@ -6,14 +6,14 @@ from typing import TYPE_CHECKING
|
|
6
6
|
|
7
7
|
from pandas import DataFrame
|
8
8
|
|
9
|
-
from hydraflow.config import iter_params
|
10
|
-
from hydraflow.
|
9
|
+
from hydraflow.core.config import iter_params
|
10
|
+
from hydraflow.core.io import load_config
|
11
11
|
|
12
12
|
if TYPE_CHECKING:
|
13
13
|
from collections.abc import Iterable
|
14
14
|
from typing import Any
|
15
15
|
|
16
|
-
from
|
16
|
+
from .run_collection import RunCollection
|
17
17
|
|
18
18
|
|
19
19
|
class RunCollectionData:
|
@@ -4,12 +4,12 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
from typing import TYPE_CHECKING
|
6
6
|
|
7
|
-
from hydraflow.
|
7
|
+
from hydraflow.core.io import get_artifact_dir
|
8
8
|
|
9
9
|
if TYPE_CHECKING:
|
10
10
|
from pathlib import Path
|
11
11
|
|
12
|
-
from
|
12
|
+
from .run_collection import RunCollection
|
13
13
|
|
14
14
|
|
15
15
|
class RunCollectionInfo:
|
@@ -0,0 +1,23 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass, field
|
4
|
+
|
5
|
+
|
6
|
+
@dataclass
|
7
|
+
class Step:
|
8
|
+
args: str = ""
|
9
|
+
batch: str = ""
|
10
|
+
options: str = ""
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class Job:
|
15
|
+
name: str = ""
|
16
|
+
run: str = ""
|
17
|
+
call: str = ""
|
18
|
+
steps: list[Step] = field(default_factory=list)
|
19
|
+
|
20
|
+
|
21
|
+
@dataclass
|
22
|
+
class HydraflowConf:
|
23
|
+
jobs: dict[str, Job] = field(default_factory=dict)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
"""Hydraflow jobs IO."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
from omegaconf import OmegaConf
|
8
|
+
|
9
|
+
from .conf import HydraflowConf
|
10
|
+
|
11
|
+
|
12
|
+
def find_config_file() -> Path | None:
|
13
|
+
"""Find the hydraflow config file."""
|
14
|
+
if Path("hydraflow.yaml").exists():
|
15
|
+
return Path("hydraflow.yaml")
|
16
|
+
|
17
|
+
if Path("hydraflow.yml").exists():
|
18
|
+
return Path("hydraflow.yml")
|
19
|
+
|
20
|
+
return None
|
21
|
+
|
22
|
+
|
23
|
+
def load_config() -> HydraflowConf:
|
24
|
+
"""Load the hydraflow config."""
|
25
|
+
schema = OmegaConf.structured(HydraflowConf)
|
26
|
+
|
27
|
+
path = find_config_file()
|
28
|
+
|
29
|
+
if path is None:
|
30
|
+
return schema
|
31
|
+
|
32
|
+
cfg = OmegaConf.load(path)
|
33
|
+
|
34
|
+
return OmegaConf.merge(schema, cfg) # type: ignore
|