experimaestro 1.10.0__py3-none-any.whl → 1.16.0__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.
- experimaestro/cli/__init__.py +2 -2
- experimaestro/cli/filter.py +1 -1
- experimaestro/connectors/__init__.py +2 -2
- experimaestro/core/arguments.py +11 -8
- experimaestro/core/identifier.py +11 -6
- experimaestro/core/objects/config.py +127 -193
- experimaestro/core/objects/config_walk.py +4 -6
- experimaestro/core/objects.pyi +2 -6
- experimaestro/core/serializers.py +1 -8
- experimaestro/core/types.py +1 -4
- experimaestro/launcherfinder/registry.py +6 -6
- experimaestro/launcherfinder/specs.py +8 -1
- experimaestro/launchers/slurm/base.py +1 -1
- experimaestro/run.py +2 -0
- experimaestro/scheduler/base.py +0 -2
- experimaestro/scheduler/workspace.py +44 -1
- experimaestro/server/__init__.py +12 -6
- experimaestro/server/data/0c35d18bf06992036b69.woff2 +0 -0
- experimaestro/server/data/1815e00441357e01619e.ttf +0 -0
- experimaestro/server/data/219aa9140e099e6c72ed.woff2 +0 -0
- experimaestro/server/data/2463b90d9a316e4e5294.woff2 +0 -0
- experimaestro/server/data/2582b0e4bcf85eceead0.ttf +0 -0
- experimaestro/server/data/3a4004a46a653d4b2166.woff +0 -0
- experimaestro/server/data/3baa5b8f3469222b822d.woff +0 -0
- experimaestro/server/data/4d73cb90e394b34b7670.woff +0 -0
- experimaestro/server/data/4ef4218c522f1eb6b5b1.woff2 +0 -0
- experimaestro/server/data/5d681e2edae8c60630db.woff +0 -0
- experimaestro/server/data/6f420cf17cc0d7676fad.woff2 +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/c380809fd3677d7d6903.woff2 +0 -0
- experimaestro/server/data/f882956fd323fd322f31.woff +0 -0
- experimaestro/server/data/favicon.ico +0 -0
- experimaestro/server/data/index.css +22963 -0
- experimaestro/server/data/index.css.map +1 -0
- experimaestro/server/data/index.html +27 -0
- experimaestro/server/data/index.js +101770 -0
- experimaestro/server/data/index.js.map +1 -0
- experimaestro/server/data/login.html +22 -0
- experimaestro/server/data/manifest.json +15 -0
- experimaestro/tests/tasks/all.py +7 -0
- experimaestro/tests/test_dependencies.py +0 -6
- experimaestro/tests/test_generators.py +93 -0
- experimaestro/tests/test_identifier.py +87 -76
- experimaestro/tests/test_instance.py +0 -12
- experimaestro/tests/test_param.py +1 -4
- experimaestro/tests/test_serializers.py +0 -59
- experimaestro/tests/test_tasks.py +10 -23
- experimaestro/tests/test_types.py +2 -2
- experimaestro/utils/multiprocessing.py +44 -0
- experimaestro/utils/resources.py +1 -1
- {experimaestro-1.10.0.dist-info → experimaestro-1.16.0.dist-info}/METADATA +5 -4
- {experimaestro-1.10.0.dist-info → experimaestro-1.16.0.dist-info}/RECORD +57 -32
- {experimaestro-1.10.0.dist-info → experimaestro-1.16.0.dist-info}/WHEEL +1 -1
- experimaestro/compat.py +0 -6
- {experimaestro-1.10.0.dist-info → experimaestro-1.16.0.dist-info}/entry_points.txt +0 -0
- {experimaestro-1.10.0.dist-info → experimaestro-1.16.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -9,7 +9,6 @@ from experimaestro import taskglobals
|
|
|
9
9
|
|
|
10
10
|
from termcolor import cprint
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
import hashlib
|
|
13
12
|
import logging
|
|
14
13
|
import io
|
|
15
14
|
from enum import Enum
|
|
@@ -20,7 +19,6 @@ from typing import (
|
|
|
20
19
|
Callable,
|
|
21
20
|
ClassVar,
|
|
22
21
|
Dict,
|
|
23
|
-
Iterator,
|
|
24
22
|
List,
|
|
25
23
|
Optional,
|
|
26
24
|
Set,
|
|
@@ -49,7 +47,6 @@ from .config_walk import ConfigWalk, ConfigWalkContext
|
|
|
49
47
|
from .config_utils import (
|
|
50
48
|
getqualattr,
|
|
51
49
|
add_to_path,
|
|
52
|
-
SealedError,
|
|
53
50
|
TaggedValue,
|
|
54
51
|
ObjectStore,
|
|
55
52
|
classproperty,
|
|
@@ -122,11 +119,11 @@ class ConfigInformation:
|
|
|
122
119
|
def __init__(self, pyobject: "ConfigMixin"):
|
|
123
120
|
# The underlying pyobject and XPM type
|
|
124
121
|
self.pyobject = pyobject
|
|
125
|
-
self.xpmtype = pyobject.__xpmtype__
|
|
122
|
+
self.xpmtype: "ObjectType" = pyobject.__xpmtype__
|
|
126
123
|
self.values = {}
|
|
127
124
|
|
|
128
125
|
# Meta-informations
|
|
129
|
-
self._tags = {}
|
|
126
|
+
self._tags: dict[str, Any] = {}
|
|
130
127
|
self._initinfo = ""
|
|
131
128
|
|
|
132
129
|
self._taskoutput = None
|
|
@@ -142,16 +139,13 @@ class ConfigInformation:
|
|
|
142
139
|
#: True when this configuration was loaded from disk
|
|
143
140
|
self.loaded = False
|
|
144
141
|
|
|
145
|
-
#
|
|
142
|
+
# Explicitly added dependencies
|
|
146
143
|
self.dependencies = []
|
|
147
144
|
|
|
148
145
|
# Concrete type variables resolutions
|
|
149
146
|
# This is used to check typevars coherence
|
|
150
147
|
self.concrete_typevars: Dict[TypeVar, type] = {}
|
|
151
148
|
|
|
152
|
-
# Lightweight tasks
|
|
153
|
-
self.pre_tasks: List["LightweightTask"] = []
|
|
154
|
-
|
|
155
149
|
# Initialization tasks
|
|
156
150
|
self.init_tasks: List["LightweightTask"] = []
|
|
157
151
|
|
|
@@ -160,16 +154,41 @@ class ConfigInformation:
|
|
|
160
154
|
|
|
161
155
|
# Cached information
|
|
162
156
|
|
|
163
|
-
self.
|
|
164
|
-
"""The
|
|
165
|
-
|
|
166
|
-
self._raw_identifier = None
|
|
167
|
-
"""The identifier without taking into account pre-tasks"""
|
|
157
|
+
self._identifier = None
|
|
158
|
+
"""The configuration identifier (cached when sealed)"""
|
|
168
159
|
|
|
169
160
|
self._validated = False
|
|
170
161
|
self._sealed = False
|
|
171
162
|
self._meta = None
|
|
172
163
|
|
|
164
|
+
# This contains the list of generated values (using context) in this
|
|
165
|
+
# configuration or any sub-configuration, is generated. This prevents
|
|
166
|
+
# problem when a configuration with generated values is re-used.
|
|
167
|
+
self._generated_values = []
|
|
168
|
+
|
|
169
|
+
def get_generated_paths(
|
|
170
|
+
self, path: list[str] = None, paths: list[str] = None
|
|
171
|
+
) -> list[str]:
|
|
172
|
+
"""Get the list of generated paths, useful to track down those
|
|
173
|
+
|
|
174
|
+
:param path: The current path
|
|
175
|
+
:param paths: The list of generated paths so far, defaults to None
|
|
176
|
+
:return: The full list of generated paths
|
|
177
|
+
"""
|
|
178
|
+
paths = [] if paths is None else paths
|
|
179
|
+
path = [] if path is None else path
|
|
180
|
+
|
|
181
|
+
for key in self._generated_values:
|
|
182
|
+
value = self.values[key]
|
|
183
|
+
if isinstance(value, ConfigMixin) and value.__xpm__._generated_values:
|
|
184
|
+
path.append(key)
|
|
185
|
+
value.__xpm__.get_generated_paths(path, paths)
|
|
186
|
+
path.pop()
|
|
187
|
+
else:
|
|
188
|
+
paths.append(".".join(path + [key]))
|
|
189
|
+
|
|
190
|
+
return paths
|
|
191
|
+
|
|
173
192
|
def set_meta(self, value: Optional[bool]):
|
|
174
193
|
"""Sets the meta flag"""
|
|
175
194
|
assert not self._sealed, "Configuration is sealed"
|
|
@@ -187,6 +206,31 @@ class ConfigInformation:
|
|
|
187
206
|
# Not an argument, bypass
|
|
188
207
|
return object.__getattribute__(self.pyobject, name)
|
|
189
208
|
|
|
209
|
+
@staticmethod
|
|
210
|
+
def is_generated_value(argument, value):
|
|
211
|
+
if argument.ignore_generated:
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
if value is None:
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
if isinstance(value, (int, str, float, bool, Path)):
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
if isinstance(value, ConfigMixin):
|
|
221
|
+
return value.__xpm__._generated_values and value.__xpm__.task is None
|
|
222
|
+
|
|
223
|
+
if isinstance(value, list):
|
|
224
|
+
return any(ConfigInformation.is_generated_value(argument, x) for x in value)
|
|
225
|
+
|
|
226
|
+
if isinstance(value, dict):
|
|
227
|
+
return any(
|
|
228
|
+
ConfigInformation.is_generated_value(argument, x)
|
|
229
|
+
for x in value.values()
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return False
|
|
233
|
+
|
|
190
234
|
def set(self, k, v, bypass=False):
|
|
191
235
|
from experimaestro.generators import Generator
|
|
192
236
|
|
|
@@ -198,9 +242,21 @@ class ConfigInformation:
|
|
|
198
242
|
if self._sealed and not bypass:
|
|
199
243
|
raise AttributeError(f"Object is read-only (trying to set {k})")
|
|
200
244
|
|
|
245
|
+
if not isinstance(v, ConfigMixin) and isinstance(v, Config):
|
|
246
|
+
raise AttributeError(
|
|
247
|
+
"Configuration (and not objects) should be used. Consider using .C(...)"
|
|
248
|
+
)
|
|
249
|
+
|
|
201
250
|
try:
|
|
202
251
|
argument = self.xpmtype.arguments.get(k, None)
|
|
203
252
|
if argument:
|
|
253
|
+
if ConfigInformation.is_generated_value(argument, v):
|
|
254
|
+
raise AttributeError(
|
|
255
|
+
f"Cannot set {k} to a configuration with generated values. "
|
|
256
|
+
"Here is the list of paths to help you: "
|
|
257
|
+
f"""{', '.join(v.__xpm__.get_generated_paths([k]))}"""
|
|
258
|
+
)
|
|
259
|
+
|
|
204
260
|
if not bypass and (
|
|
205
261
|
(isinstance(argument.generator, Generator)) or argument.constant
|
|
206
262
|
):
|
|
@@ -302,10 +358,6 @@ class ConfigInformation:
|
|
|
302
358
|
% (k, self.xpmtype, self._initinfo)
|
|
303
359
|
)
|
|
304
360
|
|
|
305
|
-
# Validate pre-tasks
|
|
306
|
-
for pre_task in self.pre_tasks:
|
|
307
|
-
pre_task.__xpm__.validate()
|
|
308
|
-
|
|
309
361
|
# Validate init tasks
|
|
310
362
|
for init_task in self.init_tasks:
|
|
311
363
|
init_task.__xpm__.validate()
|
|
@@ -326,12 +378,21 @@ class ConfigInformation:
|
|
|
326
378
|
Arguments:
|
|
327
379
|
- context: the generation context
|
|
328
380
|
"""
|
|
381
|
+
if generated_keys := [
|
|
382
|
+
k
|
|
383
|
+
for k, v in self.values.items()
|
|
384
|
+
if ConfigInformation.is_generated_value(self.xpmtype.arguments[k], v)
|
|
385
|
+
]:
|
|
386
|
+
raise AttributeError(
|
|
387
|
+
"Cannot seal a configuration with generated values:"
|
|
388
|
+
f"""{",".join(generated_keys)} in {context.currentpath}"""
|
|
389
|
+
)
|
|
329
390
|
|
|
330
391
|
class Sealer(ConfigWalk):
|
|
331
|
-
def preprocess(self, config:
|
|
392
|
+
def preprocess(self, config: ConfigMixin):
|
|
332
393
|
return not config.__xpm__._sealed, config
|
|
333
394
|
|
|
334
|
-
def postprocess(self, stub, config:
|
|
395
|
+
def postprocess(self, stub, config: ConfigMixin, values):
|
|
335
396
|
# Generate values
|
|
336
397
|
from experimaestro.generators import Generator
|
|
337
398
|
|
|
@@ -344,22 +405,36 @@ class ConfigInformation:
|
|
|
344
405
|
continue
|
|
345
406
|
value = argument.generator()
|
|
346
407
|
else:
|
|
408
|
+
# Generate a value
|
|
347
409
|
sig = inspect.signature(argument.generator)
|
|
348
410
|
if len(sig.parameters) == 0:
|
|
349
411
|
value = argument.generator()
|
|
350
412
|
elif len(sig.parameters) == 2:
|
|
413
|
+
# Only in that case do we need to flag this configuration
|
|
414
|
+
# as containing generated values
|
|
415
|
+
config.__xpm__._generated_values.append(k)
|
|
351
416
|
value = argument.generator(self.context, config)
|
|
352
417
|
else:
|
|
353
418
|
assert (
|
|
354
419
|
False
|
|
355
420
|
), "generator has either two parameters (context and config), or none"
|
|
356
421
|
config.__xpm__.set(k, value, bypass=True)
|
|
422
|
+
else:
|
|
423
|
+
value = config.__xpm__.values.get(k)
|
|
357
424
|
except Exception:
|
|
358
425
|
logger.error(
|
|
359
426
|
"While setting %s of %s", argument.name, config.__xpmtype__
|
|
360
427
|
)
|
|
361
428
|
raise
|
|
362
429
|
|
|
430
|
+
# Propagate the generated value flag
|
|
431
|
+
if (
|
|
432
|
+
(value is not None)
|
|
433
|
+
and isinstance(value, ConfigMixin)
|
|
434
|
+
and value.__xpm__._generated_values
|
|
435
|
+
):
|
|
436
|
+
config.__xpm__._generated_values.append(k)
|
|
437
|
+
|
|
363
438
|
config.__xpm__._sealed = True
|
|
364
439
|
|
|
365
440
|
Sealer(context, recurse_task=True)(self.pyobject)
|
|
@@ -372,90 +447,29 @@ class ConfigInformation:
|
|
|
372
447
|
context = ConfigWalkContext()
|
|
373
448
|
|
|
374
449
|
class Unsealer(ConfigWalk):
|
|
375
|
-
def preprocess(self, config:
|
|
450
|
+
def preprocess(self, config: ConfigMixin):
|
|
376
451
|
return config.__xpm__._sealed, config
|
|
377
452
|
|
|
378
|
-
def postprocess(self, stub, config:
|
|
453
|
+
def postprocess(self, stub, config: ConfigMixin, values):
|
|
379
454
|
config.__xpm__._sealed = False
|
|
380
455
|
config.__xpm__._identifier = None
|
|
381
456
|
|
|
382
457
|
Unsealer(context, recurse_task=True)(self.pyobject)
|
|
383
458
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
pre_tasks: Dict[int, "Config"] = {}
|
|
387
|
-
|
|
388
|
-
class PreTaskCollect(ConfigWalk):
|
|
389
|
-
def preprocess(self, config: Config):
|
|
390
|
-
# Do not cross tasks
|
|
391
|
-
return not isinstance(config.__xpm__, Task), config
|
|
392
|
-
|
|
393
|
-
def postprocess(self, stub, config: Config, values):
|
|
394
|
-
pre_tasks.update(
|
|
395
|
-
{id(pre_task): pre_task for pre_task in config.__xpm__.pre_tasks}
|
|
396
|
-
)
|
|
397
|
-
|
|
398
|
-
PreTaskCollect(context, recurse_task=True)(self.pyobject)
|
|
399
|
-
return pre_tasks.values()
|
|
400
|
-
|
|
401
|
-
def identifiers(self, only_raw: bool):
|
|
459
|
+
@property
|
|
460
|
+
def identifier(self):
|
|
402
461
|
"""Computes the unique identifier"""
|
|
403
|
-
from ..identifier import IdentifierComputer
|
|
404
|
-
|
|
405
|
-
raw_identifier = self._raw_identifier
|
|
406
|
-
full_identifier = self._full_identifier
|
|
462
|
+
from ..identifier import IdentifierComputer
|
|
407
463
|
|
|
408
464
|
# Computes raw identifier if needed
|
|
409
|
-
if
|
|
410
|
-
|
|
411
|
-
raw_identifier = IdentifierComputer.compute(self.pyobject)
|
|
412
|
-
if self._sealed:
|
|
413
|
-
self._raw_identifier = raw_identifier
|
|
414
|
-
|
|
415
|
-
if only_raw:
|
|
416
|
-
return raw_identifier, full_identifier
|
|
417
|
-
|
|
418
|
-
# OK, let's compute the full identifier
|
|
419
|
-
if full_identifier is None or not self._sealed:
|
|
420
|
-
# Compute the full identifier by including the pre-tasks
|
|
421
|
-
hasher = hashlib.sha256()
|
|
422
|
-
hasher.update(raw_identifier.all)
|
|
423
|
-
pre_tasks_ids = [
|
|
424
|
-
pre_task.__xpm__.raw_identifier.all
|
|
425
|
-
for pre_task in self.collect_pre_tasks()
|
|
426
|
-
]
|
|
427
|
-
for task_id in sorted(pre_tasks_ids):
|
|
428
|
-
hasher.update(task_id)
|
|
429
|
-
|
|
430
|
-
# Adds init tasks
|
|
431
|
-
if self.init_tasks:
|
|
432
|
-
hasher.update(IdentifierComputer.INIT_TASKS)
|
|
433
|
-
for init_task in self.init_tasks:
|
|
434
|
-
hasher.update(init_task.__xpm__.raw_identifier.all)
|
|
435
|
-
|
|
436
|
-
full_identifier = Identifier(hasher.digest())
|
|
437
|
-
full_identifier.has_loops = raw_identifier.has_loops
|
|
465
|
+
if self._identifier is not None:
|
|
466
|
+
return self._identifier
|
|
438
467
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
return
|
|
444
|
-
|
|
445
|
-
@property
|
|
446
|
-
def raw_identifier(self) -> "Identifier":
|
|
447
|
-
"""Computes the unique identifier (without task modifiers)"""
|
|
448
|
-
raw_identifier, _ = self.identifiers(True)
|
|
449
|
-
return raw_identifier
|
|
450
|
-
|
|
451
|
-
@property
|
|
452
|
-
def full_identifier(self) -> "Identifier":
|
|
453
|
-
"""Computes the unique identifier (with task modifiers)"""
|
|
454
|
-
_, full_identifier = self.identifiers(False)
|
|
455
|
-
return full_identifier
|
|
456
|
-
|
|
457
|
-
identifier = full_identifier
|
|
458
|
-
"""Deprecated: use full_identifier"""
|
|
468
|
+
# Get the main identifier
|
|
469
|
+
identifier = IdentifierComputer.compute(self.pyobject)
|
|
470
|
+
if self._sealed:
|
|
471
|
+
self._identifier = identifier
|
|
472
|
+
return identifier
|
|
459
473
|
|
|
460
474
|
def dependency(self):
|
|
461
475
|
"""Returns a dependency"""
|
|
@@ -470,12 +484,6 @@ class ConfigInformation:
|
|
|
470
484
|
path: List[str],
|
|
471
485
|
taskids: Set[int],
|
|
472
486
|
):
|
|
473
|
-
# Add pre-tasks
|
|
474
|
-
for pre_task in self.pre_tasks:
|
|
475
|
-
pre_task.__xpm__.updatedependencies(
|
|
476
|
-
dependencies, path + ["__pre_tasks__"], taskids
|
|
477
|
-
)
|
|
478
|
-
|
|
479
487
|
# Add initialization tasks
|
|
480
488
|
for init_task in self.init_tasks:
|
|
481
489
|
init_task.__xpm__.updatedependencies(
|
|
@@ -618,10 +626,11 @@ class ConfigInformation:
|
|
|
618
626
|
) or RunMode.NORMAL
|
|
619
627
|
if run_mode == RunMode.NORMAL:
|
|
620
628
|
TaskEventListener.connect(experiment.CURRENT)
|
|
629
|
+
experiment.CURRENT.submit(self.job)
|
|
621
630
|
other = experiment.CURRENT.submit(self.job)
|
|
622
631
|
if other:
|
|
623
|
-
#
|
|
624
|
-
|
|
632
|
+
# Our job = previously submitted job
|
|
633
|
+
self.job = other
|
|
625
634
|
else:
|
|
626
635
|
# Show a warning
|
|
627
636
|
if run_mode == RunMode.GENERATE_ONLY:
|
|
@@ -657,13 +666,6 @@ class ConfigInformation:
|
|
|
657
666
|
|
|
658
667
|
print(file=sys.stderr) # noqa: T201
|
|
659
668
|
|
|
660
|
-
# Handle an output configuration # FIXME: remove
|
|
661
|
-
def mark_output(config: "Config"):
|
|
662
|
-
"""Sets a dependency on the job"""
|
|
663
|
-
assert not isinstance(config, Task), "Cannot set a dependency on a task"
|
|
664
|
-
config.__xpm__.task = self.pyobject
|
|
665
|
-
return config
|
|
666
|
-
|
|
667
669
|
# Mark this configuration also
|
|
668
670
|
self.task = self.pyobject
|
|
669
671
|
|
|
@@ -752,9 +754,6 @@ class ConfigInformation:
|
|
|
752
754
|
if self.task is not None and self.task is not self:
|
|
753
755
|
ConfigInformation.__collect_objects__(self.task, objects, context)
|
|
754
756
|
|
|
755
|
-
# Serialize pre-tasks
|
|
756
|
-
ConfigInformation.__collect_objects__(self.pre_tasks, objects, context)
|
|
757
|
-
|
|
758
757
|
# Serialize initialization tasks
|
|
759
758
|
ConfigInformation.__collect_objects__(self.init_tasks, objects, context)
|
|
760
759
|
|
|
@@ -768,8 +767,6 @@ class ConfigInformation:
|
|
|
768
767
|
}
|
|
769
768
|
|
|
770
769
|
# Add pre/init tasks
|
|
771
|
-
if self.pre_tasks:
|
|
772
|
-
state_dict["pre-tasks"] = [id(pre_task) for pre_task in self.pre_tasks]
|
|
773
770
|
if self.init_tasks:
|
|
774
771
|
state_dict["init-tasks"] = [id(init_task) for init_task in self.init_tasks]
|
|
775
772
|
|
|
@@ -949,34 +946,31 @@ class ConfigInformation:
|
|
|
949
946
|
|
|
950
947
|
@overload
|
|
951
948
|
@staticmethod
|
|
952
|
-
def fromParameters(
|
|
949
|
+
def fromParameters( # noqa: E704
|
|
953
950
|
definitions: List[Dict],
|
|
954
951
|
as_instance=True,
|
|
955
952
|
save_directory: Optional[Path] = None,
|
|
956
953
|
discard_id: bool = False,
|
|
957
|
-
) -> "ConfigMixin":
|
|
958
|
-
...
|
|
954
|
+
) -> "ConfigMixin": ...
|
|
959
955
|
|
|
960
956
|
@overload
|
|
961
957
|
@staticmethod
|
|
962
|
-
def fromParameters(
|
|
958
|
+
def fromParameters( # noqa: E704
|
|
963
959
|
definitions: List[Dict],
|
|
964
960
|
as_instance=False,
|
|
965
961
|
return_tasks=True,
|
|
966
962
|
save_directory: Optional[Path] = None,
|
|
967
963
|
discard_id: bool = False,
|
|
968
|
-
) -> Tuple["Config", List["LightweightTask"]]:
|
|
969
|
-
...
|
|
964
|
+
) -> Tuple["Config", List["LightweightTask"]]: ...
|
|
970
965
|
|
|
971
966
|
@overload
|
|
972
967
|
@staticmethod
|
|
973
|
-
def fromParameters(
|
|
968
|
+
def fromParameters( # noqa: E704
|
|
974
969
|
definitions: List[Dict],
|
|
975
970
|
as_instance=False,
|
|
976
971
|
save_directory: Optional[Path] = None,
|
|
977
972
|
discard_id: bool = False,
|
|
978
|
-
) -> "Config":
|
|
979
|
-
...
|
|
973
|
+
) -> "Config": ...
|
|
980
974
|
|
|
981
975
|
@staticmethod
|
|
982
976
|
def load_objects( # noqa: C901
|
|
@@ -1101,12 +1095,6 @@ class ConfigInformation:
|
|
|
1101
1095
|
o.__post_init__()
|
|
1102
1096
|
|
|
1103
1097
|
else:
|
|
1104
|
-
# Sets pre-tasks
|
|
1105
|
-
o.__xpm__.pre_tasks = [
|
|
1106
|
-
objects[pre_task_id]
|
|
1107
|
-
for pre_task_id in definition.get("pre-tasks", [])
|
|
1108
|
-
]
|
|
1109
|
-
|
|
1110
1098
|
if task_id := definition.get("task", None):
|
|
1111
1099
|
o.__xpm__.task = objects[task_id]
|
|
1112
1100
|
|
|
@@ -1140,15 +1128,6 @@ class ConfigInformation:
|
|
|
1140
1128
|
|
|
1141
1129
|
# Run pre-task (or returns them)
|
|
1142
1130
|
if as_instance or return_tasks:
|
|
1143
|
-
# Collect pre-tasks (just once)
|
|
1144
|
-
completed_pretasks = set()
|
|
1145
|
-
pre_tasks = []
|
|
1146
|
-
for definition in definitions:
|
|
1147
|
-
for pre_task_id in definition.get("pre-tasks", []):
|
|
1148
|
-
if pre_task_id not in completed_pretasks:
|
|
1149
|
-
completed_pretasks.add(pre_task_id)
|
|
1150
|
-
pre_tasks.append(objects[pre_task_id])
|
|
1151
|
-
|
|
1152
1131
|
# Collect init tasks
|
|
1153
1132
|
init_tasks = []
|
|
1154
1133
|
for init_task_id in definitions[-1].get("init-tasks", []):
|
|
@@ -1156,14 +1135,11 @@ class ConfigInformation:
|
|
|
1156
1135
|
init_tasks.append(init_task)
|
|
1157
1136
|
|
|
1158
1137
|
if as_instance:
|
|
1159
|
-
for pre_task in pre_tasks:
|
|
1160
|
-
logger.info("Executing pre-task %s", type(pre_task))
|
|
1161
|
-
pre_task.execute()
|
|
1162
1138
|
for init_task in init_tasks:
|
|
1163
1139
|
logger.info("Executing init task %s", type(init_task))
|
|
1164
1140
|
init_task.execute()
|
|
1165
1141
|
else:
|
|
1166
|
-
return o,
|
|
1142
|
+
return o, init_tasks
|
|
1167
1143
|
|
|
1168
1144
|
return o
|
|
1169
1145
|
|
|
@@ -1171,7 +1147,6 @@ class ConfigInformation:
|
|
|
1171
1147
|
def __init__(self, context: ConfigWalkContext, *, objects: ObjectStore = None):
|
|
1172
1148
|
super().__init__(context)
|
|
1173
1149
|
self.objects = ObjectStore() if objects is None else objects
|
|
1174
|
-
self.pre_tasks = {}
|
|
1175
1150
|
|
|
1176
1151
|
def preprocess(self, config: "Config"):
|
|
1177
1152
|
if self.objects.is_constructed(id(config)):
|
|
@@ -1198,10 +1173,6 @@ class ConfigInformation:
|
|
|
1198
1173
|
# Call __post_init__
|
|
1199
1174
|
stub.__post_init__()
|
|
1200
1175
|
|
|
1201
|
-
# Gather pre-tasks
|
|
1202
|
-
for pre_task in config.__xpm__.pre_tasks:
|
|
1203
|
-
self.pre_tasks[id(pre_task)] = self.stub(pre_task)
|
|
1204
|
-
|
|
1205
1176
|
self.objects.set_constructed(id(config))
|
|
1206
1177
|
return stub
|
|
1207
1178
|
|
|
@@ -1215,10 +1186,6 @@ class ConfigInformation:
|
|
|
1215
1186
|
processor = ConfigInformation.FromPython(context, objects=objects)
|
|
1216
1187
|
last_object = processor(self.pyobject)
|
|
1217
1188
|
|
|
1218
|
-
# Execute pre-tasks
|
|
1219
|
-
for pre_task in processor.pre_tasks.values():
|
|
1220
|
-
pre_task.execute()
|
|
1221
|
-
|
|
1222
1189
|
return last_object
|
|
1223
1190
|
|
|
1224
1191
|
def add_dependencies(self, *dependencies):
|
|
@@ -1242,6 +1209,9 @@ def clone(v):
|
|
|
1242
1209
|
if isinstance(v, Enum):
|
|
1243
1210
|
return v
|
|
1244
1211
|
|
|
1212
|
+
if isinstance(v, tuple):
|
|
1213
|
+
return tuple(clone(x) for x in v)
|
|
1214
|
+
|
|
1245
1215
|
if isinstance(v, Config):
|
|
1246
1216
|
# Create a new instance
|
|
1247
1217
|
kwargs = {
|
|
@@ -1260,6 +1230,11 @@ class ConfigMixin:
|
|
|
1260
1230
|
"""Class for configuration objects"""
|
|
1261
1231
|
|
|
1262
1232
|
__xpmtype__: ObjectType
|
|
1233
|
+
"""The associated XPM type"""
|
|
1234
|
+
|
|
1235
|
+
__xpm__: ConfigInformation
|
|
1236
|
+
"""The __xpm__ object contains all instance specific information about a
|
|
1237
|
+
configuration/task"""
|
|
1263
1238
|
|
|
1264
1239
|
def __init__(self, **kwargs):
|
|
1265
1240
|
"""Initialize the configuration with the given parameters"""
|
|
@@ -1396,29 +1371,7 @@ class ConfigMixin:
|
|
|
1396
1371
|
attributes)"""
|
|
1397
1372
|
return clone(self)
|
|
1398
1373
|
|
|
1399
|
-
def
|
|
1400
|
-
assert all(
|
|
1401
|
-
[isinstance(task, LightweightTask) for task in tasks]
|
|
1402
|
-
), "One of the pre-tasks are not lightweight tasks"
|
|
1403
|
-
if self.__xpm__._sealed:
|
|
1404
|
-
raise SealedError("Cannot add pre-tasks to a sealed configuration")
|
|
1405
|
-
self.__xpm__.pre_tasks.extend(tasks)
|
|
1406
|
-
return self
|
|
1407
|
-
|
|
1408
|
-
def add_pretasks_from(self, *configs: "Config"):
|
|
1409
|
-
assert all(
|
|
1410
|
-
[isinstance(config, ConfigMixin) for config in configs]
|
|
1411
|
-
), "One of the parameters is not a configuration object"
|
|
1412
|
-
for config in configs:
|
|
1413
|
-
self.add_pretasks(*config.__xpm__.pre_tasks)
|
|
1414
|
-
return self
|
|
1415
|
-
|
|
1416
|
-
@property
|
|
1417
|
-
def pre_tasks(self) -> List["LightweightTask"]:
|
|
1418
|
-
"""Access pre-tasks"""
|
|
1419
|
-
return self.__xpm__.pre_tasks
|
|
1420
|
-
|
|
1421
|
-
def copy_dependencies(self, other: "Config"):
|
|
1374
|
+
def copy_dependencies(self, other: "ConfigMixin"):
|
|
1422
1375
|
"""Add all the dependencies from other configuration"""
|
|
1423
1376
|
|
|
1424
1377
|
# Add task dependency
|
|
@@ -1441,10 +1394,6 @@ class Config:
|
|
|
1441
1394
|
"""The object type holds all the information about a specific subclass
|
|
1442
1395
|
experimaestro metadata"""
|
|
1443
1396
|
|
|
1444
|
-
__xpm__: ConfigInformation
|
|
1445
|
-
"""The __xpm__ object contains all instance specific information about a
|
|
1446
|
-
configuration/task"""
|
|
1447
|
-
|
|
1448
1397
|
@classproperty
|
|
1449
1398
|
def XPMConfig(cls):
|
|
1450
1399
|
if issubclass(cls, ConfigMixin):
|
|
@@ -1557,17 +1506,7 @@ class Config:
|
|
|
1557
1506
|
return self.__xpm__.__json__()
|
|
1558
1507
|
|
|
1559
1508
|
def __identifier__(self) -> "Identifier":
|
|
1560
|
-
return self.__xpm__.
|
|
1561
|
-
|
|
1562
|
-
def add_pretasks(self, *tasks: "LightweightTask"):
|
|
1563
|
-
"""Add pre-tasks"""
|
|
1564
|
-
raise AssertionError("This method can only be used during configuration")
|
|
1565
|
-
|
|
1566
|
-
def add_pretasks_from(self, *configs: "Config"):
|
|
1567
|
-
"""Add pre-tasks from the listed configurations"""
|
|
1568
|
-
raise AssertionError(
|
|
1569
|
-
"The 'add_pretasks_from' can only be used during configuration"
|
|
1570
|
-
)
|
|
1509
|
+
return self.__xpm__.identifier
|
|
1571
1510
|
|
|
1572
1511
|
def copy_dependencies(self, other: "Config"):
|
|
1573
1512
|
"""Add pre-tasks from the listed configurations"""
|
|
@@ -1575,11 +1514,6 @@ class Config:
|
|
|
1575
1514
|
"The 'copy_dependencies' method can only be used during configuration"
|
|
1576
1515
|
)
|
|
1577
1516
|
|
|
1578
|
-
@property
|
|
1579
|
-
def pre_tasks(self) -> List["LightweightTask"]:
|
|
1580
|
-
"""Access pre-tasks"""
|
|
1581
|
-
raise AssertionError("Pre-tasks can be accessed only during configuration")
|
|
1582
|
-
|
|
1583
1517
|
def register_task_output(self, method, *args, **kwargs):
|
|
1584
1518
|
# Determine the path for this...
|
|
1585
1519
|
path = taskglobals.Env.instance().xpm_path / "task-outputs.jsonl"
|
|
@@ -71,6 +71,7 @@ class ConfigWalk:
|
|
|
71
71
|
return self.context.push(str(i))
|
|
72
72
|
|
|
73
73
|
def map(self, k: str):
|
|
74
|
+
"""Provides a path context when processing a tree"""
|
|
74
75
|
return self.context.push(k)
|
|
75
76
|
|
|
76
77
|
def stub(self, config):
|
|
@@ -108,11 +109,7 @@ class ConfigWalk:
|
|
|
108
109
|
else:
|
|
109
110
|
result[arg.name] = None
|
|
110
111
|
|
|
111
|
-
# Deals with
|
|
112
|
-
if info.pre_tasks:
|
|
113
|
-
with self.map("__pre_tasks__"):
|
|
114
|
-
self(info.pre_tasks)
|
|
115
|
-
|
|
112
|
+
# Deals with init tasks
|
|
116
113
|
if info.init_tasks:
|
|
117
114
|
with self.map("__init_tasks__"):
|
|
118
115
|
self(info.init_tasks)
|
|
@@ -123,7 +120,8 @@ class ConfigWalk:
|
|
|
123
120
|
and self.recurse_task
|
|
124
121
|
and x.__xpm__.task is not x
|
|
125
122
|
):
|
|
126
|
-
self(
|
|
123
|
+
with self.map("__task__"):
|
|
124
|
+
self(x.__xpm__.task)
|
|
127
125
|
|
|
128
126
|
processed = self.postprocess(stub, x, result)
|
|
129
127
|
self.visited[xid] = processed
|
experimaestro/core/objects.pyi
CHANGED
|
@@ -168,7 +168,7 @@ class ConfigMixin:
|
|
|
168
168
|
*,
|
|
169
169
|
workspace: Incomplete | None = ...,
|
|
170
170
|
launcher: Incomplete | None = ...,
|
|
171
|
-
run_mode: RunMode =
|
|
171
|
+
run_mode: RunMode = ...,
|
|
172
172
|
): ...
|
|
173
173
|
def stdout(self): ...
|
|
174
174
|
def stderr(self): ...
|
|
@@ -195,11 +195,7 @@ class Config:
|
|
|
195
195
|
def __post_init__(self) -> None: ...
|
|
196
196
|
def __json__(self): ...
|
|
197
197
|
def __identifier__(self) -> Identifier: ...
|
|
198
|
-
def add_pretasks(self, *tasks: "LightweightTask"): ...
|
|
199
|
-
def add_pretasks_from(self, configs: "Config"): ...
|
|
200
198
|
def copy_dependencies(self, other: "Config"): ...
|
|
201
|
-
@property
|
|
202
|
-
def pre_tasks(self) -> List["LightweightTask"]: ...
|
|
203
199
|
|
|
204
200
|
class LightweightTask(Config):
|
|
205
201
|
def execute(self) -> None: ...
|
|
@@ -213,7 +209,7 @@ class Task(LightweightTask):
|
|
|
213
209
|
workspace: Incomplete | None = ...,
|
|
214
210
|
launcher: Incomplete | None = ...,
|
|
215
211
|
run_mode: RunMode = ...,
|
|
216
|
-
init_tasks: List["LightweightTask"] = []
|
|
212
|
+
init_tasks: List["LightweightTask"] = [],
|
|
217
213
|
): ...
|
|
218
214
|
def task_outputs(self, dep: Callable[[Config], None]) -> Any: ...
|
|
219
215
|
|
|
@@ -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)))
|
experimaestro/core/types.py
CHANGED
|
@@ -13,10 +13,7 @@ from enum import Enum
|
|
|
13
13
|
import ast
|
|
14
14
|
import textwrap
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
from typing_extensions import _AnnotatedAlias, get_type_hints
|
|
18
|
-
else:
|
|
19
|
-
from typing import _AnnotatedAlias, get_type_hints
|
|
16
|
+
from typing import _AnnotatedAlias, get_type_hints
|
|
20
17
|
|
|
21
18
|
if typing.TYPE_CHECKING:
|
|
22
19
|
from experimaestro.scheduler.base import Job
|