hpcflow-new2 0.2.0a189__py3-none-any.whl → 0.2.0a199__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 +9 -6
- hpcflow/_version.py +1 -1
- hpcflow/app.py +1 -0
- hpcflow/data/scripts/bad_script.py +2 -0
- hpcflow/data/scripts/do_nothing.py +2 -0
- hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
- hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/input_file_generator_basic.py +3 -0
- hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
- hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +1 -1
- hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +1 -1
- hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
- hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
- hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
- hpcflow/data/scripts/output_file_parser_basic.py +3 -0
- hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
- hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/script_exit_test.py +5 -0
- hpcflow/data/template_components/environments.yaml +1 -1
- hpcflow/sdk/__init__.py +26 -15
- hpcflow/sdk/app.py +2192 -768
- hpcflow/sdk/cli.py +506 -296
- hpcflow/sdk/cli_common.py +105 -7
- hpcflow/sdk/config/__init__.py +1 -1
- hpcflow/sdk/config/callbacks.py +115 -43
- hpcflow/sdk/config/cli.py +126 -103
- hpcflow/sdk/config/config.py +674 -318
- hpcflow/sdk/config/config_file.py +131 -95
- hpcflow/sdk/config/errors.py +125 -84
- hpcflow/sdk/config/types.py +148 -0
- hpcflow/sdk/core/__init__.py +25 -1
- hpcflow/sdk/core/actions.py +1771 -1059
- hpcflow/sdk/core/app_aware.py +24 -0
- hpcflow/sdk/core/cache.py +139 -79
- hpcflow/sdk/core/command_files.py +263 -287
- hpcflow/sdk/core/commands.py +145 -112
- hpcflow/sdk/core/element.py +828 -535
- hpcflow/sdk/core/enums.py +192 -0
- hpcflow/sdk/core/environment.py +74 -93
- hpcflow/sdk/core/errors.py +455 -52
- hpcflow/sdk/core/execute.py +207 -0
- hpcflow/sdk/core/json_like.py +540 -272
- hpcflow/sdk/core/loop.py +751 -347
- hpcflow/sdk/core/loop_cache.py +164 -47
- hpcflow/sdk/core/object_list.py +370 -207
- hpcflow/sdk/core/parameters.py +1100 -627
- hpcflow/sdk/core/rule.py +59 -41
- hpcflow/sdk/core/run_dir_files.py +21 -37
- hpcflow/sdk/core/skip_reason.py +7 -0
- hpcflow/sdk/core/task.py +1649 -1339
- hpcflow/sdk/core/task_schema.py +308 -196
- hpcflow/sdk/core/test_utils.py +191 -114
- hpcflow/sdk/core/types.py +440 -0
- hpcflow/sdk/core/utils.py +485 -309
- hpcflow/sdk/core/validation.py +82 -9
- hpcflow/sdk/core/workflow.py +2544 -1178
- hpcflow/sdk/core/zarr_io.py +98 -137
- hpcflow/sdk/data/workflow_spec_schema.yaml +2 -0
- hpcflow/sdk/demo/cli.py +53 -33
- hpcflow/sdk/helper/cli.py +18 -15
- hpcflow/sdk/helper/helper.py +75 -63
- hpcflow/sdk/helper/watcher.py +61 -28
- hpcflow/sdk/log.py +122 -71
- hpcflow/sdk/persistence/__init__.py +8 -31
- hpcflow/sdk/persistence/base.py +1360 -606
- hpcflow/sdk/persistence/defaults.py +6 -0
- hpcflow/sdk/persistence/discovery.py +38 -0
- hpcflow/sdk/persistence/json.py +568 -188
- hpcflow/sdk/persistence/pending.py +382 -179
- hpcflow/sdk/persistence/store_resource.py +39 -23
- hpcflow/sdk/persistence/types.py +318 -0
- hpcflow/sdk/persistence/utils.py +14 -11
- hpcflow/sdk/persistence/zarr.py +1337 -433
- hpcflow/sdk/runtime.py +44 -41
- hpcflow/sdk/submission/{jobscript_info.py → enums.py} +39 -12
- hpcflow/sdk/submission/jobscript.py +1651 -692
- hpcflow/sdk/submission/schedulers/__init__.py +167 -39
- hpcflow/sdk/submission/schedulers/direct.py +121 -81
- hpcflow/sdk/submission/schedulers/sge.py +170 -129
- hpcflow/sdk/submission/schedulers/slurm.py +291 -268
- hpcflow/sdk/submission/schedulers/utils.py +12 -2
- hpcflow/sdk/submission/shells/__init__.py +14 -15
- hpcflow/sdk/submission/shells/base.py +150 -29
- hpcflow/sdk/submission/shells/bash.py +283 -173
- hpcflow/sdk/submission/shells/os_version.py +31 -30
- hpcflow/sdk/submission/shells/powershell.py +228 -170
- hpcflow/sdk/submission/submission.py +1014 -335
- hpcflow/sdk/submission/types.py +140 -0
- hpcflow/sdk/typing.py +182 -12
- hpcflow/sdk/utils/arrays.py +71 -0
- hpcflow/sdk/utils/deferred_file.py +55 -0
- hpcflow/sdk/utils/hashing.py +16 -0
- hpcflow/sdk/utils/patches.py +12 -0
- hpcflow/sdk/utils/strings.py +33 -0
- hpcflow/tests/api/test_api.py +32 -0
- hpcflow/tests/conftest.py +27 -6
- hpcflow/tests/data/multi_path_sequences.yaml +29 -0
- hpcflow/tests/data/workflow_test_run_abort.yaml +34 -35
- hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
- hpcflow/tests/schedulers/slurm/test_slurm_submission.py +5 -2
- hpcflow/tests/scripts/test_input_file_generators.py +282 -0
- hpcflow/tests/scripts/test_main_scripts.py +866 -85
- hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
- hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
- hpcflow/tests/shells/wsl/test_wsl_submission.py +12 -4
- hpcflow/tests/unit/test_action.py +262 -75
- hpcflow/tests/unit/test_action_rule.py +9 -4
- hpcflow/tests/unit/test_app.py +33 -6
- hpcflow/tests/unit/test_cache.py +46 -0
- hpcflow/tests/unit/test_cli.py +134 -1
- hpcflow/tests/unit/test_command.py +71 -54
- hpcflow/tests/unit/test_config.py +142 -16
- hpcflow/tests/unit/test_config_file.py +21 -18
- hpcflow/tests/unit/test_element.py +58 -62
- hpcflow/tests/unit/test_element_iteration.py +50 -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_jobscript_unit.py +757 -0
- hpcflow/tests/unit/test_json_like.py +44 -35
- hpcflow/tests/unit/test_loop.py +1396 -84
- hpcflow/tests/unit/test_meta_task.py +325 -0
- hpcflow/tests/unit/test_multi_path_sequences.py +229 -0
- hpcflow/tests/unit/test_object_list.py +17 -12
- hpcflow/tests/unit/test_parameter.py +29 -7
- hpcflow/tests/unit/test_persistence.py +237 -42
- hpcflow/tests/unit/test_resources.py +20 -18
- hpcflow/tests/unit/test_run.py +117 -6
- hpcflow/tests/unit/test_run_directories.py +29 -0
- hpcflow/tests/unit/test_runtime.py +2 -1
- hpcflow/tests/unit/test_schema_input.py +23 -15
- hpcflow/tests/unit/test_shell.py +23 -2
- hpcflow/tests/unit/test_slurm.py +8 -7
- hpcflow/tests/unit/test_submission.py +38 -89
- 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/unit/utils/test_arrays.py +40 -0
- hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
- hpcflow/tests/unit/utils/test_hashing.py +65 -0
- hpcflow/tests/unit/utils/test_patches.py +5 -0
- hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
- hpcflow/tests/workflows/__init__.py +0 -0
- hpcflow/tests/workflows/test_directory_structure.py +31 -0
- hpcflow/tests/workflows/test_jobscript.py +334 -1
- hpcflow/tests/workflows/test_run_status.py +198 -0
- hpcflow/tests/workflows/test_skip_downstream.py +696 -0
- hpcflow/tests/workflows/test_submission.py +140 -0
- hpcflow/tests/workflows/test_workflows.py +160 -15
- hpcflow/tests/workflows/test_zip.py +18 -0
- hpcflow/viz_demo.ipynb +6587 -3
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/METADATA +8 -4
- hpcflow_new2-0.2.0a199.dist-info/RECORD +221 -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.0a199.dist-info}/LICENSE +0 -0
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/cli_common.py
CHANGED
@@ -1,17 +1,59 @@
|
|
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
|
-
|
11
|
+
class BoolOrString(click.ParamType):
|
12
|
+
"""Custom Click parameter type to accepts a bool or a choice of strings."""
|
13
|
+
|
14
|
+
name = "bool-or-string"
|
15
|
+
|
16
|
+
def __init__(self, allowed_strings, true_strings=None, false_strings=None):
|
17
|
+
self.allowed_strings = allowed_strings
|
18
|
+
self.true_strings = true_strings if true_strings else ["true", "yes", "on"]
|
19
|
+
self.false_strings = false_strings if false_strings else ["false", "no", "off"]
|
20
|
+
|
21
|
+
def convert(self, value, param, ctx):
|
22
|
+
# Check if the value is a boolean
|
23
|
+
if isinstance(value, bool):
|
24
|
+
return value
|
25
|
+
|
26
|
+
# Normalize value to string
|
27
|
+
value = str(value).lower()
|
28
|
+
|
29
|
+
# Check if the value is one of the true strings
|
30
|
+
if value in self.true_strings:
|
31
|
+
return True
|
32
|
+
|
33
|
+
# Check if the value is one of the false strings
|
34
|
+
if value in self.false_strings:
|
35
|
+
return False
|
36
|
+
|
37
|
+
# If the value matches neither, it must be one of the expected strings
|
38
|
+
if value not in self.allowed_strings:
|
39
|
+
allowed_fmt = ", ".join(f"{i!r}" for i in self.allowed_strings)
|
40
|
+
self.fail(
|
41
|
+
message=f"{value} is not a valid boolean or one of {allowed_fmt}.",
|
42
|
+
param=param,
|
43
|
+
ctx=ctx,
|
44
|
+
)
|
45
|
+
|
46
|
+
return value
|
47
|
+
|
48
|
+
|
49
|
+
def sub_tasks_callback(ctx, param, value: str | None) -> list[int] | None:
|
10
50
|
"""
|
11
51
|
Parse subtasks.
|
12
52
|
"""
|
13
53
|
if value:
|
14
54
|
return [int(i) for i in value.split(",")]
|
55
|
+
else:
|
56
|
+
return None
|
15
57
|
|
16
58
|
|
17
59
|
#: Standard option
|
@@ -91,12 +133,14 @@ variables_option = click.option(
|
|
91
133
|
js_parallelism_option = click.option(
|
92
134
|
"--js-parallelism",
|
93
135
|
help=(
|
94
|
-
"If True, allow multiple jobscripts to execute simultaneously.
|
95
|
-
"
|
96
|
-
"
|
97
|
-
"
|
136
|
+
"If True, allow multiple jobscripts to execute simultaneously. If "
|
137
|
+
"'scheduled'/'direct', only allow simultaneous execution of scheduled/direct "
|
138
|
+
"jobscripts. Raises if set to True, 'scheduled', or 'direct', but the store type "
|
139
|
+
"does not support the `jobscript_parallelism` feature. If not set, jobscript "
|
140
|
+
"parallelism will be used if the store type supports it, for scheduled "
|
141
|
+
"jobscripts only."
|
98
142
|
),
|
99
|
-
type=
|
143
|
+
type=BoolOrString(["direct", "scheduled"]),
|
100
144
|
)
|
101
145
|
#: Standard option
|
102
146
|
wait_option = click.option(
|
@@ -140,6 +184,17 @@ submit_status_opt = click.option(
|
|
140
184
|
help="If True, display a live status to track submission progress.",
|
141
185
|
default=True,
|
142
186
|
)
|
187
|
+
#: Standard option
|
188
|
+
force_arr_opt = click.option(
|
189
|
+
"--force-array",
|
190
|
+
help=(
|
191
|
+
"Used to force the use of job arrays, even if the scheduler does not support it. "
|
192
|
+
"This is provided for testing purposes only."
|
193
|
+
),
|
194
|
+
is_flag=True,
|
195
|
+
default=False,
|
196
|
+
)
|
197
|
+
|
143
198
|
#: Standard option
|
144
199
|
make_status_opt = click.option(
|
145
200
|
"--status/--no-status",
|
@@ -147,6 +202,14 @@ make_status_opt = click.option(
|
|
147
202
|
default=True,
|
148
203
|
)
|
149
204
|
|
205
|
+
#: Standard option
|
206
|
+
add_sub_opt = click.option(
|
207
|
+
"--add-submission",
|
208
|
+
help=("If True, add a submission to the workflow (but do not submit)."),
|
209
|
+
is_flag=True,
|
210
|
+
default=False,
|
211
|
+
)
|
212
|
+
|
150
213
|
#: Standard option
|
151
214
|
zip_path_opt = click.option(
|
152
215
|
"--path",
|
@@ -207,6 +270,34 @@ rechunk_status_opt = click.option(
|
|
207
270
|
default=True,
|
208
271
|
help="If True, display a live status to track rechunking progress.",
|
209
272
|
)
|
273
|
+
cancel_status_opt = click.option(
|
274
|
+
"--status/--no-status",
|
275
|
+
default=True,
|
276
|
+
help="If True, display a live status to track cancel progress.",
|
277
|
+
)
|
278
|
+
|
279
|
+
list_js_max_js_opt = click.option(
|
280
|
+
"--max-js", type=click.INT, help="Display up to this jobscript only."
|
281
|
+
)
|
282
|
+
list_js_jobscripts_opt = click.option(
|
283
|
+
"--jobscripts", help="Comma-separated list of jobscript indices to show."
|
284
|
+
)
|
285
|
+
list_task_js_max_js_opt = click.option(
|
286
|
+
"--max-js", type=click.INT, help="Include jobscripts up to this jobscript only."
|
287
|
+
)
|
288
|
+
list_task_js_task_names_opt = click.option(
|
289
|
+
"--task-names", help="Comma-separated list of task name sub-strings to show."
|
290
|
+
)
|
291
|
+
list_js_width_opt = click.option(
|
292
|
+
"--width", type=click.INT, help="Width in characters of the table to print."
|
293
|
+
)
|
294
|
+
jobscript_std_array_idx_opt = click.option(
|
295
|
+
"--array-idx",
|
296
|
+
type=click.INT,
|
297
|
+
help=(
|
298
|
+
"For array jobs only, the job array index whose standard stream is to be printed."
|
299
|
+
),
|
300
|
+
)
|
210
301
|
|
211
302
|
|
212
303
|
def _add_doc_from_help(*args):
|
@@ -255,4 +346,11 @@ _add_doc_from_help(
|
|
255
346
|
rechunk_backup_opt,
|
256
347
|
rechunk_chunk_size_opt,
|
257
348
|
rechunk_status_opt,
|
349
|
+
cancel_status_opt,
|
350
|
+
list_js_max_js_opt,
|
351
|
+
list_js_jobscripts_opt,
|
352
|
+
list_task_js_max_js_opt,
|
353
|
+
list_task_js_task_names_opt,
|
354
|
+
list_js_width_opt,
|
355
|
+
jobscript_std_array_idx_opt,
|
258
356
|
)
|
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,32 +183,69 @@ 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
|
210
|
+
def callback_log_file_path(config, value):
|
211
|
+
value = value.strip()
|
212
|
+
if value:
|
213
|
+
return config._resolve_path(value)
|
214
|
+
else:
|
215
|
+
return value
|
216
|
+
|
217
|
+
|
218
|
+
def callback_update_log_console_level(config: Config, value: str) -> None:
|
176
219
|
"""
|
177
220
|
Callback to set the logging level.
|
178
221
|
"""
|
179
|
-
config._app.log.update_console_level(value)
|
222
|
+
config._app.log.update_console_level(new_level=value)
|
223
|
+
|
224
|
+
|
225
|
+
def callback_unset_log_console_level(config: Config) -> None:
|
226
|
+
"""Reset the console handler to the default level."""
|
227
|
+
config._app.log.update_console_level()
|
228
|
+
|
229
|
+
|
230
|
+
def callback_update_log_file_level(config: Config, value: str) -> None:
|
231
|
+
"""Callback to set the level of the log file handler."""
|
232
|
+
config._app.log.update_file_level(new_level=value)
|
233
|
+
|
234
|
+
|
235
|
+
def callback_update_log_file_path(config: Config, value: str) -> None:
|
236
|
+
"""
|
237
|
+
Callback to update the log file path, or remove the file handler if no path specifed.
|
238
|
+
"""
|
239
|
+
config._app.log.remove_file_handler()
|
240
|
+
if value:
|
241
|
+
config._app.log.add_file_logger(path=value, level=config.get("log_file_level"))
|
242
|
+
|
243
|
+
|
244
|
+
def callback_unset_log_file_level(config: Config) -> None:
|
245
|
+
"""Callback to reset the file handler to the default level."""
|
246
|
+
config._app.log.update_file_level()
|
247
|
+
|
248
|
+
|
249
|
+
def callback_unset_log_file_path(config: Config) -> None:
|
250
|
+
"""Callback to remove the log file handler."""
|
251
|
+
config._app.log.remove_file_handler()
|