hydraflow 0.10.0__tar.gz → 0.10.1__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 (93) hide show
  1. hydraflow-0.10.1/.cursorrules +6 -0
  2. {hydraflow-0.10.0 → hydraflow-0.10.1}/PKG-INFO +1 -1
  3. {hydraflow-0.10.0 → hydraflow-0.10.1}/pyproject.toml +1 -1
  4. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/core/io.py +1 -1
  5. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/core/main.py +8 -4
  6. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/entities/run_collection.py +1 -1
  7. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/executor/job.py +8 -5
  8. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/executor/parser.py +38 -13
  9. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/io/test_hydra_dir.py +1 -1
  10. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/main/test_match_overrides.py +8 -2
  11. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/executor/test_job.py +27 -8
  12. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/executor/test_parser.py +74 -6
  13. {hydraflow-0.10.0 → hydraflow-0.10.1}/.devcontainer/devcontainer.json +0 -0
  14. {hydraflow-0.10.0 → hydraflow-0.10.1}/.devcontainer/postCreate.sh +0 -0
  15. {hydraflow-0.10.0 → hydraflow-0.10.1}/.devcontainer/starship.toml +0 -0
  16. {hydraflow-0.10.0 → hydraflow-0.10.1}/.gitattributes +0 -0
  17. {hydraflow-0.10.0 → hydraflow-0.10.1}/.github/workflows/ci.yaml +0 -0
  18. {hydraflow-0.10.0 → hydraflow-0.10.1}/.github/workflows/docs.yaml +0 -0
  19. {hydraflow-0.10.0 → hydraflow-0.10.1}/.gitignore +0 -0
  20. {hydraflow-0.10.0 → hydraflow-0.10.1}/LICENSE +0 -0
  21. {hydraflow-0.10.0 → hydraflow-0.10.1}/README.md +0 -0
  22. {hydraflow-0.10.0 → hydraflow-0.10.1}/apps/quickstart.py +0 -0
  23. {hydraflow-0.10.0 → hydraflow-0.10.1}/docs/index.md +0 -0
  24. {hydraflow-0.10.0 → hydraflow-0.10.1}/docs/usage/quickstart.md +0 -0
  25. {hydraflow-0.10.0 → hydraflow-0.10.1}/mkdocs.yaml +0 -0
  26. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/__init__.py +0 -0
  27. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/cli.py +0 -0
  28. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/core/__init__.py +0 -0
  29. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/core/config.py +0 -0
  30. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/core/context.py +0 -0
  31. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/core/mlflow.py +0 -0
  32. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/core/param.py +0 -0
  33. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/entities/__init__.py +0 -0
  34. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/entities/run_data.py +0 -0
  35. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/entities/run_info.py +0 -0
  36. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/executor/__init__.py +0 -0
  37. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/executor/conf.py +0 -0
  38. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/executor/io.py +0 -0
  39. {hydraflow-0.10.0 → hydraflow-0.10.1}/src/hydraflow/py.typed +0 -0
  40. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/__init__.py +0 -0
  41. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/cli/__init__.py +0 -0
  42. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/cli/app.py +0 -0
  43. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/cli/conftest.py +0 -0
  44. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/cli/hydraflow.yaml +0 -0
  45. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/cli/test_run.py +0 -0
  46. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/cli/test_setup.py +0 -0
  47. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/cli/test_show.py +0 -0
  48. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/cli/test_version.py +0 -0
  49. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/conftest.py +0 -0
  50. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/__init__.py +0 -0
  51. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/config/__init__.py +0 -0
  52. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/config/test_config.py +0 -0
  53. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/config/test_params.py +0 -0
  54. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/context/__init__.py +0 -0
  55. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/context/chdir.py +0 -0
  56. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/context/log_run.py +0 -0
  57. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/context/start_run.py +0 -0
  58. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/context/test_chdir.py +0 -0
  59. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/context/test_log_run.py +0 -0
  60. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/context/test_start_run.py +0 -0
  61. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/io/__init__.py +0 -0
  62. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/io/hydra_dir.py +0 -0
  63. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/io/test_iter_dirs.py +0 -0
  64. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/io/test_run.py +0 -0
  65. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/main/__init__.py +0 -0
  66. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/main/default.py +0 -0
  67. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/main/force_new_run.py +0 -0
  68. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/main/match_overrides.py +0 -0
  69. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/main/rerun_finished.py +0 -0
  70. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/main/skip_finished.py +0 -0
  71. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/main/test_default.py +0 -0
  72. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/main/test_force_new_run.py +0 -0
  73. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/main/test_rerun_finished.py +0 -0
  74. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/main/test_skip_finished.py +0 -0
  75. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/param/__init__.py +0 -0
  76. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/param/params.py +0 -0
  77. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/param/test_param.py +0 -0
  78. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/param/test_params.py +0 -0
  79. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/core/test_mlflow.py +0 -0
  80. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/entities/__init__.py +0 -0
  81. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/entities/filter.py +0 -0
  82. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/entities/test_collection.py +0 -0
  83. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/entities/test_data.py +0 -0
  84. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/entities/test_filter.py +0 -0
  85. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/entities/test_info.py +0 -0
  86. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/entities/test_values.py +0 -0
  87. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/entities/values.py +0 -0
  88. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/executor/__init__.py +0 -0
  89. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/executor/conftest.py +0 -0
  90. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/executor/echo.py +0 -0
  91. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/executor/test_args.py +0 -0
  92. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/executor/test_conf.py +0 -0
  93. {hydraflow-0.10.0 → hydraflow-0.10.1}/tests/executor/test_io.py +0 -0
@@ -0,0 +1,6 @@
1
+ You are an AI assistant specialized in Python development. Your approach emphasizes:
2
+
3
+ 1. Comprehensive testing with pytest.
4
+ 2. Code style consistency using Ruff.
5
+ 3. Detailed documentation using docstrings and README files.
6
+ 4. Generate Git commit messages in English.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hydraflow
3
- Version: 0.10.0
3
+ Version: 0.10.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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hydraflow"
7
- version = "0.10.0"
7
+ version = "0.10.1"
8
8
  description = "Hydraflow integrates Hydra and MLflow to manage and track machine learning experiments."
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -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:
@@ -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
@@ -99,6 +99,7 @@ def multirun(job: Job) -> None:
99
99
 
100
100
  if job.run:
101
101
  base_cmds = shlex.split(job.run)
102
+
102
103
  for args in it:
103
104
  cmds = [*base_cmds, *args]
104
105
  try:
@@ -108,22 +109,24 @@ def multirun(job: Job) -> None:
108
109
  raise RuntimeError(msg) from e
109
110
 
110
111
  elif job.call:
111
- if "." not in job.call:
112
- msg = f"Invalid function path: {job.call}."
112
+ call_name, *base_args = shlex.split(job.call)
113
+
114
+ if "." not in call_name:
115
+ msg = f"Invalid function path: {call_name}."
113
116
  msg += " Expected format: 'package.module.function'"
114
117
  raise ValueError(msg)
115
118
 
116
119
  try:
117
- module_name, func_name = job.call.rsplit(".", 1)
120
+ module_name, func_name = call_name.rsplit(".", 1)
118
121
  module = importlib.import_module(module_name)
119
122
  func = getattr(module, func_name)
120
123
  except (ImportError, AttributeError, ModuleNotFoundError) as e:
121
- msg = f"Failed to import or find function: {job.call}"
124
+ msg = f"Failed to import or find function: {call_name}"
122
125
  raise ValueError(msg) from e
123
126
 
124
127
  for args in it:
125
128
  try:
126
- func(*args)
129
+ func([*base_args, *args])
127
130
  except Exception as e: # noqa: PERF203
128
131
  msg = f"Function call '{job.call}' failed with args: {args}"
129
132
  raise RuntimeError(msg) from e
@@ -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
 
@@ -61,4 +61,4 @@ def test_load_overrides(run: Run):
61
61
  from hydraflow.core.io import load_overrides
62
62
 
63
63
  overrides = load_overrides(run)
64
- assert overrides == ["name=a", "age=10"]
64
+ assert overrides == ["age=10", "name=a"]
@@ -11,8 +11,8 @@ pytestmark = pytest.mark.xdist_group(name="group5")
11
11
  def rc(collect):
12
12
  file = Path(__file__).parent / "match_overrides.py"
13
13
  collect(file, ["-m", "count=1,2"])
14
- collect(file, ["-m", "name=a,b"])
15
- return collect(file, ["-m", "name=a,b"])
14
+ collect(file, ["-m", "name=a,b", "count=1"])
15
+ return collect(file, ["-m", "count=1", "name=a,b"])
16
16
 
17
17
 
18
18
  def test_rc_len(rc: RunCollection):
@@ -23,3 +23,9 @@ def test_config(rc: RunCollection):
23
23
  df = rc.data.config
24
24
  assert len(df) == 4
25
25
  assert len(df.drop_duplicates()) == 3
26
+
27
+
28
+ def test_equals():
29
+ from hydraflow.core.main import equals
30
+
31
+ assert equals(Path.cwd(), None, []) is False
@@ -94,27 +94,46 @@ def test_multirun_run_error(job: Job):
94
94
  multirun(job)
95
95
 
96
96
 
97
- def test_multirun_call_invalid(job: Job):
97
+ def test_multirun_call(job: Job, capsys: pytest.CaptureFixture):
98
98
  from hydraflow.executor.job import multirun
99
99
 
100
- job.call = "print"
101
- with pytest.raises(ValueError):
100
+ job.call = "typer.echo"
101
+ multirun(job)
102
+ out, _ = capsys.readouterr()
103
+ assert "'a=1,2', 'b=5'" in out
104
+ assert "'a=3,4', 'c=8'" in out
105
+
106
+
107
+ def test_multirun_call_args(job: Job, capsys: pytest.CaptureFixture):
108
+ from hydraflow.executor.job import multirun
109
+
110
+ job.call = "typer.echo a 'b c'"
111
+ multirun(job)
112
+ out, _ = capsys.readouterr()
113
+ assert "['a', 'b c', '--multirun'," in out
114
+
115
+
116
+ def test_multirun_call_error(job: Job):
117
+ from hydraflow.executor.job import multirun
118
+
119
+ job.call = "hydraflow.executor.job.multirun"
120
+ with pytest.raises(RuntimeError):
102
121
  multirun(job)
103
122
 
104
123
 
105
- def test_multirun_call_not_found(job: Job):
124
+ def test_multirun_call_invalid(job: Job):
106
125
  from hydraflow.executor.job import multirun
107
126
 
108
- job.call = "hydraflow.invalid"
127
+ job.call = "print"
109
128
  with pytest.raises(ValueError):
110
129
  multirun(job)
111
130
 
112
131
 
113
- def test_multirun_call_error(job: Job):
132
+ def test_multirun_call_not_found(job: Job):
114
133
  from hydraflow.executor.job import multirun
115
134
 
116
- job.call = "typer.echo"
117
- with pytest.raises(RuntimeError):
135
+ job.call = "hydraflow.invalid"
136
+ with pytest.raises(ValueError):
118
137
  multirun(job)
119
138
 
120
139
 
@@ -129,18 +129,37 @@ def test_collect_value(s, x):
129
129
  def test_expand_value(s, x):
130
130
  from hydraflow.executor.parser import expand_values
131
131
 
132
- assert expand_values(s) == x
132
+ assert list(expand_values(s)) == x
133
+
134
+
135
+ @pytest.mark.parametrize(
136
+ ("s", "x"),
137
+ [
138
+ ("1,2,3", ["1e3", "2e3", "3e3"]),
139
+ ("1:3,5:6", ["1e3", "2e3", "3e3", "5e3", "6e3"]),
140
+ ("0:0.25:1,2.0", ["0e3", "0.25e3", "0.5e3", "0.75e3", "1.0e3", "2.0e3"]),
141
+ ("3", ["3e3"]),
142
+ ],
143
+ )
144
+ def test_expand_value_suffix(s, x):
145
+ from hydraflow.executor.parser import expand_values
146
+
147
+ assert list(expand_values(s, "k")) == x
133
148
 
134
149
 
135
150
  @pytest.mark.parametrize(
136
151
  ("s", "x"),
137
152
  [
138
153
  ("a=1", "a=1"),
154
+ ("a/M=1", "a=1e6"),
139
155
  ("a=1,2", "a=1,2"),
156
+ ("a/n=1,2", "a=1e-9,2e-9"),
140
157
  ("a=1:2", "a=1,2"),
158
+ ("a/M=1:2", "a=1e6,2e6"),
141
159
  ("a=:2:3", "a=0,2"),
142
160
  ("a=1:3:k", "a=1e3,2e3,3e3"),
143
161
  ("a=1:3:k,2:4:M", "a=1e3,2e3,3e3,2e6,3e6,4e6"),
162
+ ("a/m=1:3,8:10", "a=1e-3,2e-3,3e-3,8e-3,9e-3,10e-3"),
144
163
  ],
145
164
  )
146
165
  def test_collect_arg(s, x):
@@ -154,13 +173,17 @@ def test_collect_arg(s, x):
154
173
  [
155
174
  ("a=1", ["a=1"]),
156
175
  ("a=1,2", ["a=1", "a=2"]),
176
+ ("a/M=1,2", ["a=1e6", "a=2e6"]),
157
177
  ("a=1:2", ["a=1", "a=2"]),
178
+ ("a/n=1:2", ["a=1e-9", "a=2e-9"]),
158
179
  ("a=:2:3", ["a=0", "a=2"]),
159
180
  ("a=1:3:k", ["a=1e3", "a=2e3", "a=3e3"]),
160
181
  ("a=1:3:k,2:4:M", ["a=1e3", "a=2e3", "a=3e3", "a=2e6", "a=3e6", "a=4e6"]),
161
182
  ("a=1,2|3,4", ["a=1,2", "a=3,4"]),
183
+ ("a/G=1,2|3,4", ["a=1e9,2e9", "a=3e9,4e9"]),
162
184
  ("a=1:4|3:5:m", ["a=1,2,3,4", "a=3e-3,4e-3,5e-3"]),
163
185
  ("a=1,2|b=3,4|c=5,6", ["a=1,2", "b=3,4", "c=5,6"]),
186
+ ("a/k=1,2|b/m=3,4|c/u=5,6", ["a=1e3,2e3", "b=3e-3,4e-3", "c=5e-6,6e-6"]),
164
187
  ],
165
188
  )
166
189
  def test_expand_arg(s, x):
@@ -179,14 +202,29 @@ def test_expand_arg_error():
179
202
  @pytest.mark.parametrize(
180
203
  ("s", "x"),
181
204
  [
182
- (["a=1"], ["a=1"]),
205
+ (["a=1", "b"], ["a=1"]),
183
206
  (["a=1:3"], ["a=1,2,3"]),
207
+ (["a/m=1:3"], ["a=1e-3,2e-3,3e-3"]),
184
208
  (["a=1:3", "b=4:6"], ["a=1,2,3", "b=4,5,6"]),
209
+ (["a/k=1:3", "b/m=4:6"], ["a=1e3,2e3,3e3", "b=4e-3,5e-3,6e-3"]),
210
+ ],
211
+ )
212
+ def test_collect_list(s, x):
213
+ from hydraflow.executor.parser import collect
214
+
215
+ assert collect(s) == x
216
+
217
+
218
+ @pytest.mark.parametrize(
219
+ ("s", "x"),
220
+ [
185
221
  ("a=1:3\nb=4:6", ["a=1,2,3", "b=4,5,6"]),
222
+ ("a/k=1:3 b=4:6", ["a=1e3,2e3,3e3", "b=4,5,6"]),
223
+ ("a/n=4,5 b=c,d", ["a=4e-9,5e-9", "b=c,d"]),
186
224
  ("", []),
187
225
  ],
188
226
  )
189
- def test_collect(s, x):
227
+ def test_collect_str(s, x):
190
228
  from hydraflow.executor.parser import collect
191
229
 
192
230
  assert collect(s) == x
@@ -195,13 +233,14 @@ def test_collect(s, x):
195
233
  @pytest.mark.parametrize(
196
234
  ("s", "x"),
197
235
  [
198
- (["a=1"], [["a=1"]]),
199
- (["a=1,2"], [["a=1"], ["a=2"]]),
236
+ (["a=1", "b"], [["a=1"]]),
237
+ (["a/k=1,2"], [["a=1e3"], ["a=2e3"]]),
200
238
  (
201
239
  " a=1,2\n b=3,4\n",
202
240
  [["a=1", "b=3"], ["a=1", "b=4"], ["a=2", "b=3"], ["a=2", "b=4"]],
203
241
  ),
204
242
  (["a=1:2|3,4"], [["a=1,2"], ["a=3,4"]]),
243
+ (["a/k=1:2|3,4"], [["a=1e3,2e3"], ["a=3e3,4e3"]]),
205
244
  (
206
245
  ["a=1:2|3,4", "b=5:6|c=7,8"],
207
246
  [
@@ -211,10 +250,39 @@ def test_collect(s, x):
211
250
  ["a=3,4", "c=7,8"],
212
251
  ],
213
252
  ),
253
+ (
254
+ ["a/m=1:2|3,4", "b/k=5:6|c/u=7,8"],
255
+ [
256
+ ["a=1e-3,2e-3", "b=5e3,6e3"],
257
+ ["a=1e-3,2e-3", "c=7e-6,8e-6"],
258
+ ["a=3e-3,4e-3", "b=5e3,6e3"],
259
+ ["a=3e-3,4e-3", "c=7e-6,8e-6"],
260
+ ],
261
+ ),
262
+ ],
263
+ )
264
+ def test_expand_list(s, x):
265
+ from hydraflow.executor.parser import expand
266
+
267
+ assert expand(s) == x
268
+
269
+
270
+ @pytest.mark.parametrize(
271
+ ("s", "x"),
272
+ [
273
+ (
274
+ "a/m=1:2|3,4 b/k=5:6|c=7,8",
275
+ [
276
+ ["a=1e-3,2e-3", "b=5e3,6e3"],
277
+ ["a=1e-3,2e-3", "c=7,8"],
278
+ ["a=3e-3,4e-3", "b=5e3,6e3"],
279
+ ["a=3e-3,4e-3", "c=7,8"],
280
+ ],
281
+ ),
214
282
  ("", [[]]),
215
283
  ],
216
284
  )
217
- def test_expand(s, x):
285
+ def test_expand_str(s, x):
218
286
  from hydraflow.executor.parser import expand
219
287
 
220
288
  assert expand(s) == x
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