hydraflow 0.10.0__py3-none-any.whl → 0.10.2__py3-none-any.whl
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/cli.py +22 -23
- hydraflow/core/io.py +1 -1
- hydraflow/core/main.py +8 -4
- hydraflow/entities/run_collection.py +1 -1
- hydraflow/executor/io.py +15 -0
- hydraflow/executor/job.py +24 -11
- hydraflow/executor/parser.py +38 -13
- {hydraflow-0.10.0.dist-info → hydraflow-0.10.2.dist-info}/METADATA +1 -1
- {hydraflow-0.10.0.dist-info → hydraflow-0.10.2.dist-info}/RECORD +12 -12
- {hydraflow-0.10.0.dist-info → hydraflow-0.10.2.dist-info}/WHEEL +0 -0
- {hydraflow-0.10.0.dist-info → hydraflow-0.10.2.dist-info}/entry_points.txt +0 -0
- {hydraflow-0.10.0.dist-info → hydraflow-0.10.2.dist-info}/licenses/LICENSE +0 -0
hydraflow/cli.py
CHANGED
@@ -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)
|
hydraflow/core/io.py
CHANGED
@@ -136,7 +136,7 @@ def load_overrides(run: Run) -> ListConfig:
|
|
136
136
|
|
137
137
|
"""
|
138
138
|
path = get_artifact_dir(run) / ".hydra/overrides.yaml"
|
139
|
-
return OmegaConf.load(path) # type: ignore
|
139
|
+
return sorted(OmegaConf.load(path)) # type: ignore
|
140
140
|
|
141
141
|
|
142
142
|
def remove_run(run: Run | Iterable[Run]) -> None:
|
hydraflow/core/main.py
CHANGED
@@ -154,11 +154,15 @@ def equals(run_dir: Path, config: Any, overrides: list[str] | None) -> bool:
|
|
154
154
|
"""
|
155
155
|
if overrides is None:
|
156
156
|
path = run_dir / "artifacts/.hydra/config.yaml"
|
157
|
-
|
158
|
-
|
159
|
-
|
157
|
+
|
158
|
+
if not path.exists():
|
159
|
+
return False
|
160
|
+
|
161
|
+
return OmegaConf.load(path) == config
|
162
|
+
|
163
|
+
path = run_dir / "artifacts/.hydra/overrides.yaml"
|
160
164
|
|
161
165
|
if not path.exists():
|
162
166
|
return False
|
163
167
|
|
164
|
-
return OmegaConf.load(path) ==
|
168
|
+
return sorted(OmegaConf.load(path)) == sorted(overrides)
|
@@ -116,7 +116,7 @@ class RunCollection:
|
|
116
116
|
but not in the other.
|
117
117
|
|
118
118
|
"""
|
119
|
-
runs = [run for run in self._runs if run not in other._runs]
|
119
|
+
runs = [run for run in self._runs if run not in other._runs]
|
120
120
|
return self.__class__(runs)
|
121
121
|
|
122
122
|
@property
|
hydraflow/executor/io.py
CHANGED
@@ -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
|
hydraflow/executor/job.py
CHANGED
@@ -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
|
|
@@ -99,6 +100,9 @@ def multirun(job: Job) -> None:
|
|
99
100
|
|
100
101
|
if job.run:
|
101
102
|
base_cmds = shlex.split(job.run)
|
103
|
+
if base_cmds[0] == "python" and sys.platform == "win32":
|
104
|
+
base_cmds[0] = sys.executable
|
105
|
+
|
102
106
|
for args in it:
|
103
107
|
cmds = [*base_cmds, *args]
|
104
108
|
try:
|
@@ -108,45 +112,54 @@ def multirun(job: Job) -> None:
|
|
108
112
|
raise RuntimeError(msg) from e
|
109
113
|
|
110
114
|
elif job.call:
|
111
|
-
|
112
|
-
|
115
|
+
call_name, *base_args = shlex.split(job.call)
|
116
|
+
|
117
|
+
if "." not in call_name:
|
118
|
+
msg = f"Invalid function path: {call_name}."
|
113
119
|
msg += " Expected format: 'package.module.function'"
|
114
120
|
raise ValueError(msg)
|
115
121
|
|
116
122
|
try:
|
117
|
-
module_name, func_name =
|
123
|
+
module_name, func_name = call_name.rsplit(".", 1)
|
118
124
|
module = importlib.import_module(module_name)
|
119
125
|
func = getattr(module, func_name)
|
120
126
|
except (ImportError, AttributeError, ModuleNotFoundError) as e:
|
121
|
-
msg = f"Failed to import or find function: {
|
127
|
+
msg = f"Failed to import or find function: {call_name}"
|
122
128
|
raise ValueError(msg) from e
|
123
129
|
|
124
130
|
for args in it:
|
125
131
|
try:
|
126
|
-
func(*args)
|
132
|
+
func([*base_args, *args])
|
127
133
|
except Exception as e: # noqa: PERF203
|
128
134
|
msg = f"Function call '{job.call}' failed with args: {args}"
|
129
135
|
raise RuntimeError(msg) from e
|
130
136
|
|
131
137
|
|
132
|
-
def
|
133
|
-
"""
|
138
|
+
def to_text(job: Job) -> str:
|
139
|
+
"""Convert the job configuration to a string.
|
134
140
|
|
135
|
-
This function
|
141
|
+
This function returns the job configuration for a given job.
|
136
142
|
|
137
143
|
Args:
|
138
144
|
job (Job): The job configuration to show.
|
139
145
|
|
146
|
+
Returns:
|
147
|
+
str: The job configuration.
|
148
|
+
|
140
149
|
"""
|
150
|
+
text = ""
|
151
|
+
|
141
152
|
it = iter_batches(job)
|
142
153
|
|
143
154
|
if job.run:
|
144
155
|
base_cmds = shlex.split(job.run)
|
145
156
|
for args in it:
|
146
157
|
cmds = " ".join([*base_cmds, *args])
|
147
|
-
|
158
|
+
text += f"{cmds}\n"
|
148
159
|
|
149
160
|
elif job.call:
|
150
|
-
|
161
|
+
text = f"call: {job.call}\n"
|
151
162
|
for args in it:
|
152
|
-
|
163
|
+
text += f"args: {args}\n"
|
164
|
+
|
165
|
+
return text
|
hydraflow/executor/parser.py
CHANGED
@@ -8,7 +8,7 @@ ranges, and expand values from string arguments.
|
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
10
|
|
11
|
-
import
|
11
|
+
import shlex
|
12
12
|
from itertools import chain, product
|
13
13
|
from typing import TYPE_CHECKING
|
14
14
|
|
@@ -289,7 +289,7 @@ def split(arg: str) -> list[str]:
|
|
289
289
|
return result
|
290
290
|
|
291
291
|
|
292
|
-
def expand_values(arg: str) ->
|
292
|
+
def expand_values(arg: str, suffix: str = "") -> Iterator[str]:
|
293
293
|
"""Expand a string argument into a list of values.
|
294
294
|
|
295
295
|
Take a string containing comma-separated values or ranges and return a list
|
@@ -297,12 +297,36 @@ def expand_values(arg: str) -> list[str]:
|
|
297
297
|
|
298
298
|
Args:
|
299
299
|
arg (str): The argument to expand.
|
300
|
+
suffix (str): The suffix to append to each value.
|
300
301
|
|
301
302
|
Returns:
|
302
|
-
|
303
|
+
Iterator[str]: An iterator of the expanded values.
|
303
304
|
|
304
305
|
"""
|
305
|
-
|
306
|
+
if suffix in SUFFIX_EXPONENT:
|
307
|
+
suffix = SUFFIX_EXPONENT[suffix]
|
308
|
+
|
309
|
+
for value in chain.from_iterable(collect_values(x) for x in split(arg)):
|
310
|
+
yield f"{value}{suffix}"
|
311
|
+
|
312
|
+
|
313
|
+
def split_arg(arg: str) -> tuple[str, str, str]:
|
314
|
+
"""Split an argument into a key, suffix, and value.
|
315
|
+
|
316
|
+
Args:
|
317
|
+
arg (str): The argument to split.
|
318
|
+
|
319
|
+
Returns:
|
320
|
+
tuple[str, str, str]: A tuple containing the key, suffix, and value.
|
321
|
+
|
322
|
+
"""
|
323
|
+
key, value = arg.split("=")
|
324
|
+
|
325
|
+
if "/" in key:
|
326
|
+
key, suffix = key.split("/", 1)
|
327
|
+
return key, suffix, value
|
328
|
+
|
329
|
+
return key, "", value
|
306
330
|
|
307
331
|
|
308
332
|
def collect_arg(arg: str) -> str:
|
@@ -318,9 +342,9 @@ def collect_arg(arg: str) -> str:
|
|
318
342
|
str: A string of the collected key and values.
|
319
343
|
|
320
344
|
"""
|
321
|
-
key,
|
322
|
-
|
323
|
-
return f"{key}={
|
345
|
+
key, suffix, value = split_arg(arg)
|
346
|
+
value = ",".join(expand_values(value, suffix))
|
347
|
+
return f"{key}={value}"
|
324
348
|
|
325
349
|
|
326
350
|
def expand_arg(arg: str) -> Iterator[str]:
|
@@ -338,26 +362,27 @@ def expand_arg(arg: str) -> Iterator[str]:
|
|
338
362
|
|
339
363
|
"""
|
340
364
|
if "|" not in arg:
|
341
|
-
key, value = arg
|
365
|
+
key, suffix, value = split_arg(arg)
|
342
366
|
|
343
|
-
for v in expand_values(value):
|
367
|
+
for v in expand_values(value, suffix):
|
344
368
|
yield f"{key}={v}"
|
345
369
|
|
346
370
|
return
|
347
371
|
|
348
372
|
args = arg.split("|")
|
349
373
|
key = ""
|
374
|
+
suffix = ""
|
350
375
|
|
351
376
|
for arg_ in args:
|
352
377
|
if "=" in arg_:
|
353
|
-
key, value = arg_
|
378
|
+
key, suffix, value = split_arg(arg_)
|
354
379
|
elif key:
|
355
380
|
value = arg_
|
356
381
|
else:
|
357
382
|
msg = f"Invalid argument: {arg_}"
|
358
383
|
raise ValueError(msg)
|
359
384
|
|
360
|
-
value = ",".join(expand_values(value))
|
385
|
+
value = ",".join(expand_values(value, suffix))
|
361
386
|
yield f"{key}={value}"
|
362
387
|
|
363
388
|
|
@@ -372,7 +397,7 @@ def collect(args: str | list[str]) -> list[str]:
|
|
372
397
|
|
373
398
|
"""
|
374
399
|
if isinstance(args, str):
|
375
|
-
args =
|
400
|
+
args = shlex.split(args)
|
376
401
|
|
377
402
|
args = [arg for arg in args if "=" in arg]
|
378
403
|
|
@@ -390,7 +415,7 @@ def expand(args: str | list[str]) -> list[list[str]]:
|
|
390
415
|
|
391
416
|
"""
|
392
417
|
if isinstance(args, str):
|
393
|
-
args =
|
418
|
+
args = shlex.split(args)
|
394
419
|
|
395
420
|
args = [arg for arg in args if "=" in arg]
|
396
421
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hydraflow
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.2
|
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
|
@@ -1,24 +1,24 @@
|
|
1
1
|
hydraflow/__init__.py,sha256=f2KO2iF7um-nNmayNyEr7TWG4UICOXy7YAN1d3qu0OY,936
|
2
|
-
hydraflow/cli.py,sha256=
|
2
|
+
hydraflow/cli.py,sha256=b-M368amGUblOOqOi7JuBsnzLuaNqgpTit9ZRj0qqac,1410
|
3
3
|
hydraflow/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
hydraflow/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
hydraflow/core/config.py,sha256=SJzjgsO_kzB78_whJ3lmy7GlZvTvwZONH1BJBn8zCuI,3817
|
6
6
|
hydraflow/core/context.py,sha256=QPyPg1xrTlmhviKNn-0nDY9bXcVky1zInqRqPN-VNhc,4741
|
7
|
-
hydraflow/core/io.py,sha256=
|
8
|
-
hydraflow/core/main.py,sha256=
|
7
|
+
hydraflow/core/io.py,sha256=C207DsGAU4CLvlHySyyIl0_aKZ83ysJ-W_wNM2n4RPI,6754
|
8
|
+
hydraflow/core/main.py,sha256=500fh5cnlKj9CHFgD8-f16wZcOuPF-5zSvFeVMqI8MQ,5100
|
9
9
|
hydraflow/core/mlflow.py,sha256=M3MhiChnMzKnKRmjBl4h_SRGkAZKL7GAmFr3DdzwRuQ,5666
|
10
10
|
hydraflow/core/param.py,sha256=LHU9j9_7oA99igasoOyKofKClVr9FmGA3UABJ-KmyS0,4538
|
11
11
|
hydraflow/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
|
-
hydraflow/entities/run_collection.py,sha256=
|
12
|
+
hydraflow/entities/run_collection.py,sha256=E8IRBgxCnJE_IPCaSmS2mc9GtDXXLBfc7GHv07d2j98,19635
|
13
13
|
hydraflow/entities/run_data.py,sha256=Y2_Lc-BdQ7nXhcEIjdHGHIkLrXsmAktOftESEwYOY8o,1602
|
14
14
|
hydraflow/entities/run_info.py,sha256=FRC6ICOlzB2u_xi_33Qs-YZLt677UotuNbYqI7XSmHY,1017
|
15
15
|
hydraflow/executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
16
|
hydraflow/executor/conf.py,sha256=q_FrPXQJCVGKS1FYnGRGqTUgMQeMBkaVPW2mtQc8oxk,384
|
17
|
-
hydraflow/executor/io.py,sha256=
|
18
|
-
hydraflow/executor/job.py,sha256=
|
19
|
-
hydraflow/executor/parser.py,sha256=
|
20
|
-
hydraflow-0.10.
|
21
|
-
hydraflow-0.10.
|
22
|
-
hydraflow-0.10.
|
23
|
-
hydraflow-0.10.
|
24
|
-
hydraflow-0.10.
|
17
|
+
hydraflow/executor/io.py,sha256=xV3m-nV9eKbu9Fb7u04J2bfmR_Ky3jTEJjq4QC2m6V4,954
|
18
|
+
hydraflow/executor/job.py,sha256=YZ9JuVrdAcG3_HvNngHy9NL4-3bjJTaColE_5efF8TU,4743
|
19
|
+
hydraflow/executor/parser.py,sha256=MO8VU0uVQZeku6kbw8Urid_5QEcnR8atd5h-yDP5OhQ,10147
|
20
|
+
hydraflow-0.10.2.dist-info/METADATA,sha256=BidbFHVOZjuNytZBhYAg9_T5OBkmu4VNprJJFxnCqPI,4574
|
21
|
+
hydraflow-0.10.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
22
|
+
hydraflow-0.10.2.dist-info/entry_points.txt,sha256=XI0khPbpCIUo9UPqkNEpgh-kqK3Jy8T7L2VCWOdkbSM,48
|
23
|
+
hydraflow-0.10.2.dist-info/licenses/LICENSE,sha256=IGdDrBPqz1O0v_UwCW-NJlbX9Hy9b3uJ11t28y2srmY,1062
|
24
|
+
hydraflow-0.10.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|