hydraflow 0.2.17__py3-none-any.whl → 0.2.18__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
hydraflow/asyncio.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Provide functionality for running commands and monitoring file changes."""
2
+
1
3
  from __future__ import annotations
2
4
 
3
5
  import asyncio
@@ -27,8 +29,7 @@ async def execute_command(
27
29
  stderr: Callable[[str], None] | None = None,
28
30
  stop_event: asyncio.Event,
29
31
  ) -> int:
30
- """
31
- Runs a command asynchronously and pass the output to callback functions.
32
+ """Run a command asynchronously and pass the output to callback functions.
32
33
 
33
34
  Args:
34
35
  program (str): The program to run.
@@ -39,6 +40,7 @@ async def execute_command(
39
40
 
40
41
  Returns:
41
42
  int: The return code of the process.
43
+
42
44
  """
43
45
  try:
44
46
  process = await asyncio.create_subprocess_exec(
@@ -68,13 +70,13 @@ async def process_stream(
68
70
  stream: StreamReader | None,
69
71
  callback: Callable[[str], None] | None,
70
72
  ) -> None:
71
- """
72
- Reads a stream asynchronously and pass each line to a callback function.
73
+ """Read a stream asynchronously and pass each line to a callback function.
73
74
 
74
75
  Args:
75
76
  stream (StreamReader | None): The stream to read from.
76
77
  callback (Callable[[str], None] | None): The callback function to handle
77
78
  each line.
79
+
78
80
  """
79
81
  if stream is None or callback is None:
80
82
  return
@@ -93,9 +95,7 @@ async def monitor_file_changes(
93
95
  stop_event: asyncio.Event,
94
96
  **awatch_kwargs,
95
97
  ) -> None:
96
- """
97
- Watches for file changes in specified paths and pass the changes to a
98
- callback function.
98
+ """Watch file changes in specified paths and pass the changes to a callback.
99
99
 
100
100
  Args:
101
101
  paths (list[str | Path]): List of paths to monitor for changes.
@@ -103,6 +103,7 @@ async def monitor_file_changes(
103
103
  function to handle file changes.
104
104
  stop_event (asyncio.Event): Event to signal when to stop watching.
105
105
  **awatch_kwargs: Additional keyword arguments to pass to watchfiles.awatch.
106
+
106
107
  """
107
108
  str_paths = [str(path) for path in paths]
108
109
  try:
@@ -127,8 +128,7 @@ async def run_and_monitor(
127
128
  paths: list[str | Path] | None = None,
128
129
  **awatch_kwargs,
129
130
  ) -> int:
130
- """
131
- Runs a command and optionally watch for file changes concurrently.
131
+ """Run a command and optionally watch for file changes concurrently.
132
132
 
133
133
  Args:
134
134
  program (str): The program to run.
@@ -138,6 +138,8 @@ async def run_and_monitor(
138
138
  watch (Callable[[set[tuple[Change, str]]], None] | None): Callback for
139
139
  file changes.
140
140
  paths (list[str | Path] | None): List of paths to monitor for changes.
141
+ **awatch_kwargs: Additional keyword arguments to pass to `watchfiles.awatch`.
142
+
141
143
  """
142
144
  stop_event = asyncio.Event()
143
145
  run_task = asyncio.create_task(
@@ -184,8 +186,7 @@ def run(
184
186
  paths: list[str | Path] | None = None,
185
187
  **awatch_kwargs,
186
188
  ) -> int:
187
- """
188
- Run a command synchronously and optionally watch for file changes.
189
+ """Run a command synchronously and optionally watch for file changes.
189
190
 
190
191
  This function is a synchronous wrapper around the asynchronous
191
192
  `run_and_monitor` function. It runs a specified command and optionally
@@ -208,6 +209,7 @@ def run(
208
209
 
209
210
  Returns:
210
211
  int: The return code of the process.
212
+
211
213
  """
212
214
  if watch and not paths:
213
215
  paths = [Path.cwd()]
hydraflow/config.py CHANGED
@@ -1,7 +1,4 @@
1
- """
2
- This module provides functionality for working with configuration
3
- objects using the OmegaConf library.
4
- """
1
+ """Provide functionality for working with configuration objects using the OmegaConf."""
5
2
 
6
3
  from __future__ import annotations
7
4
 
@@ -15,8 +12,7 @@ if TYPE_CHECKING:
15
12
 
16
13
 
17
14
  def iter_params(config: object, prefix: str = "") -> Iterator[tuple[str, Any]]:
18
- """
19
- Recursively iterate over the parameters in the given configuration object.
15
+ """Recursively iterate over the parameters in the given configuration object.
20
16
 
21
17
  This function traverses the configuration object and yields key-value pairs
22
18
  representing the parameters. The keys are prefixed with the provided prefix.
@@ -29,6 +25,7 @@ def iter_params(config: object, prefix: str = "") -> Iterator[tuple[str, Any]]:
29
25
 
30
26
  Yields:
31
27
  Key-value pairs representing the parameters in the configuration object.
28
+
32
29
  """
33
30
  if config is None:
34
31
  return
hydraflow/context.py CHANGED
@@ -1,7 +1,4 @@
1
- """
2
- This module provides context managers to log parameters and manage the MLflow
3
- run context.
4
- """
1
+ """Provide context managers to log parameters and manage the MLflow run context."""
5
2
 
6
3
  from __future__ import annotations
7
4
 
@@ -34,9 +31,7 @@ def log_run(
34
31
  *,
35
32
  synchronous: bool | None = None,
36
33
  ) -> Iterator[None]:
37
- """
38
- Log the parameters from the given configuration object and manage the MLflow
39
- run context.
34
+ """Log the parameters from the given configuration object.
40
35
 
41
36
  This context manager logs the parameters from the provided configuration object
42
37
  using MLflow. It also manages the MLflow run context, ensuring that artifacts
@@ -56,6 +51,7 @@ def log_run(
56
51
  # Perform operations within the MLflow run context
57
52
  pass
58
53
  ```
54
+
59
55
  """
60
56
  log_params(config, synchronous=synchronous)
61
57
 
@@ -98,8 +94,7 @@ def start_run( # noqa: PLR0913
98
94
  log_system_metrics: bool | None = None,
99
95
  synchronous: bool | None = None,
100
96
  ) -> Iterator[Run]:
101
- """
102
- Start an MLflow run and log parameters using the provided configuration object.
97
+ """Start an MLflow run and log parameters using the provided configuration object.
103
98
 
104
99
  This context manager starts an MLflow run and logs parameters using the specified
105
100
  configuration object. It ensures that the run is properly closed after completion.
@@ -130,6 +125,7 @@ def start_run( # noqa: PLR0913
130
125
  - `mlflow.start_run`: The MLflow function to start a run directly.
131
126
  - `log_run`: A context manager to log parameters and manage the MLflow
132
127
  run context.
128
+
133
129
  """
134
130
  with (
135
131
  mlflow.start_run(
@@ -156,9 +152,7 @@ def watch(
156
152
  ignore_patterns: list[str] | None = None,
157
153
  ignore_log: bool = True,
158
154
  ) -> Iterator[None]:
159
- """
160
- Watch the given directory for changes and call the provided function
161
- when a change is detected.
155
+ """Watch the given directory for changes.
162
156
 
163
157
  This context manager sets up a file system watcher on the specified directory.
164
158
  When a file modification is detected, the provided function is called with
@@ -173,6 +167,9 @@ def watch(
173
167
  the current MLflow artifact URI is used. Defaults to "".
174
168
  timeout (int): The timeout period in seconds for the watcher
175
169
  to run after the context is exited. Defaults to 60.
170
+ ignore_patterns (list[str] | None): A list of glob patterns to ignore.
171
+ Defaults to None.
172
+ ignore_log (bool): Whether to ignore log files. Defaults to True.
176
173
 
177
174
  Yields:
178
175
  None
@@ -183,6 +180,7 @@ def watch(
183
180
  # Perform operations while watching the directory for changes
184
181
  pass
185
182
  ```
183
+
186
184
  """
187
185
  dir = dir or get_artifact_dir() # noqa: A001
188
186
  if isinstance(dir, Path):
@@ -214,6 +212,8 @@ def watch(
214
212
 
215
213
 
216
214
  class Handler(PatternMatchingEventHandler):
215
+ """Monitor file changes and call the given function when a change is detected."""
216
+
217
217
  def __init__(
218
218
  self,
219
219
  func: Callable[[Path], None],
@@ -232,6 +232,7 @@ class Handler(PatternMatchingEventHandler):
232
232
  super().__init__(ignore_patterns=ignore_patterns)
233
233
 
234
234
  def on_modified(self, event: FileModifiedEvent) -> None:
235
+ """Modify when a file is modified."""
235
236
  file = Path(str(event.src_path))
236
237
  if file.is_file():
237
238
  self.func(file)
@@ -242,9 +243,7 @@ def chdir_artifact(
242
243
  run: Run,
243
244
  artifact_path: str | None = None,
244
245
  ) -> Iterator[Path]:
245
- """
246
- Change the current working directory to the artifact directory of the
247
- given run.
246
+ """Change the current working directory to the artifact directory of the given run.
248
247
 
249
248
  This context manager changes the current working directory to the artifact
250
249
  directory of the given run. It ensures that the directory is changed back
@@ -253,6 +252,7 @@ def chdir_artifact(
253
252
  Args:
254
253
  run (Run): The run to get the artifact directory from.
255
254
  artifact_path (str | None): The artifact path.
255
+
256
256
  """
257
257
  curdir = Path.cwd()
258
258
  path = mlflow.artifacts.download_artifacts(
hydraflow/info.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Provide information about MLflow runs."""
2
+
1
3
  from __future__ import annotations
2
4
 
3
5
  from pathlib import Path
@@ -15,37 +17,44 @@ if TYPE_CHECKING:
15
17
 
16
18
 
17
19
  class RunCollectionInfo:
20
+ """Provide information about MLflow runs."""
21
+
18
22
  def __init__(self, runs: RunCollection) -> None:
19
23
  self._runs = runs
20
24
 
21
25
  @property
22
26
  def run_id(self) -> list[str]:
27
+ """Get the run ID for each run in the collection."""
23
28
  return [run.info.run_id for run in self._runs]
24
29
 
25
30
  @property
26
31
  def params(self) -> list[dict[str, str]]:
32
+ """Get the parameters for each run in the collection."""
27
33
  return [run.data.params for run in self._runs]
28
34
 
29
35
  @property
30
36
  def metrics(self) -> list[dict[str, float]]:
37
+ """Get the metrics for each run in the collection."""
31
38
  return [run.data.metrics for run in self._runs]
32
39
 
33
40
  @property
34
41
  def artifact_uri(self) -> list[str | None]:
42
+ """Get the artifact URI for each run in the collection."""
35
43
  return [run.info.artifact_uri for run in self._runs]
36
44
 
37
45
  @property
38
46
  def artifact_dir(self) -> list[Path]:
47
+ """Get the artifact directory for each run in the collection."""
39
48
  return [get_artifact_dir(run) for run in self._runs]
40
49
 
41
50
  @property
42
51
  def config(self) -> list[DictConfig]:
52
+ """Get the configuration for each run in the collection."""
43
53
  return [load_config(run) for run in self._runs]
44
54
 
45
55
 
46
56
  def get_artifact_dir(run: Run | None = None) -> Path:
47
- """
48
- Retrieve the artifact directory for the given run.
57
+ """Retrieve the artifact directory for the given run.
49
58
 
50
59
  This function uses MLflow to get the artifact directory for the given run.
51
60
 
@@ -54,6 +63,7 @@ def get_artifact_dir(run: Run | None = None) -> Path:
54
63
 
55
64
  Returns:
56
65
  The local path to the directory where the artifacts are downloaded.
66
+
57
67
  """
58
68
  if run is None:
59
69
  uri = mlflow.get_artifact_uri()
@@ -64,8 +74,7 @@ def get_artifact_dir(run: Run | None = None) -> Path:
64
74
 
65
75
 
66
76
  def get_hydra_output_dir(run: Run | None = None) -> Path:
67
- """
68
- Retrieve the Hydra output directory for the given run.
77
+ """Retrieve the Hydra output directory for the given run.
69
78
 
70
79
  This function returns the Hydra output directory. If no run is provided,
71
80
  it retrieves the output directory from the current Hydra configuration.
@@ -82,6 +91,7 @@ def get_hydra_output_dir(run: Run | None = None) -> Path:
82
91
  Raises:
83
92
  FileNotFoundError: If the Hydra configuration file is not found
84
93
  in the artifacts.
94
+
85
95
  """
86
96
  if run is None:
87
97
  hc = HydraConfig.get()
@@ -97,8 +107,7 @@ def get_hydra_output_dir(run: Run | None = None) -> Path:
97
107
 
98
108
 
99
109
  def load_config(run: Run) -> DictConfig:
100
- """
101
- Load the configuration for a given run.
110
+ """Load the configuration for a given run.
102
111
 
103
112
  This function loads the configuration for the provided Run instance
104
113
  by downloading the configuration file from the MLflow artifacts and
@@ -111,6 +120,7 @@ def load_config(run: Run) -> DictConfig:
111
120
  Returns:
112
121
  The loaded configuration as a DictConfig object. Returns an empty
113
122
  DictConfig if the configuration file is not found.
123
+
114
124
  """
115
125
  path = get_artifact_dir(run) / ".hydra/config.yaml"
116
126
  return OmegaConf.load(path) # type: ignore
hydraflow/mlflow.py CHANGED
@@ -1,20 +1,17 @@
1
- """
2
- This module provides functionality to log parameters from Hydra configuration objects
3
- and set up experiments using MLflow. It includes methods for managing experiments,
4
- searching for runs, and logging parameters and artifacts.
1
+ """Provide functionality to log parameters from Hydra configuration objects.
2
+
3
+ This module provides functions to log parameters from Hydra configuration objects
4
+ to MLflow, set experiments, and manage tracking URIs. It integrates Hydra's
5
+ configuration management with MLflow's experiment tracking capabilities.
5
6
 
6
7
  Key Features:
7
- - **Experiment Management**: Set and manage MLflow experiments with customizable names
8
- based on Hydra configuration.
9
- - **Run Logging**: Log parameters and metrics from Hydra configuration objects to
10
- MLflow, ensuring that all relevant information is captured during experiments.
11
- - **Run Search**: Search for runs based on various criteria, allowing for flexible
12
- retrieval of experiment results.
13
- - **Artifact Management**: Retrieve and log artifacts associated with runs, facilitating
14
- easy access to outputs generated during experiments.
15
-
16
- This module is designed to integrate seamlessly with Hydra, providing a robust
17
- solution for tracking machine learning experiments and their associated metadata.
8
+ - **Experiment Management**: Set experiment names and tracking URIs using Hydra
9
+ configuration details.
10
+ - **Parameter Logging**: Log parameters from Hydra configuration objects to MLflow,
11
+ supporting both synchronous and asynchronous logging.
12
+ - **Run Collection**: Utilize the `RunCollection` class to manage and interact with
13
+ multiple MLflow runs, providing methods to filter and retrieve runs based on
14
+ various criteria.
18
15
  """
19
16
 
20
17
  from __future__ import annotations
@@ -40,8 +37,7 @@ def set_experiment(
40
37
  suffix: str = "",
41
38
  uri: str | Path | None = None,
42
39
  ) -> Experiment:
43
- """
44
- Sets the experiment name and tracking URI optionally.
40
+ """Set the experiment name and tracking URI optionally.
45
41
 
46
42
  This function sets the experiment name by combining the given prefix,
47
43
  the job name from HydraConfig, and the given suffix. Optionally, it can
@@ -55,6 +51,7 @@ def set_experiment(
55
51
  Returns:
56
52
  Experiment: An instance of `mlflow.entities.Experiment` representing
57
53
  the new active experiment.
54
+
58
55
  """
59
56
  if uri is not None:
60
57
  mlflow.set_tracking_uri(uri)
@@ -65,8 +62,7 @@ def set_experiment(
65
62
 
66
63
 
67
64
  def log_params(config: object, *, synchronous: bool | None = None) -> None:
68
- """
69
- Log the parameters from the given configuration object.
65
+ """Log the parameters from the given configuration object.
70
66
 
71
67
  This method logs the parameters from the provided configuration object
72
68
  using MLflow. It iterates over the parameters and logs them using the
@@ -76,6 +72,7 @@ def log_params(config: object, *, synchronous: bool | None = None) -> None:
76
72
  config (object): The configuration object to log the parameters from.
77
73
  synchronous (bool | None): Whether to log the parameters synchronously.
78
74
  Defaults to None.
75
+
79
76
  """
80
77
  for key, value in iter_params(config):
81
78
  mlflow.log_param(key, value, synchronous=synchronous)
@@ -91,8 +88,7 @@ def search_runs( # noqa: PLR0913
91
88
  search_all_experiments: bool = False,
92
89
  experiment_names: list[str] | None = None,
93
90
  ) -> RunCollection:
94
- """
95
- Search for Runs that fit the specified criteria.
91
+ """Search for Runs that fit the specified criteria.
96
92
 
97
93
  This function wraps the `mlflow.search_runs` function and returns the
98
94
  results as a `RunCollection` object. It allows for flexible searching of
@@ -133,6 +129,7 @@ def search_runs( # noqa: PLR0913
133
129
 
134
130
  Returns:
135
131
  A `RunCollection` object containing the search results.
132
+
136
133
  """
137
134
  runs = mlflow.search_runs(
138
135
  experiment_ids=experiment_ids,
@@ -151,9 +148,9 @@ def search_runs( # noqa: PLR0913
151
148
  def list_runs(
152
149
  experiment_names: str | list[str] | None = None,
153
150
  n_jobs: int = 0,
151
+ status: str | list[str] | int | list[int] | None = None,
154
152
  ) -> RunCollection:
155
- """
156
- List all runs for the specified experiments.
153
+ """List all runs for the specified experiments.
157
154
 
158
155
  This function retrieves all runs for the given list of experiment names.
159
156
  If no experiment names are provided (None), it defaults to searching all runs
@@ -169,11 +166,27 @@ def list_runs(
169
166
  for runs. If None or an empty list is provided, the function will
170
167
  search the currently active experiment or all experiments except
171
168
  the "Default" experiment.
169
+ n_jobs (int): The number of jobs to run in parallel. If 0, the function
170
+ will search runs sequentially.
171
+ status (str | list[str] | int | list[int] | None): The status of the runs
172
+ to filter.
172
173
 
173
174
  Returns:
174
175
  RunCollection: A `RunCollection` instance containing the runs for the
175
176
  specified experiments.
177
+
176
178
  """
179
+ rc = _list_runs(experiment_names, n_jobs)
180
+ if status is None:
181
+ return rc
182
+
183
+ return rc.filter(status=status)
184
+
185
+
186
+ def _list_runs(
187
+ experiment_names: str | list[str] | None = None,
188
+ n_jobs: int = 0,
189
+ ) -> RunCollection:
177
190
  if isinstance(experiment_names, str):
178
191
  experiment_names = [experiment_names]
179
192
 
hydraflow/param.py CHANGED
@@ -1,3 +1,13 @@
1
+ """Provide utility functions for parameter matching.
2
+
3
+ The main function `match` checks if a given parameter matches a specified value.
4
+ It supports various types of values including None, boolean, list, tuple, int,
5
+ float, and str.
6
+
7
+ Helper functions `_match_list` and `_match_tuple` are used internally to handle
8
+ matching for list and tuple types respectively.
9
+ """
10
+
1
11
  from __future__ import annotations
2
12
 
3
13
  from typing import Any
@@ -13,6 +23,7 @@ def match(param: str, value: Any) -> bool:
13
23
  Returns:
14
24
  True if the parameter matches the specified value,
15
25
  False otherwise.
26
+
16
27
  """
17
28
  if value in [None, True, False]:
18
29
  return param == str(value)
hydraflow/progress.py CHANGED
@@ -1,18 +1,7 @@
1
- """
2
- Module for managing progress tracking in parallel processing using Joblib
3
- and Rich's Progress bar.
1
+ """Context managers and functions for parallel task execution with progress.
4
2
 
5
3
  Provide context managers and functions to facilitate the execution
6
4
  of tasks in parallel while displaying progress updates.
7
-
8
- The following key components are provided:
9
-
10
- - JoblibProgress: A context manager for tracking progress with Rich's progress
11
- bar.
12
- - parallel_progress: A function to execute a given function in parallel over
13
- an iterable with progress tracking.
14
- - multi_tasks_progress: A function to render auto-updating progress bars for
15
- multiple tasks concurrently.
16
5
  """
17
6
 
18
7
  from __future__ import annotations
@@ -37,8 +26,7 @@ def JoblibProgress( # noqa: N802
37
26
  total: int | None = None,
38
27
  **kwargs,
39
28
  ) -> Iterator[Progress]:
40
- """
41
- Context manager for tracking progress using Joblib with Rich's Progress bar.
29
+ """Context manager for tracking progress using Joblib with Rich's Progress bar.
42
30
 
43
31
  Args:
44
32
  *columns (ProgressColumn | str): Columns to display in the progress bar.
@@ -56,6 +44,7 @@ def JoblibProgress( # noqa: N802
56
44
  with JoblibProgress("task", total=100) as progress:
57
45
  # Your parallel processing code here
58
46
  ```
47
+
59
48
  """
60
49
  if not columns:
61
50
  columns = Progress.get_default_columns()
@@ -94,8 +83,7 @@ def parallel_progress(
94
83
  description: str | None = None,
95
84
  **kwargs,
96
85
  ) -> list[U]:
97
- """
98
- Execute a function in parallel over an iterable with progress tracking.
86
+ """Execute a function in parallel over an iterable with progress tracking.
99
87
 
100
88
  Args:
101
89
  func (Callable[[T], U]): The function to execute on each item in the
@@ -112,6 +100,7 @@ def parallel_progress(
112
100
  Returns:
113
101
  list[U]: A list of results from applying the function to each item in
114
102
  the iterable.
103
+
115
104
  """
116
105
  iterable = list(iterable)
117
106
  total = len(iterable)
@@ -130,8 +119,7 @@ def multi_tasks_progress(
130
119
  transient: bool | None = None,
131
120
  **kwargs,
132
121
  ) -> None:
133
- """
134
- Render auto-updating progress bars for multiple tasks concurrently.
122
+ """Render auto-updating progress bars for multiple tasks concurrently.
135
123
 
136
124
  Args:
137
125
  iterables (Iterable[Iterable[int | tuple[int, int]]]): A collection of
@@ -151,6 +139,7 @@ def multi_tasks_progress(
151
139
 
152
140
  Returns:
153
141
  None
142
+
154
143
  """
155
144
  if not columns:
156
145
  columns = Progress.get_default_columns()
@@ -1,6 +1,6 @@
1
- """
2
- Provide functionality for managing and interacting with MLflow runs.
3
- It includes the `RunCollection` class, which serves as a container
1
+ """Provide a collection of MLflow runs.
2
+
3
+ This module includes the `RunCollection` class, which serves as a container
4
4
  for multiple MLflow `Run` instances, and various methods to filter and
5
5
  retrieve these runs.
6
6
 
@@ -23,6 +23,8 @@ from dataclasses import dataclass, field
23
23
  from itertools import chain
24
24
  from typing import TYPE_CHECKING, Any, Concatenate, ParamSpec, TypeVar, overload
25
25
 
26
+ from mlflow.entities import RunStatus
27
+
26
28
  import hydraflow.param
27
29
  from hydraflow.config import iter_params
28
30
  from hydraflow.info import RunCollectionInfo
@@ -42,8 +44,7 @@ P = ParamSpec("P")
42
44
 
43
45
  @dataclass
44
46
  class RunCollection:
45
- """
46
- Represent a collection of MLflow runs.
47
+ """Represent a collection of MLflow runs.
47
48
 
48
49
  Provide methods to interact with the runs, such as filtering,
49
50
  retrieving specific runs, and accessing run information.
@@ -93,7 +94,6 @@ class RunCollection:
93
94
  @classmethod
94
95
  def from_list(cls, runs: list[Run]) -> RunCollection:
95
96
  """Create a `RunCollection` instance from a list of MLflow `Run` instances."""
96
-
97
97
  return cls(runs)
98
98
 
99
99
  @property
@@ -114,6 +114,7 @@ class RunCollection:
114
114
  Returns:
115
115
  A new `RunCollection` instance containing the first n runs if n is
116
116
  positive, or the last n runs if n is negative.
117
+
117
118
  """
118
119
  if n < 0:
119
120
  return self.__class__(self._runs[n:])
@@ -126,17 +127,28 @@ class RunCollection:
126
127
  *,
127
128
  reverse: bool = False,
128
129
  ) -> None:
130
+ """Sort the runs in the collection.
131
+
132
+ Sort the runs in the collection according to the provided key function
133
+ and optional reverse flag.
134
+
135
+ Args:
136
+ key (Callable[[Run], Any] | None): A function that takes a run and returns
137
+ a value to sort by.
138
+ reverse (bool): If True, sort in descending order.
139
+
140
+ """
129
141
  self._runs.sort(key=key or (lambda x: x.info.start_time), reverse=reverse)
130
142
 
131
143
  def one(self) -> Run:
132
- """
133
- Get the only `Run` instance in the collection.
144
+ """Get the only `Run` instance in the collection.
134
145
 
135
146
  Returns:
136
147
  The only `Run` instance in the collection.
137
148
 
138
149
  Raises:
139
150
  ValueError: If the collection does not contain exactly one run.
151
+
140
152
  """
141
153
  if len(self._runs) != 1:
142
154
  raise ValueError("The collection does not contain exactly one run.")
@@ -144,24 +156,24 @@ class RunCollection:
144
156
  return self._runs[0]
145
157
 
146
158
  def try_one(self) -> Run | None:
147
- """
148
- Try to get the only `Run` instance in the collection.
159
+ """Try to get the only `Run` instance in the collection.
149
160
 
150
161
  Returns:
151
162
  The only `Run` instance in the collection, or None if the collection
152
163
  does not contain exactly one run.
164
+
153
165
  """
154
166
  return self._runs[0] if len(self._runs) == 1 else None
155
167
 
156
168
  def first(self) -> Run:
157
- """
158
- Get the first `Run` instance in the collection.
169
+ """Get the first `Run` instance in the collection.
159
170
 
160
171
  Returns:
161
172
  The first `Run` instance in the collection.
162
173
 
163
174
  Raises:
164
175
  ValueError: If the collection is empty.
176
+
165
177
  """
166
178
  if not self._runs:
167
179
  raise ValueError("The collection is empty.")
@@ -169,24 +181,24 @@ class RunCollection:
169
181
  return self._runs[0]
170
182
 
171
183
  def try_first(self) -> Run | None:
172
- """
173
- Try to get the first `Run` instance in the collection.
184
+ """Try to get the first `Run` instance in the collection.
174
185
 
175
186
  Returns:
176
187
  The first `Run` instance in the collection, or None if the collection
177
188
  is empty.
189
+
178
190
  """
179
191
  return self._runs[0] if self._runs else None
180
192
 
181
193
  def last(self) -> Run:
182
- """
183
- Get the last `Run` instance in the collection.
194
+ """Get the last `Run` instance in the collection.
184
195
 
185
196
  Returns:
186
197
  The last `Run` instance in the collection.
187
198
 
188
199
  Raises:
189
200
  ValueError: If the collection is empty.
201
+
190
202
  """
191
203
  if not self._runs:
192
204
  raise ValueError("The collection is empty.")
@@ -194,18 +206,17 @@ class RunCollection:
194
206
  return self._runs[-1]
195
207
 
196
208
  def try_last(self) -> Run | None:
197
- """
198
- Try to get the last `Run` instance in the collection.
209
+ """Try to get the last `Run` instance in the collection.
199
210
 
200
211
  Returns:
201
212
  The last `Run` instance in the collection, or None if the collection
202
213
  is empty.
214
+
203
215
  """
204
216
  return self._runs[-1] if self._runs else None
205
217
 
206
218
  def filter(self, config: object | None = None, **kwargs) -> RunCollection:
207
- """
208
- Filter the `Run` instances based on the provided configuration.
219
+ """Filter the `Run` instances based on the provided configuration.
209
220
 
210
221
  This method filters the runs in the collection according to the
211
222
  specified configuration object and additional key-value pairs. The
@@ -228,12 +239,12 @@ class RunCollection:
228
239
 
229
240
  Returns:
230
241
  A new `RunCollection` object containing the filtered runs.
242
+
231
243
  """
232
244
  return RunCollection(filter_runs(self._runs, config, **kwargs))
233
245
 
234
246
  def find(self, config: object | None = None, **kwargs) -> Run:
235
- """
236
- Find the first `Run` instance based on the provided configuration.
247
+ """Find the first `Run` instance based on the provided configuration.
237
248
 
238
249
  This method filters the runs in the collection according to the
239
250
  specified configuration object and returns the first run that matches
@@ -252,6 +263,7 @@ class RunCollection:
252
263
 
253
264
  See Also:
254
265
  `filter`: Perform the actual filtering logic.
266
+
255
267
  """
256
268
  try:
257
269
  return self.filter(config, **kwargs).first()
@@ -259,8 +271,7 @@ class RunCollection:
259
271
  raise ValueError("No run matches the provided configuration.")
260
272
 
261
273
  def try_find(self, config: object | None = None, **kwargs) -> Run | None:
262
- """
263
- Try to find the first `Run` instance based on the provided configuration.
274
+ """Try to find the first `Run` instance based on the provided configuration.
264
275
 
265
276
  This method filters the runs in the collection according to the
266
277
  specified configuration object and returns the first run that matches
@@ -277,12 +288,12 @@ class RunCollection:
277
288
 
278
289
  See Also:
279
290
  `filter`: Perform the actual filtering logic.
291
+
280
292
  """
281
293
  return self.filter(config, **kwargs).try_first()
282
294
 
283
295
  def find_last(self, config: object | None = None, **kwargs) -> Run:
284
- """
285
- Find the last `Run` instance based on the provided configuration.
296
+ """Find the last `Run` instance based on the provided configuration.
286
297
 
287
298
  This method filters the runs in the collection according to the
288
299
  specified configuration object and returns the last run that matches
@@ -301,6 +312,7 @@ class RunCollection:
301
312
 
302
313
  See Also:
303
314
  `filter`: Perform the actual filtering logic.
315
+
304
316
  """
305
317
  try:
306
318
  return self.filter(config, **kwargs).last()
@@ -308,8 +320,7 @@ class RunCollection:
308
320
  raise ValueError("No run matches the provided configuration.")
309
321
 
310
322
  def try_find_last(self, config: object | None = None, **kwargs) -> Run | None:
311
- """
312
- Try to find the last `Run` instance based on the provided configuration.
323
+ """Try to find the last `Run` instance based on the provided configuration.
313
324
 
314
325
  This method filters the runs in the collection according to the
315
326
  specified configuration object and returns the last run that matches
@@ -326,12 +337,12 @@ class RunCollection:
326
337
 
327
338
  See Also:
328
339
  `filter`: Perform the actual filtering logic.
340
+
329
341
  """
330
342
  return self.filter(config, **kwargs).try_last()
331
343
 
332
344
  def get(self, config: object | None = None, **kwargs) -> Run:
333
- """
334
- Retrieve a specific `Run` instance based on the provided configuration.
345
+ """Retrieve a specific `Run` instance based on the provided configuration.
335
346
 
336
347
  This method filters the runs in the collection according to the
337
348
  specified configuration object and returns the run that matches the
@@ -351,6 +362,7 @@ class RunCollection:
351
362
 
352
363
  See Also:
353
364
  `filter`: Perform the actual filtering logic.
365
+
354
366
  """
355
367
  try:
356
368
  return self.filter(config, **kwargs).one()
@@ -359,8 +371,7 @@ class RunCollection:
359
371
  raise ValueError(msg)
360
372
 
361
373
  def try_get(self, config: object | None = None, **kwargs) -> Run | None:
362
- """
363
- Try to retrieve a specific `Run` instance based on the provided configuration.
374
+ """Try to retrieve a specific `Run` instance based on the provided config.
364
375
 
365
376
  This method filters the runs in the collection according to the
366
377
  specified configuration object and returns the run that matches the
@@ -380,12 +391,12 @@ class RunCollection:
380
391
 
381
392
  See Also:
382
393
  `filter`: Perform the actual filtering logic.
394
+
383
395
  """
384
396
  return self.filter(config, **kwargs).try_one()
385
397
 
386
398
  def get_param_names(self) -> list[str]:
387
- """
388
- Get the parameter names from the runs.
399
+ """Get the parameter names from the runs.
389
400
 
390
401
  This method extracts the unique parameter names from the provided list
391
402
  of runs. It iterates through each run and collects the parameter names
@@ -393,6 +404,7 @@ class RunCollection:
393
404
 
394
405
  Returns:
395
406
  A list of unique parameter names.
407
+
396
408
  """
397
409
  param_names = set()
398
410
 
@@ -402,24 +414,30 @@ class RunCollection:
402
414
 
403
415
  return list(param_names)
404
416
 
405
- def get_param_dict(self) -> dict[str, list[str]]:
406
- """
407
- Get the parameter dictionary from the list of runs.
417
+ def get_param_dict(self, *, drop_const: bool = False) -> dict[str, list[str]]:
418
+ """Get the parameter dictionary from the list of runs.
408
419
 
409
420
  This method extracts the parameter names and their corresponding values
410
421
  from the provided list of runs. It iterates through each run and
411
422
  collects the parameter values into a dictionary where the keys are
412
423
  parameter names and the values are lists of parameter values.
413
424
 
425
+ Args:
426
+ drop_const (bool): If True, drop the parameter values that are constant
427
+ across all runs.
428
+
414
429
  Returns:
415
430
  A dictionary where the keys are parameter names and the values are
416
431
  lists of parameter values.
432
+
417
433
  """
418
434
  params = {}
419
435
 
420
436
  for name in self.get_param_names():
421
437
  it = (run.data.params[name] for run in self if name in run.data.params)
422
- params[name] = sorted(set(it))
438
+ unique_values = sorted(set(it))
439
+ if not drop_const or len(unique_values) > 1:
440
+ params[name] = unique_values
423
441
 
424
442
  return params
425
443
 
@@ -429,9 +447,7 @@ class RunCollection:
429
447
  *args: P.args,
430
448
  **kwargs: P.kwargs,
431
449
  ) -> Iterator[T]:
432
- """
433
- Apply a function to each run in the collection and return an iterator of
434
- results.
450
+ """Return an iterator of results by applying a function to each run.
435
451
 
436
452
  This method iterates over each run in the collection and applies the
437
453
  provided function to it, along with any additional arguments and
@@ -445,6 +461,7 @@ class RunCollection:
445
461
 
446
462
  Yields:
447
463
  Results obtained by applying the function to each run in the collection.
464
+
448
465
  """
449
466
  return (func(run, *args, **kwargs) for run in self)
450
467
 
@@ -454,9 +471,7 @@ class RunCollection:
454
471
  *args: P.args,
455
472
  **kwargs: P.kwargs,
456
473
  ) -> Iterator[T]:
457
- """
458
- Apply a function to each run id in the collection and return an iterator
459
- of results.
474
+ """Return an iterator of results by applying a function to each run id.
460
475
 
461
476
  Args:
462
477
  func (Callable[[str, P], T]): A function that takes a run id and returns a
@@ -467,6 +482,7 @@ class RunCollection:
467
482
  Yields:
468
483
  Results obtained by applying the function to each run id in the
469
484
  collection.
485
+
470
486
  """
471
487
  return (func(run_id, *args, **kwargs) for run_id in self.info.run_id)
472
488
 
@@ -476,9 +492,7 @@ class RunCollection:
476
492
  *args: P.args,
477
493
  **kwargs: P.kwargs,
478
494
  ) -> Iterator[T]:
479
- """
480
- Apply a function to each run configuration in the collection and return
481
- an iterator of results.
495
+ """Return an iterator of results by applying a function to each run config.
482
496
 
483
497
  Args:
484
498
  func (Callable[[DictConfig, P], T]): A function that takes a run
@@ -489,6 +503,7 @@ class RunCollection:
489
503
  Yields:
490
504
  Results obtained by applying the function to each run configuration
491
505
  in the collection.
506
+
492
507
  """
493
508
  return (func(config, *args, **kwargs) for config in self.info.config)
494
509
 
@@ -498,9 +513,7 @@ class RunCollection:
498
513
  *args: P.args,
499
514
  **kwargs: P.kwargs,
500
515
  ) -> Iterator[T]:
501
- """
502
- Apply a function to each artifact URI in the collection and return an
503
- iterator of results.
516
+ """Return an iterator of results by applying a function to each artifact URI.
504
517
 
505
518
  Iterate over each run in the collection, retrieves the artifact URI, and
506
519
  apply the provided function to it. If a run does not have an artifact
@@ -515,6 +528,7 @@ class RunCollection:
515
528
  Yields:
516
529
  Results obtained by applying the function to each artifact URI in the
517
530
  collection.
531
+
518
532
  """
519
533
  return (func(uri, *args, **kwargs) for uri in self.info.artifact_uri)
520
534
 
@@ -524,9 +538,7 @@ class RunCollection:
524
538
  *args: P.args,
525
539
  **kwargs: P.kwargs,
526
540
  ) -> Iterator[T]:
527
- """
528
- Apply a function to each artifact directory in the collection and return
529
- an iterator of results.
541
+ """Return an iterator of results by applying a function to each artifact dir.
530
542
 
531
543
  Iterate over each run in the collection, downloads the artifact
532
544
  directory, and apply the provided function to the directory path.
@@ -540,6 +552,7 @@ class RunCollection:
540
552
  Yields:
541
553
  Results obtained by applying the function to each artifact directory
542
554
  in the collection.
555
+
543
556
  """
544
557
  return (func(dir, *args, **kwargs) for dir in self.info.artifact_dir) # noqa: A001
545
558
 
@@ -547,8 +560,7 @@ class RunCollection:
547
560
  self,
548
561
  *names: str | list[str],
549
562
  ) -> dict[tuple[str | None, ...], RunCollection]:
550
- """
551
- Group runs by specified parameter names.
563
+ """Group runs by specified parameter names.
552
564
 
553
565
  Group the runs in the collection based on the values of the
554
566
  specified parameters. Each unique combination of parameter values will
@@ -563,6 +575,7 @@ class RunCollection:
563
575
  dict[tuple[str | None, ...], RunCollection]: A dictionary where the keys
564
576
  are tuples of parameter values and the values are RunCollection objects
565
577
  containing the runs that match those parameter values.
578
+
566
579
  """
567
580
  grouped_runs: dict[tuple[str | None, ...], list[Run]] = {}
568
581
  for run in self._runs:
@@ -588,11 +601,10 @@ def filter_runs(
588
601
  runs: list[Run],
589
602
  config: object | None = None,
590
603
  *,
591
- status: str | list[str] | None = None,
604
+ status: str | list[str] | int | list[int] | None = None,
592
605
  **kwargs,
593
606
  ) -> list[Run]:
594
- """
595
- Filter the runs based on the provided configuration.
607
+ """Filter the runs based on the provided configuration.
596
608
 
597
609
  Filter the runs in the collection according to the
598
610
  specified configuration object and additional key-value pairs.
@@ -612,33 +624,61 @@ def filter_runs(
612
624
  config (object | None): The configuration object to filter the runs.
613
625
  This can be any object that provides key-value pairs through the
614
626
  `iter_params` function.
615
- status (str | list[str] | None): The status of the runs to filter.
627
+ status (str | list[str] | RunStatus | list[RunStatus] | None): The status of
628
+ the runs to filter.
616
629
  **kwargs: Additional key-value pairs to filter the runs.
617
630
 
618
631
  Returns:
619
632
  A list of runs that match the specified configuration and key-value pairs.
633
+
620
634
  """
621
635
  for key, value in chain(iter_params(config), kwargs.items()):
622
636
  runs = [run for run in runs if _param_matches(run, key, value)]
623
637
 
624
- if len(runs) == 0:
625
- return []
638
+ if len(runs) == 0 or status is None:
639
+ return runs
626
640
 
627
- if isinstance(status, str) and status.startswith("!"):
628
- status = status[1:].lower()
629
- return [run for run in runs if run.info.status.lower() != status]
641
+ return filter_runs_by_status(runs, status)
630
642
 
631
- if status:
632
- status = [status] if isinstance(status, str) else status
633
- status = [s.lower() for s in status]
634
- return [run for run in runs if run.info.status.lower() in status]
635
643
 
636
- return runs
644
+ def filter_runs_by_status(
645
+ runs: list[Run],
646
+ status: str | list[str] | int | list[int],
647
+ ) -> list[Run]:
648
+ """Filter the runs based on the provided status.
637
649
 
650
+ Args:
651
+ runs (list[Run]): The list of runs to filter.
652
+ status (str | list[str] | int | list[int]): The status of the runs
653
+ to filter.
654
+
655
+ Returns:
656
+ A list of runs that match the specified status.
638
657
 
639
- def get_params(run: Run, *names: str | list[str]) -> tuple[str | None, ...]:
640
658
  """
641
- Retrieve the values of specified parameters from the given run.
659
+ if isinstance(status, str):
660
+ if status.startswith("!"):
661
+ status = status[1:].lower()
662
+ return [run for run in runs if run.info.status.lower() != status]
663
+
664
+ status = [status]
665
+
666
+ elif isinstance(status, int):
667
+ status = [RunStatus.to_string(status)]
668
+
669
+ status = [_to_lower(s) for s in status]
670
+ return [run for run in runs if run.info.status.lower() in status]
671
+
672
+
673
+ def _to_lower(status: str | int) -> str:
674
+ if isinstance(status, str):
675
+ return status.lower()
676
+
677
+ return RunStatus.to_string(status).lower()
678
+
679
+
680
+ def get_params(run: Run, *names: str | list[str]) -> tuple[str | None, ...]:
681
+ """Retrieve the values of specified parameters from the given run.
642
682
 
643
683
  This function extracts the values of the parameters identified by the
644
684
  provided names from the specified run. It can accept both individual
@@ -653,6 +693,7 @@ def get_params(run: Run, *names: str | list[str]) -> tuple[str | None, ...]:
653
693
  Returns:
654
694
  tuple[str | None, ...]: A tuple containing the values of the specified
655
695
  parameters in the order they were provided.
696
+
656
697
  """
657
698
  names_ = []
658
699
  for name in names:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: hydraflow
3
- Version: 0.2.17
3
+ Version: 0.2.18
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,14 @@
1
+ hydraflow/__init__.py,sha256=B7rWSiGP5WwWjijcb41Bv9uuo5MQ6gbBbVWGAWYtK-k,598
2
+ hydraflow/asyncio.py,sha256=-i1C8KAmNDImrjHnk92Csaa1mpjdK8Vp4ZVaQV-l94s,6634
3
+ hydraflow/config.py,sha256=sBaEYPMAGSIOc_wdDsWm0k4y3AZyWIET8gqa_o95SDA,2089
4
+ hydraflow/context.py,sha256=ih_jnexaHoToNq1dZ6sBzhJWFluPiQluOlYTYOzNEgk,8222
5
+ hydraflow/info.py,sha256=Vzyz9dEWcU9ovRG3JWshxIazzod1cZoHF74bHhHL3AI,3946
6
+ hydraflow/mlflow.py,sha256=GkOr_pXfpfY5USYBLrCigHcP13VgrAK_e9kheR1Wke4,8579
7
+ hydraflow/param.py,sha256=dvIXcKgc_MPiju3WEk9qz5FOUeK5qSj-YWN2ophCpUM,1938
8
+ hydraflow/progress.py,sha256=zvKX1HCN8_xDOsgYOEcLLhkhdPdep-U8vHrc0XZ-6SQ,6163
9
+ hydraflow/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ hydraflow/run_collection.py,sha256=gsseBQ6a2YolNanISgEgkjei7o9U6ZGV-Tk50UYH850,24295
11
+ hydraflow-0.2.18.dist-info/METADATA,sha256=roL3lGtlIibF6rHbCp4aXrCphhq-OkNe0JwLxM1xtBY,3819
12
+ hydraflow-0.2.18.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
13
+ hydraflow-0.2.18.dist-info/licenses/LICENSE,sha256=IGdDrBPqz1O0v_UwCW-NJlbX9Hy9b3uJ11t28y2srmY,1062
14
+ hydraflow-0.2.18.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- hydraflow/__init__.py,sha256=B7rWSiGP5WwWjijcb41Bv9uuo5MQ6gbBbVWGAWYtK-k,598
2
- hydraflow/asyncio.py,sha256=eFnDbNOQ5Hmjdforr8rTW6i_rr-zFIVY3xSQQ45gMPA,6511
3
- hydraflow/config.py,sha256=YU6xYLinxq-Iqw1R3Zy7s3_u8nfpvnvXlGIkPXJTNLc,2116
4
- hydraflow/context.py,sha256=4UDaWGoVmeF36UqsKoh6dd_cS_YVRfz80gFr28ouNlo,8040
5
- hydraflow/info.py,sha256=7EsCMEH6LJZB3FZiQ3IpPFTD3Meaz7G3M-HvDQeo1rw,3466
6
- hydraflow/mlflow.py,sha256=irD1INrVaI_1RIzUCjI36voBqgZszZ4dkSLo4aT1_FM,8271
7
- hydraflow/param.py,sha256=W71zJH39s8cJcy3qV-PFQHJYyQnfa1GbnHOIqCMG3Jc,1573
8
- hydraflow/progress.py,sha256=b5LvLm3d0eW3WsaidZAZotJNTTN3OwSY3XwxXXsJV9A,6561
9
- hydraflow/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- hydraflow/run_collection.py,sha256=ym3M5ApEZVwJ1rYgOs4aYluTBfJeOECD6Z9SLFhv5O8,23260
11
- hydraflow-0.2.17.dist-info/METADATA,sha256=uD6q000C_h2JsuFh0mkf1YmpTYxVDI1RLaAUKzZ6fDw,3819
12
- hydraflow-0.2.17.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
13
- hydraflow-0.2.17.dist-info/licenses/LICENSE,sha256=IGdDrBPqz1O0v_UwCW-NJlbX9Hy9b3uJ11t28y2srmY,1062
14
- hydraflow-0.2.17.dist-info/RECORD,,