hydraflow 0.10.0__py3-none-any.whl → 0.10.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/cli.py CHANGED
@@ -2,54 +2,53 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Annotated
5
+ from typing import Annotated
6
6
 
7
7
  import typer
8
8
  from rich.console import Console
9
9
  from typer import Argument, Option
10
10
 
11
- from hydraflow.executor.io import load_config
12
-
13
- if TYPE_CHECKING:
14
- from hydraflow.executor.job import Job
11
+ from hydraflow.executor.io import get_job, load_config
15
12
 
16
13
  app = typer.Typer(add_completion=False)
17
14
  console = Console()
18
15
 
19
16
 
20
- def get_job(name: str) -> Job:
21
- cfg = load_config()
22
- job = cfg.jobs[name]
23
-
24
- if not job.name:
25
- job.name = name
26
-
27
- return job
28
-
29
-
30
17
  @app.command()
31
18
  def run(
32
19
  name: Annotated[str, Argument(help="Job name.", show_default=False)],
20
+ *,
21
+ dry_run: Annotated[
22
+ bool,
23
+ Option("--dry-run", help="Perform a dry run"),
24
+ ] = False,
33
25
  ) -> None:
34
26
  """Run a job."""
35
27
  import mlflow
36
28
 
37
- from hydraflow.executor.job import multirun
29
+ from hydraflow.executor.job import multirun, to_text
38
30
 
39
31
  job = get_job(name)
40
- mlflow.set_experiment(job.name)
41
- multirun(job)
32
+ if dry_run:
33
+ typer.echo(to_text(job))
34
+ else:
35
+ mlflow.set_experiment(job.name)
36
+ multirun(job)
42
37
 
43
38
 
44
39
  @app.command()
45
40
  def show(
46
- name: Annotated[str, Argument(help="Job name.", show_default=False)],
41
+ name: Annotated[str, Argument(help="Job name.", show_default=False)] = "",
47
42
  ) -> None:
48
- """Show a job."""
49
- from hydraflow.executor.job import show
43
+ """Show the hydraflow config."""
44
+ from omegaconf import OmegaConf
50
45
 
51
- job = get_job(name)
52
- show(job)
46
+ if name:
47
+ cfg = get_job(name)
48
+ else:
49
+ cfg = load_config()
50
+
51
+ typer.echo(OmegaConf.to_yaml(cfg))
53
52
 
54
53
 
55
54
  @app.callback(invoke_without_command=True)
hydraflow/core/io.py CHANGED
@@ -136,7 +136,7 @@ def load_overrides(run: Run) -> ListConfig:
136
136
 
137
137
  """
138
138
  path = get_artifact_dir(run) / ".hydra/overrides.yaml"
139
- return OmegaConf.load(path) # type: ignore
139
+ return sorted(OmegaConf.load(path)) # type: ignore
140
140
 
141
141
 
142
142
  def remove_run(run: Run | Iterable[Run]) -> None:
hydraflow/core/main.py CHANGED
@@ -154,11 +154,15 @@ def equals(run_dir: Path, config: Any, overrides: list[str] | None) -> bool:
154
154
  """
155
155
  if overrides is None:
156
156
  path = run_dir / "artifacts/.hydra/config.yaml"
157
- else:
158
- path = run_dir / "artifacts/.hydra/overrides.yaml"
159
- config = overrides
157
+
158
+ if not path.exists():
159
+ return False
160
+
161
+ return OmegaConf.load(path) == config
162
+
163
+ path = run_dir / "artifacts/.hydra/overrides.yaml"
160
164
 
161
165
  if not path.exists():
162
166
  return False
163
167
 
164
- return OmegaConf.load(path) == config
168
+ return sorted(OmegaConf.load(path)) == sorted(overrides)
@@ -116,7 +116,7 @@ class RunCollection:
116
116
  but not in the other.
117
117
 
118
118
  """
119
- runs = [run for run in self._runs if run not in other._runs] # noqa: SLF001
119
+ runs = [run for run in self._runs if run not in other._runs]
120
120
  return self.__class__(runs)
121
121
 
122
122
  @property
hydraflow/executor/io.py CHANGED
@@ -3,11 +3,15 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from pathlib import Path
6
+ from typing import TYPE_CHECKING
6
7
 
7
8
  from omegaconf import OmegaConf
8
9
 
9
10
  from .conf import HydraflowConf
10
11
 
12
+ if TYPE_CHECKING:
13
+ from .job import Job
14
+
11
15
 
12
16
  def find_config_file() -> Path | None:
13
17
  """Find the hydraflow config file."""
@@ -32,3 +36,14 @@ def load_config() -> HydraflowConf:
32
36
  cfg = OmegaConf.load(path)
33
37
 
34
38
  return OmegaConf.merge(schema, cfg) # type: ignore
39
+
40
+
41
+ def get_job(name: str) -> Job:
42
+ """Get a job from the config."""
43
+ cfg = load_config()
44
+ job = cfg.jobs[name]
45
+
46
+ if not job.name:
47
+ job.name = name
48
+
49
+ return job
hydraflow/executor/job.py CHANGED
@@ -20,6 +20,7 @@ from __future__ import annotations
20
20
  import importlib
21
21
  import shlex
22
22
  import subprocess
23
+ import sys
23
24
  from subprocess import CalledProcessError
24
25
  from typing import TYPE_CHECKING
25
26
 
@@ -99,6 +100,9 @@ def multirun(job: Job) -> None:
99
100
 
100
101
  if job.run:
101
102
  base_cmds = shlex.split(job.run)
103
+ if base_cmds[0] == "python" and sys.platform == "win32":
104
+ base_cmds[0] = sys.executable
105
+
102
106
  for args in it:
103
107
  cmds = [*base_cmds, *args]
104
108
  try:
@@ -108,45 +112,54 @@ def multirun(job: Job) -> None:
108
112
  raise RuntimeError(msg) from e
109
113
 
110
114
  elif job.call:
111
- if "." not in job.call:
112
- msg = f"Invalid function path: {job.call}."
115
+ call_name, *base_args = shlex.split(job.call)
116
+
117
+ if "." not in call_name:
118
+ msg = f"Invalid function path: {call_name}."
113
119
  msg += " Expected format: 'package.module.function'"
114
120
  raise ValueError(msg)
115
121
 
116
122
  try:
117
- module_name, func_name = job.call.rsplit(".", 1)
123
+ module_name, func_name = call_name.rsplit(".", 1)
118
124
  module = importlib.import_module(module_name)
119
125
  func = getattr(module, func_name)
120
126
  except (ImportError, AttributeError, ModuleNotFoundError) as e:
121
- msg = f"Failed to import or find function: {job.call}"
127
+ msg = f"Failed to import or find function: {call_name}"
122
128
  raise ValueError(msg) from e
123
129
 
124
130
  for args in it:
125
131
  try:
126
- func(*args)
132
+ func([*base_args, *args])
127
133
  except Exception as e: # noqa: PERF203
128
134
  msg = f"Function call '{job.call}' failed with args: {args}"
129
135
  raise RuntimeError(msg) from e
130
136
 
131
137
 
132
- def show(job: Job) -> None:
133
- """Show the job configuration.
138
+ def to_text(job: Job) -> str:
139
+ """Convert the job configuration to a string.
134
140
 
135
- This function shows the job configuration for a given job.
141
+ This function returns the job configuration for a given job.
136
142
 
137
143
  Args:
138
144
  job (Job): The job configuration to show.
139
145
 
146
+ Returns:
147
+ str: The job configuration.
148
+
140
149
  """
150
+ text = ""
151
+
141
152
  it = iter_batches(job)
142
153
 
143
154
  if job.run:
144
155
  base_cmds = shlex.split(job.run)
145
156
  for args in it:
146
157
  cmds = " ".join([*base_cmds, *args])
147
- print(cmds) # noqa: T201
158
+ text += f"{cmds}\n"
148
159
 
149
160
  elif job.call:
150
- print(f"call: {job.call}") # noqa: T201
161
+ text = f"call: {job.call}\n"
151
162
  for args in it:
152
- print(f"args: {args}") # noqa: T201
163
+ text += f"args: {args}\n"
164
+
165
+ return text
@@ -8,7 +8,7 @@ ranges, and expand values from string arguments.
8
8
 
9
9
  from __future__ import annotations
10
10
 
11
- import re
11
+ import shlex
12
12
  from itertools import chain, product
13
13
  from typing import TYPE_CHECKING
14
14
 
@@ -289,7 +289,7 @@ def split(arg: str) -> list[str]:
289
289
  return result
290
290
 
291
291
 
292
- def expand_values(arg: str) -> list[str]:
292
+ def expand_values(arg: str, suffix: str = "") -> Iterator[str]:
293
293
  """Expand a string argument into a list of values.
294
294
 
295
295
  Take a string containing comma-separated values or ranges and return a list
@@ -297,12 +297,36 @@ def expand_values(arg: str) -> list[str]:
297
297
 
298
298
  Args:
299
299
  arg (str): The argument to expand.
300
+ suffix (str): The suffix to append to each value.
300
301
 
301
302
  Returns:
302
- list[str]: A list of the expanded values.
303
+ Iterator[str]: An iterator of the expanded values.
303
304
 
304
305
  """
305
- return list(chain.from_iterable(collect_values(x) for x in split(arg)))
306
+ if suffix in SUFFIX_EXPONENT:
307
+ suffix = SUFFIX_EXPONENT[suffix]
308
+
309
+ for value in chain.from_iterable(collect_values(x) for x in split(arg)):
310
+ yield f"{value}{suffix}"
311
+
312
+
313
+ def split_arg(arg: str) -> tuple[str, str, str]:
314
+ """Split an argument into a key, suffix, and value.
315
+
316
+ Args:
317
+ arg (str): The argument to split.
318
+
319
+ Returns:
320
+ tuple[str, str, str]: A tuple containing the key, suffix, and value.
321
+
322
+ """
323
+ key, value = arg.split("=")
324
+
325
+ if "/" in key:
326
+ key, suffix = key.split("/", 1)
327
+ return key, suffix, value
328
+
329
+ return key, "", value
306
330
 
307
331
 
308
332
  def collect_arg(arg: str) -> str:
@@ -318,9 +342,9 @@ def collect_arg(arg: str) -> str:
318
342
  str: A string of the collected key and values.
319
343
 
320
344
  """
321
- key, arg = arg.split("=")
322
- arg = ",".join(expand_values(arg))
323
- return f"{key}={arg}"
345
+ key, suffix, value = split_arg(arg)
346
+ value = ",".join(expand_values(value, suffix))
347
+ return f"{key}={value}"
324
348
 
325
349
 
326
350
  def expand_arg(arg: str) -> Iterator[str]:
@@ -338,26 +362,27 @@ def expand_arg(arg: str) -> Iterator[str]:
338
362
 
339
363
  """
340
364
  if "|" not in arg:
341
- key, value = arg.split("=")
365
+ key, suffix, value = split_arg(arg)
342
366
 
343
- for v in expand_values(value):
367
+ for v in expand_values(value, suffix):
344
368
  yield f"{key}={v}"
345
369
 
346
370
  return
347
371
 
348
372
  args = arg.split("|")
349
373
  key = ""
374
+ suffix = ""
350
375
 
351
376
  for arg_ in args:
352
377
  if "=" in arg_:
353
- key, value = arg_.split("=")
378
+ key, suffix, value = split_arg(arg_)
354
379
  elif key:
355
380
  value = arg_
356
381
  else:
357
382
  msg = f"Invalid argument: {arg_}"
358
383
  raise ValueError(msg)
359
384
 
360
- value = ",".join(expand_values(value))
385
+ value = ",".join(expand_values(value, suffix))
361
386
  yield f"{key}={value}"
362
387
 
363
388
 
@@ -372,7 +397,7 @@ def collect(args: str | list[str]) -> list[str]:
372
397
 
373
398
  """
374
399
  if isinstance(args, str):
375
- args = re.split(r"\s+", args.strip())
400
+ args = shlex.split(args)
376
401
 
377
402
  args = [arg for arg in args if "=" in arg]
378
403
 
@@ -390,7 +415,7 @@ def expand(args: str | list[str]) -> list[list[str]]:
390
415
 
391
416
  """
392
417
  if isinstance(args, str):
393
- args = re.split(r"\s+", args.strip())
418
+ args = shlex.split(args)
394
419
 
395
420
  args = [arg for arg in args if "=" in arg]
396
421
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hydraflow
3
- Version: 0.10.0
3
+ Version: 0.10.2
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
@@ -1,24 +1,24 @@
1
1
  hydraflow/__init__.py,sha256=f2KO2iF7um-nNmayNyEr7TWG4UICOXy7YAN1d3qu0OY,936
2
- hydraflow/cli.py,sha256=gbDPj49azP8CCGxkxU0rksh1-gCyjP0VkVYH34ktcsA,1338
2
+ hydraflow/cli.py,sha256=b-M368amGUblOOqOi7JuBsnzLuaNqgpTit9ZRj0qqac,1410
3
3
  hydraflow/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  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
- hydraflow/core/io.py,sha256=KK9_mkx4XnBgA63Ab-g833N5q9aCsSrJR-7pp6unEKw,6746
8
- hydraflow/core/main.py,sha256=gYb1OOVH0CL4385Dm-06Mqi1Mr9-24URwLUiW86pGNs,5018
7
+ hydraflow/core/io.py,sha256=C207DsGAU4CLvlHySyyIl0_aKZ83ysJ-W_wNM2n4RPI,6754
8
+ hydraflow/core/main.py,sha256=500fh5cnlKj9CHFgD8-f16wZcOuPF-5zSvFeVMqI8MQ,5100
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
12
- hydraflow/entities/run_collection.py,sha256=BHbMvYB4WyG5qSNYpIzwmHySOtmGNb-jg2OrCaTPr2I,19651
12
+ hydraflow/entities/run_collection.py,sha256=E8IRBgxCnJE_IPCaSmS2mc9GtDXXLBfc7GHv07d2j98,19635
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
16
  hydraflow/executor/conf.py,sha256=q_FrPXQJCVGKS1FYnGRGqTUgMQeMBkaVPW2mtQc8oxk,384
17
- hydraflow/executor/io.py,sha256=4nafwge6vHanYFuEHxd0LRv_3ZLgMpV50qSbssZNe3Q,696
18
- hydraflow/executor/job.py,sha256=Lji8hsalwsUyxZIoGc2XO2jrXV_YysUMZ7dlBhcQKiI,4479
19
- hydraflow/executor/parser.py,sha256=y4C9wVdUnazJDxdWrT5y3yWFIo0zAGzO-cS9x1MTK_8,9486
20
- hydraflow-0.10.0.dist-info/METADATA,sha256=Oe04r5_IvW0YBddGrjQRdu_dNPdgyDb2K_2_2WuawNk,4574
21
- hydraflow-0.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
- hydraflow-0.10.0.dist-info/entry_points.txt,sha256=XI0khPbpCIUo9UPqkNEpgh-kqK3Jy8T7L2VCWOdkbSM,48
23
- hydraflow-0.10.0.dist-info/licenses/LICENSE,sha256=IGdDrBPqz1O0v_UwCW-NJlbX9Hy9b3uJ11t28y2srmY,1062
24
- hydraflow-0.10.0.dist-info/RECORD,,
17
+ hydraflow/executor/io.py,sha256=xV3m-nV9eKbu9Fb7u04J2bfmR_Ky3jTEJjq4QC2m6V4,954
18
+ hydraflow/executor/job.py,sha256=YZ9JuVrdAcG3_HvNngHy9NL4-3bjJTaColE_5efF8TU,4743
19
+ hydraflow/executor/parser.py,sha256=MO8VU0uVQZeku6kbw8Urid_5QEcnR8atd5h-yDP5OhQ,10147
20
+ hydraflow-0.10.2.dist-info/METADATA,sha256=BidbFHVOZjuNytZBhYAg9_T5OBkmu4VNprJJFxnCqPI,4574
21
+ hydraflow-0.10.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
+ hydraflow-0.10.2.dist-info/entry_points.txt,sha256=XI0khPbpCIUo9UPqkNEpgh-kqK3Jy8T7L2VCWOdkbSM,48
23
+ hydraflow-0.10.2.dist-info/licenses/LICENSE,sha256=IGdDrBPqz1O0v_UwCW-NJlbX9Hy9b3uJ11t28y2srmY,1062
24
+ hydraflow-0.10.2.dist-info/RECORD,,