hpcflow-new2 0.2.0a50__py3-none-any.whl → 0.2.0a52__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/sdk/__init__.py +1 -1
- hpcflow/sdk/api.py +1 -1
- hpcflow/sdk/app.py +20 -11
- hpcflow/sdk/cli.py +34 -59
- hpcflow/sdk/core/__init__.py +13 -1
- hpcflow/sdk/core/actions.py +235 -126
- hpcflow/sdk/core/command_files.py +32 -24
- hpcflow/sdk/core/element.py +110 -114
- hpcflow/sdk/core/errors.py +57 -0
- hpcflow/sdk/core/loop.py +18 -34
- hpcflow/sdk/core/parameters.py +5 -3
- hpcflow/sdk/core/task.py +135 -131
- hpcflow/sdk/core/task_schema.py +11 -4
- hpcflow/sdk/core/utils.py +110 -2
- hpcflow/sdk/core/workflow.py +964 -676
- hpcflow/sdk/data/template_components/environments.yaml +0 -44
- hpcflow/sdk/data/template_components/task_schemas.yaml +52 -10
- hpcflow/sdk/persistence/__init__.py +21 -33
- hpcflow/sdk/persistence/base.py +1340 -458
- hpcflow/sdk/persistence/json.py +424 -546
- hpcflow/sdk/persistence/pending.py +563 -0
- hpcflow/sdk/persistence/store_resource.py +131 -0
- hpcflow/sdk/persistence/utils.py +57 -0
- hpcflow/sdk/persistence/zarr.py +852 -841
- hpcflow/sdk/submission/jobscript.py +133 -112
- hpcflow/sdk/submission/shells/bash.py +62 -16
- hpcflow/sdk/submission/shells/powershell.py +87 -16
- hpcflow/sdk/submission/submission.py +59 -35
- hpcflow/tests/unit/test_element.py +4 -9
- hpcflow/tests/unit/test_persistence.py +218 -0
- hpcflow/tests/unit/test_task.py +11 -12
- hpcflow/tests/unit/test_utils.py +82 -0
- hpcflow/tests/unit/test_workflow.py +3 -1
- {hpcflow_new2-0.2.0a50.dist-info → hpcflow_new2-0.2.0a52.dist-info}/METADATA +3 -1
- {hpcflow_new2-0.2.0a50.dist-info → hpcflow_new2-0.2.0a52.dist-info}/RECORD +38 -34
- {hpcflow_new2-0.2.0a50.dist-info → hpcflow_new2-0.2.0a52.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a50.dist-info → hpcflow_new2-0.2.0a52.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/core/utils.py
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
import copy
|
1
2
|
from functools import wraps
|
2
3
|
import contextlib
|
3
4
|
import hashlib
|
5
|
+
from itertools import accumulate
|
4
6
|
import json
|
5
7
|
import keyword
|
6
8
|
import os
|
7
|
-
from pathlib import Path
|
9
|
+
from pathlib import Path, PurePath
|
8
10
|
import random
|
9
11
|
import re
|
10
12
|
import socket
|
@@ -14,6 +16,7 @@ from typing import Mapping
|
|
14
16
|
|
15
17
|
from ruamel.yaml import YAML
|
16
18
|
import sentry_sdk
|
19
|
+
from watchdog.utils.dirsnapshot import DirectorySnapshot
|
17
20
|
|
18
21
|
from hpcflow.sdk.core.errors import FromSpecMissingObjectError, InvalidIdentifier
|
19
22
|
from hpcflow.sdk.typing import PathLike
|
@@ -430,7 +433,7 @@ def get_nested_indices(idx, size, nest_levels, raise_on_rollover=False):
|
|
430
433
|
return [(idx // (size ** (nest_levels - (i + 1)))) % size for i in range(nest_levels)]
|
431
434
|
|
432
435
|
|
433
|
-
def ensure_in(item, lst):
|
436
|
+
def ensure_in(item, lst) -> int:
|
434
437
|
"""Get the index of an item in a list and append the item if it is not in the
|
435
438
|
list."""
|
436
439
|
# TODO: add tests
|
@@ -492,3 +495,108 @@ def replace_items(lst, start, end, repl):
|
|
492
495
|
lst_a = lst[:start]
|
493
496
|
lst_b = lst[end:]
|
494
497
|
return lst_a + repl + lst_b
|
498
|
+
|
499
|
+
|
500
|
+
def flatten(lst):
|
501
|
+
"""Flatten an arbitrarily (but of uniform depth) nested list and return shape
|
502
|
+
information to enable un-flattening.
|
503
|
+
|
504
|
+
Un-flattening can be performed with the `reshape` function.
|
505
|
+
|
506
|
+
lst
|
507
|
+
List to be flattened. Each element must contain all lists or otherwise all items
|
508
|
+
that are considered to be at the "bottom" of the nested structure (e.g. integers).
|
509
|
+
For example, `[[1, 2], [3]]` is permitted and flattens to `[1, 2, 3]`, but
|
510
|
+
`[[1, 2], 3]` is not permitted because the first element is a list, but the second
|
511
|
+
is not.
|
512
|
+
|
513
|
+
"""
|
514
|
+
|
515
|
+
def _flatten(lst, _depth=0):
|
516
|
+
out = []
|
517
|
+
for i in lst:
|
518
|
+
if isinstance(i, list):
|
519
|
+
out += _flatten(i, _depth=_depth + 1)
|
520
|
+
all_lens[_depth].append(len(i))
|
521
|
+
else:
|
522
|
+
out.append(i)
|
523
|
+
return out
|
524
|
+
|
525
|
+
def _get_max_depth(lst):
|
526
|
+
lst = lst[:]
|
527
|
+
max_depth = 0
|
528
|
+
while isinstance(lst, list):
|
529
|
+
max_depth += 1
|
530
|
+
try:
|
531
|
+
lst = lst[0]
|
532
|
+
except IndexError:
|
533
|
+
# empty list, assume this is max depth
|
534
|
+
break
|
535
|
+
return max_depth
|
536
|
+
|
537
|
+
max_depth = _get_max_depth(lst) - 1
|
538
|
+
all_lens = tuple([] for _ in range(max_depth))
|
539
|
+
|
540
|
+
return _flatten(lst), all_lens
|
541
|
+
|
542
|
+
|
543
|
+
def reshape(lst, lens):
|
544
|
+
def _reshape(lst, lens):
|
545
|
+
lens_acc = [0] + list(accumulate(lens))
|
546
|
+
lst_rs = [lst[lens_acc[idx] : lens_acc[idx + 1]] for idx in range(len(lens))]
|
547
|
+
return lst_rs
|
548
|
+
|
549
|
+
for lens_i in lens[::-1]:
|
550
|
+
lst = _reshape(lst, lens_i)
|
551
|
+
|
552
|
+
return lst
|
553
|
+
|
554
|
+
|
555
|
+
def is_fsspec_url(url: str) -> bool:
|
556
|
+
return bool(re.match(r"(?:[a-z0-9]+:{1,2})+\/\/", url))
|
557
|
+
|
558
|
+
|
559
|
+
class JSONLikeDirSnapShot(DirectorySnapshot):
|
560
|
+
"""Overridden DirectorySnapshot from watchdog to allow saving and loading from JSON."""
|
561
|
+
|
562
|
+
def __init__(self, root_path=None, data=None):
|
563
|
+
"""Create an empty snapshot or load from JSON-like data."""
|
564
|
+
|
565
|
+
self.root_path = root_path
|
566
|
+
self._stat_info = {}
|
567
|
+
self._inode_to_path = {}
|
568
|
+
|
569
|
+
if data:
|
570
|
+
for k in list((data or {}).keys()):
|
571
|
+
# add root path
|
572
|
+
full_k = str(PurePath(root_path) / PurePath(k))
|
573
|
+
stat_dat, inode_key = data[k][:-2], data[k][-2:]
|
574
|
+
self._stat_info[full_k] = os.stat_result(stat_dat)
|
575
|
+
self._inode_to_path[tuple(inode_key)] = full_k
|
576
|
+
|
577
|
+
def take(self, *args, **kwargs):
|
578
|
+
"""Take the snapshot."""
|
579
|
+
super().__init__(*args, **kwargs)
|
580
|
+
|
581
|
+
def to_json_like(self):
|
582
|
+
"""Export to a dict that is JSON-compatible and can be later reloaded.
|
583
|
+
|
584
|
+
The last two integers in `data` for each path are the keys in
|
585
|
+
`self._inode_to_path`.
|
586
|
+
|
587
|
+
"""
|
588
|
+
|
589
|
+
# first key is the root path:
|
590
|
+
root_path = next(iter(self._stat_info.keys()))
|
591
|
+
|
592
|
+
# store efficiently:
|
593
|
+
inode_invert = {v: k for k, v in self._inode_to_path.items()}
|
594
|
+
data = {}
|
595
|
+
for k, v in self._stat_info.items():
|
596
|
+
k_rel = str(PurePath(k).relative_to(root_path))
|
597
|
+
data[k_rel] = list(v) + list(inode_invert[k])
|
598
|
+
|
599
|
+
return {
|
600
|
+
"root_path": root_path,
|
601
|
+
"data": data,
|
602
|
+
}
|