hpcflow-new2 0.2.0a50__py3-none-any.whl → 0.2.0a52__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. hpcflow/_version.py +1 -1
  2. hpcflow/sdk/__init__.py +1 -1
  3. hpcflow/sdk/api.py +1 -1
  4. hpcflow/sdk/app.py +20 -11
  5. hpcflow/sdk/cli.py +34 -59
  6. hpcflow/sdk/core/__init__.py +13 -1
  7. hpcflow/sdk/core/actions.py +235 -126
  8. hpcflow/sdk/core/command_files.py +32 -24
  9. hpcflow/sdk/core/element.py +110 -114
  10. hpcflow/sdk/core/errors.py +57 -0
  11. hpcflow/sdk/core/loop.py +18 -34
  12. hpcflow/sdk/core/parameters.py +5 -3
  13. hpcflow/sdk/core/task.py +135 -131
  14. hpcflow/sdk/core/task_schema.py +11 -4
  15. hpcflow/sdk/core/utils.py +110 -2
  16. hpcflow/sdk/core/workflow.py +964 -676
  17. hpcflow/sdk/data/template_components/environments.yaml +0 -44
  18. hpcflow/sdk/data/template_components/task_schemas.yaml +52 -10
  19. hpcflow/sdk/persistence/__init__.py +21 -33
  20. hpcflow/sdk/persistence/base.py +1340 -458
  21. hpcflow/sdk/persistence/json.py +424 -546
  22. hpcflow/sdk/persistence/pending.py +563 -0
  23. hpcflow/sdk/persistence/store_resource.py +131 -0
  24. hpcflow/sdk/persistence/utils.py +57 -0
  25. hpcflow/sdk/persistence/zarr.py +852 -841
  26. hpcflow/sdk/submission/jobscript.py +133 -112
  27. hpcflow/sdk/submission/shells/bash.py +62 -16
  28. hpcflow/sdk/submission/shells/powershell.py +87 -16
  29. hpcflow/sdk/submission/submission.py +59 -35
  30. hpcflow/tests/unit/test_element.py +4 -9
  31. hpcflow/tests/unit/test_persistence.py +218 -0
  32. hpcflow/tests/unit/test_task.py +11 -12
  33. hpcflow/tests/unit/test_utils.py +82 -0
  34. hpcflow/tests/unit/test_workflow.py +3 -1
  35. {hpcflow_new2-0.2.0a50.dist-info → hpcflow_new2-0.2.0a52.dist-info}/METADATA +3 -1
  36. {hpcflow_new2-0.2.0a50.dist-info → hpcflow_new2-0.2.0a52.dist-info}/RECORD +38 -34
  37. {hpcflow_new2-0.2.0a50.dist-info → hpcflow_new2-0.2.0a52.dist-info}/WHEEL +0 -0
  38. {hpcflow_new2-0.2.0a50.dist-info → hpcflow_new2-0.2.0a52.dist-info}/entry_points.txt +0 -0
@@ -124,6 +124,7 @@ class InputFileGenerator(JSONLike):
124
124
  inputs: List[app.Parameter]
125
125
  script: str = None
126
126
  environment: app.Environment = None
127
+ abortable: Optional[bool] = False
127
128
 
128
129
  def get_action_rule(self):
129
130
  """Get the rule that allows testing if this input file generator must be
@@ -150,14 +151,10 @@ class InputFileGenerator(JSONLike):
150
151
  config_dir=r"{cfg_dir}",
151
152
  config_invocation_key=r"{cfg_invoc_key}",
152
153
  )
153
- wk_path, sub_idx, js_idx, js_elem_idx, js_act_idx = sys.argv[1:]
154
+ wk_path, EAR_ID = sys.argv[1:]
155
+ EAR_ID = int(EAR_ID)
154
156
  wk = app.Workflow(wk_path)
155
- _, EAR = wk._from_internal_get_EAR(
156
- submission_idx=int(sub_idx),
157
- jobscript_idx=int(js_idx),
158
- JS_element_idx=int(js_elem_idx),
159
- JS_action_idx=int(js_act_idx),
160
- )
157
+ EAR = wk.get_EARs_from_IDs([EAR_ID])[0]
161
158
  {script_main_func}(path=Path({file_path!r}), **EAR.get_IFG_input_values())
162
159
  """
163
160
  )
@@ -210,6 +207,16 @@ class OutputFileParser(JSONLike):
210
207
  environment: Environment = None
211
208
  inputs: List[str] = None
212
209
  options: Dict = None
210
+ abortable: Optional[bool] = False
211
+ save_files: Union[List[str], bool] = True
212
+
213
+ def __post_init__(self):
214
+ if not self.save_files:
215
+ # save no files
216
+ self.save_files = []
217
+ elif self.save_files is True:
218
+ # save all files
219
+ self.save_files = [i.label for i in self.output_files]
213
220
 
214
221
  def compose_source(self) -> str:
215
222
  """Generate the file contents of this output file parser source."""
@@ -230,23 +237,15 @@ class OutputFileParser(JSONLike):
230
237
  config_dir=r"{cfg_dir}",
231
238
  config_invocation_key=r"{cfg_invoc_key}",
232
239
  )
233
- wk_path, sub_idx, js_idx, js_elem_idx, js_act_idx = sys.argv[1:]
240
+ wk_path, EAR_ID = sys.argv[1:]
241
+ EAR_ID = int(EAR_ID)
234
242
  wk = app.Workflow(wk_path)
235
- _, EAR = wk._from_internal_get_EAR(
236
- submission_idx=int(sub_idx),
237
- jobscript_idx=int(js_idx),
238
- JS_element_idx=int(js_elem_idx),
239
- JS_action_idx=int(js_act_idx),
240
- )
241
- value = {script_main_func}(**EAR.get_OFP_output_files())
242
- wk.save_parameter(
243
- name="{param_name}",
244
- value=value,
245
- submission_idx=int(sub_idx),
246
- jobscript_idx=int(js_idx),
247
- JS_element_idx=int(js_elem_idx),
248
- JS_action_idx=int(js_act_idx),
243
+ EAR = wk.get_EARs_from_IDs([EAR_ID])[0]
244
+ value = {script_main_func}(
245
+ **EAR.get_OFP_output_files(),
246
+ **EAR.get_OFP_inputs(),
249
247
  )
248
+ wk.save_parameter(name="{param_name}", value=value, EAR_ID=EAR_ID)
250
249
 
251
250
  """
252
251
  )
@@ -366,10 +365,18 @@ class _FileContentsSpecifier(JSONLike):
366
365
  )
367
366
  # TODO: log if already persistent.
368
367
  else:
369
- data_ref = workflow._add_parameter_data(
370
- data=self._get_members(ensure_contents=True, use_file_label=True),
368
+ data_ref = workflow._add_file(
369
+ store_contents=self.store_contents,
370
+ is_input=True,
371
371
  source=source,
372
+ path=self.path,
373
+ contents=self.contents,
374
+ filename=self.file.name.name,
372
375
  )
376
+ # data_ref = workflow._add_parameter_data(
377
+ # data=self._get_members(ensure_contents=True, use_file_label=True),
378
+ # source=source,
379
+ # )
373
380
  is_new = True
374
381
  self._value_group_idx = data_ref
375
382
  self._workflow = workflow
@@ -381,6 +388,7 @@ class _FileContentsSpecifier(JSONLike):
381
388
  return (self.normalised_path, [data_ref], is_new)
382
389
 
383
390
  def _get_value(self, value_name=None):
391
+ # TODO: fix
384
392
  if self._value_group_idx is not None:
385
393
  grp = self.workflow.get_zarr_parameter_group(self._value_group_idx)
386
394
  val = zarr_decode(grp)
@@ -6,10 +6,8 @@ from typing import Any, Dict, List, Optional, Union
6
6
  from valida.conditions import ConditionLike
7
7
 
8
8
  from hpcflow.sdk import app
9
- from hpcflow.sdk.core.actions import EAR_ID, ElementID, IterationID
10
9
  from hpcflow.sdk.core.json_like import JSONLike
11
10
  from hpcflow.sdk.core.utils import check_valid_py_identifier
12
- from hpcflow.sdk.typing import E_idx_type, EAR_idx_type, EI_idx_type
13
11
 
14
12
 
15
13
  class _ElementPrefixedParameter:
@@ -174,23 +172,25 @@ class ElementIteration:
174
172
 
175
173
  def __init__(
176
174
  self,
175
+ id_: int,
176
+ is_pending: bool,
177
177
  index: int,
178
178
  element: app.Element,
179
179
  data_idx: Dict,
180
- EARs_initialised: bool,
181
- actions: List[Dict],
182
- global_idx: int,
180
+ EAR_IDs: Dict[int, int],
181
+ EARs: Union[List[Dict], None],
183
182
  schema_parameters: List[str],
184
183
  loop_idx: Dict,
185
184
  ):
185
+ self._id = id_
186
+ self._is_pending = is_pending
186
187
  self._index = index
187
188
  self._element = element
188
189
  self._data_idx = data_idx
189
- self._EARs_initialised = EARs_initialised
190
- self._global_idx = global_idx
191
190
  self._loop_idx = loop_idx
192
191
  self._schema_parameters = schema_parameters
193
- self._actions = actions
192
+ self._EARs = EARs
193
+ self._EAR_IDs = EAR_IDs
194
194
 
195
195
  # assigned on first access of corresponding properties:
196
196
  self._inputs = None
@@ -201,8 +201,8 @@ class ElementIteration:
201
201
 
202
202
  def __repr__(self):
203
203
  return (
204
- f"{self.__class__.__name__}("
205
- f"element={self.element!r}, index={self.index!r}, "
204
+ f"{self.__class__.__name__}(id={self.id_!r}, "
205
+ f"index={self.index!r}, element={self.element!r}, "
206
206
  f"EARs_initialised={self.EARs_initialised!r}"
207
207
  f")"
208
208
  )
@@ -215,7 +215,7 @@ class ElementIteration:
215
215
  @property
216
216
  def EARs_initialised(self):
217
217
  """Whether or not the EARs have been initialised."""
218
- return self._EARs_initialised
218
+ return self._EARs is not None
219
219
 
220
220
  @property
221
221
  def element(self):
@@ -226,16 +226,12 @@ class ElementIteration:
226
226
  return self._index
227
227
 
228
228
  @property
229
- def iteration_ID(self):
230
- return IterationID(
231
- task_insert_ID=self.task.insert_ID,
232
- element_idx=self.element.index,
233
- iteration_idx=self.index,
234
- )
229
+ def id_(self) -> int:
230
+ return self._id
235
231
 
236
232
  @property
237
- def global_idx(self) -> int:
238
- return self._global_idx
233
+ def is_pending(self) -> bool:
234
+ return self._is_pending
239
235
 
240
236
  @property
241
237
  def task(self):
@@ -253,6 +249,14 @@ class ElementIteration:
253
249
  def schema_parameters(self) -> List[str]:
254
250
  return self._schema_parameters
255
251
 
252
+ @property
253
+ def EAR_IDs(self) -> Dict[int, int]:
254
+ return self._EAR_IDs
255
+
256
+ @property
257
+ def EAR_IDs_flat(self):
258
+ return [j for i in self.EAR_IDs.values() for j in i]
259
+
256
260
  @property
257
261
  def actions(self) -> Dict[app.ElementAction]:
258
262
  if self._action_objs is None:
@@ -262,7 +266,7 @@ class ElementIteration:
262
266
  action_idx=act_idx,
263
267
  runs=runs,
264
268
  )
265
- for act_idx, runs in self._actions.items()
269
+ for act_idx, runs in self._EARs.items()
266
270
  }
267
271
  return self._action_objs
268
272
 
@@ -416,20 +420,16 @@ class ElementIteration:
416
420
  def get_EAR_dependencies(
417
421
  self,
418
422
  as_objects: Optional[bool] = False,
419
- ) -> List[Union[EAR_ID, app.ElementActionRun]]:
423
+ ) -> List[Union[int, app.ElementActionRun]]:
420
424
  """Get EARs that this element iteration depends on (excluding EARs of this element
421
425
  iteration)."""
422
426
  # TODO: test this includes EARs of upstream iterations of this iteration's element
423
427
  out = sorted(
424
428
  set(
425
- _EAR_ID
429
+ EAR_ID
426
430
  for i in self.action_runs
427
- for _EAR_ID in i.get_EAR_dependencies(as_objects=False)
428
- if not (
429
- _EAR_ID.task_insert_ID == self.task.insert_ID
430
- and _EAR_ID.element_idx == self.element.index
431
- and _EAR_ID.iteration_idx == self.index
432
- )
431
+ for EAR_ID in i.get_EAR_dependencies(as_objects=False)
432
+ if not EAR_ID in self.EAR_IDs_flat
433
433
  )
434
434
  )
435
435
  if as_objects:
@@ -438,15 +438,11 @@ class ElementIteration:
438
438
 
439
439
  def get_element_iteration_dependencies(
440
440
  self, as_objects: bool = False
441
- ) -> List[Union[IterationID, app.ElementIteration]]:
441
+ ) -> List[Union[int, app.ElementIteration]]:
442
442
  """Get element iterations that this element iteration depends on."""
443
443
  # TODO: test this includes previous iterations of this iteration's element
444
- out = sorted(
445
- set(
446
- _EAR_ID.get_iteration_ID()
447
- for _EAR_ID in self.get_EAR_dependencies(as_objects=False)
448
- )
449
- )
444
+ EAR_IDs = self.get_EAR_dependencies(as_objects=False)
445
+ out = sorted(set(self.workflow.get_element_iteration_IDs_from_EAR_IDs(EAR_IDs)))
450
446
  if as_objects:
451
447
  out = self.workflow.get_element_iterations_from_IDs(out)
452
448
  return out
@@ -454,15 +450,11 @@ class ElementIteration:
454
450
  def get_element_dependencies(
455
451
  self,
456
452
  as_objects: Optional[bool] = False,
457
- ) -> List[Union[ElementID, app.Element]]:
453
+ ) -> List[Union[int, app.Element]]:
458
454
  """Get elements that this element iteration depends on."""
459
455
  # TODO: this will be used in viz.
460
- out = sorted(
461
- set(
462
- _EAR_ID.get_element_ID()
463
- for _EAR_ID in self.get_EAR_dependencies(as_objects=False)
464
- )
465
- )
456
+ EAR_IDs = self.get_EAR_dependencies(as_objects=False)
457
+ out = sorted(set(self.workflow.get_element_IDs_from_EAR_IDs(EAR_IDs)))
466
458
  if as_objects:
467
459
  out = self.workflow.get_elements_from_IDs(out)
468
460
  return out
@@ -489,10 +481,9 @@ class ElementIteration:
489
481
  Dependencies may come from either elements from upstream tasks, or from locally
490
482
  defined inputs/sequences/defaults from upstream tasks."""
491
483
 
492
- out = []
493
- for elem_id in self.get_element_dependencies(as_objects=False):
494
- out.append(elem_id.task_insert_ID)
495
-
484
+ out = self.workflow.get_task_IDs_from_element_IDs(
485
+ self.get_element_dependencies(as_objects=False)
486
+ )
496
487
  for i in self.get_input_dependencies().values():
497
488
  out.append(i["task_insert_ID"])
498
489
 
@@ -505,25 +496,22 @@ class ElementIteration:
505
496
 
506
497
  def get_dependent_EARs(
507
498
  self, as_objects: bool = False
508
- ) -> List[Union[EAR_ID, app.ElementActionRun]]:
499
+ ) -> List[Union[int, app.ElementActionRun]]:
509
500
  """Get EARs of downstream iterations and tasks that depend on this element
510
501
  iteration."""
511
502
  # TODO: test this includes EARs of downstream iterations of this iteration's element
512
503
  deps = []
513
- for task in self.task.workflow.tasks[self.task.index :]:
514
- for element in task.elements:
515
- for iter_i in element.iterations:
516
- for EAR_i in iter_i.action_runs:
517
- for dep_i in EAR_i.get_EAR_dependencies(as_objects=False):
518
- dependent_EAR = EAR_i.EAR_idx
519
- if (
520
- dep_i.task_insert_ID == self.task.insert_ID
521
- and dep_i.element_idx == self.element.index
522
- and dep_i.iteration_idx == self.index
523
- and dependent_EAR not in deps
524
- ):
525
- deps.append(dependent_EAR)
526
-
504
+ for task in self.workflow.tasks[self.task.index :]:
505
+ for elem in task.elements[:]:
506
+ for iter_ in elem.iterations:
507
+ if iter_.id_ == self.id_:
508
+ # don't include EARs of this iteration
509
+ continue
510
+ for run in iter_.action_runs:
511
+ for dep_EAR_i in run.get_EAR_dependencies(as_objects=True):
512
+ # does dep_EAR_i belong to self?
513
+ if dep_EAR_i.id_ in self.EAR_IDs_flat and run.id_ not in deps:
514
+ deps.append(run.id_)
527
515
  deps = sorted(deps)
528
516
  if as_objects:
529
517
  deps = self.workflow.get_EARs_from_IDs(deps)
@@ -532,26 +520,21 @@ class ElementIteration:
532
520
 
533
521
  def get_dependent_element_iterations(
534
522
  self, as_objects: bool = False
535
- ) -> List[Union[IterationID, app.ElementIteration]]:
523
+ ) -> List[Union[int, app.ElementIteration]]:
536
524
  """Get elements iterations of downstream iterations and tasks that depend on this
537
525
  element iteration."""
538
526
  # TODO: test this includes downstream iterations of this iteration's element?
539
527
  deps = []
540
- for task in self.task.workflow.tasks[self.task.index :]:
541
- for element in task.elements:
542
- for iter_i in element.iterations:
543
- dependent_elem_iter = iter_i.iteration_ID
544
- for iter_ID in iter_i.get_element_iteration_dependencies(
545
- as_objects=False
528
+ for task in self.workflow.tasks[self.task.index :]:
529
+ for elem in task.elements[:]:
530
+ for iter_i in elem.iterations:
531
+ if iter_i.id_ == self.id_:
532
+ continue
533
+ for dep_iter_i in iter_i.get_element_iteration_dependencies(
534
+ as_objects=True
546
535
  ):
547
- if (
548
- iter_ID.task_insert_ID == self.task.insert_ID
549
- and iter_ID.element_idx == self.element.index
550
- and iter_ID.iteration_idx == self.index
551
- and dependent_elem_iter not in deps
552
- ):
553
- deps.append(dependent_elem_iter)
554
-
536
+ if dep_iter_i.id_ == self.id_ and iter_i.id_ not in deps:
537
+ deps.append(iter_i.id_)
555
538
  deps = sorted(deps)
556
539
  if as_objects:
557
540
  deps = self.workflow.get_element_iterations_from_IDs(deps)
@@ -561,20 +544,17 @@ class ElementIteration:
561
544
  def get_dependent_elements(
562
545
  self,
563
546
  as_objects: bool = False,
564
- ) -> List[Union[ElementID, app.Element]]:
547
+ ) -> List[Union[int, app.Element]]:
565
548
  """Get elements of downstream tasks that depend on this element iteration."""
566
549
  deps = []
567
550
  for task in self.task.downstream_tasks:
568
- for element in task.elements:
569
- dependent_elem = element.element_ID
551
+ for element in task.elements[:]:
570
552
  for iter_i in element.iterations:
571
- for elem_ID in iter_i.get_element_dependencies(as_objects=False):
572
- if (
573
- elem_ID.task_insert_ID == self.task.insert_ID
574
- and elem_ID.element_idx == self.element.index
575
- and dependent_elem not in deps
576
- ):
577
- deps.append(dependent_elem)
553
+ for dep_iter_i in iter_i.get_element_iteration_dependencies(
554
+ as_objects=True
555
+ ):
556
+ if dep_iter_i.id_ == self.id_ and element.id_ not in deps:
557
+ deps.append(element.id_)
578
558
 
579
559
  deps = sorted(deps)
580
560
  if as_objects:
@@ -589,14 +569,13 @@ class ElementIteration:
589
569
  """Get downstream tasks that depend on this element iteration."""
590
570
  deps = []
591
571
  for task in self.task.downstream_tasks:
592
- for elem_ID in task.get_element_dependencies(as_objects=False):
593
- if (
594
- elem_ID.task_insert_ID == self.task.insert_ID
595
- and elem_ID.element_idx == self.element.index
596
- and task.insert_ID not in deps
597
- ):
598
- deps.append(task.insert_ID)
599
-
572
+ for element in task.elements[:]:
573
+ for iter_i in element.iterations:
574
+ for dep_iter_i in iter_i.get_element_iteration_dependencies(
575
+ as_objects=True
576
+ ):
577
+ if dep_iter_i.id_ == self.id_ and task.insert_ID not in deps:
578
+ deps.append(task.insert_ID)
600
579
  deps = sorted(deps)
601
580
  if as_objects:
602
581
  deps = [self.workflow.tasks.get(insert_ID=i) for i in deps]
@@ -614,19 +593,25 @@ class Element:
614
593
 
615
594
  def __init__(
616
595
  self,
596
+ id_: int,
597
+ is_pending: bool,
617
598
  task: app.WorkflowTask,
618
599
  index: int,
619
600
  es_idx: int,
620
601
  seq_idx: Dict[str, int],
621
602
  src_idx: Dict[str, int],
622
- iterations,
603
+ iteration_IDs: List[int],
604
+ iterations: List[Dict],
623
605
  ) -> None:
606
+ self._id = id_
607
+ self._is_pending = is_pending
624
608
  self._task = task
625
609
  self._index = index
626
610
  self._es_idx = es_idx
627
611
  self._seq_idx = seq_idx
628
612
  self._src_idx = src_idx
629
613
 
614
+ self._iteration_IDs = iteration_IDs
630
615
  self._iterations = iterations
631
616
 
632
617
  # assigned on first access:
@@ -634,11 +619,19 @@ class Element:
634
619
 
635
620
  def __repr__(self):
636
621
  return (
637
- f"{self.__class__.__name__}("
638
- f"task={self.task.unique_name!r}, index={self.index!r}"
622
+ f"{self.__class__.__name__}(id={self.id_!r}, "
623
+ f"index={self.index!r}, task={self.task.unique_name!r}"
639
624
  f")"
640
625
  )
641
626
 
627
+ @property
628
+ def id_(self) -> int:
629
+ return self._id
630
+
631
+ @property
632
+ def is_pending(self) -> bool:
633
+ return self._is_pending
634
+
642
635
  @property
643
636
  def task(self) -> app.WorkflowTask:
644
637
  return self._task
@@ -652,13 +645,6 @@ class Element:
652
645
 
653
646
  return self._index
654
647
 
655
- @property
656
- def element_ID(self):
657
- return ElementID(
658
- task_insert_ID=self.task.insert_ID,
659
- element_idx=self.index,
660
- )
661
-
662
648
  @property
663
649
  def element_set_idx(self) -> int:
664
650
  return self._es_idx
@@ -686,12 +672,21 @@ class Element:
686
672
  def workflow(self) -> app.Workflow:
687
673
  return self.task.workflow
688
674
 
675
+ @property
676
+ def iteration_IDs(self) -> List[int]:
677
+ return self._iteration_IDs
678
+
689
679
  @property
690
680
  def iterations(self) -> Dict[app.ElementAction]:
681
+ # TODO: fix this
691
682
  if self._iteration_objs is None:
692
683
  self._iteration_objs = [
693
- self.app.ElementIteration(element=self, **iter_i)
694
- for iter_i in self._iterations
684
+ self.app.ElementIteration(
685
+ element=self,
686
+ index=idx,
687
+ **{k: v for k, v in iter_i.items() if k != "element_ID"},
688
+ )
689
+ for idx, iter_i in enumerate(self._iterations)
695
690
  ]
696
691
  return self._iteration_objs
697
692
 
@@ -835,13 +830,13 @@ class Element:
835
830
 
836
831
  def get_EAR_dependencies(
837
832
  self, as_objects: bool = False
838
- ) -> List[Union[EAR_idx_type, app.ElementActionRun]]:
833
+ ) -> List[Union[int, app.ElementActionRun]]:
839
834
  """Get EARs that the most recent iteration of this element depends on."""
840
835
  return self.latest_iteration.get_EAR_dependencies(as_objects=as_objects)
841
836
 
842
837
  def get_element_iteration_dependencies(
843
838
  self, as_objects: bool = False
844
- ) -> List[Union[EI_idx_type, app.ElementIteration]]:
839
+ ) -> List[Union[int, app.ElementIteration]]:
845
840
  """Get element iterations that the most recent iteration of this element depends
846
841
  on."""
847
842
  return self.latest_iteration.get_element_iteration_dependencies(
@@ -850,7 +845,7 @@ class Element:
850
845
 
851
846
  def get_element_dependencies(
852
847
  self, as_objects: bool = False
853
- ) -> List[Union[E_idx_type, app.Element]]:
848
+ ) -> List[Union[int, app.Element]]:
854
849
  """Get elements that the most recent iteration of this element depends on."""
855
850
  return self.latest_iteration.get_element_dependencies(as_objects=as_objects)
856
851
 
@@ -871,13 +866,13 @@ class Element:
871
866
 
872
867
  def get_dependent_EARs(
873
868
  self, as_objects: bool = False
874
- ) -> List[Union[EAR_idx_type, app.ElementActionRun]]:
869
+ ) -> List[Union[int, app.ElementActionRun]]:
875
870
  """Get EARs that depend on the most recent iteration of this element."""
876
871
  return self.latest_iteration.get_dependent_EARs(as_objects=as_objects)
877
872
 
878
873
  def get_dependent_element_iterations(
879
874
  self, as_objects: bool = False
880
- ) -> List[Union[EI_idx_type, app.ElementIteration]]:
875
+ ) -> List[Union[int, app.ElementIteration]]:
881
876
  """Get element iterations that depend on the most recent iteration of this
882
877
  element."""
883
878
  return self.latest_iteration.get_dependent_element_iterations(
@@ -886,7 +881,7 @@ class Element:
886
881
 
887
882
  def get_dependent_elements(
888
883
  self, as_objects: bool = False
889
- ) -> List[Union[E_idx_type, app.Element]]:
884
+ ) -> List[Union[int, app.Element]]:
890
885
  """Get elements that depend on the most recent iteration of this element."""
891
886
  return self.latest_iteration.get_dependent_elements(as_objects=as_objects)
892
887
 
@@ -901,7 +896,7 @@ class Element:
901
896
  dependencies.
902
897
 
903
898
  Dependencies are resolved using the initial iteration only. This method is used to
904
- identify from which element in the previous iteration and new iteration should be
899
+ identify from which element in the previous iteration a new iteration should be
905
900
  parametrised.
906
901
 
907
902
  Parameters
@@ -921,7 +916,8 @@ class Element:
921
916
  all_deps = get_deps(self)
922
917
 
923
918
  if task_insert_ID is not None:
924
- all_deps = [i for i in all_deps if i.task_insert_ID == task_insert_ID]
919
+ elem_ID_subset = self.workflow.tasks.get(insert_ID=task_insert_ID).element_IDs
920
+ all_deps = [i for i in all_deps if i in elem_ID_subset]
925
921
 
926
922
  return self.workflow.get_elements_from_IDs(sorted(all_deps))
927
923
 
@@ -1,3 +1,6 @@
1
+ from typing import Iterable
2
+
3
+
1
4
  class InputValueDuplicateSequenceAddress(ValueError):
2
5
  pass
3
6
 
@@ -188,3 +191,57 @@ class UnsupportedShellError(ValueError):
188
191
  """We don't support this shell on this OS."""
189
192
 
190
193
  pass
194
+
195
+
196
+ class _MissingStoreItemError(ValueError):
197
+ def __init__(self, id_lst: Iterable[int], item_type: str) -> None:
198
+ message = (
199
+ f"Store {item_type}s with the following IDs do not all exist: {id_lst!r}"
200
+ )
201
+ super().__init__(message)
202
+ self.id_lst = id_lst
203
+
204
+
205
+ class MissingStoreTaskError(_MissingStoreItemError):
206
+ """Some task IDs do not exist."""
207
+
208
+ _item_type = "task"
209
+
210
+ def __init__(self, id_lst: Iterable[int]) -> None:
211
+ super().__init__(id_lst, self._item_type)
212
+
213
+
214
+ class MissingStoreElementError(_MissingStoreItemError):
215
+ """Some element IDs do not exist."""
216
+
217
+ _item_type = "element"
218
+
219
+ def __init__(self, id_lst: Iterable[int]) -> None:
220
+ super().__init__(id_lst, self._item_type)
221
+
222
+
223
+ class MissingStoreElementIterationError(_MissingStoreItemError):
224
+ """Some element iteration IDs do not exist."""
225
+
226
+ _item_type = "element iteration"
227
+
228
+ def __init__(self, id_lst: Iterable[int]) -> None:
229
+ super().__init__(id_lst, self._item_type)
230
+
231
+
232
+ class MissingStoreEARError(_MissingStoreItemError):
233
+ """Some EAR IDs do not exist."""
234
+
235
+ _item_type = "EAR"
236
+
237
+ def __init__(self, id_lst: Iterable[int]) -> None:
238
+ super().__init__(id_lst, self._item_type)
239
+
240
+
241
+ class MissingParameterData(_MissingStoreItemError):
242
+ """Some parameter IDs do not exist"""
243
+
244
+ _item_type = "parameter"
245
+
246
+ def __init__(self, id_lst: Iterable[int]) -> None:
247
+ super().__init__(id_lst, self._item_type)