hydraflow 0.12.5__py3-none-any.whl → 0.13.0__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 +62 -29
- hydraflow/executor/conf.py +0 -1
- hydraflow/executor/job.py +83 -54
- {hydraflow-0.12.5.dist-info → hydraflow-0.13.0.dist-info}/METADATA +1 -1
- {hydraflow-0.12.5.dist-info → hydraflow-0.13.0.dist-info}/RECORD +8 -8
- {hydraflow-0.12.5.dist-info → hydraflow-0.13.0.dist-info}/WHEEL +0 -0
- {hydraflow-0.12.5.dist-info → hydraflow-0.13.0.dist-info}/entry_points.txt +0 -0
- {hydraflow-0.12.5.dist-info → hydraflow-0.13.0.dist-info}/licenses/LICENSE +0 -0
hydraflow/cli.py
CHANGED
@@ -13,53 +13,86 @@ app = typer.Typer(add_completion=False)
|
|
13
13
|
console = Console()
|
14
14
|
|
15
15
|
|
16
|
-
@app.command()
|
16
|
+
@app.command(context_settings={"ignore_unknown_options": True})
|
17
17
|
def run(
|
18
18
|
name: Annotated[str, Argument(help="Job name.", show_default=False)],
|
19
19
|
*,
|
20
|
+
args: Annotated[
|
21
|
+
list[str] | None,
|
22
|
+
Argument(help="Arguments to pass to the job.", show_default=False),
|
23
|
+
] = None,
|
20
24
|
dry_run: Annotated[
|
21
25
|
bool,
|
22
|
-
Option("--dry-run", help="Perform a dry run"),
|
26
|
+
Option("--dry-run", help="Perform a dry run."),
|
23
27
|
] = False,
|
24
28
|
) -> None:
|
25
29
|
"""Run a job."""
|
26
|
-
|
27
30
|
from hydraflow.executor.io import get_job
|
28
|
-
from hydraflow.executor.job import
|
29
|
-
iter_batches,
|
30
|
-
iter_calls,
|
31
|
-
iter_runs,
|
32
|
-
submit,
|
33
|
-
to_text,
|
34
|
-
)
|
31
|
+
from hydraflow.executor.job import iter_batches, iter_calls, iter_runs
|
35
32
|
|
33
|
+
args = args or []
|
36
34
|
job = get_job(name)
|
37
35
|
|
38
|
-
if dry_run:
|
39
|
-
typer.echo(to_text(job))
|
40
|
-
raise typer.Exit
|
41
|
-
|
42
|
-
import mlflow
|
43
|
-
|
44
|
-
mlflow.set_experiment(job.name)
|
45
|
-
|
46
|
-
if job.submit:
|
47
|
-
funcname, *args = shlex.split(job.submit)
|
48
|
-
submit(funcname, args, iter_batches(job))
|
49
|
-
raise typer.Exit
|
50
|
-
|
51
36
|
if job.run:
|
52
|
-
|
53
|
-
it = iter_runs(
|
37
|
+
args = [*shlex.split(job.run), *args]
|
38
|
+
it = iter_runs(args, iter_batches(job), dry_run=dry_run)
|
54
39
|
elif job.call:
|
55
|
-
|
56
|
-
it = iter_calls(
|
40
|
+
args = [*shlex.split(job.call), *args]
|
41
|
+
it = iter_calls(args, iter_batches(job), dry_run=dry_run)
|
57
42
|
else:
|
58
43
|
typer.echo(f"No command found in job: {job.name}.")
|
59
44
|
raise typer.Exit(1)
|
60
45
|
|
61
|
-
|
62
|
-
|
46
|
+
if not dry_run:
|
47
|
+
import mlflow
|
48
|
+
|
49
|
+
mlflow.set_experiment(job.name)
|
50
|
+
|
51
|
+
for task in it: # jobs will be executed here
|
52
|
+
if job.run and dry_run:
|
53
|
+
typer.echo(shlex.join(task.args))
|
54
|
+
elif job.call and dry_run:
|
55
|
+
funcname, *args = task.args
|
56
|
+
arg = ", ".join(f"{arg!r}" for arg in args)
|
57
|
+
typer.echo(f"{funcname}([{arg}])")
|
58
|
+
|
59
|
+
|
60
|
+
@app.command(context_settings={"ignore_unknown_options": True})
|
61
|
+
def submit(
|
62
|
+
name: Annotated[str, Argument(help="Job name.", show_default=False)],
|
63
|
+
*,
|
64
|
+
args: Annotated[
|
65
|
+
list[str] | None,
|
66
|
+
Argument(help="Arguments to pass to the job.", show_default=False),
|
67
|
+
] = None,
|
68
|
+
dry_run: Annotated[
|
69
|
+
bool,
|
70
|
+
Option("--dry-run", help="Perform a dry run."),
|
71
|
+
] = False,
|
72
|
+
) -> None:
|
73
|
+
"""Submit a job."""
|
74
|
+
from hydraflow.executor.io import get_job
|
75
|
+
from hydraflow.executor.job import iter_batches, submit
|
76
|
+
|
77
|
+
args = args or []
|
78
|
+
job = get_job(name)
|
79
|
+
|
80
|
+
if not job.run:
|
81
|
+
typer.echo(f"No run found in job: {job.name}.")
|
82
|
+
raise typer.Exit(1)
|
83
|
+
|
84
|
+
if not dry_run:
|
85
|
+
import mlflow
|
86
|
+
|
87
|
+
mlflow.set_experiment(job.name)
|
88
|
+
|
89
|
+
args = [*shlex.split(job.run), *args]
|
90
|
+
result = submit(args, iter_batches(job), dry_run=dry_run)
|
91
|
+
|
92
|
+
if dry_run and isinstance(result, tuple):
|
93
|
+
for line in result[1].splitlines():
|
94
|
+
args = shlex.split(line)
|
95
|
+
typer.echo(shlex.join([*result[0][:-1], *args]))
|
63
96
|
|
64
97
|
|
65
98
|
@app.command()
|
hydraflow/executor/conf.py
CHANGED
hydraflow/executor/job.py
CHANGED
@@ -22,7 +22,10 @@ import shlex
|
|
22
22
|
import subprocess
|
23
23
|
import sys
|
24
24
|
from dataclasses import dataclass
|
25
|
-
from
|
25
|
+
from pathlib import Path
|
26
|
+
from subprocess import CompletedProcess
|
27
|
+
from tempfile import NamedTemporaryFile
|
28
|
+
from typing import TYPE_CHECKING, overload
|
26
29
|
|
27
30
|
import ulid
|
28
31
|
|
@@ -82,29 +85,49 @@ def iter_batches(job: Job) -> Iterator[list[str]]:
|
|
82
85
|
|
83
86
|
|
84
87
|
@dataclass
|
85
|
-
class
|
86
|
-
"""An executed
|
88
|
+
class Task:
|
89
|
+
"""An executed task."""
|
87
90
|
|
91
|
+
args: list[str]
|
88
92
|
total: int
|
89
93
|
completed: int
|
94
|
+
|
95
|
+
|
96
|
+
@dataclass
|
97
|
+
class Run(Task):
|
98
|
+
"""An executed run."""
|
99
|
+
|
90
100
|
result: CompletedProcess
|
91
101
|
|
92
102
|
|
93
103
|
@dataclass
|
94
|
-
class Call:
|
104
|
+
class Call(Task):
|
95
105
|
"""An executed call."""
|
96
106
|
|
97
|
-
total: int
|
98
|
-
completed: int
|
99
107
|
result: Any
|
100
108
|
|
101
109
|
|
110
|
+
@overload
|
111
|
+
def iter_runs(args: list[str], iterable: Iterable[list[str]]) -> Iterator[Run]: ...
|
112
|
+
|
113
|
+
|
114
|
+
@overload
|
115
|
+
def iter_runs(
|
116
|
+
args: list[str],
|
117
|
+
iterable: Iterable[list[str]],
|
118
|
+
*,
|
119
|
+
dry_run: bool = False,
|
120
|
+
) -> Iterator[Task | Run]: ...
|
121
|
+
|
122
|
+
|
102
123
|
def iter_runs(
|
103
|
-
executable: str,
|
104
124
|
args: list[str],
|
105
125
|
iterable: Iterable[list[str]],
|
106
|
-
|
126
|
+
*,
|
127
|
+
dry_run: bool = False,
|
128
|
+
) -> Iterator[Task | Run]:
|
107
129
|
"""Execute multiple runs of a job using shell commands."""
|
130
|
+
executable, *args = args
|
108
131
|
if executable == "python" and sys.platform == "win32":
|
109
132
|
executable = sys.executable
|
110
133
|
|
@@ -112,34 +135,75 @@ def iter_runs(
|
|
112
135
|
total = len(iterable)
|
113
136
|
|
114
137
|
for completed, args_ in enumerate(iterable, 1):
|
115
|
-
|
116
|
-
|
138
|
+
cmd = [executable, *args, *args_]
|
139
|
+
if dry_run:
|
140
|
+
yield Task(cmd, total, completed)
|
141
|
+
else:
|
142
|
+
result = subprocess.run(cmd, check=False)
|
143
|
+
yield Run(cmd, total, completed, result)
|
144
|
+
|
145
|
+
|
146
|
+
@overload
|
147
|
+
def iter_calls(args: list[str], iterable: Iterable[list[str]]) -> Iterator[Call]: ...
|
148
|
+
|
149
|
+
|
150
|
+
@overload
|
151
|
+
def iter_calls(
|
152
|
+
args: list[str],
|
153
|
+
iterable: Iterable[list[str]],
|
154
|
+
*,
|
155
|
+
dry_run: bool = False,
|
156
|
+
) -> Iterator[Task | Call]: ...
|
117
157
|
|
118
158
|
|
119
159
|
def iter_calls(
|
120
|
-
funcname: str,
|
121
160
|
args: list[str],
|
122
161
|
iterable: Iterable[list[str]],
|
123
|
-
|
162
|
+
*,
|
163
|
+
dry_run: bool = False,
|
164
|
+
) -> Iterator[Task | Call]:
|
124
165
|
"""Execute multiple calls of a job using Python functions."""
|
166
|
+
funcname, *args = args
|
125
167
|
func = get_callable(funcname)
|
126
168
|
|
127
169
|
iterable = list(iterable)
|
128
170
|
total = len(iterable)
|
129
171
|
|
130
172
|
for completed, args_ in enumerate(iterable, 1):
|
131
|
-
|
132
|
-
|
173
|
+
cmd = [funcname, *args, *args_]
|
174
|
+
if dry_run:
|
175
|
+
yield Task(cmd, total, completed)
|
176
|
+
else:
|
177
|
+
result = func([*args, *args_])
|
178
|
+
yield Call(cmd, total, completed, result)
|
133
179
|
|
134
180
|
|
135
181
|
def submit(
|
136
|
-
funcname: str,
|
137
182
|
args: list[str],
|
138
183
|
iterable: Iterable[list[str]],
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
184
|
+
*,
|
185
|
+
dry_run: bool = False,
|
186
|
+
) -> CompletedProcess | tuple[list[str], str]:
|
187
|
+
"""Submit entire job using a shell command."""
|
188
|
+
executable, *args = args
|
189
|
+
if executable == "python" and sys.platform == "win32":
|
190
|
+
executable = sys.executable
|
191
|
+
|
192
|
+
temp = NamedTemporaryFile(dir=Path.cwd(), delete=False) # for Windows
|
193
|
+
file = Path(temp.name)
|
194
|
+
temp.close()
|
195
|
+
|
196
|
+
text = "\n".join(shlex.join(args) for args in iterable)
|
197
|
+
file.write_text(text)
|
198
|
+
cmd = [executable, *args, file.as_posix()]
|
199
|
+
|
200
|
+
try:
|
201
|
+
if dry_run:
|
202
|
+
return cmd, text
|
203
|
+
return subprocess.run(cmd, check=False)
|
204
|
+
|
205
|
+
finally:
|
206
|
+
file.unlink(missing_ok=True)
|
143
207
|
|
144
208
|
|
145
209
|
def get_callable(name: str) -> Callable:
|
@@ -156,38 +220,3 @@ def get_callable(name: str) -> Callable:
|
|
156
220
|
except (ImportError, AttributeError, ModuleNotFoundError) as e:
|
157
221
|
msg = f"Failed to import or find function: {name}"
|
158
222
|
raise ValueError(msg) from e
|
159
|
-
|
160
|
-
|
161
|
-
def to_text(job: Job) -> str:
|
162
|
-
"""Convert the job configuration to a string.
|
163
|
-
|
164
|
-
This function returns the job configuration for a given job.
|
165
|
-
|
166
|
-
Args:
|
167
|
-
job (Job): The job configuration to show.
|
168
|
-
|
169
|
-
Returns:
|
170
|
-
str: The job configuration.
|
171
|
-
|
172
|
-
"""
|
173
|
-
text = ""
|
174
|
-
|
175
|
-
it = iter_batches(job)
|
176
|
-
|
177
|
-
if job.run:
|
178
|
-
base_cmds = shlex.split(job.run)
|
179
|
-
for args in it:
|
180
|
-
cmds = " ".join([*base_cmds, *args])
|
181
|
-
text += f"{cmds}\n"
|
182
|
-
|
183
|
-
elif job.call:
|
184
|
-
text = f"call: {job.call}\n"
|
185
|
-
for args in it:
|
186
|
-
text += f"args: {args}\n"
|
187
|
-
|
188
|
-
elif job.submit:
|
189
|
-
text = f"submit: {job.submit}\n"
|
190
|
-
for args in it:
|
191
|
-
text += f"args: {args}\n"
|
192
|
-
|
193
|
-
return text.rstrip()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hydraflow
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.13.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
|
@@ -1,5 +1,5 @@
|
|
1
1
|
hydraflow/__init__.py,sha256=f2KO2iF7um-nNmayNyEr7TWG4UICOXy7YAN1d3qu0OY,936
|
2
|
-
hydraflow/cli.py,sha256=
|
2
|
+
hydraflow/cli.py,sha256=nGFrZeQnn1h7lCbVhjBJwdrcDDGoqYpVFbqqwkLMzng,3385
|
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
|
@@ -13,12 +13,12 @@ hydraflow/entities/run_collection.py,sha256=4sfZWXaS7kqnVCo9GyB3pN6BaSUCkA-ZqSSl
|
|
13
13
|
hydraflow/entities/run_data.py,sha256=lz8HPxG0iz1Jf9FU6HTFW4gcAc3N2pgMyAPqAIK6I74,1644
|
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
|
-
hydraflow/executor/conf.py,sha256=
|
16
|
+
hydraflow/executor/conf.py,sha256=2dv6_PlsynRmia-fGZlmBEVt8GopT0f32N13qY7tYnM,402
|
17
17
|
hydraflow/executor/io.py,sha256=yZMcBVmAbPZZ82cAXhgiJfj9p8WvHmzOCMBg_vtEVek,1509
|
18
|
-
hydraflow/executor/job.py,sha256=
|
18
|
+
hydraflow/executor/job.py,sha256=bmjlqE-cE7lyNFFKj1nUhxiQHKf7DsFkCSTD9iTEQ5I,5606
|
19
19
|
hydraflow/executor/parser.py,sha256=_Rfund3FDgrXitTt_znsTpgEtMDqZ_ICynaB_Zje14Q,14561
|
20
|
-
hydraflow-0.
|
21
|
-
hydraflow-0.
|
22
|
-
hydraflow-0.
|
23
|
-
hydraflow-0.
|
24
|
-
hydraflow-0.
|
20
|
+
hydraflow-0.13.0.dist-info/METADATA,sha256=DcBL4IuQHdGIyEXTqFoDGlUdI2sgH0THpTuyFoB3Wg0,4549
|
21
|
+
hydraflow-0.13.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
22
|
+
hydraflow-0.13.0.dist-info/entry_points.txt,sha256=XI0khPbpCIUo9UPqkNEpgh-kqK3Jy8T7L2VCWOdkbSM,48
|
23
|
+
hydraflow-0.13.0.dist-info/licenses/LICENSE,sha256=IGdDrBPqz1O0v_UwCW-NJlbX9Hy9b3uJ11t28y2srmY,1062
|
24
|
+
hydraflow-0.13.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|