hydraflow 0.10.2__tar.gz → 0.11.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.10.2 → hydraflow-0.11.1}/PKG-INFO +1 -2
- {hydraflow-0.10.2 → hydraflow-0.11.1}/README.md +0 -1
- {hydraflow-0.10.2 → hydraflow-0.11.1}/pyproject.toml +2 -1
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/executor/conf.py +3 -2
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/executor/job.py +14 -11
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/executor/parser.py +68 -6
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/cli/app.py +7 -1
- hydraflow-0.11.1/tests/cli/hydraflow.yaml +22 -0
- hydraflow-0.11.1/tests/cli/test_run.py +63 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/executor/echo.py +1 -1
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/executor/test_conf.py +2 -2
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/executor/test_job.py +20 -30
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/executor/test_parser.py +16 -2
- hydraflow-0.10.2/tests/cli/hydraflow.yaml +0 -13
- hydraflow-0.10.2/tests/cli/test_run.py +0 -41
- {hydraflow-0.10.2 → hydraflow-0.11.1}/.cursorrules +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/.devcontainer/devcontainer.json +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/.devcontainer/postCreate.sh +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/.devcontainer/starship.toml +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/.gitattributes +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/.github/workflows/ci.yaml +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/.github/workflows/docs.yaml +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/.gitignore +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/LICENSE +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/apps/quickstart.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/docs/index.md +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/docs/usage/quickstart.md +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/mkdocs.yaml +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/__init__.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/cli.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/core/__init__.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/core/config.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/core/context.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/core/io.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/core/main.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/core/mlflow.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/core/param.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/entities/__init__.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/entities/run_collection.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/entities/run_data.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/entities/run_info.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/executor/__init__.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/executor/io.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/src/hydraflow/py.typed +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/__init__.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/cli/__init__.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/cli/conftest.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/cli/test_setup.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/cli/test_show.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/cli/test_version.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/conftest.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/__init__.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/config/__init__.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/config/test_config.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/config/test_params.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/context/__init__.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/context/chdir.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/context/log_run.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/context/start_run.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/context/test_chdir.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/context/test_log_run.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/context/test_start_run.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/io/__init__.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/io/hydra_dir.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/io/test_hydra_dir.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/io/test_iter_dirs.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/io/test_run.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/main/__init__.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/main/default.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/main/force_new_run.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/main/match_overrides.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/main/rerun_finished.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/main/skip_finished.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/main/test_default.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/main/test_force_new_run.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/main/test_match_overrides.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/main/test_rerun_finished.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/main/test_skip_finished.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/param/__init__.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/param/params.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/param/test_param.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/param/test_params.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/core/test_mlflow.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/entities/__init__.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/entities/filter.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/entities/test_collection.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/entities/test_data.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/entities/test_filter.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/entities/test_info.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/entities/test_values.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/entities/values.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/executor/__init__.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/executor/conftest.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/executor/test_args.py +0 -0
- {hydraflow-0.10.2 → hydraflow-0.11.1}/tests/executor/test_io.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hydraflow
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.11.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
|
@@ -97,7 +97,6 @@ Here is a simple example to get you started with Hydraflow:
|
|
97
97
|
from __future__ import annotations
|
98
98
|
|
99
99
|
from dataclasses import dataclass
|
100
|
-
from pathlib import Path
|
101
100
|
from typing import TYPE_CHECKING
|
102
101
|
|
103
102
|
import 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.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" }
|
@@ -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",
|
@@ -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
|
|
@@ -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
|
@@ -31,27 +31,27 @@ from .parser import collect, expand
|
|
31
31
|
if TYPE_CHECKING:
|
32
32
|
from collections.abc import Iterator
|
33
33
|
|
34
|
-
from .conf import Job
|
34
|
+
from .conf import Job
|
35
35
|
|
36
36
|
|
37
|
-
def iter_args(
|
37
|
+
def iter_args(batch: str, args: str) -> Iterator[list[str]]:
|
38
38
|
"""Iterate over combinations generated from parsed arguments.
|
39
39
|
|
40
40
|
Generate all possible combinations of arguments by parsing and
|
41
41
|
expanding each one, yielding them as an iterator.
|
42
42
|
|
43
43
|
Args:
|
44
|
-
|
44
|
+
batch (str): The batch to parse.
|
45
|
+
args (str): The arguments to parse.
|
45
46
|
|
46
47
|
Yields:
|
47
48
|
list[str]: a list of the parsed argument combinations.
|
48
49
|
|
49
50
|
"""
|
50
|
-
|
51
|
-
options = [o for o in step.options.split(" ") if o]
|
51
|
+
args_ = collect(args)
|
52
52
|
|
53
|
-
for
|
54
|
-
yield [*
|
53
|
+
for batch_ in expand(batch):
|
54
|
+
yield [*batch_, *args_]
|
55
55
|
|
56
56
|
|
57
57
|
def iter_batches(job: Job) -> Iterator[list[str]]:
|
@@ -69,11 +69,14 @@ def iter_batches(job: Job) -> Iterator[list[str]]:
|
|
69
69
|
|
70
70
|
"""
|
71
71
|
job_name = f"hydra.job.name={job.name}"
|
72
|
+
job_configs = shlex.split(job.configs)
|
72
73
|
|
73
74
|
for step in job.steps:
|
74
|
-
|
75
|
+
configs = shlex.split(step.configs) or job_configs
|
76
|
+
|
77
|
+
for args in iter_args(step.batch, step.args):
|
75
78
|
sweep_dir = f"hydra.sweep.dir=multirun/{ulid.ULID()}"
|
76
|
-
yield ["--multirun",
|
79
|
+
yield ["--multirun", *args, job_name, sweep_dir, *configs]
|
77
80
|
|
78
81
|
|
79
82
|
def multirun(job: Job) -> None:
|
@@ -162,4 +165,4 @@ def to_text(job: Job) -> str:
|
|
162
165
|
for args in it:
|
163
166
|
text += f"args: {args}\n"
|
164
167
|
|
165
|
-
return text
|
168
|
+
return text.rstrip()
|
@@ -216,6 +216,9 @@ def collect_values(arg: str) -> list[str]:
|
|
216
216
|
list[str]: A list of the collected values.
|
217
217
|
|
218
218
|
"""
|
219
|
+
if "(" in arg:
|
220
|
+
return collect_parentheses(arg)
|
221
|
+
|
219
222
|
if ":" not in arg:
|
220
223
|
return [arg]
|
221
224
|
|
@@ -235,6 +238,58 @@ def collect_values(arg: str) -> list[str]:
|
|
235
238
|
return [add_exponent(x, exponent) for x in values]
|
236
239
|
|
237
240
|
|
241
|
+
def split_parentheses(arg: str) -> Iterator[str]:
|
242
|
+
"""Split a string with parentheses into a list of strings.
|
243
|
+
|
244
|
+
Args:
|
245
|
+
arg (str): The string to split.
|
246
|
+
|
247
|
+
Returns:
|
248
|
+
Iterator[str]: An iterator of the split strings.
|
249
|
+
|
250
|
+
Examples:
|
251
|
+
>>> list(split_parentheses("a(b,c)m(e:f)k"))
|
252
|
+
['a', 'b,c', 'e-3', 'e:f', 'e3']
|
253
|
+
>>> list(split_parentheses("(b,c)d(e:f)"))
|
254
|
+
['b,c', 'd', 'e:f']
|
255
|
+
|
256
|
+
"""
|
257
|
+
current = ""
|
258
|
+
|
259
|
+
for char in arg:
|
260
|
+
if char in ("(", ")"):
|
261
|
+
if current:
|
262
|
+
yield SUFFIX_EXPONENT.get(current, current)
|
263
|
+
current = ""
|
264
|
+
else:
|
265
|
+
current += char
|
266
|
+
|
267
|
+
if current:
|
268
|
+
yield SUFFIX_EXPONENT.get(current, current)
|
269
|
+
|
270
|
+
|
271
|
+
def collect_parentheses(arg: str) -> list[str]:
|
272
|
+
"""Collect values from a string with parentheses.
|
273
|
+
|
274
|
+
Args:
|
275
|
+
arg (str): The string to collect values from.
|
276
|
+
|
277
|
+
Returns:
|
278
|
+
list[str]: A list of the collected values.
|
279
|
+
|
280
|
+
Examples:
|
281
|
+
>>> collect_parentheses("(1:3,5:2:9,20)k")
|
282
|
+
['1e3', '2e3', '3e3', '5e3', '7e3', '9e3', '20e3']
|
283
|
+
>>> collect_parentheses("2e(-1,-2,-3)")
|
284
|
+
['2e-1', '2e-2', '2e-3']
|
285
|
+
>>> collect_parentheses("(1:3)e(3,5)")
|
286
|
+
['1e3', '2e3', '3e3', '1e5', '2e5', '3e5']
|
287
|
+
|
288
|
+
"""
|
289
|
+
it = [expand_values(x) for x in split_parentheses(arg)]
|
290
|
+
return ["".join(x[::-1]) for x in product(*it[::-1])]
|
291
|
+
|
292
|
+
|
238
293
|
def split(arg: str) -> list[str]:
|
239
294
|
r"""Split a string by top-level commas.
|
240
295
|
|
@@ -255,11 +310,14 @@ def split(arg: str) -> list[str]:
|
|
255
310
|
['"x,y"', 'z']
|
256
311
|
>>> split("'p,q',r")
|
257
312
|
["'p,q'", 'r']
|
313
|
+
>>> split("(a,b)m,(1,2:4)k")
|
314
|
+
['(a,b)m', '(1,2:4)k']
|
258
315
|
|
259
316
|
"""
|
260
317
|
result = []
|
261
318
|
current = []
|
262
319
|
bracket_count = 0
|
320
|
+
paren_count = 0
|
263
321
|
in_single_quote = False
|
264
322
|
in_double_quote = False
|
265
323
|
|
@@ -272,9 +330,14 @@ def split(arg: str) -> list[str]:
|
|
272
330
|
bracket_count += 1
|
273
331
|
elif char == "]" and not (in_single_quote or in_double_quote):
|
274
332
|
bracket_count -= 1
|
333
|
+
elif char == "(" and not (in_single_quote or in_double_quote):
|
334
|
+
paren_count += 1
|
335
|
+
elif char == ")" and not (in_single_quote or in_double_quote):
|
336
|
+
paren_count -= 1
|
275
337
|
elif (
|
276
338
|
char == ","
|
277
339
|
and bracket_count == 0
|
340
|
+
and paren_count == 0
|
278
341
|
and not in_single_quote
|
279
342
|
and not in_double_quote
|
280
343
|
):
|
@@ -303,8 +366,7 @@ def expand_values(arg: str, suffix: str = "") -> Iterator[str]:
|
|
303
366
|
Iterator[str]: An iterator of the expanded values.
|
304
367
|
|
305
368
|
"""
|
306
|
-
|
307
|
-
suffix = SUFFIX_EXPONENT[suffix]
|
369
|
+
suffix = SUFFIX_EXPONENT.get(suffix, suffix)
|
308
370
|
|
309
371
|
for value in chain.from_iterable(collect_values(x) for x in split(arg)):
|
310
372
|
yield f"{value}{suffix}"
|
@@ -320,6 +382,10 @@ def split_arg(arg: str) -> tuple[str, str, str]:
|
|
320
382
|
tuple[str, str, str]: A tuple containing the key, suffix, and value.
|
321
383
|
|
322
384
|
"""
|
385
|
+
if "=" not in arg:
|
386
|
+
msg = f"Invalid argument: {arg}"
|
387
|
+
raise ValueError(msg)
|
388
|
+
|
323
389
|
key, value = arg.split("=")
|
324
390
|
|
325
391
|
if "/" in key:
|
@@ -399,8 +465,6 @@ def collect(args: str | list[str]) -> list[str]:
|
|
399
465
|
if isinstance(args, str):
|
400
466
|
args = shlex.split(args)
|
401
467
|
|
402
|
-
args = [arg for arg in args if "=" in arg]
|
403
|
-
|
404
468
|
return [collect_arg(arg) for arg in args]
|
405
469
|
|
406
470
|
|
@@ -417,6 +481,4 @@ def expand(args: str | list[str]) -> list[list[str]]:
|
|
417
481
|
if isinstance(args, str):
|
418
482
|
args = shlex.split(args)
|
419
483
|
|
420
|
-
args = [arg for arg in args if "=" in arg]
|
421
|
-
|
422
484
|
return [list(x) for x in product(*(expand_arg(arg) for arg in args))]
|
@@ -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
|
@@ -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,7 +57,7 @@ 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
63
|
def test_multirun_run(job: Job, tmp_path: Path):
|
@@ -79,7 +68,7 @@ def test_multirun_run(job: Job, tmp_path: Path):
|
|
79
68
|
|
80
69
|
job.run = f"python {file.as_posix()} {path.as_posix()}"
|
81
70
|
multirun(job)
|
82
|
-
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"
|
83
72
|
|
84
73
|
|
85
74
|
def test_multirun_run_error(job: Job):
|
@@ -96,8 +85,8 @@ def test_multirun_call(job: Job, capsys: pytest.CaptureFixture):
|
|
96
85
|
job.call = "typer.echo"
|
97
86
|
multirun(job)
|
98
87
|
out, _ = capsys.readouterr()
|
99
|
-
assert "'
|
100
|
-
assert "'
|
88
|
+
assert "'b=5', 'a=1,2'" in out
|
89
|
+
assert "'c=8', 'a=3,4'" in out
|
101
90
|
|
102
91
|
|
103
92
|
def test_multirun_call_args(job: Job, capsys: pytest.CaptureFixture):
|
@@ -139,4 +128,5 @@ def test_to_text(job: Job):
|
|
139
128
|
job.call = "typer.echo"
|
140
129
|
text = to_text(job)
|
141
130
|
assert "call: typer.echo\n" in text
|
142
|
-
assert "'
|
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
|
@@ -103,6 +103,7 @@ def test_split_suffix(s, x):
|
|
103
103
|
("1:2:e2", ["1e2", "2e2"]),
|
104
104
|
(":2:e2", ["0", "1e2", "2e2"]),
|
105
105
|
("-2:2:k", ["-2e3", "-1e3", "0", "1e3", "2e3"]),
|
106
|
+
("(1:3,5:2:9,20)k", ["1e3", "2e3", "3e3", "5e3", "7e3", "9e3", "20e3"]),
|
106
107
|
],
|
107
108
|
)
|
108
109
|
def test_collect_value(s, x):
|
@@ -124,6 +125,8 @@ def test_collect_value(s, x):
|
|
124
125
|
("[1,2],[3,4]", ["[1,2]", "[3,4]"]),
|
125
126
|
("'1,2','3,4'", ["'1,2'", "'3,4'"]),
|
126
127
|
('"1,2","3,4"', ['"1,2"', '"3,4"']),
|
128
|
+
("(1,4)k,(6,8)M", ["1e3", "4e3", "6e6", "8e6"]),
|
129
|
+
("(1:3)e-2,(5:7)e-3", ["1e-2", "2e-2", "3e-2", "5e-3", "6e-3", "7e-3"]),
|
127
130
|
],
|
128
131
|
)
|
129
132
|
def test_expand_value(s, x):
|
@@ -147,6 +150,13 @@ def test_expand_value_suffix(s, x):
|
|
147
150
|
assert list(expand_values(s, "k")) == x
|
148
151
|
|
149
152
|
|
153
|
+
def test_split_arg_error():
|
154
|
+
from hydraflow.executor.parser import split_arg
|
155
|
+
|
156
|
+
with pytest.raises(ValueError):
|
157
|
+
split_arg("1,2,3")
|
158
|
+
|
159
|
+
|
150
160
|
@pytest.mark.parametrize(
|
151
161
|
("s", "x"),
|
152
162
|
[
|
@@ -157,6 +167,7 @@ def test_expand_value_suffix(s, x):
|
|
157
167
|
("a=1:2", "a=1,2"),
|
158
168
|
("a/M=1:2", "a=1e6,2e6"),
|
159
169
|
("a=:2:3", "a=0,2"),
|
170
|
+
("a=(2,4)m,2,3", "a=2e-3,4e-3,2,3"),
|
160
171
|
("a=1:3:k", "a=1e3,2e3,3e3"),
|
161
172
|
("a=1:3:k,2:4:M", "a=1e3,2e3,3e3,2e6,3e6,4e6"),
|
162
173
|
("a/m=1:3,8:10", "a=1e-3,2e-3,3e-3,8e-3,9e-3,10e-3"),
|
@@ -177,6 +188,9 @@ def test_collect_arg(s, x):
|
|
177
188
|
("a=1:2", ["a=1", "a=2"]),
|
178
189
|
("a/n=1:2", ["a=1e-9", "a=2e-9"]),
|
179
190
|
("a=:2:3", ["a=0", "a=2"]),
|
191
|
+
("a=(0.1:0.1:0.4)k", ["a=0.1e3", "a=0.2e3", "a=0.3e3", "a=0.4e3"]),
|
192
|
+
("a=(1,2)(e-1,e2)", ["a=1e-1", "a=2e-1", "a=1e2", "a=2e2"]),
|
193
|
+
("a=(1,2)e(-1,2)", ["a=1e-1", "a=2e-1", "a=1e2", "a=2e2"]),
|
180
194
|
("a=1:3:k", ["a=1e3", "a=2e3", "a=3e3"]),
|
181
195
|
("a=1:3:k,2:4:M", ["a=1e3", "a=2e3", "a=3e3", "a=2e6", "a=3e6", "a=4e6"]),
|
182
196
|
("a=1,2|3,4", ["a=1,2", "a=3,4"]),
|
@@ -202,7 +216,7 @@ def test_expand_arg_error():
|
|
202
216
|
@pytest.mark.parametrize(
|
203
217
|
("s", "x"),
|
204
218
|
[
|
205
|
-
(["a=1"
|
219
|
+
(["a=1"], ["a=1"]),
|
206
220
|
(["a=1:3"], ["a=1,2,3"]),
|
207
221
|
(["a/m=1:3"], ["a=1e-3,2e-3,3e-3"]),
|
208
222
|
(["a=1:3", "b=4:6"], ["a=1,2,3", "b=4,5,6"]),
|
@@ -233,7 +247,7 @@ def test_collect_str(s, x):
|
|
233
247
|
@pytest.mark.parametrize(
|
234
248
|
("s", "x"),
|
235
249
|
[
|
236
|
-
(["a=1"
|
250
|
+
(["a=1"], [["a=1"]]),
|
237
251
|
(["a/k=1,2"], [["a=1e3"], ["a=2e3"]]),
|
238
252
|
(
|
239
253
|
" a=1,2\n b=3,4\n",
|
@@ -1,41 +0,0 @@
|
|
1
|
-
import pytest
|
2
|
-
from typer.testing import CliRunner
|
3
|
-
|
4
|
-
import hydraflow
|
5
|
-
from hydraflow.cli import app
|
6
|
-
|
7
|
-
pytestmark = pytest.mark.xdist_group(name="group1")
|
8
|
-
|
9
|
-
runner = CliRunner()
|
10
|
-
|
11
|
-
|
12
|
-
def test_run_args():
|
13
|
-
result = runner.invoke(app, ["run", "args"])
|
14
|
-
assert result.exit_code == 0
|
15
|
-
run_ids = hydraflow.list_run_ids("args")
|
16
|
-
assert len(run_ids) == 12
|
17
|
-
|
18
|
-
|
19
|
-
def test_run_batch():
|
20
|
-
result = runner.invoke(app, ["run", "batch"])
|
21
|
-
assert result.exit_code == 0
|
22
|
-
run_ids = hydraflow.list_run_ids("batch")
|
23
|
-
assert len(run_ids) == 8
|
24
|
-
|
25
|
-
|
26
|
-
def test_run_args_dry_run():
|
27
|
-
result = runner.invoke(app, ["run", "args", "--dry-run"])
|
28
|
-
assert result.exit_code == 0
|
29
|
-
assert "hydra.job.name=args" in result.stdout
|
30
|
-
assert "count=1,2,3 name=a,b" in result.stdout
|
31
|
-
assert "count=4,5,6 name=c,d" in result.stdout
|
32
|
-
|
33
|
-
|
34
|
-
def test_run_batch_dry_run():
|
35
|
-
result = runner.invoke(app, ["run", "batch", "--dry-run"])
|
36
|
-
assert result.exit_code == 0
|
37
|
-
assert "hydra.job.name=batch" in result.stdout
|
38
|
-
assert "count=1,2 name=a" in result.stdout
|
39
|
-
assert "count=1,2 name=b" in result.stdout
|
40
|
-
assert "count=100 name=c,d" in result.stdout
|
41
|
-
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
|
File without changes
|