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.
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.0a180.dist-info}/METADATA +1 -1
  67. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/RECORD +70 -70
  68. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/LICENSE +0 -0
  69. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/WHEEL +0 -0
  70. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,7 @@
1
+ """
2
+ Models of data stores as resources.
3
+ """
4
+
1
5
  from abc import ABC, abstractmethod
2
6
  import copy
3
7
  import json
@@ -13,6 +17,12 @@ class StoreResource(ABC):
13
17
  A `PersistentStore` maps workflow data across zero or more store resources. Updates to
14
18
  persistent workflow data that live in the same store resource are performed together.
15
19
 
20
+ Parameters
21
+ ----------
22
+ app: App
23
+ The main application context.
24
+ name:
25
+ The store name.
16
26
  """
17
27
 
18
28
  def __init__(self, app, name: str) -> None:
@@ -26,6 +36,9 @@ class StoreResource(ABC):
26
36
 
27
37
  @property
28
38
  def logger(self):
39
+ """
40
+ The logger.
41
+ """
29
42
  return self.app.persistence_logger
30
43
 
31
44
  @abstractmethod
@@ -37,6 +50,14 @@ class StoreResource(ABC):
37
50
  pass
38
51
 
39
52
  def open(self, action):
53
+ """
54
+ Open the store.
55
+
56
+ Parameters
57
+ ----------
58
+ action: str
59
+ What we are opening the store for; typically either ``read`` or ``update``.
60
+ """
40
61
  if action == "read":
41
62
  # reuse "update" data if set, rather than re-loading from disk -- but copy,
42
63
  # so changes made in the "read" scope do not update!
@@ -64,6 +85,15 @@ class StoreResource(ABC):
64
85
  pass
65
86
 
66
87
  def close(self, action):
88
+ """
89
+ Close the store for a particular action.
90
+
91
+ Parameters
92
+ ----------
93
+ action: str
94
+ What we are closing the store for.
95
+ Should match a previous call to :py:meth:`close`.
96
+ """
67
97
  if action == "read":
68
98
  self.logger.debug(f"{self!r}: closing read.")
69
99
  elif action == "update":
@@ -88,7 +118,22 @@ class StoreResource(ABC):
88
118
 
89
119
 
90
120
  class JSONFileStoreResource(StoreResource):
91
- """For caching reads and writes to a JSON file."""
121
+ """
122
+ For caching reads and writes to a JSON file.
123
+
124
+ Parameters
125
+ ----------
126
+ app: App
127
+ The main application context.
128
+ name:
129
+ The store name.
130
+ filename:
131
+ The name of the JSON file.
132
+ path:
133
+ The path to the directory containing the JSON file.
134
+ fs:
135
+ The filesystem that the JSON file resides within.
136
+ """
92
137
 
93
138
  def __init__(self, app, name: str, filename: str, path: Union[str, Path], fs):
94
139
  self.filename = filename
@@ -114,7 +159,18 @@ class JSONFileStoreResource(StoreResource):
114
159
 
115
160
 
116
161
  class ZarrAttrsStoreResource(StoreResource):
117
- """For caching reads and writes to Zarr attributes on groups and arrays."""
162
+ """
163
+ For caching reads and writes to Zarr attributes on groups and arrays.
164
+
165
+ Parameters
166
+ ----------
167
+ app: App
168
+ The main application context.
169
+ name:
170
+ The store name.
171
+ open_call:
172
+ How to actually perform an open on the underlying resource.
173
+ """
118
174
 
119
175
  def __init__(self, app, name: str, open_call: Callable):
120
176
  self.open_call = open_call
@@ -1,9 +1,17 @@
1
+ """
2
+ Miscellaneous persistence-related helpers.
3
+ """
4
+
1
5
  from getpass import getpass
2
6
 
3
7
  from hpcflow.sdk.core.errors import WorkflowNotFoundError
4
8
 
5
9
 
6
10
  def ask_pw_on_auth_exc(f, *args, add_pw_to=None, **kwargs):
11
+ """
12
+ Run the given function on the given arguments and add a password if the function
13
+ fails with an SSHException.
14
+ """
7
15
  from paramiko.ssh_exception import SSHException
8
16
 
9
17
  try:
@@ -1,3 +1,7 @@
1
+ """
2
+ Persistence model based on writing Zarr arrays.
3
+ """
4
+
1
5
  from __future__ import annotations
2
6
 
3
7
  import copy
@@ -132,6 +136,10 @@ def append_items_to_ragged_array(arr, items):
132
136
 
133
137
  @dataclass
134
138
  class ZarrStoreTask(StoreTask):
139
+ """
140
+ Represents a task in a Zarr persistent store.
141
+ """
142
+
135
143
  def encode(self) -> Tuple[int, np.ndarray, Dict]:
136
144
  """Prepare store task data for the persistent store."""
137
145
  wk_task = {"id_": self.id_, "element_IDs": np.array(self.element_IDs)}
@@ -147,6 +155,10 @@ class ZarrStoreTask(StoreTask):
147
155
 
148
156
  @dataclass
149
157
  class ZarrStoreElement(StoreElement):
158
+ """
159
+ Represents an element in a Zarr persistent store.
160
+ """
161
+
150
162
  def encode(self, attrs: Dict) -> List:
151
163
  """Prepare store elements data for the persistent store.
152
164
 
@@ -180,6 +192,10 @@ class ZarrStoreElement(StoreElement):
180
192
 
181
193
  @dataclass
182
194
  class ZarrStoreElementIter(StoreElementIter):
195
+ """
196
+ Represents an element iteration in a Zarr persistent store.
197
+ """
198
+
183
199
  def encode(self, attrs: Dict) -> List:
184
200
  """Prepare store element iteration data for the persistent store.
185
201
 
@@ -216,6 +232,10 @@ class ZarrStoreElementIter(StoreElementIter):
216
232
 
217
233
  @dataclass
218
234
  class ZarrStoreEAR(StoreEAR):
235
+ """
236
+ Represents an element action run in a Zarr persistent store.
237
+ """
238
+
219
239
  def encode(self, attrs: Dict, ts_fmt: str) -> Tuple[List, Tuple[np.datetime64]]:
220
240
  """Prepare store EAR data for the persistent store.
221
241
 
@@ -268,6 +288,10 @@ class ZarrStoreEAR(StoreEAR):
268
288
 
269
289
  @dataclass
270
290
  class ZarrStoreParameter(StoreParameter):
291
+ """
292
+ Represents a parameter in a Zarr persistent store.
293
+ """
294
+
271
295
  _encoders = { # keys are types
272
296
  np.ndarray: _encode_numpy_array,
273
297
  np.ma.core.MaskedArray: _encode_masked_array,
@@ -301,6 +325,10 @@ class ZarrStoreParameter(StoreParameter):
301
325
 
302
326
 
303
327
  class ZarrPersistentStore(PersistentStore):
328
+ """
329
+ A persistent store implemented using Zarr.
330
+ """
331
+
304
332
  _name = "zarr"
305
333
  _features = PersistentStoreFeatures(
306
334
  create=True,
@@ -346,6 +374,9 @@ class ZarrPersistentStore(PersistentStore):
346
374
  yield attrs
347
375
 
348
376
  def remove_replaced_dir(self) -> None:
377
+ """
378
+ Remove the directory containing replaced workflow details.
379
+ """
349
380
  with self.using_resource("attrs", "update") as md:
350
381
  if "replaced_workflow" in md:
351
382
  self.logger.debug("removing temporarily renamed pre-existing workflow.")
@@ -353,6 +384,9 @@ class ZarrPersistentStore(PersistentStore):
353
384
  md["replaced_workflow"] = None
354
385
 
355
386
  def reinstate_replaced_dir(self) -> None:
387
+ """
388
+ Reinstate the directory containing replaced workflow details.
389
+ """
356
390
  with self.using_resource("attrs", "read") as md:
357
391
  if "replaced_workflow" in md:
358
392
  self.logger.debug(
@@ -380,6 +414,9 @@ class ZarrPersistentStore(PersistentStore):
380
414
  compressor: Optional[Union[str, None]] = "blosc",
381
415
  compressor_kwargs: Optional[Dict[str, Any]] = None,
382
416
  ) -> None:
417
+ """
418
+ Write an empty persistent workflow.
419
+ """
383
420
  attrs = {
384
421
  "name": name,
385
422
  "ts_fmt": ts_fmt,
@@ -796,6 +833,9 @@ class ZarrPersistentStore(PersistentStore):
796
833
 
797
834
  @property
798
835
  def zarr_store(self) -> zarr.storage.Store:
836
+ """
837
+ The underlying store object.
838
+ """
799
839
  if self._zarr_store is None:
800
840
  self._zarr_store = self._get_zarr_store(self.path, self.fs)
801
841
  return self._zarr_store
@@ -1139,18 +1179,30 @@ class ZarrPersistentStore(PersistentStore):
1139
1179
  return list(range(len(base_arr)))
1140
1180
 
1141
1181
  def get_ts_fmt(self):
1182
+ """
1183
+ Get the format for timestamps.
1184
+ """
1142
1185
  with self.using_resource("attrs", action="read") as attrs:
1143
1186
  return attrs["ts_fmt"]
1144
1187
 
1145
1188
  def get_ts_name_fmt(self):
1189
+ """
1190
+ Get the format for timestamps to use in names.
1191
+ """
1146
1192
  with self.using_resource("attrs", action="read") as attrs:
1147
1193
  return attrs["ts_name_fmt"]
1148
1194
 
1149
1195
  def get_creation_info(self):
1196
+ """
1197
+ Get information about the creation of the workflow.
1198
+ """
1150
1199
  with self.using_resource("attrs", action="read") as attrs:
1151
1200
  return copy.deepcopy(attrs["creation_info"])
1152
1201
 
1153
1202
  def get_name(self):
1203
+ """
1204
+ Get the name of the workflow.
1205
+ """
1154
1206
  with self.using_resource("attrs", action="read") as attrs:
1155
1207
  return attrs["name"]
1156
1208
 
@@ -1163,6 +1215,8 @@ class ZarrPersistentStore(PersistentStore):
1163
1215
  include_rechunk_backups=False,
1164
1216
  ):
1165
1217
  """
1218
+ Convert the persistent store to zipped form.
1219
+
1166
1220
  Parameters
1167
1221
  ----------
1168
1222
  path:
@@ -1299,6 +1353,9 @@ class ZarrPersistentStore(PersistentStore):
1299
1353
  backup: Optional[bool] = True,
1300
1354
  status: Optional[bool] = True,
1301
1355
  ):
1356
+ """
1357
+ Rechunk the parameter data to be stored more efficiently.
1358
+ """
1302
1359
  arr = self._get_parameter_base_array()
1303
1360
  return self._rechunk_arr(arr, chunk_size, backup, status)
1304
1361
 
@@ -1308,13 +1365,21 @@ class ZarrPersistentStore(PersistentStore):
1308
1365
  backup: Optional[bool] = True,
1309
1366
  status: Optional[bool] = True,
1310
1367
  ):
1368
+ """
1369
+ Rechunk the run data to be stored more efficiently.
1370
+ """
1311
1371
  arr = self._get_EARs_arr()
1312
1372
  return self._rechunk_arr(arr, chunk_size, backup, status)
1313
1373
 
1314
1374
 
1315
1375
  class ZarrZipPersistentStore(ZarrPersistentStore):
1316
1376
  """A store designed mainly as an archive format that can be uploaded to data
1317
- repositories such as Zenodo."""
1377
+ repositories such as Zenodo.
1378
+
1379
+ Note
1380
+ ----
1381
+ Archive format persistent stores cannot be updated without being unzipped first.
1382
+ """
1318
1383
 
1319
1384
  _name = "zip"
1320
1385
  _features = PersistentStoreFeatures(
@@ -1333,6 +1398,8 @@ class ZarrZipPersistentStore(ZarrPersistentStore):
1333
1398
 
1334
1399
  def unzip(self, path=".", log=None):
1335
1400
  """
1401
+ Expand the persistent store.
1402
+
1336
1403
  Parameters
1337
1404
  ----------
1338
1405
  path:
hpcflow/sdk/runtime.py CHANGED
@@ -1,3 +1,7 @@
1
+ """
2
+ Information about the Python runtime.
3
+ """
4
+
1
5
  from importlib import import_module
2
6
  import logging
3
7
  import os
@@ -15,17 +19,16 @@ class RunTimeInfo:
15
19
  """Get useful run-time information, including the executable name used to
16
20
  invoke the CLI, in the case a PyInstaller-built executable was used.
17
21
 
18
- Attributes
22
+ Parameters
19
23
  ----------
20
- sys_prefix : str
21
- From `sys.prefix`. If running in a virtual environment, this will point to the
22
- environment directory. If not running in a virtual environment, this will point to
23
- the Python installation root.
24
- sys_base_prefix : str
25
- From `sys.base_prefix`. This will be equal to `sys_prefix` (`sys.prefix`) if not
26
- running within a virtual environment. However, if running within a virtual
27
- environment, this will be the Python installation directory, and `sys_prefix` will
28
- be equal to the virtual environment directory.
24
+ name:
25
+ Application name.
26
+ package_name:
27
+ Application package name.
28
+ version:
29
+ Application version.
30
+ logger:
31
+ Where to write logging versions.
29
32
  """
30
33
 
31
34
  def __init__(self, name, package_name, version, logger):
@@ -34,22 +37,35 @@ class RunTimeInfo:
34
37
  sys._MEIPASS if is_frozen else os.path.dirname(os.path.abspath(__file__))
35
38
  )
36
39
 
40
+ #: Application name.
37
41
  self.name = name.split(".")[0] # if name is given as __name__ # TODO: what?
42
+ #: Application package name.
38
43
  self.package_name = package_name
44
+ #: Application version.
39
45
  self.version = version
46
+ #: Whether this is a frozen application.
40
47
  self.is_frozen = is_frozen
48
+ #: Working directory.
41
49
  self.working_dir = os.getcwd()
50
+ #: Where to write log messages.
42
51
  self.logger = logger
52
+ #: Host that this is running on.
43
53
  self.hostname = socket.gethostname()
44
54
 
55
+ #: Whether this application is inside iPython.
45
56
  self.in_ipython = False
57
+ #: Whether this application is being used interactively.
46
58
  self.is_interactive = False
59
+ #: Whether this application is being used in test mode.
47
60
  self.in_pytest = False # set in `conftest.py`
61
+ #: Whether this application is being run from the CLI.
48
62
  self.from_CLI = False # set in CLI
49
63
 
50
64
  if self.is_frozen:
65
+ #: The bundle directory, if frozen.
51
66
  self.bundle_dir = Path(bundle_dir)
52
67
  else:
68
+ #: The path to Python itself.
53
69
  self.python_executable_path = Path(sys.executable)
54
70
 
55
71
  try:
@@ -61,16 +77,30 @@ class RunTimeInfo:
61
77
  if hasattr(sys, "ps1"):
62
78
  self.is_interactive = True
63
79
 
80
+ #: The Python version.
64
81
  self.python_version = platform.python_version()
82
+ #: Whether the application is in a virtual environment.
65
83
  self.is_venv = hasattr(sys, "real_prefix") or sys.base_prefix != sys.prefix
84
+ #: Whether the application is in a Conda virtual environment.
66
85
  self.is_conda_venv = "CONDA_PREFIX" in os.environ
67
86
 
87
+ #: From `sys.prefix`. If running in a virtual environment, this will point to the
88
+ #: environment directory. If not running in a virtual environment, this will
89
+ #: point to the Python installation root.
68
90
  self.sys_prefix = getattr(sys, "prefix", None)
91
+ #: From `sys.base_prefix`. This will be equal to `sys_prefix` (`sys.prefix`) if
92
+ #: not running within a virtual environment. However, if running within a virtual
93
+ #: environment, this will be the Python installation directory, and `sys_prefix`
94
+ #: will be equal to the virtual environment directory.
69
95
  self.sys_base_prefix = getattr(sys, "base_prefix", None)
96
+ #: The old base prefix, from `sys.real_prefix`. Compatibility version of
97
+ #: :py:attr:`sys_base_prefix`.
70
98
  self.sys_real_prefix = getattr(sys, "real_prefix", None)
99
+ #: The Conda prefix, if defined.
71
100
  self.conda_prefix = os.environ.get("CONDA_PREFIX")
72
101
 
73
102
  try:
103
+ #: The virtual environment path.
74
104
  self.venv_path = self._set_venv_path()
75
105
  except ValueError:
76
106
  self.venv_path = None
@@ -97,6 +127,9 @@ class RunTimeInfo:
97
127
  # warnings.warn(msg)
98
128
 
99
129
  def to_dict(self):
130
+ """
131
+ Serialize this class as a dictionary.
132
+ """
100
133
  out = {
101
134
  "name": self.name,
102
135
  "package_name": self.package_name,
@@ -157,12 +190,21 @@ class RunTimeInfo:
157
190
  return out
158
191
 
159
192
  def get_activate_env_command(self):
193
+ """
194
+ Get the command to activate the virtual environment.
195
+ """
160
196
  pass
161
197
 
162
198
  def get_deactivate_env_command(self):
199
+ """
200
+ Get the command to deactivate the virtual environment.
201
+ """
163
202
  pass
164
203
 
165
204
  def show(self):
205
+ """
206
+ Display the information known by this class as a human-readable table.
207
+ """
166
208
  tab = Table(show_header=False, box=None)
167
209
  tab.add_column()
168
210
  tab.add_column()
@@ -0,0 +1,3 @@
1
+ """
2
+ Subsystem for submitting work to schedulers for enactment.
3
+ """