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.
Files changed (176) hide show
  1. hpcflow/__pyinstaller/hook-hpcflow.py +9 -6
  2. hpcflow/_version.py +1 -1
  3. hpcflow/app.py +1 -0
  4. hpcflow/data/scripts/bad_script.py +2 -0
  5. hpcflow/data/scripts/do_nothing.py +2 -0
  6. hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
  7. hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
  8. hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
  9. hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
  10. hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
  11. hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
  12. hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
  13. hpcflow/data/scripts/input_file_generator_basic.py +3 -0
  14. hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
  15. hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
  16. hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
  17. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
  18. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
  19. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
  20. hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
  21. hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
  22. hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
  23. hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +1 -1
  24. hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
  25. hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +1 -1
  26. hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
  27. hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
  28. hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
  29. hpcflow/data/scripts/output_file_parser_basic.py +3 -0
  30. hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
  31. hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
  32. hpcflow/data/scripts/script_exit_test.py +5 -0
  33. hpcflow/data/template_components/environments.yaml +1 -1
  34. hpcflow/sdk/__init__.py +26 -15
  35. hpcflow/sdk/app.py +2192 -768
  36. hpcflow/sdk/cli.py +506 -296
  37. hpcflow/sdk/cli_common.py +105 -7
  38. hpcflow/sdk/config/__init__.py +1 -1
  39. hpcflow/sdk/config/callbacks.py +115 -43
  40. hpcflow/sdk/config/cli.py +126 -103
  41. hpcflow/sdk/config/config.py +674 -318
  42. hpcflow/sdk/config/config_file.py +131 -95
  43. hpcflow/sdk/config/errors.py +125 -84
  44. hpcflow/sdk/config/types.py +148 -0
  45. hpcflow/sdk/core/__init__.py +25 -1
  46. hpcflow/sdk/core/actions.py +1771 -1059
  47. hpcflow/sdk/core/app_aware.py +24 -0
  48. hpcflow/sdk/core/cache.py +139 -79
  49. hpcflow/sdk/core/command_files.py +263 -287
  50. hpcflow/sdk/core/commands.py +145 -112
  51. hpcflow/sdk/core/element.py +828 -535
  52. hpcflow/sdk/core/enums.py +192 -0
  53. hpcflow/sdk/core/environment.py +74 -93
  54. hpcflow/sdk/core/errors.py +455 -52
  55. hpcflow/sdk/core/execute.py +207 -0
  56. hpcflow/sdk/core/json_like.py +540 -272
  57. hpcflow/sdk/core/loop.py +751 -347
  58. hpcflow/sdk/core/loop_cache.py +164 -47
  59. hpcflow/sdk/core/object_list.py +370 -207
  60. hpcflow/sdk/core/parameters.py +1100 -627
  61. hpcflow/sdk/core/rule.py +59 -41
  62. hpcflow/sdk/core/run_dir_files.py +21 -37
  63. hpcflow/sdk/core/skip_reason.py +7 -0
  64. hpcflow/sdk/core/task.py +1649 -1339
  65. hpcflow/sdk/core/task_schema.py +308 -196
  66. hpcflow/sdk/core/test_utils.py +191 -114
  67. hpcflow/sdk/core/types.py +440 -0
  68. hpcflow/sdk/core/utils.py +485 -309
  69. hpcflow/sdk/core/validation.py +82 -9
  70. hpcflow/sdk/core/workflow.py +2544 -1178
  71. hpcflow/sdk/core/zarr_io.py +98 -137
  72. hpcflow/sdk/data/workflow_spec_schema.yaml +2 -0
  73. hpcflow/sdk/demo/cli.py +53 -33
  74. hpcflow/sdk/helper/cli.py +18 -15
  75. hpcflow/sdk/helper/helper.py +75 -63
  76. hpcflow/sdk/helper/watcher.py +61 -28
  77. hpcflow/sdk/log.py +122 -71
  78. hpcflow/sdk/persistence/__init__.py +8 -31
  79. hpcflow/sdk/persistence/base.py +1360 -606
  80. hpcflow/sdk/persistence/defaults.py +6 -0
  81. hpcflow/sdk/persistence/discovery.py +38 -0
  82. hpcflow/sdk/persistence/json.py +568 -188
  83. hpcflow/sdk/persistence/pending.py +382 -179
  84. hpcflow/sdk/persistence/store_resource.py +39 -23
  85. hpcflow/sdk/persistence/types.py +318 -0
  86. hpcflow/sdk/persistence/utils.py +14 -11
  87. hpcflow/sdk/persistence/zarr.py +1337 -433
  88. hpcflow/sdk/runtime.py +44 -41
  89. hpcflow/sdk/submission/{jobscript_info.py → enums.py} +39 -12
  90. hpcflow/sdk/submission/jobscript.py +1651 -692
  91. hpcflow/sdk/submission/schedulers/__init__.py +167 -39
  92. hpcflow/sdk/submission/schedulers/direct.py +121 -81
  93. hpcflow/sdk/submission/schedulers/sge.py +170 -129
  94. hpcflow/sdk/submission/schedulers/slurm.py +291 -268
  95. hpcflow/sdk/submission/schedulers/utils.py +12 -2
  96. hpcflow/sdk/submission/shells/__init__.py +14 -15
  97. hpcflow/sdk/submission/shells/base.py +150 -29
  98. hpcflow/sdk/submission/shells/bash.py +283 -173
  99. hpcflow/sdk/submission/shells/os_version.py +31 -30
  100. hpcflow/sdk/submission/shells/powershell.py +228 -170
  101. hpcflow/sdk/submission/submission.py +1014 -335
  102. hpcflow/sdk/submission/types.py +140 -0
  103. hpcflow/sdk/typing.py +182 -12
  104. hpcflow/sdk/utils/arrays.py +71 -0
  105. hpcflow/sdk/utils/deferred_file.py +55 -0
  106. hpcflow/sdk/utils/hashing.py +16 -0
  107. hpcflow/sdk/utils/patches.py +12 -0
  108. hpcflow/sdk/utils/strings.py +33 -0
  109. hpcflow/tests/api/test_api.py +32 -0
  110. hpcflow/tests/conftest.py +27 -6
  111. hpcflow/tests/data/multi_path_sequences.yaml +29 -0
  112. hpcflow/tests/data/workflow_test_run_abort.yaml +34 -35
  113. hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
  114. hpcflow/tests/schedulers/slurm/test_slurm_submission.py +5 -2
  115. hpcflow/tests/scripts/test_input_file_generators.py +282 -0
  116. hpcflow/tests/scripts/test_main_scripts.py +866 -85
  117. hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
  118. hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
  119. hpcflow/tests/shells/wsl/test_wsl_submission.py +12 -4
  120. hpcflow/tests/unit/test_action.py +262 -75
  121. hpcflow/tests/unit/test_action_rule.py +9 -4
  122. hpcflow/tests/unit/test_app.py +33 -6
  123. hpcflow/tests/unit/test_cache.py +46 -0
  124. hpcflow/tests/unit/test_cli.py +134 -1
  125. hpcflow/tests/unit/test_command.py +71 -54
  126. hpcflow/tests/unit/test_config.py +142 -16
  127. hpcflow/tests/unit/test_config_file.py +21 -18
  128. hpcflow/tests/unit/test_element.py +58 -62
  129. hpcflow/tests/unit/test_element_iteration.py +50 -1
  130. hpcflow/tests/unit/test_element_set.py +29 -19
  131. hpcflow/tests/unit/test_group.py +4 -2
  132. hpcflow/tests/unit/test_input_source.py +116 -93
  133. hpcflow/tests/unit/test_input_value.py +29 -24
  134. hpcflow/tests/unit/test_jobscript_unit.py +757 -0
  135. hpcflow/tests/unit/test_json_like.py +44 -35
  136. hpcflow/tests/unit/test_loop.py +1396 -84
  137. hpcflow/tests/unit/test_meta_task.py +325 -0
  138. hpcflow/tests/unit/test_multi_path_sequences.py +229 -0
  139. hpcflow/tests/unit/test_object_list.py +17 -12
  140. hpcflow/tests/unit/test_parameter.py +29 -7
  141. hpcflow/tests/unit/test_persistence.py +237 -42
  142. hpcflow/tests/unit/test_resources.py +20 -18
  143. hpcflow/tests/unit/test_run.py +117 -6
  144. hpcflow/tests/unit/test_run_directories.py +29 -0
  145. hpcflow/tests/unit/test_runtime.py +2 -1
  146. hpcflow/tests/unit/test_schema_input.py +23 -15
  147. hpcflow/tests/unit/test_shell.py +23 -2
  148. hpcflow/tests/unit/test_slurm.py +8 -7
  149. hpcflow/tests/unit/test_submission.py +38 -89
  150. hpcflow/tests/unit/test_task.py +352 -247
  151. hpcflow/tests/unit/test_task_schema.py +33 -20
  152. hpcflow/tests/unit/test_utils.py +9 -11
  153. hpcflow/tests/unit/test_value_sequence.py +15 -12
  154. hpcflow/tests/unit/test_workflow.py +114 -83
  155. hpcflow/tests/unit/test_workflow_template.py +0 -1
  156. hpcflow/tests/unit/utils/test_arrays.py +40 -0
  157. hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
  158. hpcflow/tests/unit/utils/test_hashing.py +65 -0
  159. hpcflow/tests/unit/utils/test_patches.py +5 -0
  160. hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
  161. hpcflow/tests/workflows/__init__.py +0 -0
  162. hpcflow/tests/workflows/test_directory_structure.py +31 -0
  163. hpcflow/tests/workflows/test_jobscript.py +334 -1
  164. hpcflow/tests/workflows/test_run_status.py +198 -0
  165. hpcflow/tests/workflows/test_skip_downstream.py +696 -0
  166. hpcflow/tests/workflows/test_submission.py +140 -0
  167. hpcflow/tests/workflows/test_workflows.py +160 -15
  168. hpcflow/tests/workflows/test_zip.py +18 -0
  169. hpcflow/viz_demo.ipynb +6587 -3
  170. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/METADATA +8 -4
  171. hpcflow_new2-0.2.0a199.dist-info/RECORD +221 -0
  172. hpcflow/sdk/core/parallel.py +0 -21
  173. hpcflow_new2-0.2.0a189.dist-info/RECORD +0 -158
  174. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/LICENSE +0 -0
  175. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/WHEEL +0 -0
  176. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.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,39 @@ 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]] = []
60
+
61
+ def __enter__(self):
62
+ self.__class__.active = True
63
+ return self
64
+
65
+ def __exit__(self, exc_type, exc_val, exc_tb):
66
+ try:
67
+ self.__class__.summarise_string()
68
+ finally:
69
+ self.__class__.reset()
70
+ self.__class__.active = False
32
71
 
33
72
  @classmethod
34
- def decorator(cls, func):
73
+ def decorator(cls, func: Callable[P, T]) -> Callable[P, T]:
35
74
  """
36
75
  Decorator for a method that is to have its execution time monitored.
37
76
  """
38
77
 
39
78
  @wraps(func)
40
- def wrapper(*args, **kwargs):
41
-
79
+ def wrapper(*args, **kwargs) -> T:
42
80
  if not cls.active:
43
81
  return func(*args, **kwargs)
44
82
 
@@ -68,48 +106,45 @@ class TimeIt:
68
106
  return wrapper
69
107
 
70
108
  @classmethod
71
- def summarise(cls):
109
+ def _summarise(cls) -> dict[tuple[str, ...], _Summary]:
72
110
  """
73
111
  Produce a machine-readable summary of method execution time statistics.
74
112
  """
75
113
  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
- }
114
+ k: _Summary(
115
+ len(v),
116
+ statistics.mean(v),
117
+ statistics.pstdev(v),
118
+ min(v),
119
+ max(v),
120
+ sum(v),
121
+ {},
122
+ )
84
123
  for k, v in cls.timers.items()
85
124
  }
86
125
 
87
126
  # 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):
127
+ for key in sorted(stats, key=lambda x: len(x), reverse=True):
92
128
  if len(key) == 1:
93
129
  continue
94
130
  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
131
+ parent_key = key[:-1]
132
+ if parent_key in stats:
133
+ stats[parent_key].children[key] = value
100
134
 
101
135
  return stats
102
136
 
103
137
  @classmethod
104
- def summarise_string(cls):
138
+ def summarise_string(cls) -> None:
105
139
  """
106
140
  Produce a human-readable summary of method execution time statistics.
107
141
  """
108
142
 
109
- def _format_nodes(node, depth=0, depth_final=None):
110
- if depth_final is None:
111
- depth_final = []
112
- out = []
143
+ def _format_nodes(
144
+ node: dict[tuple[str, ...], _Summary],
145
+ depth: int = 0,
146
+ depth_final: Sequence[bool] = (),
147
+ ):
113
148
  for idx, (k, v) in enumerate(node.items()):
114
149
  is_final_child = idx == len(node) - 1
115
150
  angle = "└ " if is_final_child else "├ "
@@ -117,38 +152,40 @@ class TimeIt:
117
152
  if depth > 0:
118
153
  bars = "".join(f"{'│ ' if not i else ' '}" for i in depth_final)
119
154
  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}"
155
+ min_str = f"{v.min:10.6f}" if v.number > 1 else f"{f'-':^12s}"
156
+ max_str = f"{v.max:10.6f}" if v.number > 1 else f"{f'-':^12s}"
157
+ stddev_str = f"({v.stddev:8.6f})" if v.number > 1 else f"{f' ':^10s}"
124
158
  out.append(
125
- f"{k_str:.<80s} {v['sum']:12.6f} "
126
- f"{v['mean']:10.6f} {stddev_str} {number:8d} "
159
+ f"{k_str:.<80s} {v.sum:12.6f} "
160
+ f"{v.mean:10.6f} {stddev_str} {v.number:8d} "
127
161
  f"{min_str} {max_str} "
128
162
  )
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
163
+ depth_final_next = list(depth_final)
164
+ if depth > 0:
165
+ depth_final_next.append(is_final_child)
166
+ _format_nodes(v.children, depth + 1, depth_final_next)
138
167
 
139
- summary = cls.summarise()
168
+ summary = cls._summarise()
140
169
 
141
170
  out = [
142
171
  f"{'function':^80s} {'sum /s':^12s} {'mean (stddev) /s':^20s} {'N':^8s} "
143
172
  f"{'min /s':^12s} {'max /s':^12s}"
144
173
  ]
145
- out += _format_nodes(summary)
174
+ _format_nodes(summary)
146
175
  out_str = "\n".join(out)
147
176
  if cls.file_path:
148
177
  Path(cls.file_path).write_text(out_str, encoding="utf-8")
149
178
  else:
150
179
  print(out_str)
151
180
 
181
+ @classmethod
182
+ def reset(cls):
183
+ cls.timers = defaultdict(list)
184
+ cls.trace = []
185
+ cls.trace_idx = []
186
+ cls.trace_prev = []
187
+ cls.trace_idx_prev = []
188
+
152
189
 
153
190
  class AppLog:
154
191
  """
@@ -156,22 +193,23 @@ class AppLog:
156
193
  """
157
194
 
158
195
  #: Default logging level for the console.
159
- DEFAULT_LOG_CONSOLE_LEVEL = "WARNING"
196
+ DEFAULT_LOG_CONSOLE_LEVEL: ClassVar = "WARNING"
160
197
  #: Default logging level for log files.
161
- DEFAULT_LOG_FILE_LEVEL = "INFO"
198
+ DEFAULT_LOG_FILE_LEVEL: ClassVar = "WARNING"
162
199
 
163
- def __init__(self, app, log_console_level=None):
200
+ def __init__(self, app: BaseApp, log_console_level: str | None = None) -> None:
164
201
  #: The application context.
165
- self.app = app
202
+ self._app = app
166
203
  #: The base logger for the application.
167
204
  self.logger = logging.getLogger(app.package_name)
168
205
  self.logger.setLevel(logging.DEBUG)
169
206
  #: The handler for directing logging messages to the console.
170
- self.console_handler = self._add_console_logger(
207
+ self.console_handler = self.__add_console_logger(
171
208
  level=log_console_level or AppLog.DEFAULT_LOG_CONSOLE_LEVEL
172
209
  )
210
+ self.file_handler: logging.FileHandler | None = None
173
211
 
174
- def _add_console_logger(self, level, fmt=None):
212
+ def __add_console_logger(self, level: str, fmt: str | None = None) -> logging.Handler:
175
213
  fmt = fmt or "%(levelname)s %(name)s: %(message)s"
176
214
  handler = logging.StreamHandler()
177
215
  handler.setFormatter(logging.Formatter(fmt))
@@ -179,18 +217,30 @@ class AppLog:
179
217
  self.logger.addHandler(handler)
180
218
  return handler
181
219
 
182
- def update_console_level(self, new_level):
220
+ def update_console_level(self, new_level: str | None = None) -> None:
183
221
  """
184
222
  Set the logging level for console messages.
185
223
  """
186
- if new_level:
187
- self.console_handler.setLevel(new_level.upper())
188
-
189
- def add_file_logger(self, path, level=None, fmt=None, max_bytes=None):
224
+ new_level = new_level or AppLog.DEFAULT_LOG_CONSOLE_LEVEL
225
+ self.console_handler.setLevel(new_level.upper())
226
+
227
+ def update_file_level(self, new_level: str | None = None) -> None:
228
+ if self.file_handler:
229
+ new_level = new_level or AppLog.DEFAULT_LOG_FILE_LEVEL
230
+ self.file_handler.setLevel(new_level.upper())
231
+
232
+ def add_file_logger(
233
+ self,
234
+ path: str | Path,
235
+ level: str | None = None,
236
+ fmt: str | None = None,
237
+ max_bytes: int | None = None,
238
+ ) -> None:
190
239
  """
191
240
  Add a log file.
192
241
  """
193
- fmt = fmt or f"%(asctime)s %(levelname)s %(name)s: %(message)s"
242
+ path = Path(path)
243
+ fmt = fmt or "%(asctime)s %(levelname)s %(name)s: %(message)s"
194
244
  level = level or AppLog.DEFAULT_LOG_FILE_LEVEL
195
245
  max_bytes = max_bytes or int(10e6)
196
246
 
@@ -202,12 +252,13 @@ class AppLog:
202
252
  handler.setFormatter(logging.Formatter(fmt))
203
253
  handler.setLevel(level.upper())
204
254
  self.logger.addHandler(handler)
205
- return handler
206
-
207
- def remove_file_handlers(self):
208
- """Remove all file handlers."""
209
- # TODO: store a `file_handlers` attribute as well as `console_handlers`
210
- for hdlr in self.logger.handlers:
211
- if isinstance(hdlr, logging.FileHandler):
212
- self.logger.debug(f"Removing file handler from the AppLog: {hdlr!r}.")
213
- self.logger.removeHandler(hdlr)
255
+ self.file_handler = handler
256
+
257
+ def remove_file_handler(self) -> None:
258
+ """Remove the file handler."""
259
+ if self.file_handler:
260
+ self.logger.debug(
261
+ f"Removing file handler from the AppLog: {self.file_handler!r}."
262
+ )
263
+ self.logger.removeHandler(self.file_handler)
264
+ self.file_handler = None
@@ -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)