hydraflow 0.15.0__py3-none-any.whl → 0.15.1__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/core/run.py CHANGED
@@ -249,29 +249,40 @@ class Run[C, I = None]:
249
249
  if force or OmegaConf.select(cfg, k, default=MISSING) is MISSING:
250
250
  OmegaConf.update(cfg, k, v, force_add=True)
251
251
 
252
- def get(self, key: str) -> Any:
252
+ def get(self, key: str, default: Any = MISSING) -> Any:
253
253
  """Get a value from the information or configuration.
254
254
 
255
255
  Args:
256
- key: The key to look for. Can use dot notation for nested keys
257
- in configuration.
256
+ key: The key to look for. Can use dot notation for
257
+ nested keys in configuration.
258
+ default: Value to return if the key is not found.
259
+ If not provided, AttributeError will be raised.
258
260
 
259
261
  Returns:
260
- Any: The value associated with the key.
262
+ Any: The value associated with the key, or the
263
+ default value if the key is not found and a default
264
+ is provided.
261
265
 
262
266
  Raises:
263
- AttributeError: If the key is not found in any of the components.
267
+ AttributeError: If the key is not found and
268
+ no default is provided.
264
269
 
265
270
  """
266
271
  value = OmegaConf.select(self.cfg, key, default=MISSING) # type: ignore
267
272
  if value is not MISSING:
268
273
  return value
269
274
 
275
+ if self.impl and hasattr(self.impl, key):
276
+ return getattr(self.impl, key)
277
+
270
278
  info = self.info.to_dict()
271
279
  if key in info:
272
280
  return info[key]
273
281
 
274
- msg = f"Key not found: {key}"
282
+ if default is not MISSING:
283
+ return default
284
+
285
+ msg = f"No such key: {key}"
275
286
  raise AttributeError(msg)
276
287
 
277
288
  def predicate(self, key: str, value: Any) -> bool:
@@ -298,32 +309,35 @@ class Run[C, I = None]:
298
309
 
299
310
  """
300
311
  attr = self.get(key)
312
+ return _predicate(attr, value)
301
313
 
302
- if callable(value):
303
- return bool(value(attr))
314
+ def to_dict(self) -> dict[str, Any]:
315
+ """Convert the Run to a dictionary."""
316
+ info = self.info.to_dict()
317
+ cfg = OmegaConf.to_container(self.cfg)
318
+ return info | _flatten_dict(cfg) # type: ignore
304
319
 
305
- if isinstance(value, ListConfig):
306
- value = list(value)
307
320
 
308
- if isinstance(value, list | set) and not _is_iterable(attr):
309
- return attr in value
321
+ def _predicate(attr: Any, value: Any) -> bool:
322
+ if callable(value):
323
+ return bool(value(attr))
310
324
 
311
- if isinstance(value, tuple) and len(value) == 2 and not _is_iterable(attr):
312
- return value[0] <= attr <= value[1]
325
+ if isinstance(value, ListConfig):
326
+ value = list(value)
313
327
 
314
- if _is_iterable(value):
315
- value = list(value)
328
+ if isinstance(value, list | set) and not _is_iterable(attr):
329
+ return attr in value
316
330
 
317
- if _is_iterable(attr):
318
- attr = list(attr)
331
+ if isinstance(value, tuple) and len(value) == 2 and not _is_iterable(attr):
332
+ return value[0] <= attr <= value[1]
319
333
 
320
- return attr == value
334
+ if _is_iterable(value):
335
+ value = list(value)
321
336
 
322
- def to_dict(self) -> dict[str, Any]:
323
- """Convert the Run to a dictionary."""
324
- info = self.info.to_dict()
325
- cfg = OmegaConf.to_container(self.cfg)
326
- return info | _flatten_dict(cfg) # type: ignore
337
+ if _is_iterable(attr):
338
+ attr = list(attr)
339
+
340
+ return attr == value
327
341
 
328
342
 
329
343
  def _is_iterable(value: Any) -> bool:
@@ -4,10 +4,10 @@ from dataclasses import dataclass, field
4
4
 
5
5
 
6
6
  @dataclass
7
- class Step:
8
- batch: str = ""
9
- args: str = ""
10
- with_: str = ""
7
+ class Set:
8
+ each: str = ""
9
+ all: str = ""
10
+ add: str = ""
11
11
 
12
12
 
13
13
  @dataclass
@@ -16,8 +16,8 @@ class Job:
16
16
  run: str = ""
17
17
  call: str = ""
18
18
  submit: str = ""
19
- with_: str = ""
20
- steps: list[Step] = field(default_factory=list)
19
+ add: str = ""
20
+ sets: list[Set] = field(default_factory=list)
21
21
 
22
22
 
23
23
  @dataclass
hydraflow/executor/io.py CHANGED
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  from pathlib import Path
6
6
  from typing import TYPE_CHECKING
7
7
 
8
- from omegaconf import DictConfig, ListConfig, OmegaConf
8
+ from omegaconf import DictConfig, OmegaConf
9
9
 
10
10
  from .conf import HydraflowConf
11
11
 
@@ -38,25 +38,9 @@ def load_config() -> HydraflowConf:
38
38
  if not isinstance(cfg, DictConfig):
39
39
  return schema
40
40
 
41
- rename_with(cfg)
42
-
43
41
  return OmegaConf.merge(schema, cfg) # type: ignore[return-value]
44
42
 
45
43
 
46
- def rename_with(cfg: DictConfig) -> None:
47
- """Rename the `with` field to `with_`."""
48
- if "with" in cfg:
49
- cfg["with_"] = cfg.pop("with")
50
-
51
- for key in list(cfg.keys()):
52
- if isinstance(cfg[key], DictConfig):
53
- rename_with(cfg[key])
54
- elif isinstance(cfg[key], ListConfig):
55
- for item in cfg[key]:
56
- if isinstance(item, DictConfig):
57
- rename_with(item)
58
-
59
-
60
44
  def get_job(name: str) -> Job:
61
45
  """Get a job from the config."""
62
46
  cfg = load_config()
hydraflow/executor/job.py CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This module provides functionality for executing jobs in HydraFlow, including:
4
4
 
5
- - Argument parsing and expansion for job steps
5
+ - Argument parsing and expansion for job parameter sets
6
6
  - Batch processing of Hydra configurations
7
7
  - Execution of jobs via shell commands or Python functions
8
8
 
@@ -11,8 +11,9 @@ The module supports two execution modes:
11
11
  1. Shell command execution
12
12
  2. Python function calls
13
13
 
14
- Each job can consist of multiple steps, and each step can have its own
15
- arguments and configurations that will be expanded into multiple runs.
14
+ Each job can consist of multiple parameter sets, and each parameter
15
+ set can have its own arguments and configurations that will be expanded
16
+ into multiple runs.
16
17
  """
17
18
 
18
19
  from __future__ import annotations
@@ -39,24 +40,24 @@ if TYPE_CHECKING:
39
40
  from .conf import Job
40
41
 
41
42
 
42
- def iter_args(batch: str, args: str) -> Iterator[list[str]]:
43
+ def iter_args(each: str, all_: str) -> Iterator[list[str]]:
43
44
  """Iterate over combinations generated from parsed arguments.
44
45
 
45
46
  Generate all possible combinations of arguments by parsing and
46
47
  expanding each one, yielding them as an iterator.
47
48
 
48
49
  Args:
49
- batch (str): The batch to parse.
50
- args (str): The arguments to parse.
50
+ each (str): The 'each' parameter to parse.
51
+ all_ (str): The 'all' parameter to parse.
51
52
 
52
53
  Yields:
53
54
  list[str]: a list of the parsed argument combinations.
54
55
 
55
56
  """
56
- args_ = collect(args)
57
+ all_params = collect(all_)
57
58
 
58
- for batch_ in expand(batch):
59
- yield [*batch_, *args_]
59
+ for each_params in expand(each):
60
+ yield [*each_params, *all_params]
60
61
 
61
62
 
62
63
  def iter_batches(job: Job) -> Iterator[list[str]]:
@@ -74,14 +75,40 @@ def iter_batches(job: Job) -> Iterator[list[str]]:
74
75
 
75
76
  """
76
77
  job_name = f"hydra.job.name={job.name}"
77
- job_configs = shlex.split(job.with_)
78
+ job_add = shlex.split(job.add)
78
79
 
79
- for step in job.steps:
80
- configs = shlex.split(step.with_) or job_configs
80
+ for set_ in job.sets:
81
+ add = merge_args(job_add, shlex.split(set_.add)) if set_.add else job_add
81
82
 
82
- for args in iter_args(step.batch, step.args):
83
+ for args in iter_args(set_.each, set_.all):
83
84
  sweep_dir = f"hydra.sweep.dir=multirun/{ulid.ULID()}"
84
- yield ["--multirun", *args, job_name, sweep_dir, *configs]
85
+ yield ["--multirun", *args, job_name, sweep_dir, *add]
86
+
87
+
88
+ def merge_args(first: list[str], second: list[str]) -> list[str]:
89
+ """Merge two lists of arguments.
90
+
91
+ This function merges two lists of arguments by checking for conflicts
92
+ and resolving them by keeping the values from the second list.
93
+
94
+ Args:
95
+ first (list[str]): The first list of arguments.
96
+ second (list[str]): The second list of arguments.
97
+
98
+ Returns:
99
+ list[str]: A merged list of arguments.
100
+
101
+ """
102
+ merged = {}
103
+
104
+ for item in [*first, *second]:
105
+ if "=" in item:
106
+ key, value = item.split("=", 1)
107
+ merged[key] = value
108
+ else:
109
+ merged[item] = None
110
+
111
+ return [k if v is None else f"{k}={v}" for k, v in merged.items()]
85
112
 
86
113
 
87
114
  @dataclass
@@ -165,25 +165,26 @@ SUFFIX_EXPONENT = {
165
165
 
166
166
 
167
167
  def _get_range(arg: str) -> tuple[float, float, float]:
168
+ """Return a tuple of (start, stop, step)."""
168
169
  args = [to_number(x) for x in arg.split(":")]
169
170
 
170
171
  if len(args) == 2:
171
172
  if args[0] > args[1]:
172
173
  raise ValueError("start cannot be greater than stop")
173
174
 
174
- return (args[0], 1, args[1])
175
+ return (args[0], args[1], 1)
175
176
 
176
- if args[1] == 0:
177
+ if args[2] == 0:
177
178
  raise ValueError("step cannot be zero")
178
- if args[1] > 0 and args[0] > args[2]:
179
+ if args[2] > 0 and args[0] > args[1]:
179
180
  raise ValueError("start cannot be greater than stop")
180
- if args[1] < 0 and args[0] < args[2]:
181
+ if args[2] < 0 and args[0] < args[1]:
181
182
  raise ValueError("start cannot be less than stop")
182
183
 
183
184
  return args[0], args[1], args[2]
184
185
 
185
186
 
186
- def _arange(start: float, step: float, stop: float) -> list[float]:
187
+ def _arange(start: float, stop: float, step: float) -> list[float]:
187
188
  """Generate a range of floating point numbers.
188
189
 
189
190
  This function generates a range of floating point numbers
@@ -191,8 +192,8 @@ def _arange(start: float, step: float, stop: float) -> list[float]:
191
192
 
192
193
  Args:
193
194
  start (float): The starting value.
194
- step (float): The step size.
195
195
  stop (float): The end value (inclusive).
196
+ step (float): The step size.
196
197
 
197
198
  Returns:
198
199
  list[float]: A list of floating point numbers from start to stop
@@ -323,7 +324,7 @@ def collect_parentheses(arg: str) -> list[str]:
323
324
  list[str]: A list of the collected values.
324
325
 
325
326
  Examples:
326
- >>> collect_parentheses("(1:3,5:2:9,20)k")
327
+ >>> collect_parentheses("(1:3,5:9:2,20)k")
327
328
  ['1e3', '2e3', '3e3', '5e3', '7e3', '9e3', '20e3']
328
329
  >>> collect_parentheses("2e(-1,-2,-3)")
329
330
  ['2e-1', '2e-2', '2e-3']
@@ -352,7 +353,7 @@ def collect_values(arg: str) -> list[str]:
352
353
  Examples:
353
354
  >>> collect_values("1:4")
354
355
  ['1', '2', '3', '4']
355
- >>> collect_values("1.2:0.1:1.4:k")
356
+ >>> collect_values("1.2:1.4:0.1:k")
356
357
  ['1.2e3', '1.3e3', '1.4e3']
357
358
  >>> collect_values("0.1")
358
359
  ['0.1']
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hydraflow
3
- Version: 0.15.0
3
+ Version: 0.15.1
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
@@ -5,17 +5,17 @@ hydraflow/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  hydraflow/core/context.py,sha256=LFPNJxmuJQ2VUt-WBU07MC3ySbjlY8rRZ8VxuAih4o4,4148
6
6
  hydraflow/core/io.py,sha256=ZBXIL_jlBUiCI0L_J6S5S4OwtBMvdVVMXnekzMuC_JA,4404
7
7
  hydraflow/core/main.py,sha256=b9o6Rpn3uoXfDB8o0XZdl-g1yX2SKkOT12-H7lB8Les,5158
8
- hydraflow/core/run.py,sha256=9JNk3axDdKLpttGx-BC9aqw3d7rosygn2cIzL-fxVlM,11876
8
+ hydraflow/core/run.py,sha256=KqaMdRUBOzOU4vkrRUczCrPCsVx30-XUQ_e78B78BSU,12330
9
9
  hydraflow/core/run_collection.py,sha256=pV3N83uBhmda9OeaNz1jqpF9z6A9j3jfUHtqy-uxCs4,15671
10
10
  hydraflow/core/run_info.py,sha256=3dW9GgWnZZNwbXwMrw-85AqQ956zlQddUi9irSNLR5g,2550
11
11
  hydraflow/executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  hydraflow/executor/aio.py,sha256=xXsmBPIPdBlopv_1h0FdtOvoKUcuW7PQeKCV2d_lN9I,2122
13
- hydraflow/executor/conf.py,sha256=icGbLDh86KgkyiGXwDoEkmZpgAP3X8Jmu_PYqJoTooY,423
14
- hydraflow/executor/io.py,sha256=yZMcBVmAbPZZ82cAXhgiJfj9p8WvHmzOCMBg_vtEVek,1509
15
- hydraflow/executor/job.py,sha256=JX6xX9ffvHB7IiAVIfzVRjjnWKaPDxBgqdZf4ZO14CY,4651
16
- hydraflow/executor/parser.py,sha256=_Rfund3FDgrXitTt_znsTpgEtMDqZ_ICynaB_Zje14Q,14561
17
- hydraflow-0.15.0.dist-info/METADATA,sha256=2OpqrXDfnVxQ_ZJkS5tEjQH0VTa3yx8jkfFOjbkCK50,7238
18
- hydraflow-0.15.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
- hydraflow-0.15.0.dist-info/entry_points.txt,sha256=XI0khPbpCIUo9UPqkNEpgh-kqK3Jy8T7L2VCWOdkbSM,48
20
- hydraflow-0.15.0.dist-info/licenses/LICENSE,sha256=IGdDrBPqz1O0v_UwCW-NJlbX9Hy9b3uJ11t28y2srmY,1062
21
- hydraflow-0.15.0.dist-info/RECORD,,
13
+ hydraflow/executor/conf.py,sha256=8Xq4UAenRKJIl1NBgNbSfv6VUTJhdwPLayZIEAsiBR0,414
14
+ hydraflow/executor/io.py,sha256=18wnHpCMQRGYL-oN2841h9W2aSW_X2SmO68Lx-3FIbU,1043
15
+ hydraflow/executor/job.py,sha256=6QeJ18OMeocXeM04rCYL46GgArfX1SvZs9_4HTomTgE,5436
16
+ hydraflow/executor/parser.py,sha256=RxP8qpDaJ8VLqZ51VlPFyVitWctObhkE_3iPIsY66Cs,14610
17
+ hydraflow-0.15.1.dist-info/METADATA,sha256=oC-UgH0sZKw2Ry1kBiMPpNobxzlLhmhQgS8W3TIvGJI,7238
18
+ hydraflow-0.15.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
+ hydraflow-0.15.1.dist-info/entry_points.txt,sha256=XI0khPbpCIUo9UPqkNEpgh-kqK3Jy8T7L2VCWOdkbSM,48
20
+ hydraflow-0.15.1.dist-info/licenses/LICENSE,sha256=IGdDrBPqz1O0v_UwCW-NJlbX9Hy9b3uJ11t28y2srmY,1062
21
+ hydraflow-0.15.1.dist-info/RECORD,,