hpcflow 0.1.15__py3-none-any.whl → 0.2.0a271__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 (275) hide show
  1. hpcflow/__init__.py +2 -11
  2. hpcflow/__pyinstaller/__init__.py +5 -0
  3. hpcflow/__pyinstaller/hook-hpcflow.py +40 -0
  4. hpcflow/_version.py +1 -1
  5. hpcflow/app.py +43 -0
  6. hpcflow/cli.py +2 -461
  7. hpcflow/data/demo_data_manifest/__init__.py +3 -0
  8. hpcflow/data/demo_data_manifest/demo_data_manifest.json +6 -0
  9. hpcflow/data/jinja_templates/test/test_template.txt +8 -0
  10. hpcflow/data/programs/hello_world/README.md +1 -0
  11. hpcflow/data/programs/hello_world/hello_world.c +87 -0
  12. hpcflow/data/programs/hello_world/linux/hello_world +0 -0
  13. hpcflow/data/programs/hello_world/macos/hello_world +0 -0
  14. hpcflow/data/programs/hello_world/win/hello_world.exe +0 -0
  15. hpcflow/data/scripts/__init__.py +1 -0
  16. hpcflow/data/scripts/bad_script.py +2 -0
  17. hpcflow/data/scripts/demo_task_1_generate_t1_infile_1.py +8 -0
  18. hpcflow/data/scripts/demo_task_1_generate_t1_infile_2.py +8 -0
  19. hpcflow/data/scripts/demo_task_1_parse_p3.py +7 -0
  20. hpcflow/data/scripts/do_nothing.py +2 -0
  21. hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
  22. hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
  23. hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
  24. hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
  25. hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
  26. hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
  27. hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
  28. hpcflow/data/scripts/generate_t1_file_01.py +7 -0
  29. hpcflow/data/scripts/import_future_script.py +7 -0
  30. hpcflow/data/scripts/input_file_generator_basic.py +3 -0
  31. hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
  32. hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
  33. hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
  34. hpcflow/data/scripts/main_script_test_direct_in_direct_out.py +6 -0
  35. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
  36. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
  37. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
  38. hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
  39. hpcflow/data/scripts/main_script_test_direct_in_direct_out_all_iters_test.py +15 -0
  40. hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +7 -0
  41. hpcflow/data/scripts/main_script_test_direct_in_direct_out_labels.py +8 -0
  42. hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
  43. hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
  44. hpcflow/data/scripts/main_script_test_direct_sub_param_in_direct_out.py +6 -0
  45. hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +12 -0
  46. hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
  47. hpcflow/data/scripts/main_script_test_hdf5_in_obj_group.py +12 -0
  48. hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +11 -0
  49. hpcflow/data/scripts/main_script_test_json_and_direct_in_json_out.py +14 -0
  50. hpcflow/data/scripts/main_script_test_json_in_json_and_direct_out.py +17 -0
  51. hpcflow/data/scripts/main_script_test_json_in_json_out.py +14 -0
  52. hpcflow/data/scripts/main_script_test_json_in_json_out_labels.py +16 -0
  53. hpcflow/data/scripts/main_script_test_json_in_obj.py +12 -0
  54. hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
  55. hpcflow/data/scripts/main_script_test_json_out_obj.py +10 -0
  56. hpcflow/data/scripts/main_script_test_json_sub_param_in_json_out_labels.py +16 -0
  57. hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
  58. hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
  59. hpcflow/data/scripts/output_file_parser_basic.py +3 -0
  60. hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
  61. hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
  62. hpcflow/data/scripts/parse_t1_file_01.py +4 -0
  63. hpcflow/data/scripts/script_exit_test.py +5 -0
  64. hpcflow/data/template_components/__init__.py +1 -0
  65. hpcflow/data/template_components/command_files.yaml +26 -0
  66. hpcflow/data/template_components/environments.yaml +13 -0
  67. hpcflow/data/template_components/parameters.yaml +14 -0
  68. hpcflow/data/template_components/task_schemas.yaml +139 -0
  69. hpcflow/data/workflows/workflow_1.yaml +5 -0
  70. hpcflow/examples.ipynb +1037 -0
  71. hpcflow/sdk/__init__.py +149 -0
  72. hpcflow/sdk/app.py +4266 -0
  73. hpcflow/sdk/cli.py +1479 -0
  74. hpcflow/sdk/cli_common.py +385 -0
  75. hpcflow/sdk/config/__init__.py +5 -0
  76. hpcflow/sdk/config/callbacks.py +246 -0
  77. hpcflow/sdk/config/cli.py +388 -0
  78. hpcflow/sdk/config/config.py +1410 -0
  79. hpcflow/sdk/config/config_file.py +501 -0
  80. hpcflow/sdk/config/errors.py +272 -0
  81. hpcflow/sdk/config/types.py +150 -0
  82. hpcflow/sdk/core/__init__.py +38 -0
  83. hpcflow/sdk/core/actions.py +3857 -0
  84. hpcflow/sdk/core/app_aware.py +25 -0
  85. hpcflow/sdk/core/cache.py +224 -0
  86. hpcflow/sdk/core/command_files.py +814 -0
  87. hpcflow/sdk/core/commands.py +424 -0
  88. hpcflow/sdk/core/element.py +2071 -0
  89. hpcflow/sdk/core/enums.py +221 -0
  90. hpcflow/sdk/core/environment.py +256 -0
  91. hpcflow/sdk/core/errors.py +1043 -0
  92. hpcflow/sdk/core/execute.py +207 -0
  93. hpcflow/sdk/core/json_like.py +809 -0
  94. hpcflow/sdk/core/loop.py +1320 -0
  95. hpcflow/sdk/core/loop_cache.py +282 -0
  96. hpcflow/sdk/core/object_list.py +933 -0
  97. hpcflow/sdk/core/parameters.py +3371 -0
  98. hpcflow/sdk/core/rule.py +196 -0
  99. hpcflow/sdk/core/run_dir_files.py +57 -0
  100. hpcflow/sdk/core/skip_reason.py +7 -0
  101. hpcflow/sdk/core/task.py +3792 -0
  102. hpcflow/sdk/core/task_schema.py +993 -0
  103. hpcflow/sdk/core/test_utils.py +538 -0
  104. hpcflow/sdk/core/types.py +447 -0
  105. hpcflow/sdk/core/utils.py +1207 -0
  106. hpcflow/sdk/core/validation.py +87 -0
  107. hpcflow/sdk/core/values.py +477 -0
  108. hpcflow/sdk/core/workflow.py +4820 -0
  109. hpcflow/sdk/core/zarr_io.py +206 -0
  110. hpcflow/sdk/data/__init__.py +13 -0
  111. hpcflow/sdk/data/config_file_schema.yaml +34 -0
  112. hpcflow/sdk/data/config_schema.yaml +260 -0
  113. hpcflow/sdk/data/environments_spec_schema.yaml +21 -0
  114. hpcflow/sdk/data/files_spec_schema.yaml +5 -0
  115. hpcflow/sdk/data/parameters_spec_schema.yaml +7 -0
  116. hpcflow/sdk/data/task_schema_spec_schema.yaml +3 -0
  117. hpcflow/sdk/data/workflow_spec_schema.yaml +22 -0
  118. hpcflow/sdk/demo/__init__.py +3 -0
  119. hpcflow/sdk/demo/cli.py +242 -0
  120. hpcflow/sdk/helper/__init__.py +3 -0
  121. hpcflow/sdk/helper/cli.py +137 -0
  122. hpcflow/sdk/helper/helper.py +300 -0
  123. hpcflow/sdk/helper/watcher.py +192 -0
  124. hpcflow/sdk/log.py +288 -0
  125. hpcflow/sdk/persistence/__init__.py +18 -0
  126. hpcflow/sdk/persistence/base.py +2817 -0
  127. hpcflow/sdk/persistence/defaults.py +6 -0
  128. hpcflow/sdk/persistence/discovery.py +39 -0
  129. hpcflow/sdk/persistence/json.py +954 -0
  130. hpcflow/sdk/persistence/pending.py +948 -0
  131. hpcflow/sdk/persistence/store_resource.py +203 -0
  132. hpcflow/sdk/persistence/types.py +309 -0
  133. hpcflow/sdk/persistence/utils.py +73 -0
  134. hpcflow/sdk/persistence/zarr.py +2388 -0
  135. hpcflow/sdk/runtime.py +320 -0
  136. hpcflow/sdk/submission/__init__.py +3 -0
  137. hpcflow/sdk/submission/enums.py +70 -0
  138. hpcflow/sdk/submission/jobscript.py +2379 -0
  139. hpcflow/sdk/submission/schedulers/__init__.py +281 -0
  140. hpcflow/sdk/submission/schedulers/direct.py +233 -0
  141. hpcflow/sdk/submission/schedulers/sge.py +376 -0
  142. hpcflow/sdk/submission/schedulers/slurm.py +598 -0
  143. hpcflow/sdk/submission/schedulers/utils.py +25 -0
  144. hpcflow/sdk/submission/shells/__init__.py +52 -0
  145. hpcflow/sdk/submission/shells/base.py +229 -0
  146. hpcflow/sdk/submission/shells/bash.py +504 -0
  147. hpcflow/sdk/submission/shells/os_version.py +115 -0
  148. hpcflow/sdk/submission/shells/powershell.py +352 -0
  149. hpcflow/sdk/submission/submission.py +1402 -0
  150. hpcflow/sdk/submission/types.py +140 -0
  151. hpcflow/sdk/typing.py +194 -0
  152. hpcflow/sdk/utils/arrays.py +69 -0
  153. hpcflow/sdk/utils/deferred_file.py +55 -0
  154. hpcflow/sdk/utils/hashing.py +16 -0
  155. hpcflow/sdk/utils/patches.py +31 -0
  156. hpcflow/sdk/utils/strings.py +69 -0
  157. hpcflow/tests/api/test_api.py +32 -0
  158. hpcflow/tests/conftest.py +123 -0
  159. hpcflow/tests/data/__init__.py +0 -0
  160. hpcflow/tests/data/benchmark_N_elements.yaml +6 -0
  161. hpcflow/tests/data/benchmark_script_runner.yaml +26 -0
  162. hpcflow/tests/data/multi_path_sequences.yaml +29 -0
  163. hpcflow/tests/data/workflow_1.json +10 -0
  164. hpcflow/tests/data/workflow_1.yaml +5 -0
  165. hpcflow/tests/data/workflow_1_slurm.yaml +8 -0
  166. hpcflow/tests/data/workflow_1_wsl.yaml +8 -0
  167. hpcflow/tests/data/workflow_test_run_abort.yaml +42 -0
  168. hpcflow/tests/jinja_templates/test_jinja_templates.py +161 -0
  169. hpcflow/tests/programs/test_programs.py +180 -0
  170. hpcflow/tests/schedulers/direct_linux/test_direct_linux_submission.py +12 -0
  171. hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
  172. hpcflow/tests/schedulers/slurm/test_slurm_submission.py +14 -0
  173. hpcflow/tests/scripts/test_input_file_generators.py +282 -0
  174. hpcflow/tests/scripts/test_main_scripts.py +1361 -0
  175. hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
  176. hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
  177. hpcflow/tests/shells/wsl/test_wsl_submission.py +14 -0
  178. hpcflow/tests/unit/test_action.py +1066 -0
  179. hpcflow/tests/unit/test_action_rule.py +24 -0
  180. hpcflow/tests/unit/test_app.py +132 -0
  181. hpcflow/tests/unit/test_cache.py +46 -0
  182. hpcflow/tests/unit/test_cli.py +172 -0
  183. hpcflow/tests/unit/test_command.py +377 -0
  184. hpcflow/tests/unit/test_config.py +195 -0
  185. hpcflow/tests/unit/test_config_file.py +162 -0
  186. hpcflow/tests/unit/test_element.py +666 -0
  187. hpcflow/tests/unit/test_element_iteration.py +88 -0
  188. hpcflow/tests/unit/test_element_set.py +158 -0
  189. hpcflow/tests/unit/test_group.py +115 -0
  190. hpcflow/tests/unit/test_input_source.py +1479 -0
  191. hpcflow/tests/unit/test_input_value.py +398 -0
  192. hpcflow/tests/unit/test_jobscript_unit.py +757 -0
  193. hpcflow/tests/unit/test_json_like.py +1247 -0
  194. hpcflow/tests/unit/test_loop.py +2674 -0
  195. hpcflow/tests/unit/test_meta_task.py +325 -0
  196. hpcflow/tests/unit/test_multi_path_sequences.py +259 -0
  197. hpcflow/tests/unit/test_object_list.py +116 -0
  198. hpcflow/tests/unit/test_parameter.py +243 -0
  199. hpcflow/tests/unit/test_persistence.py +664 -0
  200. hpcflow/tests/unit/test_resources.py +243 -0
  201. hpcflow/tests/unit/test_run.py +286 -0
  202. hpcflow/tests/unit/test_run_directories.py +29 -0
  203. hpcflow/tests/unit/test_runtime.py +9 -0
  204. hpcflow/tests/unit/test_schema_input.py +372 -0
  205. hpcflow/tests/unit/test_shell.py +129 -0
  206. hpcflow/tests/unit/test_slurm.py +39 -0
  207. hpcflow/tests/unit/test_submission.py +502 -0
  208. hpcflow/tests/unit/test_task.py +2560 -0
  209. hpcflow/tests/unit/test_task_schema.py +182 -0
  210. hpcflow/tests/unit/test_utils.py +616 -0
  211. hpcflow/tests/unit/test_value_sequence.py +549 -0
  212. hpcflow/tests/unit/test_values.py +91 -0
  213. hpcflow/tests/unit/test_workflow.py +827 -0
  214. hpcflow/tests/unit/test_workflow_template.py +186 -0
  215. hpcflow/tests/unit/utils/test_arrays.py +40 -0
  216. hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
  217. hpcflow/tests/unit/utils/test_hashing.py +65 -0
  218. hpcflow/tests/unit/utils/test_patches.py +5 -0
  219. hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
  220. hpcflow/tests/unit/utils/test_strings.py +97 -0
  221. hpcflow/tests/workflows/__init__.py +0 -0
  222. hpcflow/tests/workflows/test_directory_structure.py +31 -0
  223. hpcflow/tests/workflows/test_jobscript.py +355 -0
  224. hpcflow/tests/workflows/test_run_status.py +198 -0
  225. hpcflow/tests/workflows/test_skip_downstream.py +696 -0
  226. hpcflow/tests/workflows/test_submission.py +140 -0
  227. hpcflow/tests/workflows/test_workflows.py +564 -0
  228. hpcflow/tests/workflows/test_zip.py +18 -0
  229. hpcflow/viz_demo.ipynb +6794 -0
  230. hpcflow-0.2.0a271.dist-info/LICENSE +375 -0
  231. hpcflow-0.2.0a271.dist-info/METADATA +65 -0
  232. hpcflow-0.2.0a271.dist-info/RECORD +237 -0
  233. {hpcflow-0.1.15.dist-info → hpcflow-0.2.0a271.dist-info}/WHEEL +4 -5
  234. hpcflow-0.2.0a271.dist-info/entry_points.txt +6 -0
  235. hpcflow/api.py +0 -490
  236. hpcflow/archive/archive.py +0 -307
  237. hpcflow/archive/cloud/cloud.py +0 -45
  238. hpcflow/archive/cloud/errors.py +0 -9
  239. hpcflow/archive/cloud/providers/dropbox.py +0 -427
  240. hpcflow/archive/errors.py +0 -5
  241. hpcflow/base_db.py +0 -4
  242. hpcflow/config.py +0 -233
  243. hpcflow/copytree.py +0 -66
  244. hpcflow/data/examples/_config.yml +0 -14
  245. hpcflow/data/examples/damask/demo/1.run.yml +0 -4
  246. hpcflow/data/examples/damask/demo/2.process.yml +0 -29
  247. hpcflow/data/examples/damask/demo/geom.geom +0 -2052
  248. hpcflow/data/examples/damask/demo/load.load +0 -1
  249. hpcflow/data/examples/damask/demo/material.config +0 -185
  250. hpcflow/data/examples/damask/inputs/geom.geom +0 -2052
  251. hpcflow/data/examples/damask/inputs/load.load +0 -1
  252. hpcflow/data/examples/damask/inputs/material.config +0 -185
  253. hpcflow/data/examples/damask/profiles/_variable_lookup.yml +0 -21
  254. hpcflow/data/examples/damask/profiles/damask.yml +0 -4
  255. hpcflow/data/examples/damask/profiles/damask_process.yml +0 -8
  256. hpcflow/data/examples/damask/profiles/damask_run.yml +0 -5
  257. hpcflow/data/examples/damask/profiles/default.yml +0 -6
  258. hpcflow/data/examples/thinking.yml +0 -177
  259. hpcflow/errors.py +0 -2
  260. hpcflow/init_db.py +0 -37
  261. hpcflow/models.py +0 -2595
  262. hpcflow/nesting.py +0 -9
  263. hpcflow/profiles.py +0 -455
  264. hpcflow/project.py +0 -81
  265. hpcflow/scheduler.py +0 -322
  266. hpcflow/utils.py +0 -103
  267. hpcflow/validation.py +0 -166
  268. hpcflow/variables.py +0 -543
  269. hpcflow-0.1.15.dist-info/METADATA +0 -168
  270. hpcflow-0.1.15.dist-info/RECORD +0 -45
  271. hpcflow-0.1.15.dist-info/entry_points.txt +0 -8
  272. hpcflow-0.1.15.dist-info/top_level.txt +0 -1
  273. /hpcflow/{archive → data/jinja_templates}/__init__.py +0 -0
  274. /hpcflow/{archive/cloud → data/programs}/__init__.py +0 -0
  275. /hpcflow/{archive/cloud/providers → data/workflows}/__init__.py +0 -0
@@ -0,0 +1,664 @@
1
+ from __future__ import annotations
2
+ from pathlib import Path
3
+ import sys
4
+
5
+ from typing import Any, cast, TYPE_CHECKING
6
+ import numpy as np
7
+ import zarr # type: ignore
8
+ import pytest
9
+ from hpcflow.sdk.core.test_utils import (
10
+ make_schemas,
11
+ make_test_data_YAML_workflow,
12
+ make_workflow,
13
+ )
14
+ from hpcflow.sdk.persistence.json import (
15
+ JSONPersistentStore,
16
+ JsonStoreElement,
17
+ JsonStoreElementIter,
18
+ JsonStoreEAR,
19
+ )
20
+ from hpcflow.sdk.persistence.zarr import ZarrPersistentStore
21
+ from hpcflow.sdk.core.parameters import NullDefault
22
+
23
+ from hpcflow.app import app as hf
24
+
25
+ if TYPE_CHECKING:
26
+ from hpcflow.sdk.persistence.zarr import ZarrPersistentStore
27
+
28
+
29
+ @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
30
+ def test_store_pending_add_task(tmp_path: Path):
31
+ """Check expected pending state after adding a task."""
32
+
33
+ # make store: 0 tasks:
34
+ store = JSONPersistentStore.make_test_store_from_spec(hf, [], dir=tmp_path)
35
+ task_ID = store.add_task()
36
+ assert store._pending.add_tasks == {task_ID: []}
37
+
38
+
39
+ @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
40
+ def test_store_pending_add_element(tmp_path: Path):
41
+ """Check expected pending state after adding an element."""
42
+
43
+ # make store: 1 task with 0 elements:
44
+ store = JSONPersistentStore.make_test_store_from_spec(app=hf, spec=[{}], dir=tmp_path)
45
+ elem_ID = store.add_element(task_ID=0)
46
+ assert store._pending.add_elements == {
47
+ elem_ID: JsonStoreElement(
48
+ id_=elem_ID,
49
+ is_pending=True,
50
+ es_idx=0,
51
+ task_ID=0,
52
+ iteration_IDs=[],
53
+ index=0,
54
+ seq_idx={},
55
+ src_idx={},
56
+ )
57
+ } and store._pending.add_task_element_IDs == {0: [0]}
58
+
59
+
60
+ @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
61
+ @pytest.mark.parametrize("elem_ID", [0, 1])
62
+ def test_store_pending_add_element_iter(tmp_path: Path, elem_ID: int):
63
+ """Check expected pending state after adding an element iteration."""
64
+
65
+ # make store: 1 task with 2 elements and 0 iterations:
66
+ store = JSONPersistentStore.make_test_store_from_spec(
67
+ hf,
68
+ [{"elements": [{}, {}]}],
69
+ dir=tmp_path,
70
+ )
71
+ iter_ID = store.add_element_iteration(
72
+ element_ID=elem_ID,
73
+ data_idx={},
74
+ schema_parameters=[],
75
+ )
76
+ assert store._pending.add_elem_iters == {
77
+ iter_ID: JsonStoreElementIter(
78
+ id_=iter_ID,
79
+ is_pending=True,
80
+ element_ID=elem_ID,
81
+ EAR_IDs={},
82
+ data_idx={},
83
+ schema_parameters=[],
84
+ EARs_initialised=False,
85
+ )
86
+ } and store._pending.add_elem_iter_IDs == {elem_ID: [iter_ID]}
87
+
88
+
89
+ @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
90
+ def test_store_pending_add_EAR(tmp_path: Path):
91
+ """Check expected pending state after adding an EAR."""
92
+
93
+ # make store: 1 task with 1 element and 1 iteration:
94
+ store = JSONPersistentStore.make_test_store_from_spec(
95
+ hf,
96
+ [{"elements": [{"iterations": [{}]}]}],
97
+ dir=tmp_path,
98
+ )
99
+ EAR_ID = store.add_EAR(
100
+ elem_iter_ID=0,
101
+ action_idx=0,
102
+ commands_idx=[],
103
+ data_idx={},
104
+ metadata={},
105
+ )
106
+ assert store._pending.add_EARs == {
107
+ EAR_ID: JsonStoreEAR(
108
+ id_=EAR_ID,
109
+ is_pending=True,
110
+ elem_iter_ID=0,
111
+ action_idx=0,
112
+ commands_idx=[],
113
+ data_idx={},
114
+ metadata={},
115
+ )
116
+ }
117
+
118
+
119
+ @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
120
+ def test_get_task_elements_task_is_pending(tmp_path: Path):
121
+ """Check we get an empty list when getting all task elements of a pending task to
122
+ which no elements have been added."""
123
+ # make store: 0 tasks:
124
+ store = JSONPersistentStore.make_test_store_from_spec(hf, [], dir=tmp_path)
125
+ task_ID = store.add_task()
126
+ assert store.get_task_elements(task_ID, slice(0, None)) == []
127
+
128
+
129
+ @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
130
+ def test_get_task_elements_single_element_is_pending(tmp_path: Path):
131
+ """Check expected return when getting all task elements of a persistent task that has
132
+ a single pending element."""
133
+ # make store: 1 task
134
+ store = JSONPersistentStore.make_test_store_from_spec(hf, [{}], dir=tmp_path)
135
+ store.add_element(task_ID=0)
136
+ assert store.get_task_elements(0, slice(0, None)) == [
137
+ {
138
+ "id": 0,
139
+ "is_pending": True,
140
+ "element_idx": 0,
141
+ "iteration_IDs": [],
142
+ "task_ID": 0,
143
+ "iterations": [],
144
+ }
145
+ ]
146
+
147
+
148
+ @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
149
+ def test_get_task_elements_multi_element_one_pending(tmp_path: Path):
150
+ """Check expected return when getting all task elements of a persistent task that has
151
+ a persistent element and a pending element."""
152
+ # make store: 1 task with 1 element:
153
+ store = JSONPersistentStore.make_test_store_from_spec(
154
+ hf, [{"elements": [{}]}], dir=tmp_path
155
+ )
156
+ store.add_element(task_ID=0)
157
+ assert store.get_task_elements(0, slice(0, None)) == [
158
+ {
159
+ "id": 0,
160
+ "is_pending": False,
161
+ "element_idx": 0,
162
+ "iteration_IDs": [],
163
+ "task_ID": 0,
164
+ "iterations": [],
165
+ },
166
+ {
167
+ "id": 1,
168
+ "is_pending": True,
169
+ "element_idx": 1,
170
+ "iteration_IDs": [],
171
+ "task_ID": 0,
172
+ "iterations": [],
173
+ },
174
+ ]
175
+
176
+
177
+ @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
178
+ def test_get_task_elements_single_element_iter_pending(tmp_path: Path):
179
+ """Check expected return when getting all task elements of a persistent task that has
180
+ a persistent element with a pending iteration."""
181
+ # make store: 1 task with 1 element:
182
+ store = JSONPersistentStore.make_test_store_from_spec(
183
+ hf, [{"elements": [{}]}], dir=tmp_path
184
+ )
185
+ store.add_element_iteration(element_ID=0, data_idx={}, schema_parameters=[])
186
+ assert store.get_task_elements(0, slice(0, None)) == [
187
+ {
188
+ "id": 0,
189
+ "is_pending": False,
190
+ "element_idx": 0,
191
+ "iteration_IDs": [0],
192
+ "task_ID": 0,
193
+ "iterations": [
194
+ {
195
+ "id": 0,
196
+ "is_pending": True,
197
+ "element_ID": 0,
198
+ "EAR_IDs": {},
199
+ "data_idx": {},
200
+ "schema_parameters": [],
201
+ "EARs": {},
202
+ }
203
+ ],
204
+ },
205
+ ]
206
+
207
+
208
+ @pytest.mark.skip("need to refactor `make_test_store_from_spec`")
209
+ def test_get_task_elements_single_element_iter_EAR_pending(tmp_path: Path):
210
+ """Check expected return when getting all task elements of a persistent task that has
211
+ a persistent element with a persistent iteration and a pending EAR"""
212
+ # make store: 1 task with 1 element with 1 iteration:
213
+ store = JSONPersistentStore.make_test_store_from_spec(
214
+ hf, [{"elements": [{"iterations": [{}]}]}], dir=tmp_path
215
+ )
216
+ store.add_EAR(elem_iter_ID=0, action_idx=0, commands_idx=[], data_idx={}, metadata={})
217
+ assert store.get_task_elements(0, slice(0, None)) == [
218
+ {
219
+ "id": 0,
220
+ "is_pending": False,
221
+ "element_idx": 0,
222
+ "iteration_IDs": [0],
223
+ "task_ID": 0,
224
+ "iterations": [
225
+ {
226
+ "id": 0,
227
+ "is_pending": False,
228
+ "element_ID": 0,
229
+ "EAR_IDs": {0: [0]},
230
+ "data_idx": {},
231
+ "schema_parameters": [],
232
+ "EARs": {
233
+ 0: [
234
+ {
235
+ "id_": 0,
236
+ "is_pending": True,
237
+ "elem_iter_ID": 0,
238
+ "action_idx": 0,
239
+ "commands_idx": [],
240
+ "data_idx": {},
241
+ "metadata": {},
242
+ }
243
+ ]
244
+ },
245
+ },
246
+ ],
247
+ },
248
+ ]
249
+
250
+
251
+ def test_make_zarr_store_zstd_compressor(null_config, tmp_path: Path):
252
+ wk = make_test_data_YAML_workflow(
253
+ workflow_name="workflow_1.yaml",
254
+ path=tmp_path,
255
+ store="zarr",
256
+ store_kwargs={"compressor": "zstd"},
257
+ )
258
+
259
+
260
+ def test_make_zarr_store_no_compressor(null_config, tmp_path: Path):
261
+ wk = make_test_data_YAML_workflow(
262
+ workflow_name="workflow_1.yaml",
263
+ path=tmp_path,
264
+ store="zarr",
265
+ store_kwargs={"compressor": None},
266
+ )
267
+
268
+
269
+ @pytest.mark.integration
270
+ @pytest.mark.skipif(
271
+ sys.version_info < (3, 9), reason="Python 3.8 support is being removed anyway."
272
+ )
273
+ def test_zarr_rechunk_data_equivalent(null_config, tmp_path: Path):
274
+ t1 = hf.Task(
275
+ schema=hf.task_schemas.test_t1_conditional_OS,
276
+ inputs={"p1": 101},
277
+ repeats=3,
278
+ )
279
+ wk = hf.Workflow.from_template_data(
280
+ tasks=[t1],
281
+ template_name="test_run_rechunk",
282
+ workflow_name="test_run_rechunk",
283
+ path=tmp_path,
284
+ )
285
+ wk.submit(wait=True, status=False, add_to_known=False)
286
+ wk.rechunk_runs(backup=True, status=False, chunk_size=None) # None -> one chunk
287
+
288
+ arr = cast("ZarrPersistentStore", wk._store)._get_EARs_arr()
289
+ assert arr.chunks == arr.shape
290
+
291
+ bak_path = (Path(arr.store.path) / arr.path).with_suffix(".bak")
292
+ arr_bak = zarr.open(bak_path)
293
+
294
+ assert arr_bak.chunks == (1, 1) # runs array is 2D
295
+
296
+ # check backup and new runs data are equal:
297
+ assert np.all(arr[:] == arr_bak[:])
298
+
299
+ # check attributes are equal:
300
+ assert arr.attrs.asdict() == arr_bak.attrs.asdict()
301
+
302
+
303
+ @pytest.mark.integration
304
+ @pytest.mark.skipif(
305
+ sys.version_info < (3, 9), reason="Python 3.8 support is being removed anyway."
306
+ )
307
+ def test_zarr_rechunk_data_equivalent_custom_chunk_size(null_config, tmp_path: Path):
308
+ t1 = hf.Task(
309
+ schema=hf.task_schemas.test_t1_conditional_OS,
310
+ inputs={"p1": 101},
311
+ repeats=3,
312
+ )
313
+ wk = hf.Workflow.from_template_data(
314
+ tasks=[t1],
315
+ template_name="test_run_rechunk",
316
+ workflow_name="test_run_rechunk",
317
+ path=tmp_path,
318
+ )
319
+ wk.submit(wait=True, status=False, add_to_known=False)
320
+ wk.rechunk_runs(backup=True, status=False, chunk_size=2)
321
+
322
+ arr = cast("ZarrPersistentStore", wk._store)._get_EARs_arr()
323
+ assert arr.chunks == (2, 2) # runs array is 2D
324
+
325
+ bak_path = (Path(arr.store.path) / arr.path).with_suffix(".bak")
326
+ arr_bak = zarr.open(bak_path)
327
+
328
+ assert arr_bak.chunks == (1, 1) # runs array is 2D
329
+
330
+ # check backup and new runs data are equal:
331
+ assert np.all(arr[:] == arr_bak[:])
332
+
333
+
334
+ @pytest.mark.integration
335
+ def test_zarr_rechunk_data_no_backup_load_runs(null_config, tmp_path: Path):
336
+ t1 = hf.Task(
337
+ schema=hf.task_schemas.test_t1_conditional_OS,
338
+ inputs={"p1": 101},
339
+ repeats=3,
340
+ )
341
+ wk = hf.Workflow.from_template_data(
342
+ tasks=[t1],
343
+ template_name="test_run_rechunk",
344
+ workflow_name="test_run_rechunk",
345
+ path=tmp_path,
346
+ )
347
+ wk.submit(wait=True, status=False, add_to_known=False)
348
+ wk.rechunk_runs(backup=False, status=False)
349
+
350
+ arr = cast("ZarrPersistentStore", wk._store)._get_EARs_arr()
351
+
352
+ bak_path = (Path(arr.store.path) / arr.path).with_suffix(".bak")
353
+ assert not bak_path.is_file()
354
+
355
+ # check we can load runs:
356
+ runs = wk._store._get_persistent_EARs(id_lst=list(range(wk.num_EARs)))
357
+ run_ID = []
358
+ for i in runs.values():
359
+ run_ID.append(i.id_)
360
+
361
+
362
+ @pytest.mark.integration
363
+ def test_zarr_rechunk_data_no_backup_load_parameter_base(null_config, tmp_path: Path):
364
+ t1 = hf.Task(
365
+ schema=hf.task_schemas.test_t1_conditional_OS,
366
+ inputs={"p1": 101},
367
+ repeats=3,
368
+ )
369
+ wk = hf.Workflow.from_template_data(
370
+ tasks=[t1],
371
+ template_name="test_run_rechunk",
372
+ workflow_name="test_run_rechunk",
373
+ path=tmp_path,
374
+ )
375
+ wk.submit(wait=True, status=False, add_to_known=False)
376
+
377
+ params_old = wk.get_all_parameter_data()
378
+ wk.rechunk_parameter_base(backup=False, status=False)
379
+
380
+ wk = wk.reload()
381
+ params_new = wk.get_all_parameter_data()
382
+ assert params_new == params_old
383
+
384
+ arr = cast("ZarrPersistentStore", wk._store)._get_parameter_base_array()
385
+
386
+ bak_path = (Path(arr.store.path) / arr.path).with_suffix(".bak")
387
+ assert not bak_path.is_file()
388
+
389
+ # check we can load parameters:
390
+ param_IDs = []
391
+ for i in wk.get_all_parameters():
392
+ param_IDs.append(i.id_)
393
+
394
+
395
+ def test_get_parameter_sources_duplicate_ids(null_config, tmp_path):
396
+ wk = make_workflow(
397
+ schemas_spec=[[{"p1": None}, ("p1",), "t1"]],
398
+ local_inputs={0: ("p1",)},
399
+ path=tmp_path,
400
+ )
401
+ id_lst = [0, 1, 1, 2, 0]
402
+ src = wk._store.get_parameter_sources(id_lst)
403
+ assert len(src) == len(id_lst)
404
+ assert src[0] == src[4]
405
+ assert src[1] == src[2]
406
+
407
+
408
+ def _transform_jobscript_dependencies_to_encodable(
409
+ deps: dict[tuple[int, int], dict[tuple[int, int], dict[str, Any]]],
410
+ ) -> dict[str, list[dict[str, Any]]]:
411
+ """Transform a dict of jobscript dependencies written in a more testing-friendly/
412
+ convenient format into the format expected by the method
413
+ `ZarrPersistentStore._encode_jobscript_block_dependencies`.
414
+
415
+ """
416
+ max_js_idx = max(i[0] for i in deps)
417
+ sub_js: dict[str, list[dict[str, Any]]] = {
418
+ "jobscripts": [
419
+ {"blocks": [], "index": js_idx} for js_idx in range(max_js_idx + 1)
420
+ ]
421
+ }
422
+ for (js_idx, blk_idx), deps_i in deps.items():
423
+ sub_js["jobscripts"][js_idx]["blocks"].append(
424
+ {
425
+ "dependencies": [[[k[0], k[1]], v] for k, v in deps_i.items()],
426
+ "index": blk_idx,
427
+ }
428
+ )
429
+ return sub_js
430
+
431
+
432
+ def test_zarr_encode_jobscript_block_dependencies_element_mapping_array_non_array_equivalence():
433
+ deps_1 = {
434
+ (0, 0): {},
435
+ (1, 0): {(0, 0): {"js_element_mapping": {0: [0]}, "is_array": True}},
436
+ }
437
+ deps_2 = {
438
+ (0, 0): {},
439
+ (1, 0): {(0, 0): {"js_element_mapping": {0: np.array([0])}, "is_array": True}},
440
+ }
441
+ deps_1 = _transform_jobscript_dependencies_to_encodable(deps_1)
442
+ deps_2 = _transform_jobscript_dependencies_to_encodable(deps_2)
443
+ arr_1 = ZarrPersistentStore._encode_jobscript_block_dependencies(deps_1)
444
+ arr_2 = ZarrPersistentStore._encode_jobscript_block_dependencies(deps_2)
445
+ assert np.array_equal(arr_1, arr_2)
446
+
447
+
448
+ def test_zarr_encode_decode_jobscript_block_dependencies():
449
+
450
+ deps = {
451
+ (0, 0): {},
452
+ (1, 0): {
453
+ (0, 0): {
454
+ "js_element_mapping": {0: [0], 1: [1]},
455
+ "is_array": True,
456
+ }
457
+ },
458
+ (2, 0): {
459
+ (1, 0): {
460
+ "js_element_mapping": {0: [0, 1], 1: [0, 1]},
461
+ "is_array": False,
462
+ }
463
+ },
464
+ (2, 1): {
465
+ (0, 0): {"js_element_mapping": {0: [0, 1]}, "is_array": False},
466
+ (2, 0): {"js_element_mapping": {0: [0, 1]}, "is_array": False},
467
+ },
468
+ }
469
+ deps_t = _transform_jobscript_dependencies_to_encodable(deps)
470
+ arr = ZarrPersistentStore._encode_jobscript_block_dependencies(deps_t)
471
+ assert np.array_equal(
472
+ arr,
473
+ np.array(
474
+ [
475
+ 2,
476
+ 0,
477
+ 0,
478
+ 12,
479
+ 1,
480
+ 0,
481
+ 9,
482
+ 0,
483
+ 0,
484
+ 1,
485
+ 2,
486
+ 0,
487
+ 0,
488
+ 2,
489
+ 1,
490
+ 1,
491
+ 14,
492
+ 2,
493
+ 0,
494
+ 11,
495
+ 1,
496
+ 0,
497
+ 0,
498
+ 3,
499
+ 0,
500
+ 0,
501
+ 1,
502
+ 3,
503
+ 1,
504
+ 0,
505
+ 1,
506
+ 18,
507
+ 2,
508
+ 1,
509
+ 7,
510
+ 0,
511
+ 0,
512
+ 0,
513
+ 3,
514
+ 0,
515
+ 0,
516
+ 1,
517
+ 7,
518
+ 2,
519
+ 0,
520
+ 0,
521
+ 3,
522
+ 0,
523
+ 0,
524
+ 1,
525
+ ]
526
+ ),
527
+ )
528
+ deps_rt = ZarrPersistentStore._decode_jobscript_block_dependencies(arr)
529
+ assert deps_rt == deps
530
+
531
+
532
+ def test_zarr_encode_decode_jobscript_block_dependencies_large_many_to_one():
533
+ deps = {
534
+ (0, 0): {},
535
+ (1, 0): {
536
+ (0, 0): {"js_element_mapping": {0: list(range(1_000_000))}, "is_array": False}
537
+ },
538
+ }
539
+ deps_t = _transform_jobscript_dependencies_to_encodable(deps)
540
+ arr = ZarrPersistentStore._encode_jobscript_block_dependencies(deps_t)
541
+ deps_rt = ZarrPersistentStore._decode_jobscript_block_dependencies(arr)
542
+ assert deps_rt == deps
543
+
544
+
545
+ def test_zarr_encode_decode_jobscript_block_dependencies_large_one_to_one():
546
+ deps = {
547
+ (0, 0): {},
548
+ (1, 0): {
549
+ (0, 0): {
550
+ "js_element_mapping": {i: [i] for i in range(1_000_000)},
551
+ "is_array": False,
552
+ }
553
+ },
554
+ }
555
+ deps_t = _transform_jobscript_dependencies_to_encodable(deps)
556
+ arr = ZarrPersistentStore._encode_jobscript_block_dependencies(deps_t)
557
+ deps_rt = ZarrPersistentStore._decode_jobscript_block_dependencies(arr)
558
+ assert deps_rt == deps
559
+
560
+
561
+ @pytest.mark.parametrize(
562
+ "array",
563
+ (
564
+ np.array([]),
565
+ np.empty(0),
566
+ np.empty((0, 1, 2)),
567
+ np.array([1, 2, 3]),
568
+ np.array([[1, 2, 3], [4, 5, 6]]),
569
+ ),
570
+ )
571
+ def test_zarr_save_persistent_array_shape(null_config, tmp_path, array):
572
+ s1 = make_schemas(({"p1": None}, ()))
573
+ t1 = hf.Task(schema=s1, inputs={"p1": array})
574
+ wk = hf.Workflow.from_template_data(
575
+ template_name="test_save_empty_array",
576
+ tasks=[t1],
577
+ path=tmp_path,
578
+ )
579
+ assert array.shape == wk.tasks[0].elements[0].get("inputs.p1")[:].shape
580
+
581
+
582
+ def test_zarr_single_chunk_threshold(null_config, tmp_path):
583
+ # test very large arrays (> ~1 GB) are saved using multiple chunks
584
+ array = np.zeros(
585
+ 268_435_456
586
+ ) # ~ 2.147483647 GB; greater than blosc's max buffer size
587
+ s1 = make_schemas(({"p1": None}, ()))
588
+ t1 = hf.Task(schema=s1, inputs={"p1": array})
589
+ wk = hf.Workflow.from_template_data(
590
+ template_name="test_large_array",
591
+ tasks=[t1],
592
+ path=tmp_path,
593
+ )
594
+
595
+
596
+ @pytest.mark.parametrize("store", ["json", "zarr"])
597
+ def test_store_parameter_encode_decode_types(null_config, tmp_path, store):
598
+
599
+ (s1,) = make_schemas(
600
+ (
601
+ {
602
+ "p1": NullDefault.NULL,
603
+ "p2": NullDefault.NULL,
604
+ "p3": NullDefault.NULL,
605
+ "p4": NullDefault.NULL,
606
+ "p5": NullDefault.NULL,
607
+ "p6": NullDefault.NULL,
608
+ "p7": NullDefault.NULL,
609
+ "p8": NullDefault.NULL,
610
+ "p9": NullDefault.NULL,
611
+ },
612
+ tuple(),
613
+ ),
614
+ )
615
+
616
+ p1 = [1, 2, 3]
617
+ p2 = (1, 2, 3)
618
+ p3 = {1, 2, 3}
619
+ p4 = None
620
+ p5 = np.arange(10)
621
+ p6 = np.ma.array(np.arange(10), mask=np.random.randint(0, 2, 10))
622
+ p7 = [[1, 2], (3, 4), {5, 6}]
623
+ p8 = np.ma.array(np.arange(10), mask=np.ones(10)) # fully masked
624
+ p9 = {
625
+ "a2": np.ma.array(np.arange(10), mask=np.ones(10), fill_value=999)
626
+ } # custom fill value
627
+
628
+ t1 = hf.Task(
629
+ schema=s1,
630
+ inputs={
631
+ "p1": p1,
632
+ "p2": p2,
633
+ "p3": p3,
634
+ "p4": p4,
635
+ "p5": p5 if store == "zarr" else None,
636
+ "p6": p6 if store == "zarr" else None,
637
+ "p7": p7,
638
+ "p8": p8 if store == "zarr" else None,
639
+ "p9": p9 if store == "zarr" else None,
640
+ },
641
+ )
642
+
643
+ wk = hf.Workflow.from_template_data(
644
+ template_name="test_store_encoders",
645
+ tasks=[t1],
646
+ store=store,
647
+ path=tmp_path,
648
+ )
649
+
650
+ assert wk.tasks[0].elements[0].get("inputs.p1") == p1
651
+ assert wk.tasks[0].elements[0].get("inputs.p2") == p2
652
+ assert wk.tasks[0].elements[0].get("inputs.p3") == p3
653
+ assert wk.tasks[0].elements[0].get("inputs.p4") == p4
654
+ assert wk.tasks[0].elements[0].get("inputs.p7") == p7
655
+
656
+ if store == "zarr":
657
+ assert np.allclose(wk.tasks[0].elements[0].get("inputs.p5"), p5)
658
+ assert np.ma.allclose(wk.tasks[0].elements[0].get("inputs.p6"), p6)
659
+ assert np.ma.allclose(wk.tasks[0].elements[0].get("inputs.p8"), p8)
660
+ assert np.ma.allclose(wk.tasks[0].elements[0].get("inputs.p9.a2"), p9["a2"])
661
+ assert (
662
+ wk.tasks[0].elements[0].get("inputs.p9")["a2"].fill_value
663
+ == p9["a2"].fill_value
664
+ )