hpcflow-new2 0.2.0a190__py3-none-any.whl → 0.2.0a200__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/__pyinstaller/hook-hpcflow.py +1 -0
- hpcflow/_version.py +1 -1
- hpcflow/data/scripts/bad_script.py +2 -0
- hpcflow/data/scripts/do_nothing.py +2 -0
- hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
- hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/input_file_generator_basic.py +3 -0
- hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
- hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
- hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
- hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
- hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
- hpcflow/data/scripts/output_file_parser_basic.py +3 -0
- hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
- hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/script_exit_test.py +5 -0
- hpcflow/data/template_components/environments.yaml +1 -1
- hpcflow/sdk/__init__.py +5 -0
- hpcflow/sdk/app.py +166 -92
- hpcflow/sdk/cli.py +263 -84
- hpcflow/sdk/cli_common.py +99 -5
- hpcflow/sdk/config/callbacks.py +38 -1
- hpcflow/sdk/config/config.py +102 -13
- hpcflow/sdk/config/errors.py +19 -5
- hpcflow/sdk/config/types.py +3 -0
- hpcflow/sdk/core/__init__.py +25 -1
- hpcflow/sdk/core/actions.py +914 -262
- hpcflow/sdk/core/cache.py +76 -34
- hpcflow/sdk/core/command_files.py +14 -128
- hpcflow/sdk/core/commands.py +35 -6
- hpcflow/sdk/core/element.py +122 -50
- hpcflow/sdk/core/errors.py +58 -2
- hpcflow/sdk/core/execute.py +207 -0
- hpcflow/sdk/core/loop.py +408 -50
- hpcflow/sdk/core/loop_cache.py +4 -4
- hpcflow/sdk/core/parameters.py +382 -37
- hpcflow/sdk/core/run_dir_files.py +13 -40
- hpcflow/sdk/core/skip_reason.py +7 -0
- hpcflow/sdk/core/task.py +119 -30
- hpcflow/sdk/core/task_schema.py +68 -0
- hpcflow/sdk/core/test_utils.py +66 -27
- hpcflow/sdk/core/types.py +54 -1
- hpcflow/sdk/core/utils.py +136 -19
- hpcflow/sdk/core/workflow.py +1587 -356
- hpcflow/sdk/data/workflow_spec_schema.yaml +2 -0
- hpcflow/sdk/demo/cli.py +7 -0
- hpcflow/sdk/helper/cli.py +1 -0
- hpcflow/sdk/log.py +42 -15
- hpcflow/sdk/persistence/base.py +405 -53
- hpcflow/sdk/persistence/json.py +177 -52
- hpcflow/sdk/persistence/pending.py +237 -69
- hpcflow/sdk/persistence/store_resource.py +3 -2
- hpcflow/sdk/persistence/types.py +15 -4
- hpcflow/sdk/persistence/zarr.py +928 -81
- hpcflow/sdk/submission/jobscript.py +1408 -489
- hpcflow/sdk/submission/schedulers/__init__.py +40 -5
- hpcflow/sdk/submission/schedulers/direct.py +33 -19
- hpcflow/sdk/submission/schedulers/sge.py +51 -16
- hpcflow/sdk/submission/schedulers/slurm.py +44 -16
- hpcflow/sdk/submission/schedulers/utils.py +7 -2
- hpcflow/sdk/submission/shells/base.py +68 -20
- hpcflow/sdk/submission/shells/bash.py +222 -129
- hpcflow/sdk/submission/shells/powershell.py +200 -150
- hpcflow/sdk/submission/submission.py +852 -119
- hpcflow/sdk/submission/types.py +18 -21
- hpcflow/sdk/typing.py +24 -5
- hpcflow/sdk/utils/arrays.py +71 -0
- hpcflow/sdk/utils/deferred_file.py +55 -0
- hpcflow/sdk/utils/hashing.py +16 -0
- hpcflow/sdk/utils/patches.py +12 -0
- hpcflow/sdk/utils/strings.py +33 -0
- hpcflow/tests/api/test_api.py +32 -0
- hpcflow/tests/conftest.py +19 -0
- hpcflow/tests/data/benchmark_script_runner.yaml +26 -0
- hpcflow/tests/data/multi_path_sequences.yaml +29 -0
- hpcflow/tests/data/workflow_test_run_abort.yaml +34 -35
- hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
- hpcflow/tests/scripts/test_input_file_generators.py +282 -0
- hpcflow/tests/scripts/test_main_scripts.py +821 -70
- hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
- hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
- hpcflow/tests/shells/wsl/test_wsl_submission.py +6 -0
- hpcflow/tests/unit/test_action.py +176 -0
- hpcflow/tests/unit/test_app.py +20 -0
- hpcflow/tests/unit/test_cache.py +46 -0
- hpcflow/tests/unit/test_cli.py +133 -0
- hpcflow/tests/unit/test_config.py +122 -1
- hpcflow/tests/unit/test_element_iteration.py +47 -0
- hpcflow/tests/unit/test_jobscript_unit.py +757 -0
- hpcflow/tests/unit/test_loop.py +1332 -27
- hpcflow/tests/unit/test_meta_task.py +325 -0
- hpcflow/tests/unit/test_multi_path_sequences.py +229 -0
- hpcflow/tests/unit/test_parameter.py +13 -0
- hpcflow/tests/unit/test_persistence.py +190 -8
- hpcflow/tests/unit/test_run.py +109 -3
- hpcflow/tests/unit/test_run_directories.py +29 -0
- hpcflow/tests/unit/test_shell.py +20 -0
- hpcflow/tests/unit/test_submission.py +5 -76
- hpcflow/tests/unit/test_workflow_template.py +31 -0
- hpcflow/tests/unit/utils/test_arrays.py +40 -0
- hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
- hpcflow/tests/unit/utils/test_hashing.py +65 -0
- hpcflow/tests/unit/utils/test_patches.py +5 -0
- hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
- hpcflow/tests/workflows/__init__.py +0 -0
- hpcflow/tests/workflows/test_directory_structure.py +31 -0
- hpcflow/tests/workflows/test_jobscript.py +332 -0
- hpcflow/tests/workflows/test_run_status.py +198 -0
- hpcflow/tests/workflows/test_skip_downstream.py +696 -0
- hpcflow/tests/workflows/test_submission.py +140 -0
- hpcflow/tests/workflows/test_workflows.py +142 -2
- hpcflow/tests/workflows/test_zip.py +18 -0
- hpcflow/viz_demo.ipynb +6587 -3
- {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a200.dist-info}/METADATA +7 -4
- hpcflow_new2-0.2.0a200.dist-info/RECORD +222 -0
- hpcflow_new2-0.2.0a190.dist-info/RECORD +0 -165
- {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a200.dist-info}/LICENSE +0 -0
- {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a200.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a200.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/persistence/base.py
CHANGED
@@ -3,6 +3,7 @@ Base persistence models.
|
|
3
3
|
|
4
4
|
Store* classes represent the element-metadata in the store, in a store-agnostic way.
|
5
5
|
"""
|
6
|
+
|
6
7
|
from __future__ import annotations
|
7
8
|
from abc import ABC, abstractmethod
|
8
9
|
import contextlib
|
@@ -10,6 +11,7 @@ import copy
|
|
10
11
|
from dataclasses import dataclass, field
|
11
12
|
import enum
|
12
13
|
from logging import Logger
|
14
|
+
from functools import wraps
|
13
15
|
import os
|
14
16
|
from pathlib import Path
|
15
17
|
import shutil
|
@@ -17,6 +19,8 @@ import socket
|
|
17
19
|
import time
|
18
20
|
from typing import Generic, TypeVar, cast, overload, TYPE_CHECKING
|
19
21
|
|
22
|
+
import numpy as np
|
23
|
+
|
20
24
|
from hpcflow.sdk.core.utils import (
|
21
25
|
flatten,
|
22
26
|
get_in_container,
|
@@ -28,6 +32,12 @@ from hpcflow.sdk.core.utils import (
|
|
28
32
|
parse_timestamp,
|
29
33
|
current_timestamp,
|
30
34
|
)
|
35
|
+
from hpcflow.sdk.core.errors import ParametersMetadataReadOnlyError
|
36
|
+
from hpcflow.sdk.submission.submission import (
|
37
|
+
JOBSCRIPT_SUBMIT_TIME_KEYS,
|
38
|
+
SUBMISSION_SUBMIT_TIME_KEYS,
|
39
|
+
)
|
40
|
+
from hpcflow.sdk.utils.strings import shorten_list_str
|
31
41
|
from hpcflow.sdk.log import TimeIt
|
32
42
|
from hpcflow.sdk.typing import hydrate
|
33
43
|
from hpcflow.sdk.persistence.pending import PendingChanges
|
@@ -46,6 +56,7 @@ if TYPE_CHECKING:
|
|
46
56
|
from typing import Any, ClassVar, Final, Literal
|
47
57
|
from typing_extensions import Self, TypeIs
|
48
58
|
from fsspec import AbstractFileSystem # type: ignore
|
59
|
+
from numpy.typing import NDArray
|
49
60
|
from .pending import CommitResourceMap
|
50
61
|
from .store_resource import StoreResource
|
51
62
|
from .types import (
|
@@ -59,6 +70,7 @@ if TYPE_CHECKING:
|
|
59
70
|
StoreCreationInfo,
|
60
71
|
TemplateMeta,
|
61
72
|
TypeLookup,
|
73
|
+
IterableParam,
|
62
74
|
)
|
63
75
|
from .zarr import ZarrAttrsDict
|
64
76
|
from ..app import BaseApp
|
@@ -66,7 +78,7 @@ if TYPE_CHECKING:
|
|
66
78
|
from ..core.json_like import JSONed, JSONDocument
|
67
79
|
from ..core.parameters import ParameterValue
|
68
80
|
from ..core.workflow import Workflow
|
69
|
-
from ..submission.types import VersionInfo
|
81
|
+
from ..submission.types import VersionInfo, ResolvedJobscriptBlockDependencies
|
70
82
|
|
71
83
|
T = TypeVar("T")
|
72
84
|
#: Type of the serialized form.
|
@@ -98,6 +110,28 @@ def update_param_source_dict(source: ParamSource, update: ParamSource) -> ParamS
|
|
98
110
|
return cast("ParamSource", dict(sorted({**source, **update}.items())))
|
99
111
|
|
100
112
|
|
113
|
+
def writes_parameter_data(func: Callable):
|
114
|
+
"""Decorator function that should wrap `PersistentStore` methods that write
|
115
|
+
parameter-associated data.
|
116
|
+
|
117
|
+
Notes
|
118
|
+
-----
|
119
|
+
This decorator checks that the parameters-metadata cache is not in use, which should
|
120
|
+
not be used during writing of parameter-associated data.
|
121
|
+
"""
|
122
|
+
|
123
|
+
@wraps(func)
|
124
|
+
def inner(self, *args, **kwargs):
|
125
|
+
if self._use_parameters_metadata_cache:
|
126
|
+
raise ParametersMetadataReadOnlyError(
|
127
|
+
"Cannot use the `parameters_metadata_cache` when writing parameter-"
|
128
|
+
"associated data!"
|
129
|
+
)
|
130
|
+
return func(self, *args, **kwargs)
|
131
|
+
|
132
|
+
return inner
|
133
|
+
|
134
|
+
|
101
135
|
@dataclass
|
102
136
|
class PersistentStoreFeatures:
|
103
137
|
"""
|
@@ -414,6 +448,26 @@ class StoreElementIter(Generic[SerFormT, ContextT]):
|
|
414
448
|
EARs_initialised=True,
|
415
449
|
)
|
416
450
|
|
451
|
+
@TimeIt.decorator
|
452
|
+
def update_data_idx(self: AnySElementIter, data_idx: DataIndex) -> AnySElementIter:
|
453
|
+
"""Return a copy with an updated `data_idx`.
|
454
|
+
|
455
|
+
The existing data index is updated, not overwritten.
|
456
|
+
|
457
|
+
"""
|
458
|
+
new_data_idx = copy.deepcopy(self.data_idx)
|
459
|
+
new_data_idx.update(data_idx)
|
460
|
+
return self.__class__(
|
461
|
+
id_=self.id_,
|
462
|
+
is_pending=self.is_pending,
|
463
|
+
element_ID=self.element_ID,
|
464
|
+
EAR_IDs=self.EAR_IDs,
|
465
|
+
data_idx=new_data_idx,
|
466
|
+
schema_parameters=self.schema_parameters,
|
467
|
+
loop_idx=self.loop_idx,
|
468
|
+
EARs_initialised=self.EARs_initialised,
|
469
|
+
)
|
470
|
+
|
417
471
|
|
418
472
|
@dataclass
|
419
473
|
class StoreEAR(Generic[SerFormT, ContextT]):
|
@@ -478,8 +532,10 @@ class StoreEAR(Generic[SerFormT, ContextT]):
|
|
478
532
|
data_idx: DataIndex
|
479
533
|
#: Which submission contained this EAR, if known.
|
480
534
|
submission_idx: int | None = None
|
535
|
+
#: Run ID whose commands can be used for this run (may be this run's ID).
|
536
|
+
commands_file_ID: int | None = None
|
481
537
|
#: Whether to skip this EAR.
|
482
|
-
skip:
|
538
|
+
skip: int = 0
|
483
539
|
#: Whether this EAR was successful, if known.
|
484
540
|
success: bool | None = None
|
485
541
|
#: When this EAR started, if known.
|
@@ -496,6 +552,7 @@ class StoreEAR(Generic[SerFormT, ContextT]):
|
|
496
552
|
metadata: Metadata | None = None
|
497
553
|
#: Where this EAR was submitted to run, if known.
|
498
554
|
run_hostname: str | None = None
|
555
|
+
port_number: int | None = None
|
499
556
|
|
500
557
|
@staticmethod
|
501
558
|
def _encode_datetime(dt: datetime | None, ts_fmt: str) -> str | None:
|
@@ -530,6 +587,7 @@ class StoreEAR(Generic[SerFormT, ContextT]):
|
|
530
587
|
"commands_idx": self.commands_idx,
|
531
588
|
"data_idx": self.data_idx,
|
532
589
|
"submission_idx": self.submission_idx,
|
590
|
+
"commands_file_ID": self.commands_file_ID,
|
533
591
|
"success": self.success,
|
534
592
|
"skip": self.skip,
|
535
593
|
"start_time": _process_datetime(self.start_time),
|
@@ -539,13 +597,15 @@ class StoreEAR(Generic[SerFormT, ContextT]):
|
|
539
597
|
"exit_code": self.exit_code,
|
540
598
|
"metadata": self.metadata,
|
541
599
|
"run_hostname": self.run_hostname,
|
600
|
+
"port_number": self.port_number,
|
542
601
|
}
|
543
602
|
|
544
603
|
@TimeIt.decorator
|
545
604
|
def update(
|
546
605
|
self,
|
547
606
|
submission_idx: int | None = None,
|
548
|
-
|
607
|
+
commands_file_ID: int | None = None,
|
608
|
+
skip: int | None = None,
|
549
609
|
success: bool | None = None,
|
550
610
|
start_time: datetime | None = None,
|
551
611
|
end_time: datetime | None = None,
|
@@ -553,6 +613,8 @@ class StoreEAR(Generic[SerFormT, ContextT]):
|
|
553
613
|
snapshot_end: dict[str, Any] | None = None,
|
554
614
|
exit_code: int | None = None,
|
555
615
|
run_hostname: str | None = None,
|
616
|
+
port_number: int | None = None,
|
617
|
+
data_idx: DataIndex | None = None,
|
556
618
|
) -> Self:
|
557
619
|
"""Return a shallow copy, with specified data updated."""
|
558
620
|
|
@@ -565,6 +627,16 @@ class StoreEAR(Generic[SerFormT, ContextT]):
|
|
565
627
|
snap_e = snapshot_end if snapshot_end is not None else self.snapshot_end
|
566
628
|
exit_code = exit_code if exit_code is not None else self.exit_code
|
567
629
|
run_hn = run_hostname if run_hostname is not None else self.run_hostname
|
630
|
+
port_num = port_number if port_number is not None else self.port_number
|
631
|
+
cmd_file = (
|
632
|
+
commands_file_ID if commands_file_ID is not None else self.commands_file_ID
|
633
|
+
)
|
634
|
+
if data_idx is not None:
|
635
|
+
new_data_idx = copy.deepcopy(self.data_idx)
|
636
|
+
new_data_idx.update(data_idx)
|
637
|
+
data_idx = new_data_idx
|
638
|
+
else:
|
639
|
+
data_idx = self.data_idx
|
568
640
|
|
569
641
|
return self.__class__(
|
570
642
|
id_=self.id_,
|
@@ -572,9 +644,10 @@ class StoreEAR(Generic[SerFormT, ContextT]):
|
|
572
644
|
elem_iter_ID=self.elem_iter_ID,
|
573
645
|
action_idx=self.action_idx,
|
574
646
|
commands_idx=self.commands_idx,
|
575
|
-
data_idx=
|
647
|
+
data_idx=data_idx,
|
576
648
|
metadata=self.metadata,
|
577
649
|
submission_idx=sub_idx,
|
650
|
+
commands_file_ID=cmd_file,
|
578
651
|
skip=skip,
|
579
652
|
success=success,
|
580
653
|
start_time=start_time,
|
@@ -583,6 +656,7 @@ class StoreEAR(Generic[SerFormT, ContextT]):
|
|
583
656
|
snapshot_end=snap_e,
|
584
657
|
exit_code=exit_code,
|
585
658
|
run_hostname=run_hn,
|
659
|
+
port_number=port_num,
|
586
660
|
)
|
587
661
|
|
588
662
|
|
@@ -929,6 +1003,8 @@ class PersistentStore(
|
|
929
1003
|
self._use_cache = False
|
930
1004
|
self._reset_cache()
|
931
1005
|
|
1006
|
+
self._use_parameters_metadata_cache: bool = False # subclass-specific cache
|
1007
|
+
|
932
1008
|
@abstractmethod
|
933
1009
|
def cached_load(self) -> contextlib.AbstractContextManager[None]:
|
934
1010
|
"""
|
@@ -1008,6 +1084,12 @@ class PersistentStore(
|
|
1008
1084
|
) -> Any:
|
1009
1085
|
...
|
1010
1086
|
|
1087
|
+
@abstractmethod
|
1088
|
+
def get_dirs_array(self) -> NDArray:
|
1089
|
+
"""
|
1090
|
+
Retrieve the run directories array.
|
1091
|
+
"""
|
1092
|
+
|
1011
1093
|
@classmethod
|
1012
1094
|
@abstractmethod
|
1013
1095
|
def write_empty_workflow(
|
@@ -1107,6 +1189,14 @@ class PersistentStore(
|
|
1107
1189
|
def num_EARs_cache(self, value: int | None):
|
1108
1190
|
self._cache["num_EARs"] = value
|
1109
1191
|
|
1192
|
+
@property
|
1193
|
+
def num_params_cache(self) -> int | None:
|
1194
|
+
return self._cache["num_params"]
|
1195
|
+
|
1196
|
+
@num_params_cache.setter
|
1197
|
+
def num_params_cache(self, value: int | None):
|
1198
|
+
self._cache["num_params"] = value
|
1199
|
+
|
1110
1200
|
@property
|
1111
1201
|
def param_sources_cache(self) -> dict[int, ParamSource]:
|
1112
1202
|
"""Cache for persistent parameter sources."""
|
@@ -1129,6 +1219,7 @@ class PersistentStore(
|
|
1129
1219
|
"num_tasks": None,
|
1130
1220
|
"parameters": {},
|
1131
1221
|
"num_EARs": None,
|
1222
|
+
"num_params": None,
|
1132
1223
|
}
|
1133
1224
|
|
1134
1225
|
@contextlib.contextmanager
|
@@ -1141,11 +1232,25 @@ class PersistentStore(
|
|
1141
1232
|
self._use_cache = False
|
1142
1233
|
self._reset_cache()
|
1143
1234
|
|
1235
|
+
@contextlib.contextmanager
|
1236
|
+
def parameters_metadata_cache(self):
|
1237
|
+
"""Context manager for using the parameters-metadata cache.
|
1238
|
+
|
1239
|
+
Notes
|
1240
|
+
-----
|
1241
|
+
This method can be overridden by a subclass to provide an implementation-specific
|
1242
|
+
cache of metadata associated with parameters, or even parameter data itself.
|
1243
|
+
|
1244
|
+
Using this cache precludes writing/setting parameter data.
|
1245
|
+
|
1246
|
+
"""
|
1247
|
+
yield
|
1248
|
+
|
1144
1249
|
@staticmethod
|
1145
1250
|
def prepare_test_store_from_spec(
|
1146
1251
|
task_spec: Sequence[
|
1147
1252
|
Mapping[str, Sequence[Mapping[str, Sequence[Mapping[str, Sequence]]]]]
|
1148
|
-
]
|
1253
|
+
],
|
1149
1254
|
) -> tuple[list[dict], list[dict], list[dict], list[dict]]:
|
1150
1255
|
"""Generate a valid store from a specification in terms of nested
|
1151
1256
|
elements/iterations/EARs.
|
@@ -1374,7 +1479,8 @@ class PersistentStore(
|
|
1374
1479
|
def add_loop(
|
1375
1480
|
self,
|
1376
1481
|
loop_template: Mapping[str, Any],
|
1377
|
-
iterable_parameters,
|
1482
|
+
iterable_parameters: Mapping[str, IterableParam],
|
1483
|
+
output_parameters: Mapping[str, int],
|
1378
1484
|
parents: Sequence[str],
|
1379
1485
|
num_added_iterations: Mapping[tuple[int, ...], int],
|
1380
1486
|
iter_IDs: Iterable[int],
|
@@ -1388,7 +1494,8 @@ class PersistentStore(
|
|
1388
1494
|
]
|
1389
1495
|
self._pending.add_loops[new_idx] = {
|
1390
1496
|
"loop_template": dict(loop_template),
|
1391
|
-
"iterable_parameters": iterable_parameters,
|
1497
|
+
"iterable_parameters": cast("dict", iterable_parameters),
|
1498
|
+
"output_parameters": cast("dict", output_parameters),
|
1392
1499
|
"parents": list(parents),
|
1393
1500
|
"num_added_iterations": added_iters,
|
1394
1501
|
}
|
@@ -1400,7 +1507,9 @@ class PersistentStore(
|
|
1400
1507
|
self.save()
|
1401
1508
|
|
1402
1509
|
@TimeIt.decorator
|
1403
|
-
def add_submission(
|
1510
|
+
def add_submission(
|
1511
|
+
self, sub_idx: int, sub_js: Mapping[str, JSONed], save: bool = True
|
1512
|
+
):
|
1404
1513
|
"""Add a new submission."""
|
1405
1514
|
self.logger.debug("Adding store submission.")
|
1406
1515
|
self._pending.add_submissions[sub_idx] = sub_js
|
@@ -1495,58 +1604,121 @@ class PersistentStore(
|
|
1495
1604
|
self.save()
|
1496
1605
|
return new_ID
|
1497
1606
|
|
1498
|
-
|
1499
|
-
|
1607
|
+
@TimeIt.decorator
|
1608
|
+
def set_run_dirs(
|
1609
|
+
self, run_dir_indices: np.ndarray, run_idx: np.ndarray, save: bool = True
|
1610
|
+
):
|
1611
|
+
self.logger.debug(f"Setting {run_idx.size} run directory indices.")
|
1612
|
+
self._pending.set_run_dirs.append((run_dir_indices, run_idx))
|
1613
|
+
if save:
|
1614
|
+
self.save()
|
1615
|
+
|
1616
|
+
def update_at_submit_metadata(
|
1617
|
+
self, sub_idx: int, submission_parts: dict[str, list[int]], save: bool = True
|
1500
1618
|
):
|
1501
1619
|
"""
|
1502
|
-
|
1620
|
+
Update metadata that is set at submit-time.
|
1503
1621
|
"""
|
1504
|
-
|
1622
|
+
if submission_parts:
|
1623
|
+
self._pending.update_at_submit_metadata[sub_idx][
|
1624
|
+
"submission_parts"
|
1625
|
+
] = submission_parts
|
1505
1626
|
if save:
|
1506
1627
|
self.save()
|
1507
1628
|
|
1508
1629
|
@TimeIt.decorator
|
1509
|
-
def
|
1510
|
-
self, EAR_ID: int, sub_idx: int, save: bool = True
|
1630
|
+
def set_run_submission_data(
|
1631
|
+
self, EAR_ID: int, cmds_ID: int | None, sub_idx: int, save: bool = True
|
1511
1632
|
) -> None:
|
1512
1633
|
"""
|
1513
|
-
Set the submission index for an element action run.
|
1634
|
+
Set the run submission data, like the submission index for an element action run.
|
1514
1635
|
"""
|
1515
|
-
self._pending.
|
1636
|
+
self._pending.set_EAR_submission_data[EAR_ID] = (sub_idx, cmds_ID)
|
1516
1637
|
if save:
|
1517
1638
|
self.save()
|
1518
1639
|
|
1519
|
-
def set_EAR_start(
|
1640
|
+
def set_EAR_start(
|
1641
|
+
self,
|
1642
|
+
EAR_ID: int,
|
1643
|
+
run_dir: Path | None,
|
1644
|
+
port_number: int | None,
|
1645
|
+
save: bool = True,
|
1646
|
+
) -> datetime:
|
1520
1647
|
"""
|
1521
1648
|
Mark an element action run as started.
|
1522
1649
|
"""
|
1523
1650
|
dt = current_timestamp()
|
1524
|
-
ss_js = self._app.RunDirAppFiles.take_snapshot()
|
1651
|
+
ss_js = self._app.RunDirAppFiles.take_snapshot() if run_dir else None
|
1652
|
+
run_hostname = socket.gethostname()
|
1653
|
+
self._pending.set_EAR_starts[EAR_ID] = (dt, ss_js, run_hostname, port_number)
|
1654
|
+
if save:
|
1655
|
+
self.save()
|
1656
|
+
return dt
|
1657
|
+
|
1658
|
+
def set_multi_run_starts(
|
1659
|
+
self,
|
1660
|
+
run_ids: list[int],
|
1661
|
+
run_dirs: list[Path | None],
|
1662
|
+
port_number: int,
|
1663
|
+
save: bool = True,
|
1664
|
+
) -> datetime:
|
1665
|
+
dt = current_timestamp()
|
1525
1666
|
run_hostname = socket.gethostname()
|
1526
|
-
|
1667
|
+
run_start_data: dict[int, tuple] = {}
|
1668
|
+
for id_i, dir_i in zip(run_ids, run_dirs):
|
1669
|
+
ss_js_i = self._app.RunDirAppFiles.take_snapshot(dir_i) if dir_i else None
|
1670
|
+
run_start_data[id_i] = (dt, ss_js_i, run_hostname, port_number)
|
1671
|
+
|
1672
|
+
self._pending.set_EAR_starts.update(run_start_data)
|
1527
1673
|
if save:
|
1528
1674
|
self.save()
|
1529
1675
|
return dt
|
1530
1676
|
|
1531
1677
|
def set_EAR_end(
|
1532
|
-
self,
|
1678
|
+
self,
|
1679
|
+
EAR_ID: int,
|
1680
|
+
exit_code: int,
|
1681
|
+
success: bool,
|
1682
|
+
snapshot: bool,
|
1683
|
+
save: bool = True,
|
1533
1684
|
) -> datetime:
|
1534
1685
|
"""
|
1535
1686
|
Mark an element action run as finished.
|
1536
1687
|
"""
|
1537
1688
|
# TODO: save output files
|
1538
1689
|
dt = current_timestamp()
|
1539
|
-
ss_js = self._app.RunDirAppFiles.take_snapshot()
|
1690
|
+
ss_js = self._app.RunDirAppFiles.take_snapshot() if snapshot else None
|
1540
1691
|
self._pending.set_EAR_ends[EAR_ID] = (dt, ss_js, exit_code, success)
|
1541
1692
|
if save:
|
1542
1693
|
self.save()
|
1543
1694
|
return dt
|
1544
1695
|
|
1545
|
-
def
|
1696
|
+
def set_multi_run_ends(
|
1697
|
+
self,
|
1698
|
+
run_ids: list[int],
|
1699
|
+
run_dirs: list[Path | None],
|
1700
|
+
exit_codes: list[int],
|
1701
|
+
successes: list[bool],
|
1702
|
+
save: bool = True,
|
1703
|
+
) -> datetime:
|
1704
|
+
self.logger.info("PersistentStore.set_multi_run_ends.")
|
1705
|
+
dt = current_timestamp()
|
1706
|
+
run_end_data: dict[int, tuple] = {}
|
1707
|
+
for id_i, dir_i, ex_i, sc_i in zip(run_ids, run_dirs, exit_codes, successes):
|
1708
|
+
ss_js_i = self._app.RunDirAppFiles.take_snapshot(dir_i) if dir_i else None
|
1709
|
+
run_end_data[id_i] = (dt, ss_js_i, ex_i, sc_i)
|
1710
|
+
|
1711
|
+
self._pending.set_EAR_ends.update(run_end_data)
|
1712
|
+
if save:
|
1713
|
+
self.save()
|
1714
|
+
self.logger.info("PersistentStore.set_multi_run_ends finished.")
|
1715
|
+
return dt
|
1716
|
+
|
1717
|
+
def set_EAR_skip(self, skip_reasons: dict[int, int], save: bool = True) -> None:
|
1546
1718
|
"""
|
1547
|
-
Mark
|
1719
|
+
Mark element action runs as skipped for the specified reasons.
|
1548
1720
|
"""
|
1549
|
-
self._pending.set_EAR_skips.
|
1721
|
+
self._pending.set_EAR_skips.update(skip_reasons)
|
1550
1722
|
if save:
|
1551
1723
|
self.save()
|
1552
1724
|
|
@@ -1566,6 +1738,7 @@ class PersistentStore(
|
|
1566
1738
|
submit_time: str | None = None,
|
1567
1739
|
submit_hostname: str | None = None,
|
1568
1740
|
submit_machine: str | None = None,
|
1741
|
+
shell_idx: int | None = None,
|
1569
1742
|
submit_cmdline: list[str] | None = None,
|
1570
1743
|
os_name: str | None = None,
|
1571
1744
|
shell_name: str | None = None,
|
@@ -1586,6 +1759,8 @@ class PersistentStore(
|
|
1586
1759
|
entry["submit_hostname"] = submit_hostname
|
1587
1760
|
if submit_machine:
|
1588
1761
|
entry["submit_machine"] = submit_machine
|
1762
|
+
if shell_idx is not None:
|
1763
|
+
entry["shell_idx"] = shell_idx
|
1589
1764
|
if submit_cmdline:
|
1590
1765
|
entry["submit_cmdline"] = submit_cmdline
|
1591
1766
|
if os_name:
|
@@ -1594,13 +1769,14 @@ class PersistentStore(
|
|
1594
1769
|
entry["shell_name"] = shell_name
|
1595
1770
|
if scheduler_name:
|
1596
1771
|
entry["scheduler_name"] = scheduler_name
|
1597
|
-
if scheduler_job_ID:
|
1772
|
+
if scheduler_job_ID or process_ID:
|
1598
1773
|
entry["scheduler_job_ID"] = scheduler_job_ID
|
1599
|
-
if process_ID:
|
1774
|
+
if process_ID or scheduler_job_ID:
|
1600
1775
|
entry["process_ID"] = process_ID
|
1601
1776
|
if save:
|
1602
1777
|
self.save()
|
1603
1778
|
|
1779
|
+
@writes_parameter_data
|
1604
1780
|
def _add_parameter(
|
1605
1781
|
self,
|
1606
1782
|
is_set: bool,
|
@@ -1749,6 +1925,7 @@ class PersistentStore(
|
|
1749
1925
|
with dst_path.open("wt") as fp:
|
1750
1926
|
fp.write(dat["contents"])
|
1751
1927
|
|
1928
|
+
@writes_parameter_data
|
1752
1929
|
def add_set_parameter(
|
1753
1930
|
self,
|
1754
1931
|
data: ParameterValue | list | tuple | set | dict | int | float | str | Any,
|
@@ -1760,6 +1937,7 @@ class PersistentStore(
|
|
1760
1937
|
"""
|
1761
1938
|
return self._add_parameter(data=data, is_set=True, source=source, save=save)
|
1762
1939
|
|
1940
|
+
@writes_parameter_data
|
1763
1941
|
def add_unset_parameter(self, source: ParamSource, save: bool = True) -> int:
|
1764
1942
|
"""
|
1765
1943
|
Add a parameter that is not set to any value.
|
@@ -1770,6 +1948,7 @@ class PersistentStore(
|
|
1770
1948
|
def _set_parameter_values(self, set_parameters: dict[int, tuple[Any, bool]]):
|
1771
1949
|
...
|
1772
1950
|
|
1951
|
+
@writes_parameter_data
|
1773
1952
|
def set_parameter_value(
|
1774
1953
|
self, param_id: int, value: Any, is_file: bool = False, save: bool = True
|
1775
1954
|
):
|
@@ -1783,7 +1962,17 @@ class PersistentStore(
|
|
1783
1962
|
if save:
|
1784
1963
|
self.save()
|
1785
1964
|
|
1965
|
+
@writes_parameter_data
|
1966
|
+
def set_parameter_values(self, values: dict[int, Any], save: bool = True):
|
1967
|
+
"""Set multiple non-file parameter values by parameter IDs."""
|
1968
|
+
param_ids = values.keys()
|
1969
|
+
self.logger.debug(f"Setting multiple store parameter IDs {param_ids!r}.")
|
1970
|
+
self._pending.set_parameters.update({k: (v, False) for k, v in values.items()})
|
1971
|
+
if save:
|
1972
|
+
self.save()
|
1973
|
+
|
1786
1974
|
@TimeIt.decorator
|
1975
|
+
@writes_parameter_data
|
1787
1976
|
def update_param_source(
|
1788
1977
|
self, param_sources: Mapping[int, ParamSource], save: bool = True
|
1789
1978
|
) -> None:
|
@@ -1834,11 +2023,23 @@ class PersistentStore(
|
|
1834
2023
|
if save:
|
1835
2024
|
self.save()
|
1836
2025
|
|
2026
|
+
def update_iter_data_indices(self, data_indices: dict[int, DataIndex]):
|
2027
|
+
"""Update data indices of one or more iterations."""
|
2028
|
+
for k, v in data_indices.items():
|
2029
|
+
self._pending.update_iter_data_idx[k].update(v)
|
2030
|
+
|
2031
|
+
def update_run_data_indices(self, data_indices: dict[int, DataIndex]):
|
2032
|
+
"""Update data indices of one or more runs."""
|
2033
|
+
for k, v in data_indices.items():
|
2034
|
+
self._pending.update_run_data_idx[k].update(v)
|
2035
|
+
|
1837
2036
|
def get_template_components(self) -> dict[str, Any]:
|
1838
2037
|
"""Get all template components, including pending."""
|
1839
2038
|
tc = copy.deepcopy(self._get_persistent_template_components())
|
1840
2039
|
for typ in TEMPLATE_COMP_TYPES:
|
1841
|
-
for hash_i, dat_i in self._pending.add_template_components
|
2040
|
+
for hash_i, dat_i in self._pending.add_template_components.get(
|
2041
|
+
typ, {}
|
2042
|
+
).items():
|
1842
2043
|
tc.setdefault(typ, {})[hash_i] = dat_i
|
1843
2044
|
|
1844
2045
|
return tc
|
@@ -1963,11 +2164,11 @@ class PersistentStore(
|
|
1963
2164
|
@abstractmethod
|
1964
2165
|
def _get_persistent_submissions(
|
1965
2166
|
self, id_lst: Iterable[int] | None = None
|
1966
|
-
) -> dict[int,
|
2167
|
+
) -> dict[int, Mapping[str, JSONed]]:
|
1967
2168
|
...
|
1968
2169
|
|
1969
2170
|
@TimeIt.decorator
|
1970
|
-
def get_submissions(self) -> dict[int,
|
2171
|
+
def get_submissions(self) -> dict[int, Mapping[str, JSONed]]:
|
1971
2172
|
"""Retrieve all submissions, including pending."""
|
1972
2173
|
|
1973
2174
|
subs = self._get_persistent_submissions()
|
@@ -1977,7 +2178,113 @@ class PersistentStore(
|
|
1977
2178
|
return dict(sorted(subs.items()))
|
1978
2179
|
|
1979
2180
|
@TimeIt.decorator
|
1980
|
-
def
|
2181
|
+
def get_submission_at_submit_metadata(
|
2182
|
+
self, sub_idx: int, metadata_attr: dict[str, Any] | None
|
2183
|
+
) -> dict[str, Any]:
|
2184
|
+
"""Retrieve the values of submission attributes that are stored at submit-time.
|
2185
|
+
|
2186
|
+
Notes
|
2187
|
+
-----
|
2188
|
+
This method may need to be overridden if these attributes are stored separately
|
2189
|
+
from the remainder of the submission attributes.
|
2190
|
+
|
2191
|
+
"""
|
2192
|
+
return metadata_attr or {i: None for i in SUBMISSION_SUBMIT_TIME_KEYS}
|
2193
|
+
|
2194
|
+
@TimeIt.decorator
|
2195
|
+
def get_jobscript_at_submit_metadata(
|
2196
|
+
self,
|
2197
|
+
sub_idx: int,
|
2198
|
+
js_idx: int,
|
2199
|
+
metadata_attr: dict[str, Any] | None,
|
2200
|
+
) -> dict[str, Any]:
|
2201
|
+
"""For the specified jobscript, retrieve the values of jobscript-submit-time
|
2202
|
+
attributes.
|
2203
|
+
|
2204
|
+
Notes
|
2205
|
+
-----
|
2206
|
+
This method may need to be overridden if these jobscript-submit-time attributes
|
2207
|
+
are stored separately from the remainder of the jobscript attributes.
|
2208
|
+
|
2209
|
+
"""
|
2210
|
+
return metadata_attr or {i: None for i in JOBSCRIPT_SUBMIT_TIME_KEYS}
|
2211
|
+
|
2212
|
+
@TimeIt.decorator
|
2213
|
+
def get_jobscript_block_run_ID_array(
|
2214
|
+
self, sub_idx: int, js_idx: int, blk_idx: int, run_ID_arr: NDArray | None
|
2215
|
+
) -> NDArray:
|
2216
|
+
"""For the specified jobscript-block, retrieve the run ID array.
|
2217
|
+
|
2218
|
+
Notes
|
2219
|
+
-----
|
2220
|
+
This method may need to be overridden if these attributes are stored separately
|
2221
|
+
from the remainder of the submission attributes.
|
2222
|
+
|
2223
|
+
"""
|
2224
|
+
assert run_ID_arr is not None
|
2225
|
+
return np.asarray(run_ID_arr)
|
2226
|
+
|
2227
|
+
@TimeIt.decorator
|
2228
|
+
def get_jobscript_block_task_elements_map(
|
2229
|
+
self,
|
2230
|
+
sub_idx: int,
|
2231
|
+
js_idx: int,
|
2232
|
+
blk_idx: int,
|
2233
|
+
task_elems_map: dict[int, list[int]] | None,
|
2234
|
+
) -> dict[int, list[int]]:
|
2235
|
+
"""For the specified jobscript-block, retrieve the task-elements mapping.
|
2236
|
+
|
2237
|
+
Notes
|
2238
|
+
-----
|
2239
|
+
This method may need to be overridden if these attributes are stored separately
|
2240
|
+
from the remainder of the submission attributes.
|
2241
|
+
|
2242
|
+
"""
|
2243
|
+
assert task_elems_map is not None
|
2244
|
+
return task_elems_map
|
2245
|
+
|
2246
|
+
@TimeIt.decorator
|
2247
|
+
def get_jobscript_block_task_actions_array(
|
2248
|
+
self,
|
2249
|
+
sub_idx: int,
|
2250
|
+
js_idx: int,
|
2251
|
+
blk_idx: int,
|
2252
|
+
task_actions_arr: NDArray | list[tuple[int, int, int]] | None,
|
2253
|
+
) -> NDArray:
|
2254
|
+
"""For the specified jobscript-block, retrieve the task-actions array.
|
2255
|
+
|
2256
|
+
Notes
|
2257
|
+
-----
|
2258
|
+
This method may need to be overridden if these attributes are stored separately
|
2259
|
+
from the remainder of the submission attributes.
|
2260
|
+
|
2261
|
+
"""
|
2262
|
+
assert task_actions_arr is not None
|
2263
|
+
return np.asarray(task_actions_arr)
|
2264
|
+
|
2265
|
+
@TimeIt.decorator
|
2266
|
+
def get_jobscript_block_dependencies(
|
2267
|
+
self,
|
2268
|
+
sub_idx: int,
|
2269
|
+
js_idx: int,
|
2270
|
+
blk_idx: int,
|
2271
|
+
js_dependencies: dict[tuple[int, int], ResolvedJobscriptBlockDependencies] | None,
|
2272
|
+
) -> dict[tuple[int, int], ResolvedJobscriptBlockDependencies]:
|
2273
|
+
"""For the specified jobscript-block, retrieve the dependencies.
|
2274
|
+
|
2275
|
+
Notes
|
2276
|
+
-----
|
2277
|
+
This method may need to be overridden if these attributes are stored separately
|
2278
|
+
from the remainder of the submission attributes.
|
2279
|
+
|
2280
|
+
"""
|
2281
|
+
assert js_dependencies is not None
|
2282
|
+
return js_dependencies
|
2283
|
+
|
2284
|
+
@TimeIt.decorator
|
2285
|
+
def get_submissions_by_ID(
|
2286
|
+
self, ids: Iterable[int]
|
2287
|
+
) -> dict[int, Mapping[str, JSONed]]:
|
1981
2288
|
"""
|
1982
2289
|
Get submissions with the given IDs.
|
1983
2290
|
"""
|
@@ -2000,7 +2307,10 @@ class PersistentStore(
|
|
2000
2307
|
"""
|
2001
2308
|
# separate pending and persistent IDs:
|
2002
2309
|
ids, id_pers, id_pend = self.__split_pending(ids, self._pending.add_elements)
|
2003
|
-
self.logger.debug(
|
2310
|
+
self.logger.debug(
|
2311
|
+
f"PersistentStore.get_elements: {len(ids)} elements: "
|
2312
|
+
f"{shorten_list_str(ids)}."
|
2313
|
+
)
|
2004
2314
|
elems = self._get_persistent_elements(id_pers) if id_pers else {}
|
2005
2315
|
elems.update((id_, self._pending.add_elements[id_]) for id_ in id_pend)
|
2006
2316
|
|
@@ -2028,7 +2338,10 @@ class PersistentStore(
|
|
2028
2338
|
"""
|
2029
2339
|
# separate pending and persistent IDs:
|
2030
2340
|
ids, id_pers, id_pend = self.__split_pending(ids, self._pending.add_elem_iters)
|
2031
|
-
self.logger.debug(
|
2341
|
+
self.logger.debug(
|
2342
|
+
f"PersistentStore.get_element_iterations: {len(ids)} iterations: "
|
2343
|
+
f"{shorten_list_str(ids)}."
|
2344
|
+
)
|
2032
2345
|
iters = self._get_persistent_element_iters(id_pers) if id_pers else {}
|
2033
2346
|
iters.update((id_, self._pending.add_elem_iters[id_]) for id_ in id_pend)
|
2034
2347
|
|
@@ -2062,7 +2375,9 @@ class PersistentStore(
|
|
2062
2375
|
"""
|
2063
2376
|
# separate pending and persistent IDs:
|
2064
2377
|
ids, id_pers, id_pend = self.__split_pending(ids, self._pending.add_EARs)
|
2065
|
-
self.logger.debug(
|
2378
|
+
self.logger.debug(
|
2379
|
+
f"PersistentStore.get_EARs: {len(ids)} EARs: {shorten_list_str(ids)}."
|
2380
|
+
)
|
2066
2381
|
EARs = self._get_persistent_EARs(id_pers) if id_pers else {}
|
2067
2382
|
EARs.update((id_, self._pending.add_EARs[id_]) for id_ in id_pend)
|
2068
2383
|
|
@@ -2070,16 +2385,19 @@ class PersistentStore(
|
|
2070
2385
|
# order as requested:
|
2071
2386
|
for EAR_i in (EARs[id_] for id_ in ids):
|
2072
2387
|
# consider updates:
|
2073
|
-
updates: dict[str, Any] = {
|
2074
|
-
"submission_idx": self._pending.set_EAR_submission_indices.get(EAR_i.id_)
|
2075
|
-
}
|
2388
|
+
updates: dict[str, Any] = {}
|
2076
2389
|
if EAR_i.id_ in self._pending.set_EAR_skips:
|
2077
2390
|
updates["skip"] = True
|
2391
|
+
(
|
2392
|
+
updates["submission_idx"],
|
2393
|
+
updates["commands_file_ID"],
|
2394
|
+
) = self._pending.set_EAR_submission_data.get(EAR_i.id_, (None, None))
|
2078
2395
|
(
|
2079
2396
|
updates["start_time"],
|
2080
2397
|
updates["snapshot_start"],
|
2081
2398
|
updates["run_hostname"],
|
2082
|
-
|
2399
|
+
updates["port_number"],
|
2400
|
+
) = self._pending.set_EAR_starts.get(EAR_i.id_, (None, None, None, None))
|
2083
2401
|
(
|
2084
2402
|
updates["end_time"],
|
2085
2403
|
updates["snapshot_end"],
|
@@ -2138,7 +2456,7 @@ class PersistentStore(
|
|
2138
2456
|
) -> tuple[dict[int, AnySParameter], list[int]]:
|
2139
2457
|
return self.__get_cached_persistent_items(id_lst, self.parameter_cache)
|
2140
2458
|
|
2141
|
-
def get_EAR_skipped(self, EAR_ID: int) ->
|
2459
|
+
def get_EAR_skipped(self, EAR_ID: int) -> int:
|
2142
2460
|
"""
|
2143
2461
|
Whether the element action run with the given ID was skipped.
|
2144
2462
|
"""
|
@@ -2294,12 +2612,12 @@ class PersistentStore(
|
|
2294
2612
|
...
|
2295
2613
|
|
2296
2614
|
@abstractmethod
|
2297
|
-
def _append_submissions(self, subs: dict[int,
|
2615
|
+
def _append_submissions(self, subs: dict[int, Mapping[str, JSONed]]) -> None:
|
2298
2616
|
...
|
2299
2617
|
|
2300
2618
|
@abstractmethod
|
2301
|
-
def
|
2302
|
-
self,
|
2619
|
+
def _update_at_submit_metadata(
|
2620
|
+
self, at_submit_metadata: dict[int, dict[str, Any]]
|
2303
2621
|
) -> None:
|
2304
2622
|
...
|
2305
2623
|
|
@@ -2334,28 +2652,24 @@ class PersistentStore(
|
|
2334
2652
|
...
|
2335
2653
|
|
2336
2654
|
@abstractmethod
|
2337
|
-
def
|
2655
|
+
def _update_EAR_submission_data(self, sub_data: Mapping[int, tuple[int, int | None]]):
|
2338
2656
|
...
|
2339
2657
|
|
2340
2658
|
@abstractmethod
|
2341
2659
|
def _update_EAR_start(
|
2342
|
-
self,
|
2660
|
+
self,
|
2661
|
+
run_starts: dict[int, tuple[datetime, dict[str, Any] | None, str, int | None]],
|
2343
2662
|
) -> None:
|
2344
2663
|
...
|
2345
2664
|
|
2346
2665
|
@abstractmethod
|
2347
2666
|
def _update_EAR_end(
|
2348
|
-
self,
|
2349
|
-
EAR_id: int,
|
2350
|
-
e_time: datetime,
|
2351
|
-
e_snap: dict[str, Any],
|
2352
|
-
ext_code: int,
|
2353
|
-
success: bool,
|
2667
|
+
self, run_ends: dict[int, tuple[datetime, dict[str, Any] | None, int, bool]]
|
2354
2668
|
) -> None:
|
2355
2669
|
...
|
2356
2670
|
|
2357
2671
|
@abstractmethod
|
2358
|
-
def _update_EAR_skip(self,
|
2672
|
+
def _update_EAR_skip(self, skips: dict[int, int]) -> None:
|
2359
2673
|
...
|
2360
2674
|
|
2361
2675
|
@abstractmethod
|
@@ -2375,7 +2689,7 @@ class PersistentStore(
|
|
2375
2689
|
...
|
2376
2690
|
|
2377
2691
|
@abstractmethod
|
2378
|
-
def _update_loop_index(self,
|
2692
|
+
def _update_loop_index(self, loop_indices: dict[int, dict[str, int]]) -> None:
|
2379
2693
|
...
|
2380
2694
|
|
2381
2695
|
@abstractmethod
|
@@ -2397,7 +2711,7 @@ class PersistentStore(
|
|
2397
2711
|
@overload
|
2398
2712
|
def using_resource(
|
2399
2713
|
self, res_label: Literal["submissions"], action: str
|
2400
|
-
) -> AbstractContextManager[list[
|
2714
|
+
) -> AbstractContextManager[list[dict[str, JSONed]]]:
|
2401
2715
|
...
|
2402
2716
|
|
2403
2717
|
@overload
|
@@ -2406,6 +2720,12 @@ class PersistentStore(
|
|
2406
2720
|
) -> AbstractContextManager[dict[str, dict[str, Any]]]:
|
2407
2721
|
...
|
2408
2722
|
|
2723
|
+
@overload
|
2724
|
+
def using_resource(
|
2725
|
+
self, res_label: Literal["runs"], action: str
|
2726
|
+
) -> AbstractContextManager[dict[str, Any]]:
|
2727
|
+
...
|
2728
|
+
|
2409
2729
|
@overload
|
2410
2730
|
def using_resource(
|
2411
2731
|
self, res_label: Literal["attrs"], action: str
|
@@ -2413,7 +2733,11 @@ class PersistentStore(
|
|
2413
2733
|
...
|
2414
2734
|
|
2415
2735
|
@contextlib.contextmanager
|
2416
|
-
def using_resource(
|
2736
|
+
def using_resource(
|
2737
|
+
self,
|
2738
|
+
res_label: Literal["metadata", "submissions", "parameters", "attrs", "runs"],
|
2739
|
+
action: str,
|
2740
|
+
) -> Iterator[Any]:
|
2417
2741
|
"""Context manager for managing `StoreResource` objects associated with the store."""
|
2418
2742
|
|
2419
2743
|
try:
|
@@ -2485,6 +2809,34 @@ class PersistentStore(
|
|
2485
2809
|
|
2486
2810
|
return _delete_no_confirm()
|
2487
2811
|
|
2812
|
+
def get_text_file(self, path: str | Path) -> str:
|
2813
|
+
"""Retrieve the contents of a text file stored within the workflow.
|
2814
|
+
|
2815
|
+
Parameters
|
2816
|
+
----------
|
2817
|
+
path
|
2818
|
+
The path to a text file stored within the workflow. This can either be an
|
2819
|
+
absolute path or a path that is relative to the workflow root.
|
2820
|
+
"""
|
2821
|
+
path = Path(path)
|
2822
|
+
if not path.is_absolute():
|
2823
|
+
path = Path(self.path).joinpath(path)
|
2824
|
+
if not path.is_file():
|
2825
|
+
raise FileNotFoundError(f"File at location {path!r} does not exist.")
|
2826
|
+
return path.read_text()
|
2827
|
+
|
2488
2828
|
@abstractmethod
|
2489
2829
|
def _append_task_element_IDs(self, task_ID: int, elem_IDs: list[int]):
|
2490
2830
|
raise NotImplementedError
|
2831
|
+
|
2832
|
+
@abstractmethod
|
2833
|
+
def _set_run_dirs(self, run_dir_arr: np.ndarray, run_idx: np.ndarray) -> None:
|
2834
|
+
...
|
2835
|
+
|
2836
|
+
@abstractmethod
|
2837
|
+
def _update_iter_data_indices(self, iter_data_indices: dict[int, DataIndex]) -> None:
|
2838
|
+
...
|
2839
|
+
|
2840
|
+
@abstractmethod
|
2841
|
+
def _update_run_data_indices(self, run_data_indices: dict[int, DataIndex]) -> None:
|
2842
|
+
...
|