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