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,203 @@
1
+ """
2
+ Models of data stores as resources.
3
+ """
4
+
5
+ from __future__ import annotations
6
+ from abc import ABC, abstractmethod
7
+ import copy
8
+ import json
9
+ from typing import Any, Callable, TYPE_CHECKING
10
+
11
+ from hpcflow.sdk.core.utils import get_md5_hash
12
+
13
+ if TYPE_CHECKING:
14
+ from collections.abc import Mapping
15
+ from logging import Logger
16
+ from pathlib import Path
17
+ import zarr # type: ignore
18
+ from fsspec import AbstractFileSystem # type: ignore
19
+ from ..app import BaseApp
20
+
21
+
22
+ class StoreResource(ABC):
23
+ """Class to represent a persistent resource within which store data lives.
24
+
25
+ A `PersistentStore` maps workflow data across zero or more store resources. Updates to
26
+ persistent workflow data that live in the same store resource are performed together.
27
+
28
+ Parameters
29
+ ----------
30
+ app: App
31
+ The main application context.
32
+ name:
33
+ The store name.
34
+ """
35
+
36
+ def __init__(self, app: BaseApp, name: str) -> None:
37
+ self._app = app
38
+ self.name = name
39
+ self.data: dict[str, Any] = {"read": None, "update": None}
40
+ self.hash = None
41
+
42
+ def __repr__(self) -> str:
43
+ return f"{self.__class__.__name__}(name={self.name!r})"
44
+
45
+ @property
46
+ def logger(self) -> Logger:
47
+ """
48
+ The logger.
49
+ """
50
+ return self._app.persistence_logger
51
+
52
+ @abstractmethod
53
+ def _load(self) -> Any:
54
+ pass
55
+
56
+ @abstractmethod
57
+ def _dump(self, data: dict | list):
58
+ pass
59
+
60
+ def open(self, action: str):
61
+ """
62
+ Open the store.
63
+
64
+ Parameters
65
+ ----------
66
+ action:
67
+ What we are opening the store for; typically either ``read`` or ``update``.
68
+ """
69
+
70
+ # TODO: some tests?
71
+
72
+ if action == "read":
73
+ # reuse "update" data if set, rather than re-loading from disk -- but copy,
74
+ # so changes made in the "read" scope do not update!
75
+ update_data = self.data["update"]
76
+ rd_msg = " (using `update` data)" if update_data else ""
77
+ self.logger.debug(f"{self!r}: opening to read{rd_msg}.")
78
+ data = copy.deepcopy(update_data) if update_data else self._load()
79
+
80
+ elif action == "update":
81
+ # reuse "read" data if set, rather than re-loading from disk; this also means
82
+ # updates will be reflected in the "read" data as soon as they are made:
83
+ read_data = self.data["read"]
84
+ upd_msg = " (using `read` data)" if read_data else ""
85
+ self.logger.debug(f"{self!r}: opening to update{upd_msg}.")
86
+ data = read_data or self._load()
87
+
88
+ else:
89
+ self._check_action(action)
90
+
91
+ self.data[action] = data
92
+
93
+ try:
94
+ self.hash = get_md5_hash(data) # type: ignore
95
+ except Exception:
96
+ pass
97
+
98
+ def close(self, action: str):
99
+ """
100
+ Close the store for a particular action.
101
+
102
+ Parameters
103
+ ----------
104
+ action:
105
+ What we are closing the store for.
106
+ Should match a previous call to :py:meth:`close`.
107
+ """
108
+ if action == "read":
109
+ self.logger.debug(f"{self!r}: closing read.")
110
+ elif action == "update":
111
+ if self.hash:
112
+ # check if it has changed:
113
+ new_hash = get_md5_hash(self.data[action])
114
+ if not self.hash or self.hash != new_hash:
115
+ self.logger.debug(f"{self!r}: data (hash) changed.")
116
+ self._dump(self.data[action])
117
+ self.logger.debug(f"{self!r}: closing update.")
118
+ else:
119
+ self._check_action(action)
120
+
121
+ # unset data for this action:
122
+ self.data[action] = None
123
+
124
+ def _check_action(self, action: str):
125
+ if action not in self.data:
126
+ raise ValueError(
127
+ f"Action {action!r} not known for {self.__class__.__name__!r}"
128
+ )
129
+
130
+
131
+ class JSONFileStoreResource(StoreResource):
132
+ """
133
+ For caching reads and writes to a JSON file.
134
+
135
+ Parameters
136
+ ----------
137
+ app: App
138
+ The main application context.
139
+ name:
140
+ The store name.
141
+ filename:
142
+ The name of the JSON file.
143
+ path:
144
+ The path to the directory containing the JSON file.
145
+ fs:
146
+ The filesystem that the JSON file resides within.
147
+ """
148
+
149
+ def __init__(
150
+ self,
151
+ app: BaseApp,
152
+ name: str,
153
+ filename: str,
154
+ path: str | Path,
155
+ fs: AbstractFileSystem,
156
+ ):
157
+ self.filename = filename
158
+ self.path = path
159
+ self.fs = fs
160
+ super().__init__(app, name)
161
+
162
+ @property
163
+ def _full_path(self) -> str:
164
+ return f"{self.path}/{self.filename}"
165
+
166
+ def _load(self) -> Any:
167
+ self.logger.debug(f"{self!r}: loading JSON from file.")
168
+ with self.fs.open(self._full_path, mode="rt") as fp:
169
+ return json.load(fp)
170
+
171
+ def _dump(self, data: Mapping | list):
172
+ self.logger.debug(f"{self!r}: dumping JSON to file")
173
+ with self.fs.open(self._full_path, mode="wt") as fp:
174
+ json.dump(data, fp, indent=2)
175
+
176
+
177
+ class ZarrAttrsStoreResource(StoreResource):
178
+ """
179
+ For caching reads and writes to Zarr attributes on groups and arrays.
180
+
181
+ Parameters
182
+ ----------
183
+ app: App
184
+ The main application context.
185
+ name:
186
+ The store name.
187
+ open_call:
188
+ How to actually perform an open on the underlying resource.
189
+ """
190
+
191
+ def __init__(self, app: BaseApp, name: str, open_call: Callable[..., zarr.Group]):
192
+ self.open_call = open_call
193
+ super().__init__(app, name)
194
+
195
+ def _load(self) -> Any:
196
+ self.logger.debug(f"{self!r}: loading Zarr attributes.")
197
+ item = self.open_call(mode="r")
198
+ return copy.deepcopy(item.attrs.asdict())
199
+
200
+ def _dump(self, data: dict | list):
201
+ self.logger.debug(f"{self!r}: dumping Zarr attributes.")
202
+ item = self.open_call(mode="r+")
203
+ item.attrs.put(data)
@@ -0,0 +1,309 @@
1
+ """
2
+ Types used in type-checking the persistence subsystem.
3
+ """
4
+
5
+ from __future__ import annotations
6
+ from typing import Any, Generic, TypeVar, TYPE_CHECKING, DefaultDict
7
+ from typing_extensions import TypedDict, NotRequired, TypeAlias
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Mapping
11
+ from .base import StoreTask, StoreElement, StoreElementIter, StoreEAR, StoreParameter
12
+ from ..core.json_like import JSONed
13
+ from ..core.parameters import ParameterValue
14
+ from ..core.types import IterableParam
15
+ from ..typing import DataIndex, ParamSource
16
+
17
+ #: Bound type variable: :class:`StoreTask`.
18
+ AnySTask = TypeVar("AnySTask", bound="StoreTask")
19
+ #: Bound type variable: :class:`StoreElement`.
20
+ AnySElement = TypeVar("AnySElement", bound="StoreElement")
21
+ #: Bound type variable: :class:`StoreElementITer`.
22
+ AnySElementIter = TypeVar("AnySElementIter", bound="StoreElementIter")
23
+ #: Bound type variable: :class:`StoreEAR`.
24
+ AnySEAR = TypeVar("AnySEAR", bound="StoreEAR")
25
+ #: Bound type variable: :class:`StoreParameter`.
26
+ AnySParameter = TypeVar("AnySParameter", bound="StoreParameter")
27
+ #: Type of possible stored parameters.
28
+ ParameterTypes: TypeAlias = (
29
+ "ParameterValue | list | tuple | set | dict | int | float | str | None | Any"
30
+ )
31
+
32
+
33
+ class File(TypedDict):
34
+ """
35
+ Descriptor for file metadata.
36
+ """
37
+
38
+ #: Whether to store the contents.
39
+ store_contents: bool
40
+ #: The path to the file.
41
+ path: str
42
+
43
+
44
+ class FileDescriptor(TypedDict):
45
+ """
46
+ Descriptor for file metadata.
47
+ """
48
+
49
+ #: Whether this is an input file.
50
+ is_input: bool
51
+ #: Whether to store the contents.
52
+ store_contents: bool
53
+ #: Where the file will go.
54
+ dst_path: str
55
+ #: The path to the file.
56
+ path: str | None
57
+ #: Whether to delete the file after processing.
58
+ clean_up: bool
59
+ # The contents of the file.
60
+ contents: NotRequired[str]
61
+
62
+
63
+ class LoopDescriptor(TypedDict):
64
+ """
65
+ Descriptor for loop metadata.
66
+ """
67
+
68
+ #: The parameters iterated over by the loop.
69
+ iterable_parameters: dict[str, IterableParam]
70
+ #: The parameters output by the loop, and the final task insert ID from which they
71
+ #: are output.
72
+ output_parameters: dict[str, int]
73
+ #: The template data from which the loop was created.
74
+ loop_template: NotRequired[dict[str, Any]]
75
+ #: The number of iterations generated by a loop.
76
+ #: Note that the type is really ``list[tuple[tuple[int, ...], int]]``
77
+ #: but the persistence implementations don't handle tuples usefully.
78
+ num_added_iterations: list[list[list[int] | int]]
79
+ #: The parents of the loop.
80
+ parents: list[str]
81
+
82
+
83
+ # TODO: This type looks familiar...
84
+ class StoreCreationInfo(TypedDict):
85
+ """
86
+ Information about the creation of the persistence store.
87
+ """
88
+
89
+ #: Information about the application.
90
+ app_info: dict[str, Any]
91
+ #: When the persistence store was created.
92
+ create_time: str
93
+ #: The unique identifier for for the store/workflow.
94
+ id: str
95
+ #: User's name.
96
+ user_name: str
97
+ #: User's ORCID.
98
+ user_orcid: str
99
+ #: User's affiliations.
100
+ user_affiliations: list[str]
101
+
102
+
103
+ class ElemMeta(TypedDict):
104
+ """
105
+ The kwargs supported for a StoreElement.
106
+ """
107
+
108
+ #: The ID of the element.
109
+ id_: int
110
+ #: The index of the element.
111
+ index: int
112
+ #: The index of the element in its element set.
113
+ es_idx: int
114
+ #: The indices of the element in the sequences that contain it.
115
+ seq_idx: dict[str, int]
116
+ #: The indices of the element's sources.
117
+ src_idx: dict[str, int]
118
+ #: The task associated with the element.
119
+ task_ID: int
120
+ #: The iteration IDs.
121
+ iteration_IDs: list[int]
122
+
123
+
124
+ class IterMeta(TypedDict):
125
+ """
126
+ The kwargs supported for a StoreElementIter.
127
+ """
128
+
129
+ #: The index of the iteration.
130
+ data_idx: DataIndex
131
+ #: The EARs associated with the iteration.
132
+ EAR_IDs: dict[int, list[int]]
133
+ #: Whether the EARs have been initialised.
134
+ EARs_initialised: bool
135
+ #: The ID of the element.
136
+ element_ID: int
137
+ #: The loops containing the iteration.
138
+ loop_idx: dict[str, int]
139
+ #: The schema parameters being iterated over.
140
+ schema_parameters: list[str]
141
+
142
+
143
+ class RunMeta(TypedDict):
144
+ """
145
+ The kwargs supported for StoreEAR.
146
+ """
147
+
148
+ #: The ID of the EAR.
149
+ id_: int
150
+ #: The ID of the element iteration containing the EAR.
151
+ elem_iter_ID: int
152
+ #: The index of the action that generated the EAR.
153
+ action_idx: int
154
+ #: The commands that the EAR will run.
155
+ commands_idx: list[int]
156
+ #: The data handled by the EAR.
157
+ data_idx: DataIndex
158
+ #: Metadata about the EAR.
159
+ metadata: Metadata | None
160
+ #: When the EAR ended, if known.
161
+ end_time: NotRequired[str | None]
162
+ #: The exit code of the EAR, if known.
163
+ exit_code: int | None
164
+ #: When the EAR started, if known.
165
+ start_time: NotRequired[str | None]
166
+ #: Working directory snapshot at start.
167
+ snapshot_start: dict[str, Any] | None
168
+ #: Working directory snapshot at end.
169
+ snapshot_end: dict[str, Any] | None
170
+ #: The index of the EAR in the submissions.
171
+ submission_idx: int | None
172
+ #: Where the EAR is set to run.
173
+ run_hostname: str | None
174
+ #: Whether the EAR succeeded, if known.
175
+ success: bool | None
176
+ #: The skip reason ID, if EAR was skipped.
177
+ skip: int
178
+ #: Port number used by ZeroMQ during execution.
179
+ port_number: int | None
180
+ #: Run ID whose command file can be used for this run (may be this run's ID).
181
+ commands_file_ID: int | None
182
+
183
+
184
+ class TaskMeta(TypedDict):
185
+ """
186
+ Information about a task.
187
+ """
188
+
189
+ #: The ID of the task.
190
+ id_: int
191
+ #: The index of the task in the workflow.
192
+ index: int
193
+ #: The elements in the task.
194
+ element_IDs: list[int]
195
+
196
+
197
+ class TemplateMeta(TypedDict): # FIXME: Incomplete, see WorkflowTemplate
198
+ """
199
+ Metadata about a workflow template.
200
+ """
201
+
202
+ #: Descriptors for loops.
203
+ loops: list[dict]
204
+ #: Descriptors for tasks.
205
+ tasks: list[dict]
206
+
207
+
208
+ class Metadata(TypedDict):
209
+ """
210
+ Workflow metadata.
211
+ """
212
+
213
+ #: Information about the store's creation.
214
+ creation_info: NotRequired[StoreCreationInfo]
215
+ #: Elements in the workflow.
216
+ elements: NotRequired[list[ElemMeta]]
217
+ #: Iterations in the workflow.
218
+ iters: NotRequired[list[IterMeta]]
219
+ #: Loops in the workflow.
220
+ loops: NotRequired[list[LoopDescriptor]]
221
+ #: The name of the workflow.
222
+ name: NotRequired[str]
223
+ #: The number of added tasks.
224
+ num_added_tasks: NotRequired[int]
225
+ #: The replacement workflow, if any.
226
+ replaced_workflow: NotRequired[str]
227
+ #: Element Action Runs in the workflow.
228
+ runs: NotRequired[list[RunMeta]]
229
+ #: Tasks in the workflow.
230
+ tasks: NotRequired[list[TaskMeta]]
231
+ #: The template that generated the workflow.
232
+ template: NotRequired[TemplateMeta]
233
+ #: Custom template components used.
234
+ template_components: NotRequired[dict[str, Any]]
235
+ #: Format for timestamps.
236
+ ts_fmt: NotRequired[str]
237
+ #: Format for timestamps used in naming.
238
+ ts_name_fmt: NotRequired[str]
239
+
240
+
241
+ #: Information for looking up the type of a parameter.
242
+ TypeLookup = DefaultDict[str, list[list]]
243
+
244
+
245
+ class EncodedStoreParameter(TypedDict):
246
+ """
247
+ The encoding of a :class:`StoreParameter`.
248
+ """
249
+
250
+ #: The parameter data.
251
+ data: Any
252
+ #: Information for looking up the type.
253
+ type_lookup: TypeLookup
254
+
255
+
256
+ class PersistenceCache(
257
+ TypedDict, Generic[AnySTask, AnySElement, AnySElementIter, AnySEAR, AnySParameter]
258
+ ):
259
+ """
260
+ Cache used internally by the persistence engine.
261
+ """
262
+
263
+ #: Tasks.
264
+ tasks: dict[int, AnySTask]
265
+ #: Elements.
266
+ elements: dict[int, AnySElement]
267
+ #: Element iterations.
268
+ element_iters: dict[int, AnySElementIter]
269
+ #: Element action runs.
270
+ EARs: dict[int, AnySEAR]
271
+ #: Parameter sources.
272
+ param_sources: dict[int, ParamSource]
273
+ #: Number of tasks.
274
+ num_tasks: int | None
275
+ #: Parameters.
276
+ parameters: dict[int, AnySParameter]
277
+ #: Number of element action runs.
278
+ num_EARs: int | None
279
+ #: Number of parameters.
280
+ num_params: int | None
281
+
282
+
283
+ class ZarrAttrsDict(TypedDict):
284
+ """
285
+ Zarr workflow attributes descriptor.
286
+ """
287
+
288
+ #: Workflow name.
289
+ name: str
290
+ #: Timestamp format.
291
+ ts_fmt: str
292
+ #: Timestamp format for names.
293
+ ts_name_fmt: str
294
+ #: Information about the creation of the workflow and persistent store.
295
+ creation_info: StoreCreationInfo
296
+ #: The template used to build the workflow.
297
+ template: TemplateMeta
298
+ #: Custom components used to build the workflow.
299
+ template_components: dict[str, Any]
300
+ #: Number of tasks added.
301
+ num_added_tasks: int
302
+ #: Tasks in the workflow.
303
+ tasks: list[dict[str, Any]]
304
+ #: Loops in the workflow.
305
+ loops: list[dict[str, Any]]
306
+ #: Submissions by the workflow.
307
+ submissions: list[Mapping[str, JSONed]]
308
+ #: Replacement workflow, if any.
309
+ replaced_workflow: NotRequired[str]
@@ -0,0 +1,73 @@
1
+ """
2
+ Miscellaneous persistence-related helpers.
3
+ """
4
+
5
+ from __future__ import annotations
6
+ from getpass import getpass
7
+ from typing import TYPE_CHECKING
8
+
9
+ from hpcflow.sdk.core.errors import WorkflowNotFoundError
10
+
11
+ if TYPE_CHECKING:
12
+ from typing import Callable, TypeVar
13
+ from fsspec import AbstractFileSystem # type: ignore
14
+
15
+ T = TypeVar("T")
16
+
17
+
18
+ def ask_pw_on_auth_exc(
19
+ f: Callable[..., T], *args, add_pw_to: str | None = None, **kwargs
20
+ ) -> tuple[T, str | None]:
21
+ """
22
+ Run the given function on the given arguments and add a password if the function
23
+ fails with an SSHException.
24
+ """
25
+ from paramiko.ssh_exception import SSHException
26
+
27
+ try:
28
+ out = f(*args, **kwargs)
29
+ pw = None
30
+
31
+ except SSHException:
32
+ pw = getpass()
33
+
34
+ if not add_pw_to:
35
+ kwargs["password"] = pw
36
+ else:
37
+ kwargs[add_pw_to] = {**kwargs[add_pw_to], "password": pw}
38
+
39
+ out = f(*args, **kwargs)
40
+
41
+ return out, pw
42
+
43
+
44
+ def infer_store(path: str, fs: AbstractFileSystem) -> str:
45
+ """Identify the store type using the path and file system parsed by fsspec.
46
+
47
+ Parameters
48
+ ----------
49
+ fs
50
+ fsspec file system
51
+
52
+ """
53
+
54
+ # TODO: raise WorkflowNotFoundError if the path does not exist
55
+ # TODO: raise MalformedWorkflowError if a known store type cannot be inferred
56
+
57
+ # try to identify store type just from the path string:
58
+ if path.endswith(".zip"):
59
+ store_fmt = "zip"
60
+
61
+ elif path.endswith(".json"):
62
+ store_fmt = "json-single"
63
+
64
+ else:
65
+ # look at the directory contents:
66
+ if fs.glob(f"{path}/.zattrs"):
67
+ store_fmt = "zarr"
68
+ elif fs.glob(f"{path}/metadata.json"):
69
+ store_fmt = "json"
70
+ else:
71
+ raise WorkflowNotFoundError(path, fs)
72
+
73
+ return store_fmt