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.
- hpcflow/_version.py +1 -1
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +7 -0
- hpcflow/sdk/app.py +29 -42
- hpcflow/sdk/cli.py +1 -1
- hpcflow/sdk/core/actions.py +87 -21
- hpcflow/sdk/core/command_files.py +6 -4
- hpcflow/sdk/core/commands.py +21 -2
- hpcflow/sdk/core/element.py +39 -8
- hpcflow/sdk/core/errors.py +16 -0
- hpcflow/sdk/core/object_list.py +26 -14
- hpcflow/sdk/core/parameters.py +21 -3
- hpcflow/sdk/core/task.py +111 -4
- hpcflow/sdk/core/task_schema.py +17 -2
- hpcflow/sdk/core/test_utils.py +5 -2
- hpcflow/sdk/core/workflow.py +93 -5
- hpcflow/sdk/data/workflow_spec_schema.yaml +14 -58
- hpcflow/sdk/demo/cli.py +1 -1
- hpcflow/sdk/persistence/base.py +6 -0
- hpcflow/sdk/persistence/zarr.py +2 -0
- hpcflow/sdk/submission/submission.py +21 -10
- hpcflow/tests/scripts/test_main_scripts.py +60 -0
- hpcflow/tests/unit/test_action.py +186 -0
- hpcflow/tests/unit/test_element.py +27 -25
- hpcflow/tests/unit/test_element_set.py +32 -0
- hpcflow/tests/unit/test_parameter.py +11 -9
- hpcflow/tests/unit/test_persistence.py +4 -1
- hpcflow/tests/unit/test_resources.py +7 -9
- hpcflow/tests/unit/test_schema_input.py +8 -8
- hpcflow/tests/unit/test_task.py +26 -27
- hpcflow/tests/unit/test_task_schema.py +39 -8
- hpcflow/tests/unit/test_value_sequence.py +5 -0
- hpcflow/tests/unit/test_workflow.py +4 -9
- hpcflow/tests/unit/test_workflow_template.py +122 -1
- {hpcflow_new2-0.2.0a162.dist-info → hpcflow_new2-0.2.0a164.dist-info}/METADATA +1 -1
- {hpcflow_new2-0.2.0a162.dist-info → hpcflow_new2-0.2.0a164.dist-info}/RECORD +37 -36
- {hpcflow_new2-0.2.0a162.dist-info → hpcflow_new2-0.2.0a164.dist-info}/WHEEL +0 -0
- {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
hpcflow/sdk/persistence/base.py
CHANGED
@@ -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
|
)
|
hpcflow/sdk/persistence/zarr.py
CHANGED
@@ -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
|
-
|
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[
|
96
|
-
if
|
97
|
-
req_envs[
|
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
|
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(
|
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 {
|
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
|
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 {
|
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 {
|
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
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
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
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
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
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
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.
|
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
|
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.
|
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
|
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.
|
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
|
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}},
|