hpcflow-new2 0.2.0a190__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 +1 -0
- hpcflow/_version.py +1 -1
- 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_2.py +12 -0
- 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 +5 -0
- hpcflow/sdk/app.py +150 -89
- hpcflow/sdk/cli.py +263 -84
- hpcflow/sdk/cli_common.py +99 -5
- hpcflow/sdk/config/callbacks.py +38 -1
- hpcflow/sdk/config/config.py +102 -13
- hpcflow/sdk/config/errors.py +19 -5
- hpcflow/sdk/config/types.py +3 -0
- hpcflow/sdk/core/__init__.py +25 -1
- hpcflow/sdk/core/actions.py +914 -262
- hpcflow/sdk/core/cache.py +76 -34
- hpcflow/sdk/core/command_files.py +14 -128
- hpcflow/sdk/core/commands.py +35 -6
- hpcflow/sdk/core/element.py +122 -50
- hpcflow/sdk/core/errors.py +58 -2
- hpcflow/sdk/core/execute.py +207 -0
- hpcflow/sdk/core/loop.py +408 -50
- hpcflow/sdk/core/loop_cache.py +4 -4
- hpcflow/sdk/core/parameters.py +382 -37
- hpcflow/sdk/core/run_dir_files.py +13 -40
- hpcflow/sdk/core/skip_reason.py +7 -0
- hpcflow/sdk/core/task.py +119 -30
- hpcflow/sdk/core/task_schema.py +68 -0
- hpcflow/sdk/core/test_utils.py +66 -27
- hpcflow/sdk/core/types.py +54 -1
- hpcflow/sdk/core/utils.py +78 -7
- hpcflow/sdk/core/workflow.py +1538 -336
- hpcflow/sdk/data/workflow_spec_schema.yaml +2 -0
- hpcflow/sdk/demo/cli.py +7 -0
- hpcflow/sdk/helper/cli.py +1 -0
- hpcflow/sdk/log.py +42 -15
- hpcflow/sdk/persistence/base.py +405 -53
- hpcflow/sdk/persistence/json.py +177 -52
- hpcflow/sdk/persistence/pending.py +237 -69
- hpcflow/sdk/persistence/store_resource.py +3 -2
- hpcflow/sdk/persistence/types.py +15 -4
- hpcflow/sdk/persistence/zarr.py +928 -81
- hpcflow/sdk/submission/jobscript.py +1408 -489
- hpcflow/sdk/submission/schedulers/__init__.py +40 -5
- hpcflow/sdk/submission/schedulers/direct.py +33 -19
- hpcflow/sdk/submission/schedulers/sge.py +51 -16
- hpcflow/sdk/submission/schedulers/slurm.py +44 -16
- hpcflow/sdk/submission/schedulers/utils.py +7 -2
- hpcflow/sdk/submission/shells/base.py +68 -20
- hpcflow/sdk/submission/shells/bash.py +222 -129
- hpcflow/sdk/submission/shells/powershell.py +200 -150
- hpcflow/sdk/submission/submission.py +852 -119
- hpcflow/sdk/submission/types.py +18 -21
- hpcflow/sdk/typing.py +24 -5
- 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 +19 -0
- 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/scripts/test_input_file_generators.py +282 -0
- hpcflow/tests/scripts/test_main_scripts.py +821 -70
- 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 +6 -0
- hpcflow/tests/unit/test_action.py +176 -0
- hpcflow/tests/unit/test_app.py +20 -0
- hpcflow/tests/unit/test_cache.py +46 -0
- hpcflow/tests/unit/test_cli.py +133 -0
- hpcflow/tests/unit/test_config.py +122 -1
- hpcflow/tests/unit/test_element_iteration.py +47 -0
- hpcflow/tests/unit/test_jobscript_unit.py +757 -0
- hpcflow/tests/unit/test_loop.py +1332 -27
- hpcflow/tests/unit/test_meta_task.py +325 -0
- hpcflow/tests/unit/test_multi_path_sequences.py +229 -0
- hpcflow/tests/unit/test_parameter.py +13 -0
- hpcflow/tests/unit/test_persistence.py +190 -8
- hpcflow/tests/unit/test_run.py +109 -3
- hpcflow/tests/unit/test_run_directories.py +29 -0
- hpcflow/tests/unit/test_shell.py +20 -0
- hpcflow/tests/unit/test_submission.py +5 -76
- 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 +332 -0
- 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 +142 -2
- hpcflow/tests/workflows/test_zip.py +18 -0
- hpcflow/viz_demo.ipynb +6587 -3
- {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/METADATA +7 -4
- hpcflow_new2-0.2.0a199.dist-info/RECORD +221 -0
- hpcflow_new2-0.2.0a190.dist-info/RECORD +0 -165
- {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/LICENSE +0 -0
- {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/core/types.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
"""
|
2
2
|
Types to support the core SDK.
|
3
3
|
"""
|
4
|
+
|
4
5
|
from __future__ import annotations
|
5
6
|
from typing import Any, Literal, Protocol, TYPE_CHECKING
|
6
7
|
from typing_extensions import NotRequired, TypeAlias, TypedDict
|
@@ -205,6 +206,9 @@ class SchemaInputKwargs(TypedDict):
|
|
205
206
|
multiple: bool
|
206
207
|
#: The labels.
|
207
208
|
labels: dict[str, LabelInfo] | None
|
209
|
+
#: The number or proportional of permitted unset parameter data found when reolving
|
210
|
+
#: this input from upstream outputs.
|
211
|
+
allow_failed_dependencies: int | float | bool | None
|
208
212
|
|
209
213
|
|
210
214
|
class RuleArgs(TypedDict):
|
@@ -333,11 +337,51 @@ class WorkflowTemplateTaskData(TypedDict):
|
|
333
337
|
#: The schema, if known.
|
334
338
|
schema: NotRequired[Any | list[Any]]
|
335
339
|
#: The element sets, if known.
|
336
|
-
element_sets: NotRequired[list[
|
340
|
+
element_sets: NotRequired[list[WorkflowTemplateElementSetData]]
|
337
341
|
#: The output labels, if known.
|
338
342
|
output_labels: NotRequired[list[str]]
|
339
343
|
|
340
344
|
|
345
|
+
class WorkflowTemplateElementSetData(TypedDict):
|
346
|
+
"""
|
347
|
+
Descriptor for element set data within a workflow template parametrisation.
|
348
|
+
"""
|
349
|
+
|
350
|
+
#: Inputs to the set of elements.
|
351
|
+
inputs: NotRequired[list[dict[str, Any]]]
|
352
|
+
#: Input files to the set of elements.
|
353
|
+
input_files: NotRequired[list[dict[str, Any]]]
|
354
|
+
#: Description of how to repeat the set of elements.
|
355
|
+
repeats: NotRequired[int | list[RepeatsDescriptor]]
|
356
|
+
#: Groupings in the set of elements.
|
357
|
+
groups: NotRequired[list[dict[str, Any]]]
|
358
|
+
#: Resources to use for the set of elements.
|
359
|
+
resources: NotRequired[dict[str, Any]]
|
360
|
+
#: Input value sequences to parameterise over.
|
361
|
+
sequences: NotRequired[list[dict[str, Any]]]
|
362
|
+
#: Input value multi-path sequences to parameterise over.
|
363
|
+
multi_path_sequences: NotRequired[list[dict[str, Any]]]
|
364
|
+
#: Input source descriptors.
|
365
|
+
input_sources: NotRequired[dict[str, list]]
|
366
|
+
#: How to handle nesting of iterations.
|
367
|
+
nesting_order: NotRequired[dict[str, float]]
|
368
|
+
#: Which environment preset to use.
|
369
|
+
env_preset: NotRequired[str]
|
370
|
+
#: Environment descriptors to use.
|
371
|
+
environments: NotRequired[dict[str, dict[str, Any]]]
|
372
|
+
#: List of global element iteration indices from which inputs for
|
373
|
+
#: the new elements associated with this element set may be sourced.
|
374
|
+
#: If ``None``, all iterations are valid.
|
375
|
+
sourceable_elem_iters: NotRequired[list[int]]
|
376
|
+
#: Whether to allow sources to come from distinct element sub-sets.
|
377
|
+
allow_non_coincident_task_sources: NotRequired[bool]
|
378
|
+
#: Whether this initialisation is the first for this data (i.e. not a
|
379
|
+
#: reconstruction from persistent workflow data), in which case, we merge
|
380
|
+
#: ``environments`` into ``resources`` using the "any" scope, and merge any multi-
|
381
|
+
#: path sequences into the sequences list.
|
382
|
+
is_creation: NotRequired[bool]
|
383
|
+
|
384
|
+
|
341
385
|
class Pending(TypedDict):
|
342
386
|
"""
|
343
387
|
Pending update information. Internal use only.
|
@@ -385,3 +429,12 @@ class ResourcePersistingWorkflow(Protocol):
|
|
385
429
|
"""
|
386
430
|
Check if all the parameters exist.
|
387
431
|
"""
|
432
|
+
|
433
|
+
|
434
|
+
BlockActionKey: TypeAlias = "tuple[int | str, int | str, int | str]"
|
435
|
+
"""
|
436
|
+
The type of indices that locate an action within a submission. The indices represent,
|
437
|
+
respectively, the jobscript index, the jobscript-block index, and the block-action index.
|
438
|
+
Usually, these are integers, but in the case of strings, they will correspond to shell
|
439
|
+
environment variables.
|
440
|
+
"""
|
hpcflow/sdk/core/utils.py
CHANGED
@@ -4,8 +4,12 @@ Miscellaneous utilities.
|
|
4
4
|
|
5
5
|
from __future__ import annotations
|
6
6
|
from collections import Counter
|
7
|
+
from asyncio import events
|
8
|
+
import contextvars
|
9
|
+
import contextlib
|
7
10
|
import copy
|
8
11
|
import enum
|
12
|
+
import functools
|
9
13
|
import hashlib
|
10
14
|
from itertools import accumulate, islice
|
11
15
|
from importlib import resources
|
@@ -20,7 +24,8 @@ import string
|
|
20
24
|
import subprocess
|
21
25
|
from datetime import datetime, timedelta, timezone
|
22
26
|
import sys
|
23
|
-
|
27
|
+
import traceback
|
28
|
+
from typing import Literal, cast, overload, TypeVar, TYPE_CHECKING
|
24
29
|
import fsspec # type: ignore
|
25
30
|
import numpy as np
|
26
31
|
|
@@ -33,12 +38,13 @@ from hpcflow.sdk.core.errors import (
|
|
33
38
|
MissingVariableSubstitutionError,
|
34
39
|
)
|
35
40
|
from hpcflow.sdk.log import TimeIt
|
41
|
+
from hpcflow.sdk.utils.deferred_file import DeferredFileWriter
|
36
42
|
|
37
43
|
if TYPE_CHECKING:
|
38
44
|
from collections.abc import Callable, Hashable, Iterable, Mapping, Sequence
|
39
45
|
from contextlib import AbstractContextManager
|
40
46
|
from types import ModuleType
|
41
|
-
from typing import Any, IO
|
47
|
+
from typing import Any, IO, Iterator
|
42
48
|
from typing_extensions import TypeAlias
|
43
49
|
from numpy.typing import NDArray
|
44
50
|
from ..typing import PathLike
|
@@ -124,7 +130,9 @@ def check_valid_py_identifier(name: str) -> str:
|
|
124
130
|
|
125
131
|
|
126
132
|
@overload
|
127
|
-
def group_by_dict_key_values(
|
133
|
+
def group_by_dict_key_values( # type: ignore[overload-overlap]
|
134
|
+
lst: list[dict[T, T2]], key: T
|
135
|
+
) -> list[list[dict[T, T2]]]:
|
128
136
|
...
|
129
137
|
|
130
138
|
|
@@ -326,7 +334,7 @@ def get_relative_path(path1: Sequence[T], path2: Sequence[T]) -> Sequence[T]:
|
|
326
334
|
|
327
335
|
|
328
336
|
def search_dir_files_by_regex(
|
329
|
-
pattern: str | re.Pattern[str], directory: str = "."
|
337
|
+
pattern: str | re.Pattern[str], directory: str | os.PathLike = "."
|
330
338
|
) -> list[str]:
|
331
339
|
"""Search recursively for files in a directory by a regex pattern and return matching
|
332
340
|
file paths, relative to the given directory."""
|
@@ -771,7 +779,12 @@ class JSONLikeDirSnapShot(DirectorySnapshot):
|
|
771
779
|
See :py:meth:`to_json_like`.
|
772
780
|
"""
|
773
781
|
|
774
|
-
def __init__(
|
782
|
+
def __init__(
|
783
|
+
self,
|
784
|
+
root_path: str | None = None,
|
785
|
+
data: dict[str, list] | None = None,
|
786
|
+
use_strings: bool = False,
|
787
|
+
):
|
775
788
|
"""
|
776
789
|
Create an empty snapshot or load from JSON-like data.
|
777
790
|
"""
|
@@ -786,6 +799,7 @@ class JSONLikeDirSnapShot(DirectorySnapshot):
|
|
786
799
|
for name, item in data.items():
|
787
800
|
# add root path
|
788
801
|
full_name = str(PurePath(root_path) / PurePath(name))
|
802
|
+
item = [int(i) for i in item] if use_strings else item
|
789
803
|
stat_dat, inode_key = item[:-2], item[-2:]
|
790
804
|
self._stat_info[full_name] = os.stat_result(stat_dat)
|
791
805
|
self._inode_to_path[tuple(inode_key)] = full_name
|
@@ -794,7 +808,7 @@ class JSONLikeDirSnapShot(DirectorySnapshot):
|
|
794
808
|
"""Take the snapshot."""
|
795
809
|
super().__init__(*args, **kwargs)
|
796
810
|
|
797
|
-
def to_json_like(self) -> dict[str, Any]:
|
811
|
+
def to_json_like(self, use_strings: bool = False) -> dict[str, Any]:
|
798
812
|
"""Export to a dict that is JSON-compatible and can be later reloaded.
|
799
813
|
|
800
814
|
The last two integers in `data` for each path are the keys in
|
@@ -807,13 +821,16 @@ class JSONLikeDirSnapShot(DirectorySnapshot):
|
|
807
821
|
# store efficiently:
|
808
822
|
inode_invert = {v: k for k, v in self._inode_to_path.items()}
|
809
823
|
data: dict[str, list] = {
|
810
|
-
str(PurePath(k).relative_to(root_path)): [
|
824
|
+
str(PurePath(k).relative_to(root_path)): [
|
825
|
+
str(i) if use_strings else i for i in [*v, *inode_invert[k]]
|
826
|
+
]
|
811
827
|
for k, v in self._stat_info.items()
|
812
828
|
}
|
813
829
|
|
814
830
|
return {
|
815
831
|
"root_path": root_path,
|
816
832
|
"data": data,
|
833
|
+
"use_strings": use_strings,
|
817
834
|
}
|
818
835
|
|
819
836
|
|
@@ -1083,3 +1100,57 @@ def get_file_context(
|
|
1083
1100
|
except AttributeError:
|
1084
1101
|
# < python 3.9
|
1085
1102
|
return resources.path(package, src or "")
|
1103
|
+
|
1104
|
+
|
1105
|
+
@contextlib.contextmanager
|
1106
|
+
def redirect_std_to_file(
|
1107
|
+
file,
|
1108
|
+
mode: Literal["w", "a"] = "a",
|
1109
|
+
ignore: Callable[[BaseException], Literal[True] | int] | None = None,
|
1110
|
+
) -> Iterator[None]:
|
1111
|
+
"""Temporarily redirect both stdout and stderr to a file, and if an exception is
|
1112
|
+
raised, catch it, print the traceback to that file, and exit.
|
1113
|
+
|
1114
|
+
File creation is deferred until an actual write is required.
|
1115
|
+
|
1116
|
+
Parameters
|
1117
|
+
----------
|
1118
|
+
ignore
|
1119
|
+
Callable to test if a given exception should be ignored. If an exception is
|
1120
|
+
not ignored, its traceback will be printed to `file` and the program will
|
1121
|
+
exit with exit code 1. The callable should accept one parameter, the
|
1122
|
+
exception, and should return True if that exception should be ignored, or
|
1123
|
+
an integer representing the exit code to exit the program with if that
|
1124
|
+
exception should not be ignored. By default, no exceptions are ignored.
|
1125
|
+
|
1126
|
+
"""
|
1127
|
+
ignore = ignore or (lambda _: 1)
|
1128
|
+
with DeferredFileWriter(file, mode=mode) as fp:
|
1129
|
+
with contextlib.redirect_stdout(fp):
|
1130
|
+
with contextlib.redirect_stderr(fp):
|
1131
|
+
try:
|
1132
|
+
yield
|
1133
|
+
except BaseException as exc:
|
1134
|
+
ignore_ret = ignore(exc)
|
1135
|
+
if ignore_ret is not True:
|
1136
|
+
traceback.print_exc()
|
1137
|
+
sys.exit(ignore_ret)
|
1138
|
+
|
1139
|
+
|
1140
|
+
async def to_thread(func, /, *args, **kwargs):
|
1141
|
+
"""Copied from https://github.com/python/cpython/blob/4b4227b907a262446b9d276c274feda2590a4e6e/Lib/asyncio/threads.py
|
1142
|
+
to support Python 3.8, which does not have `asyncio.to_thread`.
|
1143
|
+
|
1144
|
+
Asynchronously run function *func* in a separate thread.
|
1145
|
+
|
1146
|
+
Any *args and **kwargs supplied for this function are directly passed
|
1147
|
+
to *func*. Also, the current :class:`contextvars.Context` is propagated,
|
1148
|
+
allowing context variables from the main thread to be accessed in the
|
1149
|
+
separate thread.
|
1150
|
+
|
1151
|
+
Return a coroutine that can be awaited to get the eventual result of *func*.
|
1152
|
+
"""
|
1153
|
+
loop = events.get_running_loop()
|
1154
|
+
ctx = contextvars.copy_context()
|
1155
|
+
func_call = functools.partial(ctx.run, func, *args, **kwargs)
|
1156
|
+
return await loop.run_in_executor(None, func_call)
|