hpcflow-new2 0.2.0a190__py3-none-any.whl → 0.2.0a200__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 (132) 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 +166 -92
  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 +136 -19
  57. hpcflow/sdk/core/workflow.py +1587 -356
  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/benchmark_script_runner.yaml +26 -0
  88. hpcflow/tests/data/multi_path_sequences.yaml +29 -0
  89. hpcflow/tests/data/workflow_test_run_abort.yaml +34 -35
  90. hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
  91. hpcflow/tests/scripts/test_input_file_generators.py +282 -0
  92. hpcflow/tests/scripts/test_main_scripts.py +821 -70
  93. hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
  94. hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
  95. hpcflow/tests/shells/wsl/test_wsl_submission.py +6 -0
  96. hpcflow/tests/unit/test_action.py +176 -0
  97. hpcflow/tests/unit/test_app.py +20 -0
  98. hpcflow/tests/unit/test_cache.py +46 -0
  99. hpcflow/tests/unit/test_cli.py +133 -0
  100. hpcflow/tests/unit/test_config.py +122 -1
  101. hpcflow/tests/unit/test_element_iteration.py +47 -0
  102. hpcflow/tests/unit/test_jobscript_unit.py +757 -0
  103. hpcflow/tests/unit/test_loop.py +1332 -27
  104. hpcflow/tests/unit/test_meta_task.py +325 -0
  105. hpcflow/tests/unit/test_multi_path_sequences.py +229 -0
  106. hpcflow/tests/unit/test_parameter.py +13 -0
  107. hpcflow/tests/unit/test_persistence.py +190 -8
  108. hpcflow/tests/unit/test_run.py +109 -3
  109. hpcflow/tests/unit/test_run_directories.py +29 -0
  110. hpcflow/tests/unit/test_shell.py +20 -0
  111. hpcflow/tests/unit/test_submission.py +5 -76
  112. hpcflow/tests/unit/test_workflow_template.py +31 -0
  113. hpcflow/tests/unit/utils/test_arrays.py +40 -0
  114. hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
  115. hpcflow/tests/unit/utils/test_hashing.py +65 -0
  116. hpcflow/tests/unit/utils/test_patches.py +5 -0
  117. hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
  118. hpcflow/tests/workflows/__init__.py +0 -0
  119. hpcflow/tests/workflows/test_directory_structure.py +31 -0
  120. hpcflow/tests/workflows/test_jobscript.py +332 -0
  121. hpcflow/tests/workflows/test_run_status.py +198 -0
  122. hpcflow/tests/workflows/test_skip_downstream.py +696 -0
  123. hpcflow/tests/workflows/test_submission.py +140 -0
  124. hpcflow/tests/workflows/test_workflows.py +142 -2
  125. hpcflow/tests/workflows/test_zip.py +18 -0
  126. hpcflow/viz_demo.ipynb +6587 -3
  127. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a200.dist-info}/METADATA +7 -4
  128. hpcflow_new2-0.2.0a200.dist-info/RECORD +222 -0
  129. hpcflow_new2-0.2.0a190.dist-info/RECORD +0 -165
  130. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a200.dist-info}/LICENSE +0 -0
  131. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a200.dist-info}/WHEEL +0 -0
  132. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a200.dist-info}/entry_points.txt +0 -0
@@ -38,12 +38,18 @@ from hpcflow.sdk.config.callbacks import (
38
38
  callback_supported_schedulers,
39
39
  callback_supported_shells,
40
40
  callback_update_log_console_level,
41
+ callback_unset_log_console_level,
41
42
  callback_vars,
42
43
  callback_file_paths,
43
44
  exists_in_schedulers,
44
45
  set_callback_file_paths,
45
46
  check_load_data_files,
46
47
  set_scheduler_invocation_match,
48
+ callback_update_log_file_path,
49
+ callback_update_log_file_level,
50
+ callback_unset_log_file_level,
51
+ callback_unset_log_file_path,
52
+ callback_log_file_path,
47
53
  )
48
54
  from hpcflow.sdk.config.config_file import ConfigFile
49
55
  from hpcflow.sdk.config.errors import (
@@ -54,6 +60,7 @@ from hpcflow.sdk.config.errors import (
54
60
  ConfigItemAlreadyUnsetError,
55
61
  ConfigItemCallbackError,
56
62
  ConfigNonConfigurableError,
63
+ ConfigReadOnlyError,
57
64
  ConfigUnknownItemError,
58
65
  ConfigUnknownOverrideError,
59
66
  ConfigValidationError,
@@ -70,6 +77,7 @@ if TYPE_CHECKING:
70
77
  ShellConfigDescriptor,
71
78
  GetterCallback,
72
79
  SetterCallback,
80
+ UnsetterCallback,
73
81
  T,
74
82
  )
75
83
  from ..app import BaseApp
@@ -256,7 +264,7 @@ class Config:
256
264
  "environment_sources": (callback_file_paths,),
257
265
  "parameter_sources": (callback_file_paths,),
258
266
  "command_file_sources": (callback_file_paths,),
259
- "log_file_path": (callback_vars, callback_file_paths),
267
+ "log_file_path": (callback_vars, callback_log_file_path),
260
268
  "telemetry": (callback_bool,),
261
269
  "schedulers": (callback_lowercase, callback_supported_schedulers),
262
270
  "shells": (callback_lowercase,),
@@ -275,11 +283,18 @@ class Config:
275
283
  "default_scheduler": (exists_in_schedulers, set_scheduler_invocation_match),
276
284
  "default_shell": (callback_supported_shells,),
277
285
  "schedulers": (callback_supported_schedulers, callback_scheduler_set_up),
278
- "log_file_path": (set_callback_file_paths,),
286
+ "log_file_path": (callback_update_log_file_path,),
287
+ "log_file_level": (callback_update_log_file_level,),
279
288
  "log_console_level": (callback_update_log_console_level,),
280
289
  "demo_data_manifest_file": (set_callback_file_paths,),
281
290
  }
282
291
 
292
+ self._unset_callbacks: dict[str, tuple[UnsetterCallback, ...]] = {
293
+ "log_console_level": (callback_unset_log_console_level,),
294
+ "log_file_level": (callback_unset_log_file_level,),
295
+ "log_file_path": (callback_unset_log_file_path,),
296
+ }
297
+
283
298
  self._configurable_keys = self._options._configurable_keys
284
299
  self._modified_keys: ConfigDescriptor = {}
285
300
  self._unset_keys: set[str] = set()
@@ -302,6 +317,11 @@ class Config:
302
317
  }
303
318
  self._meta_data = metadata
304
319
 
320
+ # used within context manager `cached_config`:
321
+ self._use_cache = False
322
+ self._config_cache: dict[tuple[str, bool, bool, bool], Any] = {}
323
+
324
+ # note: this must go at the end, after all instance attributes have been set!
305
325
  self._options.validate(
306
326
  data=self.get_all(include_overrides=True),
307
327
  logger=self._logger,
@@ -524,10 +544,12 @@ class Config:
524
544
  def _disable_callbacks(
525
545
  self, callbacks: Sequence[str]
526
546
  ) -> tuple[
527
- dict[str, tuple[GetterCallback, ...]], dict[str, tuple[SetterCallback, ...]]
547
+ dict[str, tuple[GetterCallback, ...]],
548
+ dict[str, tuple[SetterCallback, ...]],
549
+ dict[str, tuple[UnsetterCallback, ...]],
528
550
  ]:
529
551
  """
530
- Disable named get and set callbacks.
552
+ Disable named get, set, and unset callbacks.
531
553
 
532
554
  Returns
533
555
  -------
@@ -542,19 +564,28 @@ class Config:
542
564
  k: tuple(cb for cb in v if cb.__name__ not in callbacks)
543
565
  for k, v in self._set_callbacks.items()
544
566
  }
567
+ unset_callbacks_tmp = {
568
+ k: tuple(i for i in v if i.__name__ not in callbacks)
569
+ for k, v in self._unset_callbacks.items()
570
+ }
545
571
  get_callbacks = copy.deepcopy(self._get_callbacks)
546
572
  set_callbacks = copy.deepcopy(self._set_callbacks)
573
+ unset_callbacks = copy.deepcopy(self._unset_callbacks)
547
574
  self._get_callbacks = get_callbacks_tmp
548
575
  self._set_callbacks = set_callbacks_tmp
549
- return (get_callbacks, set_callbacks)
576
+ self._unset_callbacks = unset_callbacks_tmp
577
+ return (get_callbacks, set_callbacks, unset_callbacks)
550
578
 
551
579
  @contextlib.contextmanager
552
580
  def _without_callbacks(self, *callbacks: str) -> Iterator[None]:
553
- """Context manager to temporarily exclude named get and set callbacks."""
554
- get_callbacks, set_callbacks = self._disable_callbacks(*callbacks)
555
- yield
556
- self._get_callbacks = get_callbacks
557
- self._set_callbacks = set_callbacks
581
+ """Context manager to temporarily exclude named get, set, and unset callbacks."""
582
+ get_cb, set_cb, unset_cb = self._disable_callbacks(callbacks)
583
+ try:
584
+ yield
585
+ finally:
586
+ self._get_callbacks = get_cb
587
+ self._set_callbacks = set_cb
588
+ self._unset_callbacks = unset_cb
558
589
 
559
590
  def _validate(self) -> None:
560
591
  data = self.get_all(include_overrides=True)
@@ -737,6 +768,17 @@ class Config:
737
768
  ):
738
769
  """Get a configuration item."""
739
770
 
771
+ if self._use_cache:
772
+ # note: we default_value is not necessarily hashable, so we can't cache on it!
773
+ key = (
774
+ name,
775
+ include_overrides,
776
+ raise_on_missing,
777
+ as_str,
778
+ )
779
+ if key in self._config_cache:
780
+ return self._config_cache[key]
781
+
740
782
  if name not in self._all_keys:
741
783
  raise ConfigUnknownItemError(name=name)
742
784
 
@@ -769,9 +811,12 @@ class Config:
769
811
 
770
812
  if as_str:
771
813
  if isinstance(val, (list, tuple, set)):
772
- return [str(i) for i in val]
814
+ val = [str(i) for i in val]
773
815
  else:
774
- return str(val)
816
+ val = str(val)
817
+
818
+ if self._use_cache:
819
+ self._config_cache[key] = val
775
820
 
776
821
  return val
777
822
 
@@ -805,6 +850,9 @@ class Config:
805
850
  """
806
851
  Set a configuration item.
807
852
  """
853
+ if self._use_cache:
854
+ raise ConfigReadOnlyError()
855
+
808
856
  if name not in self._configurable_keys:
809
857
  raise ConfigNonConfigurableError(name=name)
810
858
  if is_json:
@@ -914,7 +962,7 @@ class Config:
914
962
  root = value
915
963
  self._set(name, root, quiet=quiet)
916
964
 
917
- def unset(self, name: str) -> None:
965
+ def unset(self, name: str, callback: bool = True) -> None:
918
966
  """
919
967
  Unset the value of a configuration item.
920
968
 
@@ -935,6 +983,13 @@ class Config:
935
983
  self._unset_keys.add(name)
936
984
  try:
937
985
  self._validate()
986
+ if callback:
987
+ for cb in self._unset_callbacks.get(name, []):
988
+ self._logger.debug(
989
+ f"Invoking `config.unset` callback for item {name!r}: "
990
+ f"{cb.__name__!r}."
991
+ )
992
+ cb(self)
938
993
  except ConfigValidationError as err:
939
994
  self._unset_keys.remove(name)
940
995
  raise ConfigChangeValidationError(name, validation_err=err) from None
@@ -1328,3 +1383,37 @@ class Config:
1328
1383
  sha=sha, path=self._app.demo_data_dir.replace(".", "/")
1329
1384
  ),
1330
1385
  )
1386
+
1387
+ @contextlib.contextmanager
1388
+ def cached_config(self) -> Iterator[None]:
1389
+ try:
1390
+ self._use_cache = True
1391
+ yield
1392
+ finally:
1393
+ self._use_cache = False
1394
+ self._config_cache = {} # reset the cache
1395
+
1396
+ def _is_set(self, name: str) -> bool:
1397
+ """Check if a (non-metadata) config item is set."""
1398
+ if name in self._unset_keys:
1399
+ return False
1400
+ elif name in self._modified_keys:
1401
+ return True
1402
+ else:
1403
+ return self._file.is_item_set(self._config_key, name)
1404
+
1405
+ @contextlib.contextmanager
1406
+ def _with_updates(self, updates: dict[str, Any]) -> Iterator[None]:
1407
+ # need to run callbacks for unsetting?
1408
+ prev_unset = copy.deepcopy(self._unset_keys)
1409
+ prev_modified = copy.deepcopy(self._modified_keys)
1410
+ to_unset = []
1411
+ try:
1412
+ for k, v in updates.items():
1413
+ if not self._is_set(k):
1414
+ to_unset.append(k)
1415
+ self.set(k, v)
1416
+ yield
1417
+ finally:
1418
+ self._unset_keys = prev_unset
1419
+ self._modified_keys = prev_modified
@@ -1,11 +1,12 @@
1
1
  """
2
2
  Miscellaneous configuration-related errors.
3
3
  """
4
+
4
5
  from __future__ import annotations
5
6
  from typing import Any, TYPE_CHECKING
6
7
 
7
8
  if TYPE_CHECKING:
8
- from collections.abc import Sequence
9
+ from collections.abc import Sequence, Iterable
9
10
  from .types import ConfigMetadata
10
11
  from ..typing import PathLike
11
12
 
@@ -51,10 +52,15 @@ class ConfigNonConfigurableError(ConfigError):
51
52
  Raised when the configuration contains an item that can't be configured.
52
53
  """
53
54
 
54
- def __init__(self, name: str, message: str = ""):
55
- super().__init__(
56
- message or f"Specified name {name!r} is not a configurable item."
57
- )
55
+ def __init__(self, name: str | Iterable[str], message: str | None = None):
56
+ if not message:
57
+ if not isinstance(name, str):
58
+ names_str = ", ".join(f"{i!r}" for i in name)
59
+ msg = f"Specified names {names_str} are not configurable items."
60
+ else:
61
+ msg = f"Specified name {name!r} is not a configurable item."
62
+ self.message = message or msg
63
+ super().__init__(self.message)
58
64
 
59
65
 
60
66
  class ConfigItemAlreadyUnsetError(ConfigError):
@@ -250,3 +256,11 @@ class MissingEnvironmentFileError(ConfigError):
250
256
  super().__init__(
251
257
  message or (f"The environment file {file_name!r} cannot be found. \n{err!s}")
252
258
  )
259
+
260
+
261
+ class ConfigReadOnlyError(ConfigError):
262
+ pass
263
+
264
+
265
+ class UnknownMetaTaskConstitutiveSchema(ValueError):
266
+ pass
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Types used in configuration.
3
3
  """
4
+
4
5
  from __future__ import annotations
5
6
  from typing import TYPE_CHECKING
6
7
  from typing_extensions import TypeAlias, TypedDict, TypeVar
@@ -19,6 +20,8 @@ T = TypeVar("T")
19
20
  GetterCallback: TypeAlias = "Callable[[Config, T], T]"
20
21
  #: Type of a setter callback.
21
22
  SetterCallback: TypeAlias = "Callable[[Config, T], Any]"
23
+ #: Type of a unsetter callback.
24
+ UnsetterCallback: TypeAlias = "Callable[[Config], None]"
22
25
 
23
26
 
24
27
  class SGEParallelEnvsDescriptor(TypedDict):
@@ -1,6 +1,6 @@
1
1
  """Core programmatic models for hpcflow.
2
2
 
3
- EAR abort exit code is set to 64 [1].
3
+ EAR abort exit code is set to 64. EAR skipped exit code is set to 65.
4
4
 
5
5
  References
6
6
  ----------
@@ -8,7 +8,31 @@ https://tldp.org/LDP/abs/html/exitcodes.html
8
8
 
9
9
  """
10
10
 
11
+ import numpy as np
12
+
11
13
  #: Formats supported for templates.
12
14
  ALL_TEMPLATE_FORMATS = ("yaml", "json")
13
15
  #: The exit code used by an EAR when it aborts.
14
16
  ABORT_EXIT_CODE = 64
17
+ SKIPPED_EXIT_CODE = 65
18
+ NO_COMMANDS_EXIT_CODE = 66
19
+ RUN_DIR_ARR_DTYPE = [
20
+ ("task_insert_ID", np.uint8),
21
+ ("element_idx", np.uint32),
22
+ ("iteration_idx", np.uint32),
23
+ ("action_idx", np.uint8),
24
+ ("run_idx", np.uint8),
25
+ ("element_depth", np.uint8),
26
+ ("iteration_depth", np.uint8),
27
+ ]
28
+ _uint8_max = np.iinfo(np.uint8).max
29
+ _uint32_max = np.iinfo(np.uint32).max
30
+ RUN_DIR_ARR_FILL = (
31
+ _uint8_max,
32
+ _uint32_max,
33
+ _uint32_max,
34
+ _uint8_max,
35
+ _uint8_max,
36
+ _uint8_max,
37
+ _uint8_max,
38
+ )