hydraflow 0.2.1__tar.gz → 0.2.3__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {hydraflow-0.2.1 → hydraflow-0.2.3}/.devcontainer/devcontainer.json +6 -1
- {hydraflow-0.2.1 → hydraflow-0.2.3}/.gitignore +3 -2
- {hydraflow-0.2.1 → hydraflow-0.2.3}/PKG-INFO +17 -6
- {hydraflow-0.2.1 → hydraflow-0.2.3}/README.md +14 -5
- {hydraflow-0.2.1 → hydraflow-0.2.3}/pyproject.toml +16 -9
- {hydraflow-0.2.1 → hydraflow-0.2.3}/src/hydraflow/__init__.py +6 -2
- hydraflow-0.2.3/src/hydraflow/asyncio.py +199 -0
- {hydraflow-0.2.1 → hydraflow-0.2.3}/src/hydraflow/config.py +3 -0
- hydraflow-0.2.3/src/hydraflow/runs.py +849 -0
- hydraflow-0.2.3/tests/scripts/watch.py +9 -0
- hydraflow-0.2.3/tests/test_asyncio.py +159 -0
- {hydraflow-0.2.1 → hydraflow-0.2.3}/tests/test_config.py +6 -0
- hydraflow-0.2.3/tests/test_runs.py +403 -0
- {hydraflow-0.2.1 → hydraflow-0.2.3}/tests/test_watch.py +5 -8
- hydraflow-0.2.1/src/hydraflow/runs.py +0 -422
- hydraflow-0.2.1/tests/scripts/watch.py +0 -23
- hydraflow-0.2.1/tests/test_runs.py +0 -277
- {hydraflow-0.2.1 → hydraflow-0.2.3}/.devcontainer/postCreate.sh +0 -0
- {hydraflow-0.2.1 → hydraflow-0.2.3}/.devcontainer/starship.toml +0 -0
- {hydraflow-0.2.1 → hydraflow-0.2.3}/.gitattributes +0 -0
- {hydraflow-0.2.1 → hydraflow-0.2.3}/LICENSE +0 -0
- {hydraflow-0.2.1 → hydraflow-0.2.3}/src/hydraflow/context.py +0 -0
- {hydraflow-0.2.1 → hydraflow-0.2.3}/src/hydraflow/mlflow.py +0 -0
- {hydraflow-0.2.1 → hydraflow-0.2.3}/tests/scripts/__init__.py +0 -0
- {hydraflow-0.2.1 → hydraflow-0.2.3}/tests/scripts/log_run.py +0 -0
- {hydraflow-0.2.1 → hydraflow-0.2.3}/tests/test_context.py +0 -0
- {hydraflow-0.2.1 → hydraflow-0.2.3}/tests/test_log_run.py +0 -0
- {hydraflow-0.2.1 → hydraflow-0.2.3}/tests/test_mlflow.py +0 -0
- {hydraflow-0.2.1 → hydraflow-0.2.3}/tests/test_version.py +0 -0
@@ -7,7 +7,12 @@
|
|
7
7
|
},
|
8
8
|
"customizations": {
|
9
9
|
"vscode": {
|
10
|
-
"extensions": [
|
10
|
+
"extensions": [
|
11
|
+
"charliermarsh.ruff",
|
12
|
+
"henriiik.vscode-sort",
|
13
|
+
"ms-python.python",
|
14
|
+
"ms-python.vscode-pylance"
|
15
|
+
]
|
11
16
|
}
|
12
17
|
},
|
13
18
|
"postCreateCommand": ".devcontainer/postCreate.sh"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: hydraflow
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.3
|
4
4
|
Summary: Hydraflow integrates Hydra and MLflow to manage and track machine learning experiments.
|
5
5
|
Project-URL: Documentation, https://github.com/daizutabi/hydraflow
|
6
6
|
Project-URL: Source, https://github.com/daizutabi/hydraflow
|
@@ -20,7 +20,9 @@ Requires-Dist: hydra-core>1.3
|
|
20
20
|
Requires-Dist: mlflow>2.15
|
21
21
|
Requires-Dist: setuptools
|
22
22
|
Requires-Dist: watchdog
|
23
|
+
Requires-Dist: watchfiles
|
23
24
|
Provides-Extra: dev
|
25
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
24
26
|
Requires-Dist: pytest-clarity; extra == 'dev'
|
25
27
|
Requires-Dist: pytest-cov; extra == 'dev'
|
26
28
|
Requires-Dist: pytest-randomly; extra == 'dev'
|
@@ -46,14 +48,23 @@ Description-Content-Type: text/markdown
|
|
46
48
|
|
47
49
|
## Overview
|
48
50
|
|
49
|
-
Hydraflow is a powerful library designed to seamlessly integrate
|
51
|
+
Hydraflow is a powerful library designed to seamlessly integrate
|
52
|
+
[Hydra](https://hydra.cc/) and [MLflow](https://mlflow.org/), making it easier to
|
53
|
+
manage and track machine learning experiments. By combining the flexibility of
|
54
|
+
Hydra's configuration management with the robust experiment tracking capabilities
|
55
|
+
of MLflow, Hydraflow provides a comprehensive solution for managing complex
|
56
|
+
machine learning workflows.
|
50
57
|
|
51
58
|
## Key Features
|
52
59
|
|
53
|
-
- **Configuration Management**: Utilize Hydra's advanced configuration management
|
54
|
-
|
55
|
-
- **
|
56
|
-
|
60
|
+
- **Configuration Management**: Utilize Hydra's advanced configuration management
|
61
|
+
to handle complex parameter sweeps and experiment setups.
|
62
|
+
- **Experiment Tracking**: Leverage MLflow's tracking capabilities to log parameters,
|
63
|
+
metrics, and artifacts for each run.
|
64
|
+
- **Artifact Management**: Automatically log and manage artifacts, such as model
|
65
|
+
checkpoints and configuration files, with MLflow.
|
66
|
+
- **Seamless Integration**: Easily integrate Hydra and MLflow in your machine learning
|
67
|
+
projects with minimal setup.
|
57
68
|
|
58
69
|
## Installation
|
59
70
|
|
@@ -17,14 +17,23 @@
|
|
17
17
|
|
18
18
|
## Overview
|
19
19
|
|
20
|
-
Hydraflow is a powerful library designed to seamlessly integrate
|
20
|
+
Hydraflow is a powerful library designed to seamlessly integrate
|
21
|
+
[Hydra](https://hydra.cc/) and [MLflow](https://mlflow.org/), making it easier to
|
22
|
+
manage and track machine learning experiments. By combining the flexibility of
|
23
|
+
Hydra's configuration management with the robust experiment tracking capabilities
|
24
|
+
of MLflow, Hydraflow provides a comprehensive solution for managing complex
|
25
|
+
machine learning workflows.
|
21
26
|
|
22
27
|
## Key Features
|
23
28
|
|
24
|
-
- **Configuration Management**: Utilize Hydra's advanced configuration management
|
25
|
-
|
26
|
-
- **
|
27
|
-
|
29
|
+
- **Configuration Management**: Utilize Hydra's advanced configuration management
|
30
|
+
to handle complex parameter sweeps and experiment setups.
|
31
|
+
- **Experiment Tracking**: Leverage MLflow's tracking capabilities to log parameters,
|
32
|
+
metrics, and artifacts for each run.
|
33
|
+
- **Artifact Management**: Automatically log and manage artifacts, such as model
|
34
|
+
checkpoints and configuration files, with MLflow.
|
35
|
+
- **Seamless Integration**: Easily integrate Hydra and MLflow in your machine learning
|
36
|
+
projects with minimal setup.
|
28
37
|
|
29
38
|
## Installation
|
30
39
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "hydraflow"
|
7
|
-
version = "0.2.
|
7
|
+
version = "0.2.3"
|
8
8
|
description = "Hydraflow integrates Hydra and MLflow to manage and track machine learning experiments."
|
9
9
|
readme = "README.md"
|
10
10
|
license = "MIT"
|
@@ -19,10 +19,22 @@ classifiers = [
|
|
19
19
|
"Topic :: Software Development :: Documentation",
|
20
20
|
]
|
21
21
|
requires-python = ">=3.10"
|
22
|
-
dependencies = [
|
22
|
+
dependencies = [
|
23
|
+
"hydra-core>1.3",
|
24
|
+
"mlflow>2.15",
|
25
|
+
"setuptools",
|
26
|
+
"watchdog",
|
27
|
+
"watchfiles",
|
28
|
+
]
|
23
29
|
|
24
30
|
[project.optional-dependencies]
|
25
|
-
dev = [
|
31
|
+
dev = [
|
32
|
+
"pytest-asyncio",
|
33
|
+
"pytest-clarity",
|
34
|
+
"pytest-cov",
|
35
|
+
"pytest-randomly",
|
36
|
+
"pytest-xdist",
|
37
|
+
]
|
26
38
|
|
27
39
|
[project.urls]
|
28
40
|
Documentation = "https://github.com/daizutabi/hydraflow"
|
@@ -41,18 +53,13 @@ addopts = [
|
|
41
53
|
"--cov=hydraflow",
|
42
54
|
"--cov-report=lcov:lcov.info",
|
43
55
|
]
|
44
|
-
|
45
56
|
doctest_optionflags = ["NORMALIZE_WHITESPACE", "IGNORE_EXCEPTION_DETAIL"]
|
46
57
|
filterwarnings = ['ignore:pkg_resources is deprecated:DeprecationWarning']
|
58
|
+
asyncio_default_fixture_loop_scope = "function"
|
47
59
|
|
48
60
|
[tool.coverage.report]
|
49
61
|
exclude_lines = ["no cov", "raise NotImplementedError", "if TYPE_CHECKING:"]
|
50
62
|
|
51
|
-
[tool.hatch.envs.docs.scripts]
|
52
|
-
build = "mkdocs build --clean --strict {args}"
|
53
|
-
serve = "mkdocs serve --dev-addr localhost:8000 {args}"
|
54
|
-
deploy = "mkdocs gh-deploy --force"
|
55
|
-
|
56
63
|
[tool.ruff]
|
57
64
|
line-length = 100
|
58
65
|
target-version = "py312"
|
@@ -2,25 +2,29 @@ from .context import Info, chdir_artifact, log_run, watch
|
|
2
2
|
from .mlflow import set_experiment
|
3
3
|
from .runs import (
|
4
4
|
Run,
|
5
|
-
|
5
|
+
RunCollection,
|
6
6
|
filter_runs,
|
7
7
|
get_param_dict,
|
8
8
|
get_param_names,
|
9
9
|
get_run,
|
10
|
+
list_runs,
|
10
11
|
load_config,
|
12
|
+
search_runs,
|
11
13
|
)
|
12
14
|
|
13
15
|
__all__ = [
|
14
16
|
"Info",
|
15
17
|
"Run",
|
16
|
-
"
|
18
|
+
"RunCollection",
|
17
19
|
"chdir_artifact",
|
18
20
|
"filter_runs",
|
19
21
|
"get_param_dict",
|
20
22
|
"get_param_names",
|
21
23
|
"get_run",
|
24
|
+
"list_runs",
|
22
25
|
"load_config",
|
23
26
|
"log_run",
|
27
|
+
"search_runs",
|
24
28
|
"set_experiment",
|
25
29
|
"watch",
|
26
30
|
]
|
@@ -0,0 +1,199 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import logging
|
5
|
+
from asyncio.subprocess import PIPE
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import TYPE_CHECKING
|
8
|
+
|
9
|
+
import watchfiles
|
10
|
+
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
from asyncio.streams import StreamReader
|
13
|
+
from collections.abc import Callable
|
14
|
+
|
15
|
+
from watchfiles import Change
|
16
|
+
|
17
|
+
|
18
|
+
# Set up logging
|
19
|
+
logging.basicConfig(level=logging.INFO)
|
20
|
+
logger = logging.getLogger(__name__)
|
21
|
+
|
22
|
+
|
23
|
+
async def execute_command(
|
24
|
+
program: str,
|
25
|
+
*args: str,
|
26
|
+
stdout: Callable[[str], None] | None = None,
|
27
|
+
stderr: Callable[[str], None] | None = None,
|
28
|
+
stop_event: asyncio.Event,
|
29
|
+
) -> int:
|
30
|
+
"""
|
31
|
+
Runs a command asynchronously and pass the output to callback functions.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
program (str): The program to run.
|
35
|
+
*args (str): Arguments for the program.
|
36
|
+
stdout (Callable[[str], None] | None): Callback for standard output.
|
37
|
+
stderr (Callable[[str], None] | None): Callback for standard error.
|
38
|
+
stop_event (asyncio.Event): Event to signal when the process is done.
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
int: The return code of the process.
|
42
|
+
"""
|
43
|
+
try:
|
44
|
+
process = await asyncio.create_subprocess_exec(program, *args, stdout=PIPE, stderr=PIPE)
|
45
|
+
await asyncio.gather(
|
46
|
+
process_stream(process.stdout, stdout),
|
47
|
+
process_stream(process.stderr, stderr),
|
48
|
+
)
|
49
|
+
returncode = await process.wait()
|
50
|
+
|
51
|
+
except Exception as e:
|
52
|
+
logger.error(f"Error running command: {e}")
|
53
|
+
returncode = 1
|
54
|
+
|
55
|
+
finally:
|
56
|
+
stop_event.set()
|
57
|
+
|
58
|
+
return returncode
|
59
|
+
|
60
|
+
|
61
|
+
async def process_stream(
|
62
|
+
stream: StreamReader | None,
|
63
|
+
callback: Callable[[str], None] | None,
|
64
|
+
) -> None:
|
65
|
+
"""
|
66
|
+
Reads a stream asynchronously and pass each line to a callback function.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
stream (StreamReader | None): The stream to read from.
|
70
|
+
callback (Callable[[str], None] | None): The callback function to handle
|
71
|
+
each line.
|
72
|
+
"""
|
73
|
+
if stream is None or callback is None:
|
74
|
+
return
|
75
|
+
|
76
|
+
while True:
|
77
|
+
line = await stream.readline()
|
78
|
+
if line:
|
79
|
+
callback(line.decode().strip())
|
80
|
+
else:
|
81
|
+
break
|
82
|
+
|
83
|
+
|
84
|
+
async def monitor_file_changes(
|
85
|
+
paths: list[str | Path],
|
86
|
+
callback: Callable[[set[tuple[Change, str]]], None],
|
87
|
+
stop_event: asyncio.Event,
|
88
|
+
**awatch_kwargs,
|
89
|
+
) -> None:
|
90
|
+
"""
|
91
|
+
Watches for file changes in specified paths and pass the changes to a
|
92
|
+
callback function.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
paths (list[str | Path]): List of paths to monitor for changes.
|
96
|
+
callback (Callable[[set[tuple[Change, str]]], None]): The callback
|
97
|
+
function to handle file changes.
|
98
|
+
stop_event (asyncio.Event): Event to signal when to stop watching.
|
99
|
+
**awatch_kwargs: Additional keyword arguments to pass to watchfiles.awatch.
|
100
|
+
"""
|
101
|
+
str_paths = [str(path) for path in paths]
|
102
|
+
try:
|
103
|
+
async for changes in watchfiles.awatch(*str_paths, stop_event=stop_event, **awatch_kwargs):
|
104
|
+
callback(changes)
|
105
|
+
except Exception as e:
|
106
|
+
logger.error(f"Error watching files: {e}")
|
107
|
+
|
108
|
+
|
109
|
+
async def run_and_monitor(
|
110
|
+
program: str,
|
111
|
+
*args: str,
|
112
|
+
stdout: Callable[[str], None] | None = None,
|
113
|
+
stderr: Callable[[str], None] | None = None,
|
114
|
+
watch: Callable[[set[tuple[Change, str]]], None] | None = None,
|
115
|
+
paths: list[str | Path] | None = None,
|
116
|
+
**awatch_kwargs,
|
117
|
+
) -> int:
|
118
|
+
"""
|
119
|
+
Runs a command and optionally watch for file changes concurrently.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
program (str): The program to run.
|
123
|
+
*args (str): Arguments for the program.
|
124
|
+
stdout (Callable[[str], None] | None): Callback for standard output.
|
125
|
+
stderr (Callable[[str], None] | None): Callback for standard error.
|
126
|
+
watch (Callable[[set[tuple[Change, str]]], None] | None): Callback for
|
127
|
+
file changes.
|
128
|
+
paths (list[str | Path] | None): List of paths to monitor for changes.
|
129
|
+
"""
|
130
|
+
stop_event = asyncio.Event()
|
131
|
+
run_task = asyncio.create_task(
|
132
|
+
execute_command(program, *args, stop_event=stop_event, stdout=stdout, stderr=stderr)
|
133
|
+
)
|
134
|
+
if watch and paths:
|
135
|
+
monitor_task = asyncio.create_task(
|
136
|
+
monitor_file_changes(paths, watch, stop_event, **awatch_kwargs)
|
137
|
+
)
|
138
|
+
else:
|
139
|
+
monitor_task = None
|
140
|
+
|
141
|
+
try:
|
142
|
+
if monitor_task:
|
143
|
+
await asyncio.gather(run_task, monitor_task)
|
144
|
+
else:
|
145
|
+
await run_task
|
146
|
+
|
147
|
+
except Exception as e:
|
148
|
+
logger.error(f"Error in run_and_monitor: {e}")
|
149
|
+
finally:
|
150
|
+
stop_event.set()
|
151
|
+
await run_task
|
152
|
+
if monitor_task:
|
153
|
+
await monitor_task
|
154
|
+
|
155
|
+
return run_task.result()
|
156
|
+
|
157
|
+
|
158
|
+
def run(
|
159
|
+
program: str,
|
160
|
+
*args: str,
|
161
|
+
stdout: Callable[[str], None] | None = None,
|
162
|
+
stderr: Callable[[str], None] | None = None,
|
163
|
+
watch: Callable[[set[tuple[Change, str]]], None] | None = None,
|
164
|
+
paths: list[str | Path] | None = None,
|
165
|
+
**awatch_kwargs,
|
166
|
+
) -> int:
|
167
|
+
"""
|
168
|
+
Run a command synchronously and optionally watch for file changes.
|
169
|
+
|
170
|
+
This function is a synchronous wrapper around the asynchronous `run_and_monitor` function.
|
171
|
+
It runs a specified command and optionally monitors specified paths for file changes,
|
172
|
+
invoking the provided callbacks for standard output, standard error, and file changes.
|
173
|
+
|
174
|
+
Args:
|
175
|
+
program (str): The program to run.
|
176
|
+
*args (str): Arguments for the program.
|
177
|
+
stdout (Callable[[str], None] | None): Callback for handling standard output lines.
|
178
|
+
stderr (Callable[[str], None] | None): Callback for handling standard error lines.
|
179
|
+
watch (Callable[[set[tuple[Change, str]]], None] | None): Callback for handling file changes.
|
180
|
+
paths (list[str | Path] | None): List of paths to monitor for file changes.
|
181
|
+
**awatch_kwargs: Additional keyword arguments to pass to `watchfiles.awatch`.
|
182
|
+
|
183
|
+
Returns:
|
184
|
+
int: The return code of the process.
|
185
|
+
"""
|
186
|
+
if watch and not paths:
|
187
|
+
paths = [Path.cwd()]
|
188
|
+
|
189
|
+
return asyncio.run(
|
190
|
+
run_and_monitor(
|
191
|
+
program,
|
192
|
+
*args,
|
193
|
+
stdout=stdout,
|
194
|
+
stderr=stderr,
|
195
|
+
watch=watch,
|
196
|
+
paths=paths,
|
197
|
+
**awatch_kwargs,
|
198
|
+
)
|
199
|
+
)
|
@@ -30,6 +30,9 @@ def iter_params(config: object, prefix: str = "") -> Iterator[tuple[str, Any]]:
|
|
30
30
|
Yields:
|
31
31
|
Key-value pairs representing the parameters in the configuration object.
|
32
32
|
"""
|
33
|
+
if config is None:
|
34
|
+
return
|
35
|
+
|
33
36
|
if not isinstance(config, (DictConfig, ListConfig)):
|
34
37
|
config = OmegaConf.create(config) # type: ignore
|
35
38
|
|