hydraflow 0.3.0__py3-none-any.whl → 0.3.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/__init__.py CHANGED
@@ -1,19 +1,15 @@
1
- """Provide a collection of MLflow runs."""
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 .run_data import load_config
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) and loc.startswith("file://"):
211
- path = Path(mlflow.artifacts.download_artifacts(loc))
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
@@ -72,4 +72,4 @@ def _match_tuple(param: str, value: tuple) -> bool | None:
72
72
  if type(value[0]) is not type(value[1]):
73
73
  return None
74
74
 
75
- return value[0] <= type(value[0])(param) < value[1] # type: ignore
75
+ return value[0] <= type(value[0])(param) <= value[1] # type: ignore
@@ -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 bound
243
- and exclusive of the upper bound).
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 map_run_id(
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
- *names: str | list[str],
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
- *names (str | list[str]): The names of the parameters to group by.
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 dictionary where the keys
587
- are tuples of parameter values and the values are RunCollection objects
588
- containing the runs that match those parameter values.
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, *names)
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 bound and
641
- exclusive of the upper bound).
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 information about MLflow runs."""
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 omegaconf import DictConfig, OmegaConf
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 mlflow.entities import Run
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 information about MLflow runs."""
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 MLflow runs."""
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 mlflow
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 mlflow.entities import Run
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 MLflow runs."""
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.0
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,,
@@ -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,,