experimaestro 1.13.0__py3-none-any.whl → 1.15.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.
Potentially problematic release.
This version of experimaestro might be problematic. Click here for more details.
- experimaestro/core/arguments.py +10 -1
- experimaestro/core/identifier.py +11 -6
- experimaestro/core/objects/config.py +89 -181
- experimaestro/core/objects/config_walk.py +4 -6
- experimaestro/core/objects.pyi +2 -6
- experimaestro/core/serializers.py +1 -8
- experimaestro/tests/test_dependencies.py +0 -6
- experimaestro/tests/test_generators.py +63 -3
- experimaestro/tests/test_identifier.py +87 -76
- experimaestro/tests/test_instance.py +0 -12
- experimaestro/tests/test_serializers.py +0 -59
- experimaestro/tests/test_tasks.py +0 -20
- experimaestro/tests/test_types.py +2 -2
- {experimaestro-1.13.0.dist-info → experimaestro-1.15.0.dist-info}/METADATA +1 -1
- {experimaestro-1.13.0.dist-info → experimaestro-1.15.0.dist-info}/RECORD +18 -18
- {experimaestro-1.13.0.dist-info → experimaestro-1.15.0.dist-info}/WHEEL +0 -0
- {experimaestro-1.13.0.dist-info → experimaestro-1.15.0.dist-info}/entry_points.txt +0 -0
- {experimaestro-1.13.0.dist-info → experimaestro-1.15.0.dist-info}/licenses/LICENSE +0 -0
experimaestro/core/arguments.py
CHANGED
|
@@ -80,10 +80,12 @@ class Argument:
|
|
|
80
80
|
|
|
81
81
|
self.generator = generator
|
|
82
82
|
self.default = None
|
|
83
|
+
self.ignore_generated = False
|
|
83
84
|
|
|
84
85
|
if default is not None:
|
|
85
86
|
assert self.generator is None, "generator and default are exclusive options"
|
|
86
87
|
if isinstance(default, field):
|
|
88
|
+
self.ignore_generated = default.ignore_generated
|
|
87
89
|
if default.default is not None:
|
|
88
90
|
self.default = default.default
|
|
89
91
|
elif default.default_factory is not None:
|
|
@@ -184,13 +186,20 @@ DataPath = Annotated[Path, dataHint]
|
|
|
184
186
|
class field:
|
|
185
187
|
"""Extra information for a given experimaestro field (param or meta)"""
|
|
186
188
|
|
|
187
|
-
def __init__(
|
|
189
|
+
def __init__(
|
|
190
|
+
self,
|
|
191
|
+
*,
|
|
192
|
+
default: Any = None,
|
|
193
|
+
default_factory: Callable = None,
|
|
194
|
+
ignore_generated=False,
|
|
195
|
+
):
|
|
188
196
|
assert not (
|
|
189
197
|
(default is not None) and (default_factory is not None)
|
|
190
198
|
), "default and default_factory are mutually exclusive options"
|
|
191
199
|
|
|
192
200
|
self.default_factory = default_factory
|
|
193
201
|
self.default = default
|
|
202
|
+
self.ignore_generated = ignore_generated
|
|
194
203
|
|
|
195
204
|
|
|
196
205
|
class help(TypeAnnotation):
|
experimaestro/core/identifier.py
CHANGED
|
@@ -6,7 +6,7 @@ import logging
|
|
|
6
6
|
import os
|
|
7
7
|
import struct
|
|
8
8
|
from typing import Optional
|
|
9
|
-
from experimaestro.core.objects import Config
|
|
9
|
+
from experimaestro.core.objects import Config, ConfigMixin
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class ConfigPath:
|
|
@@ -116,7 +116,7 @@ class IdentifierComputer:
|
|
|
116
116
|
CYCLE_REFERENCE = b"\x0b"
|
|
117
117
|
INIT_TASKS = b"\x0c"
|
|
118
118
|
|
|
119
|
-
def __init__(self, config: "
|
|
119
|
+
def __init__(self, config: "ConfigMixin", config_path: ConfigPath, *, version=None):
|
|
120
120
|
# Hasher for parameters
|
|
121
121
|
self._hasher = hashlib.sha256()
|
|
122
122
|
self.config = config
|
|
@@ -170,7 +170,7 @@ class IdentifierComputer:
|
|
|
170
170
|
self._hashupdate(IdentifierComputer.ENUM_ID)
|
|
171
171
|
k = value.__class__
|
|
172
172
|
self._hashupdate(
|
|
173
|
-
f"{k.__module__}.{k.__qualname__
|
|
173
|
+
f"{k.__module__}.{k.__qualname__}:{value.name}".encode("utf-8"),
|
|
174
174
|
)
|
|
175
175
|
elif isinstance(value, dict):
|
|
176
176
|
self._hashupdate(IdentifierComputer.DICT_ID)
|
|
@@ -183,7 +183,7 @@ class IdentifierComputer:
|
|
|
183
183
|
self.update(value)
|
|
184
184
|
|
|
185
185
|
# Handles configurations
|
|
186
|
-
elif isinstance(value,
|
|
186
|
+
elif isinstance(value, ConfigMixin):
|
|
187
187
|
# Encodes the identifier
|
|
188
188
|
self._hashupdate(IdentifierComputer.OBJECT_ID)
|
|
189
189
|
|
|
@@ -264,12 +264,17 @@ class IdentifierComputer:
|
|
|
264
264
|
self._hashupdate(IdentifierComputer.NAME_ID)
|
|
265
265
|
self.update(argvalue)
|
|
266
266
|
|
|
267
|
+
# Add init tasks
|
|
268
|
+
if value.__xpm__.init_tasks:
|
|
269
|
+
self._hashupdate(IdentifierComputer.INIT_TASKS)
|
|
270
|
+
for init_task in value.__xpm__.init_tasks:
|
|
271
|
+
self.update(init_task)
|
|
267
272
|
else:
|
|
268
273
|
raise NotImplementedError("Cannot compute hash of type %s" % type(value))
|
|
269
274
|
|
|
270
275
|
@staticmethod
|
|
271
276
|
def compute(
|
|
272
|
-
config: "
|
|
277
|
+
config: "ConfigMixin", config_path: ConfigPath | None = None, version=None
|
|
273
278
|
) -> Identifier:
|
|
274
279
|
"""Compute the identifier for a configuration
|
|
275
280
|
|
|
@@ -281,7 +286,7 @@ class IdentifierComputer:
|
|
|
281
286
|
# Try to use the cached value first
|
|
282
287
|
# (if there are no loops)
|
|
283
288
|
if config.__xpm__._sealed:
|
|
284
|
-
identifier = config.__xpm__.
|
|
289
|
+
identifier = config.__xpm__._identifier
|
|
285
290
|
if identifier is not None and not identifier.has_loops:
|
|
286
291
|
return identifier
|
|
287
292
|
|
|
@@ -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,
|
|
@@ -149,9 +146,6 @@ class ConfigInformation:
|
|
|
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,20 +154,40 @@ 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
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
self.
|
|
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
|
|
177
191
|
|
|
178
192
|
def set_meta(self, value: Optional[bool]):
|
|
179
193
|
"""Sets the meta flag"""
|
|
@@ -192,6 +206,31 @@ class ConfigInformation:
|
|
|
192
206
|
# Not an argument, bypass
|
|
193
207
|
return object.__getattribute__(self.pyobject, name)
|
|
194
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
|
+
|
|
195
234
|
def set(self, k, v, bypass=False):
|
|
196
235
|
from experimaestro.generators import Generator
|
|
197
236
|
|
|
@@ -208,18 +247,16 @@ class ConfigInformation:
|
|
|
208
247
|
"Configuration (and not objects) should be used. Consider using .C(...)"
|
|
209
248
|
)
|
|
210
249
|
|
|
211
|
-
if (
|
|
212
|
-
isinstance(v, ConfigMixin)
|
|
213
|
-
and v.__xpm__._has_generated_value
|
|
214
|
-
and v.__xpm__.task is None
|
|
215
|
-
):
|
|
216
|
-
raise AttributeError(
|
|
217
|
-
f"Cannot set {k} to a configuration with generated values"
|
|
218
|
-
)
|
|
219
|
-
|
|
220
250
|
try:
|
|
221
251
|
argument = self.xpmtype.arguments.get(k, None)
|
|
222
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
|
+
|
|
223
260
|
if not bypass and (
|
|
224
261
|
(isinstance(argument.generator, Generator)) or argument.constant
|
|
225
262
|
):
|
|
@@ -321,10 +358,6 @@ class ConfigInformation:
|
|
|
321
358
|
% (k, self.xpmtype, self._initinfo)
|
|
322
359
|
)
|
|
323
360
|
|
|
324
|
-
# Validate pre-tasks
|
|
325
|
-
for pre_task in self.pre_tasks:
|
|
326
|
-
pre_task.__xpm__.validate()
|
|
327
|
-
|
|
328
361
|
# Validate init tasks
|
|
329
362
|
for init_task in self.init_tasks:
|
|
330
363
|
init_task.__xpm__.validate()
|
|
@@ -345,6 +378,15 @@ class ConfigInformation:
|
|
|
345
378
|
Arguments:
|
|
346
379
|
- context: the generation context
|
|
347
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
|
+
)
|
|
348
390
|
|
|
349
391
|
class Sealer(ConfigWalk):
|
|
350
392
|
def preprocess(self, config: ConfigMixin):
|
|
@@ -368,13 +410,15 @@ class ConfigInformation:
|
|
|
368
410
|
if len(sig.parameters) == 0:
|
|
369
411
|
value = argument.generator()
|
|
370
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)
|
|
371
416
|
value = argument.generator(self.context, config)
|
|
372
417
|
else:
|
|
373
418
|
assert (
|
|
374
419
|
False
|
|
375
420
|
), "generator has either two parameters (context and config), or none"
|
|
376
421
|
config.__xpm__.set(k, value, bypass=True)
|
|
377
|
-
config.__xpm__._has_generated_value = True
|
|
378
422
|
else:
|
|
379
423
|
value = config.__xpm__.values.get(k)
|
|
380
424
|
except Exception:
|
|
@@ -387,9 +431,9 @@ class ConfigInformation:
|
|
|
387
431
|
if (
|
|
388
432
|
(value is not None)
|
|
389
433
|
and isinstance(value, ConfigMixin)
|
|
390
|
-
and value.__xpm__.
|
|
434
|
+
and value.__xpm__._generated_values
|
|
391
435
|
):
|
|
392
|
-
|
|
436
|
+
config.__xpm__._generated_values.append(k)
|
|
393
437
|
|
|
394
438
|
config.__xpm__._sealed = True
|
|
395
439
|
|
|
@@ -403,90 +447,29 @@ class ConfigInformation:
|
|
|
403
447
|
context = ConfigWalkContext()
|
|
404
448
|
|
|
405
449
|
class Unsealer(ConfigWalk):
|
|
406
|
-
def preprocess(self, config:
|
|
450
|
+
def preprocess(self, config: ConfigMixin):
|
|
407
451
|
return config.__xpm__._sealed, config
|
|
408
452
|
|
|
409
|
-
def postprocess(self, stub, config:
|
|
453
|
+
def postprocess(self, stub, config: ConfigMixin, values):
|
|
410
454
|
config.__xpm__._sealed = False
|
|
411
455
|
config.__xpm__._identifier = None
|
|
412
456
|
|
|
413
457
|
Unsealer(context, recurse_task=True)(self.pyobject)
|
|
414
458
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
pre_tasks: Dict[int, "Config"] = {}
|
|
418
|
-
|
|
419
|
-
class PreTaskCollect(ConfigWalk):
|
|
420
|
-
def preprocess(self, config: Config):
|
|
421
|
-
# Do not cross tasks
|
|
422
|
-
return not isinstance(config.__xpm__, Task), config
|
|
423
|
-
|
|
424
|
-
def postprocess(self, stub, config: Config, values):
|
|
425
|
-
pre_tasks.update(
|
|
426
|
-
{id(pre_task): pre_task for pre_task in config.__xpm__.pre_tasks}
|
|
427
|
-
)
|
|
428
|
-
|
|
429
|
-
PreTaskCollect(context, recurse_task=True)(self.pyobject)
|
|
430
|
-
return pre_tasks.values()
|
|
431
|
-
|
|
432
|
-
def identifiers(self, only_raw: bool):
|
|
459
|
+
@property
|
|
460
|
+
def identifier(self):
|
|
433
461
|
"""Computes the unique identifier"""
|
|
434
|
-
from ..identifier import IdentifierComputer
|
|
435
|
-
|
|
436
|
-
raw_identifier = self._raw_identifier
|
|
437
|
-
full_identifier = self._full_identifier
|
|
462
|
+
from ..identifier import IdentifierComputer
|
|
438
463
|
|
|
439
464
|
# Computes raw identifier if needed
|
|
440
|
-
if
|
|
441
|
-
|
|
442
|
-
raw_identifier = IdentifierComputer.compute(self.pyobject)
|
|
443
|
-
if self._sealed:
|
|
444
|
-
self._raw_identifier = raw_identifier
|
|
445
|
-
|
|
446
|
-
if only_raw:
|
|
447
|
-
return raw_identifier, full_identifier
|
|
448
|
-
|
|
449
|
-
# OK, let's compute the full identifier
|
|
450
|
-
if full_identifier is None or not self._sealed:
|
|
451
|
-
# Compute the full identifier by including the pre-tasks
|
|
452
|
-
hasher = hashlib.sha256()
|
|
453
|
-
hasher.update(raw_identifier.all)
|
|
454
|
-
pre_tasks_ids = [
|
|
455
|
-
pre_task.__xpm__.raw_identifier.all
|
|
456
|
-
for pre_task in self.collect_pre_tasks()
|
|
457
|
-
]
|
|
458
|
-
for task_id in sorted(pre_tasks_ids):
|
|
459
|
-
hasher.update(task_id)
|
|
460
|
-
|
|
461
|
-
# Adds init tasks
|
|
462
|
-
if self.init_tasks:
|
|
463
|
-
hasher.update(IdentifierComputer.INIT_TASKS)
|
|
464
|
-
for init_task in self.init_tasks:
|
|
465
|
-
hasher.update(init_task.__xpm__.raw_identifier.all)
|
|
466
|
-
|
|
467
|
-
full_identifier = Identifier(hasher.digest())
|
|
468
|
-
full_identifier.has_loops = raw_identifier.has_loops
|
|
469
|
-
|
|
470
|
-
# Only cache the identifier if sealed
|
|
471
|
-
if self._sealed:
|
|
472
|
-
self._full_identifier = full_identifier
|
|
465
|
+
if self._identifier is not None:
|
|
466
|
+
return self._identifier
|
|
473
467
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
raw_identifier, _ = self.identifiers(True)
|
|
480
|
-
return raw_identifier
|
|
481
|
-
|
|
482
|
-
@property
|
|
483
|
-
def full_identifier(self) -> "Identifier":
|
|
484
|
-
"""Computes the unique identifier (with task modifiers)"""
|
|
485
|
-
_, full_identifier = self.identifiers(False)
|
|
486
|
-
return full_identifier
|
|
487
|
-
|
|
488
|
-
identifier = full_identifier
|
|
489
|
-
"""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
|
|
490
473
|
|
|
491
474
|
def dependency(self):
|
|
492
475
|
"""Returns a dependency"""
|
|
@@ -501,12 +484,6 @@ class ConfigInformation:
|
|
|
501
484
|
path: List[str],
|
|
502
485
|
taskids: Set[int],
|
|
503
486
|
):
|
|
504
|
-
# Add pre-tasks
|
|
505
|
-
for pre_task in self.pre_tasks:
|
|
506
|
-
pre_task.__xpm__.updatedependencies(
|
|
507
|
-
dependencies, path + ["__pre_tasks__"], taskids
|
|
508
|
-
)
|
|
509
|
-
|
|
510
487
|
# Add initialization tasks
|
|
511
488
|
for init_task in self.init_tasks:
|
|
512
489
|
init_task.__xpm__.updatedependencies(
|
|
@@ -776,9 +753,6 @@ class ConfigInformation:
|
|
|
776
753
|
if self.task is not None and self.task is not self:
|
|
777
754
|
ConfigInformation.__collect_objects__(self.task, objects, context)
|
|
778
755
|
|
|
779
|
-
# Serialize pre-tasks
|
|
780
|
-
ConfigInformation.__collect_objects__(self.pre_tasks, objects, context)
|
|
781
|
-
|
|
782
756
|
# Serialize initialization tasks
|
|
783
757
|
ConfigInformation.__collect_objects__(self.init_tasks, objects, context)
|
|
784
758
|
|
|
@@ -792,8 +766,6 @@ class ConfigInformation:
|
|
|
792
766
|
}
|
|
793
767
|
|
|
794
768
|
# Add pre/init tasks
|
|
795
|
-
if self.pre_tasks:
|
|
796
|
-
state_dict["pre-tasks"] = [id(pre_task) for pre_task in self.pre_tasks]
|
|
797
769
|
if self.init_tasks:
|
|
798
770
|
state_dict["init-tasks"] = [id(init_task) for init_task in self.init_tasks]
|
|
799
771
|
|
|
@@ -1125,12 +1097,6 @@ class ConfigInformation:
|
|
|
1125
1097
|
o.__post_init__()
|
|
1126
1098
|
|
|
1127
1099
|
else:
|
|
1128
|
-
# Sets pre-tasks
|
|
1129
|
-
o.__xpm__.pre_tasks = [
|
|
1130
|
-
objects[pre_task_id]
|
|
1131
|
-
for pre_task_id in definition.get("pre-tasks", [])
|
|
1132
|
-
]
|
|
1133
|
-
|
|
1134
1100
|
if task_id := definition.get("task", None):
|
|
1135
1101
|
o.__xpm__.task = objects[task_id]
|
|
1136
1102
|
|
|
@@ -1164,15 +1130,6 @@ class ConfigInformation:
|
|
|
1164
1130
|
|
|
1165
1131
|
# Run pre-task (or returns them)
|
|
1166
1132
|
if as_instance or return_tasks:
|
|
1167
|
-
# Collect pre-tasks (just once)
|
|
1168
|
-
completed_pretasks = set()
|
|
1169
|
-
pre_tasks = []
|
|
1170
|
-
for definition in definitions:
|
|
1171
|
-
for pre_task_id in definition.get("pre-tasks", []):
|
|
1172
|
-
if pre_task_id not in completed_pretasks:
|
|
1173
|
-
completed_pretasks.add(pre_task_id)
|
|
1174
|
-
pre_tasks.append(objects[pre_task_id])
|
|
1175
|
-
|
|
1176
1133
|
# Collect init tasks
|
|
1177
1134
|
init_tasks = []
|
|
1178
1135
|
for init_task_id in definitions[-1].get("init-tasks", []):
|
|
@@ -1180,14 +1137,11 @@ class ConfigInformation:
|
|
|
1180
1137
|
init_tasks.append(init_task)
|
|
1181
1138
|
|
|
1182
1139
|
if as_instance:
|
|
1183
|
-
for pre_task in pre_tasks:
|
|
1184
|
-
logger.info("Executing pre-task %s", type(pre_task))
|
|
1185
|
-
pre_task.execute()
|
|
1186
1140
|
for init_task in init_tasks:
|
|
1187
1141
|
logger.info("Executing init task %s", type(init_task))
|
|
1188
1142
|
init_task.execute()
|
|
1189
1143
|
else:
|
|
1190
|
-
return o,
|
|
1144
|
+
return o, init_tasks
|
|
1191
1145
|
|
|
1192
1146
|
return o
|
|
1193
1147
|
|
|
@@ -1195,7 +1149,6 @@ class ConfigInformation:
|
|
|
1195
1149
|
def __init__(self, context: ConfigWalkContext, *, objects: ObjectStore = None):
|
|
1196
1150
|
super().__init__(context)
|
|
1197
1151
|
self.objects = ObjectStore() if objects is None else objects
|
|
1198
|
-
self.pre_tasks = {}
|
|
1199
1152
|
|
|
1200
1153
|
def preprocess(self, config: "Config"):
|
|
1201
1154
|
if self.objects.is_constructed(id(config)):
|
|
@@ -1222,10 +1175,6 @@ class ConfigInformation:
|
|
|
1222
1175
|
# Call __post_init__
|
|
1223
1176
|
stub.__post_init__()
|
|
1224
1177
|
|
|
1225
|
-
# Gather pre-tasks
|
|
1226
|
-
for pre_task in config.__xpm__.pre_tasks:
|
|
1227
|
-
self.pre_tasks[id(pre_task)] = self.stub(pre_task)
|
|
1228
|
-
|
|
1229
1178
|
self.objects.set_constructed(id(config))
|
|
1230
1179
|
return stub
|
|
1231
1180
|
|
|
@@ -1239,10 +1188,6 @@ class ConfigInformation:
|
|
|
1239
1188
|
processor = ConfigInformation.FromPython(context, objects=objects)
|
|
1240
1189
|
last_object = processor(self.pyobject)
|
|
1241
1190
|
|
|
1242
|
-
# Execute pre-tasks
|
|
1243
|
-
for pre_task in processor.pre_tasks.values():
|
|
1244
|
-
pre_task.execute()
|
|
1245
|
-
|
|
1246
1191
|
return last_object
|
|
1247
1192
|
|
|
1248
1193
|
def add_dependencies(self, *dependencies):
|
|
@@ -1428,29 +1373,7 @@ class ConfigMixin:
|
|
|
1428
1373
|
attributes)"""
|
|
1429
1374
|
return clone(self)
|
|
1430
1375
|
|
|
1431
|
-
def
|
|
1432
|
-
assert all(
|
|
1433
|
-
[isinstance(task, LightweightTask) for task in tasks]
|
|
1434
|
-
), "One of the pre-tasks are not lightweight tasks"
|
|
1435
|
-
if self.__xpm__._sealed:
|
|
1436
|
-
raise SealedError("Cannot add pre-tasks to a sealed configuration")
|
|
1437
|
-
self.__xpm__.pre_tasks.extend(tasks)
|
|
1438
|
-
return self
|
|
1439
|
-
|
|
1440
|
-
def add_pretasks_from(self, *configs: "Config"):
|
|
1441
|
-
assert all(
|
|
1442
|
-
[isinstance(config, ConfigMixin) for config in configs]
|
|
1443
|
-
), "One of the parameters is not a configuration object"
|
|
1444
|
-
for config in configs:
|
|
1445
|
-
self.add_pretasks(*config.__xpm__.pre_tasks)
|
|
1446
|
-
return self
|
|
1447
|
-
|
|
1448
|
-
@property
|
|
1449
|
-
def pre_tasks(self) -> List["LightweightTask"]:
|
|
1450
|
-
"""Access pre-tasks"""
|
|
1451
|
-
return self.__xpm__.pre_tasks
|
|
1452
|
-
|
|
1453
|
-
def copy_dependencies(self, other: "Config"):
|
|
1376
|
+
def copy_dependencies(self, other: "ConfigMixin"):
|
|
1454
1377
|
"""Add all the dependencies from other configuration"""
|
|
1455
1378
|
|
|
1456
1379
|
# Add task dependency
|
|
@@ -1587,27 +1510,12 @@ class Config:
|
|
|
1587
1510
|
def __identifier__(self) -> "Identifier":
|
|
1588
1511
|
return self.__xpm__.full_identifier
|
|
1589
1512
|
|
|
1590
|
-
def add_pretasks(self, *tasks: "LightweightTask"):
|
|
1591
|
-
"""Add pre-tasks"""
|
|
1592
|
-
raise AssertionError("This method can only be used during configuration")
|
|
1593
|
-
|
|
1594
|
-
def add_pretasks_from(self, *configs: "Config"):
|
|
1595
|
-
"""Add pre-tasks from the listed configurations"""
|
|
1596
|
-
raise AssertionError(
|
|
1597
|
-
"The 'add_pretasks_from' can only be used during configuration"
|
|
1598
|
-
)
|
|
1599
|
-
|
|
1600
1513
|
def copy_dependencies(self, other: "Config"):
|
|
1601
1514
|
"""Add pre-tasks from the listed configurations"""
|
|
1602
1515
|
raise AssertionError(
|
|
1603
1516
|
"The 'copy_dependencies' method can only be used during configuration"
|
|
1604
1517
|
)
|
|
1605
1518
|
|
|
1606
|
-
@property
|
|
1607
|
-
def pre_tasks(self) -> List["LightweightTask"]:
|
|
1608
|
-
"""Access pre-tasks"""
|
|
1609
|
-
raise AssertionError("Pre-tasks can be accessed only during configuration")
|
|
1610
|
-
|
|
1611
1519
|
def register_task_output(self, method, *args, **kwargs):
|
|
1612
1520
|
# Determine the path for this...
|
|
1613
1521
|
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)))
|
|
@@ -85,9 +85,3 @@ def test_dependencies_inner_task_output(xp):
|
|
|
85
85
|
a = task_a.submit()
|
|
86
86
|
b = Inner_TaskB(param_a=a).submit()
|
|
87
87
|
check_dependencies(b, task_a)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def test_dependencies_pre_task(xp):
|
|
91
|
-
a = TaskA().submit()
|
|
92
|
-
a2 = TaskA().add_pretasks(a).submit()
|
|
93
|
-
check_dependencies(a2, a)
|
|
@@ -13,8 +13,35 @@ class Learner(Task):
|
|
|
13
13
|
validation: Param[Validation]
|
|
14
14
|
x: Param[int]
|
|
15
15
|
|
|
16
|
+
@staticmethod
|
|
17
|
+
def create(x: int, validation: Param[Validation]):
|
|
18
|
+
return Learner.C(x=x, validation=validation)
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
|
|
21
|
+
class LearnerList(Task):
|
|
22
|
+
validation: Param[list[Validation]]
|
|
23
|
+
x: Param[int]
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def create(x: int, validation: Param[Validation]):
|
|
27
|
+
return LearnerList.C(x=x, validation=[validation])
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class LearnerDict(Task):
|
|
31
|
+
validation: Param[dict[str, Validation]]
|
|
32
|
+
x: Param[int]
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def create(x: int, validation: Param[Validation]):
|
|
36
|
+
return LearnerDict.C(x=x, validation={"key": validation})
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ModuleLoader(Task):
|
|
40
|
+
validation: Param[Validation] = field(ignore_generated=True)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.mark.parametrize("cls", [Learner, LearnerDict, LearnerList])
|
|
44
|
+
def test_generators_reuse_on_submit(cls):
|
|
18
45
|
# We have one way to select the best model
|
|
19
46
|
validation = Validation.C()
|
|
20
47
|
|
|
@@ -25,9 +52,42 @@ def test_generators_reuse():
|
|
|
25
52
|
)
|
|
26
53
|
|
|
27
54
|
# OK, the path is generated depending on Learner with x=1
|
|
28
|
-
|
|
55
|
+
cls.create(1, validation).submit(workspace=workspace)
|
|
29
56
|
|
|
30
57
|
with pytest.raises((AttributeError)):
|
|
31
58
|
# Here we have a problem...
|
|
32
59
|
# the path is still the previous one
|
|
33
|
-
|
|
60
|
+
cls.create(2, validation).submit(workspace=workspace)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.mark.parametrize("cls", [Learner, LearnerDict, LearnerList])
|
|
64
|
+
def test_generators_delayed_submit(cls):
|
|
65
|
+
workspace = Workspace(
|
|
66
|
+
Settings(),
|
|
67
|
+
WorkspaceSettings("test_generators_simple", path=Path("/tmp")),
|
|
68
|
+
run_mode=RunMode.DRY_RUN,
|
|
69
|
+
)
|
|
70
|
+
validation = Validation.C()
|
|
71
|
+
task1 = cls.create(1, validation)
|
|
72
|
+
task2 = cls.create(2, validation)
|
|
73
|
+
task1.submit(workspace=workspace)
|
|
74
|
+
with pytest.raises((AttributeError)):
|
|
75
|
+
task2.submit(workspace=workspace)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@pytest.mark.parametrize("cls", [Learner, LearnerDict, LearnerList])
|
|
79
|
+
def test_generators_reuse_on_set(cls):
|
|
80
|
+
workspace = Workspace(
|
|
81
|
+
Settings(),
|
|
82
|
+
WorkspaceSettings("test_generators_simple", path=Path("/tmp")),
|
|
83
|
+
run_mode=RunMode.DRY_RUN,
|
|
84
|
+
)
|
|
85
|
+
validation = Validation.C()
|
|
86
|
+
cls.create(1, validation).submit(workspace=workspace)
|
|
87
|
+
with pytest.raises((AttributeError)):
|
|
88
|
+
# We should not be able to *create* a second task with the same validation,
|
|
89
|
+
# even without submitting it
|
|
90
|
+
cls.create(2, validation)
|
|
91
|
+
|
|
92
|
+
# This should run OK
|
|
93
|
+
ModuleLoader.C(validation=validation)
|
|
@@ -68,42 +68,42 @@ def assert_notequal(a, b, message=""):
|
|
|
68
68
|
assert getidentifier(a) != getidentifier(b), message
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
def
|
|
72
|
-
assert_equal(A(a=1), A(a=1))
|
|
71
|
+
def test_identifier_int():
|
|
72
|
+
assert_equal(A.C(a=1), A.C(a=1))
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
def
|
|
76
|
-
assert_notequal(A(a=1), B(a=1))
|
|
75
|
+
def test_identifier_different_type():
|
|
76
|
+
assert_notequal(A.C(a=1), B.C(a=1))
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
def
|
|
80
|
-
assert_equal(Values(value1=1, value2=2), Values(value2=2, value1=1))
|
|
79
|
+
def test_identifier_order():
|
|
80
|
+
assert_equal(Values.C(value1=1, value2=2), Values.C(value2=2, value1=1))
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
def
|
|
84
|
-
assert_equal(C(a=1, b=2), C(b=2))
|
|
83
|
+
def test_identifier_default():
|
|
84
|
+
assert_equal(C.C(a=1, b=2), C.C(b=2))
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
def test_identifier_default_field():
|
|
88
88
|
assert_equal(CField(a=1, b=2), CField(b=2))
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
def
|
|
92
|
-
assert_equal(D(a=A(a=1)), D(a=A(a=1)))
|
|
91
|
+
def test_identifier_inner_eq():
|
|
92
|
+
assert_equal(D.C(a=A.C(a=1)), D.C(a=A.C(a=1)))
|
|
93
93
|
|
|
94
94
|
|
|
95
|
-
def
|
|
96
|
-
assert_equal(Float(value=1), Float(value=1))
|
|
95
|
+
def test_identifier_float():
|
|
96
|
+
assert_equal(Float.C(value=1), Float.C(value=1))
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
def
|
|
100
|
-
assert_equal(Float(value=1.0), Float(value=1))
|
|
99
|
+
def test_identifier_float2():
|
|
100
|
+
assert_equal(Float.C(value=1.0), Float.C(value=1))
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
# --- Argument name
|
|
104
104
|
|
|
105
105
|
|
|
106
|
-
def
|
|
106
|
+
def test_identifier_name():
|
|
107
107
|
"""The identifier fully determines the hash code"""
|
|
108
108
|
|
|
109
109
|
class Config0(Config):
|
|
@@ -125,7 +125,7 @@ def test_param_name():
|
|
|
125
125
|
# --- Test option
|
|
126
126
|
|
|
127
127
|
|
|
128
|
-
def
|
|
128
|
+
def test_identifier_option():
|
|
129
129
|
class OptionConfig(Config):
|
|
130
130
|
__xpmid__ = "test.identifier.option"
|
|
131
131
|
a: Param[int]
|
|
@@ -139,7 +139,7 @@ def test_param_option():
|
|
|
139
139
|
# --- Dictionnary
|
|
140
140
|
|
|
141
141
|
|
|
142
|
-
def
|
|
142
|
+
def test_identifier_dict():
|
|
143
143
|
"""Test identifiers of dictionary structures"""
|
|
144
144
|
|
|
145
145
|
class B(Config):
|
|
@@ -163,7 +163,7 @@ class TypeWithPath(Config):
|
|
|
163
163
|
path: Param[Path]
|
|
164
164
|
|
|
165
165
|
|
|
166
|
-
def
|
|
166
|
+
def test_identifier_path():
|
|
167
167
|
"""Path should be ignored"""
|
|
168
168
|
assert_equal(TypeWithPath(a=1, path="/a/b"), TypeWithPath(a=1, path="/c/d"))
|
|
169
169
|
assert_notequal(TypeWithPath(a=2, path="/a/b"), TypeWithPath(a=1, path="/c/d"))
|
|
@@ -172,7 +172,7 @@ def test_param_identifier_path():
|
|
|
172
172
|
# --- Test with added arguments
|
|
173
173
|
|
|
174
174
|
|
|
175
|
-
def
|
|
175
|
+
def test_identifier_pathoption():
|
|
176
176
|
"""Path arguments should be ignored"""
|
|
177
177
|
|
|
178
178
|
class A_with_path(Config):
|
|
@@ -187,7 +187,7 @@ def test_param_identifier_pathoption():
|
|
|
187
187
|
assert_equal(A_with_path(a=1), A_without_path(a=1))
|
|
188
188
|
|
|
189
189
|
|
|
190
|
-
def
|
|
190
|
+
def test_identifier_enum():
|
|
191
191
|
"""test enum parameters"""
|
|
192
192
|
from enum import Enum
|
|
193
193
|
|
|
@@ -202,7 +202,7 @@ def test_param_identifier_enum():
|
|
|
202
202
|
assert_equal(EnumConfig(a=EnumParam.FIRST), EnumConfig(a=EnumParam.FIRST))
|
|
203
203
|
|
|
204
204
|
|
|
205
|
-
def
|
|
205
|
+
def test_identifier_addnone():
|
|
206
206
|
"""Test the case of new parameter (with None default)"""
|
|
207
207
|
|
|
208
208
|
class B(Config):
|
|
@@ -219,7 +219,7 @@ def test_param_identifier_addnone():
|
|
|
219
219
|
assert_notequal(A_with_b(b=B(x=1)), A())
|
|
220
220
|
|
|
221
221
|
|
|
222
|
-
def
|
|
222
|
+
def test_identifier_defaultnew():
|
|
223
223
|
"""Path arguments should be ignored"""
|
|
224
224
|
|
|
225
225
|
class A_with_b(Config):
|
|
@@ -236,7 +236,7 @@ def test_param_defaultnew():
|
|
|
236
236
|
assert_equal(A_with_b(a=1), A(a=1))
|
|
237
237
|
|
|
238
238
|
|
|
239
|
-
def
|
|
239
|
+
def test_identifier_taskconfigidentifier():
|
|
240
240
|
"""Test whether the embedded task arguments make the configuration different"""
|
|
241
241
|
|
|
242
242
|
class MyConfig(Config):
|
|
@@ -258,7 +258,7 @@ def test_param_taskconfigidentifier():
|
|
|
258
258
|
)
|
|
259
259
|
|
|
260
260
|
|
|
261
|
-
def
|
|
261
|
+
def test_identifier_constant():
|
|
262
262
|
"""Test if constants are taken into account for signature computation"""
|
|
263
263
|
|
|
264
264
|
class A1(Config):
|
|
@@ -278,7 +278,7 @@ def test_param_constant():
|
|
|
278
278
|
assert_notequal(A1(), A2())
|
|
279
279
|
|
|
280
280
|
|
|
281
|
-
def
|
|
281
|
+
def test_identifier_deprecated_class():
|
|
282
282
|
"""Test that when submitting the task, the computed identifier is the one of
|
|
283
283
|
the new class"""
|
|
284
284
|
|
|
@@ -300,7 +300,7 @@ def test_param_identifier_deprecated_class():
|
|
|
300
300
|
)
|
|
301
301
|
|
|
302
302
|
|
|
303
|
-
def
|
|
303
|
+
def test_identifier_deprecated_attribute():
|
|
304
304
|
class Values(Config):
|
|
305
305
|
values: Param[List[int]] = []
|
|
306
306
|
|
|
@@ -315,7 +315,7 @@ class MetaA(Config):
|
|
|
315
315
|
x: Param[int]
|
|
316
316
|
|
|
317
317
|
|
|
318
|
-
def
|
|
318
|
+
def test_identifier_meta():
|
|
319
319
|
"""Test forced meta-parameter"""
|
|
320
320
|
|
|
321
321
|
class B(Config):
|
|
@@ -354,7 +354,7 @@ def test_param_identifier_meta():
|
|
|
354
354
|
)
|
|
355
355
|
|
|
356
356
|
|
|
357
|
-
def
|
|
357
|
+
def test_identifier_meta_default_dict():
|
|
358
358
|
class DictConfig(Config):
|
|
359
359
|
params: Param[Dict[str, MetaA]] = {}
|
|
360
360
|
|
|
@@ -370,7 +370,7 @@ def test_param_identifier_meta_default_dict():
|
|
|
370
370
|
)
|
|
371
371
|
|
|
372
372
|
|
|
373
|
-
def
|
|
373
|
+
def test_identifier_meta_default_array():
|
|
374
374
|
class ArrayConfigWithDefault(Config):
|
|
375
375
|
array: Param[List[MetaA]] = []
|
|
376
376
|
|
|
@@ -386,37 +386,7 @@ def test_param_identifier_meta_default_array():
|
|
|
386
386
|
)
|
|
387
387
|
|
|
388
388
|
|
|
389
|
-
def
|
|
390
|
-
class MyConfig(Config):
|
|
391
|
-
pass
|
|
392
|
-
|
|
393
|
-
class IdentifierPreLightTask(LightweightTask):
|
|
394
|
-
pass
|
|
395
|
-
|
|
396
|
-
class IdentifierPreTask(Task):
|
|
397
|
-
x: Param[MyConfig]
|
|
398
|
-
|
|
399
|
-
task = IdentifierPreTask(x=MyConfig()).submit(run_mode=RunMode.DRY_RUN)
|
|
400
|
-
task_with_pre = (
|
|
401
|
-
IdentifierPreTask(x=MyConfig())
|
|
402
|
-
.add_pretasks(IdentifierPreLightTask())
|
|
403
|
-
.submit(run_mode=RunMode.DRY_RUN)
|
|
404
|
-
)
|
|
405
|
-
task_with_pre_2 = (
|
|
406
|
-
IdentifierPreTask(x=MyConfig())
|
|
407
|
-
.add_pretasks(IdentifierPreLightTask())
|
|
408
|
-
.submit(run_mode=RunMode.DRY_RUN)
|
|
409
|
-
)
|
|
410
|
-
task_with_pre_3 = IdentifierPreTask(
|
|
411
|
-
x=MyConfig().add_pretasks(IdentifierPreLightTask())
|
|
412
|
-
).submit(run_mode=RunMode.DRY_RUN)
|
|
413
|
-
|
|
414
|
-
assert_notequal(task, task_with_pre, "No pre-task")
|
|
415
|
-
assert_equal(task_with_pre, task_with_pre_2, "Same parameters")
|
|
416
|
-
assert_equal(task_with_pre, task_with_pre_3, "Pre-tasks are order-less")
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
def test_param_identifier_init_task():
|
|
389
|
+
def test_identifier_init_task():
|
|
420
390
|
class MyConfig(Config):
|
|
421
391
|
pass
|
|
422
392
|
|
|
@@ -426,26 +396,67 @@ def test_param_identifier_init_task():
|
|
|
426
396
|
class IdentifierInitTask2(Task):
|
|
427
397
|
pass
|
|
428
398
|
|
|
429
|
-
class
|
|
399
|
+
class IdentifierTask(Task):
|
|
430
400
|
x: Param[MyConfig]
|
|
431
401
|
|
|
432
|
-
task =
|
|
433
|
-
task_with_pre =
|
|
402
|
+
task = IdentifierTask.C(x=MyConfig.C()).submit(run_mode=RunMode.DRY_RUN)
|
|
403
|
+
task_with_pre = IdentifierTask.C(x=MyConfig.C()).submit(
|
|
434
404
|
run_mode=RunMode.DRY_RUN,
|
|
435
405
|
init_tasks=[IdentifierInitTask(), IdentifierInitTask2()],
|
|
436
406
|
)
|
|
437
|
-
task_with_pre_2 =
|
|
407
|
+
task_with_pre_2 = IdentifierTask.C(x=MyConfig.C()).submit(
|
|
438
408
|
run_mode=RunMode.DRY_RUN,
|
|
439
409
|
init_tasks=[IdentifierInitTask(), IdentifierInitTask2()],
|
|
440
410
|
)
|
|
441
|
-
task_with_pre_3 =
|
|
411
|
+
task_with_pre_3 = IdentifierTask.C(x=MyConfig.C()).submit(
|
|
442
412
|
run_mode=RunMode.DRY_RUN,
|
|
443
413
|
init_tasks=[IdentifierInitTask2(), IdentifierInitTask()],
|
|
444
414
|
)
|
|
445
415
|
|
|
446
|
-
assert_notequal(task, task_with_pre, "
|
|
416
|
+
assert_notequal(task, task_with_pre, "Should be different with init-task")
|
|
447
417
|
assert_equal(task_with_pre, task_with_pre_2, "Same parameters")
|
|
448
|
-
assert_notequal(task_with_pre, task_with_pre_3, "
|
|
418
|
+
assert_notequal(task_with_pre, task_with_pre_3, "Other parameters")
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def test_identifier_init_task_dep():
|
|
422
|
+
class Loader(LightweightTask):
|
|
423
|
+
param1: Param[float]
|
|
424
|
+
|
|
425
|
+
def execute(self):
|
|
426
|
+
pass
|
|
427
|
+
|
|
428
|
+
class FirstTask(Task):
|
|
429
|
+
def task_outputs(self, dep):
|
|
430
|
+
return dep(Loader.C(param1=1))
|
|
431
|
+
|
|
432
|
+
def execute(self):
|
|
433
|
+
pass
|
|
434
|
+
|
|
435
|
+
class SecondTask(Task):
|
|
436
|
+
param3: Param[int]
|
|
437
|
+
|
|
438
|
+
def execute(self):
|
|
439
|
+
pass
|
|
440
|
+
|
|
441
|
+
# Two identical tasks
|
|
442
|
+
task_a_1 = FirstTask.C()
|
|
443
|
+
task_a_2 = FirstTask.C()
|
|
444
|
+
assert_equal(task_a_1, task_a_2)
|
|
445
|
+
|
|
446
|
+
# We process them with two different init tasks
|
|
447
|
+
loader_1 = task_a_1.submit(
|
|
448
|
+
init_tasks=[Loader.C(param1=0.5)], run_mode=RunMode.DRY_RUN
|
|
449
|
+
)
|
|
450
|
+
loader_2 = task_a_2.submit(
|
|
451
|
+
init_tasks=[Loader.C(param1=5)], run_mode=RunMode.DRY_RUN
|
|
452
|
+
)
|
|
453
|
+
assert_notequal(loader_1, loader_2)
|
|
454
|
+
|
|
455
|
+
# Now, we process
|
|
456
|
+
c_1 = SecondTask.C(param3=2).submit(init_tasks=[loader_1], run_mode=RunMode.DRY_RUN)
|
|
457
|
+
|
|
458
|
+
c_2 = SecondTask.C(param3=2).submit(init_tasks=[loader_2], run_mode=RunMode.DRY_RUN)
|
|
459
|
+
assert_notequal(c_1, c_2)
|
|
449
460
|
|
|
450
461
|
|
|
451
462
|
# --- Check configuration reloads
|
|
@@ -463,7 +474,7 @@ def check_reload(config):
|
|
|
463
474
|
new_config = ConfigInformation.fromParameters(
|
|
464
475
|
data, as_instance=False, discard_id=True
|
|
465
476
|
)
|
|
466
|
-
assert new_config.__xpm__.
|
|
477
|
+
assert new_config.__xpm__._identifier is None
|
|
467
478
|
new_identifier = new_config.__xpm__.identifier.all
|
|
468
479
|
|
|
469
480
|
assert new_identifier == old_identifier
|
|
@@ -473,7 +484,7 @@ class IdentifierReloadConfig(Config):
|
|
|
473
484
|
id: Param[str]
|
|
474
485
|
|
|
475
486
|
|
|
476
|
-
def
|
|
487
|
+
def test_identifier_reload_config():
|
|
477
488
|
# Creates the configuration
|
|
478
489
|
check_reload(IdentifierReloadConfig(id="123"))
|
|
479
490
|
|
|
@@ -489,7 +500,7 @@ class IdentifierReloadDerived(Config):
|
|
|
489
500
|
task: Param[IdentifierReloadConfig]
|
|
490
501
|
|
|
491
502
|
|
|
492
|
-
def
|
|
503
|
+
def test_identifier_reload_taskoutput():
|
|
493
504
|
"""When using a task output, the identifier should not be different"""
|
|
494
505
|
|
|
495
506
|
# Creates the configuration
|
|
@@ -511,7 +522,7 @@ class IdentifierReloadTaskDerived(Config):
|
|
|
511
522
|
other: Param[IdentifierReloadTaskConfig]
|
|
512
523
|
|
|
513
524
|
|
|
514
|
-
def
|
|
525
|
+
def test_identifier_reload_task_direct():
|
|
515
526
|
"""When using a direct task output, the identifier should not be different"""
|
|
516
527
|
|
|
517
528
|
# Creates the configuration
|
|
@@ -522,7 +533,7 @@ def test_param_identifier_reload_task_direct():
|
|
|
522
533
|
check_reload(config)
|
|
523
534
|
|
|
524
535
|
|
|
525
|
-
def
|
|
536
|
+
def test_identifier_reload_meta():
|
|
526
537
|
"""Test identifier don't change when using meta"""
|
|
527
538
|
# Creates the configuration
|
|
528
539
|
task = IdentifierReloadTask(id="123").submit(run_mode=RunMode.DRY_RUN)
|
|
@@ -545,10 +556,10 @@ class LoopC(Config):
|
|
|
545
556
|
param_b: Param["LoopB"]
|
|
546
557
|
|
|
547
558
|
|
|
548
|
-
def
|
|
549
|
-
c = LoopC()
|
|
550
|
-
b = LoopB(param_c=c)
|
|
551
|
-
a = LoopA(param_b=b)
|
|
559
|
+
def test_identifier_loop():
|
|
560
|
+
c = LoopC.C()
|
|
561
|
+
b = LoopB.C(param_c=c)
|
|
562
|
+
a = LoopA.C(param_b=b)
|
|
552
563
|
c.param_a = a
|
|
553
564
|
c.param_b = b
|
|
554
565
|
|
|
@@ -46,18 +46,6 @@ class LoadModel(SerializationLWTask):
|
|
|
46
46
|
self.value.initialized = True
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def test_instance_serialized():
|
|
50
|
-
model = Model()
|
|
51
|
-
model.add_pretasks(LoadModel(value=model))
|
|
52
|
-
trainer = Evaluator(model=model)
|
|
53
|
-
instance = trainer.instance()
|
|
54
|
-
|
|
55
|
-
assert isinstance(
|
|
56
|
-
instance.model, Model
|
|
57
|
-
), f"The model is not a Model but a {type(instance.model).__qualname__}"
|
|
58
|
-
assert instance.model.initialized, "The model was not initialized"
|
|
59
|
-
|
|
60
|
-
|
|
61
49
|
class ConfigWithOptional(Config):
|
|
62
50
|
x: Param[int] = 1
|
|
63
51
|
y: Param[Optional[int]]
|
|
@@ -1,71 +1,12 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
from experimaestro import (
|
|
3
3
|
Config,
|
|
4
|
-
Task,
|
|
5
4
|
Param,
|
|
6
|
-
SerializationLWTask,
|
|
7
|
-
copyconfig,
|
|
8
5
|
state_dict,
|
|
9
6
|
from_state_dict,
|
|
10
7
|
)
|
|
11
8
|
from experimaestro.core.context import SerializationContext
|
|
12
9
|
from experimaestro.core.objects import ConfigMixin
|
|
13
|
-
from experimaestro.tests.utils import TemporaryExperiment
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class SubModel(Config):
|
|
17
|
-
pass
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class Model(Config):
|
|
21
|
-
submodel: Param[SubModel]
|
|
22
|
-
|
|
23
|
-
def __post_init__(self):
|
|
24
|
-
self.initialized = False
|
|
25
|
-
self.submodel.initialized = False
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class LoadModel(SerializationLWTask):
|
|
29
|
-
def execute(self):
|
|
30
|
-
self.value.initialized = True
|
|
31
|
-
self.value.submodel.initialized = True
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class Trainer(Task):
|
|
35
|
-
model: Param[Config]
|
|
36
|
-
|
|
37
|
-
def task_outputs(self, dep):
|
|
38
|
-
model = copyconfig(self.model)
|
|
39
|
-
return model.add_pretasks(dep(LoadModel(value=model)))
|
|
40
|
-
|
|
41
|
-
def execute(self):
|
|
42
|
-
assert not self.model.initialized, "Model not initialized"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class Evaluate(Task):
|
|
46
|
-
model: Param[Config]
|
|
47
|
-
is_submodel: Param[bool] = False
|
|
48
|
-
|
|
49
|
-
def execute(self):
|
|
50
|
-
assert self.model.initialized, "Model not initialized"
|
|
51
|
-
if self.is_submodel:
|
|
52
|
-
assert isinstance(self.model, SubModel)
|
|
53
|
-
else:
|
|
54
|
-
assert isinstance(self.model, Model)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def test_serializers_xp():
|
|
58
|
-
with TemporaryExperiment("serializers", maxwait=20, port=0):
|
|
59
|
-
model = Model(submodel=SubModel())
|
|
60
|
-
trained_model: Model = Trainer(model=model).submit()
|
|
61
|
-
|
|
62
|
-
# Use the model itself
|
|
63
|
-
Evaluate(model=trained_model).submit()
|
|
64
|
-
|
|
65
|
-
# Use a submodel
|
|
66
|
-
Evaluate(model=trained_model.submodel, is_submodel=True).add_pretasks_from(
|
|
67
|
-
trained_model
|
|
68
|
-
).submit()
|
|
69
10
|
|
|
70
11
|
|
|
71
12
|
class Object1(Config):
|
|
@@ -299,26 +299,6 @@ class MyLightweightTask(Task):
|
|
|
299
299
|
assert self.x.data == 1
|
|
300
300
|
|
|
301
301
|
|
|
302
|
-
def test_task_lightweight():
|
|
303
|
-
with TemporaryExperiment("lightweight", maxwait=20):
|
|
304
|
-
x = LightweightConfig()
|
|
305
|
-
lwtask = LightweightTask(x=x)
|
|
306
|
-
assert (
|
|
307
|
-
MyLightweightTask(x=x).add_pretasks(lwtask).submit().__xpm__.job.wait()
|
|
308
|
-
== JobState.DONE
|
|
309
|
-
), "Pre-tasks should be executed"
|
|
310
|
-
|
|
311
|
-
x_2 = LightweightConfig()
|
|
312
|
-
lwtask_2 = LightweightTask(x=x)
|
|
313
|
-
assert (
|
|
314
|
-
MyLightweightTask(x=x_2.add_pretasks(lwtask_2))
|
|
315
|
-
.add_pretasks(lwtask_2)
|
|
316
|
-
.submit()
|
|
317
|
-
.__xpm__.job.wait()
|
|
318
|
-
== JobState.DONE
|
|
319
|
-
), "Pre-tasks should be run just once"
|
|
320
|
-
|
|
321
|
-
|
|
322
302
|
def test_task_lightweight_init():
|
|
323
303
|
with TemporaryExperiment("lightweight_init", maxwait=20):
|
|
324
304
|
x = LightweightConfig()
|
|
@@ -26,7 +26,7 @@ def test_multiple_inheritance():
|
|
|
26
26
|
|
|
27
27
|
for C in (C1, C2):
|
|
28
28
|
logging.info("Testing %s", C)
|
|
29
|
-
ctype = C.__xpmtype__
|
|
29
|
+
ctype = C.C.__xpmtype__
|
|
30
30
|
assert issubclass(C, A)
|
|
31
31
|
assert issubclass(C, B)
|
|
32
32
|
assert issubclass(C, B1)
|
|
@@ -49,7 +49,7 @@ def test_missing_hierarchy():
|
|
|
49
49
|
class B(A1):
|
|
50
50
|
pass
|
|
51
51
|
|
|
52
|
-
B.__xpmtype__
|
|
52
|
+
B.C.__xpmtype__
|
|
53
53
|
|
|
54
54
|
assert issubclass(B, A)
|
|
55
55
|
assert issubclass(B, A1)
|
|
@@ -12,17 +12,17 @@ experimaestro/connectors/__init__.py,sha256=UKhDU3uv9jFH37oUb0JiejrekA85xtEirn79
|
|
|
12
12
|
experimaestro/connectors/local.py,sha256=lCGIubqmUJZ1glLtLRXOgakTMfEaEmFtNkEcw9qV5vw,6143
|
|
13
13
|
experimaestro/connectors/ssh.py,sha256=5giqvv1y0QQKF-GI0IFUzI_Z5H8Bj9EuL_Szpvk899Q,8600
|
|
14
14
|
experimaestro/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
experimaestro/core/arguments.py,sha256=
|
|
15
|
+
experimaestro/core/arguments.py,sha256=jlK2_EILKLblLSomUDjlGmR-_xq3rAZPgAwbUjU4boc,6710
|
|
16
16
|
experimaestro/core/callbacks.py,sha256=59JfeUgWcCCdIQ3pvh-xNnoRp9BX8f4iOAkgm16wBzE,1660
|
|
17
17
|
experimaestro/core/context.py,sha256=1tLmX7WcgEKSbGw77vfziTzS8KNsoZJ02JBWMBCqqOk,2606
|
|
18
|
-
experimaestro/core/identifier.py,sha256=
|
|
18
|
+
experimaestro/core/identifier.py,sha256=5r2F425yX7Sfeub3EJZCs2Q4Qggifgi4uxMGcpI-ex0,10473
|
|
19
19
|
experimaestro/core/objects/__init__.py,sha256=ucJY5e17QQ1Kc-GYXeL7g8GFj8rP0XB4g2vrl32uhxY,721
|
|
20
|
-
experimaestro/core/objects/config.py,sha256=
|
|
20
|
+
experimaestro/core/objects/config.py,sha256=dYDEqB_6WtTK51ECCkBmBQTBkKZbuAOFA-giVpyc1lU,54695
|
|
21
21
|
experimaestro/core/objects/config_utils.py,sha256=ZLECGkeIWdzunm8vwWsQhvcSgV1e064BgXbLiZnxSEM,1288
|
|
22
|
-
experimaestro/core/objects/config_walk.py,sha256=
|
|
23
|
-
experimaestro/core/objects.pyi,sha256=
|
|
22
|
+
experimaestro/core/objects/config_walk.py,sha256=SYBrGuT7HV12no9mQd1HjnebiyygHyO-zq_TO1brUtc,4233
|
|
23
|
+
experimaestro/core/objects.pyi,sha256=Q1tq4F8LrExPm00E-P5aaygxMvViYZqhHvByo_TTZRE,6315
|
|
24
24
|
experimaestro/core/serialization.py,sha256=CSPEwOzlDsgAz6V2og-TgyU0RXDtzt_nXaoXFZleDZE,5775
|
|
25
|
-
experimaestro/core/serializers.py,sha256=
|
|
25
|
+
experimaestro/core/serializers.py,sha256=iOBuASEgD8dRKPnL16iOLBsM0GHChCJgjBd7LixFui4,919
|
|
26
26
|
experimaestro/core/types.py,sha256=AH3ni1nIKGTeRmc7ECL6GH_dYqiuZp_mAN4E-JqdqnY,21741
|
|
27
27
|
experimaestro/core/utils.py,sha256=JfC3qGUS9b6FUHc2VxIYUI9ysNpXSQ1LjOBkjfZ8n7o,495
|
|
28
28
|
experimaestro/exceptions.py,sha256=cUy83WHM3GeynxmMk6QRr5xsnpqUAdAoc-m3KQVrE2o,44
|
|
@@ -117,24 +117,24 @@ experimaestro/tests/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
|
117
117
|
experimaestro/tests/tasks/all.py,sha256=OMkHsWZkErCmTajiNO7hNhvnk9eKzJC-VatWgabWlsI,1955
|
|
118
118
|
experimaestro/tests/tasks/foreign.py,sha256=uhXDOcWozkcm26ybbeEU9RElJpbhjC-zdzmlSKfPcdY,122
|
|
119
119
|
experimaestro/tests/test_checkers.py,sha256=Kg5frDNRE3pvWVmmYzyk0tJFNO885KOrK48lSu-NlYA,403
|
|
120
|
-
experimaestro/tests/test_dependencies.py,sha256=
|
|
120
|
+
experimaestro/tests/test_dependencies.py,sha256=fEmBNQaEkP8xb_P_5EfKIJflL-NCeSOOZk_FumlgBC0,1870
|
|
121
121
|
experimaestro/tests/test_experiment.py,sha256=QWF9aHewL9hepagrKKFyfikKn3iiZ_lRRXl1LLttta0,1687
|
|
122
122
|
experimaestro/tests/test_findlauncher.py,sha256=KPy8ow--NXS1KFCIpxrmEJFRvjo-v-PwlVHVyoVKLPg,3134
|
|
123
123
|
experimaestro/tests/test_forward.py,sha256=9y1zYm7hT_Lx5citxnK7n20cMZ2WJbsaEeY5irCZ9U4,735
|
|
124
|
-
experimaestro/tests/test_generators.py,sha256=
|
|
125
|
-
experimaestro/tests/test_identifier.py,sha256=
|
|
126
|
-
experimaestro/tests/test_instance.py,sha256=
|
|
124
|
+
experimaestro/tests/test_generators.py,sha256=R0UypTzxX0dPYvY4A_kozpLDHhGzQDNfLQyc0oRaSx8,2891
|
|
125
|
+
experimaestro/tests/test_identifier.py,sha256=REFB9RGyVlSdAPMpmvJ9a0WbAoe6ZTR1xl73lqtRFU0,14022
|
|
126
|
+
experimaestro/tests/test_instance.py,sha256=xLdSNVo_vAEaOWmjik1tAOYgyOkJPbmnwopyuDEQm1A,1147
|
|
127
127
|
experimaestro/tests/test_objects.py,sha256=zycxjvWuJAbPR8-q2T3zuBY9xfmlhf1YvtOcrImHxnc,2431
|
|
128
128
|
experimaestro/tests/test_outputs.py,sha256=DYzPk5TT_yLumy8SsQbl-S66ivVxJ-ERFrZ68KQZ4KU,781
|
|
129
129
|
experimaestro/tests/test_param.py,sha256=7BltCUm9uhtEupJZ-QSaj7MxuXoUl73NjE8NfzJD5BA,7290
|
|
130
130
|
experimaestro/tests/test_progress.py,sha256=wtIGQzlV3ldd_wMng11LinVESchW-1J954mCJNlG28E,7580
|
|
131
|
-
experimaestro/tests/test_serializers.py,sha256=
|
|
131
|
+
experimaestro/tests/test_serializers.py,sha256=dQkiuvHAQ1g-SCRCfOy977nQMWR7CFuBUud65N_vfiI,1248
|
|
132
132
|
experimaestro/tests/test_snippets.py,sha256=rojnyDjtmAMnSuDUj6Bv9XEgdP8oQf2nVc132JF8vsM,3081
|
|
133
133
|
experimaestro/tests/test_ssh.py,sha256=KS1NWltiXrJBSStY9d4mwrexeqgNGWmhxuAU_WLQDAU,1449
|
|
134
134
|
experimaestro/tests/test_tags.py,sha256=v_8_7LuEHfY_gfa0JRCUkmgJh8h6RMya_nd5NcPAJPw,2852
|
|
135
|
-
experimaestro/tests/test_tasks.py,sha256=
|
|
135
|
+
experimaestro/tests/test_tasks.py,sha256=eka9LiOMTp0LhRxSM4Khw1jAner6wgjF8sNDOHSqwnQ,8781
|
|
136
136
|
experimaestro/tests/test_tokens.py,sha256=U3nKBL1KftWhmk3dQZZLQd-MLJL_SscMjI3zMieMHfc,7823
|
|
137
|
-
experimaestro/tests/test_types.py,sha256=
|
|
137
|
+
experimaestro/tests/test_types.py,sha256=CVyAyiWgEeV1FpWc-oC8TUqGN4o-9eVBrv75PHvWapA,1305
|
|
138
138
|
experimaestro/tests/test_validation.py,sha256=t0fu-5wkCzpUKLu8K0rbU5zDE_LtsgehWm84HtIF5JY,4076
|
|
139
139
|
experimaestro/tests/token_reschedule.py,sha256=V8lAbjTWTatBrBjxde_KN-fDEI4sQ3HNr4scCXBU6fI,1701
|
|
140
140
|
experimaestro/tests/utils.py,sha256=41krZFgUaCxCYBQPmo5dNFDd9W6RU8ZzzyzY3FyutUI,3805
|
|
@@ -152,8 +152,8 @@ experimaestro/utils/multiprocessing.py,sha256=am3DkHP_kmWbpynbck2c9QystCUtPBoSAC
|
|
|
152
152
|
experimaestro/utils/resources.py,sha256=j-nvsTFwmgENMoVGOD2Ap-UD3WU85WkI0IgeSszMCX4,1328
|
|
153
153
|
experimaestro/utils/settings.py,sha256=jpFMqF0DLL4_P1xGal0zVR5cOrdD8O0Y2IOYvnRgN3k,793
|
|
154
154
|
experimaestro/xpmutils.py,sha256=S21eMbDYsHfvmZ1HmKpq5Pz5O-1HnCLYxKbyTBbASyQ,638
|
|
155
|
-
experimaestro-1.
|
|
156
|
-
experimaestro-1.
|
|
157
|
-
experimaestro-1.
|
|
158
|
-
experimaestro-1.
|
|
159
|
-
experimaestro-1.
|
|
155
|
+
experimaestro-1.15.0.dist-info/METADATA,sha256=c6VFYeeH9cN7JxYxGve-CJN-w45gvmxQnruB4q88WY4,5705
|
|
156
|
+
experimaestro-1.15.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
157
|
+
experimaestro-1.15.0.dist-info/entry_points.txt,sha256=TppTNiz5qm5xm1fhAcdLKdCLMrlL-eQggtCrCI00D9c,446
|
|
158
|
+
experimaestro-1.15.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
159
|
+
experimaestro-1.15.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|