hpcflow-new2 0.2.0a189__py3-none-any.whl → 0.2.0a190__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hpcflow/__pyinstaller/hook-hpcflow.py +8 -6
- hpcflow/_version.py +1 -1
- hpcflow/app.py +1 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +1 -1
- hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +1 -1
- hpcflow/sdk/__init__.py +21 -15
- hpcflow/sdk/app.py +2133 -770
- hpcflow/sdk/cli.py +281 -250
- hpcflow/sdk/cli_common.py +6 -2
- hpcflow/sdk/config/__init__.py +1 -1
- hpcflow/sdk/config/callbacks.py +77 -42
- hpcflow/sdk/config/cli.py +126 -103
- hpcflow/sdk/config/config.py +578 -311
- hpcflow/sdk/config/config_file.py +131 -95
- hpcflow/sdk/config/errors.py +112 -85
- hpcflow/sdk/config/types.py +145 -0
- hpcflow/sdk/core/actions.py +1054 -994
- hpcflow/sdk/core/app_aware.py +24 -0
- hpcflow/sdk/core/cache.py +81 -63
- hpcflow/sdk/core/command_files.py +275 -185
- hpcflow/sdk/core/commands.py +111 -107
- hpcflow/sdk/core/element.py +724 -503
- hpcflow/sdk/core/enums.py +192 -0
- hpcflow/sdk/core/environment.py +74 -93
- hpcflow/sdk/core/errors.py +398 -51
- hpcflow/sdk/core/json_like.py +540 -272
- hpcflow/sdk/core/loop.py +380 -334
- hpcflow/sdk/core/loop_cache.py +160 -43
- hpcflow/sdk/core/object_list.py +370 -207
- hpcflow/sdk/core/parameters.py +728 -600
- hpcflow/sdk/core/rule.py +59 -41
- hpcflow/sdk/core/run_dir_files.py +33 -22
- hpcflow/sdk/core/task.py +1546 -1325
- hpcflow/sdk/core/task_schema.py +240 -196
- hpcflow/sdk/core/test_utils.py +126 -88
- hpcflow/sdk/core/types.py +387 -0
- hpcflow/sdk/core/utils.py +410 -305
- hpcflow/sdk/core/validation.py +82 -9
- hpcflow/sdk/core/workflow.py +1192 -1028
- hpcflow/sdk/core/zarr_io.py +98 -137
- hpcflow/sdk/demo/cli.py +46 -33
- hpcflow/sdk/helper/cli.py +18 -16
- hpcflow/sdk/helper/helper.py +75 -63
- hpcflow/sdk/helper/watcher.py +61 -28
- hpcflow/sdk/log.py +83 -59
- hpcflow/sdk/persistence/__init__.py +8 -31
- hpcflow/sdk/persistence/base.py +988 -586
- hpcflow/sdk/persistence/defaults.py +6 -0
- hpcflow/sdk/persistence/discovery.py +38 -0
- hpcflow/sdk/persistence/json.py +408 -153
- hpcflow/sdk/persistence/pending.py +158 -123
- hpcflow/sdk/persistence/store_resource.py +37 -22
- hpcflow/sdk/persistence/types.py +307 -0
- hpcflow/sdk/persistence/utils.py +14 -11
- hpcflow/sdk/persistence/zarr.py +477 -420
- hpcflow/sdk/runtime.py +44 -41
- hpcflow/sdk/submission/{jobscript_info.py → enums.py} +39 -12
- hpcflow/sdk/submission/jobscript.py +444 -404
- hpcflow/sdk/submission/schedulers/__init__.py +133 -40
- hpcflow/sdk/submission/schedulers/direct.py +97 -71
- hpcflow/sdk/submission/schedulers/sge.py +132 -126
- hpcflow/sdk/submission/schedulers/slurm.py +263 -268
- hpcflow/sdk/submission/schedulers/utils.py +7 -2
- hpcflow/sdk/submission/shells/__init__.py +14 -15
- hpcflow/sdk/submission/shells/base.py +102 -29
- hpcflow/sdk/submission/shells/bash.py +72 -55
- hpcflow/sdk/submission/shells/os_version.py +31 -30
- hpcflow/sdk/submission/shells/powershell.py +37 -29
- hpcflow/sdk/submission/submission.py +203 -257
- hpcflow/sdk/submission/types.py +143 -0
- hpcflow/sdk/typing.py +163 -12
- hpcflow/tests/conftest.py +8 -6
- hpcflow/tests/schedulers/slurm/test_slurm_submission.py +5 -2
- hpcflow/tests/scripts/test_main_scripts.py +60 -30
- hpcflow/tests/shells/wsl/test_wsl_submission.py +6 -4
- hpcflow/tests/unit/test_action.py +86 -75
- hpcflow/tests/unit/test_action_rule.py +9 -4
- hpcflow/tests/unit/test_app.py +13 -6
- hpcflow/tests/unit/test_cli.py +1 -1
- hpcflow/tests/unit/test_command.py +71 -54
- hpcflow/tests/unit/test_config.py +20 -15
- hpcflow/tests/unit/test_config_file.py +21 -18
- hpcflow/tests/unit/test_element.py +58 -62
- hpcflow/tests/unit/test_element_iteration.py +3 -1
- hpcflow/tests/unit/test_element_set.py +29 -19
- hpcflow/tests/unit/test_group.py +4 -2
- hpcflow/tests/unit/test_input_source.py +116 -93
- hpcflow/tests/unit/test_input_value.py +29 -24
- hpcflow/tests/unit/test_json_like.py +44 -35
- hpcflow/tests/unit/test_loop.py +65 -58
- hpcflow/tests/unit/test_object_list.py +17 -12
- hpcflow/tests/unit/test_parameter.py +16 -7
- hpcflow/tests/unit/test_persistence.py +48 -35
- hpcflow/tests/unit/test_resources.py +20 -18
- hpcflow/tests/unit/test_run.py +8 -3
- hpcflow/tests/unit/test_runtime.py +2 -1
- hpcflow/tests/unit/test_schema_input.py +23 -15
- hpcflow/tests/unit/test_shell.py +3 -2
- hpcflow/tests/unit/test_slurm.py +8 -7
- hpcflow/tests/unit/test_submission.py +39 -19
- hpcflow/tests/unit/test_task.py +352 -247
- hpcflow/tests/unit/test_task_schema.py +33 -20
- hpcflow/tests/unit/test_utils.py +9 -11
- hpcflow/tests/unit/test_value_sequence.py +15 -12
- hpcflow/tests/unit/test_workflow.py +114 -83
- hpcflow/tests/unit/test_workflow_template.py +0 -1
- hpcflow/tests/workflows/test_jobscript.py +2 -1
- hpcflow/tests/workflows/test_workflows.py +18 -13
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/METADATA +2 -1
- hpcflow_new2-0.2.0a190.dist-info/RECORD +165 -0
- hpcflow/sdk/core/parallel.py +0 -21
- hpcflow_new2-0.2.0a189.dist-info/RECORD +0 -158
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/LICENSE +0 -0
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/core/object_list.py
CHANGED
@@ -2,11 +2,34 @@
|
|
2
2
|
General model of a searchable serializable list.
|
3
3
|
"""
|
4
4
|
|
5
|
+
from __future__ import annotations
|
6
|
+
from collections import defaultdict
|
7
|
+
from collections.abc import Mapping, Sequence
|
5
8
|
import copy
|
9
|
+
import sys
|
6
10
|
from types import SimpleNamespace
|
11
|
+
from typing import Generic, TypeVar, cast, overload, TYPE_CHECKING
|
12
|
+
from typing_extensions import override
|
7
13
|
|
8
14
|
from hpcflow.sdk.core.json_like import ChildObjectSpec, JSONLike
|
9
15
|
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from collections.abc import Iterable, Iterator
|
18
|
+
from typing import Any, ClassVar, Literal
|
19
|
+
from typing_extensions import Self, TypeIs
|
20
|
+
from zarr import Group # type: ignore
|
21
|
+
from .actions import ActionScope
|
22
|
+
from .command_files import FileSpec
|
23
|
+
from .environment import Environment, Executable
|
24
|
+
from .loop import WorkflowLoop
|
25
|
+
from .json_like import JSONable, JSONed
|
26
|
+
from .parameters import Parameter, ResourceSpec
|
27
|
+
from .task import Task, TaskTemplate, TaskSchema, WorkflowTask, ElementSet
|
28
|
+
from .types import Resources
|
29
|
+
from .workflow import WorkflowTemplate
|
30
|
+
|
31
|
+
T = TypeVar("T")
|
32
|
+
|
10
33
|
|
11
34
|
class ObjectListMultipleMatchError(ValueError):
|
12
35
|
"""
|
@@ -15,26 +38,32 @@ class ObjectListMultipleMatchError(ValueError):
|
|
15
38
|
"""
|
16
39
|
|
17
40
|
|
18
|
-
class ObjectList(JSONLike):
|
19
|
-
"""
|
41
|
+
class ObjectList(JSONLike, Generic[T]):
|
42
|
+
"""
|
43
|
+
A list-like class that provides item access via a `get` method according to
|
20
44
|
attributes or dict-keys.
|
21
45
|
|
22
46
|
Parameters
|
23
47
|
----------
|
24
48
|
objects : sequence
|
25
|
-
List
|
49
|
+
List of values of some type.
|
26
50
|
descriptor : str
|
27
51
|
Descriptive name for objects in the list.
|
28
52
|
"""
|
29
53
|
|
30
|
-
|
54
|
+
# This would be in the docstring except it renders really wrongly!
|
55
|
+
# Type Parameters
|
56
|
+
# ---------------
|
57
|
+
# T
|
58
|
+
# The type of elements of the list.
|
59
|
+
|
60
|
+
def __init__(self, objects: Iterable[T], descriptor: str | None = None):
|
31
61
|
self._objects = list(objects)
|
32
62
|
self._descriptor = descriptor or "object"
|
33
|
-
self._object_is_dict = False
|
34
|
-
|
63
|
+
self._object_is_dict: bool = False
|
35
64
|
self._validate()
|
36
65
|
|
37
|
-
def __deepcopy__(self, memo):
|
66
|
+
def __deepcopy__(self, memo: dict[int, Any]) -> Self:
|
38
67
|
obj = self.__class__(copy.deepcopy(self._objects, memo))
|
39
68
|
obj._descriptor = self._descriptor
|
40
69
|
obj._object_is_dict = self._object_is_dict
|
@@ -54,61 +83,62 @@ class ObjectList(JSONLike):
|
|
54
83
|
return repr(self._objects)
|
55
84
|
|
56
85
|
def __str__(self):
|
57
|
-
return str([self._get_item(
|
86
|
+
return str([self._get_item(obj) for obj in self._objects])
|
58
87
|
|
59
|
-
def __iter__(self):
|
88
|
+
def __iter__(self) -> Iterator[T]:
|
60
89
|
if self._object_is_dict:
|
61
|
-
return iter(self._get_item(
|
90
|
+
return iter(self._get_item(obj) for obj in self._objects)
|
62
91
|
else:
|
63
92
|
return self._objects.__iter__()
|
64
93
|
|
65
|
-
|
94
|
+
@overload
|
95
|
+
def __getitem__(self, key: int) -> T:
|
96
|
+
...
|
97
|
+
|
98
|
+
@overload
|
99
|
+
def __getitem__(self, key: slice) -> list[T]:
|
100
|
+
...
|
101
|
+
|
102
|
+
def __getitem__(self, key: int | slice) -> T | list[T]:
|
66
103
|
"""Provide list-like index access."""
|
67
|
-
|
104
|
+
if isinstance(key, slice):
|
105
|
+
return list(map(self._get_item, self._objects.__getitem__(key)))
|
106
|
+
else:
|
107
|
+
return self._get_item(self._objects.__getitem__(key))
|
68
108
|
|
69
|
-
def __contains__(self, item):
|
109
|
+
def __contains__(self, item: T) -> bool:
|
70
110
|
if self._objects:
|
71
|
-
if type(item)
|
111
|
+
if type(item) is type(self._get_item(self._objects[0])):
|
72
112
|
return self._objects.__contains__(item)
|
73
113
|
return False
|
74
114
|
|
75
|
-
def __eq__(self, other):
|
76
|
-
return self._objects == other
|
77
|
-
|
78
|
-
def list_attrs(self):
|
79
|
-
"""Get a tuple of the unique access-attribute values of the constituent objects."""
|
80
|
-
return tuple(self._index.keys())
|
115
|
+
def __eq__(self, other: Any) -> bool:
|
116
|
+
return isinstance(other, self.__class__) and self._objects == other._objects
|
81
117
|
|
82
|
-
def _get_item(self, obj):
|
118
|
+
def _get_item(self, obj: T):
|
83
119
|
if self._object_is_dict:
|
84
120
|
return obj.__dict__
|
85
121
|
else:
|
86
122
|
return obj
|
87
123
|
|
88
|
-
def _get_obj_attr(self, obj, attr):
|
124
|
+
def _get_obj_attr(self, obj: T, attr: str):
|
89
125
|
"""Overriding this function allows control over how the `get` functions behave."""
|
90
126
|
return getattr(obj, attr)
|
91
127
|
|
92
|
-
def
|
93
|
-
# narrow down according to kwargs:
|
94
|
-
specified_objs = []
|
128
|
+
def __specified_objs(self, objs: Iterable[T], kwargs: dict[str, Any]) -> Iterator[T]:
|
95
129
|
for obj in objs:
|
96
|
-
skip_obj = False
|
97
130
|
for k, v in kwargs.items():
|
98
131
|
try:
|
99
|
-
|
132
|
+
if self._get_obj_attr(obj, k) != v:
|
133
|
+
break
|
100
134
|
except (AttributeError, KeyError):
|
101
|
-
skip_obj = True
|
102
|
-
break
|
103
|
-
if obj_key_val != v:
|
104
|
-
skip_obj = True
|
105
135
|
break
|
106
|
-
if skip_obj:
|
107
|
-
continue
|
108
136
|
else:
|
109
|
-
|
137
|
+
yield obj
|
110
138
|
|
111
|
-
|
139
|
+
def _get_all_from_objs(self, objs: Iterable[T], **kwargs):
|
140
|
+
# narrow down according to kwargs:
|
141
|
+
return [self._get_item(obj) for obj in self.__specified_objs(objs, kwargs)]
|
112
142
|
|
113
143
|
def get_all(self, **kwargs):
|
114
144
|
"""Get one or more objects from the object list, by specifying the value of the
|
@@ -116,11 +146,11 @@ class ObjectList(JSONLike):
|
|
116
146
|
|
117
147
|
return self._get_all_from_objs(self._objects, **kwargs)
|
118
148
|
|
119
|
-
def _validate_get(self, result, kwargs):
|
149
|
+
def _validate_get(self, result: Sequence[T], kwargs: dict[str, Any]):
|
120
150
|
if not result:
|
121
|
-
available = []
|
151
|
+
available: list[dict[str, Any]] = []
|
122
152
|
for obj in self._objects:
|
123
|
-
attr_vals = {}
|
153
|
+
attr_vals: dict[str, Any] = {}
|
124
154
|
for k in kwargs:
|
125
155
|
try:
|
126
156
|
attr_vals[k] = self._get_obj_attr(obj, k)
|
@@ -144,7 +174,21 @@ class ObjectList(JSONLike):
|
|
144
174
|
attribute, and optionally additional keyword-argument attribute values."""
|
145
175
|
return self._validate_get(self.get_all(**kwargs), kwargs)
|
146
176
|
|
147
|
-
|
177
|
+
@overload
|
178
|
+
def add_object(
|
179
|
+
self, obj: T, index: int = -1, *, skip_duplicates: Literal[False] = False
|
180
|
+
) -> int:
|
181
|
+
...
|
182
|
+
|
183
|
+
@overload
|
184
|
+
def add_object(
|
185
|
+
self, obj: T, index: int = -1, *, skip_duplicates: Literal[True]
|
186
|
+
) -> int | None:
|
187
|
+
...
|
188
|
+
|
189
|
+
def add_object(
|
190
|
+
self, obj: T, index: int = -1, *, skip_duplicates: bool = False
|
191
|
+
) -> None | int:
|
148
192
|
"""
|
149
193
|
Add an object to this object list.
|
150
194
|
|
@@ -162,20 +206,35 @@ class ObjectList(JSONLike):
|
|
162
206
|
The index of the added object, or ``None`` if the object was not added.
|
163
207
|
"""
|
164
208
|
if skip_duplicates and obj in self:
|
165
|
-
return
|
209
|
+
return None
|
166
210
|
|
167
211
|
if index < 0:
|
168
212
|
index += len(self) + 1
|
169
213
|
|
170
214
|
if self._object_is_dict:
|
171
|
-
obj = SimpleNamespace(**obj)
|
215
|
+
obj = cast("T", SimpleNamespace(**cast("dict", obj)))
|
172
216
|
|
173
217
|
self._objects = self._objects[:index] + [obj] + self._objects[index:]
|
174
218
|
self._validate()
|
175
219
|
return index
|
176
220
|
|
177
221
|
|
178
|
-
class
|
222
|
+
class DotAccessAttributeError(AttributeError):
|
223
|
+
def __init__(self, name: str, obj: DotAccessObjectList) -> None:
|
224
|
+
msg = f"{obj._descriptor.title()} {name!r} does not exist. "
|
225
|
+
if obj._objects:
|
226
|
+
attr = obj._access_attribute
|
227
|
+
obj_list = (f'"{getattr(obj, attr)}"' for obj in obj._objects)
|
228
|
+
msg += f"Available {obj._descriptor}s are: {', '.join(obj_list)}."
|
229
|
+
else:
|
230
|
+
msg += "The object list is empty."
|
231
|
+
if sys.version_info >= (3, 10):
|
232
|
+
super().__init__(msg, name=name, obj=obj)
|
233
|
+
else:
|
234
|
+
super().__init__(msg)
|
235
|
+
|
236
|
+
|
237
|
+
class DotAccessObjectList(ObjectList[T], Generic[T]):
|
179
238
|
"""
|
180
239
|
Provide dot-notation access via an access attribute for the case where the access
|
181
240
|
attribute uniquely identifies a single object.
|
@@ -190,15 +249,35 @@ class DotAccessObjectList(ObjectList):
|
|
190
249
|
Descriptive name for the objects in the list.
|
191
250
|
"""
|
192
251
|
|
252
|
+
# This would be in the docstring except it renders really wrongly!
|
253
|
+
# Type Parameters
|
254
|
+
# ---------------
|
255
|
+
# T
|
256
|
+
# The type of elements of the list.
|
257
|
+
|
193
258
|
# access attributes must not be named after any "public" methods, to avoid confusion!
|
194
|
-
_pub_methods
|
259
|
+
_pub_methods: ClassVar[tuple[str, ...]] = (
|
260
|
+
"get",
|
261
|
+
"get_all",
|
262
|
+
"add_object",
|
263
|
+
"add_objects",
|
264
|
+
)
|
195
265
|
|
196
|
-
def __init__(
|
266
|
+
def __init__(
|
267
|
+
self, _objects: Iterable[T], access_attribute: str, descriptor: str | None = None
|
268
|
+
):
|
197
269
|
self._access_attribute = access_attribute
|
270
|
+
self._index: Mapping[str, Sequence[int]]
|
198
271
|
super().__init__(_objects, descriptor=descriptor)
|
199
272
|
self._update_index()
|
200
273
|
|
201
|
-
def
|
274
|
+
def __deepcopy__(self, memo: dict[int, Any]) -> Self:
|
275
|
+
obj = self.__class__(copy.deepcopy(self._objects, memo), self._access_attribute)
|
276
|
+
obj._descriptor = self._descriptor
|
277
|
+
obj._object_is_dict = self._object_is_dict
|
278
|
+
return obj
|
279
|
+
|
280
|
+
def _validate(self) -> None:
|
202
281
|
for idx, obj in enumerate(self._objects):
|
203
282
|
if not hasattr(obj, self._access_attribute):
|
204
283
|
raise TypeError(
|
@@ -211,60 +290,48 @@ class DotAccessObjectList(ObjectList):
|
|
211
290
|
f"cannot be the same as any of the methods of "
|
212
291
|
f"{self.__class__.__name__!r}, which are: {self._pub_methods!r}."
|
213
292
|
)
|
293
|
+
super()._validate()
|
214
294
|
|
215
|
-
|
216
|
-
|
217
|
-
def _update_index(self):
|
295
|
+
def _update_index(self) -> None:
|
218
296
|
"""For quick look-up by access attribute."""
|
219
297
|
|
220
|
-
_index =
|
298
|
+
_index: dict[str, list[int]] = defaultdict(list)
|
221
299
|
for idx, obj in enumerate(self._objects):
|
222
|
-
attr_val = getattr(obj, self._access_attribute)
|
300
|
+
attr_val: str = getattr(obj, self._access_attribute)
|
223
301
|
try:
|
224
|
-
|
225
|
-
_index[attr_val].append(idx)
|
226
|
-
else:
|
227
|
-
_index[attr_val] = [idx]
|
302
|
+
_index[attr_val].append(idx)
|
228
303
|
except TypeError:
|
229
304
|
raise TypeError(
|
230
305
|
f"Access attribute values ({self._access_attribute!r}) must be hashable."
|
231
306
|
)
|
232
307
|
self._index = _index
|
233
308
|
|
234
|
-
def __getattr__(self, attribute):
|
235
|
-
if
|
236
|
-
idx = self._index[attribute]
|
309
|
+
def __getattr__(self, attribute: str):
|
310
|
+
if idx := self._index.get(attribute):
|
237
311
|
if len(idx) > 1:
|
238
312
|
raise ValueError(
|
239
313
|
f"Multiple objects with access attribute: {attribute!r}."
|
240
314
|
)
|
241
315
|
return self._get_item(self._objects[idx[0]])
|
242
|
-
|
243
316
|
elif not attribute.startswith("__"):
|
244
|
-
|
245
|
-
[f'"{getattr(i, self._access_attribute)}"' for i in self._objects]
|
246
|
-
)
|
247
|
-
msg = f"{self._descriptor.title()} {attribute!r} does not exist. "
|
248
|
-
if self._objects:
|
249
|
-
msg += f"Available {self._descriptor}s are: {obj_list_fmt}."
|
250
|
-
else:
|
251
|
-
msg += "The object list is empty."
|
252
|
-
|
253
|
-
raise AttributeError(msg)
|
317
|
+
raise DotAccessAttributeError(attribute, self)
|
254
318
|
else:
|
255
319
|
raise AttributeError
|
256
320
|
|
257
|
-
def __dir__(self):
|
258
|
-
|
259
|
-
|
260
|
-
|
321
|
+
def __dir__(self) -> Iterator[str]:
|
322
|
+
yield from super().__dir__()
|
323
|
+
yield from (getattr(obj, self._access_attribute) for obj in self._objects)
|
324
|
+
|
325
|
+
def list_attrs(self) -> tuple[str, ...]:
|
326
|
+
"""Get a tuple of the unique access-attribute values of the constituent objects."""
|
327
|
+
return tuple(self._index)
|
261
328
|
|
262
|
-
def get(self, access_attribute_value=None, **kwargs):
|
329
|
+
def get(self, access_attribute_value: str | None = None, **kwargs) -> T:
|
263
330
|
"""
|
264
331
|
Get an object from this list that matches the given criteria.
|
265
332
|
"""
|
266
333
|
vld_get_kwargs = kwargs
|
267
|
-
if access_attribute_value:
|
334
|
+
if access_attribute_value is not None:
|
268
335
|
vld_get_kwargs = {self._access_attribute: access_attribute_value, **kwargs}
|
269
336
|
|
270
337
|
return self._validate_get(
|
@@ -272,57 +339,121 @@ class DotAccessObjectList(ObjectList):
|
|
272
339
|
vld_get_kwargs,
|
273
340
|
)
|
274
341
|
|
275
|
-
def get_all(self, access_attribute_value=None, **kwargs):
|
342
|
+
def get_all(self, access_attribute_value: str | None = None, **kwargs):
|
276
343
|
"""
|
277
344
|
Get all objects in this list that match the given criteria.
|
278
345
|
"""
|
279
346
|
# use the index to narrow down the search first:
|
280
|
-
if access_attribute_value:
|
281
|
-
|
282
|
-
all_idx = self._index[access_attribute_value]
|
283
|
-
except KeyError:
|
347
|
+
if access_attribute_value is not None:
|
348
|
+
if (all_idx := self._index.get(access_attribute_value)) is None:
|
284
349
|
raise ValueError(
|
285
350
|
f"Value {access_attribute_value!r} does not match the value of any "
|
286
351
|
f"object's attribute {self._access_attribute!r}. Available attribute "
|
287
352
|
f"values are: {self.list_attrs()!r}."
|
288
|
-
)
|
289
|
-
all_objs =
|
353
|
+
)
|
354
|
+
all_objs: Iterable[T] = (self._objects[idx] for idx in all_idx)
|
290
355
|
else:
|
291
356
|
all_objs = self._objects
|
292
357
|
|
293
358
|
return self._get_all_from_objs(all_objs, **kwargs)
|
294
359
|
|
295
|
-
|
360
|
+
@overload
|
361
|
+
def add_object(
|
362
|
+
self, obj: T, index: int = -1, *, skip_duplicates: Literal[False] = False
|
363
|
+
) -> int:
|
364
|
+
...
|
365
|
+
|
366
|
+
@overload
|
367
|
+
def add_object(
|
368
|
+
self, obj: T, index: int = -1, *, skip_duplicates: Literal[True]
|
369
|
+
) -> int | None:
|
370
|
+
...
|
371
|
+
|
372
|
+
def add_object(
|
373
|
+
self, obj: T, index: int = -1, *, skip_duplicates: bool = False
|
374
|
+
) -> int | None:
|
296
375
|
"""
|
297
376
|
Add an object to this list.
|
298
377
|
"""
|
299
|
-
|
378
|
+
if skip_duplicates:
|
379
|
+
new_index = super().add_object(obj, index, skip_duplicates=True)
|
380
|
+
else:
|
381
|
+
new_index = super().add_object(obj, index)
|
300
382
|
self._update_index()
|
301
|
-
return
|
383
|
+
return new_index
|
302
384
|
|
303
|
-
def add_objects(
|
385
|
+
def add_objects(
|
386
|
+
self, objs: Iterable[T], index: int = -1, *, skip_duplicates: bool = False
|
387
|
+
) -> int:
|
304
388
|
"""
|
305
389
|
Add multiple objects to the list.
|
306
390
|
"""
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
391
|
+
if skip_duplicates:
|
392
|
+
for obj in objs:
|
393
|
+
if (i := self.add_object(obj, index, skip_duplicates=True)) is not None:
|
394
|
+
index = i + 1
|
395
|
+
else:
|
396
|
+
for obj in objs:
|
397
|
+
index = self.add_object(obj, index) + 1
|
311
398
|
return index
|
312
399
|
|
313
400
|
|
314
|
-
class AppDataList(DotAccessObjectList):
|
401
|
+
class AppDataList(DotAccessObjectList[T], Generic[T]):
|
315
402
|
"""
|
316
403
|
An application-aware object list.
|
404
|
+
|
405
|
+
Type Parameters
|
406
|
+
---------------
|
407
|
+
T
|
408
|
+
The type of elements of the list.
|
317
409
|
"""
|
318
410
|
|
319
|
-
|
411
|
+
@override
|
412
|
+
def _postprocess_to_dict(self, d: dict[str, Any]) -> dict[str, Any]:
|
413
|
+
d = super()._postprocess_to_dict(d)
|
414
|
+
return {"_objects": d["_objects"]}
|
320
415
|
|
321
|
-
|
322
|
-
|
416
|
+
@classmethod
|
417
|
+
def _get_default_shared_data(cls) -> Mapping[str, ObjectList[JSONable]]:
|
418
|
+
return cls._app._shared_data
|
419
|
+
|
420
|
+
@overload
|
421
|
+
@classmethod
|
422
|
+
def from_json_like(
|
423
|
+
cls,
|
424
|
+
json_like: str,
|
425
|
+
shared_data: Mapping[str, ObjectList[JSONable]] | None = None,
|
426
|
+
is_hashed: bool = False,
|
427
|
+
) -> Self | None:
|
428
|
+
...
|
429
|
+
|
430
|
+
@overload
|
431
|
+
@classmethod
|
432
|
+
def from_json_like(
|
433
|
+
cls,
|
434
|
+
json_like: Mapping[str, JSONed] | Sequence[Mapping[str, JSONed]],
|
435
|
+
shared_data: Mapping[str, ObjectList[JSONable]] | None = None,
|
436
|
+
is_hashed: bool = False,
|
437
|
+
) -> Self:
|
438
|
+
...
|
439
|
+
|
440
|
+
@overload
|
441
|
+
@classmethod
|
442
|
+
def from_json_like(
|
443
|
+
cls,
|
444
|
+
json_like: None,
|
445
|
+
shared_data: Mapping[str, ObjectList[JSONable]] | None = None,
|
446
|
+
is_hashed: bool = False,
|
447
|
+
) -> None:
|
448
|
+
...
|
323
449
|
|
324
450
|
@classmethod
|
325
|
-
def from_json_like(
|
451
|
+
def from_json_like(
|
452
|
+
cls,
|
453
|
+
json_like: str | Mapping[str, JSONed] | Sequence[Mapping[str, JSONed]] | None,
|
454
|
+
shared_data: Mapping[str, ObjectList[JSONable]] | None = None,
|
455
|
+
is_hashed: bool = False,
|
456
|
+
) -> Self | None:
|
326
457
|
"""
|
327
458
|
Make an instance of this class from JSON (or YAML) data.
|
328
459
|
|
@@ -340,20 +471,23 @@ class AppDataList(DotAccessObjectList):
|
|
340
471
|
The deserialised object.
|
341
472
|
"""
|
342
473
|
if is_hashed:
|
343
|
-
json_like
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
474
|
+
assert isinstance(json_like, Mapping)
|
475
|
+
return super().from_json_like(
|
476
|
+
[
|
477
|
+
{**cast("Mapping", obj_js), "_hash_value": hash_val}
|
478
|
+
for hash_val, obj_js in json_like.items()
|
479
|
+
],
|
480
|
+
shared_data=shared_data,
|
481
|
+
)
|
482
|
+
else:
|
483
|
+
return super().from_json_like(json_like, shared_data=shared_data)
|
484
|
+
|
485
|
+
def _remove_object(self, index: int):
|
352
486
|
self._objects.pop(index)
|
353
487
|
self._update_index()
|
354
488
|
|
355
489
|
|
356
|
-
class TaskList(AppDataList):
|
490
|
+
class TaskList(AppDataList["Task"]):
|
357
491
|
"""A list-like container for a task-like list with dot-notation access by task
|
358
492
|
unique-name.
|
359
493
|
|
@@ -363,7 +497,7 @@ class TaskList(AppDataList):
|
|
363
497
|
The tasks in this list.
|
364
498
|
"""
|
365
499
|
|
366
|
-
_child_objects = (
|
500
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
367
501
|
ChildObjectSpec(
|
368
502
|
name="_objects",
|
369
503
|
class_name="Task",
|
@@ -372,11 +506,11 @@ class TaskList(AppDataList):
|
|
372
506
|
),
|
373
507
|
)
|
374
508
|
|
375
|
-
def __init__(self, _objects):
|
509
|
+
def __init__(self, _objects: Iterable[Task]):
|
376
510
|
super().__init__(_objects, access_attribute="unique_name", descriptor="task")
|
377
511
|
|
378
512
|
|
379
|
-
class TaskTemplateList(AppDataList):
|
513
|
+
class TaskTemplateList(AppDataList["TaskTemplate"]):
|
380
514
|
"""A list-like container for a task-like list with dot-notation access by task
|
381
515
|
unique-name.
|
382
516
|
|
@@ -386,7 +520,7 @@ class TaskTemplateList(AppDataList):
|
|
386
520
|
The task templates in this list.
|
387
521
|
"""
|
388
522
|
|
389
|
-
_child_objects = (
|
523
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
390
524
|
ChildObjectSpec(
|
391
525
|
name="_objects",
|
392
526
|
class_name="TaskTemplate",
|
@@ -395,11 +529,11 @@ class TaskTemplateList(AppDataList):
|
|
395
529
|
),
|
396
530
|
)
|
397
531
|
|
398
|
-
def __init__(self, _objects):
|
532
|
+
def __init__(self, _objects: Iterable[TaskTemplate]):
|
399
533
|
super().__init__(_objects, access_attribute="name", descriptor="task template")
|
400
534
|
|
401
535
|
|
402
|
-
class TaskSchemasList(AppDataList):
|
536
|
+
class TaskSchemasList(AppDataList["TaskSchema"]):
|
403
537
|
"""A list-like container for a task schema list with dot-notation access by task
|
404
538
|
schema unique-name.
|
405
539
|
|
@@ -409,7 +543,7 @@ class TaskSchemasList(AppDataList):
|
|
409
543
|
The task schemas in this list.
|
410
544
|
"""
|
411
545
|
|
412
|
-
_child_objects = (
|
546
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
413
547
|
ChildObjectSpec(
|
414
548
|
name="_objects",
|
415
549
|
class_name="TaskSchema",
|
@@ -418,11 +552,11 @@ class TaskSchemasList(AppDataList):
|
|
418
552
|
),
|
419
553
|
)
|
420
554
|
|
421
|
-
def __init__(self, _objects):
|
555
|
+
def __init__(self, _objects: Iterable[TaskSchema]):
|
422
556
|
super().__init__(_objects, access_attribute="name", descriptor="task schema")
|
423
557
|
|
424
558
|
|
425
|
-
class GroupList(AppDataList):
|
559
|
+
class GroupList(AppDataList["Group"]):
|
426
560
|
"""A list-like container for the task schema group list with dot-notation access by
|
427
561
|
group name.
|
428
562
|
|
@@ -432,7 +566,7 @@ class GroupList(AppDataList):
|
|
432
566
|
The groups in this list.
|
433
567
|
"""
|
434
568
|
|
435
|
-
_child_objects = (
|
569
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
436
570
|
ChildObjectSpec(
|
437
571
|
name="_objects",
|
438
572
|
class_name="Group",
|
@@ -441,11 +575,11 @@ class GroupList(AppDataList):
|
|
441
575
|
),
|
442
576
|
)
|
443
577
|
|
444
|
-
def __init__(self, _objects):
|
578
|
+
def __init__(self, _objects: Iterable[Group]):
|
445
579
|
super().__init__(_objects, access_attribute="name", descriptor="group")
|
446
580
|
|
447
581
|
|
448
|
-
class EnvironmentsList(AppDataList):
|
582
|
+
class EnvironmentsList(AppDataList["Environment"]):
|
449
583
|
"""
|
450
584
|
A list-like container for environments with dot-notation access by name.
|
451
585
|
|
@@ -455,7 +589,7 @@ class EnvironmentsList(AppDataList):
|
|
455
589
|
The environments in this list.
|
456
590
|
"""
|
457
591
|
|
458
|
-
_child_objects = (
|
592
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
459
593
|
ChildObjectSpec(
|
460
594
|
name="_objects",
|
461
595
|
class_name="Environment",
|
@@ -464,18 +598,18 @@ class EnvironmentsList(AppDataList):
|
|
464
598
|
),
|
465
599
|
)
|
466
600
|
|
467
|
-
def __init__(self, _objects):
|
601
|
+
def __init__(self, _objects: Iterable[Environment]):
|
468
602
|
super().__init__(_objects, access_attribute="name", descriptor="environment")
|
469
603
|
|
470
|
-
def _get_obj_attr(self, obj, attr):
|
604
|
+
def _get_obj_attr(self, obj: Environment, attr: str):
|
471
605
|
"""Overridden to lookup objects via the `specifiers` dict attribute"""
|
472
606
|
if attr in ("name", "_hash_value"):
|
473
607
|
return getattr(obj, attr)
|
474
608
|
else:
|
475
|
-
return
|
609
|
+
return obj.specifiers[attr]
|
476
610
|
|
477
611
|
|
478
|
-
class ExecutablesList(AppDataList):
|
612
|
+
class ExecutablesList(AppDataList["Executable"]):
|
479
613
|
"""
|
480
614
|
A list-like container for environment executables with dot-notation access by
|
481
615
|
executable label.
|
@@ -487,8 +621,8 @@ class ExecutablesList(AppDataList):
|
|
487
621
|
"""
|
488
622
|
|
489
623
|
#: The environment containing these executables.
|
490
|
-
environment = None
|
491
|
-
_child_objects = (
|
624
|
+
environment: Environment | None = None
|
625
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
492
626
|
ChildObjectSpec(
|
493
627
|
name="_objects",
|
494
628
|
class_name="Executable",
|
@@ -498,17 +632,17 @@ class ExecutablesList(AppDataList):
|
|
498
632
|
),
|
499
633
|
)
|
500
634
|
|
501
|
-
def __init__(self, _objects):
|
635
|
+
def __init__(self, _objects: Iterable[Executable]):
|
502
636
|
super().__init__(_objects, access_attribute="label", descriptor="executable")
|
503
637
|
self._set_parent_refs()
|
504
638
|
|
505
|
-
def __deepcopy__(self, memo):
|
639
|
+
def __deepcopy__(self, memo: dict[int, Any]):
|
506
640
|
obj = super().__deepcopy__(memo)
|
507
641
|
obj.environment = self.environment
|
508
642
|
return obj
|
509
643
|
|
510
644
|
|
511
|
-
class ParametersList(AppDataList):
|
645
|
+
class ParametersList(AppDataList["Parameter"]):
|
512
646
|
"""
|
513
647
|
A list-like container for parameters with dot-notation access by parameter type.
|
514
648
|
|
@@ -518,7 +652,7 @@ class ParametersList(AppDataList):
|
|
518
652
|
The parameters in this list.
|
519
653
|
"""
|
520
654
|
|
521
|
-
_child_objects = (
|
655
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
522
656
|
ChildObjectSpec(
|
523
657
|
name="_objects",
|
524
658
|
class_name="Parameter",
|
@@ -527,16 +661,16 @@ class ParametersList(AppDataList):
|
|
527
661
|
),
|
528
662
|
)
|
529
663
|
|
530
|
-
def __init__(self, _objects):
|
664
|
+
def __init__(self, _objects: Iterable[Parameter]):
|
531
665
|
super().__init__(_objects, access_attribute="typ", descriptor="parameter")
|
532
666
|
|
533
|
-
def __getattr__(self, attribute):
|
667
|
+
def __getattr__(self, attribute: str) -> Parameter:
|
534
668
|
"""Overridden to provide a default Parameter object if none exists."""
|
535
|
-
|
536
|
-
|
669
|
+
try:
|
670
|
+
if not attribute.startswith("__"):
|
537
671
|
return super().__getattr__(attribute)
|
538
|
-
|
539
|
-
|
672
|
+
except (AttributeError, ValueError):
|
673
|
+
return self._app.Parameter(typ=attribute)
|
540
674
|
raise AttributeError
|
541
675
|
|
542
676
|
def get_all(self, access_attribute_value=None, **kwargs):
|
@@ -552,7 +686,7 @@ class ParametersList(AppDataList):
|
|
552
686
|
return all_out or [self._app.Parameter(typ=typ)]
|
553
687
|
|
554
688
|
|
555
|
-
class CommandFilesList(AppDataList):
|
689
|
+
class CommandFilesList(AppDataList["FileSpec"]):
|
556
690
|
"""
|
557
691
|
A list-like container for command files with dot-notation access by label.
|
558
692
|
|
@@ -562,7 +696,7 @@ class CommandFilesList(AppDataList):
|
|
562
696
|
The files in this list.
|
563
697
|
"""
|
564
698
|
|
565
|
-
_child_objects = (
|
699
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
566
700
|
ChildObjectSpec(
|
567
701
|
name="_objects",
|
568
702
|
class_name="FileSpec",
|
@@ -571,11 +705,11 @@ class CommandFilesList(AppDataList):
|
|
571
705
|
),
|
572
706
|
)
|
573
707
|
|
574
|
-
def __init__(self, _objects):
|
708
|
+
def __init__(self, _objects: Iterable[FileSpec]):
|
575
709
|
super().__init__(_objects, access_attribute="label", descriptor="command file")
|
576
710
|
|
577
711
|
|
578
|
-
class WorkflowTaskList(DotAccessObjectList):
|
712
|
+
class WorkflowTaskList(DotAccessObjectList["WorkflowTask"]):
|
579
713
|
"""
|
580
714
|
A list-like container for workflow tasks with dot-notation access by unique name.
|
581
715
|
|
@@ -585,26 +719,28 @@ class WorkflowTaskList(DotAccessObjectList):
|
|
585
719
|
The tasks in this list.
|
586
720
|
"""
|
587
721
|
|
588
|
-
def __init__(self, _objects):
|
722
|
+
def __init__(self, _objects: Iterable[WorkflowTask]):
|
589
723
|
super().__init__(_objects, access_attribute="unique_name", descriptor="task")
|
590
724
|
|
591
|
-
def _reindex(self):
|
725
|
+
def _reindex(self) -> None:
|
592
726
|
"""Re-assign the WorkflowTask index attributes so they match their order."""
|
593
|
-
for idx,
|
594
|
-
|
727
|
+
for idx, item in enumerate(self._objects):
|
728
|
+
item._index = idx
|
595
729
|
self._update_index()
|
596
730
|
|
597
|
-
def add_object(
|
731
|
+
def add_object(
|
732
|
+
self, obj: WorkflowTask, index: int = -1, skip_duplicates=False
|
733
|
+
) -> int:
|
598
734
|
index = super().add_object(obj, index)
|
599
735
|
self._reindex()
|
600
736
|
return index
|
601
737
|
|
602
|
-
def _remove_object(self, index):
|
738
|
+
def _remove_object(self, index: int):
|
603
739
|
self._objects.pop(index)
|
604
740
|
self._reindex()
|
605
741
|
|
606
742
|
|
607
|
-
class WorkflowLoopList(DotAccessObjectList):
|
743
|
+
class WorkflowLoopList(DotAccessObjectList["WorkflowLoop"]):
|
608
744
|
"""
|
609
745
|
A list-like container for workflow loops with dot-notation access by name.
|
610
746
|
|
@@ -614,14 +750,14 @@ class WorkflowLoopList(DotAccessObjectList):
|
|
614
750
|
The loops in this list.
|
615
751
|
"""
|
616
752
|
|
617
|
-
def __init__(self, _objects):
|
753
|
+
def __init__(self, _objects: Iterable[WorkflowLoop]):
|
618
754
|
super().__init__(_objects, access_attribute="name", descriptor="loop")
|
619
755
|
|
620
|
-
def _remove_object(self, index):
|
756
|
+
def _remove_object(self, index: int):
|
621
757
|
self._objects.pop(index)
|
622
758
|
|
623
759
|
|
624
|
-
class ResourceList(ObjectList):
|
760
|
+
class ResourceList(ObjectList["ResourceSpec"]):
|
625
761
|
"""
|
626
762
|
A list-like container for resources.
|
627
763
|
Each contained resource must have a unique scope.
|
@@ -632,8 +768,7 @@ class ResourceList(ObjectList):
|
|
632
768
|
The resource descriptions in this list.
|
633
769
|
"""
|
634
770
|
|
635
|
-
|
636
|
-
_child_objects = (
|
771
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
637
772
|
ChildObjectSpec(
|
638
773
|
name="_objects",
|
639
774
|
class_name="ResourceSpec",
|
@@ -644,13 +779,15 @@ class ResourceList(ObjectList):
|
|
644
779
|
),
|
645
780
|
)
|
646
781
|
|
647
|
-
def __init__(self, _objects):
|
782
|
+
def __init__(self, _objects: Iterable[ResourceSpec]):
|
648
783
|
super().__init__(_objects, descriptor="resource specification")
|
649
|
-
self._element_set = None # assigned by parent ElementSet
|
650
|
-
self._workflow_template
|
784
|
+
self._element_set: ElementSet | None = None # assigned by parent ElementSet
|
785
|
+
self._workflow_template: WorkflowTemplate | None = (
|
786
|
+
None # assigned by parent WorkflowTemplate
|
787
|
+
)
|
651
788
|
|
652
789
|
# check distinct scopes for each item:
|
653
|
-
scopes = [
|
790
|
+
scopes = [scope.to_string() for scope in self.get_scopes()]
|
654
791
|
if len(set(scopes)) < len(scopes):
|
655
792
|
raise ValueError(
|
656
793
|
"Multiple `ResourceSpec` objects have the same scope. The scopes are "
|
@@ -659,97 +796,123 @@ class ResourceList(ObjectList):
|
|
659
796
|
|
660
797
|
self._set_parent_refs()
|
661
798
|
|
662
|
-
def __deepcopy__(self, memo):
|
799
|
+
def __deepcopy__(self, memo: dict[int, Any]):
|
663
800
|
obj = super().__deepcopy__(memo)
|
664
801
|
obj._element_set = self._element_set
|
665
802
|
obj._workflow_template = self._workflow_template
|
666
803
|
return obj
|
667
804
|
|
668
805
|
@property
|
669
|
-
def element_set(self):
|
806
|
+
def element_set(self) -> ElementSet | None:
|
670
807
|
"""
|
671
808
|
The parent element set, if a child of an element set.
|
672
809
|
"""
|
673
810
|
return self._element_set
|
674
811
|
|
675
812
|
@property
|
676
|
-
def workflow_template(self):
|
813
|
+
def workflow_template(self) -> WorkflowTemplate | None:
|
677
814
|
"""
|
678
815
|
The parent workflow template, if a child of a workflow template.
|
679
816
|
"""
|
680
817
|
return self._workflow_template
|
681
818
|
|
682
|
-
def
|
683
|
-
"""
|
819
|
+
def _postprocess_to_json(self, json_like):
|
820
|
+
"""Convert JSON doc to a dict keyed by action scope (like as can be
|
684
821
|
specified in the input YAML) instead of list."""
|
822
|
+
return {
|
823
|
+
self._app.ActionScope.from_json_like(
|
824
|
+
res_spec_js.pop("scope")
|
825
|
+
).to_string(): res_spec_js
|
826
|
+
for res_spec_js in json_like
|
827
|
+
}
|
828
|
+
|
829
|
+
@staticmethod
|
830
|
+
def __ensure_non_persistent(resource_spec: ResourceSpec) -> ResourceSpec:
|
831
|
+
"""
|
832
|
+
For any resources that are persistent, if they have a
|
833
|
+
`_resource_list` attribute, this means they are sourced from some
|
834
|
+
other persistent workflow, rather than, say, a workflow being
|
835
|
+
loaded right now, so make a non-persistent copy
|
836
|
+
|
837
|
+
Part of `normalise`.
|
838
|
+
"""
|
839
|
+
if resource_spec._value_group_idx is not None and (
|
840
|
+
resource_spec._resource_list is not None
|
841
|
+
):
|
842
|
+
return resource_spec.copy_non_persistent()
|
843
|
+
return resource_spec
|
685
844
|
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
scope = self._app.ActionScope.from_json_like(res_spec_js.pop("scope"))
|
690
|
-
as_dict[scope.to_string()] = res_spec_js
|
691
|
-
return as_dict, shared_data
|
845
|
+
@classmethod
|
846
|
+
def __is_ResourceSpec(cls, value) -> TypeIs[ResourceSpec]:
|
847
|
+
return isinstance(value, cls._app.ResourceSpec)
|
692
848
|
|
693
849
|
@classmethod
|
694
|
-
def normalise(cls, resources):
|
850
|
+
def normalise(cls, resources: Resources) -> Self:
|
695
851
|
"""Generate from resource-specs specified in potentially several ways."""
|
696
852
|
|
697
|
-
def _ensure_non_persistent(resource_spec):
|
698
|
-
# for any resources that are persistent, if they have a
|
699
|
-
# `_resource_list` attribute, this means they are sourced from some
|
700
|
-
# other persistent workflow, rather than, say, a workflow being
|
701
|
-
# loaded right now, so make a non-persistent copy:
|
702
|
-
if res_i._value_group_idx is not None and res_i._resource_list is not None:
|
703
|
-
return resource_spec.copy_non_persistent()
|
704
|
-
return resource_spec
|
705
|
-
|
706
|
-
if isinstance(resources, cls._app.ResourceSpec):
|
707
|
-
return resources
|
708
853
|
if not resources:
|
709
|
-
|
854
|
+
return cls([cls._app.ResourceSpec()])
|
855
|
+
elif isinstance(resources, ResourceList):
|
856
|
+
# Already a ResourceList
|
857
|
+
return cast("Self", resources)
|
710
858
|
elif isinstance(resources, dict):
|
711
|
-
|
712
|
-
elif
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
def get_scopes(self):
|
859
|
+
return cls.from_json_like(cast("dict", resources))
|
860
|
+
elif cls.__is_ResourceSpec(resources):
|
861
|
+
return cls([resources])
|
862
|
+
else:
|
863
|
+
return cls(
|
864
|
+
cls._app.ResourceSpec.from_json_like(cast("dict", res_i))
|
865
|
+
if isinstance(res_i, dict)
|
866
|
+
else cls.__ensure_non_persistent(res_i)
|
867
|
+
for res_i in resources
|
868
|
+
)
|
869
|
+
|
870
|
+
def get_scopes(self) -> Iterator[ActionScope]:
|
723
871
|
"""
|
724
872
|
Get the scopes of the contained resources.
|
725
873
|
"""
|
726
|
-
|
874
|
+
for rs in self._objects:
|
875
|
+
if rs.scope is not None:
|
876
|
+
yield rs.scope
|
727
877
|
|
728
|
-
def
|
878
|
+
def __get_for_scope(self, scope: ActionScope):
|
879
|
+
try:
|
880
|
+
return self.get(scope=scope)
|
881
|
+
except ValueError:
|
882
|
+
return None
|
883
|
+
|
884
|
+
def __merge(self, our_spec: ResourceSpec | None, other_spec: ResourceSpec):
|
885
|
+
"""
|
886
|
+
Merge two resource specs that have the same scope, or just add the other one to
|
887
|
+
the list if we didn't already have it.
|
888
|
+
"""
|
889
|
+
if our_spec is not None:
|
890
|
+
for k, v in other_spec._get_members().items():
|
891
|
+
if getattr(our_spec, k, None) is None:
|
892
|
+
setattr(our_spec, f"_{k}", copy.deepcopy(v))
|
893
|
+
else:
|
894
|
+
self.add_object(copy.deepcopy(other_spec))
|
895
|
+
|
896
|
+
def merge_other(self, other: ResourceList):
|
729
897
|
"""Merge lower-precedence other resource list into this resource list."""
|
730
898
|
for scope_i in other.get_scopes():
|
731
|
-
|
732
|
-
self_scoped = self.get(scope=scope_i)
|
733
|
-
except ValueError:
|
734
|
-
in_self = False
|
735
|
-
else:
|
736
|
-
in_self = True
|
899
|
+
self.__merge(self.__get_for_scope(scope_i), other.get(scope=scope_i))
|
737
900
|
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
901
|
+
def merge_one(self, other: ResourceSpec):
|
902
|
+
"""Merge lower-precedence other resource spec into this resource list.
|
903
|
+
|
904
|
+
This is a simplified version of :py:meth:`merge_other`.
|
905
|
+
"""
|
906
|
+
if other.scope is not None:
|
907
|
+
self.__merge(self.__get_for_scope(other.scope), other)
|
745
908
|
|
746
909
|
|
747
|
-
def index(obj_lst, obj):
|
910
|
+
def index(obj_lst: ObjectList[T], obj: T) -> int:
|
748
911
|
"""
|
749
912
|
Get the index of the object in the list.
|
750
913
|
The item is checked for by object identity, not equality.
|
751
914
|
"""
|
752
|
-
for idx,
|
753
|
-
if obj is
|
915
|
+
for idx, item in enumerate(obj_lst._objects):
|
916
|
+
if obj is item:
|
754
917
|
return idx
|
755
918
|
raise ValueError(f"{obj!r} not in list.")
|