hpcflow-new2 0.2.0a179__py3-none-any.whl → 0.2.0a181__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.0a181.dist-info}/METADATA +3 -3
  67. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a181.dist-info}/RECORD +70 -70
  68. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a181.dist-info}/LICENSE +0 -0
  69. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a181.dist-info}/WHEEL +0 -0
  70. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a181.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/core/cache.py CHANGED
@@ -1,3 +1,7 @@
1
+ """
2
+ Dependency resolution cache.
3
+ """
4
+
1
5
  from collections import defaultdict
2
6
  from dataclasses import dataclass
3
7
  from typing import Set, Dict
@@ -9,21 +13,39 @@ from hpcflow.sdk.log import TimeIt
9
13
  class DependencyCache:
10
14
  """Class to bulk-retrieve dependencies between elements, iterations, and runs."""
11
15
 
16
+ #: What EARs (by ID) a given EAR depends on.
12
17
  run_dependencies: Dict[int, Set]
18
+ #: What EARs (by ID) are depending on a given EAR.
13
19
  run_dependents: Dict[int, Set]
20
+ #: What EARs (by ID) a given iteration depends on.
14
21
  iter_run_dependencies: Dict[int, Set]
22
+ #: What iterations (by ID) a given iteration depends on.
15
23
  iter_iter_dependencies: Dict[int, Set]
24
+ #: What iterations (by ID) a given element depends on.
16
25
  elem_iter_dependencies: Dict[int, Set]
26
+ #: What elements (by ID) a given element depends on.
17
27
  elem_elem_dependencies: Dict[int, Set]
28
+ #: What elements (by ID) are depending on a given element.
18
29
  elem_elem_dependents: Dict[int, Set]
30
+ #: Transitive closure of :py:attr:`elem_elem_dependents`.
19
31
  elem_elem_dependents_rec: Dict[int, Set]
20
32
 
33
+ #: The elements of the workflow that this cache was built from.
21
34
  elements: Dict
35
+ #: The iterations of the workflow that this cache was built from.
22
36
  iterations: Dict
23
37
 
24
38
  @classmethod
25
39
  @TimeIt.decorator
26
40
  def build(cls, workflow):
41
+ """
42
+ Build a cache instance.
43
+
44
+ Parameters
45
+ ----------
46
+ workflow: ~hpcflow.app.Workflow
47
+ The workflow to build the cache from.
48
+ """
27
49
  num_iters = workflow.num_element_iterations
28
50
  num_elems = workflow.num_elements
29
51
  num_runs = workflow.num_EARs
@@ -1,3 +1,7 @@
1
+ """
2
+ Model of files that hold commands.
3
+ """
4
+
1
5
  from __future__ import annotations
2
6
  import copy
3
7
  from dataclasses import dataclass, field
@@ -15,12 +19,18 @@ from hpcflow.sdk.core.parameters import _process_demo_data_strings
15
19
 
16
20
  @dataclass
17
21
  class FileSpec(JSONLike):
22
+ """
23
+ A specification of a file handled by a workflow.
24
+ """
25
+
18
26
  _app_attr = "app"
19
27
 
20
28
  _validation_schema = "files_spec_schema.yaml"
21
29
  _child_objects = (ChildObjectSpec(name="name", class_name="FileNameSpec"),)
22
30
 
31
+ #: Label for this file specification.
23
32
  label: str
33
+ #: The name of the file.
24
34
  name: str
25
35
  _hash_value: Optional[str] = field(default=None, repr=False)
26
36
 
@@ -30,6 +40,9 @@ class FileSpec(JSONLike):
30
40
  )
31
41
 
32
42
  def value(self, directory="."):
43
+ """
44
+ The path to a file, optionally resolved with respect to a particular directory.
45
+ """
33
46
  return self.name.value(directory)
34
47
 
35
48
  def __eq__(self, other: object) -> bool:
@@ -41,19 +54,42 @@ class FileSpec(JSONLike):
41
54
 
42
55
  @property
43
56
  def stem(self):
57
+ """
58
+ The stem of the file name.
59
+ """
44
60
  return self.name.stem
45
61
 
46
62
  @property
47
63
  def ext(self):
64
+ """
65
+ The extension of the file name.
66
+ """
48
67
  return self.name.ext
49
68
 
50
69
 
51
70
  class FileNameSpec(JSONLike):
71
+ """
72
+ The name of a file handled by a workflow, or a pattern that matches multiple files.
73
+
74
+ Parameters
75
+ ----------
76
+ name: str
77
+ The name or pattern.
78
+ args: list
79
+ Positional arguments to use when formatting the name.
80
+ Can be omitted if the name does not contain a Python formatting pattern.
81
+ is_regex: bool
82
+ If true, the name is used as a regex to search for actual files.
83
+ """
84
+
52
85
  _app_attr = "app"
53
86
 
54
87
  def __init__(self, name, args=None, is_regex=False):
88
+ #: The name or pattern.
55
89
  self.name = name
90
+ #: Positional arguments to use when formatting the name.
56
91
  self.args = args
92
+ #: Whether the name is used as a regex to search for actual files.
57
93
  self.is_regex = is_regex
58
94
 
59
95
  def __eq__(self, other: object) -> bool:
@@ -67,13 +103,28 @@ class FileNameSpec(JSONLike):
67
103
 
68
104
  @property
69
105
  def stem(self):
106
+ """
107
+ The stem of the name or pattern.
108
+ """
70
109
  return self.app.FileNameStem(self)
71
110
 
72
111
  @property
73
112
  def ext(self):
113
+ """
114
+ The extension of the name or pattern.
115
+ """
74
116
  return self.app.FileNameExt(self)
75
117
 
76
118
  def value(self, directory="."):
119
+ """
120
+ Get the template-resolved name of the file
121
+ (or files matched if the name is a regex pattern).
122
+
123
+ Parameters
124
+ ----------
125
+ directory: str
126
+ Where to resolve values with respect to.
127
+ """
77
128
  format_args = [i.value(directory) for i in self.args or []]
78
129
  value = self.name.format(*format_args)
79
130
  if self.is_regex:
@@ -86,22 +137,60 @@ class FileNameSpec(JSONLike):
86
137
 
87
138
  @dataclass
88
139
  class FileNameStem(JSONLike):
140
+ """
141
+ The stem of a file name.
142
+ """
143
+
144
+ #: The file specification this is derived from.
89
145
  file_name: app.FileNameSpec
90
146
 
91
147
  def value(self, directory=None):
148
+ """
149
+ Get the stem, possibly with directory specified.
150
+ """
92
151
  return Path(self.file_name.value(directory)).stem
93
152
 
94
153
 
95
154
  @dataclass
96
155
  class FileNameExt(JSONLike):
156
+ """
157
+ The extension of a file name.
158
+ """
159
+
160
+ #: The file specification this is derived from.
97
161
  file_name: app.FileNameSpec
98
162
 
99
163
  def value(self, directory=None):
164
+ """
165
+ Get the extension.
166
+ """
100
167
  return Path(self.file_name.value(directory)).suffix
101
168
 
102
169
 
103
170
  @dataclass
104
171
  class InputFileGenerator(JSONLike):
172
+ """
173
+ Represents a script that is run to generate input files for an action.
174
+
175
+ Parameters
176
+ ----------
177
+ input_file:
178
+ The file to generate.
179
+ inputs: list[~hpcflow.app.Parameter]
180
+ The input parameters to the generator.
181
+ script:
182
+ The script that generates the input.
183
+ environment:
184
+ The environment in which to run the generator.
185
+ script_pass_env_spec:
186
+ Whether to pass in the environment.
187
+ abortable:
188
+ Whether the generator can be stopped early.
189
+ Quick-running scripts tend to not need this.
190
+ rules: list[~hpcflow.app.ActionRule]
191
+ User-specified rules for whether to run the generator.
192
+ """
193
+
105
194
  _app_attr = "app"
106
195
 
107
196
  _child_objects = (
@@ -127,12 +216,20 @@ class InputFileGenerator(JSONLike):
127
216
  ),
128
217
  )
129
218
 
219
+ #: The file to generate.
130
220
  input_file: app.FileSpec
221
+ #: The input parameters to the generator.
131
222
  inputs: List[app.Parameter]
223
+ #: The script that generates the inputs.
132
224
  script: str = None
225
+ #: The environment in which to run the generator.
133
226
  environment: app.Environment = None
227
+ #: Whether to pass in the environment.
134
228
  script_pass_env_spec: Optional[bool] = False
229
+ #: Whether the generator can be stopped early.
230
+ #: Quick-running scripts tend to not need this.
135
231
  abortable: Optional[bool] = False
232
+ #: User-specified rules for whether to run the generator.
136
233
  rules: Optional[List[app.ActionRule]] = None
137
234
 
138
235
  def __post_init__(self):
@@ -190,9 +287,10 @@ class InputFileGenerator(JSONLike):
190
287
  return out
191
288
 
192
289
  def write_source(self, action, env_spec: Dict[str, Any]):
193
-
194
- # write the script if it is specified as a snippet script, otherwise we assume
195
- # the script already exists in the working directory:
290
+ """
291
+ Write the script if it is specified as a snippet script, otherwise we assume
292
+ the script already exists in the working directory.
293
+ """
196
294
  snip_path = action.get_snippet_script_path(self.script, env_spec)
197
295
  if snip_path:
198
296
  source_str = self.compose_source(snip_path)
@@ -203,13 +301,35 @@ class InputFileGenerator(JSONLike):
203
301
  @dataclass
204
302
  class OutputFileParser(JSONLike):
205
303
  """
304
+ Represents a script that is run to parse output files from an action and create outputs.
305
+
206
306
  Parameters
207
307
  ----------
208
- output
308
+ output_files: list[FileSpec]
309
+ The output files that this parser will parse.
310
+ output: ~hpcflow.app.Parameter
209
311
  The singular output parsed by this parser. Not to be confused with `outputs` (plural).
210
- outputs
312
+ script: str
313
+ The name of the file containing the output file parser source.
314
+ environment: ~hpcflow.app.Environment
315
+ The environment to use to run the parser.
316
+ inputs: list[str]
317
+ The other inputs to the parser.
318
+ outputs: list[str]
211
319
  Optional multiple outputs from the upstream actions of the schema that are
212
320
  required to parametrise this parser.
321
+ options: dict
322
+ Miscellaneous options.
323
+ script_pass_env_spec: bool
324
+ Whether to pass the environment specifier to the script.
325
+ abortable: bool
326
+ Whether this script can be aborted.
327
+ save_files: list[str]
328
+ The files that should be saved to the persistent store for the workflow.
329
+ clean_files: list[str]
330
+ The files that should be immediately removed.
331
+ rules: list[~hpcflow.app.ActionRule]
332
+ Rules for whether to enable this parser.
213
333
  """
214
334
 
215
335
  _child_objects = (
@@ -249,17 +369,32 @@ class OutputFileParser(JSONLike):
249
369
  ),
250
370
  )
251
371
 
372
+ #: The output files that this parser will parse.
252
373
  output_files: List[app.FileSpec]
374
+ #: The singular output parsed by this parser.
375
+ #: Not to be confused with :py:attr:`outputs` (plural).
253
376
  output: Optional[app.Parameter] = None
377
+ #: The name of the file containing the output file parser source.
254
378
  script: str = None
379
+ #: The environment to use to run the parser.
255
380
  environment: Environment = None
381
+ #: The other inputs to the parser.
256
382
  inputs: List[str] = None
383
+ #: Optional multiple outputs from the upstream actions of the schema that are
384
+ #: required to parametrise this parser.
385
+ #: Not to be confused with :py:attr:`output` (plural).
257
386
  outputs: List[str] = None
387
+ #: Miscellaneous options.
258
388
  options: Dict = None
389
+ #: Whether to pass the environment specifier to the script.
259
390
  script_pass_env_spec: Optional[bool] = False
391
+ #: Whether this script can be aborted.
260
392
  abortable: Optional[bool] = False
393
+ #: The files that should be saved to the persistent store for the workflow.
261
394
  save_files: Union[List[str], bool] = True
395
+ #: The files that should be immediately removed.
262
396
  clean_up: Optional[List[str]] = None
397
+ #: Rules for whether to enable this parser.
263
398
  rules: Optional[List[app.ActionRule]] = None
264
399
 
265
400
  def __post_init__(self):
@@ -345,6 +480,9 @@ class OutputFileParser(JSONLike):
345
480
  return out
346
481
 
347
482
  def write_source(self, action, env_spec: Dict[str, Any]):
483
+ """
484
+ Write the actual output parser to a file so it can be enacted.
485
+ """
348
486
  if self.output is None:
349
487
  # might be used just for saving files:
350
488
  return
@@ -485,20 +623,32 @@ class _FileContentsSpecifier(JSONLike):
485
623
  return val
486
624
 
487
625
  def read_contents(self):
626
+ """
627
+ Get the actual contents of the file.
628
+ """
488
629
  with self.path.open("r") as fh:
489
630
  return fh.read()
490
631
 
491
632
  @property
492
633
  def path(self):
634
+ """
635
+ The path to the file.
636
+ """
493
637
  path = self._get_value("path")
494
638
  return Path(path) if path else None
495
639
 
496
640
  @property
497
641
  def store_contents(self):
642
+ """
643
+ Whether the file's contents are stored in the workflow's persistent store.
644
+ """
498
645
  return self._get_value("store_contents")
499
646
 
500
647
  @property
501
648
  def contents(self):
649
+ """
650
+ The contents of the file.
651
+ """
502
652
  if self.store_contents:
503
653
  contents = self._get_value("contents")
504
654
  else:
@@ -508,10 +658,16 @@ class _FileContentsSpecifier(JSONLike):
508
658
 
509
659
  @property
510
660
  def extension(self):
661
+ """
662
+ The extension of the file.
663
+ """
511
664
  return self._get_value("extension")
512
665
 
513
666
  @property
514
667
  def workflow(self) -> app.Workflow:
668
+ """
669
+ The owning workflow.
670
+ """
515
671
  if self._workflow:
516
672
  return self._workflow
517
673
  elif self._element_set:
@@ -519,6 +675,23 @@ class _FileContentsSpecifier(JSONLike):
519
675
 
520
676
 
521
677
  class InputFile(_FileContentsSpecifier):
678
+ """
679
+ An input file.
680
+
681
+ Parameters
682
+ ----------
683
+ file:
684
+ What file is this?
685
+ path: Path
686
+ Where is the (original) file?
687
+ contents: str
688
+ What is the contents of the file (if already known)?
689
+ extension: str
690
+ What is the extension of the file?
691
+ store_contents: bool
692
+ Are the file's contents to be cached in the workflow persistent store?
693
+ """
694
+
522
695
  _child_objects = (
523
696
  ChildObjectSpec(
524
697
  name="file",
@@ -536,6 +709,7 @@ class InputFile(_FileContentsSpecifier):
536
709
  extension: Optional[str] = "",
537
710
  store_contents: Optional[bool] = True,
538
711
  ):
712
+ #: What file is this?
539
713
  self.file = file
540
714
  if not isinstance(self.file, FileSpec):
541
715
  self.file = self.app.command_files.get(self.file.label)
@@ -571,14 +745,39 @@ class InputFile(_FileContentsSpecifier):
571
745
 
572
746
  @property
573
747
  def normalised_files_path(self):
748
+ """
749
+ Standard name for the file within the workflow.
750
+ """
574
751
  return self.file.label
575
752
 
576
753
  @property
577
754
  def normalised_path(self):
755
+ """
756
+ Full workflow value path to the file.
757
+
758
+ Note
759
+ ----
760
+ This is not the same as the path in the filesystem.
761
+ """
578
762
  return f"input_files.{self.normalised_files_path}"
579
763
 
580
764
 
581
765
  class InputFileGeneratorSource(_FileContentsSpecifier):
766
+ """
767
+ The source of code for use in an input file generator.
768
+
769
+ Parameters
770
+ ----------
771
+ generator:
772
+ How to generate the file.
773
+ path:
774
+ Path to the file.
775
+ contents:
776
+ Contents of the file. Only used when recreating this object.
777
+ extension:
778
+ File name extension.
779
+ """
780
+
582
781
  def __init__(
583
782
  self,
584
783
  generator: app.InputFileGenerator,
@@ -586,11 +785,27 @@ class InputFileGeneratorSource(_FileContentsSpecifier):
586
785
  contents: str = None,
587
786
  extension: str = "",
588
787
  ):
788
+ #: How to generate the file.
589
789
  self.generator = generator
590
790
  super().__init__(path, contents, extension)
591
791
 
592
792
 
593
793
  class OutputFileParserSource(_FileContentsSpecifier):
794
+ """
795
+ The source of code for use in an output file parser.
796
+
797
+ Parameters
798
+ ----------
799
+ parser:
800
+ How to parse the file.
801
+ path: Path
802
+ Path to the file.
803
+ contents:
804
+ Contents of the file. Only used when recreating this object.
805
+ extension:
806
+ File name extension.
807
+ """
808
+
594
809
  def __init__(
595
810
  self,
596
811
  parser: app.OutputFileParser,
@@ -598,5 +813,6 @@ class OutputFileParserSource(_FileContentsSpecifier):
598
813
  contents: str = None,
599
814
  extension: str = "",
600
815
  ):
816
+ #: How to parse the file.
601
817
  self.parser = parser
602
818
  super().__init__(path, contents, extension)
@@ -1,3 +1,7 @@
1
+ """
2
+ Model of a command run in an action.
3
+ """
4
+
1
5
  from dataclasses import dataclass, field
2
6
  from functools import partial
3
7
  from pathlib import Path
@@ -15,6 +19,32 @@ from hpcflow.sdk.core.parameters import ParameterValue
15
19
 
16
20
  @dataclass
17
21
  class Command(JSONLike):
22
+ """
23
+ A command that may be run within a workflow action.
24
+
25
+ Parameters
26
+ ----------
27
+ command: str
28
+ The actual command.
29
+ executable: str
30
+ The executable to run,
31
+ from the set of executable managed by the environment.
32
+ arguments: list[str]
33
+ The arguments to pass in.
34
+ variables: dict[str, str]
35
+ Values that may be substituted when preparing the arguments.
36
+ stdout: str
37
+ The name of a file to write standard output to.
38
+ stderr: str
39
+ The name of a file to write standard error to.
40
+ stdin: str
41
+ The name of a file to read standard input from.
42
+ rules: list[~hpcflow.app.ActionRule]
43
+ Rules that state whether this command is eligible to run.
44
+ """
45
+
46
+ # TODO: What is the difference between command and executable?
47
+
18
48
  _app_attr = "app"
19
49
  _child_objects = (
20
50
  ChildObjectSpec(
@@ -25,13 +55,23 @@ class Command(JSONLike):
25
55
  ),
26
56
  )
27
57
 
58
+ #: The actual command.
59
+ #: Overrides :py:attr:`executable`.
28
60
  command: Optional[str] = None
61
+ #: The executable to run,
62
+ #: from the set of executable managed by the environment.
29
63
  executable: Optional[str] = None
64
+ #: The arguments to pass in.
30
65
  arguments: Optional[List[str]] = None
66
+ #: Values that may be substituted when preparing the arguments.
31
67
  variables: Optional[Dict[str, str]] = None
68
+ #: The name of a file to write standard output to.
32
69
  stdout: Optional[str] = None
70
+ #: The name of a file to write standard error to.
33
71
  stderr: Optional[str] = None
72
+ #: The name of a file to read standard input from.
34
73
  stdin: Optional[str] = None
74
+ #: Rules that state whether this command is eligible to run.
35
75
  rules: Optional[List[app.ActionRule]] = field(default_factory=lambda: [])
36
76
 
37
77
  def __repr__(self) -> str:
@@ -207,6 +247,9 @@ class Command(JSONLike):
207
247
  return cmd_str, shell_vars
208
248
 
209
249
  def get_output_types(self):
250
+ """
251
+ Get whether stdout and stderr are workflow parameters.
252
+ """
210
253
  # note: we use "parameter" rather than "output", because it could be a schema
211
254
  # output or schema input.
212
255
  pattern = (
@@ -253,6 +296,20 @@ class Command(JSONLike):
253
296
  return kwargs
254
297
 
255
298
  def process_std_stream(self, name: str, value: str, stderr: bool):
299
+ """
300
+ Process a description of a standard stread from a command to get how it becomes
301
+ a workflow parameter for later actions.
302
+
303
+ Parameters
304
+ ---------
305
+ name:
306
+ The name of the output, describing how to process things.
307
+ value:
308
+ The actual value read from the stream.
309
+ stderr:
310
+ If true, this is handling the stderr stream. If false, the stdout stream.
311
+ """
312
+
256
313
  def _parse_list(lst_str: str, item_type: str = "str", delim: str = " "):
257
314
  return [parse_types[item_type](i) for i in lst_str.split(delim)]
258
315