hpcflow-new2 0.2.0a189__py3-none-any.whl → 0.2.0a199__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 (176) hide show
  1. hpcflow/__pyinstaller/hook-hpcflow.py +9 -6
  2. hpcflow/_version.py +1 -1
  3. hpcflow/app.py +1 -0
  4. hpcflow/data/scripts/bad_script.py +2 -0
  5. hpcflow/data/scripts/do_nothing.py +2 -0
  6. hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
  7. hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
  8. hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
  9. hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
  10. hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
  11. hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
  12. hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
  13. hpcflow/data/scripts/input_file_generator_basic.py +3 -0
  14. hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
  15. hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
  16. hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
  17. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
  18. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
  19. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
  20. hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
  21. hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
  22. hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
  23. hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +1 -1
  24. hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
  25. hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +1 -1
  26. hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
  27. hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
  28. hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
  29. hpcflow/data/scripts/output_file_parser_basic.py +3 -0
  30. hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
  31. hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
  32. hpcflow/data/scripts/script_exit_test.py +5 -0
  33. hpcflow/data/template_components/environments.yaml +1 -1
  34. hpcflow/sdk/__init__.py +26 -15
  35. hpcflow/sdk/app.py +2192 -768
  36. hpcflow/sdk/cli.py +506 -296
  37. hpcflow/sdk/cli_common.py +105 -7
  38. hpcflow/sdk/config/__init__.py +1 -1
  39. hpcflow/sdk/config/callbacks.py +115 -43
  40. hpcflow/sdk/config/cli.py +126 -103
  41. hpcflow/sdk/config/config.py +674 -318
  42. hpcflow/sdk/config/config_file.py +131 -95
  43. hpcflow/sdk/config/errors.py +125 -84
  44. hpcflow/sdk/config/types.py +148 -0
  45. hpcflow/sdk/core/__init__.py +25 -1
  46. hpcflow/sdk/core/actions.py +1771 -1059
  47. hpcflow/sdk/core/app_aware.py +24 -0
  48. hpcflow/sdk/core/cache.py +139 -79
  49. hpcflow/sdk/core/command_files.py +263 -287
  50. hpcflow/sdk/core/commands.py +145 -112
  51. hpcflow/sdk/core/element.py +828 -535
  52. hpcflow/sdk/core/enums.py +192 -0
  53. hpcflow/sdk/core/environment.py +74 -93
  54. hpcflow/sdk/core/errors.py +455 -52
  55. hpcflow/sdk/core/execute.py +207 -0
  56. hpcflow/sdk/core/json_like.py +540 -272
  57. hpcflow/sdk/core/loop.py +751 -347
  58. hpcflow/sdk/core/loop_cache.py +164 -47
  59. hpcflow/sdk/core/object_list.py +370 -207
  60. hpcflow/sdk/core/parameters.py +1100 -627
  61. hpcflow/sdk/core/rule.py +59 -41
  62. hpcflow/sdk/core/run_dir_files.py +21 -37
  63. hpcflow/sdk/core/skip_reason.py +7 -0
  64. hpcflow/sdk/core/task.py +1649 -1339
  65. hpcflow/sdk/core/task_schema.py +308 -196
  66. hpcflow/sdk/core/test_utils.py +191 -114
  67. hpcflow/sdk/core/types.py +440 -0
  68. hpcflow/sdk/core/utils.py +485 -309
  69. hpcflow/sdk/core/validation.py +82 -9
  70. hpcflow/sdk/core/workflow.py +2544 -1178
  71. hpcflow/sdk/core/zarr_io.py +98 -137
  72. hpcflow/sdk/data/workflow_spec_schema.yaml +2 -0
  73. hpcflow/sdk/demo/cli.py +53 -33
  74. hpcflow/sdk/helper/cli.py +18 -15
  75. hpcflow/sdk/helper/helper.py +75 -63
  76. hpcflow/sdk/helper/watcher.py +61 -28
  77. hpcflow/sdk/log.py +122 -71
  78. hpcflow/sdk/persistence/__init__.py +8 -31
  79. hpcflow/sdk/persistence/base.py +1360 -606
  80. hpcflow/sdk/persistence/defaults.py +6 -0
  81. hpcflow/sdk/persistence/discovery.py +38 -0
  82. hpcflow/sdk/persistence/json.py +568 -188
  83. hpcflow/sdk/persistence/pending.py +382 -179
  84. hpcflow/sdk/persistence/store_resource.py +39 -23
  85. hpcflow/sdk/persistence/types.py +318 -0
  86. hpcflow/sdk/persistence/utils.py +14 -11
  87. hpcflow/sdk/persistence/zarr.py +1337 -433
  88. hpcflow/sdk/runtime.py +44 -41
  89. hpcflow/sdk/submission/{jobscript_info.py → enums.py} +39 -12
  90. hpcflow/sdk/submission/jobscript.py +1651 -692
  91. hpcflow/sdk/submission/schedulers/__init__.py +167 -39
  92. hpcflow/sdk/submission/schedulers/direct.py +121 -81
  93. hpcflow/sdk/submission/schedulers/sge.py +170 -129
  94. hpcflow/sdk/submission/schedulers/slurm.py +291 -268
  95. hpcflow/sdk/submission/schedulers/utils.py +12 -2
  96. hpcflow/sdk/submission/shells/__init__.py +14 -15
  97. hpcflow/sdk/submission/shells/base.py +150 -29
  98. hpcflow/sdk/submission/shells/bash.py +283 -173
  99. hpcflow/sdk/submission/shells/os_version.py +31 -30
  100. hpcflow/sdk/submission/shells/powershell.py +228 -170
  101. hpcflow/sdk/submission/submission.py +1014 -335
  102. hpcflow/sdk/submission/types.py +140 -0
  103. hpcflow/sdk/typing.py +182 -12
  104. hpcflow/sdk/utils/arrays.py +71 -0
  105. hpcflow/sdk/utils/deferred_file.py +55 -0
  106. hpcflow/sdk/utils/hashing.py +16 -0
  107. hpcflow/sdk/utils/patches.py +12 -0
  108. hpcflow/sdk/utils/strings.py +33 -0
  109. hpcflow/tests/api/test_api.py +32 -0
  110. hpcflow/tests/conftest.py +27 -6
  111. hpcflow/tests/data/multi_path_sequences.yaml +29 -0
  112. hpcflow/tests/data/workflow_test_run_abort.yaml +34 -35
  113. hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
  114. hpcflow/tests/schedulers/slurm/test_slurm_submission.py +5 -2
  115. hpcflow/tests/scripts/test_input_file_generators.py +282 -0
  116. hpcflow/tests/scripts/test_main_scripts.py +866 -85
  117. hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
  118. hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
  119. hpcflow/tests/shells/wsl/test_wsl_submission.py +12 -4
  120. hpcflow/tests/unit/test_action.py +262 -75
  121. hpcflow/tests/unit/test_action_rule.py +9 -4
  122. hpcflow/tests/unit/test_app.py +33 -6
  123. hpcflow/tests/unit/test_cache.py +46 -0
  124. hpcflow/tests/unit/test_cli.py +134 -1
  125. hpcflow/tests/unit/test_command.py +71 -54
  126. hpcflow/tests/unit/test_config.py +142 -16
  127. hpcflow/tests/unit/test_config_file.py +21 -18
  128. hpcflow/tests/unit/test_element.py +58 -62
  129. hpcflow/tests/unit/test_element_iteration.py +50 -1
  130. hpcflow/tests/unit/test_element_set.py +29 -19
  131. hpcflow/tests/unit/test_group.py +4 -2
  132. hpcflow/tests/unit/test_input_source.py +116 -93
  133. hpcflow/tests/unit/test_input_value.py +29 -24
  134. hpcflow/tests/unit/test_jobscript_unit.py +757 -0
  135. hpcflow/tests/unit/test_json_like.py +44 -35
  136. hpcflow/tests/unit/test_loop.py +1396 -84
  137. hpcflow/tests/unit/test_meta_task.py +325 -0
  138. hpcflow/tests/unit/test_multi_path_sequences.py +229 -0
  139. hpcflow/tests/unit/test_object_list.py +17 -12
  140. hpcflow/tests/unit/test_parameter.py +29 -7
  141. hpcflow/tests/unit/test_persistence.py +237 -42
  142. hpcflow/tests/unit/test_resources.py +20 -18
  143. hpcflow/tests/unit/test_run.py +117 -6
  144. hpcflow/tests/unit/test_run_directories.py +29 -0
  145. hpcflow/tests/unit/test_runtime.py +2 -1
  146. hpcflow/tests/unit/test_schema_input.py +23 -15
  147. hpcflow/tests/unit/test_shell.py +23 -2
  148. hpcflow/tests/unit/test_slurm.py +8 -7
  149. hpcflow/tests/unit/test_submission.py +38 -89
  150. hpcflow/tests/unit/test_task.py +352 -247
  151. hpcflow/tests/unit/test_task_schema.py +33 -20
  152. hpcflow/tests/unit/test_utils.py +9 -11
  153. hpcflow/tests/unit/test_value_sequence.py +15 -12
  154. hpcflow/tests/unit/test_workflow.py +114 -83
  155. hpcflow/tests/unit/test_workflow_template.py +0 -1
  156. hpcflow/tests/unit/utils/test_arrays.py +40 -0
  157. hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
  158. hpcflow/tests/unit/utils/test_hashing.py +65 -0
  159. hpcflow/tests/unit/utils/test_patches.py +5 -0
  160. hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
  161. hpcflow/tests/workflows/__init__.py +0 -0
  162. hpcflow/tests/workflows/test_directory_structure.py +31 -0
  163. hpcflow/tests/workflows/test_jobscript.py +334 -1
  164. hpcflow/tests/workflows/test_run_status.py +198 -0
  165. hpcflow/tests/workflows/test_skip_downstream.py +696 -0
  166. hpcflow/tests/workflows/test_submission.py +140 -0
  167. hpcflow/tests/workflows/test_workflows.py +160 -15
  168. hpcflow/tests/workflows/test_zip.py +18 -0
  169. hpcflow/viz_demo.ipynb +6587 -3
  170. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/METADATA +8 -4
  171. hpcflow_new2-0.2.0a199.dist-info/RECORD +221 -0
  172. hpcflow/sdk/core/parallel.py +0 -21
  173. hpcflow_new2-0.2.0a189.dist-info/RECORD +0 -158
  174. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/LICENSE +0 -0
  175. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/WHEEL +0 -0
  176. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/entry_points.txt +0 -0
@@ -1,19 +1,25 @@
1
+ from __future__ import annotations
1
2
  from dataclasses import dataclass
3
+ from pathlib import Path
2
4
  import random
3
5
  import string
4
6
  import sys
5
7
  from textwrap import dedent
8
+ from typing import ClassVar
6
9
 
7
10
  import pytest
8
11
  import requests
9
12
 
10
13
  from hpcflow.app import app as hf
14
+ from hpcflow.sdk.core.errors import ParametersMetadataReadOnlyError
15
+ from hpcflow.sdk.typing import hydrate
11
16
  from hpcflow.sdk.core.parameters import ParameterValue
12
17
 
13
18
 
14
19
  @dataclass
20
+ @hydrate
15
21
  class MyParameterP1(ParameterValue):
16
- _typ = "p1_test"
22
+ _typ: ClassVar[str] = "p1_test"
17
23
  a: int
18
24
 
19
25
  def CLI_format(self):
@@ -22,7 +28,9 @@ class MyParameterP1(ParameterValue):
22
28
 
23
29
  @pytest.mark.integration
24
30
  @pytest.mark.parametrize("store", ["json", "zarr"])
25
- def test_submission_with_specified_parameter_class_module(null_config, tmp_path, store):
31
+ def test_submission_with_specified_parameter_class_module(
32
+ null_config, tmp_path: Path, store: str
33
+ ):
26
34
  """Test we can use a ParameterValue subclass that is defined separately from the main
27
35
  code (i.e. not automatically imported on app init)."""
28
36
 
@@ -84,7 +92,7 @@ def test_submission_with_specified_parameter_class_module(null_config, tmp_path,
84
92
 
85
93
 
86
94
  @pytest.mark.parametrize("store", ["json", "zarr"])
87
- def test_unseen_parameter(null_config, tmp_path, store):
95
+ def test_unseen_parameter(null_config, tmp_path: Path, store: str):
88
96
  """Test we can generate a workflow that uses an unseen parameter type."""
89
97
 
90
98
  random_str = "".join(random.choice(string.ascii_letters) for _ in range(10))
@@ -116,7 +124,7 @@ def test_unseen_parameter(null_config, tmp_path, store):
116
124
  assert wk.tasks[0].elements[0].get(f"inputs.{p_type}") == 5
117
125
 
118
126
 
119
- def test_iter(new_null_config, tmp_path):
127
+ def test_iter(new_null_config, tmp_path: Path):
120
128
  values = [1, 2, 3]
121
129
  wkt = hf.WorkflowTemplate(
122
130
  name="test",
@@ -132,7 +140,7 @@ def test_iter(new_null_config, tmp_path):
132
140
  assert param_p1_i.value == values[idx]
133
141
 
134
142
 
135
- def test_slice(new_null_config, tmp_path):
143
+ def test_slice(new_null_config, tmp_path: Path):
136
144
  values = [1, 2, 3]
137
145
  wkt = hf.WorkflowTemplate(
138
146
  name="test",
@@ -158,7 +166,7 @@ def test_slice(new_null_config, tmp_path):
158
166
  "retrieving demo data from GitHub."
159
167
  ),
160
168
  )
161
- def test_demo_data_substitution_param_value_class_method(new_null_config, tmp_path):
169
+ def test_demo_data_substitution_param_value_class_method(new_null_config, tmp_path: Path):
162
170
  yaml_str = dedent(
163
171
  """\
164
172
  name: temp
@@ -189,7 +197,9 @@ def test_demo_data_substitution_param_value_class_method(new_null_config, tmp_pa
189
197
  "retrieving demo data from GitHub."
190
198
  ),
191
199
  )
192
- def test_demo_data_substitution_value_sequence_class_method(new_null_config, tmp_path):
200
+ def test_demo_data_substitution_value_sequence_class_method(
201
+ new_null_config, tmp_path: Path
202
+ ):
193
203
  yaml_str = dedent(
194
204
  """\
195
205
  name: temp
@@ -219,3 +229,15 @@ def test_demo_data_substitution_value_sequence_class_method(new_null_config, tmp
219
229
  "9",
220
230
  "10",
221
231
  ]
232
+
233
+
234
+ def test_json_store_parameters_metadata_cache_raises_on_modify(
235
+ null_config, tmp_path: Path
236
+ ):
237
+ wk = hf.make_demo_workflow("workflow_1", path=tmp_path, store="json")
238
+ assert isinstance(wk, hf.Workflow)
239
+ num_params = len(wk.get_all_parameters())
240
+ with pytest.raises(ParametersMetadataReadOnlyError):
241
+ with wk._store.parameters_metadata_cache():
242
+ wk._add_unset_parameter_data(source={"type": "UNSET-FOR-THIS-TEST"})
243
+ assert len(wk.get_all_parameters()) == num_params
@@ -1,49 +1,65 @@
1
+ from __future__ import annotations
1
2
  from pathlib import Path
3
+ import sys
4
+ from typing import Any, Dict, List, Tuple
5
+ from typing import cast, TYPE_CHECKING
2
6
  import numpy as np
3
- import zarr
7
+ import zarr # type: ignore
4
8
  import pytest
5
- from hpcflow.sdk.core.test_utils import make_test_data_YAML_workflow
6
- from hpcflow.sdk.persistence.base import StoreEAR, StoreElement, StoreElementIter
7
- from hpcflow.sdk.persistence.json import JSONPersistentStore
9
+ from hpcflow.sdk.core.test_utils import make_test_data_YAML_workflow, make_workflow
10
+ from hpcflow.sdk.persistence.json import (
11
+ JSONPersistentStore,
12
+ JsonStoreElement,
13
+ JsonStoreElementIter,
14
+ JsonStoreEAR,
15
+ )
16
+ from hpcflow.sdk.persistence.zarr import ZarrPersistentStore
8
17
 
9
18
  from hpcflow.app import app as hf
10
19
 
20
+ if TYPE_CHECKING:
21
+ from hpcflow.sdk.persistence.zarr import ZarrPersistentStore
22
+
11
23
 
12
24
  @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
13
- def test_store_pending_add_task(tmp_path):
25
+ def test_store_pending_add_task(tmp_path: Path):
14
26
  """Check expected pending state after adding a task."""
15
27
 
16
28
  # make store: 0 tasks:
17
- store = JSONPersistentStore.make_test_store_from_spec([], dir=tmp_path)
29
+ store = JSONPersistentStore.make_test_store_from_spec(hf, [], dir=tmp_path)
18
30
  task_ID = store.add_task()
19
31
  assert store._pending.add_tasks == {task_ID: []}
20
32
 
21
33
 
22
34
  @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
23
- def test_store_pending_add_element(tmp_path):
35
+ def test_store_pending_add_element(tmp_path: Path):
24
36
  """Check expected pending state after adding an element."""
25
37
 
26
38
  # make store: 1 task with 0 elements:
27
39
  store = JSONPersistentStore.make_test_store_from_spec(app=hf, spec=[{}], dir=tmp_path)
28
40
  elem_ID = store.add_element(task_ID=0)
29
41
  assert store._pending.add_elements == {
30
- elem_ID: StoreElement(
42
+ elem_ID: JsonStoreElement(
31
43
  id_=elem_ID,
32
44
  is_pending=True,
33
- element_idx=0,
45
+ es_idx=0,
34
46
  task_ID=0,
35
47
  iteration_IDs=[],
48
+ index=0,
49
+ seq_idx={},
50
+ src_idx={},
36
51
  )
37
52
  } and store._pending.add_task_element_IDs == {0: [0]}
38
53
 
39
54
 
40
55
  @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
41
56
  @pytest.mark.parametrize("elem_ID", [0, 1])
42
- def test_store_pending_add_element_iter(tmp_path, elem_ID):
57
+ def test_store_pending_add_element_iter(tmp_path: Path, elem_ID: int):
43
58
  """Check expected pending state after adding an element iteration."""
44
59
 
45
60
  # make store: 1 task with 2 elements and 0 iterations:
46
61
  store = JSONPersistentStore.make_test_store_from_spec(
62
+ hf,
47
63
  [{"elements": [{}, {}]}],
48
64
  dir=tmp_path,
49
65
  )
@@ -53,23 +69,25 @@ def test_store_pending_add_element_iter(tmp_path, elem_ID):
53
69
  schema_parameters=[],
54
70
  )
55
71
  assert store._pending.add_elem_iters == {
56
- iter_ID: StoreElementIter(
72
+ iter_ID: JsonStoreElementIter(
57
73
  id_=iter_ID,
58
74
  is_pending=True,
59
75
  element_ID=elem_ID,
60
76
  EAR_IDs={},
61
77
  data_idx={},
62
78
  schema_parameters=[],
79
+ EARs_initialised=False,
63
80
  )
64
81
  } and store._pending.add_elem_iter_IDs == {elem_ID: [iter_ID]}
65
82
 
66
83
 
67
84
  @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
68
- def test_store_pending_add_EAR(tmp_path):
85
+ def test_store_pending_add_EAR(tmp_path: Path):
69
86
  """Check expected pending state after adding an EAR."""
70
87
 
71
88
  # make store: 1 task with 1 element and 1 iteration:
72
89
  store = JSONPersistentStore.make_test_store_from_spec(
90
+ hf,
73
91
  [{"elements": [{"iterations": [{}]}]}],
74
92
  dir=tmp_path,
75
93
  )
@@ -81,7 +99,7 @@ def test_store_pending_add_EAR(tmp_path):
81
99
  metadata={},
82
100
  )
83
101
  assert store._pending.add_EARs == {
84
- EAR_ID: StoreEAR(
102
+ EAR_ID: JsonStoreEAR(
85
103
  id_=EAR_ID,
86
104
  is_pending=True,
87
105
  elem_iter_ID=0,
@@ -94,21 +112,21 @@ def test_store_pending_add_EAR(tmp_path):
94
112
 
95
113
 
96
114
  @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
97
- def test_get_task_elements_task_is_pending(tmp_path):
115
+ def test_get_task_elements_task_is_pending(tmp_path: Path):
98
116
  """Check we get an empty list when getting all task elements of a pending task to
99
117
  which no elements have been added."""
100
118
  # make store: 0 tasks:
101
- store = JSONPersistentStore.make_test_store_from_spec([], dir=tmp_path)
119
+ store = JSONPersistentStore.make_test_store_from_spec(hf, [], dir=tmp_path)
102
120
  task_ID = store.add_task()
103
121
  assert store.get_task_elements(task_ID, slice(0, None)) == []
104
122
 
105
123
 
106
124
  @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
107
- def test_get_task_elements_single_element_is_pending(tmp_path):
125
+ def test_get_task_elements_single_element_is_pending(tmp_path: Path):
108
126
  """Check expected return when getting all task elements of a persistent task that has
109
127
  a single pending element."""
110
128
  # make store: 1 task
111
- store = JSONPersistentStore.make_test_store_from_spec([{}], dir=tmp_path)
129
+ store = JSONPersistentStore.make_test_store_from_spec(hf, [{}], dir=tmp_path)
112
130
  store.add_element(task_ID=0)
113
131
  assert store.get_task_elements(0, slice(0, None)) == [
114
132
  {
@@ -123,12 +141,12 @@ def test_get_task_elements_single_element_is_pending(tmp_path):
123
141
 
124
142
 
125
143
  @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
126
- def test_get_task_elements_multi_element_one_pending(tmp_path):
144
+ def test_get_task_elements_multi_element_one_pending(tmp_path: Path):
127
145
  """Check expected return when getting all task elements of a persistent task that has
128
146
  a persistent element and a pending element."""
129
147
  # make store: 1 task with 1 element:
130
148
  store = JSONPersistentStore.make_test_store_from_spec(
131
- [{"elements": [{}]}], dir=tmp_path
149
+ hf, [{"elements": [{}]}], dir=tmp_path
132
150
  )
133
151
  store.add_element(task_ID=0)
134
152
  assert store.get_task_elements(0, slice(0, None)) == [
@@ -152,12 +170,12 @@ def test_get_task_elements_multi_element_one_pending(tmp_path):
152
170
 
153
171
 
154
172
  @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
155
- def test_get_task_elements_single_element_iter_pending(tmp_path):
173
+ def test_get_task_elements_single_element_iter_pending(tmp_path: Path):
156
174
  """Check expected return when getting all task elements of a persistent task that has
157
175
  a persistent element with a pending iteration."""
158
176
  # make store: 1 task with 1 element:
159
177
  store = JSONPersistentStore.make_test_store_from_spec(
160
- [{"elements": [{}]}], dir=tmp_path
178
+ hf, [{"elements": [{}]}], dir=tmp_path
161
179
  )
162
180
  store.add_element_iteration(element_ID=0, data_idx={}, schema_parameters=[])
163
181
  assert store.get_task_elements(0, slice(0, None)) == [
@@ -183,12 +201,12 @@ def test_get_task_elements_single_element_iter_pending(tmp_path):
183
201
 
184
202
 
185
203
  @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
186
- def test_get_task_elements_single_element_iter_EAR_pending(tmp_path):
204
+ def test_get_task_elements_single_element_iter_EAR_pending(tmp_path: Path):
187
205
  """Check expected return when getting all task elements of a persistent task that has
188
206
  a persistent element with a persistent iteration and a pending EAR"""
189
207
  # make store: 1 task with 1 element with 1 iteration:
190
208
  store = JSONPersistentStore.make_test_store_from_spec(
191
- [{"elements": [{"iterations": [{}]}]}], dir=tmp_path
209
+ hf, [{"elements": [{"iterations": [{}]}]}], dir=tmp_path
192
210
  )
193
211
  store.add_EAR(elem_iter_ID=0, action_idx=0, commands_idx=[], data_idx={}, metadata={})
194
212
  assert store.get_task_elements(0, slice(0, None)) == [
@@ -225,7 +243,7 @@ def test_get_task_elements_single_element_iter_EAR_pending(tmp_path):
225
243
  ]
226
244
 
227
245
 
228
- def test_make_zarr_store_zstd_compressor(null_config, tmp_path):
246
+ def test_make_zarr_store_zstd_compressor(null_config, tmp_path: Path):
229
247
  wk = make_test_data_YAML_workflow(
230
248
  workflow_name="workflow_1.yaml",
231
249
  path=tmp_path,
@@ -234,7 +252,7 @@ def test_make_zarr_store_zstd_compressor(null_config, tmp_path):
234
252
  )
235
253
 
236
254
 
237
- def test_make_zarr_store_no_compressor(null_config, tmp_path):
255
+ def test_make_zarr_store_no_compressor(null_config, tmp_path: Path):
238
256
  wk = make_test_data_YAML_workflow(
239
257
  workflow_name="workflow_1.yaml",
240
258
  path=tmp_path,
@@ -244,7 +262,10 @@ def test_make_zarr_store_no_compressor(null_config, tmp_path):
244
262
 
245
263
 
246
264
  @pytest.mark.integration
247
- def test_zarr_rechunk_data_equivalent(null_config, tmp_path):
265
+ @pytest.mark.skipif(
266
+ sys.version_info < (3, 9), reason="Python 3.8 support is being removed anyway."
267
+ )
268
+ def test_zarr_rechunk_data_equivalent(null_config, tmp_path: Path):
248
269
  t1 = hf.Task(
249
270
  schema=hf.task_schemas.test_t1_conditional_OS,
250
271
  inputs={"p1": 101},
@@ -259,13 +280,13 @@ def test_zarr_rechunk_data_equivalent(null_config, tmp_path):
259
280
  wk.submit(wait=True, status=False, add_to_known=False)
260
281
  wk.rechunk_runs(backup=True, status=False, chunk_size=None) # None -> one chunk
261
282
 
262
- arr = wk._store._get_EARs_arr()
283
+ arr = cast("ZarrPersistentStore", wk._store)._get_EARs_arr()
263
284
  assert arr.chunks == arr.shape
264
285
 
265
- bak_path = (Path(wk.path) / arr.path).with_suffix(".bak")
286
+ bak_path = (Path(arr.store.path) / arr.path).with_suffix(".bak")
266
287
  arr_bak = zarr.open(bak_path)
267
288
 
268
- assert arr_bak.chunks == (1,)
289
+ assert arr_bak.chunks == (1, 1) # runs array is 2D
269
290
 
270
291
  # check backup and new runs data are equal:
271
292
  assert np.all(arr[:] == arr_bak[:])
@@ -275,7 +296,10 @@ def test_zarr_rechunk_data_equivalent(null_config, tmp_path):
275
296
 
276
297
 
277
298
  @pytest.mark.integration
278
- def test_zarr_rechunk_data_equivalent_custom_chunk_size(null_config, tmp_path):
299
+ @pytest.mark.skipif(
300
+ sys.version_info < (3, 9), reason="Python 3.8 support is being removed anyway."
301
+ )
302
+ def test_zarr_rechunk_data_equivalent_custom_chunk_size(null_config, tmp_path: Path):
279
303
  t1 = hf.Task(
280
304
  schema=hf.task_schemas.test_t1_conditional_OS,
281
305
  inputs={"p1": 101},
@@ -290,20 +314,20 @@ def test_zarr_rechunk_data_equivalent_custom_chunk_size(null_config, tmp_path):
290
314
  wk.submit(wait=True, status=False, add_to_known=False)
291
315
  wk.rechunk_runs(backup=True, status=False, chunk_size=2)
292
316
 
293
- arr = wk._store._get_EARs_arr()
294
- assert arr.chunks == (2,)
317
+ arr = cast("ZarrPersistentStore", wk._store)._get_EARs_arr()
318
+ assert arr.chunks == (2, 2) # runs array is 2D
295
319
 
296
- bak_path = (Path(wk.path) / arr.path).with_suffix(".bak")
320
+ bak_path = (Path(arr.store.path) / arr.path).with_suffix(".bak")
297
321
  arr_bak = zarr.open(bak_path)
298
322
 
299
- assert arr_bak.chunks == (1,)
323
+ assert arr_bak.chunks == (1, 1) # runs array is 2D
300
324
 
301
325
  # check backup and new runs data are equal:
302
326
  assert np.all(arr[:] == arr_bak[:])
303
327
 
304
328
 
305
329
  @pytest.mark.integration
306
- def test_zarr_rechunk_data_no_backup_load_runs(null_config, tmp_path):
330
+ def test_zarr_rechunk_data_no_backup_load_runs(null_config, tmp_path: Path):
307
331
  t1 = hf.Task(
308
332
  schema=hf.task_schemas.test_t1_conditional_OS,
309
333
  inputs={"p1": 101},
@@ -318,9 +342,9 @@ def test_zarr_rechunk_data_no_backup_load_runs(null_config, tmp_path):
318
342
  wk.submit(wait=True, status=False, add_to_known=False)
319
343
  wk.rechunk_runs(backup=False, status=False)
320
344
 
321
- arr = wk._store._get_EARs_arr()
345
+ arr = cast("ZarrPersistentStore", wk._store)._get_EARs_arr()
322
346
 
323
- bak_path = (Path(wk.path) / arr.path).with_suffix(".bak")
347
+ bak_path = (Path(arr.store.path) / arr.path).with_suffix(".bak")
324
348
  assert not bak_path.is_file()
325
349
 
326
350
  # check we can load runs:
@@ -331,7 +355,7 @@ def test_zarr_rechunk_data_no_backup_load_runs(null_config, tmp_path):
331
355
 
332
356
 
333
357
  @pytest.mark.integration
334
- def test_zarr_rechunk_data_no_backup_load_parameter_base(null_config, tmp_path):
358
+ def test_zarr_rechunk_data_no_backup_load_parameter_base(null_config, tmp_path: Path):
335
359
  t1 = hf.Task(
336
360
  schema=hf.task_schemas.test_t1_conditional_OS,
337
361
  inputs={"p1": 101},
@@ -344,15 +368,186 @@ def test_zarr_rechunk_data_no_backup_load_parameter_base(null_config, tmp_path):
344
368
  path=tmp_path,
345
369
  )
346
370
  wk.submit(wait=True, status=False, add_to_known=False)
371
+
372
+ params_old = wk.get_all_parameter_data()
347
373
  wk.rechunk_parameter_base(backup=False, status=False)
348
374
 
349
- arr = wk._store._get_parameter_base_array()
375
+ wk = wk.reload()
376
+ params_new = wk.get_all_parameter_data()
377
+ assert params_new == params_old
378
+
379
+ arr = cast("ZarrPersistentStore", wk._store)._get_parameter_base_array()
350
380
 
351
- bak_path = (Path(wk.path) / arr.path).with_suffix(".bak")
381
+ bak_path = (Path(arr.store.path) / arr.path).with_suffix(".bak")
352
382
  assert not bak_path.is_file()
353
383
 
354
384
  # check we can load parameters:
355
- params = wk.get_all_parameters()
356
385
  param_IDs = []
357
- for i in params:
386
+ for i in wk.get_all_parameters():
358
387
  param_IDs.append(i.id_)
388
+
389
+
390
+ def test_get_parameter_sources_duplicate_ids(null_config, tmp_path):
391
+ wk = make_workflow(
392
+ schemas_spec=[[{"p1": None}, ("p1",), "t1"]],
393
+ local_inputs={0: ("p1",)},
394
+ path=tmp_path,
395
+ )
396
+ id_lst = [0, 1, 1, 2, 0]
397
+ src = wk._store.get_parameter_sources(id_lst)
398
+ assert len(src) == len(id_lst)
399
+ assert src[0] == src[4]
400
+ assert src[1] == src[2]
401
+
402
+
403
+ def _transform_jobscript_dependencies_to_encodable(
404
+ deps: dict[tuple[int, int], dict[tuple[int, int], dict[str, Any]]],
405
+ ) -> dict[str, list[dict[str, Any]]]:
406
+ """Transform a dict of jobscript dependencies written in a more testing-friendly/
407
+ convenient format into the format expected by the method
408
+ `ZarrPersistentStore._encode_jobscript_block_dependencies`.
409
+
410
+ """
411
+ max_js_idx = max(i[0] for i in deps)
412
+ sub_js: dict[str, list[dict[str, Any]]] = {
413
+ "jobscripts": [
414
+ {"blocks": [], "index": js_idx} for js_idx in range(max_js_idx + 1)
415
+ ]
416
+ }
417
+ for (js_idx, blk_idx), deps_i in deps.items():
418
+ sub_js["jobscripts"][js_idx]["blocks"].append(
419
+ {
420
+ "dependencies": [[[k[0], k[1]], v] for k, v in deps_i.items()],
421
+ "index": blk_idx,
422
+ }
423
+ )
424
+ return sub_js
425
+
426
+
427
+ def test_zarr_encode_jobscript_block_dependencies_element_mapping_array_non_array_equivalence():
428
+ deps_1 = {
429
+ (0, 0): {},
430
+ (1, 0): {(0, 0): {"js_element_mapping": {0: [0]}, "is_array": True}},
431
+ }
432
+ deps_2 = {
433
+ (0, 0): {},
434
+ (1, 0): {(0, 0): {"js_element_mapping": {0: np.array([0])}, "is_array": True}},
435
+ }
436
+ deps_1 = _transform_jobscript_dependencies_to_encodable(deps_1)
437
+ deps_2 = _transform_jobscript_dependencies_to_encodable(deps_2)
438
+ arr_1 = ZarrPersistentStore._encode_jobscript_block_dependencies(deps_1)
439
+ arr_2 = ZarrPersistentStore._encode_jobscript_block_dependencies(deps_2)
440
+ assert np.array_equal(arr_1, arr_2)
441
+
442
+
443
+ def test_zarr_encode_decode_jobscript_block_dependencies():
444
+
445
+ deps = {
446
+ (0, 0): {},
447
+ (1, 0): {
448
+ (0, 0): {
449
+ "js_element_mapping": {0: [0], 1: [1]},
450
+ "is_array": True,
451
+ }
452
+ },
453
+ (2, 0): {
454
+ (1, 0): {
455
+ "js_element_mapping": {0: [0, 1], 1: [0, 1]},
456
+ "is_array": False,
457
+ }
458
+ },
459
+ (2, 1): {
460
+ (0, 0): {"js_element_mapping": {0: [0, 1]}, "is_array": False},
461
+ (2, 0): {"js_element_mapping": {0: [0, 1]}, "is_array": False},
462
+ },
463
+ }
464
+ deps_t = _transform_jobscript_dependencies_to_encodable(deps)
465
+ arr = ZarrPersistentStore._encode_jobscript_block_dependencies(deps_t)
466
+ assert np.array_equal(
467
+ arr,
468
+ np.array(
469
+ [
470
+ 2,
471
+ 0,
472
+ 0,
473
+ 12,
474
+ 1,
475
+ 0,
476
+ 9,
477
+ 0,
478
+ 0,
479
+ 1,
480
+ 2,
481
+ 0,
482
+ 0,
483
+ 2,
484
+ 1,
485
+ 1,
486
+ 14,
487
+ 2,
488
+ 0,
489
+ 11,
490
+ 1,
491
+ 0,
492
+ 0,
493
+ 3,
494
+ 0,
495
+ 0,
496
+ 1,
497
+ 3,
498
+ 1,
499
+ 0,
500
+ 1,
501
+ 18,
502
+ 2,
503
+ 1,
504
+ 7,
505
+ 0,
506
+ 0,
507
+ 0,
508
+ 3,
509
+ 0,
510
+ 0,
511
+ 1,
512
+ 7,
513
+ 2,
514
+ 0,
515
+ 0,
516
+ 3,
517
+ 0,
518
+ 0,
519
+ 1,
520
+ ]
521
+ ),
522
+ )
523
+ deps_rt = ZarrPersistentStore._decode_jobscript_block_dependencies(arr)
524
+ assert deps_rt == deps
525
+
526
+
527
+ def test_zarr_encode_decode_jobscript_block_dependencies_large_many_to_one():
528
+ deps = {
529
+ (0, 0): {},
530
+ (1, 0): {
531
+ (0, 0): {"js_element_mapping": {0: list(range(1_000_000))}, "is_array": False}
532
+ },
533
+ }
534
+ deps_t = _transform_jobscript_dependencies_to_encodable(deps)
535
+ arr = ZarrPersistentStore._encode_jobscript_block_dependencies(deps_t)
536
+ deps_rt = ZarrPersistentStore._decode_jobscript_block_dependencies(arr)
537
+ assert deps_rt == deps
538
+
539
+
540
+ def test_zarr_encode_decode_jobscript_block_dependencies_large_one_to_one():
541
+ deps = {
542
+ (0, 0): {},
543
+ (1, 0): {
544
+ (0, 0): {
545
+ "js_element_mapping": {i: [i] for i in range(1_000_000)},
546
+ "is_array": False,
547
+ }
548
+ },
549
+ }
550
+ deps_t = _transform_jobscript_dependencies_to_encodable(deps)
551
+ arr = ZarrPersistentStore._encode_jobscript_block_dependencies(deps_t)
552
+ deps_rt = ZarrPersistentStore._decode_jobscript_block_dependencies(arr)
553
+ assert deps_rt == deps