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.
Files changed (130) hide show
  1. hpcflow/__pyinstaller/hook-hpcflow.py +1 -0
  2. hpcflow/_version.py +1 -1
  3. hpcflow/data/scripts/bad_script.py +2 -0
  4. hpcflow/data/scripts/do_nothing.py +2 -0
  5. hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
  6. hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
  7. hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
  8. hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
  9. hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
  10. hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
  11. hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
  12. hpcflow/data/scripts/input_file_generator_basic.py +3 -0
  13. hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
  14. hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
  15. hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
  16. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
  17. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
  18. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
  19. hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
  20. hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
  21. hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
  22. hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
  23. hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
  24. hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
  25. hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
  26. hpcflow/data/scripts/output_file_parser_basic.py +3 -0
  27. hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
  28. hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
  29. hpcflow/data/scripts/script_exit_test.py +5 -0
  30. hpcflow/data/template_components/environments.yaml +1 -1
  31. hpcflow/sdk/__init__.py +5 -0
  32. hpcflow/sdk/app.py +150 -89
  33. hpcflow/sdk/cli.py +263 -84
  34. hpcflow/sdk/cli_common.py +99 -5
  35. hpcflow/sdk/config/callbacks.py +38 -1
  36. hpcflow/sdk/config/config.py +102 -13
  37. hpcflow/sdk/config/errors.py +19 -5
  38. hpcflow/sdk/config/types.py +3 -0
  39. hpcflow/sdk/core/__init__.py +25 -1
  40. hpcflow/sdk/core/actions.py +914 -262
  41. hpcflow/sdk/core/cache.py +76 -34
  42. hpcflow/sdk/core/command_files.py +14 -128
  43. hpcflow/sdk/core/commands.py +35 -6
  44. hpcflow/sdk/core/element.py +122 -50
  45. hpcflow/sdk/core/errors.py +58 -2
  46. hpcflow/sdk/core/execute.py +207 -0
  47. hpcflow/sdk/core/loop.py +408 -50
  48. hpcflow/sdk/core/loop_cache.py +4 -4
  49. hpcflow/sdk/core/parameters.py +382 -37
  50. hpcflow/sdk/core/run_dir_files.py +13 -40
  51. hpcflow/sdk/core/skip_reason.py +7 -0
  52. hpcflow/sdk/core/task.py +119 -30
  53. hpcflow/sdk/core/task_schema.py +68 -0
  54. hpcflow/sdk/core/test_utils.py +66 -27
  55. hpcflow/sdk/core/types.py +54 -1
  56. hpcflow/sdk/core/utils.py +78 -7
  57. hpcflow/sdk/core/workflow.py +1538 -336
  58. hpcflow/sdk/data/workflow_spec_schema.yaml +2 -0
  59. hpcflow/sdk/demo/cli.py +7 -0
  60. hpcflow/sdk/helper/cli.py +1 -0
  61. hpcflow/sdk/log.py +42 -15
  62. hpcflow/sdk/persistence/base.py +405 -53
  63. hpcflow/sdk/persistence/json.py +177 -52
  64. hpcflow/sdk/persistence/pending.py +237 -69
  65. hpcflow/sdk/persistence/store_resource.py +3 -2
  66. hpcflow/sdk/persistence/types.py +15 -4
  67. hpcflow/sdk/persistence/zarr.py +928 -81
  68. hpcflow/sdk/submission/jobscript.py +1408 -489
  69. hpcflow/sdk/submission/schedulers/__init__.py +40 -5
  70. hpcflow/sdk/submission/schedulers/direct.py +33 -19
  71. hpcflow/sdk/submission/schedulers/sge.py +51 -16
  72. hpcflow/sdk/submission/schedulers/slurm.py +44 -16
  73. hpcflow/sdk/submission/schedulers/utils.py +7 -2
  74. hpcflow/sdk/submission/shells/base.py +68 -20
  75. hpcflow/sdk/submission/shells/bash.py +222 -129
  76. hpcflow/sdk/submission/shells/powershell.py +200 -150
  77. hpcflow/sdk/submission/submission.py +852 -119
  78. hpcflow/sdk/submission/types.py +18 -21
  79. hpcflow/sdk/typing.py +24 -5
  80. hpcflow/sdk/utils/arrays.py +71 -0
  81. hpcflow/sdk/utils/deferred_file.py +55 -0
  82. hpcflow/sdk/utils/hashing.py +16 -0
  83. hpcflow/sdk/utils/patches.py +12 -0
  84. hpcflow/sdk/utils/strings.py +33 -0
  85. hpcflow/tests/api/test_api.py +32 -0
  86. hpcflow/tests/conftest.py +19 -0
  87. hpcflow/tests/data/multi_path_sequences.yaml +29 -0
  88. hpcflow/tests/data/workflow_test_run_abort.yaml +34 -35
  89. hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
  90. hpcflow/tests/scripts/test_input_file_generators.py +282 -0
  91. hpcflow/tests/scripts/test_main_scripts.py +821 -70
  92. hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
  93. hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
  94. hpcflow/tests/shells/wsl/test_wsl_submission.py +6 -0
  95. hpcflow/tests/unit/test_action.py +176 -0
  96. hpcflow/tests/unit/test_app.py +20 -0
  97. hpcflow/tests/unit/test_cache.py +46 -0
  98. hpcflow/tests/unit/test_cli.py +133 -0
  99. hpcflow/tests/unit/test_config.py +122 -1
  100. hpcflow/tests/unit/test_element_iteration.py +47 -0
  101. hpcflow/tests/unit/test_jobscript_unit.py +757 -0
  102. hpcflow/tests/unit/test_loop.py +1332 -27
  103. hpcflow/tests/unit/test_meta_task.py +325 -0
  104. hpcflow/tests/unit/test_multi_path_sequences.py +229 -0
  105. hpcflow/tests/unit/test_parameter.py +13 -0
  106. hpcflow/tests/unit/test_persistence.py +190 -8
  107. hpcflow/tests/unit/test_run.py +109 -3
  108. hpcflow/tests/unit/test_run_directories.py +29 -0
  109. hpcflow/tests/unit/test_shell.py +20 -0
  110. hpcflow/tests/unit/test_submission.py +5 -76
  111. hpcflow/tests/unit/utils/test_arrays.py +40 -0
  112. hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
  113. hpcflow/tests/unit/utils/test_hashing.py +65 -0
  114. hpcflow/tests/unit/utils/test_patches.py +5 -0
  115. hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
  116. hpcflow/tests/workflows/__init__.py +0 -0
  117. hpcflow/tests/workflows/test_directory_structure.py +31 -0
  118. hpcflow/tests/workflows/test_jobscript.py +332 -0
  119. hpcflow/tests/workflows/test_run_status.py +198 -0
  120. hpcflow/tests/workflows/test_skip_downstream.py +696 -0
  121. hpcflow/tests/workflows/test_submission.py +140 -0
  122. hpcflow/tests/workflows/test_workflows.py +142 -2
  123. hpcflow/tests/workflows/test_zip.py +18 -0
  124. hpcflow/viz_demo.ipynb +6587 -3
  125. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/METADATA +7 -4
  126. hpcflow_new2-0.2.0a199.dist-info/RECORD +221 -0
  127. hpcflow_new2-0.2.0a190.dist-info/RECORD +0 -165
  128. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/LICENSE +0 -0
  129. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/WHEEL +0 -0
  130. {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["WorkflowTemplateTaskData"]]
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
- from typing import cast, overload, TypeVar, TYPE_CHECKING
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(lst: list[dict[T, T2]], key: T) -> list[list[dict[T, T2]]]:
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__(self, root_path: str | None = None, data: dict[str, list] | None = None):
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)): [*v, *inode_invert[k]]
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)