hpcflow-new2 0.2.0a162__py3-none-any.whl → 0.2.0a164__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 (37) hide show
  1. hpcflow/_version.py +1 -1
  2. hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +7 -0
  3. hpcflow/sdk/app.py +29 -42
  4. hpcflow/sdk/cli.py +1 -1
  5. hpcflow/sdk/core/actions.py +87 -21
  6. hpcflow/sdk/core/command_files.py +6 -4
  7. hpcflow/sdk/core/commands.py +21 -2
  8. hpcflow/sdk/core/element.py +39 -8
  9. hpcflow/sdk/core/errors.py +16 -0
  10. hpcflow/sdk/core/object_list.py +26 -14
  11. hpcflow/sdk/core/parameters.py +21 -3
  12. hpcflow/sdk/core/task.py +111 -4
  13. hpcflow/sdk/core/task_schema.py +17 -2
  14. hpcflow/sdk/core/test_utils.py +5 -2
  15. hpcflow/sdk/core/workflow.py +93 -5
  16. hpcflow/sdk/data/workflow_spec_schema.yaml +14 -58
  17. hpcflow/sdk/demo/cli.py +1 -1
  18. hpcflow/sdk/persistence/base.py +6 -0
  19. hpcflow/sdk/persistence/zarr.py +2 -0
  20. hpcflow/sdk/submission/submission.py +21 -10
  21. hpcflow/tests/scripts/test_main_scripts.py +60 -0
  22. hpcflow/tests/unit/test_action.py +186 -0
  23. hpcflow/tests/unit/test_element.py +27 -25
  24. hpcflow/tests/unit/test_element_set.py +32 -0
  25. hpcflow/tests/unit/test_parameter.py +11 -9
  26. hpcflow/tests/unit/test_persistence.py +4 -1
  27. hpcflow/tests/unit/test_resources.py +7 -9
  28. hpcflow/tests/unit/test_schema_input.py +8 -8
  29. hpcflow/tests/unit/test_task.py +26 -27
  30. hpcflow/tests/unit/test_task_schema.py +39 -8
  31. hpcflow/tests/unit/test_value_sequence.py +5 -0
  32. hpcflow/tests/unit/test_workflow.py +4 -9
  33. hpcflow/tests/unit/test_workflow_template.py +122 -1
  34. {hpcflow_new2-0.2.0a162.dist-info → hpcflow_new2-0.2.0a164.dist-info}/METADATA +1 -1
  35. {hpcflow_new2-0.2.0a162.dist-info → hpcflow_new2-0.2.0a164.dist-info}/RECORD +37 -36
  36. {hpcflow_new2-0.2.0a162.dist-info → hpcflow_new2-0.2.0a164.dist-info}/WHEEL +0 -0
  37. {hpcflow_new2-0.2.0a162.dist-info → hpcflow_new2-0.2.0a164.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/demo/cli.py CHANGED
@@ -184,7 +184,7 @@ def get_demo_workflow_CLI(app):
184
184
  status=status,
185
185
  )
186
186
  if print_idx:
187
- click.echo(out)
187
+ click.echo(out[1])
188
188
 
189
189
  @demo_workflow.command("copy")
190
190
  @click.argument("workflow_name")
@@ -310,6 +310,7 @@ class StoreEAR:
310
310
  is_pending: bool
311
311
  elem_iter_ID: int
312
312
  action_idx: int
313
+ commands_idx: List[int]
313
314
  data_idx: Dict[str, int]
314
315
  submission_idx: Optional[int] = None
315
316
  skip: Optional[bool] = False
@@ -336,6 +337,7 @@ class StoreEAR:
336
337
  "id_": self.id_,
337
338
  "elem_iter_ID": self.elem_iter_ID,
338
339
  "action_idx": self.action_idx,
340
+ "commands_idx": self.commands_idx,
339
341
  "data_idx": self.data_idx,
340
342
  "submission_idx": self.submission_idx,
341
343
  "success": self.success,
@@ -371,6 +373,7 @@ class StoreEAR:
371
373
  "is_pending": self.is_pending,
372
374
  "elem_iter_ID": self.elem_iter_ID,
373
375
  "action_idx": self.action_idx,
376
+ "commands_idx": self.commands_idx,
374
377
  "data_idx": self.data_idx,
375
378
  "submission_idx": self.submission_idx,
376
379
  "success": self.success,
@@ -414,6 +417,7 @@ class StoreEAR:
414
417
  is_pending=self.is_pending,
415
418
  elem_iter_ID=self.elem_iter_ID,
416
419
  action_idx=self.action_idx,
420
+ commands_idx=self.commands_idx,
417
421
  data_idx=self.data_idx,
418
422
  metadata=self.metadata,
419
423
  submission_idx=sub_idx,
@@ -1013,6 +1017,7 @@ class PersistentStore(ABC):
1013
1017
  self,
1014
1018
  elem_iter_ID: int,
1015
1019
  action_idx: int,
1020
+ commands_idx: List[int],
1016
1021
  data_idx: Dict,
1017
1022
  metadata: Dict,
1018
1023
  save: bool = True,
@@ -1025,6 +1030,7 @@ class PersistentStore(ABC):
1025
1030
  is_pending=True,
1026
1031
  elem_iter_ID=elem_iter_ID,
1027
1032
  action_idx=action_idx,
1033
+ commands_idx=commands_idx,
1028
1034
  data_idx=data_idx,
1029
1035
  metadata=metadata,
1030
1036
  )
@@ -237,6 +237,7 @@ class ZarrStoreEAR(StoreEAR):
237
237
  self.exit_code,
238
238
  self.metadata,
239
239
  self.run_hostname,
240
+ self.commands_idx,
240
241
  ]
241
242
  return EAR_enc
242
243
 
@@ -258,6 +259,7 @@ class ZarrStoreEAR(StoreEAR):
258
259
  "exit_code": EAR_dat[11],
259
260
  "metadata": EAR_dat[12],
260
261
  "run_hostname": EAR_dat[13],
262
+ "commands_idx": EAR_dat[14],
261
263
  }
262
264
  return cls(is_pending=False, **obj_dat)
263
265
 
@@ -15,9 +15,11 @@ from hpcflow.sdk.core.errors import (
15
15
  MissingEnvironmentError,
16
16
  MissingEnvironmentExecutableError,
17
17
  MissingEnvironmentExecutableInstanceError,
18
+ MultipleEnvironmentsError,
18
19
  SubmissionFailure,
19
20
  )
20
21
  from hpcflow.sdk.core.json_like import ChildObjectSpec, JSONLike
22
+ from hpcflow.sdk.core.object_list import ObjectListMultipleMatchError
21
23
  from hpcflow.sdk.log import TimeIt
22
24
 
23
25
 
@@ -90,31 +92,40 @@ class Submission(JSONLike):
90
92
  req_envs = defaultdict(lambda: defaultdict(set))
91
93
  for js_idx, js_i in enumerate(self.jobscripts):
92
94
  for run in js_i.all_EARs:
93
- env_label = run.action.get_environment_label()
95
+ env_spec_h = tuple(zip(*run.env_spec.items())) # hashable
94
96
  for exec_label_j in run.action.get_required_executables():
95
- req_envs[env_label][exec_label_j].add(js_idx)
96
- if env_label not in req_envs:
97
- req_envs[env_label] = {}
97
+ req_envs[env_spec_h][exec_label_j].add(js_idx)
98
+ if env_spec_h not in req_envs:
99
+ req_envs[env_spec_h] = {}
98
100
 
99
101
  # check these envs/execs exist in app data:
100
102
  envs = []
101
- for env_lab, exec_js in req_envs.items():
103
+ for env_spec_h, exec_js in req_envs.items():
104
+ env_spec = dict(zip(*env_spec_h))
105
+ non_name_spec = {k: v for k, v in env_spec.items() if k != "name"}
106
+ spec_str = f" with specifiers {non_name_spec!r}" if non_name_spec else ""
107
+ env_ref = f"{env_spec['name']!r}{spec_str}"
102
108
  try:
103
- env_i = self.app.envs.get(env_lab)
109
+ env_i = self.app.envs.get(**env_spec)
110
+ except ObjectListMultipleMatchError:
111
+ raise MultipleEnvironmentsError(
112
+ f"Multiple environments {env_ref} are defined on this machine."
113
+ )
104
114
  except ValueError:
105
115
  raise MissingEnvironmentError(
106
- f"The environment {env_lab!r} is not defined on this machine, so the "
116
+ f"The environment {env_ref} is not defined on this machine, so the "
107
117
  f"submission cannot be created."
108
118
  ) from None
109
119
  else:
110
- envs.append(env_i)
120
+ if env_i not in envs:
121
+ envs.append(env_i)
111
122
 
112
123
  for exec_i_lab, js_idx_set in exec_js.items():
113
124
  try:
114
125
  exec_i = env_i.executables.get(exec_i_lab)
115
126
  except ValueError:
116
127
  raise MissingEnvironmentExecutableError(
117
- f"The environment {env_lab!r} as defined on this machine has no "
128
+ f"The environment {env_ref} as defined on this machine has no "
118
129
  f"executable labelled {exec_i_lab!r}, which is required for this "
119
130
  f"submission, so the submission cannot be created."
120
131
  ) from None
@@ -127,7 +138,7 @@ class Submission(JSONLike):
127
138
  if not exec_instances:
128
139
  raise MissingEnvironmentExecutableInstanceError(
129
140
  f"No matching executable instances found for executable "
130
- f"{exec_i_lab!r} of environment {env_lab!r} for jobscript "
141
+ f"{exec_i_lab!r} of environment {env_ref} for jobscript "
131
142
  f"index {js_idx_j!r} with requested resources "
132
143
  f"{filter_exec!r}."
133
144
  )
@@ -447,3 +447,63 @@ def test_script_hdf5_out_obj(null_config, tmp_path):
447
447
  # to be later Python versions):
448
448
  time.sleep(10)
449
449
  assert wk.tasks[0].elements[0].outputs.p1c.value == P1(a=p1_val + 100)
450
+
451
+
452
+ @pytest.mark.integration
453
+ @pytest.mark.skipif("hf.run_time_info.is_frozen")
454
+ def test_script_direct_in_pass_env_spec(new_null_config, tmp_path):
455
+
456
+ vers_spec = {"version": "1.2"}
457
+ env = hf.Environment(
458
+ name="python_env_with_specifiers",
459
+ specifiers=vers_spec,
460
+ executables=[
461
+ hf.Executable(
462
+ label="python_script",
463
+ instances=[
464
+ hf.ExecutableInstance(
465
+ command="python <<script_name>> <<args>>",
466
+ num_cores=1,
467
+ parallel_mode=None,
468
+ )
469
+ ],
470
+ )
471
+ ],
472
+ )
473
+ hf.envs.add_object(env, skip_duplicates=True)
474
+
475
+ s1 = hf.TaskSchema(
476
+ objective="t1",
477
+ inputs=[hf.SchemaInput(parameter=hf.Parameter("p1"))],
478
+ outputs=[hf.SchemaOutput(parameter=hf.Parameter("p2"))],
479
+ actions=[
480
+ hf.Action(
481
+ script="<<script:main_script_test_direct_in_direct_out_env_spec.py>>",
482
+ script_data_in="direct",
483
+ script_data_out="direct",
484
+ script_exe="python_script",
485
+ script_pass_env_spec=True,
486
+ environments=[
487
+ hf.ActionEnvironment(environment="python_env_with_specifiers")
488
+ ],
489
+ )
490
+ ],
491
+ )
492
+ t1 = hf.Task(
493
+ schema=s1,
494
+ inputs={"p1": 101},
495
+ environments={"python_env_with_specifiers": vers_spec},
496
+ )
497
+ wk = hf.Workflow.from_template_data(
498
+ tasks=[t1],
499
+ template_name="main_script_test",
500
+ path=tmp_path,
501
+ )
502
+ wk.submit(wait=True, add_to_known=False)
503
+ # TODO: investigate why the value is not always populated on GHA Ubuntu runners (tends
504
+ # to be later Python versions):
505
+ time.sleep(10)
506
+ assert wk.tasks[0].elements[0].outputs.p2.value == {
507
+ "name": "python_env_with_specifiers",
508
+ **vers_spec,
509
+ }
@@ -3,6 +3,7 @@ import pytest
3
3
 
4
4
  from hpcflow.app import app as hf
5
5
  from hpcflow.sdk.core.errors import (
6
+ ActionEnvironmentMissingNameError,
6
7
  UnknownScriptDataKey,
7
8
  UnknownScriptDataParameter,
8
9
  UnsupportedScriptDataFormat,
@@ -602,3 +603,188 @@ def test_process_script_data_in_fmt_dict_mixed(null_config):
602
603
  "p1": {"format": "json"},
603
604
  "p2": {"format": "hdf5"},
604
605
  }
606
+
607
+
608
+ def test_ActionEnvironment_env_str(null_config):
609
+ act_env = hf.ActionEnvironment(environment="my_env")
610
+ assert act_env.environment == {"name": "my_env"}
611
+
612
+
613
+ def test_ActionEnvironment_env_dict(null_config):
614
+ act_env = hf.ActionEnvironment(environment={"name": "my_env", "key": "value"})
615
+ assert act_env.environment == {"name": "my_env", "key": "value"}
616
+
617
+
618
+ def test_ActionEnvironment_raises_on_missing_name(null_config):
619
+ with pytest.raises(ActionEnvironmentMissingNameError):
620
+ hf.ActionEnvironment(environment={"key": "value"})
621
+
622
+
623
+ def test_rules_allow_runs_initialised(null_config, tmp_path):
624
+ """Test rules that do not depend on execution allow for runs to be initialised."""
625
+ act = hf.Action(
626
+ script="<<script:path/to/some/script>>",
627
+ rules=[hf.ActionRule(path="inputs.p1", condition={"value.less_than": 2})],
628
+ )
629
+ ts = hf.TaskSchema(
630
+ objective="ts1",
631
+ inputs=[hf.SchemaInput("p1")],
632
+ actions=[act],
633
+ )
634
+ t1 = hf.Task(
635
+ schema=ts, sequences=[hf.ValueSequence(path="inputs.p1", values=[1.5, 2.5])]
636
+ )
637
+ wk = hf.Workflow.from_template_data(
638
+ template_name="test",
639
+ path=tmp_path,
640
+ tasks=[t1],
641
+ )
642
+ assert wk.tasks[0].elements[0].iterations[0].EARs_initialised
643
+ assert wk.tasks[0].elements[1].iterations[0].EARs_initialised
644
+ assert len(wk.tasks[0].elements[0].actions) == 1
645
+ assert len(wk.tasks[0].elements[1].actions) == 0
646
+
647
+
648
+ def test_rules_prevent_runs_initialised(null_config, tmp_path):
649
+ """Test rules that depend on execution prevent initialising runs."""
650
+ act1 = hf.Action(script="<<script:path/to/some/script>>")
651
+ act2 = hf.Action(
652
+ script="<<script:path/to/some/script>>",
653
+ rules=[hf.ActionRule(path="inputs.p2", condition={"value.less_than": 2})],
654
+ )
655
+ ts1 = hf.TaskSchema(
656
+ objective="ts1",
657
+ inputs=[hf.SchemaInput("p1")],
658
+ outputs=[hf.SchemaOutput("p2")],
659
+ actions=[act1],
660
+ )
661
+ ts2 = hf.TaskSchema(
662
+ objective="ts2",
663
+ inputs=[hf.SchemaInput("p2")],
664
+ actions=[act2],
665
+ )
666
+ t1 = hf.Task(schema=ts1, inputs={"p1": 1.2})
667
+ t2 = hf.Task(schema=ts2)
668
+ wk = hf.Workflow.from_template_data(
669
+ template_name="test",
670
+ path=tmp_path,
671
+ tasks=[t1, t2],
672
+ )
673
+ assert wk.tasks[0].elements[0].iterations[0].EARs_initialised
674
+ assert not wk.tasks[1].elements[0].iterations[0].EARs_initialised
675
+
676
+
677
+ def test_command_rules_allow_runs_initialised(null_config, tmp_path):
678
+ """Test command rules that do not depend on execution allow for runs to be
679
+ initialised."""
680
+ act = hf.Action(
681
+ commands=[
682
+ hf.Command(
683
+ command='echo "p1=<<parameter:p1>>"',
684
+ rules=[hf.ActionRule(path="inputs.p1", condition={"value.less_than": 2})],
685
+ )
686
+ ],
687
+ )
688
+ ts = hf.TaskSchema(
689
+ objective="ts1",
690
+ inputs=[hf.SchemaInput("p1")],
691
+ actions=[act],
692
+ )
693
+ t1 = hf.Task(
694
+ schema=ts, sequences=[hf.ValueSequence(path="inputs.p1", values=[1.5, 2.5])]
695
+ )
696
+ wk = hf.Workflow.from_template_data(
697
+ template_name="test",
698
+ path=tmp_path,
699
+ tasks=[t1],
700
+ )
701
+ assert wk.tasks[0].elements[0].iterations[0].EARs_initialised
702
+ assert wk.tasks[0].elements[1].iterations[0].EARs_initialised
703
+ assert len(wk.tasks[0].elements[0].actions) == 1
704
+ assert len(wk.tasks[0].elements[1].actions) == 1
705
+ assert len(wk.tasks[0].elements[0].action_runs[0].commands_idx) == 1
706
+ assert len(wk.tasks[0].elements[1].action_runs[0].commands_idx) == 0
707
+
708
+
709
+ def test_command_rules_prevent_runs_initialised(null_config, tmp_path):
710
+ """Test command rules that do depend on execution prevent runs being initialised."""
711
+ act1 = hf.Action(
712
+ commands=[
713
+ hf.Command(command='echo "p1=<<parameter:p1>>"', stdout="<<parameter:p2>>")
714
+ ]
715
+ )
716
+ act2 = hf.Action(
717
+ commands=[
718
+ hf.Command(
719
+ command='echo "p1=<<parameter:p2>>"',
720
+ rules=[hf.ActionRule(path="inputs.p2", condition={"value.less_than": 2})],
721
+ )
722
+ ],
723
+ )
724
+ ts1 = hf.TaskSchema(
725
+ objective="ts1",
726
+ inputs=[hf.SchemaInput("p1")],
727
+ outputs=[hf.SchemaOutput("p2")],
728
+ actions=[act1],
729
+ )
730
+ ts2 = hf.TaskSchema(
731
+ objective="ts2",
732
+ inputs=[hf.SchemaInput("p2")],
733
+ actions=[act2],
734
+ )
735
+ t1 = hf.Task(schema=ts1, inputs={"p1": 0})
736
+ t2 = hf.Task(schema=ts2)
737
+ wk = hf.Workflow.from_template_data(
738
+ template_name="test",
739
+ path=tmp_path,
740
+ tasks=[t1, t2],
741
+ )
742
+ assert wk.tasks[0].elements[0].iterations[0].EARs_initialised
743
+ assert len(wk.tasks[0].elements[0].action_runs[0].commands_idx) == 1
744
+ assert not wk.tasks[1].elements[0].iterations[0].EARs_initialised
745
+
746
+
747
+ def test_command_rules_prevent_runs_initialised_with_valid_action_rules(
748
+ null_config, tmp_path
749
+ ):
750
+ """Test command rules that do depend on execution prevent runs being initialised, even
751
+ when the parent action rules can be tested and are valid."""
752
+ act1 = hf.Action(
753
+ commands=[
754
+ hf.Command(command='echo "p1=<<parameter:p1>>"', stdout="<<parameter:p2>>")
755
+ ]
756
+ )
757
+
758
+ # action rule is testable and valid, but command rule is not testable, so the action
759
+ # runs should not be initialised:
760
+ act2 = hf.Action(
761
+ commands=[
762
+ hf.Command(
763
+ command='echo "p1=<<parameter:p1>>; p2=<<parameter:p2>>"',
764
+ rules=[hf.ActionRule(path="inputs.p2", condition={"value.less_than": 2})],
765
+ )
766
+ ],
767
+ rules=[hf.ActionRule(path="inputs.p1", condition={"value.less_than": 2})],
768
+ )
769
+ ts1 = hf.TaskSchema(
770
+ objective="ts1",
771
+ inputs=[hf.SchemaInput("p1")],
772
+ outputs=[hf.SchemaOutput("p2")],
773
+ actions=[act1],
774
+ )
775
+ ts2 = hf.TaskSchema(
776
+ objective="ts2",
777
+ inputs=[hf.SchemaInput("p1"), hf.SchemaInput("p2")],
778
+ actions=[act2],
779
+ )
780
+ t1 = hf.Task(schema=ts1, inputs={"p1": 0})
781
+ t2 = hf.Task(schema=ts2)
782
+ wk = hf.Workflow.from_template_data(
783
+ template_name="test",
784
+ path=tmp_path,
785
+ tasks=[t1, t2],
786
+ )
787
+ assert wk.tasks[0].elements[0].iterations[0].EARs_initialised
788
+ assert len(wk.tasks[0].elements[0].action_runs[0].commands_idx) == 1
789
+
790
+ assert not wk.tasks[1].elements[0].iterations[0].EARs_initialised
@@ -519,11 +519,12 @@ def test_element_get_group_sequence(null_config, tmp_path):
519
519
  wkt_yaml = dedent(
520
520
  """\
521
521
  name: test_list_idx_sequence
522
- task_schemas:
523
- - objective: test_group_schema
524
- inputs:
525
- - parameter: p1
526
- group: my_group
522
+ template_components:
523
+ task_schemas:
524
+ - objective: test_group_schema
525
+ inputs:
526
+ - parameter: p1
527
+ group: my_group
527
528
  tasks:
528
529
  - schema: test_t1_ps
529
530
  inputs:
@@ -556,26 +557,27 @@ def test_element_get_group_sequence_obj(new_null_config, tmp_path):
556
557
  wkt_yaml = dedent(
557
558
  """\
558
559
  name: test_list_idx_sequence
559
- task_schemas:
560
- - objective: test_t1_ps_obj
561
- parameter_class_modules: ["hpcflow.sdk.core.test_utils"]
562
- inputs:
563
- - parameter: p1c
564
- outputs:
565
- - parameter: p2
566
- actions:
567
- - environments:
568
- - scope:
569
- type: any
570
- environment: null_env
571
- commands:
572
- - command: Write-Output ((<<parameter:p1c>> + 100))
573
- stdout: <<parameter:p2>>
574
- - objective: test_group_schema
575
- parameter_class_modules: ["hpcflow.sdk.core.test_utils"]
576
- inputs:
577
- - parameter: p1c
578
- group: my_group
560
+ template_components:
561
+ task_schemas:
562
+ - objective: test_t1_ps_obj
563
+ parameter_class_modules: ["hpcflow.sdk.core.test_utils"]
564
+ inputs:
565
+ - parameter: p1c
566
+ outputs:
567
+ - parameter: p2
568
+ actions:
569
+ - environments:
570
+ - scope:
571
+ type: any
572
+ environment: null_env
573
+ commands:
574
+ - command: Write-Output ((<<parameter:p1c>> + 100))
575
+ stdout: <<parameter:p2>>
576
+ - objective: test_group_schema
577
+ parameter_class_modules: ["hpcflow.sdk.core.test_utils"]
578
+ inputs:
579
+ - parameter: p1c
580
+ group: my_group
579
581
 
580
582
  tasks:
581
583
  - schema: test_t1_ps_obj
@@ -69,3 +69,35 @@ def test_repeats_single_int_equivalence(null_config):
69
69
  es1 = hf.ElementSet(repeats=2)
70
70
  es2 = hf.ElementSet(repeats=[{"name": "", "number": 2, "nesting_order": 0}])
71
71
  assert es1 == es2
72
+
73
+
74
+ def test_merge_envs(null_config):
75
+ envs = {"my_env": {"version": "1.0"}}
76
+ es = hf.ElementSet(environments=envs)
77
+ assert es.resources.get(scope=hf.ActionScope.any()).environments == envs
78
+
79
+
80
+ def test_merge_envs_existing_any_resources(null_config):
81
+ envs = {"my_env": {"version": "1.0"}}
82
+ num_cores = 2
83
+ es = hf.ElementSet(resources={"any": {"num_cores": num_cores}}, environments=envs)
84
+ assert es.resources.get(scope=hf.ActionScope.any()).environments == envs
85
+ assert es.resources.get(scope=hf.ActionScope.any()).num_cores == num_cores
86
+
87
+
88
+ def test_merge_envs_resource_envs_precedence(null_config):
89
+ envs = {"my_env": {"version": "1.0"}}
90
+ res_envs = {"other_env": {"version": "2.0"}}
91
+ es = hf.ElementSet(resources={"any": {"environments": res_envs}}, environments=envs)
92
+ assert es.resources.get(scope=hf.ActionScope.any()).environments == res_envs
93
+
94
+
95
+ def test_merge_envs_no_envs_with_resource_envs(null_config):
96
+ envs = {"my_env": {"version": "1.0"}}
97
+ es = hf.ElementSet(resources={"any": {"environments": envs}})
98
+ assert es.resources.get(scope=hf.ActionScope.any()).environments == envs
99
+
100
+
101
+ def test_raise_env_and_envs_specified(null_config):
102
+ with pytest.raises(ValueError):
103
+ hf.ElementSet(env_preset="my_preset", environments={"my_env": {"version": 1}})
@@ -152,11 +152,12 @@ def test_demo_data_substitution_param_value_class_method(new_null_config, tmp_pa
152
152
  yaml_str = dedent(
153
153
  """\
154
154
  name: temp
155
- task_schemas:
156
- - objective: test
157
- inputs:
158
- - parameter: p1c
159
- parameter_class_modules: [hpcflow.sdk.core.test_utils]
155
+ template_components:
156
+ task_schemas:
157
+ - objective: test
158
+ inputs:
159
+ - parameter: p1c
160
+ parameter_class_modules: [hpcflow.sdk.core.test_utils]
160
161
  tasks:
161
162
  - schema: test
162
163
  inputs:
@@ -174,10 +175,11 @@ def test_demo_data_substitution_value_sequence_class_method(new_null_config, tmp
174
175
  yaml_str = dedent(
175
176
  """\
176
177
  name: temp
177
- task_schemas:
178
- - objective: test
179
- inputs:
180
- - parameter: p1
178
+ template_components:
179
+ task_schemas:
180
+ - objective: test
181
+ inputs:
182
+ - parameter: p1
181
183
  tasks:
182
184
  - schema: test
183
185
  sequences:
@@ -74,6 +74,7 @@ def test_store_pending_add_EAR(tmp_path):
74
74
  EAR_ID = store.add_EAR(
75
75
  elem_iter_ID=0,
76
76
  action_idx=0,
77
+ commands_idx=[],
77
78
  data_idx={},
78
79
  metadata={},
79
80
  )
@@ -83,6 +84,7 @@ def test_store_pending_add_EAR(tmp_path):
83
84
  is_pending=True,
84
85
  elem_iter_ID=0,
85
86
  action_idx=0,
87
+ commands_idx=[],
86
88
  data_idx={},
87
89
  metadata={},
88
90
  )
@@ -186,7 +188,7 @@ def test_get_task_elements_single_element_iter_EAR_pending(tmp_path):
186
188
  store = JSONPersistentStore.make_test_store_from_spec(
187
189
  [{"elements": [{"iterations": [{}]}]}], dir=tmp_path
188
190
  )
189
- store.add_EAR(elem_iter_ID=0, action_idx=0, data_idx={}, metadata={})
191
+ store.add_EAR(elem_iter_ID=0, action_idx=0, commands_idx=[], data_idx={}, metadata={})
190
192
  assert store.get_task_elements(0, slice(0, None)) == [
191
193
  {
192
194
  "id": 0,
@@ -209,6 +211,7 @@ def test_get_task_elements_single_element_iter_EAR_pending(tmp_path):
209
211
  "is_pending": True,
210
212
  "elem_iter_ID": 0,
211
213
  "action_idx": 0,
214
+ "commands_idx": [],
212
215
  "data_idx": {},
213
216
  "metadata": {},
214
217
  }
@@ -29,33 +29,31 @@ def test_resource_list_raise_on_identical_scopes():
29
29
  hf.ResourceList.normalise([{"scope": "any"}, {"scope": "any"}])
30
30
 
31
31
 
32
- def test_merge_template_resources_same_scope():
32
+ def test_merge_other_same_scope():
33
33
  res_lst_1 = hf.ResourceList.from_json_like({"any": {"num_cores": 1}})
34
34
  res_lst_2 = hf.ResourceList.from_json_like({"any": {}})
35
- res_lst_2.merge_template_resources(res_lst_1)
35
+ res_lst_2.merge_other(res_lst_1)
36
36
  assert res_lst_2 == hf.ResourceList.from_json_like({"any": {"num_cores": 1}})
37
37
 
38
38
 
39
- def test_merge_template_resources_same_scope_no_overwrite():
39
+ def test_merge_other_same_scope_no_overwrite():
40
40
  res_lst_1 = hf.ResourceList.from_json_like({"any": {"num_cores": 1}})
41
41
  res_lst_2 = hf.ResourceList.from_json_like({"any": {"num_cores": 2}})
42
- res_lst_2.merge_template_resources(res_lst_1)
42
+ res_lst_2.merge_other(res_lst_1)
43
43
  assert res_lst_2 == hf.ResourceList.from_json_like({"any": {"num_cores": 2}})
44
44
 
45
45
 
46
- def test_merge_template_resources_multi_scope():
46
+ def test_merge_other_multi_scope():
47
47
  res_lst_1 = hf.ResourceList.from_json_like({"any": {"num_cores": 1}})
48
48
  res_lst_2 = hf.ResourceList.from_json_like({"any": {}, "main": {"num_cores": 3}})
49
- res_lst_2.merge_template_resources(res_lst_1)
49
+ res_lst_2.merge_other(res_lst_1)
50
50
  assert res_lst_2 == hf.ResourceList.from_json_like(
51
51
  {"any": {"num_cores": 1}, "main": {"num_cores": 3}}
52
52
  )
53
53
 
54
54
 
55
55
  @pytest.mark.parametrize("store", ["json", "zarr"])
56
- def test_merge_template_resources_persistent_workflow_reload(
57
- null_config, tmp_path, store
58
- ):
56
+ def test_merge_other_persistent_workflow_reload(null_config, tmp_path, store):
59
57
  wkt = hf.WorkflowTemplate(
60
58
  name="test_load",
61
59
  resources={"any": {"num_cores": 2}},