hydraflow 0.11.1__py3-none-any.whl → 0.12.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/main.py CHANGED
@@ -16,6 +16,7 @@ used to wrap experiment entry points. This decorator handles:
16
16
 
17
17
  Example:
18
18
  ```python
19
+ import hydraflow
19
20
  from dataclasses import dataclass
20
21
  from mlflow.entities import Run
21
22
 
@@ -24,7 +25,7 @@ Example:
24
25
  learning_rate: float
25
26
  batch_size: int
26
27
 
27
- @main(Config)
28
+ @hydraflow.main(Config)
28
29
  def train(run: Run, config: Config):
29
30
  # Your training code here
30
31
  pass
@@ -7,7 +7,7 @@ from dataclasses import dataclass, field
7
7
  class Step:
8
8
  batch: str = ""
9
9
  args: str = ""
10
- configs: str = ""
10
+ with_: str = ""
11
11
 
12
12
 
13
13
  @dataclass
@@ -15,7 +15,7 @@ class Job:
15
15
  name: str = ""
16
16
  run: str = ""
17
17
  call: str = ""
18
- configs: str = ""
18
+ with_: str = ""
19
19
  steps: list[Step] = field(default_factory=list)
20
20
 
21
21
 
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 OmegaConf
8
+ from omegaconf import DictConfig, ListConfig, OmegaConf
9
9
 
10
10
  from .conf import HydraflowConf
11
11
 
@@ -35,7 +35,26 @@ def load_config() -> HydraflowConf:
35
35
 
36
36
  cfg = OmegaConf.load(path)
37
37
 
38
- return OmegaConf.merge(schema, cfg) # type: ignore
38
+ if not isinstance(cfg, DictConfig):
39
+ return schema
40
+
41
+ rename_with(cfg)
42
+
43
+ return OmegaConf.merge(schema, cfg) # type: ignore[return-value]
44
+
45
+
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)
39
58
 
40
59
 
41
60
  def get_job(name: str) -> Job:
hydraflow/executor/job.py CHANGED
@@ -69,10 +69,10 @@ def iter_batches(job: Job) -> Iterator[list[str]]:
69
69
 
70
70
  """
71
71
  job_name = f"hydra.job.name={job.name}"
72
- job_configs = shlex.split(job.configs)
72
+ job_configs = shlex.split(job.with_)
73
73
 
74
74
  for step in job.steps:
75
- configs = shlex.split(step.configs) or job_configs
75
+ configs = shlex.split(step.with_) or job_configs
76
76
 
77
77
  for args in iter_args(step.batch, step.args):
78
78
  sweep_dir = f"hydra.sweep.dir=multirun/{ulid.ULID()}"
@@ -83,7 +83,7 @@ def count_decimal_places(x: str) -> int:
83
83
 
84
84
 
85
85
  def is_number(x: str) -> bool:
86
- """Check if a string is a number.
86
+ """Check if a string represents a valid number.
87
87
 
88
88
  Args:
89
89
  x (str): The string to check.
@@ -140,18 +140,54 @@ def _get_range(arg: str) -> tuple[float, float, float]:
140
140
 
141
141
 
142
142
  def _arange(start: float, step: float, stop: float) -> list[float]:
143
+ """Generate a range of floating point numbers.
144
+
145
+ This function generates a range of floating point numbers
146
+ with protection against rounding errors.
147
+
148
+ Args:
149
+ start (float): The starting value.
150
+ step (float): The step size.
151
+ stop (float): The end value (inclusive).
152
+
153
+ Returns:
154
+ list[float]: A list of floating point numbers from start to stop
155
+ (inclusive) with the given step.
156
+
157
+ """
158
+ if step == 0:
159
+ raise ValueError("Step cannot be zero")
160
+
161
+ epsilon = 1e-10
162
+
163
+ decimal_places = max(
164
+ count_decimal_places(str(start)),
165
+ count_decimal_places(str(step)),
166
+ count_decimal_places(str(stop)),
167
+ )
168
+
143
169
  result = []
144
170
  current = start
145
171
 
146
- while current <= stop if step > 0 else current >= stop:
147
- result.append(current)
148
- current += step
172
+ if step > 0:
173
+ while current <= stop + epsilon:
174
+ rounded = round(current, decimal_places)
175
+ result.append(rounded)
176
+ current += step
177
+ else:
178
+ while current >= stop - epsilon:
179
+ rounded = round(current, decimal_places)
180
+ result.append(rounded)
181
+ current += step
149
182
 
150
183
  return result
151
184
 
152
185
 
153
186
  def split_suffix(arg: str) -> tuple[str, str]:
154
- """Split a string into prefix and suffix.
187
+ """Split a string into the prefix and suffix.
188
+
189
+ The suffix is the part of the string that starts with a colon (:).
190
+ The prefix is the part of the string that precedes the suffix.
155
191
 
156
192
  Args:
157
193
  arg (str): The string to split.
@@ -194,6 +230,16 @@ def add_exponent(value: str, exponent: str) -> str:
194
230
  Returns:
195
231
  str: The value with the exponent added.
196
232
 
233
+ Examples:
234
+ >>> add_exponent("1", "e3")
235
+ '1e3'
236
+ >>> add_exponent("1", "")
237
+ '1'
238
+ >>> add_exponent("0", "e-3")
239
+ '0'
240
+ >>> add_exponent("0.0", "e-3")
241
+ '0.0'
242
+
197
243
  """
198
244
  if value in ["0", "0.", "0.0"] or not exponent:
199
245
  return value
@@ -201,43 +247,6 @@ def add_exponent(value: str, exponent: str) -> str:
201
247
  return f"{value}{exponent}"
202
248
 
203
249
 
204
- def collect_values(arg: str) -> list[str]:
205
- """Collect a list of values from a range argument.
206
-
207
- Collect all individual values within a numeric range
208
- represented by a string (e.g., `1:4`) and return them
209
- as a list of strings.
210
- Support both integer and floating-point ranges.
211
-
212
- Args:
213
- arg (str): The argument to collect.
214
-
215
- Returns:
216
- list[str]: A list of the collected values.
217
-
218
- """
219
- if "(" in arg:
220
- return collect_parentheses(arg)
221
-
222
- if ":" not in arg:
223
- return [arg]
224
-
225
- arg, exponent = split_suffix(arg)
226
-
227
- if ":" not in arg:
228
- return [f"{arg}{exponent}"]
229
-
230
- rng = _get_range(arg)
231
-
232
- if all(isinstance(x, int) for x in rng):
233
- values = [str(x) for x in _arange(*rng)]
234
- else:
235
- n = max(*(count_decimal_places(x) for x in arg.split(":")))
236
- values = [str(round(x, n)) for x in _arange(*rng)]
237
-
238
- return [add_exponent(x, exponent) for x in values]
239
-
240
-
241
250
  def split_parentheses(arg: str) -> Iterator[str]:
242
251
  """Split a string with parentheses into a list of strings.
243
252
 
@@ -290,8 +299,59 @@ def collect_parentheses(arg: str) -> list[str]:
290
299
  return ["".join(x[::-1]) for x in product(*it[::-1])]
291
300
 
292
301
 
302
+ def collect_values(arg: str) -> list[str]:
303
+ """Collect a list of values from a range argument.
304
+
305
+ Collect all individual values within a numeric range
306
+ represented by a string (e.g., `1:4`) and return them
307
+ as a list of strings.
308
+ Support both integer and floating-point ranges.
309
+
310
+ Args:
311
+ arg (str): The argument to collect.
312
+
313
+ Returns:
314
+ list[str]: A list of the collected values.
315
+
316
+ Examples:
317
+ >>> collect_values("1:4")
318
+ ['1', '2', '3', '4']
319
+ >>> collect_values("1.2:0.1:1.4:k")
320
+ ['1.2e3', '1.3e3', '1.4e3']
321
+ >>> collect_values("0.1")
322
+ ['0.1']
323
+ >>> collect_values("4:M")
324
+ ['4e6']
325
+ >>> collect_values("(1:3,5:7)M")
326
+ ['1e6', '2e6', '3e6', '5e6', '6e6', '7e6']
327
+ >>> collect_values("(1,5)e-(1:3)")
328
+ ['1e-1', '5e-1', '1e-2', '5e-2', '1e-3', '5e-3']
329
+
330
+ """
331
+ if "(" in arg:
332
+ return collect_parentheses(arg)
333
+
334
+ if ":" not in arg:
335
+ return [arg]
336
+
337
+ arg, exponent = split_suffix(arg)
338
+
339
+ if ":" not in arg:
340
+ return [f"{arg}{exponent}"]
341
+
342
+ rng = _get_range(arg)
343
+
344
+ if all(isinstance(x, int) for x in rng):
345
+ values = [str(x) for x in _arange(*rng)]
346
+ else:
347
+ n = max(*(count_decimal_places(x) for x in arg.split(":")))
348
+ values = [str(round(x, n)) for x in _arange(*rng)]
349
+
350
+ return [add_exponent(x, exponent) for x in values]
351
+
352
+
293
353
  def split(arg: str) -> list[str]:
294
- r"""Split a string by top-level commas.
354
+ """Split a string by top-level commas.
295
355
 
296
356
  Splits a string by commas while respecting nested structures.
297
357
  Commas inside brackets and quotes are ignored, only splitting
@@ -389,7 +449,7 @@ def split_arg(arg: str) -> tuple[str, str, str]:
389
449
  key, value = arg.split("=")
390
450
 
391
451
  if "/" in key:
392
- key, suffix = key.split("/", 1)
452
+ key, suffix = key.split("/")
393
453
  return key, suffix, value
394
454
 
395
455
  return key, "", value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hydraflow
3
- Version: 0.11.1
3
+ Version: 0.12.1
4
4
  Summary: Hydraflow integrates Hydra and MLflow to manage and track machine learning experiments.
5
5
  Project-URL: Documentation, https://daizutabi.github.io/hydraflow/
6
6
  Project-URL: Source, https://github.com/daizutabi/hydraflow
@@ -5,7 +5,7 @@ hydraflow/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  hydraflow/core/config.py,sha256=SJzjgsO_kzB78_whJ3lmy7GlZvTvwZONH1BJBn8zCuI,3817
6
6
  hydraflow/core/context.py,sha256=QPyPg1xrTlmhviKNn-0nDY9bXcVky1zInqRqPN-VNhc,4741
7
7
  hydraflow/core/io.py,sha256=C207DsGAU4CLvlHySyyIl0_aKZ83ysJ-W_wNM2n4RPI,6754
8
- hydraflow/core/main.py,sha256=500fh5cnlKj9CHFgD8-f16wZcOuPF-5zSvFeVMqI8MQ,5100
8
+ hydraflow/core/main.py,sha256=66-X2-IJVEcJ4XrEC2BIKNzdd_M1RfVIAHME8k70Jo0,5131
9
9
  hydraflow/core/mlflow.py,sha256=M3MhiChnMzKnKRmjBl4h_SRGkAZKL7GAmFr3DdzwRuQ,5666
10
10
  hydraflow/core/param.py,sha256=LHU9j9_7oA99igasoOyKofKClVr9FmGA3UABJ-KmyS0,4538
11
11
  hydraflow/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -13,12 +13,12 @@ hydraflow/entities/run_collection.py,sha256=E8IRBgxCnJE_IPCaSmS2mc9GtDXXLBfc7GHv
13
13
  hydraflow/entities/run_data.py,sha256=Y2_Lc-BdQ7nXhcEIjdHGHIkLrXsmAktOftESEwYOY8o,1602
14
14
  hydraflow/entities/run_info.py,sha256=FRC6ICOlzB2u_xi_33Qs-YZLt677UotuNbYqI7XSmHY,1017
15
15
  hydraflow/executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- hydraflow/executor/conf.py,sha256=SJNiQ87MXMlpDfdm0POcv55MY3GS5FUh5wT7u3XU3oU,406
17
- hydraflow/executor/io.py,sha256=xV3m-nV9eKbu9Fb7u04J2bfmR_Ky3jTEJjq4QC2m6V4,954
18
- hydraflow/executor/job.py,sha256=yiVbAYgsZZtSTRER-H5pUopULeygzPNzKlSkl27yFI4,4856
19
- hydraflow/executor/parser.py,sha256=6oqMlAZWykukZWLpRuFUaXXqROxIyqSEacDtNOxBCjw,11894
20
- hydraflow-0.11.1.dist-info/METADATA,sha256=iPCvp7q_NK3wwRSfvE3HzxBUfJ5kQW9r9kAd3hdazGc,4549
21
- hydraflow-0.11.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
- hydraflow-0.11.1.dist-info/entry_points.txt,sha256=XI0khPbpCIUo9UPqkNEpgh-kqK3Jy8T7L2VCWOdkbSM,48
23
- hydraflow-0.11.1.dist-info/licenses/LICENSE,sha256=IGdDrBPqz1O0v_UwCW-NJlbX9Hy9b3uJ11t28y2srmY,1062
24
- hydraflow-0.11.1.dist-info/RECORD,,
16
+ hydraflow/executor/conf.py,sha256=2dv6_PlsynRmia-fGZlmBEVt8GopT0f32N13qY7tYnM,402
17
+ hydraflow/executor/io.py,sha256=yZMcBVmAbPZZ82cAXhgiJfj9p8WvHmzOCMBg_vtEVek,1509
18
+ hydraflow/executor/job.py,sha256=IL7ek0Vwa3Bl_gANq0wCbldNCUclo8YBckeEeO6W6xg,4852
19
+ hydraflow/executor/parser.py,sha256=YiA-Upn7H39BZYyvmCumtXXS27an6aDdHKkaF7Nl8Vk,13617
20
+ hydraflow-0.12.1.dist-info/METADATA,sha256=DWSgcm2oCZt8udPqgoTpQJzyIepIrjfujSggYn3MZ_U,4549
21
+ hydraflow-0.12.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
+ hydraflow-0.12.1.dist-info/entry_points.txt,sha256=XI0khPbpCIUo9UPqkNEpgh-kqK3Jy8T7L2VCWOdkbSM,48
23
+ hydraflow-0.12.1.dist-info/licenses/LICENSE,sha256=IGdDrBPqz1O0v_UwCW-NJlbX9Hy9b3uJ11t28y2srmY,1062
24
+ hydraflow-0.12.1.dist-info/RECORD,,