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,24 @@
2
2
  Helper for running a subprocess.
3
3
  """
4
4
 
5
+ from __future__ import annotations
5
6
  import subprocess
7
+ from typing import TYPE_CHECKING
6
8
 
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Sequence
11
+ from logging import Logger
7
12
 
8
- def run_cmd(cmd, logger=None):
13
+
14
+ def run_cmd(
15
+ cmd: str | Sequence[str], logger: Logger | None = None, **kwargs
16
+ ) -> tuple[str, str]:
9
17
  """Execute a command and return stdout, stderr as strings."""
10
18
  if logger:
11
19
  logger.debug(f"running shell command: {cmd}")
12
- proc = subprocess.run(args=cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
20
+ proc = subprocess.run(
21
+ args=cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs
22
+ )
13
23
  stdout = proc.stdout.decode()
14
24
  stderr = proc.stderr.decode()
15
25
  return stdout, stderr
@@ -1,17 +1,17 @@
1
1
  """
2
2
  Adapters for various shells.
3
3
  """
4
+ from __future__ import annotations
4
5
  import os
5
- from typing import Dict, Optional
6
6
 
7
7
  from hpcflow.sdk.core.errors import UnsupportedShellError
8
8
 
9
- from .base import Shell
10
- from .bash import Bash, WSLBash
11
- from .powershell import WindowsPowerShell
9
+ from hpcflow.sdk.submission.shells.base import Shell
10
+ from hpcflow.sdk.submission.shells.bash import Bash, WSLBash
11
+ from hpcflow.sdk.submission.shells.powershell import WindowsPowerShell
12
12
 
13
13
  #: All supported shells.
14
- ALL_SHELLS = {
14
+ ALL_SHELLS: dict[str, dict[str, type[Shell]]] = {
15
15
  "bash": {"posix": Bash},
16
16
  "powershell": {"nt": WindowsPowerShell},
17
17
  "wsl+bash": {"nt": WSLBash},
@@ -25,28 +25,27 @@ DEFAULT_SHELL_NAMES = {
25
25
  }
26
26
 
27
27
 
28
- def get_supported_shells(os_name: Optional[str] = None) -> Dict[str, Shell]:
28
+ def get_supported_shells(os_name: str | None = None) -> dict[str, type[Shell]]:
29
29
  """
30
30
  Get shells supported on the current or given OS.
31
31
  """
32
- os_name = os_name or os.name
33
- return {k: v.get(os_name) for k, v in ALL_SHELLS.items() if v.get(os_name)}
32
+ os_name_ = os_name or os.name
33
+ return {k: v[os_name_] for k, v in ALL_SHELLS.items() if v.get(os_name_)}
34
34
 
35
35
 
36
- def get_shell(shell_name, os_name: Optional[str] = None, **kwargs) -> Shell:
36
+ def get_shell(shell_name: str | None, os_name: str | None = None, **kwargs) -> Shell:
37
37
  """
38
38
  Get a shell interface with the given name for a given OS (or the current one).
39
39
  """
40
40
  # TODO: apply config default shell args?
41
41
 
42
42
  os_name = os_name or os.name
43
- shell_name = shell_name.lower()
43
+ shell_name = (
44
+ DEFAULT_SHELL_NAMES[os_name] if shell_name is None else shell_name.lower()
45
+ )
44
46
 
45
47
  supported = get_supported_shells(os_name.lower())
46
- shell_cls = supported.get(shell_name)
47
- if not shell_cls:
48
+ if not (shell_cls := supported.get(shell_name)):
48
49
  raise UnsupportedShellError(shell=shell_name, supported=supported)
49
50
 
50
- shell_obj = shell_cls(**kwargs)
51
-
52
- return shell_obj
51
+ return shell_cls(**kwargs)
@@ -2,11 +2,21 @@
2
2
  Base model of a shell.
3
3
  """
4
4
 
5
+ from __future__ import annotations
5
6
  from abc import ABC, abstractmethod
6
- from pathlib import Path
7
- from typing import Dict, List, Optional
7
+ from typing import TYPE_CHECKING
8
+ from hpcflow.sdk.typing import hydrate
8
9
 
10
+ if TYPE_CHECKING:
11
+ from collections.abc import Mapping
12
+ from pathlib import Path
13
+ from typing import Any, ClassVar
14
+ from ..types import JobscriptHeaderArgs, VersionInfo
9
15
 
16
+ from hpcflow.sdk.utils.hashing import get_hash
17
+
18
+
19
+ @hydrate
10
20
  class Shell(ABC):
11
21
  """Class to represent a shell and templates for jobscript composition.
12
22
 
@@ -22,68 +32,129 @@ class Shell(ABC):
22
32
  Arguments to pass to the shell.
23
33
  """
24
34
 
25
- def __init__(self, executable=None, os_args=None):
35
+ #: Default for executable name.
36
+ DEFAULT_EXE: ClassVar[str] = "/bin/bash"
37
+ #: File extension for jobscripts.
38
+ JS_EXT: ClassVar[str]
39
+ #: Basic indent.
40
+ JS_INDENT: ClassVar[str]
41
+ #: Indent for environment setup.
42
+ JS_ENV_SETUP_INDENT: ClassVar[str]
43
+ #: Template for the jobscript shebang line.
44
+ JS_SHEBANG: ClassVar[str]
45
+ #: Template for the jobscript functions file.
46
+ JS_FUNCS: ClassVar[str]
47
+ #: Template for the common part of the jobscript header.
48
+ JS_HEADER: ClassVar[str]
49
+ #: Template for the jobscript header when scheduled.
50
+ JS_SCHEDULER_HEADER: ClassVar[str]
51
+ #: Template for the jobscript header when directly executed.
52
+ JS_DIRECT_HEADER: ClassVar[str]
53
+ #: Template for enabling writing of the app log.
54
+ JS_RUN_LOG_PATH_ENABLE: ClassVar[str]
55
+ #: Template for disabling writing of the app log.
56
+ JS_RUN_LOG_PATH_DISABLE: ClassVar[str]
57
+ #: Template for the run execution command.
58
+ JS_RUN_CMD: ClassVar[str]
59
+ #: Template for the execution command for multiple combined runs.
60
+ JS_RUN_CMD_COMBINED: ClassVar[str]
61
+ #: Template for setting up run environment variables and executing the run.
62
+ JS_RUN: ClassVar[str]
63
+ #: Template for the action-run processing loop in a jobscript.
64
+ JS_ACT_MULTI: ClassVar[str]
65
+ #: Template for the single-action-run execution in a jobscript.
66
+ JS_ACT_SINGLE: ClassVar[str]
67
+ #: Template for setting up environment variables and running one or more action-runs.
68
+ JS_MAIN: ClassVar[str]
69
+ #: Template for a jobscript-block header.
70
+ JS_BLOCK_HEADER: ClassVar[str]
71
+ #: Template for single-element execution.
72
+ JS_ELEMENT_SINGLE: ClassVar[str]
73
+ #: Template for the element processing loop in a jobscript.
74
+ JS_ELEMENT_MULTI_LOOP: ClassVar[str]
75
+ #: Template for the array handling code in a jobscript.
76
+ JS_ELEMENT_MULTI_ARRAY: ClassVar[str]
77
+ #: Template for the jobscript block loop in a jobscript.
78
+ JS_BLOCK_LOOP: ClassVar[str]
79
+ #: Template for the jobscript footer.
80
+ JS_FOOTER: ClassVar[str]
81
+
82
+ __slots__ = ("_executable", "os_args")
83
+
84
+ def __init__(
85
+ self, executable: str | None = None, os_args: dict[str, str] | None = None
86
+ ):
87
+ #: Which executable implements the shell.
26
88
  self._executable = executable or self.DEFAULT_EXE
27
- self.os_args = os_args
89
+ #: Arguments to pass to the shell.
90
+ self.os_args = os_args or {}
28
91
 
29
- def __eq__(self, other) -> bool:
92
+ def __eq__(self, other: Any) -> bool:
30
93
  if not isinstance(other, self.__class__):
31
94
  return False
32
- if self._executable == other._executable and self.os_args == other.os_args:
33
- return True
34
- return False
95
+ return self._executable == other._executable and self.os_args == other.os_args
96
+
97
+ def __hash__(self):
98
+ return get_hash((self._executable, self.os_args))
35
99
 
36
100
  @property
37
- def executable(self) -> List[str]:
101
+ def executable(self) -> list[str]:
38
102
  """
39
103
  The executable to use plus any mandatory arguments.
40
104
  """
41
105
  return [self._executable]
42
106
 
43
107
  @property
44
- def shebang_executable(self) -> str:
108
+ def shebang_executable(self) -> list[str]:
45
109
  """
46
110
  The executable to use in a shebang line.
47
111
  """
48
112
  return self.executable
49
113
 
50
- def get_direct_submit_command(self, js_path) -> List[str]:
114
+ def get_direct_submit_command(self, js_path: str) -> list[str]:
51
115
  """Get the command for submitting a non-scheduled jobscript."""
52
116
  return self.executable + [js_path]
53
117
 
118
+ def get_command_file_launch_command(self, cmd_file_path: str) -> list[str]:
119
+ """Get the command for launching the commands file for a given run."""
120
+ return self.executable + [cmd_file_path]
121
+
54
122
  @abstractmethod
55
- def get_version_info(self, exclude_os: Optional[bool] = False) -> Dict:
123
+ def get_version_info(self, exclude_os: bool = False) -> VersionInfo:
56
124
  """Get shell and operating system information."""
57
125
 
58
- def get_wait_command(self, workflow_app_alias: str, sub_idx: int, deps: Dict):
126
+ def get_wait_command(
127
+ self, workflow_app_alias: str, sub_idx: int, deps: Mapping[int, Any]
128
+ ):
59
129
  """
60
130
  Get the command to wait for a workflow.
61
131
  """
62
- if deps:
63
- return (
64
- f'{workflow_app_alias} workflow $WK_PATH_ARG wait --jobscripts "{sub_idx}:'
65
- + ",".join(str(i) for i in deps.keys())
66
- + '"'
67
- )
68
- else:
132
+ if not deps:
69
133
  return ""
134
+ return (
135
+ f"{workflow_app_alias} workflow $WK_PATH_ARG wait --jobscripts "
136
+ f'"{sub_idx}:{",".join(str(i) for i in deps)}"'
137
+ )
70
138
 
71
139
  @staticmethod
72
- def process_app_invoc_executable(app_invoc_exe):
140
+ def process_app_invoc_executable(app_invoc_exe: str) -> str:
73
141
  """
74
142
  Perform any post-processing of an application invocation command name.
75
143
  """
76
144
  return app_invoc_exe
77
145
 
78
- def process_JS_header_args(self, header_args: Dict) -> Dict:
146
+ def process_JS_header_args(
147
+ self, header_args: JobscriptHeaderArgs
148
+ ) -> JobscriptHeaderArgs:
79
149
  """
80
150
  Process the application invocation key in the jobscript header arguments.
81
151
  """
82
- app_invoc = self.process_app_invoc_executable(header_args["app_invoc"][0])
83
- if len(header_args["app_invoc"]) > 1:
84
- app_invoc += ' "' + header_args["app_invoc"][1] + '"'
85
-
86
- header_args["app_invoc"] = app_invoc
152
+ app_invoc_ = header_args["app_invoc"]
153
+ if not isinstance(app_invoc_, str):
154
+ app_invoc = self.process_app_invoc_executable(app_invoc_[0])
155
+ for item in app_invoc_[1:]:
156
+ app_invoc += f' "{item}"'
157
+ header_args["app_invoc"] = app_invoc
87
158
  return header_args
88
159
 
89
160
  def prepare_JS_path(self, js_path: Path) -> str:
@@ -92,8 +163,58 @@ class Shell(ABC):
92
163
  """
93
164
  return str(js_path)
94
165
 
95
- def prepare_element_run_dirs(self, run_dirs: List[List[Path]]) -> List[List[str]]:
166
+ def prepare_element_run_dirs(self, run_dirs: list[list[Path]]) -> list[list[str]]:
96
167
  """
97
168
  Prepare the element run directory names for use.
98
169
  """
99
- return [[str(j) for j in i] for i in run_dirs]
170
+ return [[str(path) for path in i] for i in run_dirs]
171
+
172
+ @abstractmethod
173
+ def format_save_parameter(
174
+ self,
175
+ workflow_app_alias: str,
176
+ param_name: str,
177
+ shell_var_name: str,
178
+ cmd_idx: int,
179
+ stderr: bool,
180
+ app_name: str,
181
+ ) -> str:
182
+ """
183
+ Produce code to save a parameter's value into the workflow persistent store.
184
+ """
185
+
186
+ @abstractmethod
187
+ def format_stream_assignment(self, shell_var_name: str, command: str) -> str:
188
+ """
189
+ Format a stream assignment.
190
+ """
191
+
192
+ @abstractmethod
193
+ def format_env_var_get(self, var: str) -> str:
194
+ """
195
+ Format retrieval of a shell environment variable.
196
+ """
197
+
198
+ @abstractmethod
199
+ def format_array(self, lst: list) -> str:
200
+ """
201
+ Format construction of a shell array.
202
+ """
203
+
204
+ @abstractmethod
205
+ def format_array_get_item(self, arr_name: str, index: int | str) -> str:
206
+ """
207
+ Format retrieval of a shell array item at a specified index.
208
+ """
209
+
210
+ @abstractmethod
211
+ def format_source_functions_file(self, app_name: str, commands: str) -> str:
212
+ """
213
+ Format sourcing (i.e. invocation) of the jobscript functions file.
214
+ """
215
+
216
+ @abstractmethod
217
+ def format_commands_file(self, app_name: str, commands: str) -> str:
218
+ """
219
+ Format the commands file.
220
+ """