hpcflow-new2 0.2.0a179__py3-none-any.whl → 0.2.0a180__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 (70) hide show
  1. hpcflow/_version.py +1 -1
  2. hpcflow/data/demo_data_manifest/__init__.py +3 -0
  3. hpcflow/sdk/__init__.py +4 -1
  4. hpcflow/sdk/app.py +160 -15
  5. hpcflow/sdk/cli.py +14 -0
  6. hpcflow/sdk/cli_common.py +83 -0
  7. hpcflow/sdk/config/__init__.py +4 -0
  8. hpcflow/sdk/config/callbacks.py +25 -2
  9. hpcflow/sdk/config/cli.py +4 -1
  10. hpcflow/sdk/config/config.py +188 -14
  11. hpcflow/sdk/config/config_file.py +91 -3
  12. hpcflow/sdk/config/errors.py +33 -0
  13. hpcflow/sdk/core/__init__.py +2 -0
  14. hpcflow/sdk/core/actions.py +492 -35
  15. hpcflow/sdk/core/cache.py +22 -0
  16. hpcflow/sdk/core/command_files.py +221 -5
  17. hpcflow/sdk/core/commands.py +57 -0
  18. hpcflow/sdk/core/element.py +407 -8
  19. hpcflow/sdk/core/environment.py +92 -0
  20. hpcflow/sdk/core/errors.py +245 -61
  21. hpcflow/sdk/core/json_like.py +72 -14
  22. hpcflow/sdk/core/loop.py +122 -21
  23. hpcflow/sdk/core/loop_cache.py +34 -9
  24. hpcflow/sdk/core/object_list.py +172 -26
  25. hpcflow/sdk/core/parallel.py +14 -0
  26. hpcflow/sdk/core/parameters.py +478 -25
  27. hpcflow/sdk/core/rule.py +31 -1
  28. hpcflow/sdk/core/run_dir_files.py +12 -2
  29. hpcflow/sdk/core/task.py +407 -80
  30. hpcflow/sdk/core/task_schema.py +70 -9
  31. hpcflow/sdk/core/test_utils.py +35 -0
  32. hpcflow/sdk/core/utils.py +101 -4
  33. hpcflow/sdk/core/validation.py +13 -1
  34. hpcflow/sdk/core/workflow.py +316 -96
  35. hpcflow/sdk/core/zarr_io.py +23 -0
  36. hpcflow/sdk/data/__init__.py +13 -0
  37. hpcflow/sdk/demo/__init__.py +3 -0
  38. hpcflow/sdk/helper/__init__.py +3 -0
  39. hpcflow/sdk/helper/cli.py +9 -0
  40. hpcflow/sdk/helper/helper.py +28 -0
  41. hpcflow/sdk/helper/watcher.py +33 -0
  42. hpcflow/sdk/log.py +40 -0
  43. hpcflow/sdk/persistence/__init__.py +14 -4
  44. hpcflow/sdk/persistence/base.py +289 -23
  45. hpcflow/sdk/persistence/json.py +29 -0
  46. hpcflow/sdk/persistence/pending.py +217 -107
  47. hpcflow/sdk/persistence/store_resource.py +58 -2
  48. hpcflow/sdk/persistence/utils.py +8 -0
  49. hpcflow/sdk/persistence/zarr.py +68 -1
  50. hpcflow/sdk/runtime.py +52 -10
  51. hpcflow/sdk/submission/__init__.py +3 -0
  52. hpcflow/sdk/submission/jobscript.py +198 -9
  53. hpcflow/sdk/submission/jobscript_info.py +13 -0
  54. hpcflow/sdk/submission/schedulers/__init__.py +60 -0
  55. hpcflow/sdk/submission/schedulers/direct.py +53 -0
  56. hpcflow/sdk/submission/schedulers/sge.py +45 -7
  57. hpcflow/sdk/submission/schedulers/slurm.py +45 -8
  58. hpcflow/sdk/submission/schedulers/utils.py +4 -0
  59. hpcflow/sdk/submission/shells/__init__.py +11 -1
  60. hpcflow/sdk/submission/shells/base.py +32 -1
  61. hpcflow/sdk/submission/shells/bash.py +36 -1
  62. hpcflow/sdk/submission/shells/os_version.py +18 -6
  63. hpcflow/sdk/submission/shells/powershell.py +22 -0
  64. hpcflow/sdk/submission/submission.py +88 -3
  65. hpcflow/sdk/typing.py +10 -1
  66. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/METADATA +1 -1
  67. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/RECORD +70 -70
  68. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/LICENSE +0 -0
  69. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/WHEEL +0 -0
  70. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,7 @@
1
+ """
2
+ A direct job "scheduler" that just runs immediate subprocesses.
3
+ """
4
+
1
5
  from pathlib import Path
2
6
  import shutil
3
7
  import signal
@@ -11,6 +15,22 @@ from hpcflow.sdk.submission.shells.base import Shell
11
15
 
12
16
 
13
17
  class DirectScheduler(NullScheduler):
18
+ """
19
+ A direct scheduler, that just runs jobs immediately as direct subprocesses.
20
+
21
+ The correct subclass (:py:class:`DirectPosix` or :py:class:`DirectWindows`) should
22
+ be used to create actual instances.
23
+
24
+ Keyword Args
25
+ ------------
26
+ shell_args: str
27
+ Arguments to pass to the shell. Pre-quoted.
28
+ shebang_args: str
29
+ Arguments to set on the shebang line. Pre-quoted.
30
+ options: dict
31
+ Options to the jobscript command.
32
+ """
33
+
14
34
  def __init__(self, *args, **kwargs):
15
35
  super().__init__(*args, **kwargs)
16
36
 
@@ -29,6 +49,9 @@ class DirectScheduler(NullScheduler):
29
49
  js_path: str,
30
50
  deps: List[Tuple],
31
51
  ) -> List[str]:
52
+ """
53
+ Get the concrete submission command.
54
+ """
32
55
  return shell.get_direct_submit_command(js_path)
33
56
 
34
57
  @staticmethod
@@ -104,6 +127,10 @@ class DirectScheduler(NullScheduler):
104
127
  js_refs: List[Tuple[int, List[str]]],
105
128
  jobscripts: List = None,
106
129
  ):
130
+ """
131
+ Cancel some jobs.
132
+ """
133
+
107
134
  def callback(proc):
108
135
  try:
109
136
  js = js_proc_id[proc.pid]
@@ -145,7 +172,21 @@ class DirectScheduler(NullScheduler):
145
172
 
146
173
 
147
174
  class DirectPosix(DirectScheduler):
175
+ """
176
+ A direct scheduler for POSIX systems.
177
+
178
+ Keyword Args
179
+ ------------
180
+ shell_args: str
181
+ Arguments to pass to the shell. Pre-quoted.
182
+ shebang_args: str
183
+ Arguments to set on the shebang line. Pre-quoted.
184
+ options: dict
185
+ Options to the jobscript command.
186
+ """
187
+
148
188
  _app_attr = "app"
189
+ #: Default shell.
149
190
  DEFAULT_SHELL_EXECUTABLE = "/bin/bash"
150
191
 
151
192
  def __init__(self, *args, **kwargs):
@@ -153,7 +194,19 @@ class DirectPosix(DirectScheduler):
153
194
 
154
195
 
155
196
  class DirectWindows(DirectScheduler):
197
+ """
198
+ A direct scheduler for Windows.
199
+
200
+ Keyword Args
201
+ ------------
202
+ shell_args: str
203
+ Arguments to pass to the shell. Pre-quoted.
204
+ options: dict
205
+ Options to the jobscript command.
206
+ """
207
+
156
208
  _app_attr = "app"
209
+ #: Default shell.
157
210
  DEFAULT_SHELL_EXECUTABLE = "powershell.exe"
158
211
 
159
212
  def __init__(self, *args, **kwargs):
@@ -1,3 +1,7 @@
1
+ """
2
+ An interface to SGE.
3
+ """
4
+
1
5
  from pathlib import Path
2
6
  import re
3
7
  from typing import Dict, List, Tuple
@@ -15,6 +19,18 @@ from hpcflow.sdk.submission.shells.base import Shell
15
19
 
16
20
  class SGEPosix(Scheduler):
17
21
  """
22
+ A scheduler that uses SGE.
23
+
24
+ Keyword Args
25
+ ------------
26
+ cwd_switch: str
27
+ Override of default switch to use to set the current working directory.
28
+ shell_args: str
29
+ Arguments to pass to the shell. Pre-quoted.
30
+ shebang_args: str
31
+ Arguments to set on the shebang line. Pre-quoted.
32
+ options: dict
33
+ Options to the jobscript command.
18
34
 
19
35
  Notes
20
36
  -----
@@ -29,17 +45,26 @@ class SGEPosix(Scheduler):
29
45
 
30
46
  _app_attr = "app"
31
47
 
48
+ #: Default args for shebang line.
32
49
  DEFAULT_SHEBANG_ARGS = ""
50
+ #: Default submission command.
33
51
  DEFAULT_SUBMIT_CMD = "qsub"
52
+ #: Default command to show the queue state.
34
53
  DEFAULT_SHOW_CMD = ["qstat"]
54
+ #: Default cancel command.
35
55
  DEFAULT_DEL_CMD = "qdel"
56
+ #: Default job control directive prefix.
36
57
  DEFAULT_JS_CMD = "#$"
58
+ #: Default prefix to enable array processing.
37
59
  DEFAULT_ARRAY_SWITCH = "-t"
60
+ #: Default shell variable with array ID.
38
61
  DEFAULT_ARRAY_ITEM_VAR = "SGE_TASK_ID"
62
+ #: Default switch to control CWD.
39
63
  DEFAULT_CWD_SWITCH = "-cwd"
64
+ #: Default command to get the login nodes.
40
65
  DEFAULT_LOGIN_NODES_CMD = ["qconf", "-sh"]
41
66
 
42
- # maps scheduler states:
67
+ #: Maps scheduler state codes to :py:class:`JobscriptElementState` values.
43
68
  state_lookup = {
44
69
  "qw": JobscriptElementState.pending,
45
70
  "hq": JobscriptElementState.waiting,
@@ -136,7 +161,7 @@ class SGEPosix(Scheduler):
136
161
  nodes = stdout.strip().split("\n")
137
162
  return nodes
138
163
 
139
- def format_core_request_lines(self, resources):
164
+ def _format_core_request_lines(self, resources):
140
165
  lns = []
141
166
  if resources.num_cores > 1:
142
167
  lns.append(
@@ -146,10 +171,10 @@ class SGEPosix(Scheduler):
146
171
  lns.append(f"{self.js_cmd} -tc {resources.max_array_items}")
147
172
  return lns
148
173
 
149
- def format_array_request(self, num_elements):
174
+ def _format_array_request(self, num_elements):
150
175
  return f"{self.js_cmd} {self.array_switch} 1-{num_elements}"
151
176
 
152
- def format_std_stream_file_option_lines(self, is_array, sub_idx):
177
+ def _format_std_stream_file_option_lines(self, is_array, sub_idx):
153
178
  # note: we can't modify the file names
154
179
  base = f"./artifacts/submissions/{sub_idx}"
155
180
  return [
@@ -158,13 +183,16 @@ class SGEPosix(Scheduler):
158
183
  ]
159
184
 
160
185
  def format_options(self, resources, num_elements, is_array, sub_idx):
186
+ """
187
+ Format the options to the jobscript command.
188
+ """
161
189
  opts = []
162
190
  opts.append(self.format_switch(self.cwd_switch))
163
- opts.extend(self.format_core_request_lines(resources))
191
+ opts.extend(self._format_core_request_lines(resources))
164
192
  if is_array:
165
- opts.append(self.format_array_request(num_elements))
193
+ opts.append(self._format_array_request(num_elements))
166
194
 
167
- opts.extend(self.format_std_stream_file_option_lines(is_array, sub_idx))
195
+ opts.extend(self._format_std_stream_file_option_lines(is_array, sub_idx))
168
196
 
169
197
  for opt_k, opt_v in self.options.items():
170
198
  if isinstance(opt_v, list):
@@ -197,6 +225,13 @@ class SGEPosix(Scheduler):
197
225
  js_path: str,
198
226
  deps: List[Tuple],
199
227
  ) -> List[str]:
228
+ """
229
+ Get the command to use to submit a job to the scheduler.
230
+
231
+ Returns
232
+ -------
233
+ List of argument words.
234
+ """
200
235
  cmd = [self.submit_cmd, "-terse"]
201
236
 
202
237
  dep_job_IDs = []
@@ -285,6 +320,9 @@ class SGEPosix(Scheduler):
285
320
  return info
286
321
 
287
322
  def cancel_jobs(self, js_refs: List[str], jobscripts: List = None):
323
+ """
324
+ Cancel submitted jobs.
325
+ """
288
326
  cmd = [self.del_cmd] + js_refs
289
327
  self.app.submission_logger.info(
290
328
  f"cancelling {self.__class__.__name__} jobscripts with command: {cmd}."
@@ -1,3 +1,7 @@
1
+ """
2
+ An interface to SLURM.
3
+ """
4
+
1
5
  from pathlib import Path
2
6
  import subprocess
3
7
  import time
@@ -18,12 +22,24 @@ from hpcflow.sdk.submission.shells.base import Shell
18
22
 
19
23
  class SlurmPosix(Scheduler):
20
24
  """
25
+ A scheduler that uses SLURM.
26
+
27
+ Keyword Args
28
+ ------------
29
+ shell_args: str
30
+ Arguments to pass to the shell. Pre-quoted.
31
+ shebang_args: str
32
+ Arguments to set on the shebang line. Pre-quoted.
33
+ options: dict
34
+ Options to the jobscript command.
21
35
 
22
36
  Notes
23
37
  -----
24
38
  - runs in current working directory by default [2]
25
39
 
26
- # TODO: consider getting memory usage like: https://stackoverflow.com/a/44143229/5042280
40
+ Todo
41
+ ----
42
+ - consider getting memory usage like: https://stackoverflow.com/a/44143229/5042280
27
43
 
28
44
  References
29
45
  ----------
@@ -34,16 +50,24 @@ class SlurmPosix(Scheduler):
34
50
 
35
51
  _app_attr = "app"
36
52
 
53
+ #: Default shell.
37
54
  DEFAULT_SHELL_EXECUTABLE = "/bin/bash"
55
+ #: Default args for shebang line.
38
56
  DEFAULT_SHEBANG_ARGS = ""
57
+ #: Default submission command.
39
58
  DEFAULT_SUBMIT_CMD = "sbatch"
59
+ #: Default command to show the queue state.
40
60
  DEFAULT_SHOW_CMD = ["squeue", "--me"]
61
+ #: Default cancel command.
41
62
  DEFAULT_DEL_CMD = "scancel"
63
+ #: Default job control directive prefix.
42
64
  DEFAULT_JS_CMD = "#SBATCH"
65
+ #: Default prefix to enable array processing.
43
66
  DEFAULT_ARRAY_SWITCH = "--array"
67
+ #: Default shell variable with array ID.
44
68
  DEFAULT_ARRAY_ITEM_VAR = "SLURM_ARRAY_TASK_ID"
45
69
 
46
- # maps scheduler states:
70
+ #: Maps scheduler state codes to :py:class:`JobscriptElementState` values.
47
71
  state_lookup = {
48
72
  "PENDING": JobscriptElementState.pending,
49
73
  "RUNNING": JobscriptElementState.running,
@@ -301,7 +325,7 @@ class SlurmPosix(Scheduler):
301
325
  if part_match:
302
326
  resources.SLURM_partition = part_match
303
327
 
304
- def format_core_request_lines(self, resources):
328
+ def _format_core_request_lines(self, resources):
305
329
  lns = []
306
330
  if resources.SLURM_partition:
307
331
  lns.append(f"{self.js_cmd} --partition {resources.SLURM_partition}")
@@ -324,13 +348,13 @@ class SlurmPosix(Scheduler):
324
348
 
325
349
  return lns
326
350
 
327
- def format_array_request(self, num_elements, resources):
351
+ def _format_array_request(self, num_elements, resources):
328
352
  # TODO: Slurm docs start indices at zero, why are we starting at one?
329
353
  # https://slurm.schedmd.com/sbatch.html#OPT_array
330
354
  max_str = f"%{resources.max_array_items}" if resources.max_array_items else ""
331
355
  return f"{self.js_cmd} {self.array_switch} 1-{num_elements}{max_str}"
332
356
 
333
- def format_std_stream_file_option_lines(self, is_array, sub_idx):
357
+ def _format_std_stream_file_option_lines(self, is_array, sub_idx):
334
358
  base = r"%x_"
335
359
  if is_array:
336
360
  base += r"%A.%a"
@@ -344,12 +368,15 @@ class SlurmPosix(Scheduler):
344
368
  ]
345
369
 
346
370
  def format_options(self, resources, num_elements, is_array, sub_idx):
371
+ """
372
+ Format the options to the scheduler.
373
+ """
347
374
  opts = []
348
- opts.extend(self.format_core_request_lines(resources))
375
+ opts.extend(self._format_core_request_lines(resources))
349
376
  if is_array:
350
- opts.append(self.format_array_request(num_elements, resources))
377
+ opts.append(self._format_array_request(num_elements, resources))
351
378
 
352
- opts.extend(self.format_std_stream_file_option_lines(is_array, sub_idx))
379
+ opts.extend(self._format_std_stream_file_option_lines(is_array, sub_idx))
353
380
 
354
381
  for opt_k, opt_v in self.options.items():
355
382
  if isinstance(opt_v, list):
@@ -387,6 +414,13 @@ class SlurmPosix(Scheduler):
387
414
  js_path: str,
388
415
  deps: List[Tuple],
389
416
  ) -> List[str]:
417
+ """
418
+ Get the command to use to submit a job to the scheduler.
419
+
420
+ Returns
421
+ -------
422
+ List of argument words.
423
+ """
390
424
  cmd = [self.submit_cmd, "--parsable"]
391
425
 
392
426
  dep_cmd = []
@@ -528,6 +562,9 @@ class SlurmPosix(Scheduler):
528
562
  return info
529
563
 
530
564
  def cancel_jobs(self, js_refs: List[str], jobscripts: List = None):
565
+ """
566
+ Cancel submitted jobs.
567
+ """
531
568
  cmd = [self.del_cmd] + js_refs
532
569
  self.app.submission_logger.info(
533
570
  f"cancelling {self.__class__.__name__} jobscripts with command: {cmd}."
@@ -1,3 +1,7 @@
1
+ """
2
+ Helper for running a subprocess.
3
+ """
4
+
1
5
  import subprocess
2
6
 
3
7
 
@@ -1,3 +1,6 @@
1
+ """
2
+ Adapters for various shells.
3
+ """
1
4
  import os
2
5
  from typing import Dict, Optional
3
6
 
@@ -7,6 +10,7 @@ from .base import Shell
7
10
  from .bash import Bash, WSLBash
8
11
  from .powershell import WindowsPowerShell
9
12
 
13
+ #: All supported shells.
10
14
  ALL_SHELLS = {
11
15
  "bash": {"posix": Bash},
12
16
  "powershell": {"nt": WindowsPowerShell},
@@ -14,7 +18,7 @@ ALL_SHELLS = {
14
18
  "wsl": {"nt": WSLBash}, # TODO: cast this to wsl+bash in ResourceSpec?
15
19
  }
16
20
 
17
- # used to set the default shell in the default config:
21
+ #: The default shell in the default config.
18
22
  DEFAULT_SHELL_NAMES = {
19
23
  "posix": "bash",
20
24
  "nt": "powershell",
@@ -22,11 +26,17 @@ DEFAULT_SHELL_NAMES = {
22
26
 
23
27
 
24
28
  def get_supported_shells(os_name: Optional[str] = None) -> Dict[str, Shell]:
29
+ """
30
+ Get shells supported on the current or given OS.
31
+ """
25
32
  os_name = os_name or os.name
26
33
  return {k: v.get(os_name) for k, v in ALL_SHELLS.items() if v.get(os_name)}
27
34
 
28
35
 
29
36
  def get_shell(shell_name, os_name: Optional[str] = None, **kwargs) -> Shell:
37
+ """
38
+ Get a shell interface with the given name for a given OS (or the current one).
39
+ """
30
40
  # TODO: apply config default shell args?
31
41
 
32
42
  os_name = os_name or os.name
@@ -1,3 +1,7 @@
1
+ """
2
+ Base model of a shell.
3
+ """
4
+
1
5
  from abc import ABC, abstractmethod
2
6
  from pathlib import Path
3
7
  from typing import Dict, List, Optional
@@ -10,6 +14,12 @@ class Shell(ABC):
10
14
  bash on a POSIX OS, and provides snippets that are used to compose a jobscript for
11
15
  that combination.
12
16
 
17
+ Parameters
18
+ ----------
19
+ executable: str
20
+ Which executable implements the shell.
21
+ os_args:
22
+ Arguments to pass to the shell.
13
23
  """
14
24
 
15
25
  def __init__(self, executable=None, os_args=None):
@@ -25,10 +35,16 @@ class Shell(ABC):
25
35
 
26
36
  @property
27
37
  def executable(self) -> List[str]:
38
+ """
39
+ The executable to use plus any mandatory arguments.
40
+ """
28
41
  return [self._executable]
29
42
 
30
43
  @property
31
- def shebang_executable(self) -> List[str]:
44
+ def shebang_executable(self) -> str:
45
+ """
46
+ The executable to use in a shebang line.
47
+ """
32
48
  return self.executable
33
49
 
34
50
  def get_direct_submit_command(self, js_path) -> List[str]:
@@ -40,6 +56,9 @@ class Shell(ABC):
40
56
  """Get shell and operating system information."""
41
57
 
42
58
  def get_wait_command(self, workflow_app_alias: str, sub_idx: int, deps: Dict):
59
+ """
60
+ Get the command to wait for a workflow.
61
+ """
43
62
  if deps:
44
63
  return (
45
64
  f'{workflow_app_alias} workflow $WK_PATH_ARG wait --jobscripts "{sub_idx}:'
@@ -51,9 +70,15 @@ class Shell(ABC):
51
70
 
52
71
  @staticmethod
53
72
  def process_app_invoc_executable(app_invoc_exe):
73
+ """
74
+ Perform any post-processing of an application invocation command name.
75
+ """
54
76
  return app_invoc_exe
55
77
 
56
78
  def process_JS_header_args(self, header_args: Dict) -> Dict:
79
+ """
80
+ Process the application invocation key in the jobscript header arguments.
81
+ """
57
82
  app_invoc = self.process_app_invoc_executable(header_args["app_invoc"][0])
58
83
  if len(header_args["app_invoc"]) > 1:
59
84
  app_invoc += ' "' + header_args["app_invoc"][1] + '"'
@@ -62,7 +87,13 @@ class Shell(ABC):
62
87
  return header_args
63
88
 
64
89
  def prepare_JS_path(self, js_path: Path) -> str:
90
+ """
91
+ Prepare the jobscript path for use.
92
+ """
65
93
  return str(js_path)
66
94
 
67
95
  def prepare_element_run_dirs(self, run_dirs: List[List[Path]]) -> List[List[str]]:
96
+ """
97
+ Prepare the element run directory names for use.
98
+ """
68
99
  return [[str(j) for j in i] for i in run_dirs]
@@ -1,3 +1,7 @@
1
+ """
2
+ Shell models based on the Bourne-Again Shell.
3
+ """
4
+
1
5
  from pathlib import Path
2
6
  import subprocess
3
7
  from textwrap import dedent, indent
@@ -11,14 +15,22 @@ from hpcflow.sdk.submission.shells.os_version import (
11
15
 
12
16
 
13
17
  class Bash(Shell):
14
- """Class to represent using bash on a POSIX OS to generate and submit a jobscript."""
18
+ """
19
+ Class to represent using bash on a POSIX OS to generate and submit a jobscript.
20
+ """
15
21
 
22
+ #: Default for executable name.
16
23
  DEFAULT_EXE = "/bin/bash"
17
24
 
25
+ #: File extension for jobscripts.
18
26
  JS_EXT = ".sh"
27
+ #: Basic indent.
19
28
  JS_INDENT = " "
29
+ #: Indent for environment setup.
20
30
  JS_ENV_SETUP_INDENT = 2 * JS_INDENT
31
+ #: Template for the jobscript shebang line.
21
32
  JS_SHEBANG = """#!{shebang_executable} {shebang_args}"""
33
+ #: Template for the common part of the jobscript header.
22
34
  JS_HEADER = dedent(
23
35
  """\
24
36
  {workflow_app_alias} () {{
@@ -39,6 +51,7 @@ class Bash(Shell):
39
51
  ELEM_RUN_DIR_FILE="$WK_PATH/artifacts/submissions/${{SUB_IDX}}/{element_run_dirs_file_path}"
40
52
  """
41
53
  )
54
+ #: Template for the jobscript header when scheduled.
42
55
  JS_SCHEDULER_HEADER = dedent(
43
56
  """\
44
57
  {shebang}
@@ -47,6 +60,7 @@ class Bash(Shell):
47
60
  {header}
48
61
  """
49
62
  )
63
+ #: Template for the jobscript header when directly executed.
50
64
  JS_DIRECT_HEADER = dedent(
51
65
  """\
52
66
  {shebang}
@@ -55,6 +69,7 @@ class Bash(Shell):
55
69
  {wait_command}
56
70
  """
57
71
  )
72
+ #: Template for the jobscript body.
58
73
  JS_MAIN = dedent(
59
74
  """\
60
75
  elem_EAR_IDs=`sed "$((${{JS_elem_idx}} + 1))q;d" "$EAR_ID_FILE"`
@@ -103,6 +118,7 @@ class Bash(Shell):
103
118
  done
104
119
  """
105
120
  )
121
+ #: Template for the element processing loop in a jobscript.
106
122
  JS_ELEMENT_LOOP = dedent(
107
123
  """\
108
124
  for ((JS_elem_idx=0;JS_elem_idx<{num_elements};JS_elem_idx++))
@@ -112,6 +128,7 @@ class Bash(Shell):
112
128
  cd "$WK_PATH"
113
129
  """
114
130
  )
131
+ #: Template for the array handling code in a jobscript.
115
132
  JS_ELEMENT_ARRAY = dedent(
116
133
  """\
117
134
  JS_elem_idx=$(({scheduler_array_item_var} - 1))
@@ -125,6 +142,9 @@ class Bash(Shell):
125
142
 
126
143
  @property
127
144
  def linux_release_file(self):
145
+ """
146
+ The name of the file describing the Linux version.
147
+ """
128
148
  return self.os_args["linux_release_file"]
129
149
 
130
150
  def _get_OS_info_POSIX(self):
@@ -169,6 +189,9 @@ class Bash(Shell):
169
189
  return app_invoc_exe
170
190
 
171
191
  def format_stream_assignment(self, shell_var_name, command):
192
+ """
193
+ Produce code to assign the output of the command to a shell variable.
194
+ """
172
195
  return f"{shell_var_name}=`{command}`"
173
196
 
174
197
  def format_save_parameter(
@@ -180,6 +203,9 @@ class Bash(Shell):
180
203
  cmd_idx: int,
181
204
  stderr: bool,
182
205
  ):
206
+ """
207
+ Produce code to save a parameter's value into the workflow persistent store.
208
+ """
183
209
  # TODO: quote shell_var_name as well? e.g. if it's a white-space delimited list?
184
210
  # and test.
185
211
  stderr_str = " --stderr" if stderr else ""
@@ -192,6 +218,9 @@ class Bash(Shell):
192
218
  )
193
219
 
194
220
  def format_loop_check(self, workflow_app_alias: str, loop_name: str, run_ID: int):
221
+ """
222
+ Produce code to check the looping status of part of a workflow.
223
+ """
195
224
  return (
196
225
  f"{workflow_app_alias} "
197
226
  f'internal workflow "$WK_PATH_ARG" check-loop '
@@ -248,8 +277,14 @@ class Bash(Shell):
248
277
 
249
278
 
250
279
  class WSLBash(Bash):
280
+ """
281
+ A variant of bash that handles running under WSL on Windows.
282
+ """
283
+
284
+ #: Default name of the WSL interface executable.
251
285
  DEFAULT_WSL_EXE = "wsl"
252
286
 
287
+ #: Template for the common part of the jobscript header.
253
288
  JS_HEADER = Bash.JS_HEADER.replace(
254
289
  'WK_PATH_ARG="$WK_PATH"',
255
290
  'WK_PATH_ARG=`wslpath -m "$WK_PATH"`',
@@ -1,3 +1,7 @@
1
+ """
2
+ Operating system information discovery helpers.
3
+ """
4
+
1
5
  import os
2
6
  import platform
3
7
  import re
@@ -8,6 +12,9 @@ DEFAULT_LINUX_RELEASE_FILE = "/etc/os-release"
8
12
 
9
13
 
10
14
  def get_OS_info() -> Dict:
15
+ """
16
+ Get basic operating system version info.
17
+ """
11
18
  uname = platform.uname()
12
19
  return {
13
20
  "OS_name": uname.system,
@@ -17,6 +24,9 @@ def get_OS_info() -> Dict:
17
24
 
18
25
 
19
26
  def get_OS_info_windows() -> Dict:
27
+ """
28
+ Get operating system version info: Windows version.
29
+ """
20
30
  return get_OS_info()
21
31
 
22
32
 
@@ -26,15 +36,17 @@ def get_OS_info_POSIX(
26
36
  linux_release_file: Optional[str] = None,
27
37
  ) -> Dict:
28
38
  """
39
+ Get operating system version info: POSIX version.
40
+
29
41
  Parameters
30
42
  ----------
31
- WSL_executable
43
+ WSL_executable:
32
44
  Executable to run subprocess calls via WSL on Windows.
33
- use_py
34
- If True, use the `platform.uname` Python function to get the OS information.
35
- Otherwise use subprocess to call `uname`. We set this to False when getting OS
36
- info in WSL on Windows, since we need to call the WSL executable.
37
- linux_release_file
45
+ use_py:
46
+ If True, use the :py:func:`platform.uname` Python function to get the OS
47
+ information. Otherwise use subprocess to call ``uname``. We set this to False
48
+ when getting OS info in WSL on Windows, since we need to call the WSL executable.
49
+ linux_release_file:
38
50
  If on Linux, record the name and version fields from this file.
39
51
 
40
52
  """