hydraflow 0.3.0__py3-none-any.whl → 0.3.2__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- hydraflow/__init__.py +5 -9
- hydraflow/config.py +10 -2
- hydraflow/context.py +19 -0
- hydraflow/mlflow.py +8 -2
- hydraflow/param.py +1 -1
- hydraflow/run_collection.py +17 -13
- hydraflow/run_data.py +4 -26
- hydraflow/run_info.py +4 -61
- hydraflow/utils.py +88 -0
- {hydraflow-0.3.0.dist-info → hydraflow-0.3.2.dist-info}/METADATA +1 -1
- hydraflow-0.3.2.dist-info/RECORD +16 -0
- hydraflow-0.3.0.dist-info/RECORD +0 -15
- {hydraflow-0.3.0.dist-info → hydraflow-0.3.2.dist-info}/WHEEL +0 -0
- {hydraflow-0.3.0.dist-info → hydraflow-0.3.2.dist-info}/licenses/LICENSE +0 -0
hydraflow/__init__.py
CHANGED
@@ -1,19 +1,15 @@
|
|
1
|
-
"""
|
1
|
+
"""Integrate Hydra and MLflow to manage and track machine learning experiments."""
|
2
2
|
|
3
|
-
from .context import chdir_artifact, log_run, start_run, watch
|
4
|
-
from .mlflow import
|
5
|
-
list_runs,
|
6
|
-
search_runs,
|
7
|
-
set_experiment,
|
8
|
-
)
|
3
|
+
from .context import chdir_artifact, chdir_hydra, log_run, start_run, watch
|
4
|
+
from .mlflow import list_runs, search_runs, set_experiment
|
9
5
|
from .progress import multi_tasks_progress, parallel_progress
|
10
6
|
from .run_collection import RunCollection
|
11
|
-
from .
|
12
|
-
from .run_info import get_artifact_dir, get_hydra_output_dir
|
7
|
+
from .utils import get_artifact_dir, get_hydra_output_dir, load_config
|
13
8
|
|
14
9
|
__all__ = [
|
15
10
|
"RunCollection",
|
16
11
|
"chdir_artifact",
|
12
|
+
"chdir_hydra",
|
17
13
|
"get_artifact_dir",
|
18
14
|
"get_hydra_output_dir",
|
19
15
|
"list_runs",
|
hydraflow/config.py
CHANGED
@@ -54,7 +54,7 @@ def _iter_params(config: object, prefix: str = "") -> Iterator[tuple[str, Any]]:
|
|
54
54
|
if isinstance(config, DictConfig):
|
55
55
|
for key, value in config.items():
|
56
56
|
if _is_param(value):
|
57
|
-
yield f"{prefix}{key}", value
|
57
|
+
yield f"{prefix}{key}", _convert(value)
|
58
58
|
|
59
59
|
else:
|
60
60
|
yield from _iter_params(value, f"{prefix}{key}.")
|
@@ -62,7 +62,7 @@ def _iter_params(config: object, prefix: str = "") -> Iterator[tuple[str, Any]]:
|
|
62
62
|
elif isinstance(config, ListConfig):
|
63
63
|
for index, value in enumerate(config):
|
64
64
|
if _is_param(value):
|
65
|
-
yield f"{prefix}{index}", value
|
65
|
+
yield f"{prefix}{index}", _convert(value)
|
66
66
|
|
67
67
|
else:
|
68
68
|
yield from _iter_params(value, f"{prefix}{index}.")
|
@@ -78,3 +78,11 @@ def _is_param(value: object) -> bool:
|
|
78
78
|
return False
|
79
79
|
|
80
80
|
return True
|
81
|
+
|
82
|
+
|
83
|
+
def _convert(value: Any) -> Any:
|
84
|
+
"""Convert the given value to a Python object."""
|
85
|
+
if isinstance(value, ListConfig):
|
86
|
+
return list(value)
|
87
|
+
|
88
|
+
return value
|
hydraflow/context.py
CHANGED
@@ -238,6 +238,25 @@ class Handler(PatternMatchingEventHandler):
|
|
238
238
|
self.func(file)
|
239
239
|
|
240
240
|
|
241
|
+
@contextmanager
|
242
|
+
def chdir_hydra() -> Iterator[Path]:
|
243
|
+
"""Change the current working directory to the hydra output directory.
|
244
|
+
|
245
|
+
This context manager changes the current working directory to the hydra output
|
246
|
+
directory. It ensures that the directory is changed back to the original
|
247
|
+
directory after the context is exited.
|
248
|
+
"""
|
249
|
+
curdir = Path.cwd()
|
250
|
+
path = HydraConfig.get().runtime.output_dir
|
251
|
+
|
252
|
+
os.chdir(path)
|
253
|
+
try:
|
254
|
+
yield Path(path)
|
255
|
+
|
256
|
+
finally:
|
257
|
+
os.chdir(curdir)
|
258
|
+
|
259
|
+
|
241
260
|
@contextmanager
|
242
261
|
def chdir_artifact(
|
243
262
|
run: Run,
|
hydraflow/mlflow.py
CHANGED
@@ -207,8 +207,14 @@ def _list_runs(
|
|
207
207
|
if experiment := mlflow.get_experiment_by_name(name):
|
208
208
|
loc = experiment.artifact_location
|
209
209
|
|
210
|
-
if isinstance(loc, str)
|
211
|
-
|
210
|
+
if isinstance(loc, str):
|
211
|
+
if loc.startswith("file://"):
|
212
|
+
path = Path(mlflow.artifacts.download_artifacts(loc))
|
213
|
+
elif Path(loc).is_dir():
|
214
|
+
path = Path(loc)
|
215
|
+
else:
|
216
|
+
continue
|
217
|
+
|
212
218
|
run_ids.extend(file.stem for file in path.iterdir() if file.is_dir())
|
213
219
|
|
214
220
|
it = (joblib.delayed(mlflow.get_run)(run_id) for run_id in run_ids)
|
hydraflow/param.py
CHANGED
hydraflow/run_collection.py
CHANGED
@@ -239,8 +239,8 @@ class RunCollection:
|
|
239
239
|
The filtering supports:
|
240
240
|
- Exact matches for single values.
|
241
241
|
- Membership checks for lists of values.
|
242
|
-
- Range checks for tuples of two values (inclusive of the lower
|
243
|
-
and
|
242
|
+
- Range checks for tuples of two values (inclusive of both the lower
|
243
|
+
and upper bound).
|
244
244
|
|
245
245
|
Args:
|
246
246
|
config (object | None): The configuration object to filter the runs.
|
@@ -476,7 +476,7 @@ class RunCollection:
|
|
476
476
|
"""
|
477
477
|
return (func(run, *args, **kwargs) for run in self)
|
478
478
|
|
479
|
-
def
|
479
|
+
def map_id(
|
480
480
|
self,
|
481
481
|
func: Callable[Concatenate[str, P], T],
|
482
482
|
*args: P.args,
|
@@ -569,8 +569,8 @@ class RunCollection:
|
|
569
569
|
|
570
570
|
def group_by(
|
571
571
|
self,
|
572
|
-
|
573
|
-
) -> dict[tuple[str | None, ...], RunCollection]:
|
572
|
+
names: str | list[str],
|
573
|
+
) -> dict[str | None | tuple[str | None, ...], RunCollection]:
|
574
574
|
"""Group runs by specified parameter names.
|
575
575
|
|
576
576
|
Group the runs in the collection based on the values of the
|
@@ -578,19 +578,23 @@ class RunCollection:
|
|
578
578
|
form a key in the returned dictionary.
|
579
579
|
|
580
580
|
Args:
|
581
|
-
|
581
|
+
names (str | list[str]): The names of the parameters to group by.
|
582
582
|
This can be a single parameter name or multiple names provided
|
583
583
|
as separate arguments or as a list.
|
584
584
|
|
585
585
|
Returns:
|
586
|
-
dict[tuple[str | None, ...], RunCollection]: A
|
587
|
-
are tuples of parameter values and the
|
588
|
-
containing the runs that match
|
586
|
+
dict[str | None | tuple[str | None, ...], RunCollection]: A
|
587
|
+
dictionary where the keys are tuples of parameter values and the
|
588
|
+
values are `RunCollection` objects containing the runs that match
|
589
|
+
those parameter values.
|
589
590
|
|
590
591
|
"""
|
591
|
-
grouped_runs: dict[tuple[str | None, ...], list[Run]] = {}
|
592
|
+
grouped_runs: dict[str | None | tuple[str | None, ...], list[Run]] = {}
|
593
|
+
is_list = isinstance(names, list)
|
592
594
|
for run in self._runs:
|
593
|
-
key = get_params(run,
|
595
|
+
key = get_params(run, names)
|
596
|
+
if not is_list:
|
597
|
+
key = key[0]
|
594
598
|
grouped_runs.setdefault(key, []).append(run)
|
595
599
|
|
596
600
|
return {key: RunCollection(runs) for key, runs in grouped_runs.items()}
|
@@ -637,8 +641,8 @@ def filter_runs(
|
|
637
641
|
The filtering supports:
|
638
642
|
- Exact matches for single values.
|
639
643
|
- Membership checks for lists of values.
|
640
|
-
- Range checks for tuples of two values (inclusive of the lower
|
641
|
-
|
644
|
+
- Range checks for tuples of two values (inclusive of both the lower and
|
645
|
+
upper bound).
|
642
646
|
|
643
647
|
Args:
|
644
648
|
runs (list[Run]): The list of runs to filter.
|
hydraflow/run_data.py
CHANGED
@@ -1,21 +1,19 @@
|
|
1
|
-
"""Provide
|
1
|
+
"""Provide data about `RunCollection` instances."""
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
from typing import TYPE_CHECKING
|
6
6
|
|
7
|
-
from
|
8
|
-
|
9
|
-
from hydraflow.run_info import get_artifact_dir
|
7
|
+
from hydraflow.utils import load_config
|
10
8
|
|
11
9
|
if TYPE_CHECKING:
|
12
|
-
from
|
10
|
+
from omegaconf import DictConfig
|
13
11
|
|
14
12
|
from hydraflow.run_collection import RunCollection
|
15
13
|
|
16
14
|
|
17
15
|
class RunCollectionData:
|
18
|
-
"""Provide
|
16
|
+
"""Provide data about a `RunCollection` instance."""
|
19
17
|
|
20
18
|
def __init__(self, runs: RunCollection) -> None:
|
21
19
|
self._runs = runs
|
@@ -34,23 +32,3 @@ class RunCollectionData:
|
|
34
32
|
def config(self) -> list[DictConfig]:
|
35
33
|
"""Get the configuration for each run in the collection."""
|
36
34
|
return [load_config(run) for run in self._runs]
|
37
|
-
|
38
|
-
|
39
|
-
def load_config(run: Run) -> DictConfig:
|
40
|
-
"""Load the configuration for a given run.
|
41
|
-
|
42
|
-
This function loads the configuration for the provided Run instance
|
43
|
-
by downloading the configuration file from the MLflow artifacts and
|
44
|
-
loading it using OmegaConf. It returns an empty config if
|
45
|
-
`.hydra/config.yaml` is not found in the run's artifact directory.
|
46
|
-
|
47
|
-
Args:
|
48
|
-
run (Run): The Run instance for which to load the configuration.
|
49
|
-
|
50
|
-
Returns:
|
51
|
-
The loaded configuration as a DictConfig object. Returns an empty
|
52
|
-
DictConfig if the configuration file is not found.
|
53
|
-
|
54
|
-
"""
|
55
|
-
path = get_artifact_dir(run) / ".hydra/config.yaml"
|
56
|
-
return OmegaConf.load(path) # type: ignore
|
hydraflow/run_info.py
CHANGED
@@ -1,23 +1,19 @@
|
|
1
|
-
"""Provide information about
|
1
|
+
"""Provide information about `RunCollection` instances."""
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from pathlib import Path
|
6
5
|
from typing import TYPE_CHECKING
|
7
6
|
|
8
|
-
import
|
9
|
-
from hydra.core.hydra_config import HydraConfig
|
10
|
-
from mlflow.tracking import artifact_utils
|
11
|
-
from omegaconf import OmegaConf
|
7
|
+
from hydraflow.utils import get_artifact_dir
|
12
8
|
|
13
9
|
if TYPE_CHECKING:
|
14
|
-
from
|
10
|
+
from pathlib import Path
|
15
11
|
|
16
12
|
from hydraflow.run_collection import RunCollection
|
17
13
|
|
18
14
|
|
19
15
|
class RunCollectionInfo:
|
20
|
-
"""Provide information about
|
16
|
+
"""Provide information about a `RunCollection` instance."""
|
21
17
|
|
22
18
|
def __init__(self, runs: RunCollection) -> None:
|
23
19
|
self._runs = runs
|
@@ -36,56 +32,3 @@ class RunCollectionInfo:
|
|
36
32
|
def artifact_dir(self) -> list[Path]:
|
37
33
|
"""Get the artifact directory for each run in the collection."""
|
38
34
|
return [get_artifact_dir(run) for run in self._runs]
|
39
|
-
|
40
|
-
|
41
|
-
def get_artifact_dir(run: Run | None = None) -> Path:
|
42
|
-
"""Retrieve the artifact directory for the given run.
|
43
|
-
|
44
|
-
This function uses MLflow to get the artifact directory for the given run.
|
45
|
-
|
46
|
-
Args:
|
47
|
-
run (Run | None): The run object. Defaults to None.
|
48
|
-
|
49
|
-
Returns:
|
50
|
-
The local path to the directory where the artifacts are downloaded.
|
51
|
-
|
52
|
-
"""
|
53
|
-
if run is None:
|
54
|
-
uri = mlflow.get_artifact_uri()
|
55
|
-
else:
|
56
|
-
uri = artifact_utils.get_artifact_uri(run.info.run_id)
|
57
|
-
|
58
|
-
return Path(mlflow.artifacts.download_artifacts(uri))
|
59
|
-
|
60
|
-
|
61
|
-
def get_hydra_output_dir(run: Run | None = None) -> Path:
|
62
|
-
"""Retrieve the Hydra output directory for the given run.
|
63
|
-
|
64
|
-
This function returns the Hydra output directory. If no run is provided,
|
65
|
-
it retrieves the output directory from the current Hydra configuration.
|
66
|
-
If a run is provided, it retrieves the artifact path for the run, loads
|
67
|
-
the Hydra configuration from the downloaded artifacts, and returns the
|
68
|
-
output directory specified in that configuration.
|
69
|
-
|
70
|
-
Args:
|
71
|
-
run (Run | None): The run object. Defaults to None.
|
72
|
-
|
73
|
-
Returns:
|
74
|
-
Path: The path to the Hydra output directory.
|
75
|
-
|
76
|
-
Raises:
|
77
|
-
FileNotFoundError: If the Hydra configuration file is not found
|
78
|
-
in the artifacts.
|
79
|
-
|
80
|
-
"""
|
81
|
-
if run is None:
|
82
|
-
hc = HydraConfig.get()
|
83
|
-
return Path(hc.runtime.output_dir)
|
84
|
-
|
85
|
-
path = get_artifact_dir(run) / ".hydra/hydra.yaml"
|
86
|
-
|
87
|
-
if path.exists():
|
88
|
-
hc = OmegaConf.load(path)
|
89
|
-
return Path(hc.hydra.runtime.output_dir)
|
90
|
-
|
91
|
-
raise FileNotFoundError
|
hydraflow/utils.py
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
"""Provide utility functions for HydraFlow."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import TYPE_CHECKING
|
7
|
+
|
8
|
+
import mlflow
|
9
|
+
from hydra.core.hydra_config import HydraConfig
|
10
|
+
from mlflow.entities import Run
|
11
|
+
from mlflow.tracking import artifact_utils
|
12
|
+
from omegaconf import DictConfig, OmegaConf
|
13
|
+
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from mlflow.entities import Run
|
16
|
+
|
17
|
+
|
18
|
+
def get_artifact_dir(run: Run | None = None) -> Path:
|
19
|
+
"""Retrieve the artifact directory for the given run.
|
20
|
+
|
21
|
+
This function uses MLflow to get the artifact directory for the given run.
|
22
|
+
|
23
|
+
Args:
|
24
|
+
run (Run | None): The run object. Defaults to None.
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
The local path to the directory where the artifacts are downloaded.
|
28
|
+
|
29
|
+
"""
|
30
|
+
if run is None:
|
31
|
+
uri = mlflow.get_artifact_uri()
|
32
|
+
else:
|
33
|
+
uri = artifact_utils.get_artifact_uri(run.info.run_id)
|
34
|
+
|
35
|
+
return Path(mlflow.artifacts.download_artifacts(uri))
|
36
|
+
|
37
|
+
|
38
|
+
def get_hydra_output_dir(run: Run | None = None) -> Path:
|
39
|
+
"""Retrieve the Hydra output directory for the given run.
|
40
|
+
|
41
|
+
This function returns the Hydra output directory. If no run is provided,
|
42
|
+
it retrieves the output directory from the current Hydra configuration.
|
43
|
+
If a run is provided, it retrieves the artifact path for the run, loads
|
44
|
+
the Hydra configuration from the downloaded artifacts, and returns the
|
45
|
+
output directory specified in that configuration.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
run (Run | None): The run object. Defaults to None.
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
Path: The path to the Hydra output directory.
|
52
|
+
|
53
|
+
Raises:
|
54
|
+
FileNotFoundError: If the Hydra configuration file is not found
|
55
|
+
in the artifacts.
|
56
|
+
|
57
|
+
"""
|
58
|
+
if run is None:
|
59
|
+
hc = HydraConfig.get()
|
60
|
+
return Path(hc.runtime.output_dir)
|
61
|
+
|
62
|
+
path = get_artifact_dir(run) / ".hydra/hydra.yaml"
|
63
|
+
|
64
|
+
if path.exists():
|
65
|
+
hc = OmegaConf.load(path)
|
66
|
+
return Path(hc.hydra.runtime.output_dir)
|
67
|
+
|
68
|
+
raise FileNotFoundError
|
69
|
+
|
70
|
+
|
71
|
+
def load_config(run: Run) -> DictConfig:
|
72
|
+
"""Load the configuration for a given run.
|
73
|
+
|
74
|
+
This function loads the configuration for the provided Run instance
|
75
|
+
by downloading the configuration file from the MLflow artifacts and
|
76
|
+
loading it using OmegaConf. It returns an empty config if
|
77
|
+
`.hydra/config.yaml` is not found in the run's artifact directory.
|
78
|
+
|
79
|
+
Args:
|
80
|
+
run (Run): The Run instance for which to load the configuration.
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
The loaded configuration as a DictConfig object. Returns an empty
|
84
|
+
DictConfig if the configuration file is not found.
|
85
|
+
|
86
|
+
"""
|
87
|
+
path = get_artifact_dir(run) / ".hydra/config.yaml"
|
88
|
+
return OmegaConf.load(path) # type: ignore
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: hydraflow
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.2
|
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
|
@@ -0,0 +1,16 @@
|
|
1
|
+
hydraflow/__init__.py,sha256=6sfM1ashUkfrNf7lOR7raFYhG8YdOAJR-JgRNL_IVo8,698
|
2
|
+
hydraflow/asyncio.py,sha256=-i1C8KAmNDImrjHnk92Csaa1mpjdK8Vp4ZVaQV-l94s,6634
|
3
|
+
hydraflow/config.py,sha256=6V5omJ3-h9-ZwVpM5rTA4FqE_mu8urTy9OqV4zG79gw,2671
|
4
|
+
hydraflow/context.py,sha256=412884e84qIEYtbxJT4roYsKfldGaTKzgo6Q1FAsT5U,8733
|
5
|
+
hydraflow/mlflow.py,sha256=JELqXFCJ9MsEJaQWT5dyleTFk8BHL7cQwW_gzhkPoIg,8729
|
6
|
+
hydraflow/param.py,sha256=QkLeQvt5ZF3GyRGnhP66o0GElc1ZOOCxCL7PdyfIUbA,1939
|
7
|
+
hydraflow/progress.py,sha256=zvKX1HCN8_xDOsgYOEcLLhkhdPdep-U8vHrc0XZ-6SQ,6163
|
8
|
+
hydraflow/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
hydraflow/run_collection.py,sha256=0SCParNkSoXw7Ule-FH3az578Hhu-1TKGyrFNXxTciU,25082
|
10
|
+
hydraflow/run_data.py,sha256=ZXVr0PHyufH9wwyQYWtpE4_MheAC2ArTW_J1TTMQ4iI,983
|
11
|
+
hydraflow/run_info.py,sha256=sMXOo20ClaRIommMEzuAbO_OrcXx7M1Yt4FMV7spxz0,998
|
12
|
+
hydraflow/utils.py,sha256=aRdBdToKfvHhN2qFiRzPHIdQxS7cTpZREQeP8HreAfI,2676
|
13
|
+
hydraflow-0.3.2.dist-info/METADATA,sha256=RktfWMufNqrMU2CBSbmVxwE_e7VPbLhVYQHgBKXuh9Q,3840
|
14
|
+
hydraflow-0.3.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
15
|
+
hydraflow-0.3.2.dist-info/licenses/LICENSE,sha256=IGdDrBPqz1O0v_UwCW-NJlbX9Hy9b3uJ11t28y2srmY,1062
|
16
|
+
hydraflow-0.3.2.dist-info/RECORD,,
|
hydraflow-0.3.0.dist-info/RECORD
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
hydraflow/__init__.py,sha256=zlLTztJPXyBFJC5Z8G7_OnlfzAHJPRrfE1c2OoDvlTg,667
|
2
|
-
hydraflow/asyncio.py,sha256=-i1C8KAmNDImrjHnk92Csaa1mpjdK8Vp4ZVaQV-l94s,6634
|
3
|
-
hydraflow/config.py,sha256=Wx7jymwLVr5EfpzXBpvv3Ax3VhGhvWyA7Yy6EzsPYWk,2479
|
4
|
-
hydraflow/context.py,sha256=IaDy-ZCdCfWwv95S-gyQNp062oBdtSVaz6dxGmO6Y8w,8226
|
5
|
-
hydraflow/mlflow.py,sha256=GkOr_pXfpfY5USYBLrCigHcP13VgrAK_e9kheR1Wke4,8579
|
6
|
-
hydraflow/param.py,sha256=dvIXcKgc_MPiju3WEk9qz5FOUeK5qSj-YWN2ophCpUM,1938
|
7
|
-
hydraflow/progress.py,sha256=zvKX1HCN8_xDOsgYOEcLLhkhdPdep-U8vHrc0XZ-6SQ,6163
|
8
|
-
hydraflow/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
hydraflow/run_collection.py,sha256=Xv6-KD5ac-vv-4Q3PZrzJy1x84H_g7UoP7ZqZ8_DQeQ,24973
|
10
|
-
hydraflow/run_data.py,sha256=HgXGjV5oN6VxOAhrFRjubWz5ZiRqT1a2VdS5OcH2UQQ,1732
|
11
|
-
hydraflow/run_info.py,sha256=4QrTmyPEQ_PVn7JKXJIa9NkXGAdqh8k5Sue1ggQS5aQ,2678
|
12
|
-
hydraflow-0.3.0.dist-info/METADATA,sha256=DmC1Yjwuc3snUQiePCr5xvdtbfIevOapiA2sg8w6Aho,3840
|
13
|
-
hydraflow-0.3.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
14
|
-
hydraflow-0.3.0.dist-info/licenses/LICENSE,sha256=IGdDrBPqz1O0v_UwCW-NJlbX9Hy9b3uJ11t28y2srmY,1062
|
15
|
-
hydraflow-0.3.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|