hpcflow-new2 0.2.0a189__py3-none-any.whl → 0.2.0a190__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.
- hpcflow/__pyinstaller/hook-hpcflow.py +8 -6
- hpcflow/_version.py +1 -1
- hpcflow/app.py +1 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +1 -1
- hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +1 -1
- hpcflow/sdk/__init__.py +21 -15
- hpcflow/sdk/app.py +2133 -770
- hpcflow/sdk/cli.py +281 -250
- hpcflow/sdk/cli_common.py +6 -2
- hpcflow/sdk/config/__init__.py +1 -1
- hpcflow/sdk/config/callbacks.py +77 -42
- hpcflow/sdk/config/cli.py +126 -103
- hpcflow/sdk/config/config.py +578 -311
- hpcflow/sdk/config/config_file.py +131 -95
- hpcflow/sdk/config/errors.py +112 -85
- hpcflow/sdk/config/types.py +145 -0
- hpcflow/sdk/core/actions.py +1054 -994
- hpcflow/sdk/core/app_aware.py +24 -0
- hpcflow/sdk/core/cache.py +81 -63
- hpcflow/sdk/core/command_files.py +275 -185
- hpcflow/sdk/core/commands.py +111 -107
- hpcflow/sdk/core/element.py +724 -503
- hpcflow/sdk/core/enums.py +192 -0
- hpcflow/sdk/core/environment.py +74 -93
- hpcflow/sdk/core/errors.py +398 -51
- hpcflow/sdk/core/json_like.py +540 -272
- hpcflow/sdk/core/loop.py +380 -334
- hpcflow/sdk/core/loop_cache.py +160 -43
- hpcflow/sdk/core/object_list.py +370 -207
- hpcflow/sdk/core/parameters.py +728 -600
- hpcflow/sdk/core/rule.py +59 -41
- hpcflow/sdk/core/run_dir_files.py +33 -22
- hpcflow/sdk/core/task.py +1546 -1325
- hpcflow/sdk/core/task_schema.py +240 -196
- hpcflow/sdk/core/test_utils.py +126 -88
- hpcflow/sdk/core/types.py +387 -0
- hpcflow/sdk/core/utils.py +410 -305
- hpcflow/sdk/core/validation.py +82 -9
- hpcflow/sdk/core/workflow.py +1192 -1028
- hpcflow/sdk/core/zarr_io.py +98 -137
- hpcflow/sdk/demo/cli.py +46 -33
- hpcflow/sdk/helper/cli.py +18 -16
- hpcflow/sdk/helper/helper.py +75 -63
- hpcflow/sdk/helper/watcher.py +61 -28
- hpcflow/sdk/log.py +83 -59
- hpcflow/sdk/persistence/__init__.py +8 -31
- hpcflow/sdk/persistence/base.py +988 -586
- hpcflow/sdk/persistence/defaults.py +6 -0
- hpcflow/sdk/persistence/discovery.py +38 -0
- hpcflow/sdk/persistence/json.py +408 -153
- hpcflow/sdk/persistence/pending.py +158 -123
- hpcflow/sdk/persistence/store_resource.py +37 -22
- hpcflow/sdk/persistence/types.py +307 -0
- hpcflow/sdk/persistence/utils.py +14 -11
- hpcflow/sdk/persistence/zarr.py +477 -420
- hpcflow/sdk/runtime.py +44 -41
- hpcflow/sdk/submission/{jobscript_info.py → enums.py} +39 -12
- hpcflow/sdk/submission/jobscript.py +444 -404
- hpcflow/sdk/submission/schedulers/__init__.py +133 -40
- hpcflow/sdk/submission/schedulers/direct.py +97 -71
- hpcflow/sdk/submission/schedulers/sge.py +132 -126
- hpcflow/sdk/submission/schedulers/slurm.py +263 -268
- hpcflow/sdk/submission/schedulers/utils.py +7 -2
- hpcflow/sdk/submission/shells/__init__.py +14 -15
- hpcflow/sdk/submission/shells/base.py +102 -29
- hpcflow/sdk/submission/shells/bash.py +72 -55
- hpcflow/sdk/submission/shells/os_version.py +31 -30
- hpcflow/sdk/submission/shells/powershell.py +37 -29
- hpcflow/sdk/submission/submission.py +203 -257
- hpcflow/sdk/submission/types.py +143 -0
- hpcflow/sdk/typing.py +163 -12
- hpcflow/tests/conftest.py +8 -6
- hpcflow/tests/schedulers/slurm/test_slurm_submission.py +5 -2
- hpcflow/tests/scripts/test_main_scripts.py +60 -30
- hpcflow/tests/shells/wsl/test_wsl_submission.py +6 -4
- hpcflow/tests/unit/test_action.py +86 -75
- hpcflow/tests/unit/test_action_rule.py +9 -4
- hpcflow/tests/unit/test_app.py +13 -6
- hpcflow/tests/unit/test_cli.py +1 -1
- hpcflow/tests/unit/test_command.py +71 -54
- hpcflow/tests/unit/test_config.py +20 -15
- hpcflow/tests/unit/test_config_file.py +21 -18
- hpcflow/tests/unit/test_element.py +58 -62
- hpcflow/tests/unit/test_element_iteration.py +3 -1
- hpcflow/tests/unit/test_element_set.py +29 -19
- hpcflow/tests/unit/test_group.py +4 -2
- hpcflow/tests/unit/test_input_source.py +116 -93
- hpcflow/tests/unit/test_input_value.py +29 -24
- hpcflow/tests/unit/test_json_like.py +44 -35
- hpcflow/tests/unit/test_loop.py +65 -58
- hpcflow/tests/unit/test_object_list.py +17 -12
- hpcflow/tests/unit/test_parameter.py +16 -7
- hpcflow/tests/unit/test_persistence.py +48 -35
- hpcflow/tests/unit/test_resources.py +20 -18
- hpcflow/tests/unit/test_run.py +8 -3
- hpcflow/tests/unit/test_runtime.py +2 -1
- hpcflow/tests/unit/test_schema_input.py +23 -15
- hpcflow/tests/unit/test_shell.py +3 -2
- hpcflow/tests/unit/test_slurm.py +8 -7
- hpcflow/tests/unit/test_submission.py +39 -19
- hpcflow/tests/unit/test_task.py +352 -247
- hpcflow/tests/unit/test_task_schema.py +33 -20
- hpcflow/tests/unit/test_utils.py +9 -11
- hpcflow/tests/unit/test_value_sequence.py +15 -12
- hpcflow/tests/unit/test_workflow.py +114 -83
- hpcflow/tests/unit/test_workflow_template.py +0 -1
- hpcflow/tests/workflows/test_jobscript.py +2 -1
- hpcflow/tests/workflows/test_workflows.py +18 -13
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/METADATA +2 -1
- hpcflow_new2-0.2.0a190.dist-info/RECORD +165 -0
- hpcflow/sdk/core/parallel.py +0 -21
- hpcflow_new2-0.2.0a189.dist-info/RECORD +0 -158
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/LICENSE +0 -0
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/core/commands.py
CHANGED
@@ -2,22 +2,32 @@
|
|
2
2
|
Model of a command run in an action.
|
3
3
|
"""
|
4
4
|
|
5
|
+
from __future__ import annotations
|
5
6
|
from dataclasses import dataclass, field
|
6
7
|
from functools import partial
|
7
8
|
from pathlib import Path
|
8
9
|
import re
|
9
|
-
from typing import
|
10
|
+
from typing import Any, ClassVar, TYPE_CHECKING
|
10
11
|
|
11
12
|
import numpy as np
|
12
13
|
|
13
|
-
from hpcflow.sdk import
|
14
|
+
from hpcflow.sdk.typing import hydrate
|
14
15
|
from hpcflow.sdk.core.element import ElementResources
|
15
16
|
from hpcflow.sdk.core.errors import NoCLIFormatMethodError
|
16
17
|
from hpcflow.sdk.core.json_like import ChildObjectSpec, JSONLike
|
17
18
|
from hpcflow.sdk.core.parameters import ParameterValue
|
18
19
|
|
20
|
+
if TYPE_CHECKING:
|
21
|
+
from collections.abc import Callable, Iterable, Mapping, Sequence
|
22
|
+
from re import Pattern
|
23
|
+
from .actions import ActionRule
|
24
|
+
from .element import ElementActionRun
|
25
|
+
from .environment import Environment
|
26
|
+
from ..submission.shells import Shell
|
27
|
+
|
19
28
|
|
20
29
|
@dataclass
|
30
|
+
@hydrate
|
21
31
|
class Command(JSONLike):
|
22
32
|
"""
|
23
33
|
A command that may be run within a workflow action.
|
@@ -43,10 +53,7 @@ class Command(JSONLike):
|
|
43
53
|
Rules that state whether this command is eligible to run.
|
44
54
|
"""
|
45
55
|
|
46
|
-
|
47
|
-
|
48
|
-
_app_attr = "app"
|
49
|
-
_child_objects = (
|
56
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
50
57
|
ChildObjectSpec(
|
51
58
|
name="rules",
|
52
59
|
class_name="ActionRule",
|
@@ -57,22 +64,22 @@ class Command(JSONLike):
|
|
57
64
|
|
58
65
|
#: The actual command.
|
59
66
|
#: Overrides :py:attr:`executable`.
|
60
|
-
command:
|
67
|
+
command: str | None = None
|
61
68
|
#: The executable to run,
|
62
69
|
#: from the set of executable managed by the environment.
|
63
|
-
executable:
|
70
|
+
executable: str | None = None
|
64
71
|
#: The arguments to pass in.
|
65
|
-
arguments:
|
72
|
+
arguments: list[str] | None = None
|
66
73
|
#: Values that may be substituted when preparing the arguments.
|
67
|
-
variables:
|
74
|
+
variables: dict[str, str] | None = None
|
68
75
|
#: The name of a file to write standard output to.
|
69
|
-
stdout:
|
76
|
+
stdout: str | None = None
|
70
77
|
#: The name of a file to write standard error to.
|
71
|
-
stderr:
|
78
|
+
stderr: str | None = None
|
72
79
|
#: The name of a file to read standard input from.
|
73
|
-
stdin:
|
80
|
+
stdin: str | None = None
|
74
81
|
#: Rules that state whether this command is eligible to run.
|
75
|
-
rules:
|
82
|
+
rules: list[ActionRule] = field(default_factory=list)
|
76
83
|
|
77
84
|
def __repr__(self) -> str:
|
78
85
|
out = []
|
@@ -95,61 +102,63 @@ class Command(JSONLike):
|
|
95
102
|
|
96
103
|
return f"{self.__class__.__name__}({', '.join(out)})"
|
97
104
|
|
98
|
-
def
|
105
|
+
def __get_initial_command_line(self) -> str:
|
99
106
|
if self.command:
|
100
107
|
return self.command
|
101
108
|
else:
|
102
109
|
return self.executable or ""
|
103
110
|
|
104
|
-
|
111
|
+
__EXE_SCRIPT_RE: ClassVar[Pattern] = re.compile(r"\<\<(executable|script):(.*?)\>\>")
|
112
|
+
__ENV_SPEC_RE: ClassVar[Pattern] = re.compile(r"\<\<env:(.*?)\>\>")
|
113
|
+
|
114
|
+
def get_command_line(
|
115
|
+
self, EAR: ElementActionRun, shell: Shell, env: Environment
|
116
|
+
) -> tuple[str, list[tuple[str, ...]]]:
|
105
117
|
"""Return the resolved command line.
|
106
118
|
|
107
119
|
This is ordinarily called at run-time by `Workflow.write_commands`.
|
108
|
-
|
109
120
|
"""
|
110
121
|
|
111
|
-
self.
|
112
|
-
cmd_str = self.
|
122
|
+
self._app.persistence_logger.debug("Command.get_command_line")
|
123
|
+
cmd_str = self.__get_initial_command_line()
|
113
124
|
|
114
125
|
def _format_sum(iterable: Iterable) -> str:
|
115
126
|
return str(sum(iterable))
|
116
127
|
|
117
128
|
def _join(iterable: Iterable, delim: str) -> str:
|
118
|
-
return delim.join(str
|
129
|
+
return delim.join(map(str, iterable))
|
119
130
|
|
120
|
-
parse_types = {
|
131
|
+
parse_types: dict[str, Callable[..., str]] = {
|
121
132
|
"sum": _format_sum,
|
122
133
|
"join": _join,
|
123
134
|
}
|
124
135
|
|
125
|
-
def exec_script_repl(match_obj):
|
136
|
+
def exec_script_repl(match_obj: re.Match[str]) -> str:
|
126
137
|
typ, val = match_obj.groups()
|
127
138
|
if typ == "executable":
|
128
139
|
executable = env.executables.get(val)
|
129
140
|
filterable = ElementResources.get_env_instance_filterable_attributes()
|
130
|
-
filter_exec = {
|
141
|
+
filter_exec = {attr: EAR.get_resources().get(attr) for attr in filterable}
|
131
142
|
exec_cmd = executable.filter_instances(**filter_exec)[0].command
|
132
|
-
|
143
|
+
return exec_cmd.replace("<<num_cores>>", str(EAR.resources.num_cores))
|
133
144
|
elif typ == "script":
|
134
|
-
|
135
|
-
|
145
|
+
return EAR.action.get_script_name(val)
|
146
|
+
else:
|
147
|
+
raise ValueError("impossible match occurred")
|
136
148
|
|
137
|
-
def input_param_repl(match_obj, inp_val):
|
149
|
+
def input_param_repl(match_obj: re.Match[str], inp_val) -> str:
|
138
150
|
_, func, func_kwargs, method, method_kwargs = match_obj.groups()
|
139
151
|
|
140
152
|
if isinstance(inp_val, ParameterValue):
|
141
153
|
if not method:
|
142
154
|
method = "CLI_format"
|
143
155
|
if not hasattr(inp_val, method):
|
144
|
-
raise NoCLIFormatMethodError(
|
145
|
-
|
146
|
-
f"object {inp_val!r}."
|
147
|
-
)
|
148
|
-
kwargs = self._prepare_kwargs_from_string(args_str=method_kwargs)
|
156
|
+
raise NoCLIFormatMethodError(method, inp_val)
|
157
|
+
kwargs = self.__prepare_kwargs_from_string(args_str=method_kwargs)
|
149
158
|
inp_val = getattr(inp_val, method)(**kwargs)
|
150
159
|
|
151
160
|
if func:
|
152
|
-
kwargs = self.
|
161
|
+
kwargs = self.__prepare_kwargs_from_string(
|
153
162
|
args_str=func_kwargs,
|
154
163
|
doubled_quoted_args=["delim"],
|
155
164
|
)
|
@@ -158,12 +167,9 @@ class Command(JSONLike):
|
|
158
167
|
return str(inp_val)
|
159
168
|
|
160
169
|
file_regex = r"(\<\<file:{}\>\>?)"
|
161
|
-
exe_script_regex = r"\<\<(executable|script):(.*?)\>\>"
|
162
|
-
env_specs_regex = r"\<\<env:(.*?)\>\>"
|
163
170
|
|
164
171
|
# substitute executables:
|
165
|
-
cmd_str =
|
166
|
-
pattern=exe_script_regex,
|
172
|
+
cmd_str = self.__EXE_SCRIPT_RE.sub(
|
167
173
|
repl=exec_script_repl,
|
168
174
|
string=cmd_str,
|
169
175
|
)
|
@@ -172,14 +178,13 @@ class Command(JSONLike):
|
|
172
178
|
# an `<<args>>` variable::
|
173
179
|
for var_key, var_val in (self.variables or {}).items():
|
174
180
|
# substitute any `<<env:>>` specifiers
|
175
|
-
var_val =
|
176
|
-
|
177
|
-
repl=lambda match_obj: EAR.env_spec[match_obj.group(1)],
|
181
|
+
var_val = self.__ENV_SPEC_RE.sub(
|
182
|
+
repl=lambda match_obj: EAR.env_spec[match_obj[1]],
|
178
183
|
string=var_val,
|
179
184
|
)
|
180
185
|
cmd_str = cmd_str.replace(f"<<{var_key}>>", var_val)
|
181
186
|
if "<<args>>" in cmd_str:
|
182
|
-
args_str = " ".join(self.arguments or
|
187
|
+
args_str = " ".join(self.arguments or ())
|
183
188
|
ends_in_args = cmd_str.endswith("<<args>>")
|
184
189
|
cmd_str = cmd_str.replace("<<args>>", args_str)
|
185
190
|
if ends_in_args and not args_str:
|
@@ -226,7 +231,7 @@ class Command(JSONLike):
|
|
226
231
|
string=cmd_str,
|
227
232
|
)
|
228
233
|
|
229
|
-
shell_vars = []
|
234
|
+
shell_vars: list[tuple[str, ...]] = []
|
230
235
|
out_types = self.get_output_types()
|
231
236
|
if out_types["stdout"]:
|
232
237
|
# TODO: also map stderr/both if possible
|
@@ -246,56 +251,53 @@ class Command(JSONLike):
|
|
246
251
|
|
247
252
|
return cmd_str, shell_vars
|
248
253
|
|
249
|
-
|
254
|
+
# note: we use "parameter" rather than "output", because it could be a schema
|
255
|
+
# output or schema input.
|
256
|
+
__PARAM_RE: ClassVar[Pattern] = re.compile(
|
257
|
+
r"(?:\<\<(?:\w+(?:\[(?:.*)\])?\()?parameter:(\w+)"
|
258
|
+
r"(?:\.(?:\w+)\((?:.*?)\))?\)?\>\>?)"
|
259
|
+
)
|
260
|
+
|
261
|
+
def get_output_types(self) -> Mapping[str, str | None]:
|
250
262
|
"""
|
251
263
|
Get whether stdout and stderr are workflow parameters.
|
252
264
|
"""
|
253
|
-
|
254
|
-
# output or schema input.
|
255
|
-
pattern = (
|
256
|
-
r"(?:\<\<(?:\w+(?:\[(?:.*)\])?\()?parameter:(\w+)"
|
257
|
-
r"(?:\.(?:\w+)\((?:.*?)\))?\)?\>\>?)"
|
258
|
-
)
|
259
|
-
out = {"stdout": None, "stderr": None}
|
265
|
+
out: dict[str, str | None] = {"stdout": None, "stderr": None}
|
260
266
|
for i, label in zip((self.stdout, self.stderr), ("stdout", "stderr")):
|
261
|
-
if i:
|
262
|
-
|
263
|
-
if match:
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
)
|
271
|
-
out[label] = param_typ
|
267
|
+
if i and (match := self.__PARAM_RE.search(i)):
|
268
|
+
param_typ: str = match[1]
|
269
|
+
if match.span(0) != (0, len(i)):
|
270
|
+
raise ValueError(
|
271
|
+
f"If specified as a parameter, `{label}` must not include"
|
272
|
+
f" any characters other than the parameter "
|
273
|
+
f"specification, but this was given: {i!r}."
|
274
|
+
)
|
275
|
+
out[label] = param_typ
|
272
276
|
return out
|
273
277
|
|
274
278
|
@staticmethod
|
275
|
-
def
|
276
|
-
|
279
|
+
def __prepare_kwargs_from_string(
|
280
|
+
args_str: str | None, doubled_quoted_args: list[str] | None = None
|
281
|
+
) -> dict[str, str]:
|
277
282
|
if args_str is None:
|
278
|
-
return
|
283
|
+
return {}
|
279
284
|
|
285
|
+
kwargs: dict[str, str] = {}
|
280
286
|
# deal with specified double-quoted arguments first if it exists:
|
281
|
-
for quote_arg in doubled_quoted_args or
|
287
|
+
for quote_arg in doubled_quoted_args or ():
|
282
288
|
quote_pat = r'.*({quote_arg}="(.*)").*'.format(quote_arg=quote_arg)
|
283
|
-
match
|
284
|
-
if match:
|
289
|
+
if match := re.match(quote_pat, args_str):
|
285
290
|
quote_str, quote_contents = match.groups()
|
286
291
|
args_str = args_str.replace(quote_str, "")
|
287
292
|
kwargs[quote_arg] = quote_contents
|
288
293
|
|
289
|
-
args_str
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
name_i = i_split[0].strip()
|
294
|
-
value = i_split[1].strip()
|
295
|
-
kwargs[name_i] = value
|
294
|
+
if args_str := args_str.strip().strip(","):
|
295
|
+
for arg_part in args_str.split(","):
|
296
|
+
name_i, value_i = map(str.strip, arg_part.split("="))
|
297
|
+
kwargs[name_i] = value_i
|
296
298
|
return kwargs
|
297
299
|
|
298
|
-
def process_std_stream(self, name: str, value: str, stderr: bool):
|
300
|
+
def process_std_stream(self, name: str, value: str, stderr: bool) -> Any:
|
299
301
|
"""
|
300
302
|
Process a description of a standard stread from a command to get how it becomes
|
301
303
|
a workflow parameter for later actions.
|
@@ -310,15 +312,19 @@ class Command(JSONLike):
|
|
310
312
|
If true, this is handling the stderr stream. If false, the stdout stream.
|
311
313
|
"""
|
312
314
|
|
313
|
-
def _parse_list(
|
315
|
+
def _parse_list(
|
316
|
+
lst_str: str, item_type: str = "str", delim: str = " "
|
317
|
+
) -> list[Any]:
|
314
318
|
return [parse_types[item_type](i) for i in lst_str.split(delim)]
|
315
319
|
|
316
|
-
def _parse_array(
|
320
|
+
def _parse_array(
|
321
|
+
arr_str: str, item_type: str = "float", delim: str = " "
|
322
|
+
) -> np.ndarray[Any, np.dtype[Any]]:
|
317
323
|
return np.array(
|
318
324
|
_parse_list(lst_str=arr_str, item_type=item_type, delim=delim)
|
319
325
|
)
|
320
326
|
|
321
|
-
def _parse_bool(bool_str):
|
327
|
+
def _parse_bool(bool_str: str) -> bool:
|
322
328
|
bool_str = bool_str.lower()
|
323
329
|
if bool_str in ("true", "1"):
|
324
330
|
return True
|
@@ -331,7 +337,7 @@ class Command(JSONLike):
|
|
331
337
|
f"{self.stderr if stderr else self.stdout!r}."
|
332
338
|
)
|
333
339
|
|
334
|
-
parse_types = {
|
340
|
+
parse_types: dict[str, Callable[[str], Any]] = {
|
335
341
|
"str": str,
|
336
342
|
"int": int,
|
337
343
|
"float": float,
|
@@ -341,6 +347,7 @@ class Command(JSONLike):
|
|
341
347
|
}
|
342
348
|
types_pattern = "|".join(parse_types)
|
343
349
|
|
350
|
+
# TODO: use str.removeprefix in 3.9 onwards
|
344
351
|
out_name = name.replace("outputs.", "")
|
345
352
|
pattern = (
|
346
353
|
r"(\<\<(?:({types_pattern})(?:\[(.*)\])?\()?parameter:{name}(?:\.(\w+)"
|
@@ -348,42 +355,39 @@ class Command(JSONLike):
|
|
348
355
|
)
|
349
356
|
pattern = pattern.format(types_pattern=types_pattern, name=out_name)
|
350
357
|
spec = self.stderr if stderr else self.stdout
|
351
|
-
|
358
|
+
assert spec is not None
|
359
|
+
self._app.submission_logger.info(
|
352
360
|
f"processing shell standard stream according to spec: {spec!r}"
|
353
361
|
)
|
354
|
-
param = self.
|
355
|
-
match
|
356
|
-
try:
|
357
|
-
groups = match.groups()
|
358
|
-
except AttributeError:
|
362
|
+
param = self._app.Parameter(out_name)
|
363
|
+
if (match := re.match(pattern, spec)) is None:
|
359
364
|
return value
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
365
|
+
groups = match.groups()
|
366
|
+
parse_type, parse_args_str = groups[1:3]
|
367
|
+
parse_args = self.__prepare_kwargs_from_string(
|
368
|
+
args_str=parse_args_str,
|
369
|
+
doubled_quoted_args=["delim"],
|
370
|
+
)
|
371
|
+
if param._value_class:
|
372
|
+
method, method_args_str = groups[3:5]
|
373
|
+
method_args = self.__prepare_kwargs_from_string(
|
374
|
+
args_str=method_args_str,
|
364
375
|
doubled_quoted_args=["delim"],
|
365
376
|
)
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
doubled_quoted_args=["delim"],
|
371
|
-
)
|
372
|
-
method = method or "CLI_parse"
|
373
|
-
value = getattr(param._value_class, method)(value, **method_args)
|
374
|
-
if parse_type:
|
375
|
-
value = parse_types[parse_type](value, **parse_args)
|
377
|
+
method = method or "CLI_parse"
|
378
|
+
value = getattr(param._value_class, method)(value, **method_args)
|
379
|
+
if parse_type:
|
380
|
+
value = parse_types[parse_type](value, **parse_args)
|
376
381
|
|
377
382
|
return value
|
378
383
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
384
|
+
__EXE_RE: ClassVar[Pattern] = re.compile(r"\<\<(?:executable):(.*?)\>\>")
|
385
|
+
|
386
|
+
@classmethod
|
387
|
+
def _extract_executable_labels(cls, cmd_str: str) -> Sequence[str]:
|
388
|
+
return cls.__EXE_RE.findall(cmd_str)
|
383
389
|
|
384
|
-
def get_required_executables(self) ->
|
390
|
+
def get_required_executables(self) -> Sequence[str]:
|
385
391
|
"""Return executable labels required by this command."""
|
386
392
|
# an executable label might appear in the `command` or `executable` attribute:
|
387
|
-
|
388
|
-
exe_labels = self._extract_executable_labels(cmd_str)
|
389
|
-
return exe_labels
|
393
|
+
return self._extract_executable_labels(self.__get_initial_command_line())
|