hpcflow-new2 0.2.0a188__py3-none-any.whl → 0.2.0a190__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.
Files changed (115) hide show
  1. hpcflow/__pyinstaller/hook-hpcflow.py +8 -6
  2. hpcflow/_version.py +1 -1
  3. hpcflow/app.py +1 -0
  4. hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +1 -1
  5. hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +1 -1
  6. hpcflow/sdk/__init__.py +21 -15
  7. hpcflow/sdk/app.py +2133 -770
  8. hpcflow/sdk/cli.py +281 -250
  9. hpcflow/sdk/cli_common.py +6 -2
  10. hpcflow/sdk/config/__init__.py +1 -1
  11. hpcflow/sdk/config/callbacks.py +77 -42
  12. hpcflow/sdk/config/cli.py +126 -103
  13. hpcflow/sdk/config/config.py +578 -311
  14. hpcflow/sdk/config/config_file.py +131 -95
  15. hpcflow/sdk/config/errors.py +112 -85
  16. hpcflow/sdk/config/types.py +145 -0
  17. hpcflow/sdk/core/actions.py +1054 -994
  18. hpcflow/sdk/core/app_aware.py +24 -0
  19. hpcflow/sdk/core/cache.py +81 -63
  20. hpcflow/sdk/core/command_files.py +275 -185
  21. hpcflow/sdk/core/commands.py +111 -107
  22. hpcflow/sdk/core/element.py +724 -503
  23. hpcflow/sdk/core/enums.py +192 -0
  24. hpcflow/sdk/core/environment.py +74 -93
  25. hpcflow/sdk/core/errors.py +398 -51
  26. hpcflow/sdk/core/json_like.py +540 -272
  27. hpcflow/sdk/core/loop.py +380 -334
  28. hpcflow/sdk/core/loop_cache.py +160 -43
  29. hpcflow/sdk/core/object_list.py +370 -207
  30. hpcflow/sdk/core/parameters.py +728 -600
  31. hpcflow/sdk/core/rule.py +59 -41
  32. hpcflow/sdk/core/run_dir_files.py +33 -22
  33. hpcflow/sdk/core/task.py +1546 -1325
  34. hpcflow/sdk/core/task_schema.py +240 -196
  35. hpcflow/sdk/core/test_utils.py +126 -88
  36. hpcflow/sdk/core/types.py +387 -0
  37. hpcflow/sdk/core/utils.py +410 -305
  38. hpcflow/sdk/core/validation.py +82 -9
  39. hpcflow/sdk/core/workflow.py +1192 -1028
  40. hpcflow/sdk/core/zarr_io.py +98 -137
  41. hpcflow/sdk/demo/cli.py +46 -33
  42. hpcflow/sdk/helper/cli.py +18 -16
  43. hpcflow/sdk/helper/helper.py +75 -63
  44. hpcflow/sdk/helper/watcher.py +61 -28
  45. hpcflow/sdk/log.py +83 -59
  46. hpcflow/sdk/persistence/__init__.py +8 -31
  47. hpcflow/sdk/persistence/base.py +988 -586
  48. hpcflow/sdk/persistence/defaults.py +6 -0
  49. hpcflow/sdk/persistence/discovery.py +38 -0
  50. hpcflow/sdk/persistence/json.py +408 -153
  51. hpcflow/sdk/persistence/pending.py +158 -123
  52. hpcflow/sdk/persistence/store_resource.py +37 -22
  53. hpcflow/sdk/persistence/types.py +307 -0
  54. hpcflow/sdk/persistence/utils.py +14 -11
  55. hpcflow/sdk/persistence/zarr.py +477 -420
  56. hpcflow/sdk/runtime.py +44 -41
  57. hpcflow/sdk/submission/{jobscript_info.py → enums.py} +39 -12
  58. hpcflow/sdk/submission/jobscript.py +444 -404
  59. hpcflow/sdk/submission/schedulers/__init__.py +133 -40
  60. hpcflow/sdk/submission/schedulers/direct.py +97 -71
  61. hpcflow/sdk/submission/schedulers/sge.py +132 -126
  62. hpcflow/sdk/submission/schedulers/slurm.py +263 -268
  63. hpcflow/sdk/submission/schedulers/utils.py +7 -2
  64. hpcflow/sdk/submission/shells/__init__.py +14 -15
  65. hpcflow/sdk/submission/shells/base.py +102 -29
  66. hpcflow/sdk/submission/shells/bash.py +72 -55
  67. hpcflow/sdk/submission/shells/os_version.py +31 -30
  68. hpcflow/sdk/submission/shells/powershell.py +37 -29
  69. hpcflow/sdk/submission/submission.py +203 -257
  70. hpcflow/sdk/submission/types.py +143 -0
  71. hpcflow/sdk/typing.py +163 -12
  72. hpcflow/tests/conftest.py +8 -6
  73. hpcflow/tests/schedulers/slurm/test_slurm_submission.py +5 -2
  74. hpcflow/tests/scripts/test_main_scripts.py +60 -30
  75. hpcflow/tests/shells/wsl/test_wsl_submission.py +6 -4
  76. hpcflow/tests/unit/test_action.py +86 -75
  77. hpcflow/tests/unit/test_action_rule.py +9 -4
  78. hpcflow/tests/unit/test_app.py +13 -6
  79. hpcflow/tests/unit/test_cli.py +1 -1
  80. hpcflow/tests/unit/test_command.py +71 -54
  81. hpcflow/tests/unit/test_config.py +20 -15
  82. hpcflow/tests/unit/test_config_file.py +21 -18
  83. hpcflow/tests/unit/test_element.py +58 -62
  84. hpcflow/tests/unit/test_element_iteration.py +3 -1
  85. hpcflow/tests/unit/test_element_set.py +29 -19
  86. hpcflow/tests/unit/test_group.py +4 -2
  87. hpcflow/tests/unit/test_input_source.py +116 -93
  88. hpcflow/tests/unit/test_input_value.py +29 -24
  89. hpcflow/tests/unit/test_json_like.py +44 -35
  90. hpcflow/tests/unit/test_loop.py +65 -58
  91. hpcflow/tests/unit/test_object_list.py +17 -12
  92. hpcflow/tests/unit/test_parameter.py +16 -7
  93. hpcflow/tests/unit/test_persistence.py +48 -35
  94. hpcflow/tests/unit/test_resources.py +20 -18
  95. hpcflow/tests/unit/test_run.py +8 -3
  96. hpcflow/tests/unit/test_runtime.py +2 -1
  97. hpcflow/tests/unit/test_schema_input.py +23 -15
  98. hpcflow/tests/unit/test_shell.py +3 -2
  99. hpcflow/tests/unit/test_slurm.py +8 -7
  100. hpcflow/tests/unit/test_submission.py +39 -19
  101. hpcflow/tests/unit/test_task.py +352 -247
  102. hpcflow/tests/unit/test_task_schema.py +33 -20
  103. hpcflow/tests/unit/test_utils.py +9 -11
  104. hpcflow/tests/unit/test_value_sequence.py +15 -12
  105. hpcflow/tests/unit/test_workflow.py +114 -83
  106. hpcflow/tests/unit/test_workflow_template.py +0 -1
  107. hpcflow/tests/workflows/test_jobscript.py +2 -1
  108. hpcflow/tests/workflows/test_workflows.py +18 -13
  109. {hpcflow_new2-0.2.0a188.dist-info → hpcflow_new2-0.2.0a190.dist-info}/METADATA +2 -1
  110. hpcflow_new2-0.2.0a190.dist-info/RECORD +165 -0
  111. hpcflow/sdk/core/parallel.py +0 -21
  112. hpcflow_new2-0.2.0a188.dist-info/RECORD +0 -158
  113. {hpcflow_new2-0.2.0a188.dist-info → hpcflow_new2-0.2.0a190.dist-info}/LICENSE +0 -0
  114. {hpcflow_new2-0.2.0a188.dist-info → hpcflow_new2-0.2.0a190.dist-info}/WHEEL +0 -0
  115. {hpcflow_new2-0.2.0a188.dist-info → hpcflow_new2-0.2.0a190.dist-info}/entry_points.txt +0 -0
@@ -2,19 +2,43 @@
2
2
  Utilities for making data to use in testing.
3
3
  """
4
4
 
5
+ from __future__ import annotations
5
6
  from dataclasses import dataclass
6
- from importlib import resources
7
7
  from pathlib import Path
8
- from typing import Dict, List, Optional, Tuple, Union
8
+ from typing import Any, ClassVar, TYPE_CHECKING
9
9
  from hpcflow.app import app as hf
10
10
  from hpcflow.sdk.core.parameters import ParameterValue
11
-
12
-
13
- def make_schemas(ins_outs, ret_list=False):
11
+ from hpcflow.sdk.core.utils import get_file_context
12
+ from hpcflow.sdk.typing import hydrate
13
+
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Iterable, Mapping
16
+ from typing_extensions import TypeAlias
17
+ from h5py import Group as HDFSGroup # type: ignore
18
+ from .actions import Action
19
+ from .element import ElementGroup
20
+ from .loop import Loop
21
+ from .parameters import InputSource, Parameter
22
+ from .task import Task
23
+ from .task_schema import TaskSchema
24
+ from .types import Resources
25
+ from .workflow import Workflow, WorkflowTemplate
26
+ from ..app import BaseApp
27
+ from ..typing import PathLike
28
+ # mypy: disable-error-code="no-untyped-def"
29
+
30
+ #: A string or a tuple of strings.
31
+ Strs: TypeAlias = "str | tuple[str, ...]"
32
+
33
+
34
+ def make_schemas(
35
+ *ins_outs: tuple[dict[str, Any], tuple[str, ...]]
36
+ | tuple[dict[str, Any], tuple[str, ...], str]
37
+ ) -> list[TaskSchema]:
14
38
  """
15
39
  Construct a collection of schemas.
16
40
  """
17
- out = []
41
+ out: list[TaskSchema] = []
18
42
  for idx, info in enumerate(ins_outs):
19
43
  if len(info) == 2:
20
44
  (ins_i, outs_i) = info
@@ -40,7 +64,7 @@ def make_schemas(ins_outs, ret_list=False):
40
64
  for out_i in outs_i[2:]
41
65
  ]
42
66
  cmd = hf.Command(
43
- " ".join(f"echo $((<<parameter:{i}>> + 100))" for i in ins_i.keys()),
67
+ " ".join(f"echo $((<<parameter:{i}>> + 100))" for i in ins_i),
44
68
  stdout=stdout,
45
69
  stderr=stderr,
46
70
  )
@@ -55,15 +79,13 @@ def make_schemas(ins_outs, ret_list=False):
55
79
  objective=obj,
56
80
  actions=[act_i],
57
81
  inputs=[hf.SchemaInput(k, default_value=v) for k, v in ins_i.items()],
58
- outputs=[hf.SchemaOutput(k) for k in outs_i],
82
+ outputs=[hf.SchemaOutput(hf.Parameter(k)) for k in outs_i],
59
83
  )
60
84
  )
61
- if len(ins_outs) == 1 and not ret_list:
62
- out = out[0]
63
85
  return out
64
86
 
65
87
 
66
- def make_parameters(num):
88
+ def make_parameters(num: int) -> list[Parameter]:
67
89
  """
68
90
  Construct a sequence of parameters.
69
91
  """
@@ -71,9 +93,9 @@ def make_parameters(num):
71
93
 
72
94
 
73
95
  def make_actions(
74
- ins_outs: List[Tuple[Union[Tuple, str], str]],
75
- env="env1",
76
- ) -> List[hf.Action]:
96
+ ins_outs: list[tuple[Strs, str] | tuple[Strs, str, str]],
97
+ env: str = "env1",
98
+ ) -> list[Action]:
77
99
  """
78
100
  Construct a collection of actions.
79
101
  """
@@ -82,7 +104,7 @@ def make_actions(
82
104
  for ins_outs_i in ins_outs:
83
105
  if len(ins_outs_i) == 2:
84
106
  ins, out = ins_outs_i
85
- err = None
107
+ err: str | None = None
86
108
  else:
87
109
  ins, out, err = ins_outs_i
88
110
  if not isinstance(ins, tuple):
@@ -103,14 +125,19 @@ def make_actions(
103
125
 
104
126
 
105
127
  def make_tasks(
106
- schemas_spec,
107
- local_inputs=None,
108
- local_sequences=None,
109
- local_resources=None,
110
- nesting_orders=None,
111
- input_sources=None,
112
- groups=None,
113
- ):
128
+ schemas_spec: Iterable[
129
+ tuple[dict[str, Any], tuple[str, ...]]
130
+ | tuple[dict[str, Any], tuple[str, ...], str]
131
+ ],
132
+ local_inputs: dict[int, Iterable[str]] | None = None,
133
+ local_sequences: (
134
+ dict[int, Iterable[tuple[str, int, int | float | None]]] | None
135
+ ) = None,
136
+ local_resources: dict[int, dict[str, dict]] | None = None,
137
+ nesting_orders: dict[int, dict[str, float]] | None = None,
138
+ input_sources: dict[int, dict[str, list[InputSource]]] | None = None,
139
+ groups: dict[int, Iterable[ElementGroup]] | None = None,
140
+ ) -> list[Task]:
114
141
  """
115
142
  Construct a sequence of tasks.
116
143
  """
@@ -120,12 +147,12 @@ def make_tasks(
120
147
  nesting_orders = nesting_orders or {}
121
148
  input_sources = input_sources or {}
122
149
  groups = groups or {}
123
- schemas = make_schemas(schemas_spec, ret_list=True)
124
- tasks = []
150
+ schemas = make_schemas(*schemas_spec)
151
+ tasks: list[Task] = []
125
152
  for s_idx, s in enumerate(schemas):
126
153
  inputs = [
127
154
  hf.InputValue(hf.Parameter(i), value=int(i[1:]) * 100)
128
- for i in local_inputs.get(s_idx, [])
155
+ for i in local_inputs.get(s_idx, ())
129
156
  ]
130
157
  seqs = [
131
158
  hf.ValueSequence(
@@ -133,7 +160,7 @@ def make_tasks(
133
160
  values=[(int(i[0].split(".")[1][1:]) * 100) + j for j in range(i[1])],
134
161
  nesting_order=i[2],
135
162
  )
136
- for i in local_sequences.get(s_idx, [])
163
+ for i in local_sequences.get(s_idx, ())
137
164
  ]
138
165
  res = {k: v for k, v in local_resources.get(s_idx, {}).items()}
139
166
  task = hf.Task(
@@ -143,27 +170,32 @@ def make_tasks(
143
170
  resources=res,
144
171
  nesting_order=nesting_orders.get(s_idx, {}),
145
172
  input_sources=input_sources.get(s_idx, None),
146
- groups=groups.get(s_idx),
173
+ groups=list(groups.get(s_idx, ())),
147
174
  )
148
175
  tasks.append(task)
149
176
  return tasks
150
177
 
151
178
 
152
179
  def make_workflow(
153
- schemas_spec,
154
- path,
155
- local_inputs=None,
156
- local_sequences=None,
157
- local_resources=None,
158
- nesting_orders=None,
159
- input_sources=None,
160
- resources=None,
161
- loops=None,
162
- groups=None,
163
- name="w1",
164
- overwrite=False,
165
- store="zarr",
166
- ):
180
+ schemas_spec: Iterable[
181
+ tuple[dict[str, Any], tuple[str, ...]]
182
+ | tuple[dict[str, Any], tuple[str, ...], str]
183
+ ],
184
+ path: PathLike,
185
+ local_inputs: dict[int, Iterable[str]] | None = None,
186
+ local_sequences: (
187
+ dict[int, Iterable[tuple[str, int, int | float | None]]] | None
188
+ ) = None,
189
+ local_resources: dict[int, dict[str, dict]] | None = None,
190
+ nesting_orders: dict[int, dict[str, float]] | None = None,
191
+ input_sources: dict[int, dict[str, list[InputSource]]] | None = None,
192
+ resources: Resources = None,
193
+ loops: list[Loop] | None = None,
194
+ groups: dict[int, Iterable[ElementGroup]] | None = None,
195
+ name: str = "w1",
196
+ overwrite: bool = False,
197
+ store: str = "zarr",
198
+ ) -> Workflow:
167
199
  """
168
200
  Construct a workflow.
169
201
  """
@@ -176,13 +208,12 @@ def make_workflow(
176
208
  input_sources=input_sources,
177
209
  groups=groups,
178
210
  )
179
- template = {
211
+ template: Mapping[str, Any] = {
180
212
  "name": name,
181
213
  "tasks": tasks,
182
214
  "resources": resources,
215
+ **({"loops": loops} if loops else {}),
183
216
  }
184
- if loops:
185
- template["loops"] = loops
186
217
  wk = hf.Workflow.from_template(
187
218
  hf.WorkflowTemplate(**template),
188
219
  path=path,
@@ -194,36 +225,40 @@ def make_workflow(
194
225
 
195
226
 
196
227
  def make_test_data_YAML_workflow(
197
- workflow_name, path, app=None, pkg="hpcflow.tests.data", **kwargs
198
- ):
228
+ workflow_name: str,
229
+ path: PathLike,
230
+ app: BaseApp | None = None,
231
+ pkg: str = "hpcflow.tests.data",
232
+ **kwargs,
233
+ ) -> Workflow:
199
234
  """Generate a workflow whose template file is defined in the test data directory."""
200
- app = hf if app is None else app
201
- script_ctx = resources.as_file(resources.files(pkg).joinpath(workflow_name))
202
-
203
- with script_ctx as file_path:
235
+ app = app or hf
236
+ with get_file_context(pkg, workflow_name) as file_path:
204
237
  return app.Workflow.from_YAML_file(YAML_path=file_path, path=path, **kwargs)
205
238
 
206
239
 
207
240
  def make_test_data_YAML_workflow_template(
208
- workflow_name, app=None, pkg="hpcflow.tests.data", **kwargs
209
- ):
241
+ workflow_name: str,
242
+ app: BaseApp | None = None,
243
+ pkg: str = "hpcflow.tests.data",
244
+ **kwargs,
245
+ ) -> WorkflowTemplate:
210
246
  """Generate a workflow template whose file is defined in the test data directory."""
211
- app = hf if app is None else app
212
- script_ctx = resources.as_file(resources.files(pkg).joinpath(workflow_name))
213
-
214
- with script_ctx as file_path:
247
+ app = app or hf
248
+ with get_file_context(pkg, workflow_name) as file_path:
215
249
  return app.WorkflowTemplate.from_file(path=file_path, **kwargs)
216
250
 
217
251
 
218
252
  @dataclass
253
+ @hydrate
219
254
  class P1_sub_parameter_cls(ParameterValue):
220
255
  """
221
256
  Parameter value handler: ``p1_sub``
222
257
  """
223
258
 
224
- _typ = "p1_sub"
259
+ _typ: ClassVar[str] = "p1_sub"
225
260
 
226
- e: int
261
+ e: int = 0
227
262
 
228
263
  def CLI_format(self) -> str:
229
264
  return str(self.e)
@@ -232,25 +267,27 @@ class P1_sub_parameter_cls(ParameterValue):
232
267
  def twice_e(self):
233
268
  return self.e * 2
234
269
 
235
- def prepare_JSON_dump(self) -> Dict:
270
+ def prepare_JSON_dump(self) -> dict[str, Any]:
236
271
  return {"e": self.e}
237
272
 
238
- def dump_to_HDF5_group(self, group):
273
+ def dump_to_HDF5_group(self, group: HDFSGroup):
239
274
  group.attrs["e"] = self.e
240
275
 
241
276
 
242
277
  @dataclass
278
+ @hydrate
243
279
  class P1_sub_parameter_cls_2(ParameterValue):
244
280
  """
245
281
  Parameter value handler: ``p1_sub_2``
246
282
  """
247
283
 
248
- _typ = "p1_sub_2"
284
+ _typ: ClassVar[str] = "p1_sub_2"
249
285
 
250
- f: int
286
+ f: int = 0
251
287
 
252
288
 
253
289
  @dataclass
290
+ @hydrate
254
291
  class P1_parameter_cls(ParameterValue):
255
292
  """
256
293
  Parameter value handler: ``p1c``
@@ -260,12 +297,15 @@ class P1_parameter_cls(ParameterValue):
260
297
  This is a composite value handler.
261
298
  """
262
299
 
263
- _typ = "p1c"
264
- _sub_parameters = {"sub_param": "p1_sub", "sub_param_2": "p1_sub_2"}
300
+ _typ: ClassVar[str] = "p1c"
301
+ _sub_parameters: ClassVar[dict[str, str]] = {
302
+ "sub_param": "p1_sub",
303
+ "sub_param_2": "p1_sub_2",
304
+ }
265
305
 
266
- a: int
267
- d: Optional[int] = None
268
- sub_param: Optional[P1_sub_parameter_cls] = None
306
+ a: int = 0
307
+ d: int | None = None
308
+ sub_param: P1_sub_parameter_cls | None = None
269
309
 
270
310
  def __post_init__(self):
271
311
  if self.sub_param is not None and not isinstance(
@@ -274,22 +314,22 @@ class P1_parameter_cls(ParameterValue):
274
314
  self.sub_param = P1_sub_parameter_cls(**self.sub_param)
275
315
 
276
316
  @classmethod
277
- def from_data(cls, b, c):
317
+ def from_data(cls, b: int, c: int):
278
318
  return cls(a=b + c)
279
319
 
280
320
  @classmethod
281
- def from_file(cls, path):
321
+ def from_file(cls, path: str):
282
322
  with Path(path).open("rt") as fh:
283
323
  lns = fh.readlines()
284
324
  a = int(lns[0])
285
325
  return cls(a=a)
286
326
 
287
327
  @property
288
- def twice_a(self):
328
+ def twice_a(self) -> int:
289
329
  return self.a * 2
290
330
 
291
331
  @property
292
- def sub_param_prop(self):
332
+ def sub_param_prop(self) -> P1_sub_parameter_cls:
293
333
  return P1_sub_parameter_cls(e=4 * self.a)
294
334
 
295
335
  def CLI_format(self) -> str:
@@ -297,20 +337,18 @@ class P1_parameter_cls(ParameterValue):
297
337
 
298
338
  @staticmethod
299
339
  def CLI_format_group(*objs) -> str:
300
- pass
340
+ return ""
301
341
 
302
342
  @staticmethod
303
343
  def sum(*objs, **kwargs) -> str:
304
344
  return str(sum(i.a for i in objs))
305
345
 
306
- def custom_CLI_format(
307
- self, add: Optional[str] = None, sub: Optional[str] = None
308
- ) -> str:
309
- add = 4 if add is None else int(add)
310
- sub = 0 if sub is None else int(sub)
311
- return str(self.a + add - sub)
346
+ def custom_CLI_format(self, add: str | None = None, sub: str | None = None) -> str:
347
+ add_i = 4 if add is None else int(add)
348
+ sub_i = 0 if sub is None else int(sub)
349
+ return str(self.a + add_i - sub_i)
312
350
 
313
- def custom_CLI_format_prep(self, reps: Optional[str] = None) -> List[int]:
351
+ def custom_CLI_format_prep(self, reps: str | None = None) -> list[int]:
314
352
  """Used for testing custom object CLI formatting.
315
353
 
316
354
  For example, with a command like this:
@@ -318,11 +356,11 @@ class P1_parameter_cls(ParameterValue):
318
356
  `<<join[delim=","](parameter:p1c.custom_CLI_format_prep(reps=4))>>`.
319
357
 
320
358
  """
321
- reps = 1 if reps is None else int(reps)
322
- return [self.a] * reps
359
+ reps_int = 1 if reps is None else int(reps)
360
+ return [self.a] * reps_int
323
361
 
324
362
  @classmethod
325
- def CLI_parse(cls, a_str: str, double: Optional[str] = "", e: Optional[str] = None):
363
+ def CLI_parse(cls, a_str: str, double: str = "", e: str | None = None):
326
364
  a = int(a_str)
327
365
  if double.lower() == "true":
328
366
  a *= 2
@@ -332,25 +370,25 @@ class P1_parameter_cls(ParameterValue):
332
370
  sub_param = None
333
371
  return cls(a=a, sub_param=sub_param)
334
372
 
335
- def prepare_JSON_dump(self) -> Dict:
373
+ def prepare_JSON_dump(self) -> dict[str, Any]:
336
374
  sub_param_js = self.sub_param.prepare_JSON_dump() if self.sub_param else None
337
375
  return {"a": self.a, "d": self.d, "sub_param": sub_param_js}
338
376
 
339
- def dump_to_HDF5_group(self, group):
377
+ def dump_to_HDF5_group(self, group: HDFSGroup):
340
378
  group.attrs["a"] = self.a
341
379
  if self.d is not None:
342
380
  group.attrs["d"] = self.d
343
381
  if self.sub_param:
344
- sub_group = group.add_group("sub_param")
382
+ sub_group = group.create_group("sub_param")
345
383
  self.sub_param.dump_to_HDF5_group(sub_group)
346
384
 
347
385
  @classmethod
348
- def save_from_JSON(cls, data, param_id: int, workflow):
386
+ def save_from_JSON(cls, data: dict, param_id: int | list[int], workflow: Workflow):
349
387
  obj = cls(**data) # TODO: pass sub-param
350
388
  workflow.set_parameter_value(param_id=param_id, value=obj, commit=True)
351
389
 
352
390
  @classmethod
353
- def save_from_HDF5_group(cls, group, param_id: int, workflow):
391
+ def save_from_HDF5_group(cls, group: HDFSGroup, param_id: int, workflow: Workflow):
354
392
  a = group.attrs["a"].item()
355
393
  if "d" in group.attrs:
356
394
  d = group.attrs["d"].item()