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/cli_common.py
CHANGED
@@ -1,17 +1,21 @@
|
|
1
1
|
"""Click CLI options that are used as decorators in multiple modules."""
|
2
2
|
|
3
|
+
from __future__ import annotations
|
3
4
|
import click
|
4
5
|
|
5
6
|
from hpcflow.sdk.core import ALL_TEMPLATE_FORMATS
|
6
|
-
from hpcflow.sdk.persistence import
|
7
|
+
from hpcflow.sdk.persistence.defaults import DEFAULT_STORE_FORMAT
|
8
|
+
from hpcflow.sdk.persistence.discovery import ALL_STORE_FORMATS
|
7
9
|
|
8
10
|
|
9
|
-
def sub_tasks_callback(ctx, param, value):
|
11
|
+
def sub_tasks_callback(ctx, param, value: str | None) -> list[int] | None:
|
10
12
|
"""
|
11
13
|
Parse subtasks.
|
12
14
|
"""
|
13
15
|
if value:
|
14
16
|
return [int(i) for i in value.split(",")]
|
17
|
+
else:
|
18
|
+
return None
|
15
19
|
|
16
20
|
|
17
21
|
#: Standard option
|
hpcflow/sdk/config/__init__.py
CHANGED
hpcflow/sdk/config/callbacks.py
CHANGED
@@ -1,44 +1,58 @@
|
|
1
1
|
"""Module that defines built-in callback functions for configuration item values."""
|
2
2
|
|
3
|
-
|
3
|
+
from __future__ import annotations
|
4
4
|
import os
|
5
5
|
import re
|
6
|
-
import fsspec
|
6
|
+
import fsspec # type: ignore
|
7
|
+
from typing import overload, TYPE_CHECKING
|
7
8
|
from hpcflow.sdk.core.errors import UnsupportedSchedulerError, UnsupportedShellError
|
8
|
-
|
9
9
|
from hpcflow.sdk.submission.shells import get_supported_shells
|
10
10
|
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
from typing import Any, TypeVar
|
13
|
+
from .config import Config
|
14
|
+
from ..typing import PathLike
|
15
|
+
|
16
|
+
T = TypeVar("T")
|
11
17
|
|
12
|
-
|
18
|
+
|
19
|
+
def callback_vars(config: Config, value) -> str:
|
13
20
|
"""
|
14
21
|
Callback that substitutes configuration variables.
|
15
22
|
"""
|
16
23
|
|
17
|
-
def vars_repl(match_obj):
|
18
|
-
|
19
|
-
return config._variables[var_name]
|
24
|
+
def vars_repl(match_obj: re.Match[str]) -> str:
|
25
|
+
return config._variables[match_obj[1]]
|
20
26
|
|
21
|
-
|
22
|
-
|
23
|
-
value = re.sub(
|
27
|
+
vars_regex = rf"\<\<({ '|'.join(config._variables) })\>\>"
|
28
|
+
return re.sub(
|
24
29
|
pattern=vars_regex,
|
25
30
|
repl=vars_repl,
|
26
31
|
string=str(value),
|
27
32
|
)
|
28
|
-
return value
|
29
33
|
|
30
34
|
|
31
|
-
|
35
|
+
@overload
|
36
|
+
def callback_file_paths(config: Config, file_path: PathLike) -> PathLike:
|
37
|
+
...
|
38
|
+
|
39
|
+
|
40
|
+
@overload
|
41
|
+
def callback_file_paths(config: Config, file_path: list[PathLike]) -> list[PathLike]:
|
42
|
+
...
|
43
|
+
|
44
|
+
|
45
|
+
def callback_file_paths(config: Config, file_path: PathLike | list[PathLike]):
|
32
46
|
"""
|
33
47
|
Callback that resolves file paths.
|
34
48
|
"""
|
35
49
|
if isinstance(file_path, list):
|
36
|
-
return [config._resolve_path(
|
50
|
+
return [config._resolve_path(path) for path in file_path]
|
37
51
|
else:
|
38
52
|
return config._resolve_path(file_path)
|
39
53
|
|
40
54
|
|
41
|
-
def callback_bool(config, value):
|
55
|
+
def callback_bool(config: Config, value: str | bool) -> bool:
|
42
56
|
"""
|
43
57
|
Callback that coerces values to boolean.
|
44
58
|
"""
|
@@ -52,19 +66,36 @@ def callback_bool(config, value):
|
|
52
66
|
return value
|
53
67
|
|
54
68
|
|
55
|
-
|
69
|
+
@overload
|
70
|
+
def callback_lowercase(config: Config, value: list[str]) -> list[str]:
|
71
|
+
...
|
72
|
+
|
73
|
+
|
74
|
+
@overload
|
75
|
+
def callback_lowercase(config: Config, value: dict[str, T]) -> dict[str, T]:
|
76
|
+
...
|
77
|
+
|
78
|
+
|
79
|
+
@overload
|
80
|
+
def callback_lowercase(config: Config, value: str) -> str:
|
81
|
+
...
|
82
|
+
|
83
|
+
|
84
|
+
def callback_lowercase(
|
85
|
+
config: Config, value: list[str] | dict[str, T] | str
|
86
|
+
) -> list[str] | dict[str, T] | str:
|
56
87
|
"""
|
57
88
|
Callback that forces a string to lower case.
|
58
89
|
"""
|
59
90
|
if isinstance(value, list):
|
60
|
-
return [
|
91
|
+
return [item.lower() for item in value]
|
61
92
|
elif isinstance(value, dict):
|
62
93
|
return {k.lower(): v for k, v in value.items()}
|
63
94
|
else:
|
64
95
|
return value.lower()
|
65
96
|
|
66
97
|
|
67
|
-
def exists_in_schedulers(config, value):
|
98
|
+
def exists_in_schedulers(config: Config, value: T) -> T:
|
68
99
|
"""
|
69
100
|
Callback that tests that a value is a supported scheduler name.
|
70
101
|
"""
|
@@ -77,21 +108,25 @@ def exists_in_schedulers(config, value):
|
|
77
108
|
return value
|
78
109
|
|
79
110
|
|
80
|
-
def callback_supported_schedulers(
|
111
|
+
def callback_supported_schedulers(
|
112
|
+
config: Config, schedulers: dict[str, Any]
|
113
|
+
) -> dict[str, Any]:
|
81
114
|
"""
|
82
115
|
Callback that tests that all values are names of supported schedulers.
|
83
116
|
"""
|
84
117
|
# validate against supported schedulers according to the OS - this won't validate that
|
85
118
|
# a particular scheduler actually exists on this system:
|
86
|
-
available = config._app.get_OS_supported_schedulers()
|
87
|
-
for k in schedulers:
|
88
|
-
|
89
|
-
raise UnsupportedSchedulerError(scheduler=k, available=available)
|
90
|
-
|
119
|
+
available = set(config._app.get_OS_supported_schedulers())
|
120
|
+
if any((witness := k) not in available for k in schedulers):
|
121
|
+
raise UnsupportedSchedulerError(scheduler=witness, available=available)
|
91
122
|
return schedulers
|
92
123
|
|
93
124
|
|
94
|
-
def
|
125
|
+
def _hostname_in_invocation(config: Config) -> bool:
|
126
|
+
return "hostname" in config._file.get_invocation(config._config_key)["match"]
|
127
|
+
|
128
|
+
|
129
|
+
def set_scheduler_invocation_match(config: Config, scheduler: str) -> None:
|
95
130
|
"""Invoked on set of `default_scheduler`.
|
96
131
|
|
97
132
|
For clusters with "proper" schedulers (SGE, SLURM, etc.), login nodes are typically
|
@@ -100,25 +135,25 @@ def set_scheduler_invocation_match(config, scheduler: str):
|
|
100
135
|
that on clusters the hostname match is explicitly set.
|
101
136
|
|
102
137
|
"""
|
103
|
-
default_args = config.get(f"schedulers.{scheduler}").get("defaults", {})
|
104
138
|
sched = config._app.get_scheduler(
|
105
139
|
scheduler_name=scheduler,
|
106
140
|
os_name=os.name,
|
107
|
-
scheduler_args=
|
141
|
+
scheduler_args=config.get(f"schedulers.{scheduler}").get("defaults", {}),
|
108
142
|
)
|
109
|
-
if
|
110
|
-
if
|
143
|
+
if isinstance(sched, config._app.QueuedScheduler):
|
144
|
+
if not _hostname_in_invocation(config):
|
111
145
|
config._file.update_invocation(
|
112
146
|
config_key=config._config_key,
|
113
147
|
match={"hostname": sched.DEFAULT_LOGIN_NODE_MATCH},
|
114
148
|
)
|
115
149
|
|
116
150
|
|
117
|
-
def callback_scheduler_set_up(
|
151
|
+
def callback_scheduler_set_up(
|
152
|
+
config: Config, schedulers: dict[str, Any]
|
153
|
+
) -> dict[str, Any]:
|
118
154
|
"""Invoked on set of `schedulers`.
|
119
155
|
|
120
156
|
Runs scheduler-specific config initialisation.
|
121
|
-
|
122
157
|
"""
|
123
158
|
for k, v in schedulers.items():
|
124
159
|
sched = config._app.get_scheduler(
|
@@ -126,19 +161,19 @@ def callback_scheduler_set_up(config, schedulers):
|
|
126
161
|
os_name=os.name,
|
127
162
|
scheduler_args=v.get("defaults", {}),
|
128
163
|
)
|
129
|
-
|
130
|
-
|
164
|
+
|
165
|
+
if isinstance(sched, config._app.SGEPosix):
|
166
|
+
# some `QueuedScheduler` classes have a `get_login_nodes` method which can be used
|
131
167
|
# to populate the names of login nodes explicitly, if not already set:
|
132
|
-
if
|
133
|
-
login_nodes = sched.get_login_nodes()
|
168
|
+
if not _hostname_in_invocation(config):
|
134
169
|
config._file.update_invocation(
|
135
170
|
config_key=config._config_key,
|
136
|
-
match={"hostname":
|
171
|
+
match={"hostname": sched.get_login_nodes()},
|
137
172
|
)
|
138
173
|
return schedulers
|
139
174
|
|
140
175
|
|
141
|
-
def callback_supported_shells(config, shell_name):
|
176
|
+
def callback_supported_shells(config: Config, shell_name: str) -> str:
|
142
177
|
"""
|
143
178
|
Callback that tests if a shell names is supported on this OS.
|
144
179
|
"""
|
@@ -148,31 +183,31 @@ def callback_supported_shells(config, shell_name):
|
|
148
183
|
return shell_name
|
149
184
|
|
150
185
|
|
151
|
-
def set_callback_file_paths(config, value):
|
186
|
+
def set_callback_file_paths(config: Config, value: PathLike | list[PathLike]) -> None:
|
152
187
|
"""Check the file(s) is/are accessible. This is only done on `config.set` (and not on
|
153
188
|
`config.get` or `config._validate`) because it could be expensive in the case of remote
|
154
189
|
files."""
|
155
190
|
value = callback_file_paths(config, value)
|
156
191
|
|
157
|
-
to_check = value
|
158
|
-
if not isinstance(value, list):
|
159
|
-
to_check = [value]
|
192
|
+
to_check = value if isinstance(value, list) else [value]
|
160
193
|
|
161
194
|
for file_path in to_check:
|
195
|
+
if file_path is None:
|
196
|
+
continue
|
162
197
|
with fsspec.open(file_path, mode="rt") as fh:
|
163
198
|
pass
|
164
199
|
# TODO: also check something in it?
|
165
200
|
print(f"Checked access to: {file_path}")
|
166
201
|
|
167
202
|
|
168
|
-
def check_load_data_files(config, value):
|
203
|
+
def check_load_data_files(config: Config, value: Any) -> None:
|
169
204
|
"""Check data files (e.g., task schema files) can be loaded successfully. This is only
|
170
205
|
done on `config.set` (and not on `config.get` or `config._validate`) because it could
|
171
206
|
be expensive in the case of remote files."""
|
172
207
|
config._app.reload_template_components(warn=False)
|
173
208
|
|
174
209
|
|
175
|
-
def callback_update_log_console_level(config, value):
|
210
|
+
def callback_update_log_console_level(config: Config, value: str) -> None:
|
176
211
|
"""
|
177
212
|
Callback to set the logging level.
|
178
213
|
"""
|