hpcflow-new2 0.2.0a188__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.0a188.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.0a188.dist-info/RECORD +0 -158
- {hpcflow_new2-0.2.0a188.dist-info → hpcflow_new2-0.2.0a190.dist-info}/LICENSE +0 -0
- {hpcflow_new2-0.2.0a188.dist-info → hpcflow_new2-0.2.0a190.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a188.dist-info → hpcflow_new2-0.2.0a190.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/core/rule.py
CHANGED
@@ -3,16 +3,21 @@ Rules apply conditions to workflow elements or loops.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
from __future__ import annotations
|
6
|
-
from typing import
|
6
|
+
from typing import TYPE_CHECKING
|
7
7
|
|
8
|
-
from valida.conditions import ConditionLike
|
9
|
-
from valida
|
8
|
+
from valida.conditions import ConditionLike # type: ignore
|
9
|
+
from valida import Rule as ValidaRule # type: ignore
|
10
10
|
|
11
|
-
from hpcflow.sdk import app
|
12
11
|
from hpcflow.sdk.core.json_like import JSONLike
|
13
12
|
from hpcflow.sdk.core.utils import get_in_container
|
14
13
|
from hpcflow.sdk.log import TimeIt
|
15
14
|
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
from typing import Any
|
17
|
+
from typing_extensions import TypeIs
|
18
|
+
from .actions import Action, ElementActionRun
|
19
|
+
from .element import ElementIteration
|
20
|
+
|
16
21
|
|
17
22
|
class Rule(JSONLike):
|
18
23
|
"""
|
@@ -39,21 +44,24 @@ class Rule(JSONLike):
|
|
39
44
|
|
40
45
|
def __init__(
|
41
46
|
self,
|
42
|
-
check_exists:
|
43
|
-
check_missing:
|
44
|
-
path:
|
45
|
-
condition:
|
46
|
-
cast:
|
47
|
-
doc:
|
47
|
+
check_exists: str | None = None,
|
48
|
+
check_missing: str | None = None,
|
49
|
+
path: str | None = None,
|
50
|
+
condition: dict[str, Any] | ConditionLike | None = None,
|
51
|
+
cast: str | None = None,
|
52
|
+
doc: str | None = None,
|
48
53
|
):
|
49
|
-
if sum(
|
54
|
+
if sum(arg is not None for arg in (check_exists, check_missing, condition)) != 1:
|
50
55
|
raise ValueError(
|
51
56
|
"Specify either one of `check_exists`, `check_missing` or a `condition` "
|
52
57
|
"(and optional `path`)"
|
53
58
|
)
|
54
59
|
|
55
|
-
if isinstance(condition, dict):
|
56
|
-
condition
|
60
|
+
if not isinstance(condition, dict):
|
61
|
+
#: A general condition for this rule to check.
|
62
|
+
self.condition = condition
|
63
|
+
else:
|
64
|
+
self.condition = ConditionLike.from_json_like(condition)
|
57
65
|
|
58
66
|
#: If set, this rule checks this attribute exists.
|
59
67
|
self.check_exists = check_exists
|
@@ -61,14 +69,12 @@ class Rule(JSONLike):
|
|
61
69
|
self.check_missing = check_missing
|
62
70
|
#: Where to look up the attribute to check (if not determined by context).
|
63
71
|
self.path = path
|
64
|
-
#: A general condition for this rule to check.
|
65
|
-
self.condition = condition
|
66
72
|
#: If set, a cast to apply prior to running the general check.
|
67
73
|
self.cast = cast
|
68
74
|
#: Optional descriptive text.
|
69
75
|
self.doc = doc
|
70
76
|
|
71
|
-
def __repr__(self):
|
77
|
+
def __repr__(self) -> str:
|
72
78
|
out = f"{self.__class__.__name__}("
|
73
79
|
if self.check_exists:
|
74
80
|
out += f"check_exists={self.check_exists!r}"
|
@@ -84,26 +90,27 @@ class Rule(JSONLike):
|
|
84
90
|
out += ")"
|
85
91
|
return out
|
86
92
|
|
87
|
-
def __eq__(self, other):
|
88
|
-
if not isinstance(other,
|
93
|
+
def __eq__(self, other) -> bool:
|
94
|
+
if not isinstance(other, self.__class__):
|
89
95
|
return False
|
90
|
-
|
96
|
+
return (
|
91
97
|
self.check_exists == other.check_exists
|
92
98
|
and self.check_missing == other.check_missing
|
93
99
|
and self.path == other.path
|
94
100
|
and self.condition == other.condition
|
95
101
|
and self.cast == other.cast
|
96
102
|
and self.doc == other.doc
|
97
|
-
)
|
98
|
-
|
99
|
-
|
100
|
-
|
103
|
+
)
|
104
|
+
|
105
|
+
@classmethod
|
106
|
+
def __is_ElementIteration(cls, value) -> TypeIs[ElementIteration]:
|
107
|
+
return isinstance(value, cls._app.ElementIteration)
|
101
108
|
|
102
109
|
@TimeIt.decorator
|
103
110
|
def test(
|
104
111
|
self,
|
105
|
-
element_like:
|
106
|
-
action:
|
112
|
+
element_like: ElementIteration | ElementActionRun,
|
113
|
+
action: Action | None = None,
|
107
114
|
) -> bool:
|
108
115
|
"""Test if the rule evaluates to true or false for a given run, or element
|
109
116
|
iteration and action combination."""
|
@@ -111,33 +118,29 @@ class Rule(JSONLike):
|
|
111
118
|
task = element_like.task
|
112
119
|
schema_data_idx = element_like.data_idx
|
113
120
|
|
114
|
-
check
|
115
|
-
|
116
|
-
param_s = check.split(".")
|
117
|
-
if len(param_s) > 2:
|
121
|
+
if check := self.check_exists or self.check_missing:
|
122
|
+
if len(check.split(".")) > 2:
|
118
123
|
# sub-parameter, so need to try to retrieve parameter data
|
119
124
|
try:
|
120
125
|
task._get_merged_parameter_data(
|
121
126
|
schema_data_idx, raise_on_missing=True
|
122
127
|
)
|
123
|
-
return
|
128
|
+
return bool(self.check_exists)
|
124
129
|
except ValueError:
|
125
|
-
return
|
130
|
+
return not self.check_exists
|
126
131
|
else:
|
127
132
|
if self.check_exists:
|
128
133
|
return self.check_exists in schema_data_idx
|
129
134
|
elif self.check_missing:
|
130
135
|
return self.check_missing not in schema_data_idx
|
131
|
-
|
132
136
|
else:
|
133
|
-
if self.path.startswith("resources."):
|
134
|
-
|
135
|
-
|
137
|
+
if self.path and self.path.startswith("resources."):
|
138
|
+
if self.__is_ElementIteration(element_like):
|
139
|
+
assert action is not None
|
136
140
|
elem_res = element_like.get_resources(
|
137
141
|
action=action, set_defaults=True
|
138
142
|
)
|
139
|
-
|
140
|
-
# must be an `ElementActionRun`
|
143
|
+
else:
|
141
144
|
elem_res = element_like.get_resources()
|
142
145
|
|
143
146
|
res_path = self.path.split(".")[1:]
|
@@ -151,7 +154,22 @@ class Rule(JSONLike):
|
|
151
154
|
raise_on_unset=True,
|
152
155
|
)
|
153
156
|
# test the rule:
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
157
|
+
return self._valida_check(element_dat)
|
158
|
+
|
159
|
+
# Something bizarre was specified. Don't match it!
|
160
|
+
return False
|
161
|
+
|
162
|
+
def _valida_check(self, value: Any) -> bool:
|
163
|
+
"""
|
164
|
+
Check this rule against the specific object, under the assumption that we need
|
165
|
+
to use valida for the check. Does not do path tracing to select the object to
|
166
|
+
pass; that is the caller's responsibility.
|
167
|
+
"""
|
168
|
+
# note: Valida can't `rule.test` scalars yet, so wrap it in a list and set
|
169
|
+
# path to first element (see: https://github.com/hpcflow/valida/issues/9):
|
170
|
+
rule = ValidaRule(
|
171
|
+
path=[0],
|
172
|
+
condition=self.condition,
|
173
|
+
cast=self.cast,
|
174
|
+
)
|
175
|
+
return rule.test([value]).is_valid
|
@@ -2,72 +2,83 @@
|
|
2
2
|
Model of files in the run directory.
|
3
3
|
"""
|
4
4
|
|
5
|
+
from __future__ import annotations
|
5
6
|
import re
|
7
|
+
from typing import Any, TYPE_CHECKING
|
8
|
+
from hpcflow.sdk.core.app_aware import AppAware
|
6
9
|
from hpcflow.sdk.core.utils import JSONLikeDirSnapShot
|
7
10
|
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
from re import Pattern
|
13
|
+
from typing_extensions import ClassVar
|
14
|
+
from ..submission.shells.base import Shell
|
8
15
|
|
9
|
-
|
16
|
+
|
17
|
+
class RunDirAppFiles(AppAware):
|
10
18
|
"""A class to encapsulate the naming/recognition of app-created files within run
|
11
19
|
directories."""
|
12
20
|
|
13
|
-
|
14
|
-
|
15
|
-
_CMD_FILES_RE_PATTERN = r"js_\d+_act_\d+\.?\w*"
|
21
|
+
__CMD_FILES_RE_PATTERN: ClassVar[Pattern] = re.compile(r"js_\d+_act_\d+\.?\w*")
|
16
22
|
|
17
23
|
@classmethod
|
18
|
-
def get_log_file_name(cls):
|
24
|
+
def get_log_file_name(cls) -> str:
|
19
25
|
"""File name for the app log file."""
|
20
|
-
return f"{cls.
|
26
|
+
return f"{cls._app.package_name}.log"
|
21
27
|
|
22
28
|
@classmethod
|
23
|
-
def get_std_file_name(cls):
|
29
|
+
def get_std_file_name(cls) -> str:
|
24
30
|
"""File name for stdout and stderr streams from the app."""
|
25
|
-
return f"{cls.
|
31
|
+
return f"{cls._app.package_name}_std.txt"
|
26
32
|
|
27
33
|
@staticmethod
|
28
|
-
def get_run_file_prefix(js_idx: int, js_action_idx: int):
|
34
|
+
def get_run_file_prefix(js_idx: int | str, js_action_idx: int | str) -> str:
|
29
35
|
"""
|
30
36
|
Get the common prefix for files associated with a run.
|
31
37
|
"""
|
32
38
|
return f"js_{js_idx}_act_{js_action_idx}"
|
33
39
|
|
34
40
|
@classmethod
|
35
|
-
def get_commands_file_name(
|
41
|
+
def get_commands_file_name(
|
42
|
+
cls, js_idx: int | str, js_action_idx: int | str, shell: Shell
|
43
|
+
) -> str:
|
36
44
|
"""
|
37
45
|
Get the name of the file containing commands.
|
38
46
|
"""
|
39
47
|
return cls.get_run_file_prefix(js_idx, js_action_idx) + shell.JS_EXT
|
40
48
|
|
41
49
|
@classmethod
|
42
|
-
def get_run_param_dump_file_prefix(
|
50
|
+
def get_run_param_dump_file_prefix(
|
51
|
+
cls, js_idx: int | str, js_action_idx: int | str
|
52
|
+
) -> str:
|
43
53
|
"""Get the prefix to a file in the run directory that the app will dump parameter
|
44
54
|
data to."""
|
45
55
|
return cls.get_run_file_prefix(js_idx, js_action_idx) + "_inputs"
|
46
56
|
|
47
57
|
@classmethod
|
48
|
-
def get_run_param_load_file_prefix(
|
58
|
+
def get_run_param_load_file_prefix(
|
59
|
+
cls, js_idx: int | str, js_action_idx: int | str
|
60
|
+
) -> str:
|
49
61
|
"""Get the prefix to a file in the run directory that the app will load parameter
|
50
62
|
data from."""
|
51
63
|
return cls.get_run_file_prefix(js_idx, js_action_idx) + "_outputs"
|
52
64
|
|
53
65
|
@classmethod
|
54
|
-
def take_snapshot(cls):
|
55
|
-
"""
|
66
|
+
def take_snapshot(cls) -> dict[str, Any]:
|
67
|
+
"""
|
68
|
+
Take a :py:class:`JSONLikeDirSnapShot`, and process to ignore files created by
|
69
|
+
the app.
|
56
70
|
|
57
71
|
This includes command files that are invoked by jobscripts, the app log file, and
|
58
72
|
the app standard out/error file.
|
59
|
-
|
60
73
|
"""
|
61
74
|
snapshot = JSONLikeDirSnapShot()
|
62
75
|
snapshot.take(".")
|
63
76
|
ss_js = snapshot.to_json_like()
|
64
77
|
ss_js.pop("root_path") # always the current working directory of the run
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
):
|
71
|
-
ss_js["data"].pop(k)
|
78
|
+
excluded = {cls.get_log_file_name(), cls.get_std_file_name()}
|
79
|
+
data: dict[str, Any] = ss_js["data"]
|
80
|
+
for filename in tuple(data):
|
81
|
+
if filename in excluded or cls.__CMD_FILES_RE_PATTERN.match(filename):
|
82
|
+
data.pop(filename)
|
72
83
|
|
73
84
|
return ss_js
|