hydraflow 0.10.1__tar.gz → 0.11.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.10.1 → hydraflow-0.11.0}/.gitignore +1 -1
- {hydraflow-0.10.1 → hydraflow-0.11.0}/PKG-INFO +1 -1
- {hydraflow-0.10.1 → hydraflow-0.11.0}/pyproject.toml +2 -1
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/cli.py +22 -23
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/executor/conf.py +3 -2
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/executor/io.py +15 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/executor/job.py +29 -16
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/cli/app.py +7 -1
- hydraflow-0.11.0/tests/cli/hydraflow.yaml +22 -0
- hydraflow-0.11.0/tests/cli/test_run.py +63 -0
- hydraflow-0.11.0/tests/cli/test_show.py +18 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/echo.py +1 -1
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/test_conf.py +2 -2
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/test_job.py +26 -41
- hydraflow-0.10.1/tests/cli/hydraflow.yaml +0 -13
- hydraflow-0.10.1/tests/cli/test_run.py +0 -33
- hydraflow-0.10.1/tests/cli/test_show.py +0 -23
- {hydraflow-0.10.1 → hydraflow-0.11.0}/.cursorrules +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/.devcontainer/devcontainer.json +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/.devcontainer/postCreate.sh +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/.devcontainer/starship.toml +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/.gitattributes +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/.github/workflows/ci.yaml +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/.github/workflows/docs.yaml +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/LICENSE +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/README.md +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/apps/quickstart.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/docs/index.md +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/docs/usage/quickstart.md +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/mkdocs.yaml +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/__init__.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/core/__init__.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/core/config.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/core/context.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/core/io.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/core/main.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/core/mlflow.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/core/param.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/entities/__init__.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/entities/run_collection.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/entities/run_data.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/entities/run_info.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/executor/__init__.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/executor/parser.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/py.typed +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/__init__.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/cli/__init__.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/cli/conftest.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/cli/test_setup.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/cli/test_version.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/conftest.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/__init__.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/config/__init__.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/config/test_config.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/config/test_params.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/context/__init__.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/context/chdir.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/context/log_run.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/context/start_run.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/context/test_chdir.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/context/test_log_run.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/context/test_start_run.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/io/__init__.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/io/hydra_dir.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/io/test_hydra_dir.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/io/test_iter_dirs.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/io/test_run.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/__init__.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/default.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/force_new_run.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/match_overrides.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/rerun_finished.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/skip_finished.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/test_default.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/test_force_new_run.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/test_match_overrides.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/test_rerun_finished.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/test_skip_finished.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/param/__init__.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/param/params.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/param/test_param.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/param/test_params.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/test_mlflow.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/__init__.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/filter.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/test_collection.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/test_data.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/test_filter.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/test_info.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/test_values.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/values.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/__init__.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/conftest.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/test_args.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/test_io.py +0 -0
- {hydraflow-0.10.1 → hydraflow-0.11.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.11.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
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "hydraflow"
|
7
|
-
version = "0.
|
7
|
+
version = "0.11.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" }
|
@@ -38,6 +38,7 @@ hydraflow = "hydraflow.cli:app"
|
|
38
38
|
|
39
39
|
[dependency-groups]
|
40
40
|
dev = [
|
41
|
+
"hydra-joblib-launcher",
|
41
42
|
"pytest-clarity",
|
42
43
|
"pytest-cov",
|
43
44
|
"pytest-order",
|
@@ -2,54 +2,53 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from typing import
|
5
|
+
from typing import Annotated
|
6
6
|
|
7
7
|
import typer
|
8
8
|
from rich.console import Console
|
9
9
|
from typer import Argument, Option
|
10
10
|
|
11
|
-
from hydraflow.executor.io import load_config
|
12
|
-
|
13
|
-
if TYPE_CHECKING:
|
14
|
-
from hydraflow.executor.job import Job
|
11
|
+
from hydraflow.executor.io import get_job, load_config
|
15
12
|
|
16
13
|
app = typer.Typer(add_completion=False)
|
17
14
|
console = Console()
|
18
15
|
|
19
16
|
|
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
17
|
@app.command()
|
31
18
|
def run(
|
32
19
|
name: Annotated[str, Argument(help="Job name.", show_default=False)],
|
20
|
+
*,
|
21
|
+
dry_run: Annotated[
|
22
|
+
bool,
|
23
|
+
Option("--dry-run", help="Perform a dry run"),
|
24
|
+
] = False,
|
33
25
|
) -> None:
|
34
26
|
"""Run a job."""
|
35
27
|
import mlflow
|
36
28
|
|
37
|
-
from hydraflow.executor.job import multirun
|
29
|
+
from hydraflow.executor.job import multirun, to_text
|
38
30
|
|
39
31
|
job = get_job(name)
|
40
|
-
|
41
|
-
|
32
|
+
if dry_run:
|
33
|
+
typer.echo(to_text(job))
|
34
|
+
else:
|
35
|
+
mlflow.set_experiment(job.name)
|
36
|
+
multirun(job)
|
42
37
|
|
43
38
|
|
44
39
|
@app.command()
|
45
40
|
def show(
|
46
|
-
name: Annotated[str, Argument(help="Job name.", show_default=False)],
|
41
|
+
name: Annotated[str, Argument(help="Job name.", show_default=False)] = "",
|
47
42
|
) -> None:
|
48
|
-
"""Show
|
49
|
-
from
|
43
|
+
"""Show the hydraflow config."""
|
44
|
+
from omegaconf import OmegaConf
|
50
45
|
|
51
|
-
|
52
|
-
|
46
|
+
if name:
|
47
|
+
cfg = get_job(name)
|
48
|
+
else:
|
49
|
+
cfg = load_config()
|
50
|
+
|
51
|
+
typer.echo(OmegaConf.to_yaml(cfg))
|
53
52
|
|
54
53
|
|
55
54
|
@app.callback(invoke_without_command=True)
|
@@ -5,9 +5,9 @@ from dataclasses import dataclass, field
|
|
5
5
|
|
6
6
|
@dataclass
|
7
7
|
class Step:
|
8
|
-
args: str = ""
|
9
8
|
batch: str = ""
|
10
|
-
|
9
|
+
args: str = ""
|
10
|
+
configs: str = ""
|
11
11
|
|
12
12
|
|
13
13
|
@dataclass
|
@@ -15,6 +15,7 @@ class Job:
|
|
15
15
|
name: str = ""
|
16
16
|
run: str = ""
|
17
17
|
call: str = ""
|
18
|
+
configs: str = ""
|
18
19
|
steps: list[Step] = field(default_factory=list)
|
19
20
|
|
20
21
|
|
@@ -3,11 +3,15 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
from pathlib import Path
|
6
|
+
from typing import TYPE_CHECKING
|
6
7
|
|
7
8
|
from omegaconf import OmegaConf
|
8
9
|
|
9
10
|
from .conf import HydraflowConf
|
10
11
|
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from .job import Job
|
14
|
+
|
11
15
|
|
12
16
|
def find_config_file() -> Path | None:
|
13
17
|
"""Find the hydraflow config file."""
|
@@ -32,3 +36,14 @@ def load_config() -> HydraflowConf:
|
|
32
36
|
cfg = OmegaConf.load(path)
|
33
37
|
|
34
38
|
return OmegaConf.merge(schema, cfg) # type: ignore
|
39
|
+
|
40
|
+
|
41
|
+
def get_job(name: str) -> Job:
|
42
|
+
"""Get a job from the config."""
|
43
|
+
cfg = load_config()
|
44
|
+
job = cfg.jobs[name]
|
45
|
+
|
46
|
+
if not job.name:
|
47
|
+
job.name = name
|
48
|
+
|
49
|
+
return job
|
@@ -12,7 +12,7 @@ The module supports two execution modes:
|
|
12
12
|
2. Python function calls
|
13
13
|
|
14
14
|
Each job can consist of multiple steps, and each step can have its own
|
15
|
-
arguments and
|
15
|
+
arguments and configurations that will be expanded into multiple runs.
|
16
16
|
"""
|
17
17
|
|
18
18
|
from __future__ import annotations
|
@@ -20,6 +20,7 @@ from __future__ import annotations
|
|
20
20
|
import importlib
|
21
21
|
import shlex
|
22
22
|
import subprocess
|
23
|
+
import sys
|
23
24
|
from subprocess import CalledProcessError
|
24
25
|
from typing import TYPE_CHECKING
|
25
26
|
|
@@ -30,27 +31,27 @@ from .parser import collect, expand
|
|
30
31
|
if TYPE_CHECKING:
|
31
32
|
from collections.abc import Iterator
|
32
33
|
|
33
|
-
from .conf import Job
|
34
|
+
from .conf import Job
|
34
35
|
|
35
36
|
|
36
|
-
def iter_args(
|
37
|
+
def iter_args(batch: str, args: str) -> Iterator[list[str]]:
|
37
38
|
"""Iterate over combinations generated from parsed arguments.
|
38
39
|
|
39
40
|
Generate all possible combinations of arguments by parsing and
|
40
41
|
expanding each one, yielding them as an iterator.
|
41
42
|
|
42
43
|
Args:
|
43
|
-
|
44
|
+
batch (str): The batch to parse.
|
45
|
+
args (str): The arguments to parse.
|
44
46
|
|
45
47
|
Yields:
|
46
48
|
list[str]: a list of the parsed argument combinations.
|
47
49
|
|
48
50
|
"""
|
49
|
-
|
50
|
-
options = [o for o in step.options.split(" ") if o]
|
51
|
+
args_ = collect(args)
|
51
52
|
|
52
|
-
for
|
53
|
-
yield [*
|
53
|
+
for batch_ in expand(batch):
|
54
|
+
yield [*batch_, *args_]
|
54
55
|
|
55
56
|
|
56
57
|
def iter_batches(job: Job) -> Iterator[list[str]]:
|
@@ -68,11 +69,14 @@ def iter_batches(job: Job) -> Iterator[list[str]]:
|
|
68
69
|
|
69
70
|
"""
|
70
71
|
job_name = f"hydra.job.name={job.name}"
|
72
|
+
job_configs = shlex.split(job.configs)
|
71
73
|
|
72
74
|
for step in job.steps:
|
73
|
-
|
75
|
+
configs = shlex.split(step.configs) or job_configs
|
76
|
+
|
77
|
+
for args in iter_args(step.batch, step.args):
|
74
78
|
sweep_dir = f"hydra.sweep.dir=multirun/{ulid.ULID()}"
|
75
|
-
yield ["--multirun",
|
79
|
+
yield ["--multirun", *args, job_name, sweep_dir, *configs]
|
76
80
|
|
77
81
|
|
78
82
|
def multirun(job: Job) -> None:
|
@@ -99,6 +103,8 @@ def multirun(job: Job) -> None:
|
|
99
103
|
|
100
104
|
if job.run:
|
101
105
|
base_cmds = shlex.split(job.run)
|
106
|
+
if base_cmds[0] == "python" and sys.platform == "win32":
|
107
|
+
base_cmds[0] = sys.executable
|
102
108
|
|
103
109
|
for args in it:
|
104
110
|
cmds = [*base_cmds, *args]
|
@@ -132,24 +138,31 @@ def multirun(job: Job) -> None:
|
|
132
138
|
raise RuntimeError(msg) from e
|
133
139
|
|
134
140
|
|
135
|
-
def
|
136
|
-
"""
|
141
|
+
def to_text(job: Job) -> str:
|
142
|
+
"""Convert the job configuration to a string.
|
137
143
|
|
138
|
-
This function
|
144
|
+
This function returns the job configuration for a given job.
|
139
145
|
|
140
146
|
Args:
|
141
147
|
job (Job): The job configuration to show.
|
142
148
|
|
149
|
+
Returns:
|
150
|
+
str: The job configuration.
|
151
|
+
|
143
152
|
"""
|
153
|
+
text = ""
|
154
|
+
|
144
155
|
it = iter_batches(job)
|
145
156
|
|
146
157
|
if job.run:
|
147
158
|
base_cmds = shlex.split(job.run)
|
148
159
|
for args in it:
|
149
160
|
cmds = " ".join([*base_cmds, *args])
|
150
|
-
|
161
|
+
text += f"{cmds}\n"
|
151
162
|
|
152
163
|
elif job.call:
|
153
|
-
|
164
|
+
text = f"call: {job.call}\n"
|
154
165
|
for args in it:
|
155
|
-
|
166
|
+
text += f"args: {args}\n"
|
167
|
+
|
168
|
+
return text.rstrip()
|
@@ -1,5 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import logging
|
4
|
+
import time
|
3
5
|
from dataclasses import dataclass
|
4
6
|
from typing import TYPE_CHECKING
|
5
7
|
|
@@ -8,6 +10,8 @@ import hydraflow
|
|
8
10
|
if TYPE_CHECKING:
|
9
11
|
from mlflow.entities import Run
|
10
12
|
|
13
|
+
log = logging.getLogger(__name__)
|
14
|
+
|
11
15
|
|
12
16
|
@dataclass
|
13
17
|
class Config:
|
@@ -17,7 +21,9 @@ class Config:
|
|
17
21
|
|
18
22
|
@hydraflow.main(Config)
|
19
23
|
def app(run: Run, cfg: Config):
|
20
|
-
|
24
|
+
log.info("start")
|
25
|
+
time.sleep(0.2)
|
26
|
+
log.info("end")
|
21
27
|
|
22
28
|
|
23
29
|
if __name__ == "__main__":
|
@@ -0,0 +1,22 @@
|
|
1
|
+
jobs:
|
2
|
+
args:
|
3
|
+
run: python app.py
|
4
|
+
steps:
|
5
|
+
- args: count=1:3 name=a,b
|
6
|
+
- args: count=4:6 name=c,d
|
7
|
+
batch:
|
8
|
+
run: python app.py
|
9
|
+
steps:
|
10
|
+
- batch: name=a,b
|
11
|
+
args: count=1,2
|
12
|
+
- batch: name=c,d|e,f
|
13
|
+
args: count=100
|
14
|
+
parallel:
|
15
|
+
run: python app.py
|
16
|
+
configs: hydra/launcher=joblib hydra.launcher.n_jobs=2
|
17
|
+
steps:
|
18
|
+
- batch: name=a
|
19
|
+
args: count=1:4
|
20
|
+
- batch: name=b
|
21
|
+
args: count=11:14
|
22
|
+
configs: hydra/launcher=joblib hydra.launcher.n_jobs=4
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import pytest
|
2
|
+
from typer.testing import CliRunner
|
3
|
+
|
4
|
+
import hydraflow
|
5
|
+
from hydraflow.cli import app
|
6
|
+
|
7
|
+
runner = CliRunner()
|
8
|
+
|
9
|
+
|
10
|
+
def test_run_args_dry_run():
|
11
|
+
result = runner.invoke(app, ["run", "args", "--dry-run"])
|
12
|
+
assert result.exit_code == 0
|
13
|
+
out = result.stdout
|
14
|
+
assert "hydra.job.name=args" in out
|
15
|
+
assert "count=1,2,3 name=a,b" in out
|
16
|
+
assert "count=4,5,6 name=c,d" in out
|
17
|
+
|
18
|
+
|
19
|
+
def test_run_batch_dry_run():
|
20
|
+
result = runner.invoke(app, ["run", "batch", "--dry-run"])
|
21
|
+
assert result.exit_code == 0
|
22
|
+
out = result.stdout
|
23
|
+
assert "name=a count=1,2" in out
|
24
|
+
assert "name=b count=1,2" in out
|
25
|
+
assert "name=c,d count=100" in out
|
26
|
+
assert "name=e,f count=100" in out
|
27
|
+
assert "hydra.job.name=batch" in out
|
28
|
+
|
29
|
+
|
30
|
+
def test_run_parallel_dry_run():
|
31
|
+
result = runner.invoke(app, ["run", "parallel", "--dry-run"])
|
32
|
+
assert result.exit_code == 0
|
33
|
+
out = result.stdout
|
34
|
+
lines = out.splitlines()
|
35
|
+
assert len(lines) == 2
|
36
|
+
assert "count=1,2,3,4" in lines[0]
|
37
|
+
assert "hydra.launcher.n_jobs=2" in lines[0]
|
38
|
+
assert "count=11,12,13,14" in lines[1]
|
39
|
+
assert "hydra.launcher.n_jobs=4" in lines[1]
|
40
|
+
|
41
|
+
|
42
|
+
@pytest.mark.xdist_group(name="group1")
|
43
|
+
def test_run_args():
|
44
|
+
result = runner.invoke(app, ["run", "args"])
|
45
|
+
assert result.exit_code == 0
|
46
|
+
run_ids = hydraflow.list_run_ids("args")
|
47
|
+
assert len(run_ids) == 12
|
48
|
+
|
49
|
+
|
50
|
+
@pytest.mark.xdist_group(name="group2")
|
51
|
+
def test_run_batch():
|
52
|
+
result = runner.invoke(app, ["run", "batch"])
|
53
|
+
assert result.exit_code == 0
|
54
|
+
run_ids = hydraflow.list_run_ids("batch")
|
55
|
+
assert len(run_ids) == 8
|
56
|
+
|
57
|
+
|
58
|
+
@pytest.mark.xdist_group(name="group3")
|
59
|
+
def test_run_parallel():
|
60
|
+
result = runner.invoke(app, ["run", "parallel"])
|
61
|
+
assert result.exit_code == 0
|
62
|
+
run_ids = hydraflow.list_run_ids("parallel")
|
63
|
+
assert len(run_ids) == 8
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from typer.testing import CliRunner
|
2
|
+
|
3
|
+
from hydraflow.cli import app
|
4
|
+
|
5
|
+
runner = CliRunner()
|
6
|
+
|
7
|
+
|
8
|
+
def test_show():
|
9
|
+
result = runner.invoke(app, ["show"])
|
10
|
+
assert result.exit_code == 0
|
11
|
+
assert "jobs:\n" in result.stdout
|
12
|
+
assert " args:\n" in result.stdout
|
13
|
+
|
14
|
+
|
15
|
+
def test_show_job():
|
16
|
+
result = runner.invoke(app, ["show", "args"])
|
17
|
+
assert result.exit_code == 0
|
18
|
+
assert "name: args\n" in result.stdout
|
@@ -30,5 +30,5 @@ def test_job(config):
|
|
30
30
|
|
31
31
|
|
32
32
|
def test_step(config):
|
33
|
-
cfg = config("jobs:\n a:\n steps:\n -
|
34
|
-
assert cfg.jobs["a"].steps[0].
|
33
|
+
cfg = config("jobs:\n a:\n steps:\n - configs: --opt1 --opt2\n")
|
34
|
+
assert cfg.jobs["a"].steps[0].configs == "--opt1 --opt2"
|
@@ -9,36 +9,25 @@ from hydraflow.executor.conf import Job, Step
|
|
9
9
|
def test_iter_args():
|
10
10
|
from hydraflow.executor.job import iter_args
|
11
11
|
|
12
|
-
|
13
|
-
it =
|
14
|
-
assert next(it) == ["
|
15
|
-
assert next(it) == ["
|
16
|
-
assert next(it) == ["
|
17
|
-
assert next(it) == ["a=1,2,3", "b=4", "c=6"]
|
12
|
+
it = iter_args("b=3,4 c=5,6", "a=1:3")
|
13
|
+
assert next(it) == ["b=3", "c=5", "a=1,2,3"]
|
14
|
+
assert next(it) == ["b=3", "c=6", "a=1,2,3"]
|
15
|
+
assert next(it) == ["b=4", "c=5", "a=1,2,3"]
|
16
|
+
assert next(it) == ["b=4", "c=6", "a=1,2,3"]
|
18
17
|
|
19
18
|
|
20
19
|
def test_iter_args_pipe():
|
21
20
|
from hydraflow.executor.job import iter_args
|
22
21
|
|
23
|
-
|
24
|
-
it =
|
25
|
-
assert next(it) == ["
|
26
|
-
assert next(it) == ["a=1,2,3", "c=5,6,7"]
|
27
|
-
|
28
|
-
|
29
|
-
def test_iter_args_with_options():
|
30
|
-
from hydraflow.executor.job import iter_args
|
31
|
-
|
32
|
-
step = Step(args="a=1:3", batch="b=3,4", options="--opt1 --opt2")
|
33
|
-
it = iter_args(step)
|
34
|
-
assert next(it) == ["--opt1", "--opt2", "a=1,2,3", "b=3"]
|
35
|
-
assert next(it) == ["--opt1", "--opt2", "a=1,2,3", "b=4"]
|
22
|
+
it = iter_args("b=3,4|c=5:7", "a=1:3")
|
23
|
+
assert next(it) == ["b=3,4", "a=1,2,3"]
|
24
|
+
assert next(it) == ["c=5,6,7", "a=1,2,3"]
|
36
25
|
|
37
26
|
|
38
27
|
@pytest.fixture
|
39
28
|
def job():
|
40
|
-
s1 = Step(
|
41
|
-
s2 = Step(
|
29
|
+
s1 = Step(batch="b=5,6", args="a=1:2")
|
30
|
+
s2 = Step(batch="c=7,8", args="a=3:4")
|
42
31
|
return Job(name="test", steps=[s1, s2])
|
43
32
|
|
44
33
|
|
@@ -50,17 +39,17 @@ def batches(job: Job):
|
|
50
39
|
|
51
40
|
|
52
41
|
def test_sweep_dir(batches):
|
53
|
-
assert all(x[1].startswith("hydra.sweep.dir=multirun/") for x in batches)
|
54
|
-
assert all(len(x[1].split("/")[-1]) == 26 for x in batches)
|
42
|
+
assert all(x[-1].startswith("hydra.sweep.dir=multirun/") for x in batches)
|
43
|
+
assert all(len(x[-1].split("/")[-1]) == 26 for x in batches)
|
55
44
|
|
56
45
|
|
57
46
|
def test_job_name(batches):
|
58
|
-
assert all(x[2].startswith("hydra.job.name=test") for x in batches)
|
47
|
+
assert all(x[-2].startswith("hydra.job.name=test") for x in batches)
|
59
48
|
|
60
49
|
|
61
50
|
@pytest.mark.parametrize(("i", "x"), [(0, "b=5"), (1, "b=6"), (2, "c=7"), (3, "c=8")])
|
62
51
|
def test_batch_args(batches, i, x):
|
63
|
-
assert batches[i][
|
52
|
+
assert batches[i][1] == x
|
64
53
|
|
65
54
|
|
66
55
|
@pytest.mark.parametrize(
|
@@ -68,28 +57,24 @@ def test_batch_args(batches, i, x):
|
|
68
57
|
[(0, "a=1,2"), (1, "a=1,2"), (2, "a=3,4"), (3, "a=3,4")],
|
69
58
|
)
|
70
59
|
def test_sweep_args(batches, i, x):
|
71
|
-
assert batches[i][-
|
60
|
+
assert batches[i][-3] == x
|
72
61
|
|
73
62
|
|
74
|
-
@pytest.mark.skipif(
|
75
|
-
sys.platform == "win32",
|
76
|
-
reason="Windows does not support this test",
|
77
|
-
)
|
78
63
|
def test_multirun_run(job: Job, tmp_path: Path):
|
79
64
|
from hydraflow.executor.job import multirun
|
80
65
|
|
81
66
|
path = tmp_path / "output.txt"
|
82
67
|
file = Path(__file__).parent / "echo.py"
|
83
68
|
|
84
|
-
job.run = f"
|
69
|
+
job.run = f"python {file.as_posix()} {path.as_posix()}"
|
85
70
|
multirun(job)
|
86
|
-
assert path.read_text() == "a=1,2 b=
|
71
|
+
assert path.read_text() == "b=5 a=1,2 b=6 a=1,2 c=7 a=3,4 c=8 a=3,4"
|
87
72
|
|
88
73
|
|
89
74
|
def test_multirun_run_error(job: Job):
|
90
75
|
from hydraflow.executor.job import multirun
|
91
76
|
|
92
|
-
job.run = "false"
|
77
|
+
job.run = "cmd /c exit 1" if sys.platform == "win32" else "false"
|
93
78
|
with pytest.raises(RuntimeError):
|
94
79
|
multirun(job)
|
95
80
|
|
@@ -100,8 +85,8 @@ def test_multirun_call(job: Job, capsys: pytest.CaptureFixture):
|
|
100
85
|
job.call = "typer.echo"
|
101
86
|
multirun(job)
|
102
87
|
out, _ = capsys.readouterr()
|
103
|
-
assert "'
|
104
|
-
assert "'
|
88
|
+
assert "'b=5', 'a=1,2'" in out
|
89
|
+
assert "'c=8', 'a=3,4'" in out
|
105
90
|
|
106
91
|
|
107
92
|
def test_multirun_call_args(job: Job, capsys: pytest.CaptureFixture):
|
@@ -137,11 +122,11 @@ def test_multirun_call_not_found(job: Job):
|
|
137
122
|
multirun(job)
|
138
123
|
|
139
124
|
|
140
|
-
def
|
141
|
-
from hydraflow.executor.job import
|
125
|
+
def test_to_text(job: Job):
|
126
|
+
from hydraflow.executor.job import to_text
|
142
127
|
|
143
128
|
job.call = "typer.echo"
|
144
|
-
|
145
|
-
|
146
|
-
assert "
|
147
|
-
assert "'
|
129
|
+
text = to_text(job)
|
130
|
+
assert "call: typer.echo\n" in text
|
131
|
+
assert "'b=5', 'a=1,2', 'hydra.job.name=test'" in text
|
132
|
+
assert "'c=8', 'a=3,4', 'hydra.job.name=test'" in text
|
@@ -1,33 +0,0 @@
|
|
1
|
-
import sys
|
2
|
-
|
3
|
-
import pytest
|
4
|
-
from typer.testing import CliRunner
|
5
|
-
|
6
|
-
import hydraflow
|
7
|
-
from hydraflow.cli import app
|
8
|
-
|
9
|
-
pytestmark = pytest.mark.xdist_group(name="group1")
|
10
|
-
|
11
|
-
runner = CliRunner()
|
12
|
-
|
13
|
-
|
14
|
-
@pytest.mark.skipif(
|
15
|
-
sys.platform == "win32",
|
16
|
-
reason="Windows does not support this test",
|
17
|
-
)
|
18
|
-
def test_run_args():
|
19
|
-
result = runner.invoke(app, ["run", "args"])
|
20
|
-
assert result.exit_code == 0
|
21
|
-
run_ids = hydraflow.list_run_ids("args")
|
22
|
-
assert len(run_ids) == 12
|
23
|
-
|
24
|
-
|
25
|
-
@pytest.mark.skipif(
|
26
|
-
sys.platform == "win32",
|
27
|
-
reason="Windows does not support this test",
|
28
|
-
)
|
29
|
-
def test_run_batch():
|
30
|
-
result = runner.invoke(app, ["run", "batch"])
|
31
|
-
assert result.exit_code == 0
|
32
|
-
run_ids = hydraflow.list_run_ids("batch")
|
33
|
-
assert len(run_ids) == 8
|
@@ -1,23 +0,0 @@
|
|
1
|
-
from typer.testing import CliRunner
|
2
|
-
|
3
|
-
from hydraflow.cli import app
|
4
|
-
|
5
|
-
runner = CliRunner()
|
6
|
-
|
7
|
-
|
8
|
-
def test_show_args():
|
9
|
-
result = runner.invoke(app, ["show", "args"])
|
10
|
-
assert result.exit_code == 0
|
11
|
-
assert "hydra.job.name=args" in result.stdout
|
12
|
-
assert "count=1,2,3 name=a,b" in result.stdout
|
13
|
-
assert "count=4,5,6 name=c,d" in result.stdout
|
14
|
-
|
15
|
-
|
16
|
-
def test_show_batch():
|
17
|
-
result = runner.invoke(app, ["show", "batch"])
|
18
|
-
assert result.exit_code == 0
|
19
|
-
assert "hydra.job.name=batch" in result.stdout
|
20
|
-
assert "count=1,2 name=a" in result.stdout
|
21
|
-
assert "count=1,2 name=b" in result.stdout
|
22
|
-
assert "count=100 name=c,d" in result.stdout
|
23
|
-
assert "count=100 name=e,f" in result.stdout
|
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
|