hpcflow-new2 0.2.0a158__py3-none-any.whl → 0.2.0a160__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/app.py +0 -3
- hpcflow/sdk/__init__.py +2 -0
- hpcflow/sdk/app.py +91 -18
- hpcflow/sdk/cli.py +18 -0
- hpcflow/sdk/cli_common.py +16 -0
- hpcflow/sdk/config/config.py +0 -4
- hpcflow/sdk/core/actions.py +20 -7
- hpcflow/sdk/core/command_files.py +4 -4
- hpcflow/sdk/core/element.py +15 -16
- hpcflow/sdk/core/rule.py +2 -0
- hpcflow/sdk/core/run_dir_files.py +63 -0
- hpcflow/sdk/core/task.py +34 -35
- hpcflow/sdk/core/utils.py +37 -15
- hpcflow/sdk/core/workflow.py +147 -49
- hpcflow/sdk/data/config_schema.yaml +0 -6
- hpcflow/sdk/demo/cli.py +12 -0
- hpcflow/sdk/log.py +2 -2
- hpcflow/sdk/persistence/base.py +142 -12
- hpcflow/sdk/persistence/json.py +84 -63
- hpcflow/sdk/persistence/pending.py +21 -7
- hpcflow/sdk/persistence/utils.py +2 -1
- hpcflow/sdk/persistence/zarr.py +143 -108
- hpcflow/sdk/runtime.py +0 -12
- hpcflow/sdk/submission/jobscript.py +25 -4
- hpcflow/sdk/submission/schedulers/sge.py +3 -0
- hpcflow/sdk/submission/schedulers/slurm.py +3 -0
- hpcflow/sdk/submission/shells/bash.py +2 -2
- hpcflow/sdk/submission/shells/powershell.py +2 -2
- hpcflow/sdk/submission/submission.py +24 -7
- hpcflow/tests/scripts/test_main_scripts.py +40 -0
- hpcflow/tests/unit/test_utils.py +28 -0
- {hpcflow_new2-0.2.0a158.dist-info → hpcflow_new2-0.2.0a160.dist-info}/METADATA +1 -2
- {hpcflow_new2-0.2.0a158.dist-info → hpcflow_new2-0.2.0a160.dist-info}/RECORD +36 -35
- {hpcflow_new2-0.2.0a158.dist-info → hpcflow_new2-0.2.0a160.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a158.dist-info → hpcflow_new2-0.2.0a160.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/persistence/base.py
CHANGED
@@ -9,10 +9,11 @@ from datetime import datetime, timezone
|
|
9
9
|
import enum
|
10
10
|
import os
|
11
11
|
from pathlib import Path
|
12
|
+
import re
|
12
13
|
import shutil
|
13
14
|
import socket
|
14
15
|
import time
|
15
|
-
from typing import Any, Dict, Iterable, List, Optional, Tuple, TypeVar, Union
|
16
|
+
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, TypeVar, Union
|
16
17
|
|
17
18
|
from hpcflow.sdk.core.utils import (
|
18
19
|
flatten,
|
@@ -107,6 +108,7 @@ class StoreTask:
|
|
107
108
|
"""
|
108
109
|
return cls(is_pending=False, **task_dat)
|
109
110
|
|
111
|
+
@TimeIt.decorator
|
110
112
|
def append_element_IDs(self: AnySTask, pend_IDs: List[int]) -> AnySTask:
|
111
113
|
"""Return a copy, with additional element IDs."""
|
112
114
|
elem_IDs = self.element_IDs[:] + pend_IDs
|
@@ -164,6 +166,7 @@ class StoreElement:
|
|
164
166
|
"iterations": iters,
|
165
167
|
}
|
166
168
|
|
169
|
+
@TimeIt.decorator
|
167
170
|
def append_iteration_IDs(self: AnySElement, pend_IDs: List[int]) -> AnySElement:
|
168
171
|
"""Return a copy, with additional iteration IDs."""
|
169
172
|
iter_IDs = self.iteration_IDs[:] + pend_IDs
|
@@ -234,6 +237,7 @@ class StoreElementIter:
|
|
234
237
|
"loop_idx": self.loop_idx,
|
235
238
|
}
|
236
239
|
|
240
|
+
@TimeIt.decorator
|
237
241
|
def append_EAR_IDs(
|
238
242
|
self: AnySElementIter, pend_IDs: Dict[int, List[int]]
|
239
243
|
) -> AnySElementIter:
|
@@ -256,6 +260,7 @@ class StoreElementIter:
|
|
256
260
|
EARs_initialised=self.EARs_initialised,
|
257
261
|
)
|
258
262
|
|
263
|
+
@TimeIt.decorator
|
259
264
|
def update_loop_idx(
|
260
265
|
self: AnySElementIter, loop_idx: Dict[str, int]
|
261
266
|
) -> AnySElementIter:
|
@@ -273,6 +278,7 @@ class StoreElementIter:
|
|
273
278
|
loop_idx=loop_idx_new,
|
274
279
|
)
|
275
280
|
|
281
|
+
@TimeIt.decorator
|
276
282
|
def set_EARs_initialised(self: AnySElementIter) -> AnySElementIter:
|
277
283
|
"""Return a copy with `EARs_initialised` set to `True`."""
|
278
284
|
return self.__class__(
|
@@ -378,6 +384,7 @@ class StoreEAR:
|
|
378
384
|
"run_hostname": self.run_hostname,
|
379
385
|
}
|
380
386
|
|
387
|
+
@TimeIt.decorator
|
381
388
|
def update(
|
382
389
|
self,
|
383
390
|
submission_idx: Optional[int] = None,
|
@@ -655,6 +662,10 @@ class PersistentStore(ABC):
|
|
655
662
|
self._resources_in_use = set()
|
656
663
|
self._in_batch_mode = False
|
657
664
|
|
665
|
+
self._use_cache = False
|
666
|
+
self._cache = None
|
667
|
+
self._reset_cache()
|
668
|
+
|
658
669
|
@property
|
659
670
|
def logger(self):
|
660
671
|
return self.app.persistence_logger
|
@@ -672,6 +683,70 @@ class PersistentStore(ABC):
|
|
672
683
|
"""Does this store support workflow submission?"""
|
673
684
|
return self.fs.__class__.__name__ == "LocalFileSystem"
|
674
685
|
|
686
|
+
@property
|
687
|
+
def use_cache(self):
|
688
|
+
return self._use_cache
|
689
|
+
|
690
|
+
@property
|
691
|
+
def task_cache(self):
|
692
|
+
"""Cache for persistent tasks."""
|
693
|
+
return self._cache["tasks"]
|
694
|
+
|
695
|
+
@property
|
696
|
+
def element_cache(self):
|
697
|
+
"""Cache for persistent elements."""
|
698
|
+
return self._cache["elements"]
|
699
|
+
|
700
|
+
@property
|
701
|
+
def element_iter_cache(self):
|
702
|
+
"""Cache for persistent element iterations."""
|
703
|
+
return self._cache["element_iters"]
|
704
|
+
|
705
|
+
@property
|
706
|
+
def EAR_cache(self):
|
707
|
+
"""Cache for persistent EARs."""
|
708
|
+
return self._cache["EARs"]
|
709
|
+
|
710
|
+
@property
|
711
|
+
def num_tasks_cache(self):
|
712
|
+
"""Cache for number of persistent tasks."""
|
713
|
+
return self._cache["num_tasks"]
|
714
|
+
|
715
|
+
@property
|
716
|
+
def param_sources_cache(self):
|
717
|
+
"""Cache for persistent parameter sources."""
|
718
|
+
return self._cache["param_sources"]
|
719
|
+
|
720
|
+
@property
|
721
|
+
def parameter_cache(self):
|
722
|
+
"""Cache for persistent parameters."""
|
723
|
+
return self._cache["parameters"]
|
724
|
+
|
725
|
+
@num_tasks_cache.setter
|
726
|
+
def num_tasks_cache(self, value):
|
727
|
+
self._cache["num_tasks"] = value
|
728
|
+
|
729
|
+
def _reset_cache(self):
|
730
|
+
self._cache = {
|
731
|
+
"tasks": {},
|
732
|
+
"elements": {},
|
733
|
+
"element_iters": {},
|
734
|
+
"EARs": {},
|
735
|
+
"param_sources": {},
|
736
|
+
"num_tasks": None,
|
737
|
+
"parameters": {},
|
738
|
+
}
|
739
|
+
|
740
|
+
@contextlib.contextmanager
|
741
|
+
def cache_ctx(self):
|
742
|
+
"""Context manager for using the persistent element/iteration/run cache."""
|
743
|
+
self._use_cache = True
|
744
|
+
try:
|
745
|
+
yield
|
746
|
+
finally:
|
747
|
+
self._use_cache = False
|
748
|
+
self._reset_cache()
|
749
|
+
|
675
750
|
@staticmethod
|
676
751
|
def prepare_test_store_from_spec(task_spec):
|
677
752
|
"""Generate a valid store from a specification in terms of nested
|
@@ -872,6 +947,7 @@ class PersistentStore(ABC):
|
|
872
947
|
if save:
|
873
948
|
self.save()
|
874
949
|
|
950
|
+
@TimeIt.decorator
|
875
951
|
def add_submission(self, sub_idx: int, sub_js: Dict, save: bool = True):
|
876
952
|
"""Add a new submission."""
|
877
953
|
self.logger.debug(f"Adding store submission.")
|
@@ -964,6 +1040,7 @@ class PersistentStore(ABC):
|
|
964
1040
|
if save:
|
965
1041
|
self.save()
|
966
1042
|
|
1043
|
+
@TimeIt.decorator
|
967
1044
|
def set_EAR_submission_index(
|
968
1045
|
self, EAR_ID: int, sub_idx: int, save: bool = True
|
969
1046
|
) -> None:
|
@@ -973,9 +1050,7 @@ class PersistentStore(ABC):
|
|
973
1050
|
|
974
1051
|
def set_EAR_start(self, EAR_ID: int, save: bool = True) -> datetime:
|
975
1052
|
dt = datetime.utcnow()
|
976
|
-
|
977
|
-
snapshot.take(".")
|
978
|
-
ss_js = snapshot.to_json_like()
|
1053
|
+
ss_js = self.app.RunDirAppFiles.take_snapshot()
|
979
1054
|
run_hostname = socket.gethostname()
|
980
1055
|
self._pending.set_EAR_starts[EAR_ID] = (dt, ss_js, run_hostname)
|
981
1056
|
if save:
|
@@ -987,9 +1062,7 @@ class PersistentStore(ABC):
|
|
987
1062
|
) -> datetime:
|
988
1063
|
# TODO: save output files
|
989
1064
|
dt = datetime.utcnow()
|
990
|
-
|
991
|
-
snapshot.take(".")
|
992
|
-
ss_js = snapshot.to_json_like()
|
1065
|
+
ss_js = self.app.RunDirAppFiles.take_snapshot()
|
993
1066
|
self._pending.set_EAR_ends[EAR_ID] = (dt, ss_js, exit_code, success)
|
994
1067
|
if save:
|
995
1068
|
self.save()
|
@@ -1245,6 +1318,7 @@ class PersistentStore(ABC):
|
|
1245
1318
|
def _get_task_id_to_idx_map(self) -> Dict[int, int]:
|
1246
1319
|
return {i.id_: i.index for i in self.get_tasks()}
|
1247
1320
|
|
1321
|
+
@TimeIt.decorator
|
1248
1322
|
def get_task(self, task_idx: int) -> AnySTask:
|
1249
1323
|
return self.get_tasks()[task_idx]
|
1250
1324
|
|
@@ -1287,10 +1361,10 @@ class PersistentStore(ABC):
|
|
1287
1361
|
|
1288
1362
|
return self._process_retrieved_tasks(tasks)
|
1289
1363
|
|
1364
|
+
@TimeIt.decorator
|
1290
1365
|
def get_tasks(self) -> List[AnySTask]:
|
1291
1366
|
"""Retrieve all tasks, including pending."""
|
1292
|
-
|
1293
|
-
tasks = self._get_persistent_tasks()
|
1367
|
+
tasks = self._get_persistent_tasks(range(self._get_num_persistent_tasks()))
|
1294
1368
|
tasks.update({k: v for k, v in self._pending.add_tasks.items()})
|
1295
1369
|
|
1296
1370
|
# order by index:
|
@@ -1326,6 +1400,7 @@ class PersistentStore(ABC):
|
|
1326
1400
|
|
1327
1401
|
return self._process_retrieved_loops(loops)
|
1328
1402
|
|
1403
|
+
@TimeIt.decorator
|
1329
1404
|
def get_submissions(self) -> Dict[int, Dict]:
|
1330
1405
|
"""Retrieve all submissions, including pending."""
|
1331
1406
|
|
@@ -1337,6 +1412,7 @@ class PersistentStore(ABC):
|
|
1337
1412
|
|
1338
1413
|
return subs
|
1339
1414
|
|
1415
|
+
@TimeIt.decorator
|
1340
1416
|
def get_submissions_by_ID(self, id_lst: Iterable[int]) -> Dict[int, Dict]:
|
1341
1417
|
# separate pending and persistent IDs:
|
1342
1418
|
id_set = set(id_lst)
|
@@ -1352,6 +1428,7 @@ class PersistentStore(ABC):
|
|
1352
1428
|
|
1353
1429
|
return subs
|
1354
1430
|
|
1431
|
+
@TimeIt.decorator
|
1355
1432
|
def get_elements(self, id_lst: Iterable[int]) -> List[AnySElement]:
|
1356
1433
|
self.logger.debug(f"PersistentStore.get_elements: id_lst={id_lst!r}")
|
1357
1434
|
|
@@ -1378,6 +1455,7 @@ class PersistentStore(ABC):
|
|
1378
1455
|
|
1379
1456
|
return elems_new
|
1380
1457
|
|
1458
|
+
@TimeIt.decorator
|
1381
1459
|
def get_element_iterations(self, id_lst: Iterable[int]) -> List[AnySElementIter]:
|
1382
1460
|
self.logger.debug(f"PersistentStore.get_element_iterations: id_lst={id_lst!r}")
|
1383
1461
|
|
@@ -1413,6 +1491,7 @@ class PersistentStore(ABC):
|
|
1413
1491
|
|
1414
1492
|
return iters_new
|
1415
1493
|
|
1494
|
+
@TimeIt.decorator
|
1416
1495
|
def get_EARs(self, id_lst: Iterable[int]) -> List[AnySEAR]:
|
1417
1496
|
self.logger.debug(f"PersistentStore.get_EARs: id_lst={id_lst!r}")
|
1418
1497
|
|
@@ -1457,10 +1536,51 @@ class PersistentStore(ABC):
|
|
1457
1536
|
|
1458
1537
|
return EARs_new
|
1459
1538
|
|
1539
|
+
@TimeIt.decorator
|
1540
|
+
def _get_cached_persistent_items(
|
1541
|
+
self, id_lst: Iterable[int], cache: Dict
|
1542
|
+
) -> Tuple[Dict[int, Any], List[int]]:
|
1543
|
+
id_lst = list(id_lst)
|
1544
|
+
if self.use_cache:
|
1545
|
+
id_set = set(id_lst)
|
1546
|
+
all_cached = set(cache.keys())
|
1547
|
+
id_cached = id_set.intersection(all_cached)
|
1548
|
+
id_non_cached = list(id_set.difference(all_cached))
|
1549
|
+
items = {k: cache[k] for k in id_cached}
|
1550
|
+
else:
|
1551
|
+
items = {}
|
1552
|
+
id_non_cached = id_lst
|
1553
|
+
return items, id_non_cached
|
1554
|
+
|
1555
|
+
def _get_cached_persistent_EARs(
|
1556
|
+
self, id_lst: Iterable[int]
|
1557
|
+
) -> Tuple[Dict[int, AnySEAR], List[int]]:
|
1558
|
+
return self._get_cached_persistent_items(id_lst, self.EAR_cache)
|
1559
|
+
|
1560
|
+
def _get_cached_persistent_element_iters(
|
1561
|
+
self, id_lst: Iterable[int]
|
1562
|
+
) -> Tuple[Dict[int, AnySEAR], List[int]]:
|
1563
|
+
return self._get_cached_persistent_items(id_lst, self.element_iter_cache)
|
1564
|
+
|
1565
|
+
def _get_cached_persistent_elements(
|
1566
|
+
self, id_lst: Iterable[int]
|
1567
|
+
) -> Tuple[Dict[int, AnySEAR], List[int]]:
|
1568
|
+
return self._get_cached_persistent_items(id_lst, self.element_cache)
|
1569
|
+
|
1570
|
+
def _get_cached_persistent_tasks(self, id_lst: Iterable[int]):
|
1571
|
+
return self._get_cached_persistent_items(id_lst, self.task_cache)
|
1572
|
+
|
1573
|
+
def _get_cached_persistent_param_sources(self, id_lst: Iterable[int]):
|
1574
|
+
return self._get_cached_persistent_items(id_lst, self.param_sources_cache)
|
1575
|
+
|
1576
|
+
def _get_cached_persistent_parameters(self, id_lst: Iterable[int]):
|
1577
|
+
return self._get_cached_persistent_items(id_lst, self.parameter_cache)
|
1578
|
+
|
1460
1579
|
def get_EAR_skipped(self, EAR_ID: int) -> bool:
|
1461
1580
|
self.logger.debug(f"PersistentStore.get_EAR_skipped: EAR_ID={EAR_ID!r}")
|
1462
1581
|
return self.get_EARs([EAR_ID])[0].skip
|
1463
1582
|
|
1583
|
+
@TimeIt.decorator
|
1464
1584
|
def get_parameters(
|
1465
1585
|
self,
|
1466
1586
|
id_lst: Iterable[int],
|
@@ -1487,6 +1607,7 @@ class PersistentStore(ABC):
|
|
1487
1607
|
|
1488
1608
|
return params
|
1489
1609
|
|
1610
|
+
@TimeIt.decorator
|
1490
1611
|
def get_parameter_set_statuses(self, id_lst: Iterable[int]) -> List[bool]:
|
1491
1612
|
# separate pending and persistent IDs:
|
1492
1613
|
id_set = set(id_lst)
|
@@ -1500,6 +1621,7 @@ class PersistentStore(ABC):
|
|
1500
1621
|
# order as requested:
|
1501
1622
|
return [set_status[id_] for id_ in id_lst]
|
1502
1623
|
|
1624
|
+
@TimeIt.decorator
|
1503
1625
|
def get_parameter_sources(self, id_lst: Iterable[int]) -> List[Dict]:
|
1504
1626
|
# separate pending and persistent IDs:
|
1505
1627
|
id_set = set(id_lst)
|
@@ -1523,15 +1645,23 @@ class PersistentStore(ABC):
|
|
1523
1645
|
|
1524
1646
|
return src_new
|
1525
1647
|
|
1526
|
-
|
1527
|
-
|
1648
|
+
@TimeIt.decorator
|
1649
|
+
def get_task_elements(
|
1650
|
+
self,
|
1651
|
+
task_id,
|
1652
|
+
idx_lst: Optional[Iterable[int]] = None,
|
1653
|
+
) -> List[Dict]:
|
1654
|
+
"""Get element data by an indices within a given task.
|
1528
1655
|
|
1529
1656
|
Element iterations and EARs belonging to the elements are included.
|
1530
1657
|
|
1531
1658
|
"""
|
1532
1659
|
|
1533
1660
|
all_elem_IDs = self.get_task(task_id).element_IDs
|
1534
|
-
|
1661
|
+
if idx_lst is None:
|
1662
|
+
req_IDs = all_elem_IDs
|
1663
|
+
else:
|
1664
|
+
req_IDs = [all_elem_IDs[i] for i in idx_lst]
|
1535
1665
|
store_elements = self.get_elements(req_IDs)
|
1536
1666
|
iter_IDs = [i.iteration_IDs for i in store_elements]
|
1537
1667
|
iter_IDs_flat, iter_IDs_lens = flatten(iter_IDs)
|
hpcflow/sdk/persistence/json.py
CHANGED
@@ -229,9 +229,10 @@ class JSONPersistentStore(PersistentStore):
|
|
229
229
|
with self.using_resource("metadata", action="update") as md:
|
230
230
|
md["runs"].extend(i.encode(self.ts_fmt) for i in EARs)
|
231
231
|
|
232
|
-
def
|
232
|
+
def _update_EAR_submission_indices(self, sub_indices: Dict[int, int]):
|
233
233
|
with self.using_resource("metadata", action="update") as md:
|
234
|
-
|
234
|
+
for EAR_ID_i, sub_idx_i in sub_indices.items():
|
235
|
+
md["runs"][EAR_ID_i]["submission_idx"] = sub_idx_i
|
235
236
|
|
236
237
|
def _update_EAR_start(self, EAR_id: int, s_time: datetime, s_snap: Dict, s_hn: str):
|
237
238
|
with self.using_resource("metadata", action="update") as md:
|
@@ -264,20 +265,6 @@ class JSONPersistentStore(PersistentStore):
|
|
264
265
|
params["data"][str(param_i.id_)] = param_i.encode()
|
265
266
|
params["sources"][str(param_i.id_)] = param_i.source
|
266
267
|
|
267
|
-
def _set_parameter_value(self, param_id: int, value: Any, is_file: bool):
|
268
|
-
"""Set an unset persistent parameter."""
|
269
|
-
|
270
|
-
# the `decode` call in `_get_persistent_parameters` should be quick:
|
271
|
-
param = self._get_persistent_parameters([param_id])[param_id]
|
272
|
-
if is_file:
|
273
|
-
param = param.set_file(value)
|
274
|
-
else:
|
275
|
-
param = param.set_data(value)
|
276
|
-
|
277
|
-
with self.using_resource("parameters", "update") as params:
|
278
|
-
# no need to update sources array:
|
279
|
-
params["data"][str(param_id)] = param.encode()
|
280
|
-
|
281
268
|
def _set_parameter_values(self, set_parameters: Dict[int, Tuple[Any, bool]]):
|
282
269
|
"""Set multiple unset persistent parameters."""
|
283
270
|
param_ids = list(set_parameters.keys())
|
@@ -310,8 +297,13 @@ class JSONPersistentStore(PersistentStore):
|
|
310
297
|
|
311
298
|
def _get_num_persistent_tasks(self) -> int:
|
312
299
|
"""Get the number of persistent tasks."""
|
313
|
-
|
314
|
-
|
300
|
+
if self.num_tasks_cache is not None:
|
301
|
+
num = self.num_tasks_cache
|
302
|
+
else:
|
303
|
+
with self.using_resource("metadata", action="read") as md:
|
304
|
+
num = len(md["tasks"])
|
305
|
+
self.num_tasks_cache = num
|
306
|
+
return num
|
315
307
|
|
316
308
|
def _get_num_persistent_loops(self) -> int:
|
317
309
|
"""Get the number of persistent loops."""
|
@@ -386,16 +378,18 @@ class JSONPersistentStore(PersistentStore):
|
|
386
378
|
with self.using_resource("metadata", "read") as md:
|
387
379
|
return md["template"]
|
388
380
|
|
389
|
-
def _get_persistent_tasks(
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
381
|
+
def _get_persistent_tasks(self, id_lst: Iterable[int]) -> Dict[int, StoreTask]:
|
382
|
+
tasks, id_lst = self._get_cached_persistent_tasks(id_lst)
|
383
|
+
if id_lst:
|
384
|
+
with self.using_resource("metadata", action="read") as md:
|
385
|
+
new_tasks = {
|
386
|
+
i["id_"]: StoreTask.decode({**i, "index": idx})
|
387
|
+
for idx, i in enumerate(md["tasks"])
|
388
|
+
if id_lst is None or i["id_"] in id_lst
|
389
|
+
}
|
390
|
+
self.task_cache.update(new_tasks)
|
391
|
+
tasks.update(new_tasks)
|
392
|
+
return tasks
|
399
393
|
|
400
394
|
def _get_persistent_loops(self, id_lst: Optional[Iterable[int]] = None):
|
401
395
|
with self.using_resource("metadata", "read") as md:
|
@@ -428,54 +422,81 @@ class JSONPersistentStore(PersistentStore):
|
|
428
422
|
return subs_dat
|
429
423
|
|
430
424
|
def _get_persistent_elements(self, id_lst: Iterable[int]) -> Dict[int, StoreElement]:
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
425
|
+
elems, id_lst = self._get_cached_persistent_elements(id_lst)
|
426
|
+
if id_lst:
|
427
|
+
# could convert `id_lst` to e.g. slices if more efficient for a given store
|
428
|
+
with self.using_resource("metadata", action="read") as md:
|
429
|
+
try:
|
430
|
+
elem_dat = {i: md["elements"][i] for i in id_lst}
|
431
|
+
except KeyError:
|
432
|
+
raise MissingStoreElementError(id_lst) from None
|
433
|
+
new_elems = {k: StoreElement.decode(v) for k, v in elem_dat.items()}
|
434
|
+
self.element_cache.update(new_elems)
|
435
|
+
elems.update(new_elems)
|
436
|
+
return elems
|
438
437
|
|
439
438
|
def _get_persistent_element_iters(
|
440
439
|
self, id_lst: Iterable[int]
|
441
440
|
) -> Dict[int, StoreElementIter]:
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
441
|
+
iters, id_lst = self._get_cached_persistent_element_iters(id_lst)
|
442
|
+
if id_lst:
|
443
|
+
with self.using_resource("metadata", action="read") as md:
|
444
|
+
try:
|
445
|
+
iter_dat = {i: md["iters"][i] for i in id_lst}
|
446
|
+
except KeyError:
|
447
|
+
raise MissingStoreElementIterationError(id_lst) from None
|
448
|
+
new_iters = {k: StoreElementIter.decode(v) for k, v in iter_dat.items()}
|
449
|
+
self.element_iter_cache.update(new_iters)
|
450
|
+
iters.update(new_iters)
|
451
|
+
return iters
|
448
452
|
|
449
453
|
def _get_persistent_EARs(self, id_lst: Iterable[int]) -> Dict[int, StoreEAR]:
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
454
|
+
runs, id_lst = self._get_cached_persistent_EARs(id_lst)
|
455
|
+
if id_lst:
|
456
|
+
with self.using_resource("metadata", action="read") as md:
|
457
|
+
try:
|
458
|
+
EAR_dat = {i: md["runs"][i] for i in id_lst}
|
459
|
+
except KeyError:
|
460
|
+
raise MissingStoreEARError(id_lst) from None
|
461
|
+
new_runs = {
|
462
|
+
k: StoreEAR.decode(v, self.ts_fmt) for k, v in EAR_dat.items()
|
463
|
+
}
|
464
|
+
self.EAR_cache.update(new_runs)
|
465
|
+
runs.update(new_runs)
|
466
|
+
return runs
|
456
467
|
|
457
468
|
def _get_persistent_parameters(
|
458
469
|
self,
|
459
470
|
id_lst: Iterable[int],
|
460
471
|
) -> Dict[int, StoreParameter]:
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
+
params, id_lst = self._get_cached_persistent_parameters(id_lst)
|
473
|
+
if id_lst:
|
474
|
+
with self.using_resource("parameters", "read") as params:
|
475
|
+
try:
|
476
|
+
param_dat = {i: params["data"][str(i)] for i in id_lst}
|
477
|
+
src_dat = {i: params["sources"][str(i)] for i in id_lst}
|
478
|
+
except KeyError:
|
479
|
+
raise MissingParameterData(id_lst) from None
|
480
|
+
|
481
|
+
new_params = {
|
482
|
+
k: StoreParameter.decode(id_=k, data=v, source=src_dat[k])
|
483
|
+
for k, v in param_dat.items()
|
484
|
+
}
|
485
|
+
self.parameter_cache.update(new_params)
|
486
|
+
params.update(new_params)
|
487
|
+
return params
|
472
488
|
|
473
489
|
def _get_persistent_param_sources(self, id_lst: Iterable[int]) -> Dict[int, Dict]:
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
490
|
+
sources, id_lst = self._get_cached_persistent_param_sources(id_lst)
|
491
|
+
if id_lst:
|
492
|
+
with self.using_resource("parameters", "read") as params:
|
493
|
+
try:
|
494
|
+
new_sources = {i: params["sources"][str(i)] for i in id_lst}
|
495
|
+
except KeyError:
|
496
|
+
raise MissingParameterData(id_lst) from None
|
497
|
+
self.param_sources_cache.update(new_sources)
|
498
|
+
sources.update(new_sources)
|
499
|
+
return sources
|
479
500
|
|
480
501
|
def _get_persistent_parameter_set_status(
|
481
502
|
self, id_lst: Iterable[int]
|
@@ -142,6 +142,7 @@ class PendingChanges:
|
|
142
142
|
task_ids = list(self.add_tasks.keys())
|
143
143
|
self.logger.debug(f"commit: adding pending tasks with IDs: {task_ids!r}")
|
144
144
|
self.store._append_tasks(tasks)
|
145
|
+
self.store.num_tasks_cache = None # invalidate cache
|
145
146
|
# pending element IDs that belong to pending tasks are now committed:
|
146
147
|
self.add_elem_IDs = {
|
147
148
|
k: v for k, v in self.add_elem_IDs.items() if k not in task_ids
|
@@ -187,6 +188,7 @@ class PendingChanges:
|
|
187
188
|
f"commit: adding pending element IDs to task {task_ID!r}: {elem_IDs!r}."
|
188
189
|
)
|
189
190
|
self.store._append_task_element_IDs(task_ID, elem_IDs)
|
191
|
+
self.store.task_cache.pop(task_ID, None) # invalidate cache
|
190
192
|
self.clear_add_elem_IDs()
|
191
193
|
|
192
194
|
@TimeIt.decorator
|
@@ -219,6 +221,7 @@ class PendingChanges:
|
|
219
221
|
f"{iter_IDs!r}."
|
220
222
|
)
|
221
223
|
self.store._append_elem_iter_IDs(elem_ID, iter_IDs)
|
224
|
+
self.store.element_cache.pop(elem_ID, None) # invalidate cache
|
222
225
|
self.clear_add_elem_iter_IDs()
|
223
226
|
|
224
227
|
@TimeIt.decorator
|
@@ -250,6 +253,7 @@ class PendingChanges:
|
|
250
253
|
)
|
251
254
|
for act_idx, EAR_IDs in act_EAR_IDs.items():
|
252
255
|
self.store._append_elem_iter_EAR_IDs(iter_ID, act_idx, EAR_IDs)
|
256
|
+
self.store.element_iter_cache.pop(iter_ID, None) # invalidate cache
|
253
257
|
self.clear_add_elem_iter_EAR_IDs()
|
254
258
|
|
255
259
|
@TimeIt.decorator
|
@@ -286,19 +290,21 @@ class PendingChanges:
|
|
286
290
|
)
|
287
291
|
# TODO: could be batched up?
|
288
292
|
for i in iter_ids:
|
289
|
-
self.
|
293
|
+
self.store._update_elem_iter_EARs_initialised(i)
|
294
|
+
self.store.element_iter_cache.pop(i, None) # invalidate cache
|
290
295
|
self.clear_set_EARs_initialised()
|
291
296
|
|
292
297
|
@TimeIt.decorator
|
293
298
|
def commit_EAR_submission_indices(self) -> None:
|
294
|
-
|
295
|
-
for EAR_id, sub_idx in self.set_EAR_submission_indices.items():
|
299
|
+
if self.set_EAR_submission_indices:
|
296
300
|
self.logger.debug(
|
297
|
-
f"commit:
|
298
|
-
f"{
|
301
|
+
f"commit: updating submission indices: "
|
302
|
+
f"{self.set_EAR_submission_indices!r}."
|
299
303
|
)
|
300
|
-
self.store.
|
301
|
-
|
304
|
+
self.store._update_EAR_submission_indices(self.set_EAR_submission_indices)
|
305
|
+
for EAR_ID_i in self.set_EAR_submission_indices.keys():
|
306
|
+
self.store.EAR_cache.pop(EAR_ID_i, None) # invalidate cache
|
307
|
+
self.clear_set_EAR_submission_indices()
|
302
308
|
|
303
309
|
@TimeIt.decorator
|
304
310
|
def commit_EAR_starts(self) -> None:
|
@@ -309,6 +315,7 @@ class PendingChanges:
|
|
309
315
|
f"({hostname!r}), and directory snapshot to EAR ID {EAR_id!r}."
|
310
316
|
)
|
311
317
|
self.store._update_EAR_start(EAR_id, time, snap, hostname)
|
318
|
+
self.store.EAR_cache.pop(EAR_id, None) # invalidate cache
|
312
319
|
self.clear_set_EAR_starts()
|
313
320
|
|
314
321
|
@TimeIt.decorator
|
@@ -320,6 +327,7 @@ class PendingChanges:
|
|
320
327
|
f"exit code ({ext!r}), and success status {suc!r} to EAR ID {EAR_id!r}."
|
321
328
|
)
|
322
329
|
self.store._update_EAR_end(EAR_id, time, snap, ext, suc)
|
330
|
+
self.store.EAR_cache.pop(EAR_id, None) # invalidate cache
|
323
331
|
self.clear_set_EAR_ends()
|
324
332
|
|
325
333
|
@TimeIt.decorator
|
@@ -328,6 +336,7 @@ class PendingChanges:
|
|
328
336
|
for EAR_id in self.set_EAR_skips:
|
329
337
|
self.logger.debug(f"commit: setting EAR ID {EAR_id!r} as skipped.")
|
330
338
|
self.store._update_EAR_skip(EAR_id)
|
339
|
+
self.store.EAR_cache.pop(EAR_id, None) # invalidate cache
|
331
340
|
self.clear_set_EAR_skips()
|
332
341
|
|
333
342
|
@TimeIt.decorator
|
@@ -353,6 +362,8 @@ class PendingChanges:
|
|
353
362
|
param_ids = list(self.set_parameters.keys())
|
354
363
|
self.logger.debug(f"commit: setting values of parameter IDs {param_ids!r}.")
|
355
364
|
self.store._set_parameter_values(self.set_parameters)
|
365
|
+
for id_i in param_ids:
|
366
|
+
self.store.parameter_cache.pop(id_i, None)
|
356
367
|
|
357
368
|
self.clear_set_parameters()
|
358
369
|
|
@@ -378,6 +389,8 @@ class PendingChanges:
|
|
378
389
|
param_ids = list(self.update_param_sources.keys())
|
379
390
|
self.logger.debug(f"commit: updating sources of parameter IDs {param_ids!r}.")
|
380
391
|
self.store._update_parameter_sources(self.update_param_sources)
|
392
|
+
for id_i in param_ids:
|
393
|
+
self.store.param_sources_cache.pop(id_i, None) # invalidate cache
|
381
394
|
self.clear_update_param_sources()
|
382
395
|
|
383
396
|
@TimeIt.decorator
|
@@ -389,6 +402,7 @@ class PendingChanges:
|
|
389
402
|
f"{loop_idx!r}."
|
390
403
|
)
|
391
404
|
self.store._update_loop_index(iter_ID, loop_idx)
|
405
|
+
self.store.element_iter_cache.pop(iter_ID, None) # invalidate cache
|
392
406
|
self.clear_update_loop_indices()
|
393
407
|
|
394
408
|
@TimeIt.decorator
|
hpcflow/sdk/persistence/utils.py
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
from getpass import getpass
|
2
|
-
from paramiko.ssh_exception import SSHException
|
3
2
|
|
4
3
|
from hpcflow.sdk.core.errors import WorkflowNotFoundError
|
5
4
|
|
6
5
|
|
7
6
|
def ask_pw_on_auth_exc(f, *args, add_pw_to=None, **kwargs):
|
7
|
+
from paramiko.ssh_exception import SSHException
|
8
|
+
|
8
9
|
try:
|
9
10
|
out = f(*args, **kwargs)
|
10
11
|
pw = None
|