experimaestro 1.5.1__py3-none-any.whl → 2.0.0a8__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.
Potentially problematic release.
This version of experimaestro might be problematic. Click here for more details.
- experimaestro/__init__.py +14 -4
- experimaestro/__main__.py +3 -423
- experimaestro/annotations.py +14 -4
- experimaestro/cli/__init__.py +311 -0
- experimaestro/{filter.py → cli/filter.py} +23 -9
- experimaestro/cli/jobs.py +268 -0
- experimaestro/cli/progress.py +269 -0
- experimaestro/click.py +0 -35
- experimaestro/commandline.py +3 -7
- experimaestro/connectors/__init__.py +29 -14
- experimaestro/connectors/local.py +19 -10
- experimaestro/connectors/ssh.py +27 -8
- experimaestro/core/arguments.py +45 -3
- experimaestro/core/callbacks.py +52 -0
- experimaestro/core/context.py +8 -9
- experimaestro/core/identifier.py +310 -0
- experimaestro/core/objects/__init__.py +44 -0
- experimaestro/core/{objects.py → objects/config.py} +399 -772
- experimaestro/core/objects/config_utils.py +58 -0
- experimaestro/core/objects/config_walk.py +151 -0
- experimaestro/core/objects.pyi +15 -45
- experimaestro/core/serialization.py +63 -9
- experimaestro/core/serializers.py +1 -8
- experimaestro/core/types.py +104 -66
- experimaestro/experiments/cli.py +154 -72
- experimaestro/experiments/configuration.py +10 -1
- experimaestro/generators.py +6 -1
- experimaestro/ipc.py +4 -1
- experimaestro/launcherfinder/__init__.py +1 -1
- experimaestro/launcherfinder/base.py +2 -18
- experimaestro/launcherfinder/parser.py +8 -3
- experimaestro/launcherfinder/registry.py +52 -140
- experimaestro/launcherfinder/specs.py +49 -10
- experimaestro/launchers/direct.py +0 -47
- experimaestro/launchers/slurm/base.py +54 -14
- experimaestro/mkdocs/__init__.py +1 -1
- experimaestro/mkdocs/base.py +6 -8
- experimaestro/notifications.py +38 -12
- experimaestro/progress.py +406 -0
- experimaestro/run.py +24 -3
- experimaestro/scheduler/__init__.py +18 -1
- experimaestro/scheduler/base.py +108 -808
- experimaestro/scheduler/dynamic_outputs.py +184 -0
- experimaestro/scheduler/experiment.py +387 -0
- experimaestro/scheduler/jobs.py +475 -0
- experimaestro/scheduler/signal_handler.py +32 -0
- experimaestro/scheduler/state.py +75 -0
- experimaestro/scheduler/workspace.py +27 -8
- experimaestro/scriptbuilder.py +18 -3
- experimaestro/server/__init__.py +36 -5
- experimaestro/server/data/1815e00441357e01619e.ttf +0 -0
- experimaestro/server/data/2463b90d9a316e4e5294.woff2 +0 -0
- experimaestro/server/data/2582b0e4bcf85eceead0.ttf +0 -0
- experimaestro/server/data/89999bdf5d835c012025.woff2 +0 -0
- experimaestro/server/data/914997e1bdfc990d0897.ttf +0 -0
- experimaestro/server/data/c210719e60948b211a12.woff2 +0 -0
- experimaestro/server/data/index.css +5187 -5068
- experimaestro/server/data/index.css.map +1 -1
- experimaestro/server/data/index.js +68887 -68064
- experimaestro/server/data/index.js.map +1 -1
- experimaestro/settings.py +45 -5
- experimaestro/sphinx/__init__.py +7 -17
- experimaestro/taskglobals.py +7 -2
- experimaestro/tests/core/__init__.py +0 -0
- experimaestro/tests/core/test_generics.py +206 -0
- experimaestro/tests/definitions_types.py +5 -3
- experimaestro/tests/launchers/bin/sbatch +34 -7
- experimaestro/tests/launchers/bin/srun +5 -0
- experimaestro/tests/launchers/common.py +17 -5
- experimaestro/tests/launchers/config_slurm/launchers.py +25 -0
- experimaestro/tests/restart.py +10 -5
- experimaestro/tests/tasks/all.py +23 -10
- experimaestro/tests/tasks/foreign.py +2 -4
- experimaestro/tests/test_checkers.py +2 -2
- experimaestro/tests/test_dependencies.py +11 -17
- experimaestro/tests/test_experiment.py +73 -0
- experimaestro/tests/test_file_progress.py +425 -0
- experimaestro/tests/test_file_progress_integration.py +477 -0
- experimaestro/tests/test_findlauncher.py +12 -5
- experimaestro/tests/test_forward.py +5 -5
- experimaestro/tests/test_generators.py +93 -0
- experimaestro/tests/test_identifier.py +182 -158
- experimaestro/tests/test_instance.py +19 -27
- experimaestro/tests/test_objects.py +13 -20
- experimaestro/tests/test_outputs.py +6 -6
- experimaestro/tests/test_param.py +68 -30
- experimaestro/tests/test_progress.py +4 -4
- experimaestro/tests/test_serializers.py +24 -64
- experimaestro/tests/test_ssh.py +7 -0
- experimaestro/tests/test_tags.py +50 -21
- experimaestro/tests/test_tasks.py +42 -51
- experimaestro/tests/test_tokens.py +11 -8
- experimaestro/tests/test_types.py +24 -21
- experimaestro/tests/test_validation.py +67 -110
- experimaestro/tests/token_reschedule.py +1 -1
- experimaestro/tokens.py +24 -13
- experimaestro/tools/diff.py +8 -1
- experimaestro/typingutils.py +20 -11
- experimaestro/utils/asyncio.py +6 -2
- experimaestro/utils/multiprocessing.py +44 -0
- experimaestro/utils/resources.py +11 -3
- {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/METADATA +28 -36
- experimaestro-2.0.0a8.dist-info/RECORD +166 -0
- {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/WHEEL +1 -1
- {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/entry_points.txt +0 -4
- experimaestro/launchers/slurm/cli.py +0 -29
- experimaestro/launchers/slurm/configuration.py +0 -597
- experimaestro/scheduler/environment.py +0 -94
- experimaestro/server/data/016b4a6cdced82ab3aa1.ttf +0 -0
- experimaestro/server/data/50701fbb8177c2dde530.ttf +0 -0
- experimaestro/server/data/878f31251d960bd6266f.woff2 +0 -0
- experimaestro/server/data/b041b1fa4fe241b23445.woff2 +0 -0
- experimaestro/server/data/b6879d41b0852f01ed5b.woff2 +0 -0
- experimaestro/server/data/d75e3fd1eb12e9bd6655.ttf +0 -0
- experimaestro/tests/launchers/config_slurm/launchers.yaml +0 -134
- experimaestro/utils/yaml.py +0 -202
- experimaestro-1.5.1.dist-info/RECORD +0 -148
- {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from typing import Any, Dict, Set
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def getqualattr(module, qualname):
|
|
6
|
+
"""Get a qualified attributed value"""
|
|
7
|
+
cls = module
|
|
8
|
+
for part in qualname.split("."):
|
|
9
|
+
cls = getattr(cls, part)
|
|
10
|
+
return cls
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@contextmanager
|
|
14
|
+
def add_to_path(p):
|
|
15
|
+
"""Temporarily add a path to sys.path"""
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
old_path = sys.path
|
|
19
|
+
sys.path = sys.path[:]
|
|
20
|
+
sys.path.insert(0, p)
|
|
21
|
+
try:
|
|
22
|
+
yield
|
|
23
|
+
finally:
|
|
24
|
+
sys.path = old_path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ObjectStore:
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.store: Dict[int, Any] = {}
|
|
30
|
+
self.constructed: Set[int] = set()
|
|
31
|
+
|
|
32
|
+
def set_constructed(self, identifier: int):
|
|
33
|
+
self.constructed.add(identifier)
|
|
34
|
+
|
|
35
|
+
def is_constructed(self, identifier: int):
|
|
36
|
+
return identifier in self.constructed
|
|
37
|
+
|
|
38
|
+
def retrieve(self, identifier: int):
|
|
39
|
+
return self.store.get(identifier, None)
|
|
40
|
+
|
|
41
|
+
def add_stub(self, identifier: int, stub: Any):
|
|
42
|
+
self.store[identifier] = stub
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SealedError(Exception):
|
|
46
|
+
"""Exception when trying to modify a sealed configuration"""
|
|
47
|
+
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TaggedValue:
|
|
52
|
+
def __init__(self, value):
|
|
53
|
+
self.value = value
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class classproperty(property):
|
|
57
|
+
def __get__(self, owner_self, owner_cls):
|
|
58
|
+
return self.fget(owner_cls)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import (
|
|
4
|
+
Any,
|
|
5
|
+
Dict,
|
|
6
|
+
Tuple,
|
|
7
|
+
)
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ConfigWalkContext:
|
|
12
|
+
"""Context when generating values in configurations"""
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def path(self):
|
|
16
|
+
"""Returns the path of the job directory"""
|
|
17
|
+
raise NotImplementedError()
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self._configpath = None
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def task(self):
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
def currentpath(self) -> Path:
|
|
27
|
+
"""Returns the configuration folder"""
|
|
28
|
+
if self._configpath:
|
|
29
|
+
return self.path / self._configpath
|
|
30
|
+
return self.path
|
|
31
|
+
|
|
32
|
+
@contextmanager
|
|
33
|
+
def push(self, key: str):
|
|
34
|
+
"""Push a new key to contextualize paths"""
|
|
35
|
+
p = self._configpath
|
|
36
|
+
try:
|
|
37
|
+
self._configpath = (Path("out") if p is None else p) / key
|
|
38
|
+
yield key
|
|
39
|
+
finally:
|
|
40
|
+
self._configpath = p
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ConfigWalk:
|
|
44
|
+
"""Allows to perform an operation on all nested configurations"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, context: ConfigWalkContext = None, recurse_task=False):
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
:param recurse_task: Recurse into linked tasks
|
|
50
|
+
:param context: The context, by default only tracks the position in the
|
|
51
|
+
config tree
|
|
52
|
+
"""
|
|
53
|
+
self.recurse_task = recurse_task
|
|
54
|
+
self.context = ConfigWalkContext() if context is None else context
|
|
55
|
+
|
|
56
|
+
# Stores already visited nodes
|
|
57
|
+
self.visited = {}
|
|
58
|
+
|
|
59
|
+
def preprocess(self, config) -> Tuple[bool, Any]:
|
|
60
|
+
"""Returns a tuple boolean/value
|
|
61
|
+
|
|
62
|
+
The boolean value is used to stop the processing if False.
|
|
63
|
+
The value is returned
|
|
64
|
+
"""
|
|
65
|
+
return True, None
|
|
66
|
+
|
|
67
|
+
def postprocess(self, stub, config, values: Dict[str, Any]):
|
|
68
|
+
return stub
|
|
69
|
+
|
|
70
|
+
def list(self, i: int):
|
|
71
|
+
return self.context.push(str(i))
|
|
72
|
+
|
|
73
|
+
def map(self, k: str):
|
|
74
|
+
"""Provides a path context when processing a tree"""
|
|
75
|
+
return self.context.push(k)
|
|
76
|
+
|
|
77
|
+
def stub(self, config):
|
|
78
|
+
return config
|
|
79
|
+
|
|
80
|
+
def __call__(self, x):
|
|
81
|
+
from experimaestro.core.objects import Config
|
|
82
|
+
from experimaestro.core.objects import ConfigInformation # noqa: F401
|
|
83
|
+
|
|
84
|
+
if isinstance(x, Config):
|
|
85
|
+
info = x.__xpm__ # type: ConfigInformation
|
|
86
|
+
|
|
87
|
+
# Avoid loops
|
|
88
|
+
xid = id(x)
|
|
89
|
+
if xid in self.visited:
|
|
90
|
+
return self.visited[xid]
|
|
91
|
+
|
|
92
|
+
# Get a stub
|
|
93
|
+
stub = self.stub(x)
|
|
94
|
+
self.visited[xid] = stub
|
|
95
|
+
|
|
96
|
+
# Pre-process
|
|
97
|
+
flag, value = self.preprocess(x)
|
|
98
|
+
|
|
99
|
+
if not flag:
|
|
100
|
+
# Stop processing and returns value
|
|
101
|
+
return value
|
|
102
|
+
|
|
103
|
+
# Process all the arguments
|
|
104
|
+
result = {}
|
|
105
|
+
for arg, v in info.xpmvalues():
|
|
106
|
+
if v is not None:
|
|
107
|
+
with self.map(arg.name):
|
|
108
|
+
result[arg.name] = self(v)
|
|
109
|
+
else:
|
|
110
|
+
result[arg.name] = None
|
|
111
|
+
|
|
112
|
+
# Deals with init tasks
|
|
113
|
+
if info.init_tasks:
|
|
114
|
+
with self.map("__init_tasks__"):
|
|
115
|
+
self(info.init_tasks)
|
|
116
|
+
|
|
117
|
+
# Process task if different
|
|
118
|
+
if (
|
|
119
|
+
x.__xpm__.task is not None
|
|
120
|
+
and self.recurse_task
|
|
121
|
+
and x.__xpm__.task is not x
|
|
122
|
+
):
|
|
123
|
+
with self.map("__task__"):
|
|
124
|
+
self(x.__xpm__.task)
|
|
125
|
+
|
|
126
|
+
processed = self.postprocess(stub, x, result)
|
|
127
|
+
self.visited[xid] = processed
|
|
128
|
+
return processed
|
|
129
|
+
|
|
130
|
+
if x is None:
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
if isinstance(x, list):
|
|
134
|
+
result = []
|
|
135
|
+
for i, sv in enumerate(x):
|
|
136
|
+
with self.list(i):
|
|
137
|
+
result.append(self(sv))
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
if isinstance(x, dict):
|
|
141
|
+
result = {}
|
|
142
|
+
for key, value in x.items():
|
|
143
|
+
assert isinstance(key, (str, float, int))
|
|
144
|
+
with self.map(key):
|
|
145
|
+
result[key] = self(value)
|
|
146
|
+
return result
|
|
147
|
+
|
|
148
|
+
if isinstance(x, (float, int, str, Path, Enum)):
|
|
149
|
+
return x
|
|
150
|
+
|
|
151
|
+
raise NotImplementedError(f"Cannot handle a value of type {type(x)}")
|
experimaestro/core/objects.pyi
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from abc import ABC
|
|
2
|
+
from attrs import define
|
|
2
3
|
import typing_extensions
|
|
3
4
|
|
|
4
5
|
from experimaestro.core.types import ObjectType
|
|
@@ -40,40 +41,8 @@ from typing_extensions import Self
|
|
|
40
41
|
|
|
41
42
|
TConfig = TypeVar("TConfig", bound="Config")
|
|
42
43
|
|
|
43
|
-
class Identifier:
|
|
44
|
-
main: Incomplete
|
|
45
|
-
sub: Incomplete
|
|
46
|
-
def __init__(self, main: bytes, sub: Optional[bytes] = ...) -> None: ...
|
|
47
|
-
def all(self): ...
|
|
48
|
-
def state_dict(self): ...
|
|
49
|
-
@staticmethod
|
|
50
|
-
def from_state_dict(data: Union[Dict[str, str], str]): ...
|
|
51
|
-
|
|
52
|
-
def is_ignored(value): ...
|
|
53
|
-
def remove_meta(value): ...
|
|
54
|
-
|
|
55
44
|
class ObjectStore: ...
|
|
56
45
|
|
|
57
|
-
class HashComputer:
|
|
58
|
-
OBJECT_ID: bytes
|
|
59
|
-
INT_ID: bytes
|
|
60
|
-
FLOAT_ID: bytes
|
|
61
|
-
STR_ID: bytes
|
|
62
|
-
PATH_ID: bytes
|
|
63
|
-
NAME_ID: bytes
|
|
64
|
-
NONE_ID: bytes
|
|
65
|
-
LIST_ID: bytes
|
|
66
|
-
TASK_ID: bytes
|
|
67
|
-
DICT_ID: bytes
|
|
68
|
-
ENUM_ID: bytes
|
|
69
|
-
def __init__(self) -> None: ...
|
|
70
|
-
def identifier(self) -> Identifier: ...
|
|
71
|
-
def update(self, value, myself: bool = ...): ...
|
|
72
|
-
|
|
73
|
-
def updatedependencies(
|
|
74
|
-
dependencies, value: Config, path: List[str], taskids: Set[int]
|
|
75
|
-
): ...
|
|
76
|
-
|
|
77
46
|
class TaggedValue:
|
|
78
47
|
value: Incomplete
|
|
79
48
|
def __init__(self, value) -> None: ...
|
|
@@ -108,6 +77,10 @@ class ConfigWalk(ConfigProcessing):
|
|
|
108
77
|
def map(self, k: str): ...
|
|
109
78
|
|
|
110
79
|
def getqualattr(module, qualname): ...
|
|
80
|
+
@define(frozen=True)
|
|
81
|
+
class WatchedOutput:
|
|
82
|
+
config: "Config"
|
|
83
|
+
method_name: str
|
|
111
84
|
|
|
112
85
|
class ConfigInformation:
|
|
113
86
|
LOADING: bool
|
|
@@ -116,7 +89,8 @@ class ConfigInformation:
|
|
|
116
89
|
values: Dict[str, Any]
|
|
117
90
|
job: Job
|
|
118
91
|
dependencies: Incomplete
|
|
119
|
-
|
|
92
|
+
watched_outputs: List[WatchedOutput]
|
|
93
|
+
def __init__(self, pyobject: ConfigMixin) -> None: ...
|
|
120
94
|
def set_meta(self, value: Optional[bool]): ...
|
|
121
95
|
@property
|
|
122
96
|
def meta(self): ...
|
|
@@ -156,7 +130,7 @@ class ConfigInformation:
|
|
|
156
130
|
definitions: List[Dict],
|
|
157
131
|
as_instance: bool = ...,
|
|
158
132
|
save_directory: Optional[Path] = ...,
|
|
159
|
-
) ->
|
|
133
|
+
) -> ConfigMixin: ...
|
|
160
134
|
@overload
|
|
161
135
|
@staticmethod
|
|
162
136
|
def fromParameters(
|
|
@@ -177,7 +151,7 @@ class ConfigInformation:
|
|
|
177
151
|
def clone(v): ...
|
|
178
152
|
def cache(fn, name: str): ...
|
|
179
153
|
|
|
180
|
-
class
|
|
154
|
+
class ConfigMixin:
|
|
181
155
|
__xpmtype__: ObjectType
|
|
182
156
|
__xpm__: Incomplete
|
|
183
157
|
def __init__(self, **kwargs) -> None: ...
|
|
@@ -194,7 +168,7 @@ class TypeConfig:
|
|
|
194
168
|
*,
|
|
195
169
|
workspace: Incomplete | None = ...,
|
|
196
170
|
launcher: Incomplete | None = ...,
|
|
197
|
-
run_mode: RunMode =
|
|
171
|
+
run_mode: RunMode = ...,
|
|
198
172
|
): ...
|
|
199
173
|
def stdout(self): ...
|
|
200
174
|
def stderr(self): ...
|
|
@@ -211,8 +185,8 @@ class Config:
|
|
|
211
185
|
__use_xpmobject__: ClassVar[bool]
|
|
212
186
|
|
|
213
187
|
XPMValue: Type[Self]
|
|
214
|
-
XPMConfig: Union[Type[Self], Type[
|
|
215
|
-
C: Union[Type[Self], Type[
|
|
188
|
+
XPMConfig: Union[Type[Self], Type[ConfigMixin[Self]]]
|
|
189
|
+
C: Union[Type[Self], Type[ConfigMixin[Self]]]
|
|
216
190
|
|
|
217
191
|
@classmethod
|
|
218
192
|
def __getxpmtype__(cls) -> ObjectType: ...
|
|
@@ -221,17 +195,13 @@ class Config:
|
|
|
221
195
|
def __post_init__(self) -> None: ...
|
|
222
196
|
def __json__(self): ...
|
|
223
197
|
def __identifier__(self) -> Identifier: ...
|
|
224
|
-
def add_pretasks(self, *tasks: "LightweightTask"): ...
|
|
225
|
-
def add_pretasks_from(self, configs: "Config"): ...
|
|
226
198
|
def copy_dependencies(self, other: "Config"): ...
|
|
227
|
-
@property
|
|
228
|
-
def pre_tasks(self) -> List["LightweightTask"]: ...
|
|
229
199
|
|
|
230
200
|
class LightweightTask(Config):
|
|
231
201
|
def execute(self) -> None: ...
|
|
232
202
|
|
|
233
203
|
class Task(LightweightTask):
|
|
234
|
-
__tags__: Dict[str, str]
|
|
204
|
+
# __tags__: Dict[str, str]
|
|
235
205
|
|
|
236
206
|
def submit(
|
|
237
207
|
self,
|
|
@@ -239,13 +209,13 @@ class Task(LightweightTask):
|
|
|
239
209
|
workspace: Incomplete | None = ...,
|
|
240
210
|
launcher: Incomplete | None = ...,
|
|
241
211
|
run_mode: RunMode = ...,
|
|
242
|
-
init_tasks: List["LightweightTask"] = []
|
|
212
|
+
init_tasks: List["LightweightTask"] = [],
|
|
243
213
|
): ...
|
|
244
214
|
def task_outputs(self, dep: Callable[[Config], None]) -> Any: ...
|
|
245
215
|
|
|
246
216
|
def copyconfig(config_or_output: TConfig, **kwargs) -> TConfig: ...
|
|
247
217
|
def setmeta(config: TConfig, flag: bool) -> TConfig: ...
|
|
248
218
|
|
|
249
|
-
class
|
|
219
|
+
class ConfigMixin(Generic[T]):
|
|
250
220
|
def __validate__(self):
|
|
251
221
|
pass
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Any, Dict, List, Tuple, Union, TYPE_CHECKING
|
|
3
|
+
from typing import Any, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
|
|
4
4
|
from experimaestro.core.context import (
|
|
5
5
|
SerializationContext,
|
|
6
6
|
SerializedPath,
|
|
@@ -17,7 +17,7 @@ def json_object(context: SerializationContext, value: Any, objects=[]):
|
|
|
17
17
|
|
|
18
18
|
if isinstance(value, Config):
|
|
19
19
|
value.__xpm__.__get_objects__(objects, context)
|
|
20
|
-
elif isinstance(value, list):
|
|
20
|
+
elif isinstance(value, (list, tuple)):
|
|
21
21
|
for el in value:
|
|
22
22
|
ConfigInformation.__collect_objects__(el, objects, context)
|
|
23
23
|
elif isinstance(value, dict):
|
|
@@ -36,28 +36,38 @@ def state_dict(context: SerializationContext, obj: Any):
|
|
|
36
36
|
:param context: The serialization context
|
|
37
37
|
:param obj: the object to serialize
|
|
38
38
|
"""
|
|
39
|
-
objects = []
|
|
39
|
+
objects: list[Any] = []
|
|
40
40
|
data = json_object(context, obj, objects)
|
|
41
41
|
return {"objects": objects, "data": data}
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def
|
|
44
|
+
def save_definition(obj: Any, context: SerializationContext, path: Path):
|
|
45
|
+
data = state_dict(context, obj)
|
|
46
|
+
with path.open("wt") as out:
|
|
47
|
+
json.dump(data, out)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def save(obj: Any, save_directory: Optional[Path]):
|
|
45
51
|
"""Saves an object into a disk file
|
|
46
52
|
|
|
53
|
+
The serialization process also stores in the given folder the different
|
|
54
|
+
files or folders that are registered as Path parameters (or
|
|
55
|
+
meta-parameters).
|
|
56
|
+
|
|
47
57
|
:param save_directory: The directory in which the object and its data will
|
|
48
58
|
be saved (by default, the object is saved in "definition.json")
|
|
49
59
|
"""
|
|
50
60
|
context = SerializationContext(save_directory=save_directory)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
json.dump(data, out)
|
|
61
|
+
save_definition(
|
|
62
|
+
obj, context, save_directory / "definition.json" if save_directory else None
|
|
63
|
+
)
|
|
55
64
|
|
|
56
65
|
|
|
57
66
|
def get_data_loader(path: Union[str, Path, SerializedPathLoader]):
|
|
58
67
|
if path is None:
|
|
59
68
|
|
|
60
|
-
def data_loader():
|
|
69
|
+
def data_loader(_: Union[str, Path, SerializedPathLoader]):
|
|
70
|
+
# Just raise an exception
|
|
61
71
|
raise RuntimeError("No serialization path was given")
|
|
62
72
|
|
|
63
73
|
return data_loader
|
|
@@ -126,3 +136,47 @@ def from_task_dir(
|
|
|
126
136
|
content["data"] = {"type": "python", "value": content["objects"][-1]["id"]}
|
|
127
137
|
|
|
128
138
|
return from_state_dict(content, as_instance=as_instance)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def serialize(
|
|
142
|
+
obj: Any, save_directory: Path, *, init_tasks: list["LightweightTask"] = []
|
|
143
|
+
):
|
|
144
|
+
"""Saves an object into a disk file, including initialization tasks
|
|
145
|
+
|
|
146
|
+
The serialization process also stores in the given folder the different
|
|
147
|
+
files or folders that are registered as Path parameters (or
|
|
148
|
+
meta-parameters).
|
|
149
|
+
|
|
150
|
+
:param save_directory: The directory in which the object and its data will
|
|
151
|
+
be saved (by default, the object is saved in "definition.json")
|
|
152
|
+
:param init_tasks: The optional
|
|
153
|
+
"""
|
|
154
|
+
context = SerializationContext(save_directory=save_directory)
|
|
155
|
+
save_definition((obj, init_tasks), context, save_directory / "definition.json")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def deserialize(
|
|
159
|
+
path: Union[str, Path, SerializedPathLoader],
|
|
160
|
+
as_instance: bool = False,
|
|
161
|
+
) -> tuple[Any, List["LightweightTask"]] | Any:
|
|
162
|
+
"""Load data from disk, and initialize the object
|
|
163
|
+
|
|
164
|
+
:param path: A directory or a function that transforms relative file path
|
|
165
|
+
into absolute ones
|
|
166
|
+
:param as_instance: returns instances instead of configuration objects
|
|
167
|
+
:returns: either the object (as_instance is true), or a tuple
|
|
168
|
+
"""
|
|
169
|
+
data_loader = get_data_loader(path)
|
|
170
|
+
|
|
171
|
+
with data_loader("definition.json").open("rt") as fh:
|
|
172
|
+
content = json.load(fh)
|
|
173
|
+
|
|
174
|
+
object, init_tasks = from_state_dict(content, data_loader, as_instance=as_instance)
|
|
175
|
+
|
|
176
|
+
if as_instance:
|
|
177
|
+
for init_task in init_tasks:
|
|
178
|
+
init_task.execute()
|
|
179
|
+
|
|
180
|
+
return object
|
|
181
|
+
|
|
182
|
+
return object, init_tasks
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
from typing import List, TypeVar
|
|
2
|
-
from pathlib import Path
|
|
1
|
+
from typing import List, TypeVar
|
|
3
2
|
from experimaestro import Param
|
|
4
3
|
|
|
5
4
|
from .objects import Config, LightweightTask
|
|
6
5
|
from .arguments import DataPath
|
|
7
|
-
from experimaestro import copyconfig
|
|
8
6
|
|
|
9
7
|
|
|
10
8
|
class SerializationLWTask(LightweightTask):
|
|
@@ -39,8 +37,3 @@ class PathSerializationLWTask(SerializationLWTask):
|
|
|
39
37
|
|
|
40
38
|
path: DataPath
|
|
41
39
|
"""Path containing the data"""
|
|
42
|
-
|
|
43
|
-
@classmethod
|
|
44
|
-
def construct(cls, value: T, path: Path, dep: Callable[[Config], Any]) -> T:
|
|
45
|
-
value = copyconfig(value)
|
|
46
|
-
return value.add_pretasks(dep(cls(value=value, path=path)))
|