hydraflow 0.18.1__tar.gz → 0.18.3__tar.gz

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.
Files changed (109) hide show
  1. {hydraflow-0.18.1 → hydraflow-0.18.3}/PKG-INFO +1 -1
  2. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/part3-analysis/run-class.md +30 -0
  3. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/part3-analysis/run-collection.md +29 -0
  4. {hydraflow-0.18.1 → hydraflow-0.18.3}/pyproject.toml +1 -1
  5. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/core/collection.py +8 -2
  6. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/core/run.py +22 -1
  7. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/core/run_collection.py +26 -0
  8. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/run/test_run.py +14 -0
  9. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/run/test_run_collection.py +14 -1
  10. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/test_collection.py +5 -0
  11. {hydraflow-0.18.1 → hydraflow-0.18.3}/.devcontainer/devcontainer.json +0 -0
  12. {hydraflow-0.18.1 → hydraflow-0.18.3}/.devcontainer/postCreate.sh +0 -0
  13. {hydraflow-0.18.1 → hydraflow-0.18.3}/.devcontainer/starship.toml +0 -0
  14. {hydraflow-0.18.1 → hydraflow-0.18.3}/.gitattributes +0 -0
  15. {hydraflow-0.18.1 → hydraflow-0.18.3}/.github/workflows/ci.yaml +0 -0
  16. {hydraflow-0.18.1 → hydraflow-0.18.3}/.github/workflows/docs.yaml +0 -0
  17. {hydraflow-0.18.1 → hydraflow-0.18.3}/.github/workflows/publish.yaml +0 -0
  18. {hydraflow-0.18.1 → hydraflow-0.18.3}/.gitignore +0 -0
  19. {hydraflow-0.18.1 → hydraflow-0.18.3}/LICENSE +0 -0
  20. {hydraflow-0.18.1 → hydraflow-0.18.3}/README.md +0 -0
  21. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/getting-started/concepts.md +0 -0
  22. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/getting-started/index.md +0 -0
  23. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/getting-started/installation.md +0 -0
  24. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/index.md +0 -0
  25. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/part1-applications/configuration.md +0 -0
  26. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/part1-applications/execution.md +0 -0
  27. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/part1-applications/index.md +0 -0
  28. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/part1-applications/main-decorator.md +0 -0
  29. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/part2-advanced/index.md +0 -0
  30. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/part2-advanced/job-configuration.md +0 -0
  31. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/part2-advanced/sweep-syntax.md +0 -0
  32. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/part3-analysis/index.md +0 -0
  33. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/part3-analysis/updating-runs.md +0 -0
  34. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/practical-tutorials/advanced.md +0 -0
  35. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/practical-tutorials/analysis.md +0 -0
  36. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/practical-tutorials/applications.md +0 -0
  37. {hydraflow-0.18.1 → hydraflow-0.18.3}/docs/practical-tutorials/index.md +0 -0
  38. {hydraflow-0.18.1 → hydraflow-0.18.3}/examples/example.py +0 -0
  39. {hydraflow-0.18.1 → hydraflow-0.18.3}/examples/hydraflow.yaml +0 -0
  40. {hydraflow-0.18.1 → hydraflow-0.18.3}/examples/submit.py +0 -0
  41. {hydraflow-0.18.1 → hydraflow-0.18.3}/mkdocs.yaml +0 -0
  42. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/__init__.py +0 -0
  43. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/cli.py +0 -0
  44. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/core/__init__.py +0 -0
  45. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/core/context.py +0 -0
  46. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/core/group_by.py +0 -0
  47. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/core/io.py +0 -0
  48. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/core/main.py +0 -0
  49. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/core/run_info.py +0 -0
  50. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/executor/__init__.py +0 -0
  51. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/executor/aio.py +0 -0
  52. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/executor/conf.py +0 -0
  53. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/executor/io.py +0 -0
  54. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/executor/job.py +0 -0
  55. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/executor/parser.py +0 -0
  56. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/py.typed +0 -0
  57. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/utils/__init__.py +0 -0
  58. {hydraflow-0.18.1 → hydraflow-0.18.3}/src/hydraflow/utils/progress.py +0 -0
  59. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/__init__.py +0 -0
  60. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/cli/__init__.py +0 -0
  61. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/cli/app.py +0 -0
  62. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/cli/conftest.py +0 -0
  63. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/cli/hydraflow.yaml +0 -0
  64. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/cli/submit.py +0 -0
  65. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/cli/test_run.py +0 -0
  66. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/cli/test_setup.py +0 -0
  67. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/cli/test_show.py +0 -0
  68. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/cli/test_version.py +0 -0
  69. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/conftest.py +0 -0
  70. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/__init__.py +0 -0
  71. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/context/__init__.py +0 -0
  72. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/context/chdir.py +0 -0
  73. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/context/log_run.py +0 -0
  74. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/context/start_run.py +0 -0
  75. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/context/test_chdir.py +0 -0
  76. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/context/test_log_run.py +0 -0
  77. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/context/test_start_run.py +0 -0
  78. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/__init__.py +0 -0
  79. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/default.py +0 -0
  80. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/force_new_run.py +0 -0
  81. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/match_overrides.py +0 -0
  82. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/rerun_finished.py +0 -0
  83. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/skip_finished.py +0 -0
  84. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/test_default.py +0 -0
  85. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/test_dry_run.py +0 -0
  86. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/test_force_new_run.py +0 -0
  87. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/test_main.py +0 -0
  88. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/test_match_overrides.py +0 -0
  89. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/test_rerun_finished.py +0 -0
  90. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/test_skip_finished.py +0 -0
  91. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/test_update.py +0 -0
  92. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/main/update.py +0 -0
  93. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/run/__init__.py +0 -0
  94. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/run/run.py +0 -0
  95. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/run/test_run_info.py +0 -0
  96. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/test_group_by.py +0 -0
  97. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/core/test_io.py +0 -0
  98. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/executor/__init__.py +0 -0
  99. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/executor/conftest.py +0 -0
  100. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/executor/echo.py +0 -0
  101. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/executor/read.py +0 -0
  102. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/executor/test_aio.py +0 -0
  103. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/executor/test_args.py +0 -0
  104. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/executor/test_conf.py +0 -0
  105. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/executor/test_io.py +0 -0
  106. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/executor/test_job.py +0 -0
  107. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/executor/test_parser.py +0 -0
  108. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/utils/__init__.py +0 -0
  109. {hydraflow-0.18.1 → hydraflow-0.18.3}/tests/utils/test_progress.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hydraflow
3
- Version: 0.18.1
3
+ Version: 0.18.3
4
4
  Summary: HydraFlow seamlessly integrates Hydra and MLflow to streamline ML experiment management, combining Hydra's configuration management with MLflow's tracking capabilities.
5
5
  Project-URL: Documentation, https://daizutabi.github.io/hydraflow/
6
6
  Project-URL: Source, https://github.com/daizutabi/hydraflow
@@ -207,6 +207,36 @@ runs = Run.load(run_dirs)
207
207
  runs = Run.load(run_dirs, n_jobs=4) # Use 4 parallel jobs for loading
208
208
  runs = Run.load(run_dirs, n_jobs=-1) # Use all available CPU cores
209
209
  ```
210
+ ## Converting to DataFrame
211
+
212
+ To convert a Run instance to a Polars DataFrame, use the
213
+ [`to_frame`][hydraflow.core.run.Run.to_frame] method.
214
+ This method adds the Run's information as columns to the DataFrame.
215
+
216
+ ```python
217
+ # Basic usage
218
+ df = run.to_frame(
219
+ lambda r: DataFrame({"value": [1, 2, 3]}),
220
+ "run_id",
221
+ "experiment_name"
222
+ )
223
+
224
+ # With default values
225
+ df = run.to_frame(
226
+ lambda r: DataFrame({"value": [1, 2, 3]}),
227
+ "run_id",
228
+ ("status", lambda r: "completed")
229
+ )
230
+ ```
231
+
232
+ The `to_frame` method accepts the following parameters:
233
+
234
+ - `func`: A function that takes a Run instance and returns a DataFrame
235
+ - `*keys`: Keys for the Run's information to add. Accepts the following formats:
236
+ - String: A simple key (e.g., "run_id")
237
+ - Tuple: A tuple of (key, default value or function returning default value)
238
+
239
+
210
240
 
211
241
  ### Finding Runs with `iter_run_dirs`
212
242
 
@@ -303,6 +303,35 @@ filled_df = missing_values_df.with_columns(
303
303
  )
304
304
  ```
305
305
 
306
+ ## Concatenating Multiple Runs
307
+
308
+ To convert and concatenate multiple Run instances into a DataFrame,
309
+ use the [`concat`][hydraflow.core.run_collection.RunCollection.concat] method.
310
+ This method adds each Run's information as columns to the DataFrame and concatenates them.
311
+
312
+ ```python
313
+ # Basic usage
314
+ df = run_collection.concat(
315
+ lambda r: DataFrame({"value": [1, 2, 3]}),
316
+ "run_id",
317
+ "experiment_name"
318
+ )
319
+
320
+ # With default values
321
+ df = run_collection.concat(
322
+ lambda r: DataFrame({"value": [1, 2, 3]}),
323
+ "run_id",
324
+ ("status", lambda r: "completed")
325
+ )
326
+ ```
327
+
328
+ The `concat` method accepts the following parameters:
329
+
330
+ - `func`: A function that takes each Run instance and returns a DataFrame
331
+ - `*keys`: Keys for the Run's information to add. Accepts the following formats:
332
+ - String: A simple key (e.g., "run_id")
333
+ - Tuple: A tuple of (key, default value or function returning default value)
334
+
306
335
  ## Grouping Runs
307
336
 
308
337
  The `group_by` method allows you to organize runs based on parameter values:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hydraflow"
7
- version = "0.18.1"
7
+ version = "0.18.3"
8
8
  description = "HydraFlow seamlessly integrates Hydra and MLflow to streamline ML experiment management, combining Hydra's configuration management with MLflow's tracking capabilities."
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -17,6 +17,7 @@ from .group_by import GroupBy
17
17
 
18
18
  if TYPE_CHECKING:
19
19
  from collections.abc import Callable, Iterator
20
+ from re import Pattern, _FlagsType
20
21
  from typing import Any, Self
21
22
 
22
23
  from numpy.typing import NDArray
@@ -807,9 +808,10 @@ class Collection[I](Sequence[I]):
807
808
  def match(
808
809
  self,
809
810
  key: str,
810
- pattern: str | re.Pattern,
811
+ pattern: str | Pattern[str],
811
812
  *,
812
813
  default: Any | Callable[[I], Any] = MISSING,
814
+ flags: _FlagsType = 0,
813
815
  ) -> Callable[[I], bool]:
814
816
  """Create a predicate function that checks if an attribute matches a pattern.
815
817
 
@@ -817,12 +819,16 @@ class Collection[I](Sequence[I]):
817
819
  key (str): The name of the attribute to check.
818
820
  pattern (str | re.Pattern): The pattern to check for.
819
821
  default (Any | Callable[[I], Any], optional): The default value.
822
+ flags (re.RegexFlag, optional): Flags for the regex pattern.
820
823
 
821
824
  Returns:
822
825
  Callable[[I], bool]: A predicate function for filtering.
823
826
 
824
827
  """
825
- return lambda i: re.match(pattern, str(self._get(i, key, default))) is not None
828
+ return (
829
+ lambda i: re.match(pattern, str(self._get(i, key, default)), flags)
830
+ is not None
831
+ )
826
832
 
827
833
 
828
834
  def to_hashable(value: Any) -> Hashable:
@@ -40,7 +40,7 @@ if TYPE_CHECKING:
40
40
  from collections.abc import Iterator
41
41
  from typing import Any, Self
42
42
 
43
- from polars import Expr
43
+ from polars import DataFrame, Expr
44
44
  from polars._typing import PolarsDataType
45
45
 
46
46
  from .run_collection import RunCollection
@@ -341,6 +341,27 @@ class Run[C, I = None]:
341
341
  value = self.get(key, default)
342
342
  return pl.lit(value, dtype).alias(key)
343
343
 
344
+ def to_frame(
345
+ self,
346
+ func: Callable[[Self], DataFrame],
347
+ *keys: str | tuple[str, Any | Callable[[Self], Any]],
348
+ ) -> DataFrame:
349
+ """Convert the Run to a DataFrame.
350
+
351
+ Args:
352
+ func (Callable[[Run], DataFrame]): A function that takes a Run
353
+ instance and returns a DataFrame.
354
+ keys (str | tuple[str, Any | Callable[[Run], Any]]): The keys to
355
+ add to the DataFrame.
356
+
357
+ Returns:
358
+ DataFrame: A DataFrame representation of the Run.
359
+
360
+ """
361
+ return func(self).with_columns(
362
+ self.lit(k) if isinstance(k, str) else self.lit(k[0], k[1]) for k in keys
363
+ )
364
+
344
365
  def to_dict(self, flatten: bool = True) -> dict[str, Any]:
345
366
  """Convert the Run to a dictionary.
346
367
 
@@ -43,6 +43,8 @@ from __future__ import annotations
43
43
  from functools import cached_property
44
44
  from typing import TYPE_CHECKING, overload
45
45
 
46
+ import polars as pl
47
+
46
48
  from .collection import Collection
47
49
  from .run import Run
48
50
 
@@ -51,6 +53,8 @@ if TYPE_CHECKING:
51
53
  from pathlib import Path
52
54
  from typing import Any, Self
53
55
 
56
+ from polars import DataFrame
57
+
54
58
 
55
59
  class RunCollection[R: Run[Any, Any], I = None](Collection[R]):
56
60
  """A collection of Run instances that implements the Sequence protocol.
@@ -168,6 +172,28 @@ class RunCollection[R: Run[Any, Any], I = None](Collection[R]):
168
172
  for run in self:
169
173
  run.update(key, value, force=force)
170
174
 
175
+ def concat(
176
+ self,
177
+ func: Callable[[R], DataFrame],
178
+ *keys: str | tuple[str, Any | Callable[[R], Any]],
179
+ ) -> DataFrame:
180
+ """Concatenate the results of a function applied to all runs in the collection.
181
+
182
+ This method applies the provided function to each run in the collection
183
+ and concatenates the resulting DataFrames along the specified keys.
184
+
185
+ Args:
186
+ func (Callable[[R], DataFrame]): A function that takes a Run
187
+ instance and returns a DataFrame.
188
+ keys (str | tuple[str, Any | Callable[[R], Any]]): The keys to
189
+ add to the DataFrame.
190
+
191
+ Returns:
192
+ DataFrame: A DataFrame representation of the Run.
193
+
194
+ """
195
+ return pl.concat(run.to_frame(func, *keys) for run in self)
196
+
171
197
  @cached_property
172
198
  def impls(self) -> Collection[I]:
173
199
  """Get the implementation objects for all runs in the collection.
@@ -138,6 +138,20 @@ def test_lit(run: Run[Config]):
138
138
  assert df.item(2, "db.b") == 100
139
139
 
140
140
 
141
+ def test_to_frame(run: Run[Config]):
142
+ def func(run: Run[Config]) -> pl.DataFrame:
143
+ return pl.DataFrame({"a": [run.get("a"), 20]})
144
+
145
+ run.update("a", 10)
146
+ run.update("db.b", 100)
147
+ df = run.to_frame(func, "db.b", ("x", 123), ("y", lambda r: r.get("a")))
148
+ assert df.shape == (2, 4)
149
+ assert df["a"].to_list() == [10, 20]
150
+ assert df["db.b"].to_list() == [100, 100]
151
+ assert df["x"].to_list() == [123, 123]
152
+ assert df["y"].to_list() == [10, 10]
153
+
154
+
141
155
  def test_to_dict(run: Run[Config]):
142
156
  run.update("a", 10)
143
157
  run.update("db.name", "abc")
@@ -4,6 +4,7 @@ from pathlib import Path
4
4
 
5
5
  import numpy as np
6
6
  import pytest
7
+ from polars import DataFrame
7
8
 
8
9
  from hydraflow.core.run import Run
9
10
  from hydraflow.core.run_collection import RunCollection
@@ -278,7 +279,19 @@ def test_group_by(rc: Rc):
278
279
  assert isinstance(gp, GroupBy)
279
280
 
280
281
 
281
- def test_impl(rc: Rc):
282
+ def test_concat(rc: Rc):
283
+ def func(r: Run[Config, Impl]) -> DataFrame:
284
+ return DataFrame({"a": [r.get("count"), 20]})
285
+
286
+ df = rc.concat(func, "size.width", ("z", lambda r: r.get("count") * 20))
287
+ assert df.shape == (24, 3)
288
+ assert df["a"].to_list()[:4] == [1, 20, 1, 20]
289
+ assert df["size.width"].to_list()[-6:] == [10, 10, 20, 20, 30, 30]
290
+ assert df["z"].to_list()[:6] == [20, 20, 20, 20, 20, 20]
291
+ assert df["z"].to_list()[-6:] == [40, 40, 40, 40, 40, 40]
292
+
293
+
294
+ def test_impls(rc: Rc):
282
295
  impls = rc.impls
283
296
  assert len(impls) == 12
284
297
  assert len(impls.filter(lambda i: i.y[0] == "1")) == 6
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from collections.abc import Callable
2
3
  from dataclasses import dataclass
3
4
  from itertools import product
@@ -414,3 +415,7 @@ def test_endswith(rcd: Collection):
414
415
 
415
416
  def test_match(rcd: Collection):
416
417
  assert len(rcd.filter(rcd.match("z", r".*ac.*"))) == 2
418
+
419
+
420
+ def test_match_flags(rcd: Collection):
421
+ assert len(rcd.filter(rcd.match("z", r".*AC.*", flags=re.IGNORECASE))) == 2
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes