hpcflow-new2 0.2.0a179__py3-none-any.whl → 0.2.0a181__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 (70) hide show
  1. hpcflow/_version.py +1 -1
  2. hpcflow/data/demo_data_manifest/__init__.py +3 -0
  3. hpcflow/sdk/__init__.py +4 -1
  4. hpcflow/sdk/app.py +160 -15
  5. hpcflow/sdk/cli.py +14 -0
  6. hpcflow/sdk/cli_common.py +83 -0
  7. hpcflow/sdk/config/__init__.py +4 -0
  8. hpcflow/sdk/config/callbacks.py +25 -2
  9. hpcflow/sdk/config/cli.py +4 -1
  10. hpcflow/sdk/config/config.py +188 -14
  11. hpcflow/sdk/config/config_file.py +91 -3
  12. hpcflow/sdk/config/errors.py +33 -0
  13. hpcflow/sdk/core/__init__.py +2 -0
  14. hpcflow/sdk/core/actions.py +492 -35
  15. hpcflow/sdk/core/cache.py +22 -0
  16. hpcflow/sdk/core/command_files.py +221 -5
  17. hpcflow/sdk/core/commands.py +57 -0
  18. hpcflow/sdk/core/element.py +407 -8
  19. hpcflow/sdk/core/environment.py +92 -0
  20. hpcflow/sdk/core/errors.py +245 -61
  21. hpcflow/sdk/core/json_like.py +72 -14
  22. hpcflow/sdk/core/loop.py +122 -21
  23. hpcflow/sdk/core/loop_cache.py +34 -9
  24. hpcflow/sdk/core/object_list.py +172 -26
  25. hpcflow/sdk/core/parallel.py +14 -0
  26. hpcflow/sdk/core/parameters.py +478 -25
  27. hpcflow/sdk/core/rule.py +31 -1
  28. hpcflow/sdk/core/run_dir_files.py +12 -2
  29. hpcflow/sdk/core/task.py +407 -80
  30. hpcflow/sdk/core/task_schema.py +70 -9
  31. hpcflow/sdk/core/test_utils.py +35 -0
  32. hpcflow/sdk/core/utils.py +101 -4
  33. hpcflow/sdk/core/validation.py +13 -1
  34. hpcflow/sdk/core/workflow.py +316 -96
  35. hpcflow/sdk/core/zarr_io.py +23 -0
  36. hpcflow/sdk/data/__init__.py +13 -0
  37. hpcflow/sdk/demo/__init__.py +3 -0
  38. hpcflow/sdk/helper/__init__.py +3 -0
  39. hpcflow/sdk/helper/cli.py +9 -0
  40. hpcflow/sdk/helper/helper.py +28 -0
  41. hpcflow/sdk/helper/watcher.py +33 -0
  42. hpcflow/sdk/log.py +40 -0
  43. hpcflow/sdk/persistence/__init__.py +14 -4
  44. hpcflow/sdk/persistence/base.py +289 -23
  45. hpcflow/sdk/persistence/json.py +29 -0
  46. hpcflow/sdk/persistence/pending.py +217 -107
  47. hpcflow/sdk/persistence/store_resource.py +58 -2
  48. hpcflow/sdk/persistence/utils.py +8 -0
  49. hpcflow/sdk/persistence/zarr.py +68 -1
  50. hpcflow/sdk/runtime.py +52 -10
  51. hpcflow/sdk/submission/__init__.py +3 -0
  52. hpcflow/sdk/submission/jobscript.py +198 -9
  53. hpcflow/sdk/submission/jobscript_info.py +13 -0
  54. hpcflow/sdk/submission/schedulers/__init__.py +60 -0
  55. hpcflow/sdk/submission/schedulers/direct.py +53 -0
  56. hpcflow/sdk/submission/schedulers/sge.py +45 -7
  57. hpcflow/sdk/submission/schedulers/slurm.py +45 -8
  58. hpcflow/sdk/submission/schedulers/utils.py +4 -0
  59. hpcflow/sdk/submission/shells/__init__.py +11 -1
  60. hpcflow/sdk/submission/shells/base.py +32 -1
  61. hpcflow/sdk/submission/shells/bash.py +36 -1
  62. hpcflow/sdk/submission/shells/os_version.py +18 -6
  63. hpcflow/sdk/submission/shells/powershell.py +22 -0
  64. hpcflow/sdk/submission/submission.py +88 -3
  65. hpcflow/sdk/typing.py +10 -1
  66. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a181.dist-info}/METADATA +3 -3
  67. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a181.dist-info}/RECORD +70 -70
  68. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a181.dist-info}/LICENSE +0 -0
  69. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a181.dist-info}/WHEEL +0 -0
  70. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a181.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,7 @@
1
+ """
2
+ Utilities for working with Zarr.
3
+ """
4
+
1
5
  from typing import Any, Dict, Union
2
6
 
3
7
  import zarr
@@ -68,6 +72,9 @@ def _zarr_encode(obj, zarr_group, path=None, encoded=None):
68
72
 
69
73
 
70
74
  def zarr_encode(data, zarr_group, is_pending_add, is_set):
75
+ """
76
+ Encode data into a zarr group.
77
+ """
71
78
  data, encoded = _zarr_encode(data, zarr_group)
72
79
  zarr_group.attrs["encoded"] = encoded
73
80
  zarr_group.attrs["data"] = data
@@ -176,6 +183,9 @@ def zarr_decode(
176
183
  path=None,
177
184
  dataset_copy=False,
178
185
  ):
186
+ """
187
+ Decode data from a zarr group.
188
+ """
179
189
  if param_data is None:
180
190
  return None
181
191
 
@@ -204,19 +214,32 @@ def zarr_decode(
204
214
 
205
215
 
206
216
  class ZarrEncodable:
217
+ """
218
+ Base class of data that can be converted to and from zarr form.
219
+ """
220
+
207
221
  _typ = None
208
222
 
209
223
  def to_dict(self):
224
+ """
225
+ Convert this object to a dict.
226
+ """
210
227
  if hasattr(self, "__dict__"):
211
228
  return dict(self.__dict__)
212
229
  elif hasattr(self, "__slots__"):
213
230
  return {k: getattr(self, k) for k in self.__slots__}
214
231
 
215
232
  def to_zarr(self, zarr_group):
233
+ """
234
+ Save this object into the given zarr group.
235
+ """
216
236
  data = self.to_dict()
217
237
  zarr_encode(data, zarr_group)
218
238
 
219
239
  @classmethod
220
240
  def from_zarr(cls, zarr_group, dataset_copy=False):
241
+ """
242
+ Read an instance of this class from the given zarr group.
243
+ """
221
244
  data = zarr_decode(zarr_group, dataset_copy=dataset_copy)
222
245
  return cls(**data)
@@ -0,0 +1,13 @@
1
+ """
2
+ YAML schemas.
3
+
4
+ Contents:
5
+
6
+ * ``config_file_schema.yaml`` – Schema for configuration selection files.
7
+ * ``config_schema.yaml`` – Schema for configuration files.
8
+ * ``environments_spec_schema.yaml`` – Schema for execution environment definition files.
9
+ * ``files_spec_schema.yaml`` – Schema for input/output specification files.
10
+ * ``parameters_spec_schema.yaml`` – Schema for parameter specification files.
11
+ * ``task_schema_spec_schema.yaml`` – Schema for task template specification files.
12
+ * ``workflow_spec_schema.yaml`` – Schema for workflow files.
13
+ """
@@ -0,0 +1,3 @@
1
+ """
2
+ Demonstration code.
3
+ """
@@ -0,0 +1,3 @@
1
+ """
2
+ Helpers for the CLI.
3
+ """
hpcflow/sdk/helper/cli.py CHANGED
@@ -1,3 +1,7 @@
1
+ """
2
+ Common Click command line options related to the helper.
3
+ """
4
+
1
5
  from datetime import timedelta
2
6
 
3
7
  import click
@@ -18,7 +22,9 @@ from .helper import (
18
22
  get_helper_PID,
19
23
  get_helper_uptime,
20
24
  )
25
+ from ..cli_common import _add_doc_from_help
21
26
 
27
+ #: Helper option: ``--timeout``
22
28
  timeout_option = click.option(
23
29
  "--timeout",
24
30
  type=click.FLOAT,
@@ -26,6 +32,7 @@ timeout_option = click.option(
26
32
  show_default=True,
27
33
  help="Helper timeout in seconds.",
28
34
  )
35
+ #: Helper option: ``--timeout-check-interval``
29
36
  timeout_check_interval_option = click.option(
30
37
  "--timeout-check-interval",
31
38
  type=click.FLOAT,
@@ -33,6 +40,7 @@ timeout_check_interval_option = click.option(
33
40
  show_default=True,
34
41
  help="Interval between testing if the timeout has been exceeded in seconds.",
35
42
  )
43
+ #: Helper option: ``--watch interval``
36
44
  watch_interval_option = click.option(
37
45
  "--watch-interval",
38
46
  type=click.FLOAT,
@@ -43,6 +51,7 @@ watch_interval_option = click.option(
43
51
  "seconds."
44
52
  ),
45
53
  )
54
+ _add_doc_from_help(timeout_option, timeout_check_interval_option, watch_interval_option)
46
55
 
47
56
 
48
57
  def get_helper_CLI(app):
@@ -1,3 +1,7 @@
1
+ """
2
+ Implementation of a helper process used to monitor jobs.
3
+ """
4
+
1
5
  from datetime import datetime, timedelta
2
6
  import logging
3
7
  from logging.handlers import RotatingFileHandler
@@ -71,6 +75,9 @@ def start_helper(
71
75
  watch_interval=DEFAULT_WATCH_INTERVAL,
72
76
  logger=None,
73
77
  ):
78
+ """
79
+ Start the helper process.
80
+ """
74
81
  PID_file = get_PID_file_path(app)
75
82
  if PID_file.is_file():
76
83
  with PID_file.open("rt") as fp:
@@ -135,11 +142,17 @@ def restart_helper(
135
142
  timeout_check_interval=DEFAULT_TIMEOUT_CHECK,
136
143
  watch_interval=DEFAULT_WATCH_INTERVAL,
137
144
  ):
145
+ """
146
+ Restart the helper process.
147
+ """
138
148
  logger = stop_helper(app, return_logger=True)
139
149
  start_helper(app, timeout, timeout_check_interval, watch_interval, logger=logger)
140
150
 
141
151
 
142
152
  def get_helper_PID(app):
153
+ """
154
+ Get the process ID of the helper process.
155
+ """
143
156
  PID_file = get_PID_file_path(app)
144
157
  if not PID_file.is_file():
145
158
  print("Helper not running!")
@@ -151,6 +164,9 @@ def get_helper_PID(app):
151
164
 
152
165
 
153
166
  def stop_helper(app, return_logger=False):
167
+ """
168
+ Stop the helper process.
169
+ """
154
170
  logger = get_helper_logger(app)
155
171
  pid_info = get_helper_PID(app)
156
172
  if pid_info:
@@ -168,6 +184,9 @@ def stop_helper(app, return_logger=False):
168
184
 
169
185
 
170
186
  def clear_helper(app):
187
+ """
188
+ Stop the helper or remove any stale information relating to it.
189
+ """
171
190
  try:
172
191
  stop_helper(app)
173
192
  except psutil.NoSuchProcess:
@@ -179,6 +198,9 @@ def clear_helper(app):
179
198
 
180
199
 
181
200
  def get_helper_uptime(app):
201
+ """
202
+ Get the amount of time that the helper has been running.
203
+ """
182
204
  pid_info = get_helper_PID(app)
183
205
  if pid_info:
184
206
  proc = psutil.Process(pid_info[0])
@@ -188,6 +210,9 @@ def get_helper_uptime(app):
188
210
 
189
211
 
190
212
  def get_helper_logger(app):
213
+ """
214
+ Get the logger for helper-related messages.
215
+ """
191
216
  log_path = get_helper_log_path(app)
192
217
  logger = logging.getLogger(__name__)
193
218
  logger.setLevel(logging.INFO)
@@ -225,6 +250,9 @@ def run_helper(
225
250
  timeout_check_interval=DEFAULT_TIMEOUT_CHECK,
226
251
  watch_interval=DEFAULT_WATCH_INTERVAL,
227
252
  ):
253
+ """
254
+ Run the helper core.
255
+ """
228
256
  # TODO: when writing to watch_workflows from a workflow, copy, modify and then rename
229
257
  # this will be atomic - so there will be only one event fired.
230
258
  # Also return a local run ID (the position in the file) to be used in jobscript naming
@@ -1,3 +1,7 @@
1
+ """
2
+ File-system watcher classes.
3
+ """
4
+
1
5
  from datetime import timedelta
2
6
  from pathlib import Path
3
7
  from watchdog.observers.polling import PollingObserver
@@ -5,6 +9,10 @@ from watchdog.events import FileSystemEventHandler, PatternMatchingEventHandler
5
9
 
6
10
 
7
11
  class MonitorController:
12
+ """
13
+ Controller for tracking watch files.
14
+ """
15
+
8
16
  def __init__(self, workflow_dirs_file_path, watch_interval, logger):
9
17
 
10
18
  if isinstance(watch_interval, timedelta):
@@ -46,6 +54,9 @@ class MonitorController:
46
54
 
47
55
  @staticmethod
48
56
  def parse_watch_workflows_file(path, logger):
57
+ """
58
+ Parse the file describing what workflows to watch.
59
+ """
49
60
  # TODO: and parse element IDs as well; and record which are set/unset.
50
61
  with Path(path).open("rt") as fp:
51
62
  lns = fp.readlines()
@@ -69,20 +80,33 @@ class MonitorController:
69
80
  return wks
70
81
 
71
82
  def on_modified(self, event):
83
+ """
84
+ Callback when files are modified.
85
+ """
72
86
  self.logger.info(f"Watch file modified: {event.src_path}")
73
87
  wks = self.parse_watch_workflows_file(event.src_path, logger=self.logger)
74
88
  self.workflow_monitor.update_workflow_paths(wks)
75
89
 
76
90
  def join(self):
91
+ """
92
+ Join the worker thread.
93
+ """
77
94
  self.observer.join()
78
95
 
79
96
  def stop(self):
97
+ """
98
+ Stop this monitor.
99
+ """
80
100
  self.observer.stop()
81
101
  self.observer.join() # wait for it to stop!
82
102
  self.workflow_monitor.stop()
83
103
 
84
104
 
85
105
  class WorkflowMonitor:
106
+ """
107
+ Workflow monitor.
108
+ """
109
+
86
110
  def __init__(self, workflow_paths, watch_interval, logger):
87
111
 
88
112
  if isinstance(watch_interval, timedelta):
@@ -106,15 +130,24 @@ class WorkflowMonitor:
106
130
  self.observer.start()
107
131
 
108
132
  def on_modified(self, event):
133
+ """
134
+ Triggered on a workflow being modified.
135
+ """
109
136
  self.logger.info(f"Workflow modified: {event.src_path}")
110
137
 
111
138
  def update_workflow_paths(self, new_paths):
139
+ """
140
+ Change the set of paths to monitored workflows.
141
+ """
112
142
  self.logger.info(f"Updating watched workflows.")
113
143
  self.stop()
114
144
  self.workflow_paths = new_paths
115
145
  self._monitor_workflow_paths()
116
146
 
117
147
  def stop(self):
148
+ """
149
+ Stop this monitor.
150
+ """
118
151
  if self.observer:
119
152
  self.observer.stop()
120
153
  self.observer.join() # wait for it to stop!
hpcflow/sdk/log.py CHANGED
@@ -1,3 +1,7 @@
1
+ """
2
+ Interface to the standard logger, and performance logging utility.
3
+ """
4
+
1
5
  from functools import wraps
2
6
  import logging
3
7
  from pathlib import Path
@@ -7,17 +11,31 @@ import statistics
7
11
 
8
12
 
9
13
  class TimeIt:
14
+ """
15
+ Method execution time instrumentation.
16
+ """
10
17
 
18
+ #: Whether the instrumentation is active.
11
19
  active = False
20
+ #: Where to log to.
12
21
  file_path = None
22
+ #: The details be tracked.
13
23
  timers = defaultdict(list)
24
+ #: Traces of the stack.
14
25
  trace = []
26
+ #: Trace indices.
15
27
  trace_idx = []
28
+ #: Preceding traces.
16
29
  trace_prev = []
30
+ #: Preceding trace indices.
17
31
  trace_idx_prev = []
18
32
 
19
33
  @classmethod
20
34
  def decorator(cls, func):
35
+ """
36
+ Decorator for a method that is to have its execution time monitored.
37
+ """
38
+
21
39
  @wraps(func)
22
40
  def wrapper(*args, **kwargs):
23
41
 
@@ -51,6 +69,9 @@ class TimeIt:
51
69
 
52
70
  @classmethod
53
71
  def summarise(cls):
72
+ """
73
+ Produce a machine-readable summary of method execution time statistics.
74
+ """
54
75
  stats = {
55
76
  k: {
56
77
  "number": len(v),
@@ -81,6 +102,10 @@ class TimeIt:
81
102
 
82
103
  @classmethod
83
104
  def summarise_string(cls):
105
+ """
106
+ Produce a human-readable summary of method execution time statistics.
107
+ """
108
+
84
109
  def _format_nodes(node, depth=0, depth_final=None):
85
110
  if depth_final is None:
86
111
  depth_final = []
@@ -126,13 +151,22 @@ class TimeIt:
126
151
 
127
152
 
128
153
  class AppLog:
154
+ """
155
+ Application log control.
156
+ """
157
+
158
+ #: Default logging level for the console.
129
159
  DEFAULT_LOG_CONSOLE_LEVEL = "WARNING"
160
+ #: Default logging level for log files.
130
161
  DEFAULT_LOG_FILE_LEVEL = "INFO"
131
162
 
132
163
  def __init__(self, app, log_console_level=None):
164
+ #: The application context.
133
165
  self.app = app
166
+ #: The base logger for the application.
134
167
  self.logger = logging.getLogger(app.package_name)
135
168
  self.logger.setLevel(logging.DEBUG)
169
+ #: The handler for directing logging messages to the console.
136
170
  self.console_handler = self._add_console_logger(
137
171
  level=log_console_level or AppLog.DEFAULT_LOG_CONSOLE_LEVEL
138
172
  )
@@ -146,10 +180,16 @@ class AppLog:
146
180
  return handler
147
181
 
148
182
  def update_console_level(self, new_level):
183
+ """
184
+ Set the logging level for console messages.
185
+ """
149
186
  if new_level:
150
187
  self.console_handler.setLevel(new_level.upper())
151
188
 
152
189
  def add_file_logger(self, path, level=None, fmt=None, max_bytes=None):
190
+ """
191
+ Add a log file.
192
+ """
153
193
  fmt = fmt or f"%(asctime)s %(levelname)s %(name)s: %(message)s"
154
194
  level = level or AppLog.DEFAULT_LOG_FILE_LEVEL
155
195
  max_bytes = max_bytes or int(10e6)
@@ -1,3 +1,7 @@
1
+ """
2
+ Workflow persistence subsystem.
3
+ """
4
+
1
5
  import copy
2
6
  from pathlib import Path
3
7
  import random
@@ -11,21 +15,27 @@ from hpcflow.sdk.persistence.base import PersistentStore
11
15
  from hpcflow.sdk.persistence.json import JSONPersistentStore
12
16
  from hpcflow.sdk.persistence.zarr import ZarrPersistentStore, ZarrZipPersistentStore
13
17
 
14
- ALL_STORE_CLS = {
18
+ _ALL_STORE_CLS = {
15
19
  "zarr": ZarrPersistentStore,
16
20
  "zip": ZarrZipPersistentStore,
17
21
  "json": JSONPersistentStore,
18
22
  # "json-single": JSONPersistentStore, # TODO
19
23
  }
24
+ #: The name of the default persistence store.
20
25
  DEFAULT_STORE_FORMAT = "zarr"
21
- ALL_STORE_FORMATS = tuple(ALL_STORE_CLS.keys())
26
+ #: The persistence formats supported.
27
+ ALL_STORE_FORMATS = tuple(_ALL_STORE_CLS.keys())
28
+ #: The persistence formats supported for creation.
22
29
  ALL_CREATE_STORE_FORMATS = tuple(
23
- k for k, v in ALL_STORE_CLS.items() if v._features.create
30
+ k for k, v in _ALL_STORE_CLS.items() if v._features.create
24
31
  )
25
32
 
26
33
 
27
34
  def store_cls_from_str(store_format: str) -> Type[PersistentStore]:
35
+ """
36
+ Get the class that implements the persistence store from its name.
37
+ """
28
38
  try:
29
- return ALL_STORE_CLS[store_format]
39
+ return _ALL_STORE_CLS[store_format]
30
40
  except KeyError:
31
41
  raise ValueError(f"Store format {store_format!r} not known.")