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.
Files changed (96) hide show
  1. {hydraflow-0.10.1 → hydraflow-0.11.0}/.gitignore +1 -1
  2. {hydraflow-0.10.1 → hydraflow-0.11.0}/PKG-INFO +1 -1
  3. {hydraflow-0.10.1 → hydraflow-0.11.0}/pyproject.toml +2 -1
  4. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/cli.py +22 -23
  5. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/executor/conf.py +3 -2
  6. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/executor/io.py +15 -0
  7. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/executor/job.py +29 -16
  8. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/cli/app.py +7 -1
  9. hydraflow-0.11.0/tests/cli/hydraflow.yaml +22 -0
  10. hydraflow-0.11.0/tests/cli/test_run.py +63 -0
  11. hydraflow-0.11.0/tests/cli/test_show.py +18 -0
  12. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/echo.py +1 -1
  13. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/test_conf.py +2 -2
  14. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/test_job.py +26 -41
  15. hydraflow-0.10.1/tests/cli/hydraflow.yaml +0 -13
  16. hydraflow-0.10.1/tests/cli/test_run.py +0 -33
  17. hydraflow-0.10.1/tests/cli/test_show.py +0 -23
  18. {hydraflow-0.10.1 → hydraflow-0.11.0}/.cursorrules +0 -0
  19. {hydraflow-0.10.1 → hydraflow-0.11.0}/.devcontainer/devcontainer.json +0 -0
  20. {hydraflow-0.10.1 → hydraflow-0.11.0}/.devcontainer/postCreate.sh +0 -0
  21. {hydraflow-0.10.1 → hydraflow-0.11.0}/.devcontainer/starship.toml +0 -0
  22. {hydraflow-0.10.1 → hydraflow-0.11.0}/.gitattributes +0 -0
  23. {hydraflow-0.10.1 → hydraflow-0.11.0}/.github/workflows/ci.yaml +0 -0
  24. {hydraflow-0.10.1 → hydraflow-0.11.0}/.github/workflows/docs.yaml +0 -0
  25. {hydraflow-0.10.1 → hydraflow-0.11.0}/LICENSE +0 -0
  26. {hydraflow-0.10.1 → hydraflow-0.11.0}/README.md +0 -0
  27. {hydraflow-0.10.1 → hydraflow-0.11.0}/apps/quickstart.py +0 -0
  28. {hydraflow-0.10.1 → hydraflow-0.11.0}/docs/index.md +0 -0
  29. {hydraflow-0.10.1 → hydraflow-0.11.0}/docs/usage/quickstart.md +0 -0
  30. {hydraflow-0.10.1 → hydraflow-0.11.0}/mkdocs.yaml +0 -0
  31. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/__init__.py +0 -0
  32. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/core/__init__.py +0 -0
  33. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/core/config.py +0 -0
  34. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/core/context.py +0 -0
  35. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/core/io.py +0 -0
  36. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/core/main.py +0 -0
  37. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/core/mlflow.py +0 -0
  38. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/core/param.py +0 -0
  39. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/entities/__init__.py +0 -0
  40. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/entities/run_collection.py +0 -0
  41. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/entities/run_data.py +0 -0
  42. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/entities/run_info.py +0 -0
  43. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/executor/__init__.py +0 -0
  44. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/executor/parser.py +0 -0
  45. {hydraflow-0.10.1 → hydraflow-0.11.0}/src/hydraflow/py.typed +0 -0
  46. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/__init__.py +0 -0
  47. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/cli/__init__.py +0 -0
  48. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/cli/conftest.py +0 -0
  49. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/cli/test_setup.py +0 -0
  50. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/cli/test_version.py +0 -0
  51. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/conftest.py +0 -0
  52. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/__init__.py +0 -0
  53. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/config/__init__.py +0 -0
  54. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/config/test_config.py +0 -0
  55. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/config/test_params.py +0 -0
  56. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/context/__init__.py +0 -0
  57. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/context/chdir.py +0 -0
  58. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/context/log_run.py +0 -0
  59. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/context/start_run.py +0 -0
  60. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/context/test_chdir.py +0 -0
  61. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/context/test_log_run.py +0 -0
  62. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/context/test_start_run.py +0 -0
  63. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/io/__init__.py +0 -0
  64. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/io/hydra_dir.py +0 -0
  65. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/io/test_hydra_dir.py +0 -0
  66. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/io/test_iter_dirs.py +0 -0
  67. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/io/test_run.py +0 -0
  68. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/__init__.py +0 -0
  69. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/default.py +0 -0
  70. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/force_new_run.py +0 -0
  71. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/match_overrides.py +0 -0
  72. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/rerun_finished.py +0 -0
  73. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/skip_finished.py +0 -0
  74. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/test_default.py +0 -0
  75. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/test_force_new_run.py +0 -0
  76. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/test_match_overrides.py +0 -0
  77. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/test_rerun_finished.py +0 -0
  78. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/main/test_skip_finished.py +0 -0
  79. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/param/__init__.py +0 -0
  80. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/param/params.py +0 -0
  81. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/param/test_param.py +0 -0
  82. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/param/test_params.py +0 -0
  83. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/core/test_mlflow.py +0 -0
  84. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/__init__.py +0 -0
  85. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/filter.py +0 -0
  86. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/test_collection.py +0 -0
  87. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/test_data.py +0 -0
  88. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/test_filter.py +0 -0
  89. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/test_info.py +0 -0
  90. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/test_values.py +0 -0
  91. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/entities/values.py +0 -0
  92. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/__init__.py +0 -0
  93. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/conftest.py +0 -0
  94. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/test_args.py +0 -0
  95. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/test_io.py +0 -0
  96. {hydraflow-0.10.1 → hydraflow-0.11.0}/tests/executor/test_parser.py +0 -0
@@ -1,5 +1,5 @@
1
1
  *.db
2
- .coverage
2
+ .coverage*
3
3
  .env
4
4
  .venv/
5
5
  __pycache__/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hydraflow
3
- Version: 0.10.1
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.10.1"
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 TYPE_CHECKING, Annotated
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
- mlflow.set_experiment(job.name)
41
- multirun(job)
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 a job."""
49
- from hydraflow.executor.job import show
43
+ """Show the hydraflow config."""
44
+ from omegaconf import OmegaConf
50
45
 
51
- job = get_job(name)
52
- show(job)
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
- options: str = ""
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 options that will be expanded into multiple runs.
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, Step
34
+ from .conf import Job
34
35
 
35
36
 
36
- def iter_args(step: Step) -> Iterator[list[str]]:
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
- step (Step): The step to parse.
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
- args = collect(step.args)
50
- options = [o for o in step.options.split(" ") if o]
51
+ args_ = collect(args)
51
52
 
52
- for batch in expand(step.batch):
53
- yield [*options, *sorted([*batch, *args])]
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
- for args in iter_args(step):
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", sweep_dir, job_name, *args]
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 show(job: Job) -> None:
136
- """Show the job configuration.
141
+ def to_text(job: Job) -> str:
142
+ """Convert the job configuration to a string.
137
143
 
138
- This function shows the job configuration for a given job.
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
- print(cmds) # noqa: T201
161
+ text += f"{cmds}\n"
151
162
 
152
163
  elif job.call:
153
- print(f"call: {job.call}") # noqa: T201
164
+ text = f"call: {job.call}\n"
154
165
  for args in it:
155
- print(f"args: {args}") # noqa: T201
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
- pass
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
@@ -4,7 +4,7 @@ from pathlib import Path
4
4
 
5
5
  def main():
6
6
  path = Path(sys.argv[1])
7
- arg = " ".join(sys.argv[-2:])
7
+ arg = " ".join(sys.argv[3:5])
8
8
 
9
9
  if not path.exists():
10
10
  path.write_text(arg)
@@ -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 - options: --opt1 --opt2\n")
34
- assert cfg.jobs["a"].steps[0].options == "--opt1 --opt2"
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
- step = Step(args="a=1:3", batch="b=3,4 c=5,6")
13
- it = iter_args(step)
14
- assert next(it) == ["a=1,2,3", "b=3", "c=5"]
15
- assert next(it) == ["a=1,2,3", "b=3", "c=6"]
16
- assert next(it) == ["a=1,2,3", "b=4", "c=5"]
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
- step = Step(args="a=1:3", batch="b=3,4|c=5:7")
24
- it = iter_args(step)
25
- assert next(it) == ["a=1,2,3", "b=3,4"]
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(args="a=1:2", batch="b=5,6")
41
- s2 = Step(args="a=3:4", batch="c=7,8")
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][-1] == x
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][-2] == x
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"{sys.executable} {file.as_posix()} {path.as_posix()}"
69
+ job.run = f"python {file.as_posix()} {path.as_posix()}"
85
70
  multirun(job)
86
- assert path.read_text() == "a=1,2 b=5 a=1,2 b=6 a=3,4 c=7 a=3,4 c=8"
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 "'a=1,2', 'b=5'" in out
104
- assert "'a=3,4', 'c=8'" in out
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 test_show(job: Job, capsys):
141
- from hydraflow.executor.job import show
125
+ def test_to_text(job: Job):
126
+ from hydraflow.executor.job import to_text
142
127
 
143
128
  job.call = "typer.echo"
144
- show(job)
145
- out, _ = capsys.readouterr()
146
- assert "call: typer.echo\n" in out
147
- assert "'hydra.job.name=test', 'a=3,4', 'c=8']" in out
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,13 +0,0 @@
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
- - args: count=1,2
11
- batch: name=a,b
12
- - args: count=100
13
- batch: name=c,d|e,f
@@ -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