hpcflow-new2 0.2.0a188__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.0a188.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.0a188.dist-info/RECORD +0 -158
- {hpcflow_new2-0.2.0a188.dist-info → hpcflow_new2-0.2.0a190.dist-info}/LICENSE +0 -0
- {hpcflow_new2-0.2.0a188.dist-info → hpcflow_new2-0.2.0a190.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a188.dist-info → hpcflow_new2-0.2.0a190.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/core/task_schema.py
CHANGED
@@ -2,29 +2,43 @@
|
|
2
2
|
Abstract task, prior to instantiation.
|
3
3
|
"""
|
4
4
|
|
5
|
+
from __future__ import annotations
|
5
6
|
from contextlib import contextmanager
|
6
7
|
import copy
|
7
8
|
from dataclasses import dataclass
|
8
9
|
from importlib import import_module
|
9
|
-
from
|
10
|
+
from itertools import chain
|
11
|
+
from typing import TYPE_CHECKING
|
10
12
|
from html import escape
|
11
13
|
|
12
14
|
from rich import print as rich_print
|
13
|
-
from rich.console import Console
|
14
15
|
from rich.table import Table
|
15
16
|
from rich.panel import Panel
|
16
17
|
from rich.markup import escape as rich_esc
|
17
18
|
from rich.text import Text
|
18
19
|
|
19
|
-
from hpcflow.sdk import
|
20
|
+
from hpcflow.sdk.typing import hydrate
|
21
|
+
from hpcflow.sdk.core.enums import ParameterPropagationMode
|
20
22
|
from hpcflow.sdk.core.errors import EnvironmentPresetUnknownEnvironmentError
|
23
|
+
from hpcflow.sdk.core.json_like import ChildObjectSpec, JSONLike
|
21
24
|
from hpcflow.sdk.core.parameters import Parameter
|
22
|
-
from .
|
23
|
-
|
24
|
-
|
25
|
+
from hpcflow.sdk.core.utils import check_valid_py_identifier
|
26
|
+
|
27
|
+
if TYPE_CHECKING:
|
28
|
+
from collections.abc import Iterable, Iterator, Mapping, Sequence
|
29
|
+
from typing import Any, ClassVar
|
30
|
+
from typing_extensions import Self, TypeIs
|
31
|
+
from .actions import Action
|
32
|
+
from .object_list import ParametersList, TaskSchemasList
|
33
|
+
from .parameters import InputValue, SchemaInput, SchemaOutput, SchemaParameter
|
34
|
+
from .task import TaskTemplate
|
35
|
+
from .types import ActParameterDependence
|
36
|
+
from .workflow import Workflow
|
37
|
+
from ..typing import ParamSource
|
25
38
|
|
26
39
|
|
27
40
|
@dataclass
|
41
|
+
@hydrate
|
28
42
|
class TaskObjective(JSONLike):
|
29
43
|
"""
|
30
44
|
A thing that a task is attempting to achieve.
|
@@ -35,7 +49,7 @@ class TaskObjective(JSONLike):
|
|
35
49
|
The name of the objective. A valid Python identifier.
|
36
50
|
"""
|
37
51
|
|
38
|
-
_child_objects = (
|
52
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
39
53
|
ChildObjectSpec(
|
40
54
|
name="name",
|
41
55
|
is_single_attribute=True,
|
@@ -48,6 +62,10 @@ class TaskObjective(JSONLike):
|
|
48
62
|
def __post_init__(self):
|
49
63
|
self.name = check_valid_py_identifier(self.name)
|
50
64
|
|
65
|
+
@classmethod
|
66
|
+
def _parse_from_string(cls, string):
|
67
|
+
return string
|
68
|
+
|
51
69
|
|
52
70
|
class TaskSchema(JSONLike):
|
53
71
|
"""Class to represent the inputs, outputs and implementation mechanism of a given
|
@@ -79,11 +97,11 @@ class TaskSchema(JSONLike):
|
|
79
97
|
cases in the concrete tasks.
|
80
98
|
"""
|
81
99
|
|
82
|
-
_validation_schema = "task_schema_spec_schema.yaml"
|
100
|
+
_validation_schema: ClassVar[str] = "task_schema_spec_schema.yaml"
|
83
101
|
_hash_value = None
|
84
102
|
_validate_actions = True
|
85
103
|
|
86
|
-
_child_objects = (
|
104
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
87
105
|
ChildObjectSpec(name="objective", class_name="TaskObjective"),
|
88
106
|
ChildObjectSpec(
|
89
107
|
name="inputs",
|
@@ -100,23 +118,35 @@ class TaskSchema(JSONLike):
|
|
100
118
|
),
|
101
119
|
)
|
102
120
|
|
121
|
+
@classmethod
|
122
|
+
def __is_InputValue(cls, value) -> TypeIs[InputValue]:
|
123
|
+
return isinstance(value, cls._app.InputValue)
|
124
|
+
|
125
|
+
@classmethod
|
126
|
+
def __is_Parameter(cls, value) -> TypeIs[Parameter]:
|
127
|
+
return isinstance(value, cls._app.Parameter)
|
128
|
+
|
129
|
+
@classmethod
|
130
|
+
def __is_SchemaOutput(cls, value) -> TypeIs[SchemaOutput]:
|
131
|
+
return isinstance(value, cls._app.SchemaOutput)
|
132
|
+
|
103
133
|
def __init__(
|
104
134
|
self,
|
105
|
-
objective:
|
106
|
-
actions:
|
107
|
-
method:
|
108
|
-
implementation:
|
109
|
-
inputs:
|
110
|
-
outputs:
|
111
|
-
version:
|
112
|
-
parameter_class_modules:
|
113
|
-
web_doc:
|
114
|
-
environment_presets:
|
135
|
+
objective: TaskObjective | str,
|
136
|
+
actions: list[Action] | None = None,
|
137
|
+
method: str | None = None,
|
138
|
+
implementation: str | None = None,
|
139
|
+
inputs: list[Parameter | SchemaInput] | None = None,
|
140
|
+
outputs: list[Parameter | SchemaParameter] | None = None,
|
141
|
+
version: str | None = None,
|
142
|
+
parameter_class_modules: list[str] | None = None,
|
143
|
+
web_doc: bool | None = True,
|
144
|
+
environment_presets: Mapping[str, Mapping[str, Mapping[str, Any]]] | None = None,
|
115
145
|
doc: str = "",
|
116
|
-
_hash_value:
|
146
|
+
_hash_value: str | None = None,
|
117
147
|
):
|
118
148
|
#: This is a string representing the objective of the task schema.
|
119
|
-
self.objective = objective
|
149
|
+
self.objective = self.__coerce_objective(objective)
|
120
150
|
#: A list of Action objects whose commands are to be executed by the task.
|
121
151
|
self.actions = actions or []
|
122
152
|
#: An optional string to label the task schema by its method.
|
@@ -124,9 +154,9 @@ class TaskSchema(JSONLike):
|
|
124
154
|
#: An optional string to label the task schema by its implementation.
|
125
155
|
self.implementation = implementation
|
126
156
|
#: A list of SchemaInput objects that define the inputs to the task.
|
127
|
-
self.inputs = inputs or
|
157
|
+
self.inputs = self.__coerce_inputs(inputs or ())
|
128
158
|
#: A list of SchemaOutput objects that define the outputs of the task.
|
129
|
-
self.outputs = outputs or
|
159
|
+
self.outputs = self.__coerce_outputs(outputs or ())
|
130
160
|
#: Where to find implementations of parameter value handlers.
|
131
161
|
self.parameter_class_modules = parameter_class_modules or []
|
132
162
|
#: Whether this object should be included in the Sphinx documentation
|
@@ -141,67 +171,76 @@ class TaskSchema(JSONLike):
|
|
141
171
|
self._set_parent_refs()
|
142
172
|
|
143
173
|
# process `Action` script_data_in/out formats:
|
144
|
-
for
|
145
|
-
|
174
|
+
for act in self.actions:
|
175
|
+
act.process_script_data_formats()
|
146
176
|
|
147
177
|
self._validate()
|
148
|
-
self.actions = self.
|
178
|
+
self.actions = self.__expand_actions()
|
149
179
|
#: The version of this task schema.
|
150
180
|
self.version = version
|
151
|
-
self._task_template = None # assigned by parent Task
|
181
|
+
self._task_template: TaskTemplate | None = None # assigned by parent Task
|
152
182
|
|
153
|
-
self.
|
183
|
+
self.__update_parameter_value_classes()
|
154
184
|
|
155
185
|
if self.environment_presets:
|
156
186
|
# validate against env names in actions:
|
157
187
|
env_names = {act.get_environment_name() for act in self.actions}
|
158
|
-
preset_envs = {
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
)
|
188
|
+
preset_envs = {
|
189
|
+
preset_name
|
190
|
+
for preset in self.environment_presets.values()
|
191
|
+
for preset_name in preset
|
192
|
+
}
|
193
|
+
if bad_envs := preset_envs - env_names:
|
194
|
+
raise EnvironmentPresetUnknownEnvironmentError(self.name, bad_envs)
|
166
195
|
|
167
196
|
# if version is not None: # TODO: this seems fragile
|
168
197
|
# self.assign_versions(
|
169
198
|
# version=version,
|
170
|
-
# app_data_obj_list=self.
|
199
|
+
# app_data_obj_list=self._app.task_schemas
|
171
200
|
# if app.is_data_files_loaded
|
172
201
|
# else [],
|
173
202
|
# )
|
174
203
|
|
175
|
-
def __repr__(self):
|
204
|
+
def __repr__(self) -> str:
|
176
205
|
return f"{self.__class__.__name__}({self.objective.name!r})"
|
177
206
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
try:
|
183
|
-
type_fmt = parameter._validation.to_tree()[0]["type_fmt"]
|
184
|
-
except Exception:
|
185
|
-
pass
|
186
|
-
elif parameter._value_class:
|
187
|
-
param_cls = parameter._value_class
|
188
|
-
cls_url = (
|
189
|
-
f"{self.app.docs_url}/reference/_autosummary/{param_cls.__module__}."
|
190
|
-
f"{param_cls.__name__}"
|
191
|
-
)
|
192
|
-
type_fmt = f"[link={cls_url}]{param_cls.__name__}[/link]"
|
193
|
-
return type_fmt
|
207
|
+
@classmethod
|
208
|
+
def __parameters(cls) -> ParametersList:
|
209
|
+
# Workaround for a dumb mypy bug
|
210
|
+
return cls._app.parameters
|
194
211
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
212
|
+
@classmethod
|
213
|
+
def __task_schemas(cls) -> TaskSchemasList:
|
214
|
+
# Workaround for a dumb mypy bug
|
215
|
+
return cls._app.task_schemas
|
216
|
+
|
217
|
+
def __get_param_type_str(self, param: Parameter) -> str:
|
218
|
+
type_fmt = "-"
|
219
|
+
if param._validation:
|
220
|
+
try:
|
221
|
+
type_fmt = param._validation.to_tree()[0]["type_fmt"]
|
222
|
+
except Exception:
|
223
|
+
pass
|
224
|
+
elif param._value_class:
|
225
|
+
param_cls = param._value_class
|
226
|
+
cls_url = (
|
227
|
+
f"{self._app.docs_url}/reference/_autosummary/{param_cls.__module__}."
|
228
|
+
f"{param_cls.__name__}"
|
229
|
+
)
|
230
|
+
type_fmt = f"[link={cls_url}]{param_cls.__name__}[/link]"
|
231
|
+
return type_fmt
|
232
|
+
|
233
|
+
def __format_parameter_type(self, param: Parameter) -> str:
|
234
|
+
param_typ_fmt = param.typ
|
235
|
+
if param.typ in self.__parameters().list_attrs():
|
236
|
+
param_url = (
|
237
|
+
f"{self._app.docs_url}/reference/template_components/"
|
238
|
+
f"parameters.html#{param.url_slug}"
|
239
|
+
)
|
240
|
+
param_typ_fmt = f"[link={param_url}]{param_typ_fmt}[/link]"
|
241
|
+
return param_typ_fmt
|
204
242
|
|
243
|
+
def __get_info(self, include: Sequence[str] = ()):
|
205
244
|
if not include:
|
206
245
|
include = ("inputs", "outputs", "actions")
|
207
246
|
|
@@ -209,9 +248,7 @@ class TaskSchema(JSONLike):
|
|
209
248
|
tab.add_column(justify="right")
|
210
249
|
tab.add_column()
|
211
250
|
|
212
|
-
|
213
|
-
|
214
|
-
tab_ins_outs = None
|
251
|
+
tab_ins_outs: Table | None = None
|
215
252
|
if "inputs" in include or "outputs" in include:
|
216
253
|
tab_ins_outs = Table(
|
217
254
|
show_header=False,
|
@@ -226,6 +263,7 @@ class TaskSchema(JSONLike):
|
|
226
263
|
tab_ins_outs.add_row()
|
227
264
|
|
228
265
|
if "inputs" in include:
|
266
|
+
assert tab_ins_outs
|
229
267
|
if self.inputs:
|
230
268
|
tab_ins_outs.add_row(
|
231
269
|
"",
|
@@ -236,19 +274,20 @@ class TaskSchema(JSONLike):
|
|
236
274
|
for inp_idx, inp in enumerate(self.inputs):
|
237
275
|
def_str = "-"
|
238
276
|
if not inp.multiple:
|
239
|
-
if inp.default_value
|
277
|
+
if self.__is_InputValue(inp.default_value):
|
240
278
|
if inp.default_value.value is None:
|
241
279
|
def_str = "None"
|
242
280
|
else:
|
243
281
|
def_str = f"{rich_esc(str(inp.default_value.value))!r}"
|
244
282
|
tab_ins_outs.add_row(
|
245
283
|
"" if inp_idx > 0 else "[bold]Inputs[/bold]",
|
246
|
-
|
247
|
-
|
284
|
+
self.__format_parameter_type(inp.parameter),
|
285
|
+
self.__get_param_type_str(inp.parameter),
|
248
286
|
def_str,
|
249
287
|
)
|
250
288
|
|
251
289
|
if "outputs" in include:
|
290
|
+
assert tab_ins_outs
|
252
291
|
if "inputs" in include:
|
253
292
|
tab_ins_outs.add_row() # for spacing
|
254
293
|
else:
|
@@ -261,8 +300,8 @@ class TaskSchema(JSONLike):
|
|
261
300
|
for out_idx, out in enumerate(self.outputs):
|
262
301
|
tab_ins_outs.add_row(
|
263
302
|
"" if out_idx > 0 else "[bold]Outputs[/bold]",
|
264
|
-
|
265
|
-
|
303
|
+
self.__format_parameter_type(out.parameter),
|
304
|
+
self.__get_param_type_str(out.parameter),
|
266
305
|
"",
|
267
306
|
)
|
268
307
|
|
@@ -308,7 +347,7 @@ class TaskSchema(JSONLike):
|
|
308
347
|
cmd_str = "cmd" if cmd.command else "exe"
|
309
348
|
tab_cmds_i.add_row(
|
310
349
|
f"[italic]{cmd_str}:[/italic]",
|
311
|
-
rich_esc(cmd.command or cmd.executable),
|
350
|
+
rich_esc(cmd.command or cmd.executable or ""),
|
312
351
|
)
|
313
352
|
if cmd.stdout:
|
314
353
|
tab_cmds_i.add_row(
|
@@ -329,36 +368,30 @@ class TaskSchema(JSONLike):
|
|
329
368
|
panel = Panel(tab, title=f"Task schema: {rich_esc(self.objective.name)!r}")
|
330
369
|
return panel
|
331
370
|
|
332
|
-
def
|
333
|
-
panel = self._get_info(include=include)
|
334
|
-
rich_print(panel)
|
335
|
-
|
336
|
-
@property
|
337
|
-
def basic_info(self):
|
371
|
+
def basic_info(self) -> None:
|
338
372
|
"""Show inputs and outputs, formatted in a table."""
|
339
|
-
|
373
|
+
rich_print(self.__get_info(include=("inputs", "outputs")))
|
340
374
|
|
341
|
-
|
342
|
-
def info(self):
|
375
|
+
def info(self) -> None:
|
343
376
|
"""Show inputs, outputs, and actions, formatted in a table."""
|
344
|
-
|
377
|
+
rich_print(self.__get_info(include=()))
|
345
378
|
|
346
379
|
def get_info_html(self) -> str:
|
347
380
|
"""
|
348
381
|
Describe the task schema as an HTML document.
|
349
382
|
"""
|
350
383
|
|
351
|
-
def _format_parameter_type(param):
|
384
|
+
def _format_parameter_type(param: Parameter) -> str:
|
352
385
|
param_typ_fmt = param.typ
|
353
386
|
if param.typ in param_types:
|
354
387
|
param_url = (
|
355
|
-
f"{self.
|
388
|
+
f"{self._app.docs_url}/reference/template_components/"
|
356
389
|
f"parameters.html#{param.url_slug}"
|
357
390
|
)
|
358
391
|
param_typ_fmt = f'<a href="{param_url}">{param_typ_fmt}</a>'
|
359
392
|
return param_typ_fmt
|
360
393
|
|
361
|
-
def _get_param_type_str(param) -> str:
|
394
|
+
def _get_param_type_str(param: Parameter) -> str:
|
362
395
|
type_fmt = "-"
|
363
396
|
if param._validation:
|
364
397
|
try:
|
@@ -368,13 +401,15 @@ class TaskSchema(JSONLike):
|
|
368
401
|
elif param._value_class:
|
369
402
|
param_cls = param._value_class
|
370
403
|
cls_url = (
|
371
|
-
f"{self.
|
404
|
+
f"{self._app.docs_url}/reference/_autosummary/{param_cls.__module__}."
|
372
405
|
f"{param_cls.__name__}"
|
373
406
|
)
|
374
407
|
type_fmt = f'<a href="{cls_url}">{param_cls.__name__}</a>'
|
375
408
|
return type_fmt
|
376
409
|
|
377
|
-
def _prepare_script_data_format_table(
|
410
|
+
def _prepare_script_data_format_table(
|
411
|
+
script_data_grouped: Mapping[str, Mapping[str, Mapping[str, str]]]
|
412
|
+
) -> str:
|
378
413
|
out = ""
|
379
414
|
rows = ""
|
380
415
|
for fmt, params in script_data_grouped.items():
|
@@ -388,14 +423,14 @@ class TaskSchema(JSONLike):
|
|
388
423
|
|
389
424
|
return out
|
390
425
|
|
391
|
-
param_types = self.
|
426
|
+
param_types = self.__parameters().list_attrs()
|
392
427
|
|
393
|
-
inputs_header_row =
|
428
|
+
inputs_header_row = "<tr><th>parameter</th><th>type</th><th>default</th></tr>"
|
394
429
|
input_rows = ""
|
395
430
|
for inp in self.inputs:
|
396
431
|
def_str = "-"
|
397
432
|
if not inp.multiple:
|
398
|
-
if inp.default_value
|
433
|
+
if self.__is_InputValue(inp.default_value):
|
399
434
|
if inp.default_value.value is None:
|
400
435
|
def_str = "None"
|
401
436
|
else:
|
@@ -418,11 +453,11 @@ class TaskSchema(JSONLike):
|
|
418
453
|
)
|
419
454
|
else:
|
420
455
|
inputs_table = (
|
421
|
-
|
422
|
-
|
456
|
+
'<span class="schema-note-no-inputs">This task schema has no input '
|
457
|
+
"parameters.</span>"
|
423
458
|
)
|
424
459
|
|
425
|
-
outputs_header_row =
|
460
|
+
outputs_header_row = "<tr><th>parameter</th><th>type</th></tr>"
|
426
461
|
output_rows = ""
|
427
462
|
for out in self.outputs:
|
428
463
|
param_str = _format_parameter_type(out.parameter)
|
@@ -437,8 +472,8 @@ class TaskSchema(JSONLike):
|
|
437
472
|
|
438
473
|
else:
|
439
474
|
outputs_table = (
|
440
|
-
|
441
|
-
|
475
|
+
'<span class="schema-note-no-outputs">This task schema has no output '
|
476
|
+
"parameters.</span>"
|
442
477
|
)
|
443
478
|
|
444
479
|
action_rows = ""
|
@@ -496,7 +531,7 @@ class TaskSchema(JSONLike):
|
|
496
531
|
num_inp_fg_rows = 0
|
497
532
|
if act.input_file_generators:
|
498
533
|
inp_fg = act.input_file_generators[0] # should be only one
|
499
|
-
inps = ", ".join(f"<code>{
|
534
|
+
inps = ", ".join(f"<code>{in_.typ}</code>" for in_ in inp_fg.inputs)
|
500
535
|
inp_fg_rows += (
|
501
536
|
f"<tr>"
|
502
537
|
f'<td class="action-header-cell">input file:</td>'
|
@@ -513,11 +548,13 @@ class TaskSchema(JSONLike):
|
|
513
548
|
num_out_fp_rows = 0
|
514
549
|
if act.output_file_parsers:
|
515
550
|
out_fp = act.output_file_parsers[0] # should be only one
|
516
|
-
files = ", ".join(
|
551
|
+
files = ", ".join(
|
552
|
+
f"<code>{of_.label}</code>" for of_ in out_fp.output_files
|
553
|
+
)
|
517
554
|
out_fp_rows += (
|
518
555
|
f"<tr>"
|
519
556
|
f'<td class="action-header-cell">output:</td>'
|
520
|
-
f"<td><code>{out_fp.output.typ}</code></td>"
|
557
|
+
f"<td><code>{out_fp.output.typ if out_fp.output else ''}</code></td>"
|
521
558
|
f"</tr>"
|
522
559
|
f"<tr>"
|
523
560
|
f'<td class="action-header-cell">output files:</td>'
|
@@ -534,7 +571,7 @@ class TaskSchema(JSONLike):
|
|
534
571
|
f'<td rowspan="{bool(cmd.stdout) + bool(cmd.stderr) + 1}">'
|
535
572
|
f'<span class="cmd-idx-numeral">{cmd_idx}</span></td>'
|
536
573
|
f'<td class="command-header-cell">{"cmd" if cmd.command else "exe"}:'
|
537
|
-
f"</td><td><code><pre>{escape(cmd.command or cmd.executable)}</pre>"
|
574
|
+
f"</td><td><code><pre>{escape(cmd.command or cmd.executable or '')}</pre>"
|
538
575
|
f"</code></td></tr>"
|
539
576
|
)
|
540
577
|
if cmd.stdout:
|
@@ -549,8 +586,8 @@ class TaskSchema(JSONLike):
|
|
549
586
|
)
|
550
587
|
if cmd_idx < len(act.commands) - 1:
|
551
588
|
cmd_j_tab_rows += (
|
552
|
-
|
553
|
-
|
589
|
+
'<tr><td colspan="3" class="commands-table-bottom-spacer-cell">'
|
590
|
+
"</td></tr>"
|
554
591
|
)
|
555
592
|
act_i_cmds_tab_rows += cmd_j_tab_rows
|
556
593
|
|
@@ -580,22 +617,22 @@ class TaskSchema(JSONLike):
|
|
580
617
|
if action_rows:
|
581
618
|
action_table = f'<table class="action-table hidden">{action_rows}</table>'
|
582
619
|
action_show_hide = (
|
583
|
-
|
584
|
-
|
585
|
-
|
620
|
+
'<span class="actions-show-hide-toggle">[<span class="action-show-text">'
|
621
|
+
'show ↓</span><span class="action-hide-text hidden">hide ↑</span>]'
|
622
|
+
"</span>"
|
586
623
|
)
|
587
624
|
act_heading_class = ' class="actions-heading"'
|
588
625
|
else:
|
589
626
|
action_table = (
|
590
|
-
|
591
|
-
|
627
|
+
'<span class="schema-note-no-actions">'
|
628
|
+
"This task schema has no actions.</span>"
|
592
629
|
)
|
593
630
|
action_show_hide = ""
|
594
631
|
act_heading_class = ""
|
595
632
|
description = (
|
596
633
|
f"<h3 class='task-desc'>Description</h3>{self.doc}" if self.doc else ""
|
597
634
|
)
|
598
|
-
|
635
|
+
return (
|
599
636
|
f"{description}"
|
600
637
|
f"<h3>Inputs</h3>{inputs_table}"
|
601
638
|
f"<h3>Outputs</h3>{outputs_table}"
|
@@ -603,12 +640,13 @@ class TaskSchema(JSONLike):
|
|
603
640
|
f"<h3{act_heading_class}>Actions{action_show_hide}</h3>"
|
604
641
|
f"{action_table}"
|
605
642
|
)
|
606
|
-
return out
|
607
643
|
|
608
|
-
def __eq__(self, other):
|
609
|
-
if
|
644
|
+
def __eq__(self, other: Any):
|
645
|
+
if id(self) == id(other):
|
646
|
+
return True
|
647
|
+
if not isinstance(other, self.__class__):
|
610
648
|
return False
|
611
|
-
|
649
|
+
return (
|
612
650
|
self.objective == other.objective
|
613
651
|
and self.actions == other.actions
|
614
652
|
and self.method == other.method
|
@@ -617,11 +655,9 @@ class TaskSchema(JSONLike):
|
|
617
655
|
and self.outputs == other.outputs
|
618
656
|
and self.version == other.version
|
619
657
|
and self._hash_value == other._hash_value
|
620
|
-
)
|
621
|
-
return True
|
622
|
-
return False
|
658
|
+
)
|
623
659
|
|
624
|
-
def __deepcopy__(self, memo):
|
660
|
+
def __deepcopy__(self, memo: dict[int, Any]) -> Self:
|
625
661
|
kwargs = self.to_dict()
|
626
662
|
obj = self.__class__(**copy.deepcopy(kwargs, memo))
|
627
663
|
obj._task_template = self._task_template
|
@@ -629,7 +665,7 @@ class TaskSchema(JSONLike):
|
|
629
665
|
|
630
666
|
@classmethod
|
631
667
|
@contextmanager
|
632
|
-
def ignore_invalid_actions(cls):
|
668
|
+
def ignore_invalid_actions(cls) -> Iterator[None]:
|
633
669
|
"""
|
634
670
|
A context manager within which invalid actions will be ignored.
|
635
671
|
"""
|
@@ -639,71 +675,82 @@ class TaskSchema(JSONLike):
|
|
639
675
|
finally:
|
640
676
|
cls._validate_actions = True
|
641
677
|
|
642
|
-
|
643
|
-
|
644
|
-
|
678
|
+
@classmethod
|
679
|
+
def __coerce_objective(cls, objective: TaskObjective | str) -> TaskObjective:
|
680
|
+
if isinstance(objective, str):
|
681
|
+
return cls._app.TaskObjective(objective)
|
682
|
+
else:
|
683
|
+
return objective
|
684
|
+
|
685
|
+
@classmethod
|
686
|
+
def __coerce_one_input(cls, inp: Parameter | SchemaInput) -> SchemaInput:
|
687
|
+
return cls._app.SchemaInput(inp) if cls.__is_Parameter(inp) else inp
|
688
|
+
|
689
|
+
@classmethod
|
690
|
+
def __coerce_inputs(
|
691
|
+
cls, inputs: Iterable[Parameter | SchemaInput]
|
692
|
+
) -> list[SchemaInput]:
|
693
|
+
"""coerce Parameters to SchemaInputs"""
|
694
|
+
return [cls.__coerce_one_input(inp) for inp in inputs]
|
645
695
|
|
696
|
+
@classmethod
|
697
|
+
def __coerce_one_output(cls, out: Parameter | SchemaParameter) -> SchemaOutput:
|
698
|
+
return (
|
699
|
+
out
|
700
|
+
if cls.__is_SchemaOutput(out)
|
701
|
+
else cls._app.SchemaOutput(out if cls.__is_Parameter(out) else out.parameter)
|
702
|
+
)
|
703
|
+
|
704
|
+
@classmethod
|
705
|
+
def __coerce_outputs(
|
706
|
+
cls, outputs: Iterable[Parameter | SchemaParameter]
|
707
|
+
) -> list[SchemaOutput]:
|
708
|
+
"""coerce Parameters to SchemaOutputs"""
|
709
|
+
return [cls.__coerce_one_output(out) for out in outputs]
|
710
|
+
|
711
|
+
def _validate(self) -> None:
|
646
712
|
if self.method:
|
647
713
|
self.method = check_valid_py_identifier(self.method)
|
648
714
|
if self.implementation:
|
649
715
|
self.implementation = check_valid_py_identifier(self.implementation)
|
650
716
|
|
651
|
-
# coerce Parameters to SchemaInputs
|
652
|
-
for idx, i in enumerate(self.inputs):
|
653
|
-
if isinstance(
|
654
|
-
i, Parameter
|
655
|
-
): # TODO: doc. that we should use the sdk class for type checking!
|
656
|
-
self.inputs[idx] = self.app.SchemaInput(i)
|
657
|
-
|
658
|
-
# coerce Parameters to SchemaOutputs
|
659
|
-
for idx, i in enumerate(self.outputs):
|
660
|
-
if isinstance(i, Parameter):
|
661
|
-
self.outputs[idx] = self.app.SchemaOutput(i)
|
662
|
-
elif isinstance(i, SchemaInput):
|
663
|
-
self.outputs[idx] = self.app.SchemaOutput(i.parameter)
|
664
|
-
|
665
717
|
# check action input/outputs
|
666
718
|
if self._validate_actions:
|
667
719
|
has_script = any(
|
668
|
-
|
669
|
-
|
720
|
+
act.script
|
721
|
+
and not act.input_file_generators
|
722
|
+
and not act.output_file_parsers
|
723
|
+
for act in self.actions
|
670
724
|
)
|
671
725
|
|
672
|
-
all_outs =
|
726
|
+
all_outs: set[str] = set()
|
673
727
|
extra_ins = set(self.input_types)
|
674
728
|
|
675
729
|
act_ins_lst = [act.get_input_types() for act in self.actions]
|
676
730
|
act_outs_lst = [act.get_output_types() for act in self.actions]
|
677
731
|
|
678
|
-
schema_ins = set(self.input_types)
|
679
732
|
schema_outs = set(self.output_types)
|
680
733
|
|
681
|
-
all_act_ins = set(
|
682
|
-
all_act_outs = set(
|
734
|
+
all_act_ins = set(chain.from_iterable(act_ins_lst))
|
735
|
+
all_act_outs = set(chain.from_iterable(act_outs_lst))
|
683
736
|
|
684
|
-
non_schema_act_ins = all_act_ins
|
685
|
-
non_schema_act_outs =
|
737
|
+
non_schema_act_ins = all_act_ins.difference(self.input_types)
|
738
|
+
non_schema_act_outs = all_act_outs.difference(schema_outs)
|
686
739
|
|
687
740
|
extra_act_outs = non_schema_act_outs
|
688
|
-
seen_act_outs =
|
741
|
+
seen_act_outs: set[str] = set()
|
689
742
|
for act_idx in range(len(self.actions)):
|
690
|
-
for act_in in [
|
691
|
-
|
692
|
-
]:
|
693
|
-
if act_in not in seen_act_outs:
|
743
|
+
for act_in in act_ins_lst[act_idx]:
|
744
|
+
if act_in in non_schema_act_ins and act_in not in seen_act_outs:
|
694
745
|
raise ValueError(
|
695
746
|
f"Action {act_idx} input {act_in!r} of schema {self.name!r} "
|
696
747
|
f"is not a schema input, but nor is it an action output from "
|
697
748
|
f"a preceding action."
|
698
749
|
)
|
699
|
-
seen_act_outs
|
700
|
-
|
701
|
-
]
|
702
|
-
|
703
|
-
act_inputs = set(act_ins_lst[act_idx])
|
704
|
-
act_outputs = set(act_outs_lst[act_idx])
|
705
|
-
extra_ins = extra_ins - act_inputs
|
706
|
-
all_outs.extend(list(act_outputs))
|
750
|
+
seen_act_outs.update(act_outs_lst[act_idx])
|
751
|
+
extra_act_outs.difference_update(act_ins_lst[act_idx])
|
752
|
+
extra_ins.difference_update(act_ins_lst[act_idx])
|
753
|
+
all_outs.update(act_outs_lst[act_idx])
|
707
754
|
|
708
755
|
if extra_act_outs:
|
709
756
|
raise ValueError(
|
@@ -717,9 +764,9 @@ class TaskSchema(JSONLike):
|
|
717
764
|
# i.e. are all schema inputs "consumed" by an action?
|
718
765
|
|
719
766
|
# consider OFP inputs:
|
720
|
-
for
|
721
|
-
for
|
722
|
-
extra_ins
|
767
|
+
for act in self.actions:
|
768
|
+
for ofp in act.output_file_parsers:
|
769
|
+
extra_ins.difference_update(ofp.inputs or ())
|
723
770
|
|
724
771
|
if self.actions and extra_ins:
|
725
772
|
# allow for no actions (e.g. defining inputs for downstream tasks)
|
@@ -728,7 +775,7 @@ class TaskSchema(JSONLike):
|
|
728
775
|
f"by any actions."
|
729
776
|
)
|
730
777
|
|
731
|
-
missing_outs =
|
778
|
+
missing_outs = schema_outs - all_outs
|
732
779
|
if missing_outs and not has_script:
|
733
780
|
# TODO: bit of a hack, need to consider script ins/outs later
|
734
781
|
raise ValueError(
|
@@ -736,12 +783,12 @@ class TaskSchema(JSONLike):
|
|
736
783
|
f"generated by any actions."
|
737
784
|
)
|
738
785
|
|
739
|
-
def
|
786
|
+
def __expand_actions(self) -> list[Action]:
|
740
787
|
"""Create new actions for input file generators and output parsers in existing
|
741
788
|
actions."""
|
742
|
-
return [
|
789
|
+
return [new_act for act in self.actions for new_act in act.expand()]
|
743
790
|
|
744
|
-
def
|
791
|
+
def __update_parameter_value_classes(self):
|
745
792
|
# ensure any referenced parameter_class_modules are imported:
|
746
793
|
for module in self.parameter_class_modules:
|
747
794
|
import_module(module)
|
@@ -755,12 +802,14 @@ class TaskSchema(JSONLike):
|
|
755
802
|
for out in self.outputs:
|
756
803
|
out.parameter._set_value_class()
|
757
804
|
|
758
|
-
def make_persistent(
|
805
|
+
def make_persistent(
|
806
|
+
self, workflow: Workflow, source: ParamSource
|
807
|
+
) -> list[int | list[int]]:
|
759
808
|
"""
|
760
809
|
Convert this task schema to persistent form within the context of the given
|
761
810
|
workflow.
|
762
811
|
"""
|
763
|
-
new_refs = []
|
812
|
+
new_refs: list[int | list[int]] = []
|
764
813
|
for input_i in self.inputs:
|
765
814
|
for lab_info in input_i.labelled_info():
|
766
815
|
if "default_value" in lab_info:
|
@@ -771,78 +820,77 @@ class TaskSchema(JSONLike):
|
|
771
820
|
return new_refs
|
772
821
|
|
773
822
|
@property
|
774
|
-
def name(self):
|
823
|
+
def name(self) -> str:
|
775
824
|
"""
|
776
825
|
The name of this schema.
|
777
826
|
"""
|
778
|
-
|
827
|
+
return (
|
779
828
|
f"{self.objective.name}"
|
780
829
|
f"{f'_{self.method}' if self.method else ''}"
|
781
830
|
f"{f'_{self.implementation}' if self.implementation else ''}"
|
782
831
|
)
|
783
|
-
return out
|
784
832
|
|
785
833
|
@property
|
786
|
-
def input_types(self):
|
834
|
+
def input_types(self) -> list[str]:
|
787
835
|
"""
|
788
836
|
The input types to the schema.
|
789
837
|
"""
|
790
|
-
return
|
838
|
+
return [typ for inp in self.inputs for typ in inp.all_labelled_types]
|
791
839
|
|
792
840
|
@property
|
793
|
-
def output_types(self):
|
841
|
+
def output_types(self) -> list[str]:
|
794
842
|
"""
|
795
843
|
The output types from the schema.
|
796
844
|
"""
|
797
|
-
return
|
845
|
+
return [out.typ for out in self.outputs]
|
798
846
|
|
799
847
|
@property
|
800
|
-
def provides_parameters(self) ->
|
848
|
+
def provides_parameters(self) -> Iterator[tuple[str, str]]:
|
801
849
|
"""
|
802
850
|
The parameters that this schema provides.
|
803
851
|
"""
|
804
|
-
out = []
|
805
852
|
for schema_inp in self.inputs:
|
806
|
-
for
|
807
|
-
prop_mode = labelled_info["propagation_mode"]
|
853
|
+
for label, prop_mode in schema_inp._simple_labelled_info:
|
808
854
|
if prop_mode is not ParameterPropagationMode.NEVER:
|
809
|
-
|
810
|
-
(schema_inp.input_or_output, labelled_info["labelled_type"])
|
811
|
-
)
|
855
|
+
yield (schema_inp.input_or_output, label)
|
812
856
|
for schema_out in self.outputs:
|
813
857
|
if schema_out.propagation_mode is not ParameterPropagationMode.NEVER:
|
814
|
-
|
815
|
-
return tuple(out)
|
858
|
+
yield (schema_out.input_or_output, schema_out.typ)
|
816
859
|
|
817
860
|
@property
|
818
|
-
def task_template(self):
|
861
|
+
def task_template(self) -> TaskTemplate | None:
|
819
862
|
"""
|
820
863
|
The template that this schema is contained in.
|
821
864
|
"""
|
822
865
|
return self._task_template
|
823
866
|
|
824
867
|
@classmethod
|
825
|
-
def get_by_key(cls, key):
|
868
|
+
def get_by_key(cls, key: str) -> TaskSchema:
|
826
869
|
"""Get a config-loaded task schema from a key."""
|
827
|
-
return cls.
|
870
|
+
return cls.__task_schemas().get(key)
|
828
871
|
|
829
|
-
def get_parameter_dependence(
|
872
|
+
def get_parameter_dependence(
|
873
|
+
self, parameter: SchemaParameter
|
874
|
+
) -> ActParameterDependence:
|
830
875
|
"""Find if/where a given parameter is used by the schema's actions."""
|
831
|
-
out = {"input_file_writers": [], "commands": []}
|
876
|
+
out: ActParameterDependence = {"input_file_writers": [], "commands": []}
|
832
877
|
for act_idx, action in enumerate(self.actions):
|
833
878
|
deps = action.get_parameter_dependence(parameter)
|
834
|
-
|
835
|
-
|
879
|
+
out["input_file_writers"].extend(
|
880
|
+
(act_idx, ifw) for ifw in deps["input_file_writers"]
|
881
|
+
)
|
882
|
+
out["commands"].extend((act_idx, cmd) for cmd in deps["commands"])
|
836
883
|
return out
|
837
884
|
|
838
|
-
def get_key(self):
|
885
|
+
def get_key(self) -> tuple:
|
839
886
|
"""
|
840
887
|
Get the hashable value that represents this schema.
|
841
888
|
"""
|
842
889
|
return (str(self.objective), self.method, self.implementation)
|
843
890
|
|
844
|
-
def _get_single_label_lookup(self, prefix="") ->
|
845
|
-
"""
|
891
|
+
def _get_single_label_lookup(self, prefix: str = "") -> Mapping[str, str]:
|
892
|
+
"""
|
893
|
+
Get a mapping between schema input types that have a single label (i.e.
|
846
894
|
labelled but with `multiple=False`) and the non-labelled type string.
|
847
895
|
|
848
896
|
For example, if a task schema has a schema input like:
|
@@ -854,7 +902,7 @@ class TaskSchema(JSONLike):
|
|
854
902
|
`{"inputs.p1[one]": "inputs.p1"}`.
|
855
903
|
|
856
904
|
"""
|
857
|
-
lookup = {}
|
905
|
+
lookup: dict[str, str] = {}
|
858
906
|
if prefix and not prefix.endswith("."):
|
859
907
|
prefix += "."
|
860
908
|
for sch_inp in self.inputs:
|
@@ -864,10 +912,6 @@ class TaskSchema(JSONLike):
|
|
864
912
|
return lookup
|
865
913
|
|
866
914
|
@property
|
867
|
-
def multi_input_types(self) ->
|
915
|
+
def multi_input_types(self) -> list[str]:
|
868
916
|
"""Get a list of input types that have multiple labels."""
|
869
|
-
|
870
|
-
for inp in self.inputs:
|
871
|
-
if inp.multiple:
|
872
|
-
out.append(inp.parameter.typ)
|
873
|
-
return out
|
917
|
+
return [inp.parameter.typ for inp in self.inputs if inp.multiple]
|