hpcflow-new2 0.2.0a179__py3-none-any.whl → 0.2.0a180__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.
- hpcflow/_version.py +1 -1
- hpcflow/data/demo_data_manifest/__init__.py +3 -0
- hpcflow/sdk/__init__.py +4 -1
- hpcflow/sdk/app.py +160 -15
- hpcflow/sdk/cli.py +14 -0
- hpcflow/sdk/cli_common.py +83 -0
- hpcflow/sdk/config/__init__.py +4 -0
- hpcflow/sdk/config/callbacks.py +25 -2
- hpcflow/sdk/config/cli.py +4 -1
- hpcflow/sdk/config/config.py +188 -14
- hpcflow/sdk/config/config_file.py +91 -3
- hpcflow/sdk/config/errors.py +33 -0
- hpcflow/sdk/core/__init__.py +2 -0
- hpcflow/sdk/core/actions.py +492 -35
- hpcflow/sdk/core/cache.py +22 -0
- hpcflow/sdk/core/command_files.py +221 -5
- hpcflow/sdk/core/commands.py +57 -0
- hpcflow/sdk/core/element.py +407 -8
- hpcflow/sdk/core/environment.py +92 -0
- hpcflow/sdk/core/errors.py +245 -61
- hpcflow/sdk/core/json_like.py +72 -14
- hpcflow/sdk/core/loop.py +122 -21
- hpcflow/sdk/core/loop_cache.py +34 -9
- hpcflow/sdk/core/object_list.py +172 -26
- hpcflow/sdk/core/parallel.py +14 -0
- hpcflow/sdk/core/parameters.py +478 -25
- hpcflow/sdk/core/rule.py +31 -1
- hpcflow/sdk/core/run_dir_files.py +12 -2
- hpcflow/sdk/core/task.py +407 -80
- hpcflow/sdk/core/task_schema.py +70 -9
- hpcflow/sdk/core/test_utils.py +35 -0
- hpcflow/sdk/core/utils.py +101 -4
- hpcflow/sdk/core/validation.py +13 -1
- hpcflow/sdk/core/workflow.py +316 -96
- hpcflow/sdk/core/zarr_io.py +23 -0
- hpcflow/sdk/data/__init__.py +13 -0
- hpcflow/sdk/demo/__init__.py +3 -0
- hpcflow/sdk/helper/__init__.py +3 -0
- hpcflow/sdk/helper/cli.py +9 -0
- hpcflow/sdk/helper/helper.py +28 -0
- hpcflow/sdk/helper/watcher.py +33 -0
- hpcflow/sdk/log.py +40 -0
- hpcflow/sdk/persistence/__init__.py +14 -4
- hpcflow/sdk/persistence/base.py +289 -23
- hpcflow/sdk/persistence/json.py +29 -0
- hpcflow/sdk/persistence/pending.py +217 -107
- hpcflow/sdk/persistence/store_resource.py +58 -2
- hpcflow/sdk/persistence/utils.py +8 -0
- hpcflow/sdk/persistence/zarr.py +68 -1
- hpcflow/sdk/runtime.py +52 -10
- hpcflow/sdk/submission/__init__.py +3 -0
- hpcflow/sdk/submission/jobscript.py +198 -9
- hpcflow/sdk/submission/jobscript_info.py +13 -0
- hpcflow/sdk/submission/schedulers/__init__.py +60 -0
- hpcflow/sdk/submission/schedulers/direct.py +53 -0
- hpcflow/sdk/submission/schedulers/sge.py +45 -7
- hpcflow/sdk/submission/schedulers/slurm.py +45 -8
- hpcflow/sdk/submission/schedulers/utils.py +4 -0
- hpcflow/sdk/submission/shells/__init__.py +11 -1
- hpcflow/sdk/submission/shells/base.py +32 -1
- hpcflow/sdk/submission/shells/bash.py +36 -1
- hpcflow/sdk/submission/shells/os_version.py +18 -6
- hpcflow/sdk/submission/shells/powershell.py +22 -0
- hpcflow/sdk/submission/submission.py +88 -3
- hpcflow/sdk/typing.py +10 -1
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/METADATA +1 -1
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/RECORD +70 -70
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/LICENSE +0 -0
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/core/zarr_io.py
CHANGED
@@ -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)
|
hpcflow/sdk/data/__init__.py
CHANGED
@@ -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
|
+
"""
|
hpcflow/sdk/demo/__init__.py
CHANGED
hpcflow/sdk/helper/__init__.py
CHANGED
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):
|
hpcflow/sdk/helper/helper.py
CHANGED
@@ -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
|
hpcflow/sdk/helper/watcher.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
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
|
39
|
+
return _ALL_STORE_CLS[store_format]
|
30
40
|
except KeyError:
|
31
41
|
raise ValueError(f"Store format {store_format!r} not known.")
|