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.
Files changed (38) hide show
  1. hpcflow/_version.py +1 -1
  2. hpcflow/sdk/__init__.py +1 -1
  3. hpcflow/sdk/api.py +1 -1
  4. hpcflow/sdk/app.py +20 -11
  5. hpcflow/sdk/cli.py +34 -59
  6. hpcflow/sdk/core/__init__.py +13 -1
  7. hpcflow/sdk/core/actions.py +235 -126
  8. hpcflow/sdk/core/command_files.py +32 -24
  9. hpcflow/sdk/core/element.py +110 -114
  10. hpcflow/sdk/core/errors.py +57 -0
  11. hpcflow/sdk/core/loop.py +18 -34
  12. hpcflow/sdk/core/parameters.py +5 -3
  13. hpcflow/sdk/core/task.py +135 -131
  14. hpcflow/sdk/core/task_schema.py +11 -4
  15. hpcflow/sdk/core/utils.py +110 -2
  16. hpcflow/sdk/core/workflow.py +964 -676
  17. hpcflow/sdk/data/template_components/environments.yaml +0 -44
  18. hpcflow/sdk/data/template_components/task_schemas.yaml +52 -10
  19. hpcflow/sdk/persistence/__init__.py +21 -33
  20. hpcflow/sdk/persistence/base.py +1340 -458
  21. hpcflow/sdk/persistence/json.py +424 -546
  22. hpcflow/sdk/persistence/pending.py +563 -0
  23. hpcflow/sdk/persistence/store_resource.py +131 -0
  24. hpcflow/sdk/persistence/utils.py +57 -0
  25. hpcflow/sdk/persistence/zarr.py +852 -841
  26. hpcflow/sdk/submission/jobscript.py +133 -112
  27. hpcflow/sdk/submission/shells/bash.py +62 -16
  28. hpcflow/sdk/submission/shells/powershell.py +87 -16
  29. hpcflow/sdk/submission/submission.py +59 -35
  30. hpcflow/tests/unit/test_element.py +4 -9
  31. hpcflow/tests/unit/test_persistence.py +218 -0
  32. hpcflow/tests/unit/test_task.py +11 -12
  33. hpcflow/tests/unit/test_utils.py +82 -0
  34. hpcflow/tests/unit/test_workflow.py +3 -1
  35. {hpcflow_new2-0.2.0a50.dist-info → hpcflow_new2-0.2.0a52.dist-info}/METADATA +3 -1
  36. {hpcflow_new2-0.2.0a50.dist-info → hpcflow_new2-0.2.0a52.dist-info}/RECORD +38 -34
  37. {hpcflow_new2-0.2.0a50.dist-info → hpcflow_new2-0.2.0a52.dist-info}/WHEEL +0 -0
  38. {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
+ }