hpcflow-new2 0.2.0a189__py3-none-any.whl → 0.2.0a199__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hpcflow/__pyinstaller/hook-hpcflow.py +9 -6
- hpcflow/_version.py +1 -1
- hpcflow/app.py +1 -0
- hpcflow/data/scripts/bad_script.py +2 -0
- hpcflow/data/scripts/do_nothing.py +2 -0
- hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
- hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/input_file_generator_basic.py +3 -0
- hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
- hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +1 -1
- hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +1 -1
- hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
- hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
- hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
- hpcflow/data/scripts/output_file_parser_basic.py +3 -0
- hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
- hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/script_exit_test.py +5 -0
- hpcflow/data/template_components/environments.yaml +1 -1
- hpcflow/sdk/__init__.py +26 -15
- hpcflow/sdk/app.py +2192 -768
- hpcflow/sdk/cli.py +506 -296
- hpcflow/sdk/cli_common.py +105 -7
- hpcflow/sdk/config/__init__.py +1 -1
- hpcflow/sdk/config/callbacks.py +115 -43
- hpcflow/sdk/config/cli.py +126 -103
- hpcflow/sdk/config/config.py +674 -318
- hpcflow/sdk/config/config_file.py +131 -95
- hpcflow/sdk/config/errors.py +125 -84
- hpcflow/sdk/config/types.py +148 -0
- hpcflow/sdk/core/__init__.py +25 -1
- hpcflow/sdk/core/actions.py +1771 -1059
- hpcflow/sdk/core/app_aware.py +24 -0
- hpcflow/sdk/core/cache.py +139 -79
- hpcflow/sdk/core/command_files.py +263 -287
- hpcflow/sdk/core/commands.py +145 -112
- hpcflow/sdk/core/element.py +828 -535
- hpcflow/sdk/core/enums.py +192 -0
- hpcflow/sdk/core/environment.py +74 -93
- hpcflow/sdk/core/errors.py +455 -52
- hpcflow/sdk/core/execute.py +207 -0
- hpcflow/sdk/core/json_like.py +540 -272
- hpcflow/sdk/core/loop.py +751 -347
- hpcflow/sdk/core/loop_cache.py +164 -47
- hpcflow/sdk/core/object_list.py +370 -207
- hpcflow/sdk/core/parameters.py +1100 -627
- hpcflow/sdk/core/rule.py +59 -41
- hpcflow/sdk/core/run_dir_files.py +21 -37
- hpcflow/sdk/core/skip_reason.py +7 -0
- hpcflow/sdk/core/task.py +1649 -1339
- hpcflow/sdk/core/task_schema.py +308 -196
- hpcflow/sdk/core/test_utils.py +191 -114
- hpcflow/sdk/core/types.py +440 -0
- hpcflow/sdk/core/utils.py +485 -309
- hpcflow/sdk/core/validation.py +82 -9
- hpcflow/sdk/core/workflow.py +2544 -1178
- hpcflow/sdk/core/zarr_io.py +98 -137
- hpcflow/sdk/data/workflow_spec_schema.yaml +2 -0
- hpcflow/sdk/demo/cli.py +53 -33
- hpcflow/sdk/helper/cli.py +18 -15
- hpcflow/sdk/helper/helper.py +75 -63
- hpcflow/sdk/helper/watcher.py +61 -28
- hpcflow/sdk/log.py +122 -71
- hpcflow/sdk/persistence/__init__.py +8 -31
- hpcflow/sdk/persistence/base.py +1360 -606
- hpcflow/sdk/persistence/defaults.py +6 -0
- hpcflow/sdk/persistence/discovery.py +38 -0
- hpcflow/sdk/persistence/json.py +568 -188
- hpcflow/sdk/persistence/pending.py +382 -179
- hpcflow/sdk/persistence/store_resource.py +39 -23
- hpcflow/sdk/persistence/types.py +318 -0
- hpcflow/sdk/persistence/utils.py +14 -11
- hpcflow/sdk/persistence/zarr.py +1337 -433
- hpcflow/sdk/runtime.py +44 -41
- hpcflow/sdk/submission/{jobscript_info.py → enums.py} +39 -12
- hpcflow/sdk/submission/jobscript.py +1651 -692
- hpcflow/sdk/submission/schedulers/__init__.py +167 -39
- hpcflow/sdk/submission/schedulers/direct.py +121 -81
- hpcflow/sdk/submission/schedulers/sge.py +170 -129
- hpcflow/sdk/submission/schedulers/slurm.py +291 -268
- hpcflow/sdk/submission/schedulers/utils.py +12 -2
- hpcflow/sdk/submission/shells/__init__.py +14 -15
- hpcflow/sdk/submission/shells/base.py +150 -29
- hpcflow/sdk/submission/shells/bash.py +283 -173
- hpcflow/sdk/submission/shells/os_version.py +31 -30
- hpcflow/sdk/submission/shells/powershell.py +228 -170
- hpcflow/sdk/submission/submission.py +1014 -335
- hpcflow/sdk/submission/types.py +140 -0
- hpcflow/sdk/typing.py +182 -12
- hpcflow/sdk/utils/arrays.py +71 -0
- hpcflow/sdk/utils/deferred_file.py +55 -0
- hpcflow/sdk/utils/hashing.py +16 -0
- hpcflow/sdk/utils/patches.py +12 -0
- hpcflow/sdk/utils/strings.py +33 -0
- hpcflow/tests/api/test_api.py +32 -0
- hpcflow/tests/conftest.py +27 -6
- hpcflow/tests/data/multi_path_sequences.yaml +29 -0
- hpcflow/tests/data/workflow_test_run_abort.yaml +34 -35
- hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
- hpcflow/tests/schedulers/slurm/test_slurm_submission.py +5 -2
- hpcflow/tests/scripts/test_input_file_generators.py +282 -0
- hpcflow/tests/scripts/test_main_scripts.py +866 -85
- hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
- hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
- hpcflow/tests/shells/wsl/test_wsl_submission.py +12 -4
- hpcflow/tests/unit/test_action.py +262 -75
- hpcflow/tests/unit/test_action_rule.py +9 -4
- hpcflow/tests/unit/test_app.py +33 -6
- hpcflow/tests/unit/test_cache.py +46 -0
- hpcflow/tests/unit/test_cli.py +134 -1
- hpcflow/tests/unit/test_command.py +71 -54
- hpcflow/tests/unit/test_config.py +142 -16
- hpcflow/tests/unit/test_config_file.py +21 -18
- hpcflow/tests/unit/test_element.py +58 -62
- hpcflow/tests/unit/test_element_iteration.py +50 -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_jobscript_unit.py +757 -0
- hpcflow/tests/unit/test_json_like.py +44 -35
- hpcflow/tests/unit/test_loop.py +1396 -84
- hpcflow/tests/unit/test_meta_task.py +325 -0
- hpcflow/tests/unit/test_multi_path_sequences.py +229 -0
- hpcflow/tests/unit/test_object_list.py +17 -12
- hpcflow/tests/unit/test_parameter.py +29 -7
- hpcflow/tests/unit/test_persistence.py +237 -42
- hpcflow/tests/unit/test_resources.py +20 -18
- hpcflow/tests/unit/test_run.py +117 -6
- hpcflow/tests/unit/test_run_directories.py +29 -0
- hpcflow/tests/unit/test_runtime.py +2 -1
- hpcflow/tests/unit/test_schema_input.py +23 -15
- hpcflow/tests/unit/test_shell.py +23 -2
- hpcflow/tests/unit/test_slurm.py +8 -7
- hpcflow/tests/unit/test_submission.py +38 -89
- 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/unit/utils/test_arrays.py +40 -0
- hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
- hpcflow/tests/unit/utils/test_hashing.py +65 -0
- hpcflow/tests/unit/utils/test_patches.py +5 -0
- hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
- hpcflow/tests/workflows/__init__.py +0 -0
- hpcflow/tests/workflows/test_directory_structure.py +31 -0
- hpcflow/tests/workflows/test_jobscript.py +334 -1
- hpcflow/tests/workflows/test_run_status.py +198 -0
- hpcflow/tests/workflows/test_skip_downstream.py +696 -0
- hpcflow/tests/workflows/test_submission.py +140 -0
- hpcflow/tests/workflows/test_workflows.py +160 -15
- hpcflow/tests/workflows/test_zip.py +18 -0
- hpcflow/viz_demo.ipynb +6587 -3
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/METADATA +8 -4
- hpcflow_new2-0.2.0a199.dist-info/RECORD +221 -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.0a199.dist-info}/LICENSE +0 -0
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/core/parameters.py
CHANGED
@@ -3,48 +3,77 @@ Parameters represent information passed around within a workflow.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
from __future__ import annotations
|
6
|
+
from collections.abc import Sequence
|
6
7
|
import copy
|
7
8
|
from dataclasses import dataclass, field
|
8
9
|
from datetime import timedelta
|
9
10
|
import enum
|
10
11
|
from pathlib import Path
|
11
12
|
import re
|
12
|
-
from typing import
|
13
|
+
from typing import TypeVar, cast, TYPE_CHECKING
|
14
|
+
from typing_extensions import override, TypeIs
|
13
15
|
|
14
16
|
import numpy as np
|
15
|
-
import
|
16
|
-
|
17
|
-
|
18
|
-
from hpcflow.sdk.
|
17
|
+
from scipy.stats.qmc import LatinHypercube
|
18
|
+
from valida import Schema as ValidaSchema # type: ignore
|
19
|
+
|
20
|
+
from hpcflow.sdk.typing import hydrate
|
21
|
+
from hpcflow.sdk.core.enums import (
|
22
|
+
InputSourceType,
|
23
|
+
ParallelMode,
|
24
|
+
ParameterPropagationMode,
|
25
|
+
TaskSourceType,
|
26
|
+
)
|
19
27
|
from hpcflow.sdk.core.errors import (
|
20
28
|
MalformedParameterPathError,
|
21
29
|
UnknownResourceSpecItemError,
|
22
30
|
WorkflowParameterMissingError,
|
23
31
|
)
|
24
32
|
from hpcflow.sdk.core.json_like import ChildObjectSpec, JSONLike
|
25
|
-
from hpcflow.sdk.core.parallel import ParallelMode
|
26
|
-
from hpcflow.sdk.core.rule import Rule
|
27
33
|
from hpcflow.sdk.core.utils import (
|
28
34
|
check_valid_py_identifier,
|
29
35
|
get_enum_by_name_or_val,
|
30
36
|
linspace_rect,
|
31
37
|
process_string_nodes,
|
32
38
|
split_param_label,
|
39
|
+
timedelta_format,
|
33
40
|
)
|
34
|
-
from hpcflow.sdk.submission.shells import get_shell
|
35
|
-
from hpcflow.sdk.submission.submission import timedelta_format
|
36
41
|
|
42
|
+
if TYPE_CHECKING:
|
43
|
+
from collections.abc import Iterable, Iterator, Mapping
|
44
|
+
from typing import Any, ClassVar, Literal
|
45
|
+
from typing_extensions import Self, TypeAlias
|
46
|
+
from h5py import Group # type: ignore
|
47
|
+
from numpy.typing import NDArray
|
48
|
+
from ..app import BaseApp
|
49
|
+
from ..typing import ParamSource
|
50
|
+
from .actions import ActionScope
|
51
|
+
from .element import ElementFilter
|
52
|
+
from .object_list import ResourceList
|
53
|
+
from .rule import Rule
|
54
|
+
from .task import ElementSet, TaskSchema, TaskTemplate, WorkflowTask
|
55
|
+
from .types import (
|
56
|
+
Address,
|
57
|
+
Numeric,
|
58
|
+
LabelInfo,
|
59
|
+
LabellingDescriptor,
|
60
|
+
ResourcePersistingWorkflow,
|
61
|
+
RuleArgs,
|
62
|
+
SchemaInputKwargs,
|
63
|
+
)
|
64
|
+
from .workflow import Workflow, WorkflowTemplate
|
65
|
+
from .validation import Schema
|
66
|
+
|
67
|
+
|
68
|
+
T = TypeVar("T")
|
37
69
|
|
38
|
-
Address = List[Union[int, float, str]]
|
39
|
-
Numeric = Union[int, float, np.number]
|
40
70
|
|
71
|
+
def _process_demo_data_strings(app: BaseApp, value: T) -> T:
|
72
|
+
demo_pattern = re.compile(r"\<\<demo_data_file:(.*)\>\>")
|
41
73
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
str_out = re.sub(
|
46
|
-
pattern=demo_pattern,
|
47
|
-
repl=lambda x: str(app.get_demo_data_file_path(x.group(1))),
|
74
|
+
def string_processor(str_in: str) -> str:
|
75
|
+
str_out = demo_pattern.sub(
|
76
|
+
repl=lambda x: str(app.get_demo_data_file_path(x[1])),
|
48
77
|
string=str_in,
|
49
78
|
)
|
50
79
|
return str_out
|
@@ -52,6 +81,8 @@ def _process_demo_data_strings(app, value):
|
|
52
81
|
return process_string_nodes(value, string_processor)
|
53
82
|
|
54
83
|
|
84
|
+
@dataclass
|
85
|
+
@hydrate
|
55
86
|
class ParameterValue:
|
56
87
|
"""
|
57
88
|
The value handler for a parameter.
|
@@ -59,58 +90,60 @@ class ParameterValue:
|
|
59
90
|
Intended to be subclassed.
|
60
91
|
"""
|
61
92
|
|
62
|
-
_typ = None
|
63
|
-
_sub_parameters = {}
|
93
|
+
_typ: ClassVar[str | None] = None
|
94
|
+
_sub_parameters: ClassVar[dict[str, str]] = {}
|
64
95
|
|
65
|
-
def to_dict(self):
|
96
|
+
def to_dict(self) -> dict[str, Any]:
|
66
97
|
"""
|
67
98
|
Serialise this parameter value as a dictionary.
|
68
99
|
"""
|
69
100
|
if hasattr(self, "__dict__"):
|
70
|
-
return dict(self.__dict__)
|
101
|
+
return self._postprocess_to_dict(dict(self.__dict__))
|
71
102
|
elif hasattr(self, "__slots__"):
|
72
|
-
return
|
103
|
+
return self._postprocess_to_dict(
|
104
|
+
{k: getattr(self, k) for k in self.__slots__}
|
105
|
+
)
|
106
|
+
else:
|
107
|
+
raise NotImplementedError
|
108
|
+
|
109
|
+
def _postprocess_to_dict(self, d: dict[str, Any]) -> dict[str, Any]:
|
110
|
+
"""Postprocess the results of :meth:`to_dict`."""
|
111
|
+
return d
|
73
112
|
|
74
|
-
def prepare_JSON_dump(self) ->
|
113
|
+
def prepare_JSON_dump(self) -> dict[str, Any]:
|
75
114
|
"""
|
76
115
|
Prepare this parameter value for serialisation as JSON.
|
77
116
|
"""
|
78
117
|
raise NotImplementedError
|
79
118
|
|
80
|
-
def dump_to_HDF5_group(self, group):
|
119
|
+
def dump_to_HDF5_group(self, group: Group):
|
81
120
|
"""
|
82
121
|
Write this parameter value to an HDF5 group.
|
83
122
|
"""
|
84
123
|
raise NotImplementedError
|
85
124
|
|
86
125
|
@classmethod
|
87
|
-
def
|
126
|
+
def dump_element_group_to_HDF5_group(cls, objs: list[ParameterValue], group: Group):
|
127
|
+
"""
|
128
|
+
Write a list (from an element group) of parameter values to an HDF5 group.
|
129
|
+
"""
|
130
|
+
raise NotImplementedError
|
131
|
+
|
132
|
+
@classmethod
|
133
|
+
def save_from_HDF5_group(cls, group: Group, param_id: int, workflow: Workflow):
|
88
134
|
"""
|
89
135
|
Extract a parameter value from an HDF5 group.
|
90
136
|
"""
|
91
137
|
raise NotImplementedError
|
92
138
|
|
93
139
|
@classmethod
|
94
|
-
def save_from_JSON(cls, data, param_id: int, workflow):
|
140
|
+
def save_from_JSON(cls, data, param_id: int | list[int], workflow: Workflow):
|
95
141
|
"""
|
96
142
|
Extract a parameter value from JSON data.
|
97
143
|
"""
|
98
144
|
raise NotImplementedError
|
99
145
|
|
100
146
|
|
101
|
-
class ParameterPropagationMode(enum.Enum):
|
102
|
-
"""
|
103
|
-
How a parameter is propagated.
|
104
|
-
"""
|
105
|
-
|
106
|
-
#: Parameter is propagated implicitly.
|
107
|
-
IMPLICIT = 0
|
108
|
-
#: Parameter is propagated explicitly.
|
109
|
-
EXPLICIT = 1
|
110
|
-
#: Parameter is never propagated.
|
111
|
-
NEVER = 2
|
112
|
-
|
113
|
-
|
114
147
|
@dataclass
|
115
148
|
class ParameterPath(JSONLike):
|
116
149
|
"""
|
@@ -119,14 +152,13 @@ class ParameterPath(JSONLike):
|
|
119
152
|
|
120
153
|
# TODO: unused?
|
121
154
|
#: The path to the parameter.
|
122
|
-
path: Sequence[
|
155
|
+
path: Sequence[str | int | float]
|
123
156
|
#: The task in which to look up the parameter.
|
124
|
-
task:
|
125
|
-
Union[app.TaskTemplate, app.TaskSchema]
|
126
|
-
] = None # default is "current" task
|
157
|
+
task: TaskTemplate | TaskSchema | None = None # default is "current" task
|
127
158
|
|
128
159
|
|
129
160
|
@dataclass
|
161
|
+
@hydrate
|
130
162
|
class Parameter(JSONLike):
|
131
163
|
"""
|
132
164
|
A general parameter to a workflow task.
|
@@ -150,15 +182,15 @@ class Parameter(JSONLike):
|
|
150
182
|
Validation schema.
|
151
183
|
"""
|
152
184
|
|
153
|
-
_validation_schema = "parameters_spec_schema.yaml"
|
154
|
-
_child_objects = (
|
185
|
+
_validation_schema: ClassVar[str] = "parameters_spec_schema.yaml"
|
186
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
155
187
|
ChildObjectSpec(
|
156
188
|
name="typ",
|
157
189
|
json_like_name="type",
|
158
190
|
),
|
159
191
|
ChildObjectSpec(
|
160
192
|
name="_validation",
|
161
|
-
class_obj=
|
193
|
+
class_obj=ValidaSchema,
|
162
194
|
),
|
163
195
|
)
|
164
196
|
|
@@ -168,10 +200,10 @@ class Parameter(JSONLike):
|
|
168
200
|
#: Whether this parameter represents a file.
|
169
201
|
is_file: bool = False
|
170
202
|
#: Any parameters packed within this one.
|
171
|
-
sub_parameters:
|
172
|
-
_value_class:
|
173
|
-
_hash_value:
|
174
|
-
_validation:
|
203
|
+
sub_parameters: list[SubParameter] = field(default_factory=list)
|
204
|
+
_value_class: type[ParameterValue] | None = None
|
205
|
+
_hash_value: str | None = field(default=None, repr=False)
|
206
|
+
_validation: Schema | None = None
|
175
207
|
|
176
208
|
def __repr__(self) -> str:
|
177
209
|
is_file_str = ""
|
@@ -192,31 +224,42 @@ class Parameter(JSONLike):
|
|
192
224
|
f")"
|
193
225
|
)
|
194
226
|
|
195
|
-
def __post_init__(self):
|
227
|
+
def __post_init__(self) -> None:
|
196
228
|
self.typ = check_valid_py_identifier(self.typ)
|
197
229
|
self._set_value_class()
|
198
230
|
|
199
|
-
def _set_value_class(self):
|
231
|
+
def _set_value_class(self) -> None:
|
200
232
|
# custom parameter classes must inherit from `ParameterValue` not the app
|
201
233
|
# subclass:
|
202
234
|
if self._value_class is None:
|
203
|
-
|
204
|
-
|
205
|
-
|
235
|
+
self._value_class = next(
|
236
|
+
(
|
237
|
+
pv_class
|
238
|
+
for pv_class in ParameterValue.__subclasses__()
|
239
|
+
if pv_class._typ == self.typ
|
240
|
+
),
|
241
|
+
None,
|
242
|
+
)
|
206
243
|
|
207
|
-
def
|
244
|
+
def __eq__(self, other: Any) -> bool:
|
245
|
+
return isinstance(other, self.__class__) and self.typ == other.typ
|
246
|
+
|
247
|
+
def __lt__(self, other: Parameter):
|
208
248
|
return self.typ < other.typ
|
209
249
|
|
210
|
-
def __deepcopy__(self, memo):
|
250
|
+
def __deepcopy__(self, memo: dict[int, Any]):
|
211
251
|
kwargs = self.to_dict()
|
212
252
|
_validation = kwargs.pop("_validation")
|
213
253
|
obj = self.__class__(**copy.deepcopy(kwargs, memo))
|
214
254
|
obj._validation = _validation
|
215
255
|
return obj
|
216
256
|
|
217
|
-
|
218
|
-
|
257
|
+
@override
|
258
|
+
def _postprocess_to_dict(self, d: dict[str, Any]) -> dict[str, Any]:
|
259
|
+
dct = super()._postprocess_to_dict(d)
|
219
260
|
del dct["_value_class"]
|
261
|
+
if dct.get("name", None) is None:
|
262
|
+
dct.pop("name", None)
|
220
263
|
dct.pop("_task_schema", None) # TODO: how do we have a _task_schema ref?
|
221
264
|
return dct
|
222
265
|
|
@@ -227,6 +270,25 @@ class Parameter(JSONLike):
|
|
227
270
|
"""
|
228
271
|
return self.typ.lower().replace("_", "-")
|
229
272
|
|
273
|
+
def _instantiate_value(self, source: ParamSource, val: dict) -> Any:
|
274
|
+
"""
|
275
|
+
Convert the serialized form of this parameter to its "real" form,
|
276
|
+
if that is valid to do at all.
|
277
|
+
"""
|
278
|
+
if self._value_class is None:
|
279
|
+
return val
|
280
|
+
if (method_name := source.get("value_class_method")) is not None:
|
281
|
+
method = getattr(self._value_class, method_name)
|
282
|
+
else:
|
283
|
+
method = self._value_class
|
284
|
+
return method(**val)
|
285
|
+
|
286
|
+
def _force_value_class(self) -> type[ParameterValue] | None:
|
287
|
+
if (param_cls := self._value_class) is None:
|
288
|
+
self._set_value_class()
|
289
|
+
param_cls = self._value_class
|
290
|
+
return param_cls
|
291
|
+
|
230
292
|
|
231
293
|
@dataclass
|
232
294
|
class SubParameter:
|
@@ -237,10 +299,11 @@ class SubParameter:
|
|
237
299
|
#: How to find this within the containing paraneter.
|
238
300
|
address: Address
|
239
301
|
#: The containing main parameter.
|
240
|
-
parameter:
|
302
|
+
parameter: Parameter
|
241
303
|
|
242
304
|
|
243
305
|
@dataclass
|
306
|
+
@hydrate
|
244
307
|
class SchemaParameter(JSONLike):
|
245
308
|
"""
|
246
309
|
A parameter bound in a schema.
|
@@ -251,9 +314,7 @@ class SchemaParameter(JSONLike):
|
|
251
314
|
The parameter.
|
252
315
|
"""
|
253
316
|
|
254
|
-
|
255
|
-
|
256
|
-
_child_objects = (
|
317
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
257
318
|
ChildObjectSpec(
|
258
319
|
name="parameter",
|
259
320
|
class_name="Parameter",
|
@@ -262,22 +323,15 @@ class SchemaParameter(JSONLike):
|
|
262
323
|
),
|
263
324
|
)
|
264
325
|
|
265
|
-
def __post_init__(self):
|
326
|
+
def __post_init__(self) -> None:
|
266
327
|
self._validate()
|
267
328
|
|
268
|
-
def _validate(self):
|
329
|
+
def _validate(self) -> None:
|
269
330
|
if isinstance(self.parameter, str):
|
270
|
-
self.parameter = self.
|
271
|
-
|
272
|
-
@property
|
273
|
-
def name(self):
|
274
|
-
"""
|
275
|
-
The name of the parameter.
|
276
|
-
"""
|
277
|
-
return self.parameter.name
|
331
|
+
self.parameter: Parameter = self._app.Parameter(typ=self.parameter)
|
278
332
|
|
279
333
|
@property
|
280
|
-
def typ(self):
|
334
|
+
def typ(self) -> str:
|
281
335
|
"""
|
282
336
|
The type code of the parameter.
|
283
337
|
"""
|
@@ -294,6 +348,7 @@ class NullDefault(enum.Enum):
|
|
294
348
|
NULL = 0
|
295
349
|
|
296
350
|
|
351
|
+
@hydrate
|
297
352
|
class SchemaInput(SchemaParameter):
|
298
353
|
"""A Parameter as used within a particular schema, for which a default value may be
|
299
354
|
applied.
|
@@ -330,11 +385,18 @@ class SchemaInput(SchemaParameter):
|
|
330
385
|
Determines the name of the element group from which this input should be sourced.
|
331
386
|
This is a default value that will be applied to all `labels` if a "group" key
|
332
387
|
does not exist.
|
388
|
+
allow_failed_dependencies
|
389
|
+
This controls whether failure to retrieve inputs (i.e. an
|
390
|
+
`UnsetParameterDataError` is raised for one of the input sources) should be
|
391
|
+
allowed. By default, the unset value, which is equivalent to `False`, means no
|
392
|
+
failures are allowed. If set to `True`, any number of failures are allowed. If an
|
393
|
+
integer is specified, that number of failures are permitted. Finally, if a float
|
394
|
+
is specified, that proportion of failures are allowed.
|
333
395
|
"""
|
334
396
|
|
335
|
-
_task_schema = None # assigned by parent TaskSchema
|
397
|
+
_task_schema: TaskSchema | None = None # assigned by parent TaskSchema
|
336
398
|
|
337
|
-
_child_objects = (
|
399
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
338
400
|
ChildObjectSpec(
|
339
401
|
name="parameter",
|
340
402
|
class_name="Parameter",
|
@@ -345,12 +407,13 @@ class SchemaInput(SchemaParameter):
|
|
345
407
|
|
346
408
|
def __init__(
|
347
409
|
self,
|
348
|
-
parameter:
|
410
|
+
parameter: Parameter | str,
|
349
411
|
multiple: bool = False,
|
350
|
-
labels:
|
351
|
-
default_value:
|
412
|
+
labels: dict[str, LabelInfo] | None = None,
|
413
|
+
default_value: InputValue | Any | NullDefault = NullDefault.NULL,
|
352
414
|
propagation_mode: ParameterPropagationMode = ParameterPropagationMode.IMPLICIT,
|
353
|
-
group:
|
415
|
+
group: str | None = None,
|
416
|
+
allow_failed_dependencies: int | float | bool | None = False,
|
354
417
|
):
|
355
418
|
# TODO: can we define elements groups on local inputs as well, or should these be
|
356
419
|
# just for elements from other tasks?
|
@@ -360,24 +423,32 @@ class SchemaInput(SchemaParameter):
|
|
360
423
|
|
361
424
|
if isinstance(parameter, str):
|
362
425
|
try:
|
363
|
-
parameter
|
426
|
+
#: The parameter (i.e. type) of this schema input.
|
427
|
+
self.parameter = self._app.parameters.get(parameter)
|
364
428
|
except ValueError:
|
365
|
-
parameter = self.
|
429
|
+
self.parameter = self._app.Parameter(parameter)
|
430
|
+
else:
|
431
|
+
self.parameter = parameter
|
366
432
|
|
367
|
-
|
368
|
-
|
369
|
-
|
433
|
+
if allow_failed_dependencies is None:
|
434
|
+
allow_failed_dependencies = 0.0
|
435
|
+
elif isinstance(allow_failed_dependencies, bool):
|
436
|
+
allow_failed_dependencies = float(allow_failed_dependencies)
|
437
|
+
|
438
|
+
#: Whether to expect multiple labels for this parameter.
|
370
439
|
self.multiple = multiple
|
440
|
+
self.allow_failed_dependencies = allow_failed_dependencies
|
441
|
+
|
371
442
|
#: Dict whose keys represent the string labels that distinguish multiple
|
372
443
|
#: parameters if `multiple` is `True`.
|
373
|
-
self.labels
|
374
|
-
|
375
|
-
if self.labels is None:
|
444
|
+
self.labels: dict[str, LabelInfo]
|
445
|
+
if labels is None:
|
376
446
|
if self.multiple:
|
377
447
|
self.labels = {"*": {}}
|
378
448
|
else:
|
379
449
|
self.labels = {"": {}}
|
380
450
|
else:
|
451
|
+
self.labels = labels
|
381
452
|
if not self.multiple:
|
382
453
|
# check single-item:
|
383
454
|
if len(self.labels) > 1:
|
@@ -387,7 +458,7 @@ class SchemaInput(SchemaParameter):
|
|
387
458
|
f"`labels` is: {self.labels!r}."
|
388
459
|
)
|
389
460
|
|
390
|
-
labels_defaults = {}
|
461
|
+
labels_defaults: LabelInfo = {}
|
391
462
|
if propagation_mode is not None:
|
392
463
|
labels_defaults["propagation_mode"] = propagation_mode
|
393
464
|
if group is not None:
|
@@ -397,14 +468,15 @@ class SchemaInput(SchemaParameter):
|
|
397
468
|
for k, v in self.labels.items():
|
398
469
|
labels_defaults_i = copy.deepcopy(labels_defaults)
|
399
470
|
if default_value is not NullDefault.NULL:
|
400
|
-
if
|
401
|
-
default_value =
|
471
|
+
if isinstance(default_value, InputValue):
|
472
|
+
labels_defaults_i["default_value"] = default_value
|
473
|
+
else:
|
474
|
+
labels_defaults_i["default_value"] = self._app.InputValue(
|
402
475
|
parameter=self.parameter,
|
403
476
|
value=default_value,
|
404
477
|
label=k,
|
405
478
|
)
|
406
|
-
|
407
|
-
label_i = {**labels_defaults_i, **v}
|
479
|
+
label_i: LabelInfo = {**labels_defaults_i, **v}
|
408
480
|
if "propagation_mode" in label_i:
|
409
481
|
label_i["propagation_mode"] = get_enum_by_name_or_val(
|
410
482
|
ParameterPropagationMode, label_i["propagation_mode"]
|
@@ -420,8 +492,8 @@ class SchemaInput(SchemaParameter):
|
|
420
492
|
default_str = ""
|
421
493
|
group_str = ""
|
422
494
|
labels_str = ""
|
423
|
-
if not self.multiple:
|
424
|
-
label = next(iter(self.labels
|
495
|
+
if not self.multiple and self.labels:
|
496
|
+
label = next(iter(self.labels)) # the single key
|
425
497
|
|
426
498
|
default_str = ""
|
427
499
|
if "default_value" in self.labels[label]:
|
@@ -429,8 +501,7 @@ class SchemaInput(SchemaParameter):
|
|
429
501
|
f", default_value={self.labels[label]['default_value'].value!r}"
|
430
502
|
)
|
431
503
|
|
432
|
-
group
|
433
|
-
if group is not None:
|
504
|
+
if (group := self.labels[label].get("group")) is not None:
|
434
505
|
group_str = f", group={group!r}"
|
435
506
|
|
436
507
|
else:
|
@@ -444,20 +515,20 @@ class SchemaInput(SchemaParameter):
|
|
444
515
|
f")"
|
445
516
|
)
|
446
517
|
|
447
|
-
|
448
|
-
|
518
|
+
@override
|
519
|
+
def _postprocess_to_dict(self, d: dict[str, Any]) -> dict[str, Any]:
|
520
|
+
dct = super()._postprocess_to_dict(d)
|
521
|
+
v: dict[str, ParameterPropagationMode]
|
449
522
|
for k, v in dct["labels"].items():
|
450
|
-
prop_mode
|
451
|
-
if prop_mode:
|
523
|
+
if (prop_mode := v.get("parameter_propagation_mode")) is not None:
|
452
524
|
dct["labels"][k]["parameter_propagation_mode"] = prop_mode.name
|
453
525
|
return dct
|
454
526
|
|
455
|
-
def
|
456
|
-
|
457
|
-
for k, v in out["labels"].items():
|
527
|
+
def _postprocess_to_json(self, json_like):
|
528
|
+
for v in json_like["labels"].values():
|
458
529
|
if "default_value" in v:
|
459
|
-
|
460
|
-
return
|
530
|
+
v["default_value_is_input_value"] = True
|
531
|
+
return json_like
|
461
532
|
|
462
533
|
@classmethod
|
463
534
|
def from_json_like(cls, json_like, shared_data=None):
|
@@ -473,81 +544,86 @@ class SchemaInput(SchemaParameter):
|
|
473
544
|
}
|
474
545
|
json_like["labels"][k][
|
475
546
|
"default_value"
|
476
|
-
] = cls.
|
547
|
+
] = cls._app.InputValue.from_json_like(
|
477
548
|
json_like=inp_val_kwargs,
|
478
549
|
shared_data=shared_data,
|
479
550
|
)
|
480
551
|
|
481
|
-
|
482
|
-
return obj
|
552
|
+
return super().from_json_like(json_like, shared_data)
|
483
553
|
|
484
|
-
def __deepcopy__(self, memo):
|
485
|
-
kwargs = {
|
486
|
-
"parameter": self.parameter,
|
554
|
+
def __deepcopy__(self, memo: dict[int, Any]):
|
555
|
+
kwargs: SchemaInputKwargs = {
|
556
|
+
"parameter": copy.deepcopy(self.parameter, memo),
|
487
557
|
"multiple": self.multiple,
|
488
|
-
"labels": self.labels,
|
558
|
+
"labels": copy.deepcopy(self.labels, memo),
|
559
|
+
"allow_failed_dependencies": self.allow_failed_dependencies,
|
489
560
|
}
|
490
|
-
obj = self.__class__(**
|
561
|
+
obj = self.__class__(**kwargs)
|
491
562
|
obj._task_schema = self._task_schema
|
492
563
|
return obj
|
493
564
|
|
494
565
|
@property
|
495
|
-
def default_value(self):
|
566
|
+
def default_value(self) -> InputValue | Literal[NullDefault.NULL] | None:
|
496
567
|
"""
|
497
568
|
The default value of the input.
|
498
569
|
"""
|
499
|
-
if
|
500
|
-
if "default_value" in
|
501
|
-
return
|
570
|
+
if single_data := self.single_labelled_data:
|
571
|
+
if "default_value" in single_data:
|
572
|
+
return single_data["default_value"]
|
502
573
|
else:
|
503
574
|
return NullDefault.NULL
|
575
|
+
return None
|
504
576
|
|
505
577
|
@property
|
506
|
-
def task_schema(self):
|
578
|
+
def task_schema(self) -> TaskSchema:
|
507
579
|
"""
|
508
580
|
The schema containing this input.
|
509
581
|
"""
|
582
|
+
assert self._task_schema is not None
|
510
583
|
return self._task_schema
|
511
584
|
|
512
585
|
@property
|
513
|
-
def all_labelled_types(self):
|
586
|
+
def all_labelled_types(self) -> list[str]:
|
514
587
|
"""
|
515
588
|
The types of the input labels.
|
516
589
|
"""
|
517
|
-
return
|
590
|
+
return [(f"{self.typ}[{i}]" if i else self.typ) for i in self.labels]
|
518
591
|
|
519
592
|
@property
|
520
|
-
def single_label(self):
|
593
|
+
def single_label(self) -> str | None:
|
521
594
|
"""
|
522
595
|
The label of this input, assuming it is not mulitple.
|
523
596
|
"""
|
524
597
|
if not self.multiple:
|
525
598
|
return next(iter(self.labels))
|
599
|
+
return None
|
526
600
|
|
527
601
|
@property
|
528
|
-
def single_labelled_type(self):
|
602
|
+
def single_labelled_type(self) -> str | None:
|
529
603
|
"""
|
530
604
|
The type code of this input, assuming it is not mulitple.
|
531
605
|
"""
|
532
606
|
if not self.multiple:
|
533
607
|
return next(iter(self.labelled_info()))["labelled_type"]
|
608
|
+
return None
|
534
609
|
|
535
610
|
@property
|
536
|
-
def single_labelled_data(self):
|
611
|
+
def single_labelled_data(self) -> LabelInfo | None:
|
537
612
|
"""
|
538
613
|
The value of this input, assuming it is not mulitple.
|
539
614
|
"""
|
540
|
-
if
|
541
|
-
return self.labels[
|
615
|
+
if (label := self.single_label) is not None:
|
616
|
+
return self.labels[label]
|
617
|
+
return None
|
542
618
|
|
543
|
-
def labelled_info(self):
|
619
|
+
def labelled_info(self) -> Iterator[LabellingDescriptor]:
|
544
620
|
"""
|
545
621
|
Get descriptors for all the labels associated with this input.
|
546
622
|
"""
|
547
623
|
for k, v in self.labels.items():
|
548
|
-
label = f"[{k}]" if k else
|
549
|
-
dct = {
|
550
|
-
"labelled_type":
|
624
|
+
label = f"{self.parameter.typ}[{k}]" if k else self.parameter.typ
|
625
|
+
dct: LabellingDescriptor = {
|
626
|
+
"labelled_type": label,
|
551
627
|
"propagation_mode": v["propagation_mode"],
|
552
628
|
"group": v.get("group"),
|
553
629
|
}
|
@@ -555,18 +631,28 @@ class SchemaInput(SchemaParameter):
|
|
555
631
|
dct["default_value"] = v["default_value"]
|
556
632
|
yield dct
|
557
633
|
|
558
|
-
|
634
|
+
@property
|
635
|
+
def _simple_labelled_info(self) -> Iterator[tuple[str, ParameterPropagationMode]]:
|
636
|
+
"""
|
637
|
+
Cut-down version of :py:meth:`labelled_info` that has lower overheads.
|
638
|
+
"""
|
639
|
+
for k, v in self.labels.items():
|
640
|
+
label = f"{self.parameter.typ}[{k}]" if k else self.parameter.typ
|
641
|
+
yield label, v["propagation_mode"]
|
642
|
+
|
643
|
+
def _validate(self) -> None:
|
559
644
|
super()._validate()
|
560
645
|
for k, v in self.labels.items():
|
561
646
|
if "default_value" in v:
|
562
647
|
if not isinstance(v["default_value"], InputValue):
|
563
|
-
def_val = self.
|
648
|
+
def_val = self._app.InputValue(
|
564
649
|
parameter=self.parameter,
|
565
650
|
value=v["default_value"],
|
566
651
|
label=k,
|
567
652
|
)
|
568
|
-
|
569
|
-
|
653
|
+
v["default_value"] = def_val
|
654
|
+
else:
|
655
|
+
def_val = v["default_value"]
|
570
656
|
if def_val.parameter != self.parameter or def_val.label != k:
|
571
657
|
raise ValueError(
|
572
658
|
f"{self.__class__.__name__} `default_value` for label {k!r} must "
|
@@ -576,24 +662,36 @@ class SchemaInput(SchemaParameter):
|
|
576
662
|
)
|
577
663
|
|
578
664
|
@property
|
579
|
-
def input_or_output(self):
|
665
|
+
def input_or_output(self) -> str:
|
580
666
|
"""
|
581
667
|
Whether this is an input or output. Always ``input``.
|
582
668
|
"""
|
583
669
|
return "input"
|
584
670
|
|
585
671
|
|
586
|
-
@dataclass
|
672
|
+
@dataclass(init=False)
|
673
|
+
@hydrate
|
587
674
|
class SchemaOutput(SchemaParameter):
|
588
675
|
"""A Parameter as outputted from particular task."""
|
589
676
|
|
590
677
|
#: The basic parameter this supplies.
|
591
678
|
parameter: Parameter
|
592
679
|
#: How this output propagates.
|
593
|
-
propagation_mode: ParameterPropagationMode
|
680
|
+
propagation_mode: ParameterPropagationMode
|
681
|
+
|
682
|
+
def __init__(
|
683
|
+
self,
|
684
|
+
parameter: Parameter | str,
|
685
|
+
propagation_mode: ParameterPropagationMode = ParameterPropagationMode.IMPLICIT,
|
686
|
+
):
|
687
|
+
if isinstance(parameter, str):
|
688
|
+
self.parameter: Parameter = self._app.Parameter(typ=parameter)
|
689
|
+
else:
|
690
|
+
self.parameter = parameter
|
691
|
+
self.propagation_mode = propagation_mode
|
594
692
|
|
595
693
|
@property
|
596
|
-
def input_or_output(self):
|
694
|
+
def input_or_output(self) -> str:
|
597
695
|
"""
|
598
696
|
Whether this is an input or output. Always ``output``.
|
599
697
|
"""
|
@@ -624,7 +722,52 @@ class BuiltinSchemaParameter:
|
|
624
722
|
pass
|
625
723
|
|
626
724
|
|
627
|
-
class
|
725
|
+
class _BaseSequence(JSONLike):
|
726
|
+
"""
|
727
|
+
A base class for shared methods of `ValueSequence` and `MultiPathSequence`.
|
728
|
+
"""
|
729
|
+
|
730
|
+
def __eq__(self, other: Any) -> bool:
|
731
|
+
if not isinstance(other, self.__class__):
|
732
|
+
return False
|
733
|
+
return self.to_dict() == other.to_dict()
|
734
|
+
|
735
|
+
@classmethod
|
736
|
+
def from_json_like(cls, json_like, shared_data=None):
|
737
|
+
if "path" in json_like: # note: singular
|
738
|
+
# only applicable to ValueSequence, although not well-defined/useful anyway,
|
739
|
+
# I think.
|
740
|
+
if "::" in json_like["path"]:
|
741
|
+
path, cls_method = json_like["path"].split("::")
|
742
|
+
json_like["path"] = path
|
743
|
+
json_like["value_class_method"] = cls_method
|
744
|
+
|
745
|
+
val_key = next((item for item in json_like if "values" in item), "")
|
746
|
+
if "::" in val_key:
|
747
|
+
# class method (e.g. `from_range`, `from_file` etc):
|
748
|
+
_, method = val_key.split("::")
|
749
|
+
_values_method_args = json_like.pop(val_key)
|
750
|
+
|
751
|
+
if "paths" in json_like: # note: plural
|
752
|
+
# only applicable to `MultiPathSequence`, where it is useful to know
|
753
|
+
# how many paths we are generating sequences for:
|
754
|
+
_values_method_args["paths"] = json_like["paths"]
|
755
|
+
|
756
|
+
_values_method = f"_values_{method}"
|
757
|
+
_values_method_args = _process_demo_data_strings(
|
758
|
+
cls._app, _values_method_args
|
759
|
+
)
|
760
|
+
json_like["values"] = getattr(cls, _values_method)(**_values_method_args)
|
761
|
+
|
762
|
+
obj = super().from_json_like(json_like, shared_data)
|
763
|
+
if "::" in val_key:
|
764
|
+
obj._values_method = method
|
765
|
+
obj._values_method_args = _values_method_args
|
766
|
+
|
767
|
+
return obj
|
768
|
+
|
769
|
+
|
770
|
+
class ValueSequence(_BaseSequence):
|
628
771
|
"""
|
629
772
|
A sequence of values.
|
630
773
|
|
@@ -645,39 +788,43 @@ class ValueSequence(JSONLike):
|
|
645
788
|
def __init__(
|
646
789
|
self,
|
647
790
|
path: str,
|
648
|
-
values:
|
649
|
-
nesting_order:
|
650
|
-
label:
|
651
|
-
value_class_method:
|
791
|
+
values: Sequence[Any] | None,
|
792
|
+
nesting_order: int | float | None = None,
|
793
|
+
label: str | int | None = None,
|
794
|
+
value_class_method: str | None = None,
|
652
795
|
):
|
653
|
-
|
654
|
-
path, label = self._validate_parameter_path(path, label)
|
655
|
-
|
796
|
+
path_, label_ = self._validate_parameter_path(path, label)
|
656
797
|
#: The path to this sequence.
|
657
|
-
self.path =
|
798
|
+
self.path = path_
|
658
799
|
#: The label of this sequence.
|
659
|
-
self.label =
|
800
|
+
self.label = label_
|
660
801
|
#: The nesting order for this sequence.
|
661
|
-
self.nesting_order = nesting_order
|
802
|
+
self.nesting_order = None if nesting_order is None else float(nesting_order)
|
662
803
|
#: Name of a method used to generate sequence values.
|
663
804
|
self.value_class_method = value_class_method
|
664
805
|
|
665
806
|
if values is not None:
|
666
|
-
self._values
|
807
|
+
self._values: list[Any] | None = [
|
808
|
+
_process_demo_data_strings(self._app, i) for i in values
|
809
|
+
]
|
810
|
+
else:
|
811
|
+
self._values = None
|
667
812
|
|
668
|
-
self._values_group_idx = None
|
669
|
-
self._values_are_objs
|
813
|
+
self._values_group_idx: list[int] | None = None
|
814
|
+
self._values_are_objs: list[
|
815
|
+
bool
|
816
|
+
] | None = None # assigned initially on `make_persistent`
|
670
817
|
|
671
|
-
self._workflow = None
|
672
|
-
self._element_set = None # assigned by parent ElementSet
|
818
|
+
self._workflow: Workflow | None = None # assigned in `make_persistent`
|
819
|
+
self._element_set: ElementSet | None = None # assigned by parent `ElementSet`
|
673
820
|
|
674
821
|
# assigned if this is an "inputs" sequence in `WorkflowTask._add_element_set`:
|
675
|
-
self._parameter = None
|
822
|
+
self._parameter: Parameter | None = None
|
676
823
|
|
677
|
-
self._path_split = None # assigned by property `path_split`
|
824
|
+
self._path_split: list[str] | None = None # assigned by property `path_split`
|
678
825
|
|
679
|
-
self._values_method = None
|
680
|
-
self._values_method_args = None
|
826
|
+
self._values_method: str | None = None
|
827
|
+
self._values_method_args: dict | None = None
|
681
828
|
|
682
829
|
def __repr__(self):
|
683
830
|
label_str = ""
|
@@ -698,14 +845,7 @@ class ValueSequence(JSONLike):
|
|
698
845
|
f")"
|
699
846
|
)
|
700
847
|
|
701
|
-
def
|
702
|
-
if not isinstance(other, self.__class__):
|
703
|
-
return False
|
704
|
-
if self.to_dict() == other.to_dict():
|
705
|
-
return True
|
706
|
-
return False
|
707
|
-
|
708
|
-
def __deepcopy__(self, memo):
|
848
|
+
def __deepcopy__(self, memo: dict[int, Any]):
|
709
849
|
kwargs = self.to_dict()
|
710
850
|
kwargs["values"] = kwargs.pop("_values")
|
711
851
|
|
@@ -728,95 +868,73 @@ class ValueSequence(JSONLike):
|
|
728
868
|
|
729
869
|
return obj
|
730
870
|
|
731
|
-
@classmethod
|
732
|
-
def from_json_like(cls, json_like, shared_data=None):
|
733
|
-
if "::" in json_like["path"]:
|
734
|
-
path, cls_method = json_like["path"].split("::")
|
735
|
-
json_like["path"] = path
|
736
|
-
json_like["value_class_method"] = cls_method
|
737
|
-
|
738
|
-
val_key = None
|
739
|
-
for i in json_like:
|
740
|
-
if "values" in i:
|
741
|
-
val_key = i
|
742
|
-
if "::" in val_key:
|
743
|
-
# class method (e.g. `from_range`, `from_file` etc):
|
744
|
-
_, method = val_key.split("::")
|
745
|
-
_values_method_args = json_like.pop(val_key)
|
746
|
-
_values_method = f"_values_{method}"
|
747
|
-
_values_method_args = _process_demo_data_strings(cls.app, _values_method_args)
|
748
|
-
json_like["values"] = getattr(cls, _values_method)(**_values_method_args)
|
749
|
-
|
750
|
-
obj = super().from_json_like(json_like, shared_data)
|
751
|
-
if "::" in val_key:
|
752
|
-
obj._values_method = method
|
753
|
-
obj._values_method_args = _values_method_args
|
754
|
-
|
755
|
-
return obj
|
756
|
-
|
757
871
|
@property
|
758
|
-
def parameter(self):
|
872
|
+
def parameter(self) -> Parameter | None:
|
759
873
|
"""
|
760
874
|
The parameter this sequence supplies.
|
761
875
|
"""
|
762
876
|
return self._parameter
|
763
877
|
|
764
878
|
@property
|
765
|
-
def path_split(self):
|
879
|
+
def path_split(self) -> Sequence[str]:
|
766
880
|
"""
|
767
|
-
The components of
|
881
|
+
The components of this path.
|
768
882
|
"""
|
769
883
|
if self._path_split is None:
|
770
884
|
self._path_split = self.path.split(".")
|
771
885
|
return self._path_split
|
772
886
|
|
773
887
|
@property
|
774
|
-
def path_type(self):
|
888
|
+
def path_type(self) -> str:
|
775
889
|
"""
|
776
890
|
The type of path this is.
|
777
891
|
"""
|
778
892
|
return self.path_split[0]
|
779
893
|
|
780
894
|
@property
|
781
|
-
def input_type(self):
|
895
|
+
def input_type(self) -> str | None:
|
782
896
|
"""
|
783
897
|
The type of input sequence this is, if it is one.
|
784
898
|
"""
|
785
899
|
if self.path_type == "inputs":
|
786
900
|
return self.path_split[1].replace(self._label_fmt, "")
|
901
|
+
return None
|
787
902
|
|
788
903
|
@property
|
789
|
-
def input_path(self):
|
904
|
+
def input_path(self) -> str | None:
|
790
905
|
"""
|
791
906
|
The path of the input sequence this is, if it is one.
|
792
907
|
"""
|
793
908
|
if self.path_type == "inputs":
|
794
909
|
return ".".join(self.path_split[2:])
|
910
|
+
return None
|
795
911
|
|
796
912
|
@property
|
797
|
-
def resource_scope(self):
|
913
|
+
def resource_scope(self) -> str | None:
|
798
914
|
"""
|
799
915
|
The scope of the resources this is, if it is one.
|
800
916
|
"""
|
801
917
|
if self.path_type == "resources":
|
802
918
|
return self.path_split[1]
|
919
|
+
return None
|
803
920
|
|
804
921
|
@property
|
805
|
-
def is_sub_value(self):
|
922
|
+
def is_sub_value(self) -> bool:
|
806
923
|
"""True if the values are for a sub part of the parameter."""
|
807
|
-
return
|
924
|
+
return bool(self.input_path)
|
808
925
|
|
809
926
|
@property
|
810
|
-
def _label_fmt(self):
|
927
|
+
def _label_fmt(self) -> str:
|
811
928
|
return f"[{self.label}]" if self.label else ""
|
812
929
|
|
813
930
|
@property
|
814
|
-
def labelled_type(self):
|
931
|
+
def labelled_type(self) -> str | None:
|
815
932
|
"""
|
816
933
|
The labelled type of input sequence this is, if it is one.
|
817
934
|
"""
|
818
935
|
if self.input_type:
|
819
936
|
return f"{self.input_type}{self._label_fmt}"
|
937
|
+
return None
|
820
938
|
|
821
939
|
@classmethod
|
822
940
|
def _json_like_constructor(cls, json_like):
|
@@ -836,7 +954,9 @@ class ValueSequence(JSONLike):
|
|
836
954
|
obj._values_method_args = _values_method_args
|
837
955
|
return obj
|
838
956
|
|
839
|
-
def _validate_parameter_path(
|
957
|
+
def _validate_parameter_path(
|
958
|
+
self, path: str, label: str | int | None
|
959
|
+
) -> tuple[str, str | int | None]:
|
840
960
|
"""Parse the supplied path and perform basic checks on it.
|
841
961
|
|
842
962
|
This method also adds the specified `SchemaInput` label to the path and checks for
|
@@ -852,25 +972,24 @@ class ValueSequence(JSONLike):
|
|
852
972
|
)
|
853
973
|
path_l = path.lower()
|
854
974
|
path_split = path_l.split(".")
|
855
|
-
|
856
|
-
if not path_split[0] in
|
975
|
+
ALLOWED_PATH_START = ("inputs", "resources", "environments", "env_preset")
|
976
|
+
if not path_split[0] in ALLOWED_PATH_START:
|
857
977
|
raise MalformedParameterPathError(
|
858
978
|
f"`path` must start with one of: "
|
859
|
-
f'{", ".join(f"{
|
979
|
+
f'{", ".join(f"{pfx!r}" for pfx in ALLOWED_PATH_START)}, but given path '
|
860
980
|
f"is: {path!r}."
|
861
981
|
)
|
862
982
|
|
863
983
|
_, label_from_path = split_param_label(path_l)
|
864
984
|
|
865
985
|
if path_split[0] == "inputs":
|
866
|
-
if label_arg:
|
867
|
-
if
|
986
|
+
if label_arg is not None and label_arg != "":
|
987
|
+
if label_from_path is None:
|
868
988
|
# add label to path without lower casing any parts:
|
869
989
|
path_split_orig = path.split(".")
|
870
990
|
path_split_orig[1] += f"[{label_arg}]"
|
871
991
|
path = ".".join(path_split_orig)
|
872
|
-
|
873
|
-
elif label_arg != label_from_path:
|
992
|
+
elif str(label_arg) != label_from_path:
|
874
993
|
raise ValueError(
|
875
994
|
f"{self.__class__.__name__} `label` argument is specified as "
|
876
995
|
f"{label_arg!r}, but a distinct label is implied by the sequence "
|
@@ -887,7 +1006,7 @@ class ValueSequence(JSONLike):
|
|
887
1006
|
f"`resource` sequences."
|
888
1007
|
)
|
889
1008
|
try:
|
890
|
-
self.
|
1009
|
+
self._app.ActionScope.from_json_like(path_split[1])
|
891
1010
|
except Exception as err:
|
892
1011
|
raise MalformedParameterPathError(
|
893
1012
|
f"Cannot parse a resource action scope from the second component of the "
|
@@ -895,42 +1014,42 @@ class ValueSequence(JSONLike):
|
|
895
1014
|
) from None
|
896
1015
|
|
897
1016
|
if len(path_split) > 2:
|
898
|
-
|
899
|
-
allowed = ResourceSpec.ALLOWED_PARAMETERS
|
900
|
-
if path_split_2 not in allowed:
|
901
|
-
allowed_keys_str = ", ".join(f'"{i}"' for i in allowed)
|
1017
|
+
if path_split[2] not in ResourceSpec.ALLOWED_PARAMETERS:
|
902
1018
|
raise UnknownResourceSpecItemError(
|
903
|
-
f"Resource item name {
|
904
|
-
f"resource item names are: {
|
1019
|
+
f"Resource item name {path_split[2]!r} is unknown. Allowed "
|
1020
|
+
f"resource item names are: {ResourceSpec._allowed_params_quoted()}."
|
905
1021
|
)
|
1022
|
+
label = ""
|
906
1023
|
|
907
1024
|
elif path_split[0] == "environments":
|
908
1025
|
# rewrite as a resources path:
|
909
1026
|
path = f"resources.any.{path}"
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
1027
|
+
label = str(label) if label is not None else ""
|
1028
|
+
else:
|
1029
|
+
pass
|
1030
|
+
# note: `env_preset` paths also need to be transformed into `resources`
|
1031
|
+
# paths, but we cannot do that until the sequence is part of a task, since
|
1032
|
+
# the available environment presets are defined in the task schema.
|
914
1033
|
|
915
1034
|
return path, label
|
916
1035
|
|
917
|
-
|
918
|
-
|
1036
|
+
@override
|
1037
|
+
def _postprocess_to_dict(self, d: dict[str, Any]) -> dict[str, Any]:
|
1038
|
+
out = super()._postprocess_to_dict(d)
|
919
1039
|
del out["_parameter"]
|
920
1040
|
del out["_path_split"]
|
921
|
-
|
922
|
-
del out["_workflow"]
|
1041
|
+
out.pop("_workflow", None)
|
923
1042
|
return out
|
924
1043
|
|
925
1044
|
@property
|
926
|
-
def normalised_path(self):
|
1045
|
+
def normalised_path(self) -> str:
|
927
1046
|
"""
|
928
1047
|
The path to this sequence.
|
929
1048
|
"""
|
930
1049
|
return self.path
|
931
1050
|
|
932
1051
|
@property
|
933
|
-
def normalised_inputs_path(self):
|
1052
|
+
def normalised_inputs_path(self) -> str | None:
|
934
1053
|
"""
|
935
1054
|
The normalised path without the "inputs" prefix, if the sequence is an
|
936
1055
|
inputs sequence, else return None.
|
@@ -941,63 +1060,68 @@ class ValueSequence(JSONLike):
|
|
941
1060
|
return f"{self.labelled_type}.{self.input_path}"
|
942
1061
|
else:
|
943
1062
|
return self.labelled_type
|
1063
|
+
return None
|
944
1064
|
|
945
1065
|
def make_persistent(
|
946
|
-
self, workflow:
|
947
|
-
) ->
|
1066
|
+
self, workflow: Workflow, source: ParamSource
|
1067
|
+
) -> tuple[str, list[int], bool]:
|
948
1068
|
"""Save value to a persistent workflow."""
|
949
1069
|
|
950
1070
|
if self._values_group_idx is not None:
|
951
|
-
|
952
|
-
data_ref = self._values_group_idx
|
953
|
-
if not all(workflow.check_parameters_exist(data_ref)):
|
1071
|
+
if not workflow.check_parameters_exist(self._values_group_idx):
|
954
1072
|
raise RuntimeError(
|
955
1073
|
f"{self.__class__.__name__} has a parameter group index "
|
956
|
-
f"({
|
1074
|
+
f"({self._values_group_idx}), but does not exist in the workflow."
|
957
1075
|
)
|
958
1076
|
# TODO: log if already persistent.
|
1077
|
+
return self.normalised_path, self._values_group_idx, False
|
959
1078
|
|
960
|
-
|
961
|
-
|
962
|
-
|
1079
|
+
data_ref: list[int] = []
|
1080
|
+
source = copy.deepcopy(source)
|
1081
|
+
if self.value_class_method:
|
963
1082
|
source["value_class_method"] = self.value_class_method
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
is_new = True
|
975
|
-
self._values_group_idx = data_ref
|
976
|
-
self._workflow = workflow
|
977
|
-
self._values = None
|
978
|
-
self._values_are_objs = are_objs
|
1083
|
+
are_objs: list[bool] = []
|
1084
|
+
assert self._values is not None
|
1085
|
+
for idx, item in enumerate(self._values):
|
1086
|
+
# record if ParameterValue sub-classes are passed for values, which allows
|
1087
|
+
# us to re-init the objects on access to `.value`:
|
1088
|
+
are_objs.append(isinstance(item, ParameterValue))
|
1089
|
+
source = copy.deepcopy(source)
|
1090
|
+
source["sequence_idx"] = idx
|
1091
|
+
pg_idx_i = workflow._add_parameter_data(item, source=source)
|
1092
|
+
data_ref.append(pg_idx_i)
|
979
1093
|
|
980
|
-
|
1094
|
+
self._values_group_idx = data_ref
|
1095
|
+
self._workflow = workflow
|
1096
|
+
self._values = None
|
1097
|
+
self._values_are_objs = are_objs
|
1098
|
+
return self.normalised_path, data_ref, True
|
981
1099
|
|
982
1100
|
@property
|
983
|
-
def workflow(self):
|
1101
|
+
def workflow(self) -> Workflow | None:
|
984
1102
|
"""
|
985
1103
|
The workflow containing this sequence.
|
986
1104
|
"""
|
987
1105
|
if self._workflow:
|
1106
|
+
# (assigned in `make_persistent`)
|
988
1107
|
return self._workflow
|
989
1108
|
elif self._element_set:
|
990
|
-
|
1109
|
+
# (assigned by parent `ElementSet`)
|
1110
|
+
if tmpl := self._element_set.task_template.workflow_template:
|
1111
|
+
return tmpl.workflow
|
1112
|
+
return None
|
991
1113
|
|
992
1114
|
@property
|
993
|
-
def values(self):
|
1115
|
+
def values(self) -> Sequence[Any] | None:
|
994
1116
|
"""
|
995
1117
|
The values in this sequence.
|
996
1118
|
"""
|
997
1119
|
if self._values_group_idx is not None:
|
998
|
-
vals = []
|
1120
|
+
vals: list[Any] = []
|
999
1121
|
for idx, pg_idx_i in enumerate(self._values_group_idx):
|
1000
|
-
|
1122
|
+
if not (w := self.workflow):
|
1123
|
+
continue
|
1124
|
+
param_i = w.get_parameter(pg_idx_i)
|
1001
1125
|
if param_i.data is not None:
|
1002
1126
|
val_i = param_i.data
|
1003
1127
|
else:
|
@@ -1007,16 +1131,11 @@ class ValueSequence(JSONLike):
|
|
1007
1131
|
# yet been committed to disk:
|
1008
1132
|
if (
|
1009
1133
|
self.parameter
|
1010
|
-
and self.
|
1134
|
+
and self._values_are_objs
|
1011
1135
|
and self._values_are_objs[idx]
|
1012
|
-
and
|
1136
|
+
and isinstance(val_i, dict)
|
1013
1137
|
):
|
1014
|
-
|
1015
|
-
if method_name:
|
1016
|
-
method = getattr(self.parameter._value_class, method_name)
|
1017
|
-
else:
|
1018
|
-
method = self.parameter._value_class
|
1019
|
-
val_i = method(**val_i)
|
1138
|
+
val_i = self.parameter._instantiate_value(param_i.source, val_i)
|
1020
1139
|
|
1021
1140
|
vals.append(val_i)
|
1022
1141
|
return vals
|
@@ -1024,52 +1143,72 @@ class ValueSequence(JSONLike):
|
|
1024
1143
|
return self._values
|
1025
1144
|
|
1026
1145
|
@classmethod
|
1027
|
-
def _values_from_linear_space(
|
1146
|
+
def _values_from_linear_space(
|
1147
|
+
cls, start: float, stop: float, num: int, **kwargs
|
1148
|
+
) -> list[float]:
|
1028
1149
|
return np.linspace(start, stop, num=num, **kwargs).tolist()
|
1029
1150
|
|
1030
1151
|
@classmethod
|
1031
|
-
def _values_from_geometric_space(
|
1152
|
+
def _values_from_geometric_space(
|
1153
|
+
cls, start: float, stop: float, num: int, **kwargs
|
1154
|
+
) -> list[float]:
|
1032
1155
|
return np.geomspace(start, stop, num=num, **kwargs).tolist()
|
1033
1156
|
|
1034
1157
|
@classmethod
|
1035
|
-
def _values_from_log_space(
|
1158
|
+
def _values_from_log_space(
|
1159
|
+
cls, start: float, stop: float, num: int, base: float = 10.0, **kwargs
|
1160
|
+
) -> list[float]:
|
1036
1161
|
return np.logspace(start, stop, num=num, base=base, **kwargs).tolist()
|
1037
1162
|
|
1038
1163
|
@classmethod
|
1039
|
-
def _values_from_range(
|
1164
|
+
def _values_from_range(
|
1165
|
+
cls, start: int | float, stop: int | float, step: int | float, **kwargs
|
1166
|
+
) -> list[float]:
|
1040
1167
|
return np.arange(start, stop, step, **kwargs).tolist()
|
1041
1168
|
|
1042
1169
|
@classmethod
|
1043
|
-
def _values_from_file(cls, file_path):
|
1170
|
+
def _values_from_file(cls, file_path: str | Path) -> list[str]:
|
1044
1171
|
with Path(file_path).open("rt") as fh:
|
1045
|
-
|
1046
|
-
return vals
|
1172
|
+
return [line.strip() for line in fh.readlines()]
|
1047
1173
|
|
1048
1174
|
@classmethod
|
1049
|
-
def _values_from_rectangle(
|
1175
|
+
def _values_from_rectangle(
|
1176
|
+
cls,
|
1177
|
+
start: Sequence[float],
|
1178
|
+
stop: Sequence[float],
|
1179
|
+
num: Sequence[int],
|
1180
|
+
coord: int | tuple[int, int] | None = None,
|
1181
|
+
include: Sequence[str] | None = None,
|
1182
|
+
**kwargs,
|
1183
|
+
) -> list[float]:
|
1050
1184
|
vals = linspace_rect(start=start, stop=stop, num=num, include=include, **kwargs)
|
1051
1185
|
if coord is not None:
|
1052
|
-
|
1186
|
+
return vals[coord].tolist()
|
1053
1187
|
else:
|
1054
|
-
|
1055
|
-
return vals
|
1188
|
+
return (vals.T).tolist()
|
1056
1189
|
|
1057
1190
|
@classmethod
|
1058
|
-
def _values_from_random_uniform(
|
1191
|
+
def _values_from_random_uniform(
|
1192
|
+
cls,
|
1193
|
+
num: int,
|
1194
|
+
low: float = 0.0,
|
1195
|
+
high: float = 1.0,
|
1196
|
+
seed: int | list[int] | None = None,
|
1197
|
+
) -> list[float]:
|
1059
1198
|
rng = np.random.default_rng(seed)
|
1060
1199
|
return rng.uniform(low=low, high=high, size=num).tolist()
|
1061
1200
|
|
1062
1201
|
@classmethod
|
1063
1202
|
def from_linear_space(
|
1064
1203
|
cls,
|
1065
|
-
path,
|
1066
|
-
start,
|
1067
|
-
stop,
|
1068
|
-
num,
|
1069
|
-
nesting_order=0,
|
1070
|
-
label=None,
|
1204
|
+
path: str,
|
1205
|
+
start: float,
|
1206
|
+
stop: float,
|
1207
|
+
num: int,
|
1208
|
+
nesting_order: float = 0,
|
1209
|
+
label: str | int | None = None,
|
1071
1210
|
**kwargs,
|
1072
|
-
):
|
1211
|
+
) -> Self:
|
1073
1212
|
"""
|
1074
1213
|
Build a sequence from a NumPy linear space.
|
1075
1214
|
"""
|
@@ -1084,15 +1223,15 @@ class ValueSequence(JSONLike):
|
|
1084
1223
|
@classmethod
|
1085
1224
|
def from_geometric_space(
|
1086
1225
|
cls,
|
1087
|
-
path,
|
1088
|
-
start,
|
1089
|
-
stop,
|
1090
|
-
num,
|
1091
|
-
nesting_order=0,
|
1226
|
+
path: str,
|
1227
|
+
start: float,
|
1228
|
+
stop: float,
|
1229
|
+
num: int,
|
1230
|
+
nesting_order: float = 0,
|
1092
1231
|
endpoint=True,
|
1093
|
-
label=None,
|
1232
|
+
label: str | int | None = None,
|
1094
1233
|
**kwargs,
|
1095
|
-
):
|
1234
|
+
) -> Self:
|
1096
1235
|
"""
|
1097
1236
|
Build a sequence from a NumPy geometric space.
|
1098
1237
|
"""
|
@@ -1106,16 +1245,16 @@ class ValueSequence(JSONLike):
|
|
1106
1245
|
@classmethod
|
1107
1246
|
def from_log_space(
|
1108
1247
|
cls,
|
1109
|
-
path,
|
1110
|
-
start,
|
1111
|
-
stop,
|
1112
|
-
num,
|
1113
|
-
nesting_order=0,
|
1248
|
+
path: str,
|
1249
|
+
start: float,
|
1250
|
+
stop: float,
|
1251
|
+
num: int,
|
1252
|
+
nesting_order: float = 0,
|
1114
1253
|
base=10.0,
|
1115
1254
|
endpoint=True,
|
1116
|
-
label=None,
|
1255
|
+
label: str | int | None = None,
|
1117
1256
|
**kwargs,
|
1118
|
-
):
|
1257
|
+
) -> Self:
|
1119
1258
|
"""
|
1120
1259
|
Build a sequence from a NumPy logarithmic space.
|
1121
1260
|
"""
|
@@ -1136,14 +1275,14 @@ class ValueSequence(JSONLike):
|
|
1136
1275
|
@classmethod
|
1137
1276
|
def from_range(
|
1138
1277
|
cls,
|
1139
|
-
path,
|
1140
|
-
start,
|
1141
|
-
stop,
|
1142
|
-
nesting_order=0,
|
1143
|
-
step=1,
|
1144
|
-
label=None,
|
1278
|
+
path: str,
|
1279
|
+
start: float,
|
1280
|
+
stop: float,
|
1281
|
+
nesting_order: float = 0,
|
1282
|
+
step: int | float = 1,
|
1283
|
+
label: str | int | None = None,
|
1145
1284
|
**kwargs,
|
1146
|
-
):
|
1285
|
+
) -> Self:
|
1147
1286
|
"""
|
1148
1287
|
Build a sequence from a range.
|
1149
1288
|
"""
|
@@ -1173,12 +1312,12 @@ class ValueSequence(JSONLike):
|
|
1173
1312
|
@classmethod
|
1174
1313
|
def from_file(
|
1175
1314
|
cls,
|
1176
|
-
path,
|
1177
|
-
file_path,
|
1178
|
-
nesting_order=0,
|
1179
|
-
label=None,
|
1315
|
+
path: str,
|
1316
|
+
file_path: str | Path,
|
1317
|
+
nesting_order: float = 0,
|
1318
|
+
label: str | int | None = None,
|
1180
1319
|
**kwargs,
|
1181
|
-
):
|
1320
|
+
) -> Self:
|
1182
1321
|
"""
|
1183
1322
|
Build a sequence from a simple file.
|
1184
1323
|
"""
|
@@ -1198,16 +1337,16 @@ class ValueSequence(JSONLike):
|
|
1198
1337
|
@classmethod
|
1199
1338
|
def from_rectangle(
|
1200
1339
|
cls,
|
1201
|
-
path,
|
1202
|
-
start,
|
1203
|
-
stop,
|
1204
|
-
num,
|
1205
|
-
coord:
|
1206
|
-
include:
|
1207
|
-
nesting_order=0,
|
1208
|
-
label=None,
|
1340
|
+
path: str,
|
1341
|
+
start: Sequence[float],
|
1342
|
+
stop: Sequence[float],
|
1343
|
+
num: Sequence[int],
|
1344
|
+
coord: int | None = None,
|
1345
|
+
include: list[str] | None = None,
|
1346
|
+
nesting_order: float = 0,
|
1347
|
+
label: str | int | None = None,
|
1209
1348
|
**kwargs,
|
1210
|
-
):
|
1349
|
+
) -> Self:
|
1211
1350
|
"""
|
1212
1351
|
Build a sequence to cover a rectangle.
|
1213
1352
|
|
@@ -1238,14 +1377,14 @@ class ValueSequence(JSONLike):
|
|
1238
1377
|
def from_random_uniform(
|
1239
1378
|
cls,
|
1240
1379
|
path,
|
1241
|
-
num,
|
1242
|
-
low=0.0,
|
1243
|
-
high=1.0,
|
1244
|
-
seed=None,
|
1245
|
-
nesting_order=0,
|
1246
|
-
label=None,
|
1380
|
+
num: int,
|
1381
|
+
low: float = 0.0,
|
1382
|
+
high: float = 1.0,
|
1383
|
+
seed: int | list[int] | None = None,
|
1384
|
+
nesting_order: float = 0,
|
1385
|
+
label: str | int | None = None,
|
1247
1386
|
**kwargs,
|
1248
|
-
):
|
1387
|
+
) -> Self:
|
1249
1388
|
"""
|
1250
1389
|
Build a sequence from a uniform random number generator.
|
1251
1390
|
"""
|
@@ -1257,13 +1396,264 @@ class ValueSequence(JSONLike):
|
|
1257
1396
|
return obj
|
1258
1397
|
|
1259
1398
|
|
1399
|
+
class MultiPathSequence(_BaseSequence):
|
1400
|
+
"""
|
1401
|
+
A sequence of values to be distributed across one or more paths.
|
1402
|
+
|
1403
|
+
Notes
|
1404
|
+
-----
|
1405
|
+
This is useful when we would like to generate values for multiple input paths that
|
1406
|
+
have some interdependency, or when they must be generate together in one go.
|
1407
|
+
|
1408
|
+
Parameters
|
1409
|
+
----------
|
1410
|
+
paths:
|
1411
|
+
The paths to this multi-path sequence.
|
1412
|
+
values:
|
1413
|
+
The values in this multi-path sequence.
|
1414
|
+
nesting_order: int
|
1415
|
+
A nesting order for this multi-path sequence. Can be used to compose sequences
|
1416
|
+
together.
|
1417
|
+
label: str
|
1418
|
+
A label for this multi-path sequence.
|
1419
|
+
value_class_method: str
|
1420
|
+
Name of a method used to generate multi-path sequence values. Not normally used
|
1421
|
+
directly.
|
1422
|
+
"""
|
1423
|
+
|
1424
|
+
# TODO: add a `path_axis` argument with doc string like:
|
1425
|
+
# path_axis:
|
1426
|
+
# The axis (as in a Numpy axis) along `values` to which the different paths
|
1427
|
+
# correspond.
|
1428
|
+
|
1429
|
+
def __init__(
|
1430
|
+
self,
|
1431
|
+
paths: Sequence[str],
|
1432
|
+
values: NDArray | Sequence[Sequence] | None,
|
1433
|
+
nesting_order: int | float | None = None,
|
1434
|
+
label: str | int | None = None,
|
1435
|
+
value_class_method: str | None = None,
|
1436
|
+
):
|
1437
|
+
self.paths = list(paths)
|
1438
|
+
self.nesting_order = nesting_order
|
1439
|
+
self.label = label
|
1440
|
+
self.value_class_method = value_class_method
|
1441
|
+
|
1442
|
+
self._sequences: list[ValueSequence] | None = None
|
1443
|
+
self._values: NDArray | Sequence[Sequence] | None = None
|
1444
|
+
|
1445
|
+
if values is not None:
|
1446
|
+
if (len_paths := len(paths)) != (len_vals := len(values)):
|
1447
|
+
raise ValueError(
|
1448
|
+
f"The number of values ({len_vals}) must be equal to the number of "
|
1449
|
+
f"paths provided ({len_paths})."
|
1450
|
+
)
|
1451
|
+
self._values = values
|
1452
|
+
self._sequences = [
|
1453
|
+
self._app.ValueSequence(
|
1454
|
+
path=path,
|
1455
|
+
values=values[idx],
|
1456
|
+
label=label,
|
1457
|
+
nesting_order=nesting_order,
|
1458
|
+
value_class_method=value_class_method,
|
1459
|
+
)
|
1460
|
+
for idx, path in enumerate(paths)
|
1461
|
+
]
|
1462
|
+
|
1463
|
+
# assigned by `_move_to_sequence_list` (invoked by first init of parent
|
1464
|
+
# `ElementSet`), corresponds to the sequence indices with the element set's
|
1465
|
+
# sequence list:
|
1466
|
+
self._sequence_indices: Sequence[int] | None = None
|
1467
|
+
|
1468
|
+
self._element_set: ElementSet | None = None # assigned by parent `ElementSet`
|
1469
|
+
|
1470
|
+
self._values_method: str | None = None
|
1471
|
+
self._values_method_args: dict | None = None
|
1472
|
+
|
1473
|
+
def __repr__(self):
|
1474
|
+
|
1475
|
+
label_str = f"label={self.label!r}, " if self.label else ""
|
1476
|
+
val_cls_str = (
|
1477
|
+
f"value_class_method={self.value_class_method!r}, "
|
1478
|
+
if self.value_class_method
|
1479
|
+
else ""
|
1480
|
+
)
|
1481
|
+
return (
|
1482
|
+
f"{self.__class__.__name__}("
|
1483
|
+
f"paths={self.paths!r}, "
|
1484
|
+
f"{label_str}"
|
1485
|
+
f"nesting_order={self.nesting_order}, "
|
1486
|
+
f"{val_cls_str}"
|
1487
|
+
f"values={self.values}"
|
1488
|
+
f")"
|
1489
|
+
)
|
1490
|
+
|
1491
|
+
def __deepcopy__(self, memo: dict[int, Any]):
|
1492
|
+
kwargs = self.to_dict()
|
1493
|
+
kwargs["values"] = kwargs.pop("_values")
|
1494
|
+
|
1495
|
+
_sequences = kwargs.pop("_sequences", None)
|
1496
|
+
_sequence_indices = kwargs.pop("_sequence_indices", None)
|
1497
|
+
_values_method = kwargs.pop("_values_method", None)
|
1498
|
+
_values_method_args = kwargs.pop("_values_method_args", None)
|
1499
|
+
|
1500
|
+
obj = self.__class__(**copy.deepcopy(kwargs, memo))
|
1501
|
+
|
1502
|
+
obj._sequences = _sequences
|
1503
|
+
obj._sequence_indices = _sequence_indices
|
1504
|
+
obj._values_method = _values_method
|
1505
|
+
obj._values_method_args = _values_method_args
|
1506
|
+
|
1507
|
+
obj._element_set = self._element_set
|
1508
|
+
|
1509
|
+
return obj
|
1510
|
+
|
1511
|
+
@override
|
1512
|
+
def _postprocess_to_dict(self, d: dict[str, Any]) -> dict[str, Any]:
|
1513
|
+
dct = super()._postprocess_to_dict(d)
|
1514
|
+
del dct["_sequences"]
|
1515
|
+
return dct
|
1516
|
+
|
1517
|
+
@classmethod
|
1518
|
+
def _json_like_constructor(cls, json_like):
|
1519
|
+
"""Invoked by `JSONLike.from_json_like` instead of `__init__`."""
|
1520
|
+
|
1521
|
+
# pop the keys we don't accept in `__init__`, and then assign after `__init__`:
|
1522
|
+
_sequence_indices = json_like.pop("_sequence_indices", None)
|
1523
|
+
|
1524
|
+
_values_method = json_like.pop("_values_method", None)
|
1525
|
+
_values_method_args = json_like.pop("_values_method_args", None)
|
1526
|
+
if "_values" in json_like:
|
1527
|
+
json_like["values"] = json_like.pop("_values")
|
1528
|
+
|
1529
|
+
obj = cls(**json_like)
|
1530
|
+
obj._sequence_indices = _sequence_indices
|
1531
|
+
obj._values_method = _values_method
|
1532
|
+
obj._values_method_args = _values_method_args
|
1533
|
+
return obj
|
1534
|
+
|
1535
|
+
@property
|
1536
|
+
def sequence_indices(self) -> Sequence[int] | None:
|
1537
|
+
"""
|
1538
|
+
The range indices (start and stop) to the parent element set's sequences list that
|
1539
|
+
correspond to the `ValueSequence`s generated by this multi-path sequence, if this
|
1540
|
+
object is bound to a parent element set.
|
1541
|
+
"""
|
1542
|
+
return self._sequence_indices
|
1543
|
+
|
1544
|
+
@property
|
1545
|
+
def sequences(self) -> Sequence[ValueSequence]:
|
1546
|
+
"""
|
1547
|
+
The child value sequences, one for each path.
|
1548
|
+
"""
|
1549
|
+
if self._sequence_indices:
|
1550
|
+
# they are stored in the parent `ElementSet`
|
1551
|
+
assert self._element_set
|
1552
|
+
return self._element_set.sequences[slice(*self._sequence_indices)]
|
1553
|
+
else:
|
1554
|
+
# not yet bound to a parent `ElementSet`
|
1555
|
+
assert self._sequences
|
1556
|
+
return self._sequences
|
1557
|
+
|
1558
|
+
@property
|
1559
|
+
def values(self) -> list[Sequence[Any]]:
|
1560
|
+
values = []
|
1561
|
+
for seq_i in self.sequences:
|
1562
|
+
assert seq_i.values
|
1563
|
+
values.append(seq_i.values)
|
1564
|
+
return values
|
1565
|
+
|
1566
|
+
def _move_to_sequence_list(self, sequences: list[ValueSequence]) -> None:
|
1567
|
+
"""
|
1568
|
+
Move the individual value sequences to an external list of value sequences (i.e.,
|
1569
|
+
the parent `ElementSet`'s), and update the `sequence_indices` attribute so we can
|
1570
|
+
retrieve the sequences from that list at will.
|
1571
|
+
"""
|
1572
|
+
len_ours = len(self.sequences)
|
1573
|
+
len_ext = len(sequences)
|
1574
|
+
sequences.extend(self.sequences)
|
1575
|
+
|
1576
|
+
# child sequences are now stored externally, and values retrieved via those:
|
1577
|
+
self._sequences = None
|
1578
|
+
self._values = None
|
1579
|
+
self._sequence_indices = [len_ext, len_ext + len_ours]
|
1580
|
+
|
1581
|
+
@classmethod
|
1582
|
+
def _values_from_latin_hypercube(
|
1583
|
+
cls,
|
1584
|
+
paths: Sequence[str],
|
1585
|
+
num_samples: int,
|
1586
|
+
*,
|
1587
|
+
scramble: bool = True,
|
1588
|
+
strength: int = 1,
|
1589
|
+
optimization: Literal["random-cd", "lloyd"] | None = None,
|
1590
|
+
rng=None,
|
1591
|
+
) -> NDArray:
|
1592
|
+
|
1593
|
+
num_paths = len(paths)
|
1594
|
+
kwargs = dict(
|
1595
|
+
d=num_paths,
|
1596
|
+
scramble=scramble,
|
1597
|
+
strength=strength,
|
1598
|
+
optimization=optimization,
|
1599
|
+
rng=rng,
|
1600
|
+
)
|
1601
|
+
try:
|
1602
|
+
sampler = LatinHypercube(**kwargs)
|
1603
|
+
except TypeError:
|
1604
|
+
# `rng` was previously (<1.15.0) `seed`:
|
1605
|
+
kwargs["seed"] = kwargs.pop("rng")
|
1606
|
+
sampler = LatinHypercube(**kwargs)
|
1607
|
+
return sampler.random(n=num_samples).T
|
1608
|
+
|
1609
|
+
@classmethod
|
1610
|
+
def from_latin_hypercube(
|
1611
|
+
cls,
|
1612
|
+
paths: Sequence[str],
|
1613
|
+
num_samples: int,
|
1614
|
+
*,
|
1615
|
+
scramble: bool = True,
|
1616
|
+
strength: int = 1,
|
1617
|
+
optimization: Literal["random-cd", "lloyd"] | None = None,
|
1618
|
+
rng=None,
|
1619
|
+
nesting_order: int | float | None = None,
|
1620
|
+
label: str | int | None = None,
|
1621
|
+
) -> Self:
|
1622
|
+
"""
|
1623
|
+
Generate values from SciPy's latin hypercube sampler: :class:`scipy.stats.qmc.LatinHypercube`.
|
1624
|
+
"""
|
1625
|
+
kwargs = {
|
1626
|
+
"paths": paths,
|
1627
|
+
"num_samples": num_samples,
|
1628
|
+
"scramble": scramble,
|
1629
|
+
"strength": strength,
|
1630
|
+
"optimization": optimization,
|
1631
|
+
"rng": rng,
|
1632
|
+
}
|
1633
|
+
values = cls._values_from_latin_hypercube(**kwargs)
|
1634
|
+
assert values is not None
|
1635
|
+
obj = cls(
|
1636
|
+
paths=paths,
|
1637
|
+
values=values,
|
1638
|
+
nesting_order=nesting_order,
|
1639
|
+
label=label,
|
1640
|
+
)
|
1641
|
+
obj._values_method = "from_latin_hypercube"
|
1642
|
+
obj._values_method_args = kwargs
|
1643
|
+
return obj
|
1644
|
+
|
1645
|
+
|
1260
1646
|
@dataclass
|
1261
1647
|
class AbstractInputValue(JSONLike):
|
1262
1648
|
"""Class to represent all sequence-able inputs to a task."""
|
1263
1649
|
|
1264
|
-
_workflow = None
|
1650
|
+
_workflow: Workflow | None = None
|
1651
|
+
_element_set: ElementSet | None = None
|
1652
|
+
_schema_input: SchemaInput | None = None
|
1653
|
+
_value: Any | None = None
|
1654
|
+
_value_group_idx: int | list[int] | None = None
|
1265
1655
|
|
1266
|
-
def __repr__(self):
|
1656
|
+
def __repr__(self) -> str:
|
1267
1657
|
try:
|
1268
1658
|
value_str = f", value={self.value}"
|
1269
1659
|
except WorkflowParameterMissingError:
|
@@ -1276,25 +1666,26 @@ class AbstractInputValue(JSONLike):
|
|
1276
1666
|
f")"
|
1277
1667
|
)
|
1278
1668
|
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
del out["_schema_input"]
|
1669
|
+
@override
|
1670
|
+
def _postprocess_to_dict(self, d: dict[str, Any]) -> dict[str, Any]:
|
1671
|
+
out = super()._postprocess_to_dict(d)
|
1672
|
+
out.pop("_workflow", None)
|
1673
|
+
out.pop("_schema_input", None)
|
1285
1674
|
return out
|
1286
1675
|
|
1287
1676
|
def make_persistent(
|
1288
|
-
self, workflow:
|
1289
|
-
) ->
|
1677
|
+
self, workflow: Workflow, source: ParamSource
|
1678
|
+
) -> tuple[str, list[int | list[int]], bool]:
|
1290
1679
|
"""Save value to a persistent workflow.
|
1291
1680
|
|
1292
1681
|
Returns
|
1293
1682
|
-------
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1683
|
+
str
|
1684
|
+
Normalised path for this task input.
|
1685
|
+
list[int | list[int]]
|
1686
|
+
The index of the parameter data Zarr group where the data is stored.
|
1687
|
+
bool
|
1688
|
+
Whether this is newly persistent.
|
1298
1689
|
"""
|
1299
1690
|
|
1300
1691
|
if self._value_group_idx is not None:
|
@@ -1315,30 +1706,34 @@ class AbstractInputValue(JSONLike):
|
|
1315
1706
|
return (self.normalised_path, [data_ref], is_new)
|
1316
1707
|
|
1317
1708
|
@property
|
1318
|
-
def
|
1709
|
+
def normalised_path(self) -> str:
|
1710
|
+
"""
|
1711
|
+
The normalised path, if known.
|
1712
|
+
"""
|
1713
|
+
raise NotImplementedError
|
1714
|
+
|
1715
|
+
@property
|
1716
|
+
def workflow(self) -> Workflow | None:
|
1319
1717
|
"""
|
1320
1718
|
The workflow containing this input value.
|
1321
1719
|
"""
|
1322
1720
|
if self._workflow:
|
1323
1721
|
return self._workflow
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1722
|
+
if self._element_set:
|
1723
|
+
if w_tmpl := self._element_set.task_template.workflow_template:
|
1724
|
+
return w_tmpl.workflow
|
1725
|
+
if self._schema_input:
|
1726
|
+
if t_tmpl := self._schema_input.task_schema.task_template:
|
1727
|
+
if w_tmpl := t_tmpl.workflow_template:
|
1728
|
+
return w_tmpl.workflow
|
1729
|
+
return None
|
1328
1730
|
|
1329
1731
|
@property
|
1330
|
-
def value(self):
|
1732
|
+
def value(self) -> Any:
|
1331
1733
|
"""
|
1332
1734
|
The value itself.
|
1333
1735
|
"""
|
1334
|
-
|
1335
|
-
val = self.workflow.get_parameter_data(self._value_group_idx)
|
1336
|
-
if self._value_is_obj and self.parameter._value_class:
|
1337
|
-
val = self.parameter._value_class(**val)
|
1338
|
-
else:
|
1339
|
-
val = self._value
|
1340
|
-
|
1341
|
-
return val
|
1736
|
+
return self._value
|
1342
1737
|
|
1343
1738
|
|
1344
1739
|
@dataclass
|
@@ -1348,13 +1743,16 @@ class ValuePerturbation(AbstractInputValue):
|
|
1348
1743
|
"""
|
1349
1744
|
|
1350
1745
|
#: The name of this perturbation.
|
1351
|
-
name: str
|
1746
|
+
name: str = ""
|
1352
1747
|
#: The path to the value(s) to perturb.
|
1353
|
-
path:
|
1748
|
+
path: Sequence[str | int | float] | None = None
|
1354
1749
|
#: The multiplicative factor to apply.
|
1355
|
-
multiplicative_factor:
|
1750
|
+
multiplicative_factor: Numeric | None = 1
|
1356
1751
|
#: The additive factor to apply.
|
1357
|
-
additive_factor:
|
1752
|
+
additive_factor: Numeric | None = 0
|
1753
|
+
|
1754
|
+
def __post_init__(self):
|
1755
|
+
assert self.name
|
1358
1756
|
|
1359
1757
|
@classmethod
|
1360
1758
|
def from_spec(cls, spec):
|
@@ -1364,6 +1762,7 @@ class ValuePerturbation(AbstractInputValue):
|
|
1364
1762
|
return cls(**spec)
|
1365
1763
|
|
1366
1764
|
|
1765
|
+
@hydrate
|
1367
1766
|
class InputValue(AbstractInputValue):
|
1368
1767
|
"""
|
1369
1768
|
An input value to a task.
|
@@ -1386,7 +1785,7 @@ class InputValue(AbstractInputValue):
|
|
1386
1785
|
|
1387
1786
|
"""
|
1388
1787
|
|
1389
|
-
_child_objects = (
|
1788
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
1390
1789
|
ChildObjectSpec(
|
1391
1790
|
name="parameter",
|
1392
1791
|
class_name="Parameter",
|
@@ -1397,45 +1796,40 @@ class InputValue(AbstractInputValue):
|
|
1397
1796
|
|
1398
1797
|
def __init__(
|
1399
1798
|
self,
|
1400
|
-
parameter:
|
1401
|
-
value:
|
1402
|
-
label:
|
1403
|
-
value_class_method:
|
1404
|
-
path:
|
1405
|
-
|
1799
|
+
parameter: Parameter | SchemaInput | str,
|
1800
|
+
value: Any | None = None,
|
1801
|
+
label: str | int | None = None,
|
1802
|
+
value_class_method: str | None = None,
|
1803
|
+
path: str | None = None,
|
1804
|
+
_check_obj: bool = True,
|
1406
1805
|
):
|
1806
|
+
super().__init__()
|
1407
1807
|
if isinstance(parameter, str):
|
1408
1808
|
try:
|
1409
|
-
|
1809
|
+
#: Parameter whose value is to be specified.
|
1810
|
+
self.parameter = self._app.parameters.get(parameter)
|
1410
1811
|
except ValueError:
|
1411
|
-
parameter = self.
|
1812
|
+
self.parameter = self._app.Parameter(parameter)
|
1412
1813
|
elif isinstance(parameter, SchemaInput):
|
1413
|
-
parameter = parameter.parameter
|
1814
|
+
self.parameter = parameter.parameter
|
1815
|
+
else:
|
1816
|
+
self.parameter = parameter
|
1414
1817
|
|
1415
|
-
#: Parameter whose value is to be specified.
|
1416
|
-
self.parameter = parameter
|
1417
1818
|
#: Identifier to be used where the associated `SchemaInput` accepts multiple
|
1418
1819
|
#: parameters of the specified type.
|
1419
1820
|
self.label = str(label) if label is not None else ""
|
1420
1821
|
#: Dot-delimited path within the parameter's nested data structure for which
|
1421
1822
|
#: `value` should be set.
|
1422
|
-
self.path = (path.strip(".") if path else None
|
1823
|
+
self.path = (path.strip(".") or None) if path else None
|
1423
1824
|
#: A class method that can be invoked with the `value` attribute as keyword
|
1424
1825
|
#: arguments.
|
1425
1826
|
self.value_class_method = value_class_method
|
1426
|
-
self._value = _process_demo_data_strings(self.
|
1427
|
-
|
1428
|
-
self._value_group_idx = None # assigned by method make_persistent
|
1429
|
-
self._element_set = None # assigned by parent ElementSet (if belonging)
|
1430
|
-
|
1431
|
-
# assigned by parent SchemaInput (if this object is a default value of a
|
1432
|
-
# SchemaInput):
|
1433
|
-
self._schema_input = None
|
1827
|
+
self._value = _process_demo_data_strings(self._app, value)
|
1434
1828
|
|
1435
1829
|
# record if a ParameterValue sub-class is passed for value, which allows us
|
1436
1830
|
# to re-init the object on `.value`:
|
1437
1831
|
self._value_is_obj = isinstance(value, ParameterValue)
|
1438
|
-
if
|
1832
|
+
if _check_obj:
|
1439
1833
|
self._check_dict_value_if_object()
|
1440
1834
|
|
1441
1835
|
def _check_dict_value_if_object(self):
|
@@ -1459,13 +1853,13 @@ class InputValue(AbstractInputValue):
|
|
1459
1853
|
f"dict."
|
1460
1854
|
)
|
1461
1855
|
|
1462
|
-
def __deepcopy__(self, memo):
|
1856
|
+
def __deepcopy__(self, memo: dict[int, Any]) -> Self:
|
1463
1857
|
kwargs = self.to_dict()
|
1464
1858
|
_value = kwargs.pop("_value")
|
1465
1859
|
kwargs.pop("_schema_input", None)
|
1466
1860
|
_value_group_idx = kwargs.pop("_value_group_idx")
|
1467
1861
|
_value_is_obj = kwargs.pop("_value_is_obj")
|
1468
|
-
obj = self.__class__(**copy.deepcopy(kwargs, memo),
|
1862
|
+
obj = self.__class__(**copy.deepcopy(kwargs, memo), _check_obj=False)
|
1469
1863
|
obj._value = _value
|
1470
1864
|
obj._value_group_idx = _value_group_idx
|
1471
1865
|
obj._value_is_obj = _value_is_obj
|
@@ -1473,7 +1867,7 @@ class InputValue(AbstractInputValue):
|
|
1473
1867
|
obj._schema_input = self._schema_input
|
1474
1868
|
return obj
|
1475
1869
|
|
1476
|
-
def __repr__(self):
|
1870
|
+
def __repr__(self) -> str:
|
1477
1871
|
val_grp_idx = ""
|
1478
1872
|
if self._value_group_idx is not None:
|
1479
1873
|
val_grp_idx = f", value_group_idx={self._value_group_idx}"
|
@@ -1500,12 +1894,10 @@ class InputValue(AbstractInputValue):
|
|
1500
1894
|
f")"
|
1501
1895
|
)
|
1502
1896
|
|
1503
|
-
def __eq__(self, other) -> bool:
|
1897
|
+
def __eq__(self, other: Any) -> bool:
|
1504
1898
|
if not isinstance(other, self.__class__):
|
1505
1899
|
return False
|
1506
|
-
|
1507
|
-
return True
|
1508
|
-
return False
|
1900
|
+
return self.to_dict() == other.to_dict()
|
1509
1901
|
|
1510
1902
|
@classmethod
|
1511
1903
|
def _json_like_constructor(cls, json_like):
|
@@ -1516,14 +1908,14 @@ class InputValue(AbstractInputValue):
|
|
1516
1908
|
if "_value" in json_like:
|
1517
1909
|
json_like["value"] = json_like.pop("_value")
|
1518
1910
|
|
1519
|
-
obj = cls(**json_like,
|
1911
|
+
obj = cls(**json_like, _check_obj=False)
|
1520
1912
|
obj._value_group_idx = _value_group_idx
|
1521
1913
|
obj._value_is_obj = _value_is_obj
|
1522
1914
|
obj._check_dict_value_if_object()
|
1523
1915
|
return obj
|
1524
1916
|
|
1525
1917
|
@property
|
1526
|
-
def labelled_type(self):
|
1918
|
+
def labelled_type(self) -> str:
|
1527
1919
|
"""
|
1528
1920
|
The labelled type of this input value.
|
1529
1921
|
"""
|
@@ -1531,22 +1923,25 @@ class InputValue(AbstractInputValue):
|
|
1531
1923
|
return f"{self.parameter.typ}{label}"
|
1532
1924
|
|
1533
1925
|
@property
|
1534
|
-
def normalised_inputs_path(self):
|
1926
|
+
def normalised_inputs_path(self) -> str:
|
1535
1927
|
"""
|
1536
1928
|
The normalised input path without the ``inputs.`` prefix.
|
1537
1929
|
"""
|
1538
1930
|
return f"{self.labelled_type}{f'.{self.path}' if self.path else ''}"
|
1539
1931
|
|
1540
1932
|
@property
|
1541
|
-
def normalised_path(self):
|
1933
|
+
def normalised_path(self) -> str:
|
1542
1934
|
"""
|
1543
1935
|
The full normalised input path.
|
1544
1936
|
"""
|
1545
1937
|
return f"inputs.{self.normalised_inputs_path}"
|
1546
1938
|
|
1547
|
-
def make_persistent(
|
1939
|
+
def make_persistent(
|
1940
|
+
self, workflow: Workflow, source: ParamSource
|
1941
|
+
) -> tuple[str, list[int | list[int]], bool]:
|
1548
1942
|
source = copy.deepcopy(source)
|
1549
|
-
|
1943
|
+
if self.value_class_method is not None:
|
1944
|
+
source["value_class_method"] = self.value_class_method
|
1550
1945
|
return super().make_persistent(workflow, source)
|
1551
1946
|
|
1552
1947
|
@classmethod
|
@@ -1558,25 +1953,37 @@ class InputValue(AbstractInputValue):
|
|
1558
1953
|
json_like["label"] = label
|
1559
1954
|
|
1560
1955
|
if "::" in json_like["parameter"]:
|
1956
|
+
# double-colon syntax indicates a `ParameterValue`-subclass class method
|
1957
|
+
# of the specified name should be used to construct the values:
|
1561
1958
|
param, cls_method = json_like["parameter"].split("::")
|
1562
1959
|
json_like["parameter"] = param
|
1563
1960
|
json_like["value_class_method"] = cls_method
|
1564
1961
|
|
1565
1962
|
if "path" not in json_like:
|
1566
|
-
|
1567
|
-
|
1568
|
-
json_like["
|
1569
|
-
|
1570
|
-
|
1963
|
+
# in the case this value corresponds to some sub-part of the parameter's
|
1964
|
+
# nested data structure:
|
1965
|
+
param, *path = json_like["parameter"].split(".")
|
1966
|
+
json_like["parameter"] = param
|
1967
|
+
json_like["path"] = ".".join(path)
|
1571
1968
|
|
1572
|
-
return
|
1969
|
+
return super().from_json_like(json_like, shared_data)
|
1573
1970
|
|
1574
1971
|
@property
|
1575
|
-
def is_sub_value(self):
|
1972
|
+
def is_sub_value(self) -> bool:
|
1576
1973
|
"""True if the value is for a sub part of the parameter (i.e. if `path` is set).
|
1577
1974
|
Sub-values are not added to the base parameter data, but are interpreted as
|
1578
1975
|
single-value sequences."""
|
1579
|
-
return
|
1976
|
+
return bool(self.path)
|
1977
|
+
|
1978
|
+
@property
|
1979
|
+
def value(self) -> Any:
|
1980
|
+
if self._value_group_idx is not None and self.workflow:
|
1981
|
+
val = self.workflow.get_parameter_data(cast("int", self._value_group_idx))
|
1982
|
+
if self._value_is_obj and self.parameter._value_class:
|
1983
|
+
return self.parameter._value_class(**val)
|
1984
|
+
return val
|
1985
|
+
else:
|
1986
|
+
return self._value
|
1580
1987
|
|
1581
1988
|
|
1582
1989
|
class ResourceSpec(JSONLike):
|
@@ -1611,6 +2018,12 @@ class ResourceSpec(JSONLike):
|
|
1611
2018
|
Whether to use array jobs.
|
1612
2019
|
max_array_items: int
|
1613
2020
|
If using array jobs, up to how many items should be in the job array.
|
2021
|
+
write_app_logs: bool
|
2022
|
+
Whether an app log file should be written.
|
2023
|
+
combine_jobscript_std: bool
|
2024
|
+
Whether jobscript standard output and error streams should be combined.
|
2025
|
+
combine_scripts: bool
|
2026
|
+
Whether Python scripts should be combined.
|
1614
2027
|
time_limit: str
|
1615
2028
|
How long to run for.
|
1616
2029
|
scheduler_args: dict[str, Any]
|
@@ -1621,6 +2034,13 @@ class ResourceSpec(JSONLike):
|
|
1621
2034
|
Which OS to use.
|
1622
2035
|
environments: dict
|
1623
2036
|
Which execution environments to use.
|
2037
|
+
resources_id: int
|
2038
|
+
An arbitrary integer that can be used to force multiple jobscripts.
|
2039
|
+
skip_downstream_on_failure: bool
|
2040
|
+
Whether to skip downstream dependents on failure.
|
2041
|
+
allow_failed_dependencies: int | float | bool | None
|
2042
|
+
The failure tolerance with respect to dependencies, specified as a number or
|
2043
|
+
proportion.
|
1624
2044
|
SGE_parallel_env: str
|
1625
2045
|
Which SGE parallel environment to request.
|
1626
2046
|
SLURM_partition: str
|
@@ -1636,7 +2056,7 @@ class ResourceSpec(JSONLike):
|
|
1636
2056
|
"""
|
1637
2057
|
|
1638
2058
|
#: The names of parameters that may be used when making an instance of this class.
|
1639
|
-
ALLOWED_PARAMETERS = {
|
2059
|
+
ALLOWED_PARAMETERS: ClassVar[set[str]] = {
|
1640
2060
|
"scratch",
|
1641
2061
|
"parallel_mode",
|
1642
2062
|
"num_cores",
|
@@ -1647,11 +2067,16 @@ class ResourceSpec(JSONLike):
|
|
1647
2067
|
"shell",
|
1648
2068
|
"use_job_array",
|
1649
2069
|
"max_array_items",
|
2070
|
+
"write_app_logs",
|
2071
|
+
"combine_jobscript_std",
|
2072
|
+
"combine_scripts",
|
1650
2073
|
"time_limit",
|
1651
2074
|
"scheduler_args",
|
1652
2075
|
"shell_args",
|
1653
2076
|
"os_name",
|
1654
2077
|
"environments",
|
2078
|
+
"resources_id",
|
2079
|
+
"skip_downstream_on_failure",
|
1655
2080
|
"SGE_parallel_env",
|
1656
2081
|
"SLURM_partition",
|
1657
2082
|
"SLURM_num_tasks",
|
@@ -1660,51 +2085,76 @@ class ResourceSpec(JSONLike):
|
|
1660
2085
|
"SLURM_num_cpus_per_task",
|
1661
2086
|
}
|
1662
2087
|
|
1663
|
-
_resource_list = None
|
2088
|
+
_resource_list: ResourceList | None = None
|
1664
2089
|
|
1665
|
-
_child_objects = (
|
2090
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
1666
2091
|
ChildObjectSpec(
|
1667
2092
|
name="scope",
|
1668
2093
|
class_name="ActionScope",
|
1669
2094
|
),
|
1670
2095
|
)
|
1671
2096
|
|
2097
|
+
@staticmethod
|
2098
|
+
def __quoted(values: Iterable):
|
2099
|
+
return ", ".join(f'"{item}"' for item in values)
|
2100
|
+
|
2101
|
+
@classmethod
|
2102
|
+
def _allowed_params_quoted(cls) -> str:
|
2103
|
+
"""
|
2104
|
+
The string version of the list of allowed parameters.
|
2105
|
+
"""
|
2106
|
+
return cls.__quoted(cls.ALLOWED_PARAMETERS)
|
2107
|
+
|
2108
|
+
@staticmethod
|
2109
|
+
def __parse_thing(
|
2110
|
+
typ: type[ActionScope], val: ActionScope | str | None
|
2111
|
+
) -> ActionScope | None:
|
2112
|
+
if isinstance(val, typ):
|
2113
|
+
return val
|
2114
|
+
elif val is None:
|
2115
|
+
return typ.any()
|
2116
|
+
else:
|
2117
|
+
return typ.from_json_like(cast("str", val))
|
2118
|
+
|
1672
2119
|
def __init__(
|
1673
2120
|
self,
|
1674
|
-
scope:
|
1675
|
-
scratch:
|
1676
|
-
parallel_mode:
|
1677
|
-
num_cores:
|
1678
|
-
num_cores_per_node:
|
1679
|
-
num_threads:
|
1680
|
-
num_nodes:
|
1681
|
-
scheduler:
|
1682
|
-
shell:
|
1683
|
-
use_job_array:
|
1684
|
-
max_array_items:
|
1685
|
-
|
1686
|
-
|
1687
|
-
|
1688
|
-
|
1689
|
-
|
1690
|
-
|
1691
|
-
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1695
|
-
|
2121
|
+
scope: ActionScope | str | None = None,
|
2122
|
+
scratch: str | None = None,
|
2123
|
+
parallel_mode: str | ParallelMode | None = None,
|
2124
|
+
num_cores: int | None = None,
|
2125
|
+
num_cores_per_node: int | None = None,
|
2126
|
+
num_threads: int | None = None,
|
2127
|
+
num_nodes: int | None = None,
|
2128
|
+
scheduler: str | None = None,
|
2129
|
+
shell: str | None = None,
|
2130
|
+
use_job_array: bool | None = None,
|
2131
|
+
max_array_items: int | None = None,
|
2132
|
+
write_app_logs: bool | None = None,
|
2133
|
+
combine_jobscript_std: bool | None = None,
|
2134
|
+
combine_scripts: bool | None = None,
|
2135
|
+
time_limit: str | timedelta | None = None,
|
2136
|
+
scheduler_args: dict[str, Any] | None = None,
|
2137
|
+
shell_args: dict[str, Any] | None = None,
|
2138
|
+
os_name: str | None = None,
|
2139
|
+
environments: Mapping[str, Mapping[str, Any]] | None = None,
|
2140
|
+
resources_id: int | None = None,
|
2141
|
+
skip_downstream_on_failure: bool | None = None,
|
2142
|
+
SGE_parallel_env: str | None = None,
|
2143
|
+
SLURM_partition: str | None = None,
|
2144
|
+
SLURM_num_tasks: str | None = None,
|
2145
|
+
SLURM_num_tasks_per_node: str | None = None,
|
2146
|
+
SLURM_num_nodes: str | None = None,
|
2147
|
+
SLURM_num_cpus_per_task: str | None = None,
|
1696
2148
|
):
|
1697
2149
|
#: Which scope does this apply to.
|
1698
|
-
self.scope =
|
1699
|
-
if not isinstance(self.scope, self.app.ActionScope):
|
1700
|
-
self.scope = self.app.ActionScope.from_json_like(self.scope)
|
2150
|
+
self.scope = self.__parse_thing(self._app.ActionScope, scope)
|
1701
2151
|
|
1702
2152
|
if isinstance(time_limit, timedelta):
|
1703
2153
|
time_limit = timedelta_format(time_limit)
|
1704
2154
|
|
1705
2155
|
# assigned by `make_persistent`
|
1706
|
-
self._workflow = None
|
1707
|
-
self._value_group_idx = None
|
2156
|
+
self._workflow: Workflow | None = None
|
2157
|
+
self._value_group_idx: int | list[int] | None = None
|
1708
2158
|
|
1709
2159
|
# user-specified resource parameters:
|
1710
2160
|
self._scratch = scratch
|
@@ -1717,8 +2167,13 @@ class ResourceSpec(JSONLike):
|
|
1717
2167
|
self._shell = self._process_string(shell)
|
1718
2168
|
self._os_name = self._process_string(os_name)
|
1719
2169
|
self._environments = environments
|
2170
|
+
self._resources_id = resources_id
|
2171
|
+
self._skip_downstream_on_failure = skip_downstream_on_failure
|
1720
2172
|
self._use_job_array = use_job_array
|
1721
2173
|
self._max_array_items = max_array_items
|
2174
|
+
self._write_app_logs = write_app_logs
|
2175
|
+
self._combine_jobscript_std = combine_jobscript_std
|
2176
|
+
self._combine_scripts = combine_scripts
|
1722
2177
|
self._time_limit = time_limit
|
1723
2178
|
self._scheduler_args = scheduler_args
|
1724
2179
|
self._shell_args = shell_args
|
@@ -1733,7 +2188,7 @@ class ResourceSpec(JSONLike):
|
|
1733
2188
|
self._SLURM_num_nodes = SLURM_num_nodes
|
1734
2189
|
self._SLURM_num_cpus_per_task = SLURM_num_cpus_per_task
|
1735
2190
|
|
1736
|
-
def __deepcopy__(self, memo):
|
2191
|
+
def __deepcopy__(self, memo: dict[int, Any]) -> Self:
|
1737
2192
|
kwargs = copy.deepcopy(self.to_dict(), memo)
|
1738
2193
|
_value_group_idx = kwargs.pop("value_group_idx", None)
|
1739
2194
|
obj = self.__class__(**kwargs)
|
@@ -1743,65 +2198,60 @@ class ResourceSpec(JSONLike):
|
|
1743
2198
|
|
1744
2199
|
def __repr__(self):
|
1745
2200
|
param_strs = ""
|
1746
|
-
for
|
1747
|
-
i_str = ""
|
2201
|
+
for param in self.ALLOWED_PARAMETERS:
|
1748
2202
|
try:
|
1749
|
-
i_val = getattr(self,
|
2203
|
+
i_val = getattr(self, param)
|
1750
2204
|
except WorkflowParameterMissingError:
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
i_str = f", {i}={i_val!r}"
|
1755
|
-
|
1756
|
-
param_strs += i_str
|
2205
|
+
continue
|
2206
|
+
if i_val is not None:
|
2207
|
+
param_strs += f", {param}={i_val!r}"
|
1757
2208
|
|
1758
2209
|
return f"{self.__class__.__name__}(scope={self.scope}{param_strs})"
|
1759
2210
|
|
1760
|
-
def __eq__(self, other) -> bool:
|
2211
|
+
def __eq__(self, other: Any) -> bool:
|
1761
2212
|
if not isinstance(other, self.__class__):
|
1762
2213
|
return False
|
1763
|
-
|
1764
|
-
return True
|
1765
|
-
return False
|
2214
|
+
return self.to_dict() == other.to_dict()
|
1766
2215
|
|
1767
2216
|
@classmethod
|
1768
|
-
def _json_like_constructor(cls, json_like):
|
2217
|
+
def _json_like_constructor(cls, json_like) -> Self:
|
1769
2218
|
"""Invoked by `JSONLike.from_json_like` instead of `__init__`."""
|
1770
2219
|
|
1771
2220
|
_value_group_idx = json_like.pop("value_group_idx", None)
|
1772
2221
|
try:
|
1773
2222
|
obj = cls(**json_like)
|
1774
2223
|
except TypeError:
|
1775
|
-
given_keys = set(k for k in json_like
|
1776
|
-
bad_keys = given_keys - cls.ALLOWED_PARAMETERS
|
1777
|
-
|
1778
|
-
allowed_keys_str = ", ".join(f'"{i}"' for i in cls.ALLOWED_PARAMETERS)
|
2224
|
+
given_keys = set(k for k in json_like if k != "scope")
|
2225
|
+
bad_keys = cls.__quoted(given_keys - cls.ALLOWED_PARAMETERS)
|
2226
|
+
good_keys = cls._allowed_params_quoted()
|
1779
2227
|
raise UnknownResourceSpecItemError(
|
1780
|
-
f"The following resource item names are unknown: {
|
1781
|
-
f"resource item names are: {
|
2228
|
+
f"The following resource item names are unknown: {bad_keys}. "
|
2229
|
+
f"Allowed resource item names are: {good_keys}."
|
1782
2230
|
)
|
1783
2231
|
obj._value_group_idx = _value_group_idx
|
1784
2232
|
|
1785
2233
|
return obj
|
1786
2234
|
|
1787
2235
|
@property
|
1788
|
-
def normalised_resources_path(self):
|
2236
|
+
def normalised_resources_path(self) -> str:
|
1789
2237
|
"""
|
1790
2238
|
Standard name of this resource spec.
|
1791
2239
|
"""
|
1792
|
-
|
2240
|
+
scope = self.scope
|
2241
|
+
assert scope is not None
|
2242
|
+
return scope.to_string()
|
1793
2243
|
|
1794
2244
|
@property
|
1795
|
-
def normalised_path(self):
|
2245
|
+
def normalised_path(self) -> str:
|
1796
2246
|
"""
|
1797
2247
|
Full name of this resource spec.
|
1798
2248
|
"""
|
1799
2249
|
return f"resources.{self.normalised_resources_path}"
|
1800
2250
|
|
1801
|
-
|
1802
|
-
|
1803
|
-
|
1804
|
-
|
2251
|
+
@override
|
2252
|
+
def _postprocess_to_dict(self, d: dict[str, Any]) -> dict[str, Any]:
|
2253
|
+
out = super()._postprocess_to_dict(d)
|
2254
|
+
out.pop("_workflow", None)
|
1805
2255
|
|
1806
2256
|
if self._value_group_idx is not None:
|
1807
2257
|
# only store pointer to persistent data:
|
@@ -1819,9 +2269,13 @@ class ResourceSpec(JSONLike):
|
|
1819
2269
|
out = {k: v for k, v in out.items() if v is not None}
|
1820
2270
|
return out
|
1821
2271
|
|
2272
|
+
@classmethod
|
2273
|
+
def __is_Workflow(cls, value) -> TypeIs[Workflow]:
|
2274
|
+
return isinstance(value, cls._app.Workflow)
|
2275
|
+
|
1822
2276
|
def make_persistent(
|
1823
|
-
self, workflow:
|
1824
|
-
) ->
|
2277
|
+
self, workflow: ResourcePersistingWorkflow, source: ParamSource
|
2278
|
+
) -> tuple[str, list[int | list[int]], bool]:
|
1825
2279
|
"""Save to a persistent workflow.
|
1826
2280
|
|
1827
2281
|
Returns
|
@@ -1830,6 +2284,9 @@ class ResourceSpec(JSONLike):
|
|
1830
2284
|
contains the indices of the parameter data Zarr groups where the data is
|
1831
2285
|
stored.
|
1832
2286
|
|
2287
|
+
Note
|
2288
|
+
----
|
2289
|
+
May modify the internal state of this object.
|
1833
2290
|
"""
|
1834
2291
|
|
1835
2292
|
if self._value_group_idx is not None:
|
@@ -1845,7 +2302,8 @@ class ResourceSpec(JSONLike):
|
|
1845
2302
|
data_ref = workflow._add_parameter_data(self._get_members(), source=source)
|
1846
2303
|
is_new = True
|
1847
2304
|
self._value_group_idx = data_ref
|
1848
|
-
self.
|
2305
|
+
if self.__is_Workflow(workflow):
|
2306
|
+
self._workflow = workflow
|
1849
2307
|
|
1850
2308
|
self._num_cores = None
|
1851
2309
|
self._scratch = None
|
@@ -1853,11 +2311,16 @@ class ResourceSpec(JSONLike):
|
|
1853
2311
|
self._shell = None
|
1854
2312
|
self._use_job_array = None
|
1855
2313
|
self._max_array_items = None
|
2314
|
+
self._write_app_logs = None
|
2315
|
+
self._combine_jobscript_std = None
|
2316
|
+
self._combine_scripts = None
|
1856
2317
|
self._time_limit = None
|
1857
2318
|
self._scheduler_args = None
|
1858
2319
|
self._shell_args = None
|
1859
2320
|
self._os_name = None
|
1860
2321
|
self._environments = None
|
2322
|
+
self._resources_id = None
|
2323
|
+
self._skip_downstream_on_failure = None
|
1861
2324
|
|
1862
2325
|
return (self.normalised_path, [data_ref], is_new)
|
1863
2326
|
|
@@ -1868,18 +2331,18 @@ class ResourceSpec(JSONLike):
|
|
1868
2331
|
kwargs[name] = getattr(self, name)
|
1869
2332
|
return self.__class__(**kwargs)
|
1870
2333
|
|
1871
|
-
def _get_value(self, value_name=None):
|
1872
|
-
if self._value_group_idx is not None:
|
1873
|
-
val = self.workflow.get_parameter_data(self._value_group_idx)
|
2334
|
+
def _get_value(self, value_name: str | None = None):
|
2335
|
+
if self._value_group_idx is not None and self.workflow:
|
2336
|
+
val = self.workflow.get_parameter_data(cast("int", self._value_group_idx))
|
1874
2337
|
else:
|
1875
2338
|
val = self._get_members()
|
1876
|
-
if value_name:
|
1877
|
-
|
2339
|
+
if value_name is not None and val is not None:
|
2340
|
+
return val.get(value_name)
|
1878
2341
|
|
1879
2342
|
return val
|
1880
2343
|
|
1881
2344
|
@staticmethod
|
1882
|
-
def _process_string(value:
|
2345
|
+
def _process_string(value: str | None):
|
1883
2346
|
return value.lower().strip() if value else value
|
1884
2347
|
|
1885
2348
|
def _setter_persistent_check(self):
|
@@ -1889,7 +2352,7 @@ class ResourceSpec(JSONLike):
|
|
1889
2352
|
)
|
1890
2353
|
|
1891
2354
|
@property
|
1892
|
-
def scratch(self):
|
2355
|
+
def scratch(self) -> str | None:
|
1893
2356
|
"""
|
1894
2357
|
Which scratch space to use.
|
1895
2358
|
|
@@ -1900,164 +2363,182 @@ class ResourceSpec(JSONLike):
|
|
1900
2363
|
return self._get_value("scratch")
|
1901
2364
|
|
1902
2365
|
@property
|
1903
|
-
def parallel_mode(self):
|
2366
|
+
def parallel_mode(self) -> ParallelMode | None:
|
1904
2367
|
"""
|
1905
2368
|
Which parallel mode to use.
|
1906
2369
|
"""
|
1907
2370
|
return self._get_value("parallel_mode")
|
1908
2371
|
|
1909
2372
|
@property
|
1910
|
-
def num_cores(self):
|
2373
|
+
def num_cores(self) -> int | None:
|
1911
2374
|
"""
|
1912
2375
|
How many cores to request.
|
1913
2376
|
"""
|
1914
2377
|
return self._get_value("num_cores")
|
1915
2378
|
|
1916
2379
|
@property
|
1917
|
-
def num_cores_per_node(self):
|
2380
|
+
def num_cores_per_node(self) -> int | None:
|
1918
2381
|
"""
|
1919
2382
|
How many cores per compute node to request.
|
1920
2383
|
"""
|
1921
2384
|
return self._get_value("num_cores_per_node")
|
1922
2385
|
|
1923
2386
|
@property
|
1924
|
-
def num_nodes(self):
|
2387
|
+
def num_nodes(self) -> int | None:
|
1925
2388
|
"""
|
1926
2389
|
How many compute nodes to request.
|
1927
2390
|
"""
|
1928
2391
|
return self._get_value("num_nodes")
|
1929
2392
|
|
1930
2393
|
@property
|
1931
|
-
def num_threads(self):
|
2394
|
+
def num_threads(self) -> int | None:
|
1932
2395
|
"""
|
1933
2396
|
How many threads to request.
|
1934
2397
|
"""
|
1935
2398
|
return self._get_value("num_threads")
|
1936
2399
|
|
1937
2400
|
@property
|
1938
|
-
def scheduler(self):
|
2401
|
+
def scheduler(self) -> str | None:
|
1939
2402
|
"""
|
1940
2403
|
Which scheduler to use.
|
1941
2404
|
"""
|
1942
2405
|
return self._get_value("scheduler")
|
1943
2406
|
|
1944
2407
|
@scheduler.setter
|
1945
|
-
def scheduler(self, value):
|
2408
|
+
def scheduler(self, value: str | None):
|
1946
2409
|
self._setter_persistent_check()
|
1947
|
-
|
1948
|
-
self._scheduler = value
|
2410
|
+
self._scheduler = self._process_string(value)
|
1949
2411
|
|
1950
2412
|
@property
|
1951
|
-
def shell(self):
|
2413
|
+
def shell(self) -> str | None:
|
1952
2414
|
"""
|
1953
2415
|
Which system shell to use.
|
1954
2416
|
"""
|
1955
2417
|
return self._get_value("shell")
|
1956
2418
|
|
1957
2419
|
@shell.setter
|
1958
|
-
def shell(self, value):
|
2420
|
+
def shell(self, value: str | None):
|
1959
2421
|
self._setter_persistent_check()
|
1960
|
-
|
1961
|
-
self._shell = value
|
2422
|
+
self._shell = self._process_string(value)
|
1962
2423
|
|
1963
2424
|
@property
|
1964
|
-
def use_job_array(self):
|
2425
|
+
def use_job_array(self) -> bool:
|
1965
2426
|
"""
|
1966
2427
|
Whether to use array jobs.
|
1967
2428
|
"""
|
1968
2429
|
return self._get_value("use_job_array")
|
1969
2430
|
|
1970
2431
|
@property
|
1971
|
-
def max_array_items(self):
|
2432
|
+
def max_array_items(self) -> int | None:
|
1972
2433
|
"""
|
1973
2434
|
If using array jobs, up to how many items should be in the job array.
|
1974
2435
|
"""
|
1975
2436
|
return self._get_value("max_array_items")
|
1976
2437
|
|
1977
2438
|
@property
|
1978
|
-
def
|
2439
|
+
def write_app_logs(self) -> bool:
|
2440
|
+
return self._get_value("write_app_logs")
|
2441
|
+
|
2442
|
+
@property
|
2443
|
+
def combine_jobscript_std(self) -> bool:
|
2444
|
+
return self._get_value("combine_jobscript_std")
|
2445
|
+
|
2446
|
+
@property
|
2447
|
+
def combine_scripts(self) -> bool:
|
2448
|
+
return self._get_value("combine_scripts")
|
2449
|
+
|
2450
|
+
@property
|
2451
|
+
def time_limit(self) -> str | None:
|
1979
2452
|
"""
|
1980
2453
|
How long to run for.
|
1981
2454
|
"""
|
1982
2455
|
return self._get_value("time_limit")
|
1983
2456
|
|
1984
2457
|
@property
|
1985
|
-
def scheduler_args(self):
|
2458
|
+
def scheduler_args(self) -> Mapping: # TODO: TypedDict
|
1986
2459
|
"""
|
1987
2460
|
Additional arguments to pass to the scheduler.
|
1988
2461
|
"""
|
1989
2462
|
return self._get_value("scheduler_args")
|
1990
2463
|
|
1991
2464
|
@property
|
1992
|
-
def shell_args(self):
|
2465
|
+
def shell_args(self) -> Mapping | None: # TODO: TypedDict
|
1993
2466
|
"""
|
1994
2467
|
Additional arguments to pass to the shell.
|
1995
2468
|
"""
|
1996
2469
|
return self._get_value("shell_args")
|
1997
2470
|
|
1998
2471
|
@property
|
1999
|
-
def os_name(self):
|
2472
|
+
def os_name(self) -> str:
|
2000
2473
|
"""
|
2001
2474
|
Which OS to use.
|
2002
2475
|
"""
|
2003
2476
|
return self._get_value("os_name")
|
2004
2477
|
|
2478
|
+
@os_name.setter
|
2479
|
+
def os_name(self, value: str):
|
2480
|
+
self._setter_persistent_check()
|
2481
|
+
self._os_name = self._process_string(value)
|
2482
|
+
|
2005
2483
|
@property
|
2006
|
-
def environments(self):
|
2484
|
+
def environments(self) -> Mapping | None: # TODO: TypedDict
|
2007
2485
|
"""
|
2008
2486
|
Which execution environments to use.
|
2009
2487
|
"""
|
2010
2488
|
return self._get_value("environments")
|
2011
2489
|
|
2012
2490
|
@property
|
2013
|
-
def
|
2491
|
+
def resources_id(self) -> int:
|
2492
|
+
return self._get_value("resources_id")
|
2493
|
+
|
2494
|
+
@property
|
2495
|
+
def skip_downstream_on_failure(self) -> bool:
|
2496
|
+
return self._get_value("skip_downstream_on_failure")
|
2497
|
+
|
2498
|
+
@property
|
2499
|
+
def SGE_parallel_env(self) -> str | None:
|
2014
2500
|
"""
|
2015
2501
|
Which SGE parallel environment to request.
|
2016
2502
|
"""
|
2017
2503
|
return self._get_value("SGE_parallel_env")
|
2018
2504
|
|
2019
2505
|
@property
|
2020
|
-
def SLURM_partition(self):
|
2506
|
+
def SLURM_partition(self) -> str | None:
|
2021
2507
|
"""
|
2022
2508
|
Which SLURM partition to request.
|
2023
2509
|
"""
|
2024
2510
|
return self._get_value("SLURM_partition")
|
2025
2511
|
|
2026
2512
|
@property
|
2027
|
-
def SLURM_num_tasks(self):
|
2513
|
+
def SLURM_num_tasks(self) -> int | None:
|
2028
2514
|
"""
|
2029
2515
|
How many SLURM tasks to request.
|
2030
2516
|
"""
|
2031
2517
|
return self._get_value("SLURM_num_tasks")
|
2032
2518
|
|
2033
2519
|
@property
|
2034
|
-
def SLURM_num_tasks_per_node(self):
|
2520
|
+
def SLURM_num_tasks_per_node(self) -> int | None:
|
2035
2521
|
"""
|
2036
2522
|
How many SLURM tasks per compute node to request.
|
2037
2523
|
"""
|
2038
2524
|
return self._get_value("SLURM_num_tasks_per_node")
|
2039
2525
|
|
2040
2526
|
@property
|
2041
|
-
def SLURM_num_nodes(self):
|
2527
|
+
def SLURM_num_nodes(self) -> int | None:
|
2042
2528
|
"""
|
2043
2529
|
How many compute nodes to request.
|
2044
2530
|
"""
|
2045
2531
|
return self._get_value("SLURM_num_nodes")
|
2046
2532
|
|
2047
2533
|
@property
|
2048
|
-
def SLURM_num_cpus_per_task(self):
|
2534
|
+
def SLURM_num_cpus_per_task(self) -> int | None:
|
2049
2535
|
"""
|
2050
2536
|
How many CPU cores to ask for per SLURM task.
|
2051
2537
|
"""
|
2052
2538
|
return self._get_value("SLURM_num_cpus_per_task")
|
2053
2539
|
|
2054
|
-
@os_name.setter
|
2055
|
-
def os_name(self, value):
|
2056
|
-
self._setter_persistent_check()
|
2057
|
-
self._os_name = self._process_string(value)
|
2058
|
-
|
2059
2540
|
@property
|
2060
|
-
def workflow(self):
|
2541
|
+
def workflow(self) -> Workflow | None:
|
2061
2542
|
"""
|
2062
2543
|
The workflow owning this resource spec.
|
2063
2544
|
"""
|
@@ -2066,7 +2547,8 @@ class ResourceSpec(JSONLike):
|
|
2066
2547
|
|
2067
2548
|
elif self.element_set:
|
2068
2549
|
# element-set-level resources
|
2069
|
-
|
2550
|
+
wt = self.element_set.task_template.workflow_template
|
2551
|
+
return wt.workflow if wt else None
|
2070
2552
|
|
2071
2553
|
elif self.workflow_template:
|
2072
2554
|
# template-level resources
|
@@ -2079,47 +2561,29 @@ class ResourceSpec(JSONLike):
|
|
2079
2561
|
f"creating the workflow object."
|
2080
2562
|
)
|
2081
2563
|
|
2564
|
+
return None
|
2565
|
+
|
2082
2566
|
@property
|
2083
|
-
def element_set(self):
|
2567
|
+
def element_set(self) -> ElementSet | None:
|
2084
2568
|
"""
|
2085
2569
|
The element set that will use this resource spec.
|
2086
2570
|
"""
|
2571
|
+
if not self._resource_list:
|
2572
|
+
return None
|
2087
2573
|
return self._resource_list.element_set
|
2088
2574
|
|
2089
2575
|
@property
|
2090
|
-
def workflow_template(self):
|
2576
|
+
def workflow_template(self) -> WorkflowTemplate | None:
|
2091
2577
|
"""
|
2092
2578
|
The workflow template that will use this resource spec.
|
2093
2579
|
"""
|
2580
|
+
if not self._resource_list:
|
2581
|
+
return None
|
2094
2582
|
return self._resource_list.workflow_template
|
2095
2583
|
|
2096
2584
|
|
2097
|
-
|
2098
|
-
|
2099
|
-
The types if input sources.
|
2100
|
-
"""
|
2101
|
-
|
2102
|
-
#: Input source is an import.
|
2103
|
-
IMPORT = 0
|
2104
|
-
#: Input source is local.
|
2105
|
-
LOCAL = 1
|
2106
|
-
#: Input source is a default.
|
2107
|
-
DEFAULT = 2
|
2108
|
-
#: Input source is a task.
|
2109
|
-
TASK = 3
|
2110
|
-
|
2111
|
-
|
2112
|
-
class TaskSourceType(enum.Enum):
|
2113
|
-
"""
|
2114
|
-
The types of task-based input sources.
|
2115
|
-
"""
|
2116
|
-
|
2117
|
-
#: Input source is a task input.
|
2118
|
-
INPUT = 0
|
2119
|
-
#: Input source is a task output.
|
2120
|
-
OUTPUT = 1
|
2121
|
-
#: Input source is unspecified.
|
2122
|
-
ANY = 2
|
2585
|
+
#: How to specify a selection rule.
|
2586
|
+
Where: TypeAlias = "RuleArgs | Rule | Sequence[RuleArgs | Rule] | ElementFilter"
|
2123
2587
|
|
2124
2588
|
|
2125
2589
|
class InputSource(JSONLike):
|
@@ -2144,7 +2608,7 @@ class InputSource(JSONLike):
|
|
2144
2608
|
Filtering rules.
|
2145
2609
|
"""
|
2146
2610
|
|
2147
|
-
_child_objects = (
|
2611
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
2148
2612
|
ChildObjectSpec(
|
2149
2613
|
name="source_type",
|
2150
2614
|
json_like_name="type",
|
@@ -2153,55 +2617,61 @@ class InputSource(JSONLike):
|
|
2153
2617
|
),
|
2154
2618
|
)
|
2155
2619
|
|
2620
|
+
@classmethod
|
2621
|
+
def __is_ElementFilter(cls, value) -> TypeIs[ElementFilter]:
|
2622
|
+
return isinstance(value, cls._app.ElementFilter)
|
2623
|
+
|
2624
|
+
@classmethod
|
2625
|
+
def __is_Rule(cls, value) -> TypeIs[Rule]:
|
2626
|
+
return isinstance(value, cls._app.Rule)
|
2627
|
+
|
2156
2628
|
def __init__(
|
2157
2629
|
self,
|
2158
|
-
source_type,
|
2159
|
-
import_ref=None,
|
2160
|
-
task_ref=None,
|
2161
|
-
task_source_type=None,
|
2162
|
-
element_iters=None,
|
2163
|
-
path=None,
|
2164
|
-
where:
|
2165
|
-
Union[dict, app.Rule, List[dict], List[app.Rule], app.ElementFilter]
|
2166
|
-
] = None,
|
2630
|
+
source_type: InputSourceType | str,
|
2631
|
+
import_ref: int | None = None,
|
2632
|
+
task_ref: int | None = None,
|
2633
|
+
task_source_type: TaskSourceType | str | None = None,
|
2634
|
+
element_iters: list[int] | None = None,
|
2635
|
+
path: str | None = None,
|
2636
|
+
where: Where | None = None,
|
2167
2637
|
):
|
2168
|
-
if where is
|
2169
|
-
|
2170
|
-
|
2171
|
-
|
2172
|
-
|
2173
|
-
|
2174
|
-
|
2175
|
-
|
2638
|
+
if where is None or self.__is_ElementFilter(where):
|
2639
|
+
#: Filtering rules.
|
2640
|
+
self.where: ElementFilter | None = where
|
2641
|
+
else:
|
2642
|
+
self.where = self._app.ElementFilter(
|
2643
|
+
rules=[
|
2644
|
+
rule if self.__is_Rule(rule) else self._app.Rule(**rule)
|
2645
|
+
for rule in (where if isinstance(where, Sequence) else [where])
|
2646
|
+
]
|
2647
|
+
)
|
2176
2648
|
|
2177
2649
|
#: Type of the input source.
|
2178
|
-
self.source_type =
|
2650
|
+
self.source_type = get_enum_by_name_or_val(InputSourceType, source_type)
|
2179
2651
|
#: Where the input comes from when the type is `IMPORT`.
|
2180
2652
|
self.import_ref = import_ref
|
2181
2653
|
#: Which task is this an input for? Used when the type is `TASK`.
|
2182
2654
|
self.task_ref = task_ref
|
2183
2655
|
#: Type of task source.
|
2184
|
-
self.task_source_type =
|
2656
|
+
self.task_source_type = get_enum_by_name_or_val(TaskSourceType, task_source_type)
|
2185
2657
|
#: Which element iterations does this apply to?
|
2186
2658
|
self.element_iters = element_iters
|
2187
|
-
#: Filtering rules.
|
2188
|
-
self.where = where
|
2189
2659
|
#: Path to where this input goes.
|
2190
2660
|
self.path = path
|
2191
2661
|
|
2192
2662
|
if self.source_type is InputSourceType.TASK:
|
2193
2663
|
if self.task_ref is None:
|
2194
|
-
raise ValueError(
|
2664
|
+
raise ValueError("Must specify `task_ref` if `source_type` is TASK.")
|
2195
2665
|
if self.task_source_type is None:
|
2196
2666
|
self.task_source_type = TaskSourceType.OUTPUT
|
2197
2667
|
|
2198
2668
|
if self.source_type is InputSourceType.IMPORT and self.import_ref is None:
|
2199
|
-
raise ValueError(
|
2669
|
+
raise ValueError("Must specify `import_ref` if `source_type` is IMPORT.")
|
2200
2670
|
|
2201
|
-
def __eq__(self, other):
|
2671
|
+
def __eq__(self, other: Any):
|
2202
2672
|
if not isinstance(other, self.__class__):
|
2203
2673
|
return False
|
2204
|
-
|
2674
|
+
return (
|
2205
2675
|
self.source_type == other.source_type
|
2206
2676
|
and self.import_ref == other.import_ref
|
2207
2677
|
and self.task_ref == other.task_ref
|
@@ -2209,23 +2679,22 @@ class InputSource(JSONLike):
|
|
2209
2679
|
and self.element_iters == other.element_iters
|
2210
2680
|
and self.where == other.where
|
2211
2681
|
and self.path == other.path
|
2212
|
-
)
|
2213
|
-
return True
|
2214
|
-
else:
|
2215
|
-
return False
|
2682
|
+
)
|
2216
2683
|
|
2217
2684
|
def __repr__(self) -> str:
|
2685
|
+
assert self.source_type
|
2218
2686
|
cls_method_name = self.source_type.name.lower()
|
2219
2687
|
|
2220
|
-
args_lst = []
|
2688
|
+
args_lst: list[str] = []
|
2221
2689
|
|
2222
2690
|
if self.source_type is InputSourceType.IMPORT:
|
2223
2691
|
cls_method_name += "_"
|
2224
2692
|
args_lst.append(f"import_ref={self.import_ref}")
|
2225
2693
|
|
2226
2694
|
elif self.source_type is InputSourceType.TASK:
|
2227
|
-
|
2228
|
-
|
2695
|
+
assert self.task_source_type
|
2696
|
+
args_lst.append(f"task_ref={self.task_ref}")
|
2697
|
+
args_lst.append(
|
2229
2698
|
f"task_source_type={self.task_source_type.name.lower()!r}",
|
2230
2699
|
)
|
2231
2700
|
|
@@ -2240,15 +2709,16 @@ class InputSource(JSONLike):
|
|
2240
2709
|
|
2241
2710
|
return out
|
2242
2711
|
|
2243
|
-
def get_task(self, workflow):
|
2712
|
+
def get_task(self, workflow: Workflow) -> WorkflowTask | None:
|
2244
2713
|
"""If source_type is task, then return the referenced task from the given
|
2245
2714
|
workflow."""
|
2246
2715
|
if self.source_type is InputSourceType.TASK:
|
2247
|
-
|
2248
|
-
if task.insert_ID == self.task_ref
|
2249
|
-
|
2716
|
+
return next(
|
2717
|
+
(task for task in workflow.tasks if task.insert_ID == self.task_ref), None
|
2718
|
+
)
|
2719
|
+
return None
|
2250
2720
|
|
2251
|
-
def is_in(self, other_input_sources:
|
2721
|
+
def is_in(self, other_input_sources: Sequence[InputSource]) -> int | None:
|
2252
2722
|
"""Check if this input source is in a list of other input sources, without
|
2253
2723
|
considering the `element_iters` and `where` attributes."""
|
2254
2724
|
|
@@ -2263,51 +2733,38 @@ class InputSource(JSONLike):
|
|
2263
2733
|
return idx
|
2264
2734
|
return None
|
2265
2735
|
|
2266
|
-
def to_string(self):
|
2736
|
+
def to_string(self) -> str:
|
2267
2737
|
"""
|
2268
2738
|
Render this input source as a string.
|
2269
2739
|
"""
|
2270
2740
|
out = [self.source_type.name.lower()]
|
2271
2741
|
if self.source_type is InputSourceType.TASK:
|
2272
|
-
|
2742
|
+
assert self.task_source_type
|
2743
|
+
out.append(str(self.task_ref))
|
2744
|
+
out.append(self.task_source_type.name.lower())
|
2273
2745
|
if self.element_iters is not None:
|
2274
|
-
out
|
2746
|
+
out.append(f'[{",".join(map(str, self.element_iters))}]')
|
2275
2747
|
elif self.source_type is InputSourceType.IMPORT:
|
2276
|
-
out
|
2748
|
+
out.append(str(self.import_ref))
|
2277
2749
|
return ".".join(out)
|
2278
2750
|
|
2279
|
-
@staticmethod
|
2280
|
-
def _validate_source_type(src_type):
|
2281
|
-
if src_type is None:
|
2282
|
-
return None
|
2283
|
-
if isinstance(src_type, InputSourceType):
|
2284
|
-
return src_type
|
2285
|
-
try:
|
2286
|
-
src_type = getattr(InputSourceType, src_type.upper())
|
2287
|
-
except AttributeError:
|
2288
|
-
raise ValueError(
|
2289
|
-
f"InputSource `source_type` specified as {src_type!r}, but "
|
2290
|
-
f"must be one of: {[i.name for i in InputSourceType]!r}."
|
2291
|
-
)
|
2292
|
-
return src_type
|
2293
|
-
|
2294
2751
|
@classmethod
|
2295
|
-
def _validate_task_source_type(cls, task_src_type):
|
2752
|
+
def _validate_task_source_type(cls, task_src_type) -> None | TaskSourceType:
|
2296
2753
|
if task_src_type is None:
|
2297
2754
|
return None
|
2298
2755
|
if isinstance(task_src_type, TaskSourceType):
|
2299
2756
|
return task_src_type
|
2300
2757
|
try:
|
2301
|
-
task_source_type = getattr(cls.
|
2758
|
+
task_source_type = getattr(cls._app.TaskSourceType, task_src_type.upper())
|
2302
2759
|
except AttributeError:
|
2303
2760
|
raise ValueError(
|
2304
2761
|
f"InputSource `task_source_type` specified as {task_src_type!r}, but "
|
2305
|
-
f"must be one of: {
|
2762
|
+
f"must be one of: {TaskSourceType.names!r}."
|
2306
2763
|
)
|
2307
2764
|
return task_source_type
|
2308
2765
|
|
2309
2766
|
@classmethod
|
2310
|
-
def from_string(cls, str_defn):
|
2767
|
+
def from_string(cls, str_defn: str) -> Self:
|
2311
2768
|
"""Parse a dot-delimited string definition of an InputSource.
|
2312
2769
|
|
2313
2770
|
Parameter
|
@@ -2322,44 +2779,49 @@ class InputSource(JSONLike):
|
|
2322
2779
|
local
|
2323
2780
|
default
|
2324
2781
|
import.[import_ref]
|
2325
|
-
|
2326
2782
|
"""
|
2327
2783
|
return cls(**cls._parse_from_string(str_defn))
|
2328
2784
|
|
2329
|
-
@
|
2330
|
-
def _parse_from_string(
|
2785
|
+
@staticmethod
|
2786
|
+
def _parse_from_string(str_defn: str) -> dict[str, Any]:
|
2787
|
+
"""Parse a dot-delimited string definition of an InputSource.
|
2788
|
+
|
2789
|
+
Examples
|
2790
|
+
--------
|
2791
|
+
task.[task_ref].input
|
2792
|
+
task.[task_ref].output
|
2793
|
+
local
|
2794
|
+
default
|
2795
|
+
import.[import_ref]
|
2796
|
+
"""
|
2331
2797
|
parts = str_defn.split(".")
|
2332
|
-
source_type =
|
2333
|
-
task_ref = None
|
2334
|
-
task_source_type = None
|
2335
|
-
import_ref = None
|
2798
|
+
source_type = get_enum_by_name_or_val(InputSourceType, parts[0])
|
2799
|
+
task_ref: int | None = None
|
2800
|
+
task_source_type: TaskSourceType | None = None
|
2801
|
+
import_ref: int | None = None
|
2336
2802
|
if (
|
2337
2803
|
(
|
2338
|
-
source_type
|
2339
|
-
in (cls.app.InputSourceType.LOCAL, cls.app.InputSourceType.DEFAULT)
|
2804
|
+
source_type in (InputSourceType.LOCAL, InputSourceType.DEFAULT)
|
2340
2805
|
and len(parts) > 1
|
2341
2806
|
)
|
2342
|
-
or (source_type is
|
2343
|
-
or (source_type is
|
2807
|
+
or (source_type is InputSourceType.TASK and len(parts) > 3)
|
2808
|
+
or (source_type is InputSourceType.IMPORT and len(parts) > 2)
|
2344
2809
|
):
|
2345
2810
|
raise ValueError(f"InputSource string not understood: {str_defn!r}.")
|
2346
2811
|
|
2347
|
-
if source_type is
|
2812
|
+
if source_type is InputSourceType.TASK:
|
2348
2813
|
# TODO: does this include element_iters?
|
2349
|
-
task_ref = parts[1]
|
2350
2814
|
try:
|
2351
|
-
task_ref = int(
|
2815
|
+
task_ref = int(parts[1])
|
2352
2816
|
except ValueError:
|
2353
2817
|
pass
|
2354
2818
|
try:
|
2355
|
-
|
2819
|
+
task_source_type = get_enum_by_name_or_val(TaskSourceType, parts[2])
|
2356
2820
|
except IndexError:
|
2357
|
-
|
2358
|
-
|
2359
|
-
elif source_type is cls.app.InputSourceType.IMPORT:
|
2360
|
-
import_ref = parts[1]
|
2821
|
+
task_source_type = TaskSourceType.OUTPUT
|
2822
|
+
elif source_type is InputSourceType.IMPORT:
|
2361
2823
|
try:
|
2362
|
-
import_ref = int(
|
2824
|
+
import_ref = int(parts[1])
|
2363
2825
|
except ValueError:
|
2364
2826
|
pass
|
2365
2827
|
|
@@ -2377,9 +2839,14 @@ class InputSource(JSONLike):
|
|
2377
2839
|
return super().from_json_like(json_like, shared_data)
|
2378
2840
|
|
2379
2841
|
@classmethod
|
2380
|
-
def import_(
|
2842
|
+
def import_(
|
2843
|
+
cls,
|
2844
|
+
import_ref: int,
|
2845
|
+
element_iters: list[int] | None = None,
|
2846
|
+
where: Where | None = None,
|
2847
|
+
) -> Self:
|
2381
2848
|
"""
|
2382
|
-
Make an
|
2849
|
+
Make an instance of an input source that is an import.
|
2383
2850
|
|
2384
2851
|
Parameters
|
2385
2852
|
----------
|
@@ -2391,30 +2858,36 @@ class InputSource(JSONLike):
|
|
2391
2858
|
Filtering rule.
|
2392
2859
|
"""
|
2393
2860
|
return cls(
|
2394
|
-
source_type=
|
2861
|
+
source_type=InputSourceType.IMPORT,
|
2395
2862
|
import_ref=import_ref,
|
2396
2863
|
element_iters=element_iters,
|
2397
2864
|
where=where,
|
2398
2865
|
)
|
2399
2866
|
|
2400
2867
|
@classmethod
|
2401
|
-
def local(cls):
|
2868
|
+
def local(cls) -> Self:
|
2402
2869
|
"""
|
2403
|
-
Make an
|
2870
|
+
Make an instance of an input source that is local.
|
2404
2871
|
"""
|
2405
|
-
return cls(source_type=
|
2872
|
+
return cls(source_type=InputSourceType.LOCAL)
|
2406
2873
|
|
2407
2874
|
@classmethod
|
2408
|
-
def default(cls):
|
2875
|
+
def default(cls) -> Self:
|
2409
2876
|
"""
|
2410
|
-
Make an
|
2877
|
+
Make an instance of an input source that is default.
|
2411
2878
|
"""
|
2412
|
-
return cls(source_type=
|
2879
|
+
return cls(source_type=InputSourceType.DEFAULT)
|
2413
2880
|
|
2414
2881
|
@classmethod
|
2415
|
-
def task(
|
2882
|
+
def task(
|
2883
|
+
cls,
|
2884
|
+
task_ref: int,
|
2885
|
+
task_source_type: TaskSourceType | str | None = None,
|
2886
|
+
element_iters: list[int] | None = None,
|
2887
|
+
where: Where | None = None,
|
2888
|
+
) -> Self:
|
2416
2889
|
"""
|
2417
|
-
Make an
|
2890
|
+
Make an instance of an input source that is a task.
|
2418
2891
|
|
2419
2892
|
Parameters
|
2420
2893
|
----------
|
@@ -2427,12 +2900,12 @@ class InputSource(JSONLike):
|
|
2427
2900
|
where:
|
2428
2901
|
Filtering rule.
|
2429
2902
|
"""
|
2430
|
-
if not task_source_type:
|
2431
|
-
task_source_type = cls.app.TaskSourceType.OUTPUT
|
2432
2903
|
return cls(
|
2433
|
-
source_type=
|
2904
|
+
source_type=InputSourceType.TASK,
|
2434
2905
|
task_ref=task_ref,
|
2435
|
-
task_source_type=
|
2906
|
+
task_source_type=get_enum_by_name_or_val(
|
2907
|
+
TaskSourceType, task_source_type or TaskSourceType.OUTPUT
|
2908
|
+
),
|
2436
2909
|
where=where,
|
2437
2910
|
element_iters=element_iters,
|
2438
2911
|
)
|