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.
Files changed (115) hide show
  1. hpcflow/__pyinstaller/hook-hpcflow.py +8 -6
  2. hpcflow/_version.py +1 -1
  3. hpcflow/app.py +1 -0
  4. hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +1 -1
  5. hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +1 -1
  6. hpcflow/sdk/__init__.py +21 -15
  7. hpcflow/sdk/app.py +2133 -770
  8. hpcflow/sdk/cli.py +281 -250
  9. hpcflow/sdk/cli_common.py +6 -2
  10. hpcflow/sdk/config/__init__.py +1 -1
  11. hpcflow/sdk/config/callbacks.py +77 -42
  12. hpcflow/sdk/config/cli.py +126 -103
  13. hpcflow/sdk/config/config.py +578 -311
  14. hpcflow/sdk/config/config_file.py +131 -95
  15. hpcflow/sdk/config/errors.py +112 -85
  16. hpcflow/sdk/config/types.py +145 -0
  17. hpcflow/sdk/core/actions.py +1054 -994
  18. hpcflow/sdk/core/app_aware.py +24 -0
  19. hpcflow/sdk/core/cache.py +81 -63
  20. hpcflow/sdk/core/command_files.py +275 -185
  21. hpcflow/sdk/core/commands.py +111 -107
  22. hpcflow/sdk/core/element.py +724 -503
  23. hpcflow/sdk/core/enums.py +192 -0
  24. hpcflow/sdk/core/environment.py +74 -93
  25. hpcflow/sdk/core/errors.py +398 -51
  26. hpcflow/sdk/core/json_like.py +540 -272
  27. hpcflow/sdk/core/loop.py +380 -334
  28. hpcflow/sdk/core/loop_cache.py +160 -43
  29. hpcflow/sdk/core/object_list.py +370 -207
  30. hpcflow/sdk/core/parameters.py +728 -600
  31. hpcflow/sdk/core/rule.py +59 -41
  32. hpcflow/sdk/core/run_dir_files.py +33 -22
  33. hpcflow/sdk/core/task.py +1546 -1325
  34. hpcflow/sdk/core/task_schema.py +240 -196
  35. hpcflow/sdk/core/test_utils.py +126 -88
  36. hpcflow/sdk/core/types.py +387 -0
  37. hpcflow/sdk/core/utils.py +410 -305
  38. hpcflow/sdk/core/validation.py +82 -9
  39. hpcflow/sdk/core/workflow.py +1192 -1028
  40. hpcflow/sdk/core/zarr_io.py +98 -137
  41. hpcflow/sdk/demo/cli.py +46 -33
  42. hpcflow/sdk/helper/cli.py +18 -16
  43. hpcflow/sdk/helper/helper.py +75 -63
  44. hpcflow/sdk/helper/watcher.py +61 -28
  45. hpcflow/sdk/log.py +83 -59
  46. hpcflow/sdk/persistence/__init__.py +8 -31
  47. hpcflow/sdk/persistence/base.py +988 -586
  48. hpcflow/sdk/persistence/defaults.py +6 -0
  49. hpcflow/sdk/persistence/discovery.py +38 -0
  50. hpcflow/sdk/persistence/json.py +408 -153
  51. hpcflow/sdk/persistence/pending.py +158 -123
  52. hpcflow/sdk/persistence/store_resource.py +37 -22
  53. hpcflow/sdk/persistence/types.py +307 -0
  54. hpcflow/sdk/persistence/utils.py +14 -11
  55. hpcflow/sdk/persistence/zarr.py +477 -420
  56. hpcflow/sdk/runtime.py +44 -41
  57. hpcflow/sdk/submission/{jobscript_info.py → enums.py} +39 -12
  58. hpcflow/sdk/submission/jobscript.py +444 -404
  59. hpcflow/sdk/submission/schedulers/__init__.py +133 -40
  60. hpcflow/sdk/submission/schedulers/direct.py +97 -71
  61. hpcflow/sdk/submission/schedulers/sge.py +132 -126
  62. hpcflow/sdk/submission/schedulers/slurm.py +263 -268
  63. hpcflow/sdk/submission/schedulers/utils.py +7 -2
  64. hpcflow/sdk/submission/shells/__init__.py +14 -15
  65. hpcflow/sdk/submission/shells/base.py +102 -29
  66. hpcflow/sdk/submission/shells/bash.py +72 -55
  67. hpcflow/sdk/submission/shells/os_version.py +31 -30
  68. hpcflow/sdk/submission/shells/powershell.py +37 -29
  69. hpcflow/sdk/submission/submission.py +203 -257
  70. hpcflow/sdk/submission/types.py +143 -0
  71. hpcflow/sdk/typing.py +163 -12
  72. hpcflow/tests/conftest.py +8 -6
  73. hpcflow/tests/schedulers/slurm/test_slurm_submission.py +5 -2
  74. hpcflow/tests/scripts/test_main_scripts.py +60 -30
  75. hpcflow/tests/shells/wsl/test_wsl_submission.py +6 -4
  76. hpcflow/tests/unit/test_action.py +86 -75
  77. hpcflow/tests/unit/test_action_rule.py +9 -4
  78. hpcflow/tests/unit/test_app.py +13 -6
  79. hpcflow/tests/unit/test_cli.py +1 -1
  80. hpcflow/tests/unit/test_command.py +71 -54
  81. hpcflow/tests/unit/test_config.py +20 -15
  82. hpcflow/tests/unit/test_config_file.py +21 -18
  83. hpcflow/tests/unit/test_element.py +58 -62
  84. hpcflow/tests/unit/test_element_iteration.py +3 -1
  85. hpcflow/tests/unit/test_element_set.py +29 -19
  86. hpcflow/tests/unit/test_group.py +4 -2
  87. hpcflow/tests/unit/test_input_source.py +116 -93
  88. hpcflow/tests/unit/test_input_value.py +29 -24
  89. hpcflow/tests/unit/test_json_like.py +44 -35
  90. hpcflow/tests/unit/test_loop.py +65 -58
  91. hpcflow/tests/unit/test_object_list.py +17 -12
  92. hpcflow/tests/unit/test_parameter.py +16 -7
  93. hpcflow/tests/unit/test_persistence.py +48 -35
  94. hpcflow/tests/unit/test_resources.py +20 -18
  95. hpcflow/tests/unit/test_run.py +8 -3
  96. hpcflow/tests/unit/test_runtime.py +2 -1
  97. hpcflow/tests/unit/test_schema_input.py +23 -15
  98. hpcflow/tests/unit/test_shell.py +3 -2
  99. hpcflow/tests/unit/test_slurm.py +8 -7
  100. hpcflow/tests/unit/test_submission.py +39 -19
  101. hpcflow/tests/unit/test_task.py +352 -247
  102. hpcflow/tests/unit/test_task_schema.py +33 -20
  103. hpcflow/tests/unit/test_utils.py +9 -11
  104. hpcflow/tests/unit/test_value_sequence.py +15 -12
  105. hpcflow/tests/unit/test_workflow.py +114 -83
  106. hpcflow/tests/unit/test_workflow_template.py +0 -1
  107. hpcflow/tests/workflows/test_jobscript.py +2 -1
  108. hpcflow/tests/workflows/test_workflows.py +18 -13
  109. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/METADATA +2 -1
  110. hpcflow_new2-0.2.0a190.dist-info/RECORD +165 -0
  111. hpcflow/sdk/core/parallel.py +0 -21
  112. hpcflow_new2-0.2.0a189.dist-info/RECORD +0 -158
  113. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/LICENSE +0 -0
  114. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/WHEEL +0 -0
  115. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/log.py CHANGED
@@ -2,12 +2,40 @@
2
2
  Interface to the standard logger, and performance logging utility.
3
3
  """
4
4
 
5
+ from __future__ import annotations
5
6
  from functools import wraps
6
7
  import logging
8
+ import logging.handlers
7
9
  from pathlib import Path
8
10
  import time
9
11
  from collections import defaultdict
12
+ from collections.abc import Callable, Sequence
10
13
  import statistics
14
+ from dataclasses import dataclass
15
+ from typing import ClassVar, TypeVar, TYPE_CHECKING
16
+ from typing_extensions import ParamSpec
17
+
18
+ if TYPE_CHECKING:
19
+ from .app import BaseApp
20
+
21
+
22
+ P = ParamSpec("P")
23
+ T = TypeVar("T")
24
+
25
+
26
+ @dataclass
27
+ class _Summary:
28
+ """
29
+ Summary of a particular node's execution time.
30
+ """
31
+
32
+ number: int
33
+ mean: float
34
+ stddev: float
35
+ min: float
36
+ max: float
37
+ sum: float
38
+ children: dict[tuple[str, ...], _Summary]
11
39
 
12
40
 
13
41
  class TimeIt:
@@ -16,29 +44,28 @@ class TimeIt:
16
44
  """
17
45
 
18
46
  #: Whether the instrumentation is active.
19
- active = False
47
+ active: ClassVar = False
20
48
  #: Where to log to.
21
- file_path = None
49
+ file_path: ClassVar[str | None] = None
22
50
  #: The details be tracked.
23
- timers = defaultdict(list)
51
+ timers: ClassVar[dict[tuple[str, ...], list[float]]] = defaultdict(list)
24
52
  #: Traces of the stack.
25
- trace = []
53
+ trace: ClassVar[list[str]] = []
26
54
  #: Trace indices.
27
- trace_idx = []
55
+ trace_idx: ClassVar[list[int]] = []
28
56
  #: Preceding traces.
29
- trace_prev = []
57
+ trace_prev: ClassVar[list[str]] = []
30
58
  #: Preceding trace indices.
31
- trace_idx_prev = []
59
+ trace_idx_prev: ClassVar[list[int]] = []
32
60
 
33
61
  @classmethod
34
- def decorator(cls, func):
62
+ def decorator(cls, func: Callable[P, T]) -> Callable[P, T]:
35
63
  """
36
64
  Decorator for a method that is to have its execution time monitored.
37
65
  """
38
66
 
39
67
  @wraps(func)
40
- def wrapper(*args, **kwargs):
41
-
68
+ def wrapper(*args, **kwargs) -> T:
42
69
  if not cls.active:
43
70
  return func(*args, **kwargs)
44
71
 
@@ -68,48 +95,45 @@ class TimeIt:
68
95
  return wrapper
69
96
 
70
97
  @classmethod
71
- def summarise(cls):
98
+ def _summarise(cls) -> dict[tuple[str, ...], _Summary]:
72
99
  """
73
100
  Produce a machine-readable summary of method execution time statistics.
74
101
  """
75
102
  stats = {
76
- k: {
77
- "number": len(v),
78
- "mean": statistics.mean(v),
79
- "stddev": statistics.pstdev(v),
80
- "min": min(v),
81
- "max": max(v),
82
- "sum": sum(v),
83
- }
103
+ k: _Summary(
104
+ len(v),
105
+ statistics.mean(v),
106
+ statistics.pstdev(v),
107
+ min(v),
108
+ max(v),
109
+ sum(v),
110
+ {},
111
+ )
84
112
  for k, v in cls.timers.items()
85
113
  }
86
114
 
87
115
  # make a graph
88
- for k in stats:
89
- stats[k]["children"] = {}
90
-
91
- for key in sorted(stats.keys(), key=lambda x: len(x), reverse=True):
116
+ for key in sorted(stats, key=lambda x: len(x), reverse=True):
92
117
  if len(key) == 1:
93
118
  continue
94
119
  value = stats.pop(key)
95
- parent = key[:-1]
96
- for other_key in stats.keys():
97
- if other_key == parent:
98
- stats[other_key]["children"][key] = value
99
- break
120
+ parent_key = key[:-1]
121
+ if parent_key in stats:
122
+ stats[parent_key].children[key] = value
100
123
 
101
124
  return stats
102
125
 
103
126
  @classmethod
104
- def summarise_string(cls):
127
+ def summarise_string(cls) -> None:
105
128
  """
106
129
  Produce a human-readable summary of method execution time statistics.
107
130
  """
108
131
 
109
- def _format_nodes(node, depth=0, depth_final=None):
110
- if depth_final is None:
111
- depth_final = []
112
- out = []
132
+ def _format_nodes(
133
+ node: dict[tuple[str, ...], _Summary],
134
+ depth: int = 0,
135
+ depth_final: Sequence[bool] = (),
136
+ ):
113
137
  for idx, (k, v) in enumerate(node.items()):
114
138
  is_final_child = idx == len(node) - 1
115
139
  angle = "└ " if is_final_child else "├ "
@@ -117,32 +141,26 @@ class TimeIt:
117
141
  if depth > 0:
118
142
  bars = "".join(f"{'│ ' if not i else ' '}" for i in depth_final)
119
143
  k_str = bars + (angle if depth > 0 else "") + f"{k[depth]}"
120
- number = v["number"]
121
- min_str = f"{v['min']:10.6f}" if number > 1 else f"{f'-':^12s}"
122
- max_str = f"{v['max']:10.6f}" if number > 1 else f"{f'-':^12s}"
123
- stddev_str = f"({v['stddev']:8.6f})" if number > 1 else f"{f' ':^10s}"
144
+ min_str = f"{v.min:10.6f}" if v.number > 1 else f"{f'-':^12s}"
145
+ max_str = f"{v.max:10.6f}" if v.number > 1 else f"{f'-':^12s}"
146
+ stddev_str = f"({v.stddev:8.6f})" if v.number > 1 else f"{f' ':^10s}"
124
147
  out.append(
125
- f"{k_str:.<80s} {v['sum']:12.6f} "
126
- f"{v['mean']:10.6f} {stddev_str} {number:8d} "
148
+ f"{k_str:.<80s} {v.sum:12.6f} "
149
+ f"{v.mean:10.6f} {stddev_str} {v.number:8d} "
127
150
  f"{min_str} {max_str} "
128
151
  )
129
- depth_final_next = list(depth_final) + (
130
- [is_final_child] if depth > 0 else []
131
- )
132
- out.extend(
133
- _format_nodes(
134
- v["children"], depth=depth + 1, depth_final=depth_final_next
135
- )
136
- )
137
- return out
152
+ depth_final_next = list(depth_final)
153
+ if depth > 0:
154
+ depth_final_next.append(is_final_child)
155
+ _format_nodes(v.children, depth + 1, depth_final_next)
138
156
 
139
- summary = cls.summarise()
157
+ summary = cls._summarise()
140
158
 
141
159
  out = [
142
160
  f"{'function':^80s} {'sum /s':^12s} {'mean (stddev) /s':^20s} {'N':^8s} "
143
161
  f"{'min /s':^12s} {'max /s':^12s}"
144
162
  ]
145
- out += _format_nodes(summary)
163
+ _format_nodes(summary)
146
164
  out_str = "\n".join(out)
147
165
  if cls.file_path:
148
166
  Path(cls.file_path).write_text(out_str, encoding="utf-8")
@@ -156,22 +174,22 @@ class AppLog:
156
174
  """
157
175
 
158
176
  #: Default logging level for the console.
159
- DEFAULT_LOG_CONSOLE_LEVEL = "WARNING"
177
+ DEFAULT_LOG_CONSOLE_LEVEL: ClassVar = "WARNING"
160
178
  #: Default logging level for log files.
161
- DEFAULT_LOG_FILE_LEVEL = "INFO"
179
+ DEFAULT_LOG_FILE_LEVEL: ClassVar = "INFO"
162
180
 
163
- def __init__(self, app, log_console_level=None):
181
+ def __init__(self, app: BaseApp, log_console_level: str | None = None) -> None:
164
182
  #: The application context.
165
183
  self.app = app
166
184
  #: The base logger for the application.
167
185
  self.logger = logging.getLogger(app.package_name)
168
186
  self.logger.setLevel(logging.DEBUG)
169
187
  #: The handler for directing logging messages to the console.
170
- self.console_handler = self._add_console_logger(
188
+ self.console_handler = self.__add_console_logger(
171
189
  level=log_console_level or AppLog.DEFAULT_LOG_CONSOLE_LEVEL
172
190
  )
173
191
 
174
- def _add_console_logger(self, level, fmt=None):
192
+ def __add_console_logger(self, level: str, fmt: str | None = None) -> logging.Handler:
175
193
  fmt = fmt or "%(levelname)s %(name)s: %(message)s"
176
194
  handler = logging.StreamHandler()
177
195
  handler.setFormatter(logging.Formatter(fmt))
@@ -179,18 +197,24 @@ class AppLog:
179
197
  self.logger.addHandler(handler)
180
198
  return handler
181
199
 
182
- def update_console_level(self, new_level):
200
+ def update_console_level(self, new_level: str) -> None:
183
201
  """
184
202
  Set the logging level for console messages.
185
203
  """
186
204
  if new_level:
187
205
  self.console_handler.setLevel(new_level.upper())
188
206
 
189
- def add_file_logger(self, path, level=None, fmt=None, max_bytes=None):
207
+ def add_file_logger(
208
+ self,
209
+ path: Path,
210
+ level: str | None = None,
211
+ fmt: str | None = None,
212
+ max_bytes: int | None = None,
213
+ ) -> logging.Handler:
190
214
  """
191
215
  Add a log file.
192
216
  """
193
- fmt = fmt or f"%(asctime)s %(levelname)s %(name)s: %(message)s"
217
+ fmt = fmt or "%(asctime)s %(levelname)s %(name)s: %(message)s"
194
218
  level = level or AppLog.DEFAULT_LOG_FILE_LEVEL
195
219
  max_bytes = max_bytes or int(10e6)
196
220
 
@@ -204,7 +228,7 @@ class AppLog:
204
228
  self.logger.addHandler(handler)
205
229
  return handler
206
230
 
207
- def remove_file_handlers(self):
231
+ def remove_file_handlers(self) -> None:
208
232
  """Remove all file handlers."""
209
233
  # TODO: store a `file_handlers` attribute as well as `console_handlers`
210
234
  for hdlr in self.logger.handlers:
@@ -2,40 +2,17 @@
2
2
  Workflow persistence subsystem.
3
3
  """
4
4
 
5
- import copy
6
- from pathlib import Path
7
- import random
8
- import string
9
- import time
10
- from typing import Type, Union
5
+ from __future__ import annotations
6
+ from typing import TYPE_CHECKING
11
7
 
12
- from reretry import retry
8
+ if TYPE_CHECKING:
9
+ from .base import PersistentStore
13
10
 
14
- from hpcflow.sdk.persistence.base import PersistentStore
15
- from hpcflow.sdk.persistence.json import JSONPersistentStore
16
- from hpcflow.sdk.persistence.zarr import ZarrPersistentStore, ZarrZipPersistentStore
17
11
 
18
- _ALL_STORE_CLS = {
19
- "zarr": ZarrPersistentStore,
20
- "zip": ZarrZipPersistentStore,
21
- "json": JSONPersistentStore,
22
- # "json-single": JSONPersistentStore, # TODO
23
- }
24
- #: The name of the default persistence store.
25
- DEFAULT_STORE_FORMAT = "zarr"
26
- #: The persistence formats supported.
27
- ALL_STORE_FORMATS = tuple(_ALL_STORE_CLS.keys())
28
- #: The persistence formats supported for creation.
29
- ALL_CREATE_STORE_FORMATS = tuple(
30
- k for k, v in _ALL_STORE_CLS.items() if v._features.create
31
- )
32
-
33
-
34
- def store_cls_from_str(store_format: str) -> Type[PersistentStore]:
12
+ def store_cls_from_str(store_format: str) -> type[PersistentStore]:
35
13
  """
36
14
  Get the class that implements the persistence store from its name.
37
15
  """
38
- try:
39
- return _ALL_STORE_CLS[store_format]
40
- except KeyError:
41
- raise ValueError(f"Store format {store_format!r} not known.")
16
+ from .discovery import store_cls_from_str as impl
17
+
18
+ return impl(store_format)