experimaestro 1.11.1__py3-none-any.whl → 2.0.0rc0__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/annotations.py +1 -1
- experimaestro/cli/__init__.py +10 -11
- experimaestro/cli/progress.py +269 -0
- experimaestro/core/identifier.py +11 -2
- experimaestro/core/objects/config.py +64 -94
- experimaestro/core/types.py +35 -57
- experimaestro/launcherfinder/registry.py +3 -3
- experimaestro/mkdocs/base.py +6 -8
- experimaestro/notifications.py +12 -3
- experimaestro/progress.py +406 -0
- experimaestro/settings.py +4 -2
- experimaestro/tests/launchers/common.py +2 -2
- experimaestro/tests/restart.py +1 -1
- experimaestro/tests/test_checkers.py +2 -2
- experimaestro/tests/test_dependencies.py +12 -12
- experimaestro/tests/test_experiment.py +3 -3
- experimaestro/tests/test_file_progress.py +425 -0
- experimaestro/tests/test_file_progress_integration.py +477 -0
- experimaestro/tests/test_generators.py +61 -0
- experimaestro/tests/test_identifier.py +90 -81
- experimaestro/tests/test_instance.py +9 -9
- experimaestro/tests/test_objects.py +9 -32
- experimaestro/tests/test_outputs.py +6 -6
- experimaestro/tests/test_param.py +14 -14
- experimaestro/tests/test_progress.py +4 -4
- experimaestro/tests/test_serializers.py +5 -5
- experimaestro/tests/test_tags.py +15 -15
- experimaestro/tests/test_tasks.py +40 -36
- experimaestro/tests/test_tokens.py +8 -6
- experimaestro/tests/test_types.py +10 -10
- experimaestro/tests/test_validation.py +19 -19
- experimaestro/tests/token_reschedule.py +1 -1
- {experimaestro-1.11.1.dist-info → experimaestro-2.0.0rc0.dist-info}/METADATA +1 -1
- {experimaestro-1.11.1.dist-info → experimaestro-2.0.0rc0.dist-info}/RECORD +37 -32
- {experimaestro-1.11.1.dist-info → experimaestro-2.0.0rc0.dist-info}/LICENSE +0 -0
- {experimaestro-1.11.1.dist-info → experimaestro-2.0.0rc0.dist-info}/WHEEL +0 -0
- {experimaestro-1.11.1.dist-info → experimaestro-2.0.0rc0.dist-info}/entry_points.txt +0 -0
experimaestro/core/types.py
CHANGED
|
@@ -203,18 +203,14 @@ class ObjectType(Type):
|
|
|
203
203
|
"""ObjectType contains class-level information about
|
|
204
204
|
experimaestro configurations and tasks
|
|
205
205
|
|
|
206
|
-
:param
|
|
207
|
-
:param
|
|
208
|
-
property for arguments
|
|
206
|
+
:param value_type: The Python type of the associated object
|
|
207
|
+
:param config_type: The Python type of the configuration object
|
|
209
208
|
"""
|
|
210
209
|
|
|
211
|
-
# Those entries should not be copied in the __dict__
|
|
212
|
-
FORBIDDEN_KEYS = set(("__dict__", "__weakref__"))
|
|
213
|
-
|
|
214
210
|
def __init__(
|
|
215
211
|
self,
|
|
216
212
|
tp: type,
|
|
217
|
-
identifier: Union[str, Identifier] = None,
|
|
213
|
+
identifier: Union[str, Identifier, None] = None,
|
|
218
214
|
):
|
|
219
215
|
"""Creates a type"""
|
|
220
216
|
from .objects import Config, ConfigMixin
|
|
@@ -225,7 +221,7 @@ class ObjectType(Type):
|
|
|
225
221
|
self._title = None
|
|
226
222
|
self.submit_hooks = set()
|
|
227
223
|
|
|
228
|
-
# Get the identifier
|
|
224
|
+
# --- Get the identifier
|
|
229
225
|
if identifier is None and hasattr(tp, "__xpmid__"):
|
|
230
226
|
__xpmid__ = getattr(tp, "__xpmid__")
|
|
231
227
|
if isinstance(__xpmid__, Identifier):
|
|
@@ -250,59 +246,41 @@ class ObjectType(Type):
|
|
|
250
246
|
# --- Creates the config type and not config type
|
|
251
247
|
|
|
252
248
|
self.originaltype = tp
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
__bases__ = () if tp.__bases__ == (object,) else tp.__bases__
|
|
256
|
-
__dict__ = dict(tp.__dict__)
|
|
257
|
-
|
|
258
|
-
__dict__ = {
|
|
259
|
-
key: value
|
|
260
|
-
for key, value in tp.__dict__.items()
|
|
261
|
-
if key not in ObjectType.FORBIDDEN_KEYS
|
|
262
|
-
}
|
|
263
|
-
self.basetype = type(tp.__name__, (Config,) + __bases__, __dict__)
|
|
264
|
-
self.basetype.__module__ = tp.__module__
|
|
265
|
-
self.basetype.__qualname__ = tp.__qualname__
|
|
266
|
-
else:
|
|
267
|
-
self.basetype = tp
|
|
249
|
+
assert issubclass(tp, Config)
|
|
250
|
+
self.value_type = tp
|
|
268
251
|
|
|
269
252
|
# --- Create the type-specific configuration class (XPMConfig)
|
|
270
253
|
__configbases__ = tuple(
|
|
271
|
-
s.__getxpmtype__().
|
|
254
|
+
s.__getxpmtype__().config_type
|
|
272
255
|
for s in tp.__bases__
|
|
273
256
|
if issubclass(s, Config) and (s is not Config)
|
|
274
257
|
) or (ConfigMixin,)
|
|
275
258
|
|
|
276
|
-
*tp_qual, tp_name = self.
|
|
277
|
-
self.
|
|
278
|
-
f"{tp_name}.XPMConfig", __configbases__ + (self.
|
|
259
|
+
*tp_qual, tp_name = self.value_type.__qualname__.split(".")
|
|
260
|
+
self.config_type = type(
|
|
261
|
+
f"{tp_name}.XPMConfig", __configbases__ + (self.value_type,), {}
|
|
279
262
|
)
|
|
280
|
-
self.
|
|
281
|
-
self.
|
|
263
|
+
self.config_type.__qualname__ = ".".join(tp_qual + [self.config_type.__name__])
|
|
264
|
+
self.config_type.__module__ = tp.__module__
|
|
282
265
|
|
|
283
|
-
#
|
|
284
|
-
if hasattr(self.
|
|
266
|
+
# --- Get the return type
|
|
267
|
+
if hasattr(self.value_type, "task_outputs") or False:
|
|
285
268
|
self.returntype = get_type_hints(
|
|
286
|
-
getattr(self.
|
|
269
|
+
getattr(self.value_type, "task_outputs")
|
|
287
270
|
).get("return", typing.Any)
|
|
288
271
|
else:
|
|
289
|
-
self.returntype = self.
|
|
272
|
+
self.returntype = self.value_type
|
|
290
273
|
|
|
291
|
-
# Registers ourselves
|
|
292
|
-
self.
|
|
293
|
-
self.
|
|
274
|
+
# --- Registers ourselves
|
|
275
|
+
self.value_type.__xpmtype__ = self
|
|
276
|
+
self.config_type.__xpmtype__ = self
|
|
294
277
|
|
|
295
|
-
# Other initializations
|
|
278
|
+
# --- Other initializations
|
|
296
279
|
self.__initialized__ = False
|
|
297
280
|
self._runtype = None
|
|
298
281
|
self.annotations = []
|
|
299
282
|
self._deprecated = False
|
|
300
283
|
|
|
301
|
-
@property
|
|
302
|
-
def objecttype(self):
|
|
303
|
-
"""Returns the object type"""
|
|
304
|
-
return self.basetype.XPMValue
|
|
305
|
-
|
|
306
284
|
def addAnnotation(self, annotation):
|
|
307
285
|
assert not self.__initialized__
|
|
308
286
|
self.annotations.append(annotation)
|
|
@@ -357,15 +335,15 @@ class ObjectType(Type):
|
|
|
357
335
|
# Add task
|
|
358
336
|
if self.taskcommandfactory is not None:
|
|
359
337
|
self.task = self.taskcommandfactory(self)
|
|
360
|
-
elif issubclass(self.
|
|
338
|
+
elif issubclass(self.value_type, Task):
|
|
361
339
|
self.task = self.getpythontaskcommand()
|
|
362
340
|
|
|
363
341
|
# Add arguments from type hints
|
|
364
342
|
from .arguments import TypeAnnotation
|
|
365
343
|
|
|
366
|
-
if hasattr(self.
|
|
367
|
-
typekeys = set(self.
|
|
368
|
-
hints = get_type_hints(self.
|
|
344
|
+
if hasattr(self.value_type, "__annotations__"):
|
|
345
|
+
typekeys = set(self.value_type.__dict__.get("__annotations__", {}).keys())
|
|
346
|
+
hints = get_type_hints(self.value_type, include_extras=True)
|
|
369
347
|
for key, typehint in hints.items():
|
|
370
348
|
# Filter out hints from parent classes
|
|
371
349
|
if key in typekeys:
|
|
@@ -378,19 +356,19 @@ class ObjectType(Type):
|
|
|
378
356
|
try:
|
|
379
357
|
self.addArgument(
|
|
380
358
|
options.create(
|
|
381
|
-
key, self.
|
|
359
|
+
key, self.value_type, typehint.__args__[0]
|
|
382
360
|
)
|
|
383
361
|
)
|
|
384
362
|
except Exception:
|
|
385
363
|
logger.error(
|
|
386
364
|
"while adding argument %s of %s",
|
|
387
365
|
key,
|
|
388
|
-
self.
|
|
366
|
+
self.value_type,
|
|
389
367
|
)
|
|
390
368
|
raise
|
|
391
369
|
|
|
392
370
|
def name(self):
|
|
393
|
-
return f"{self.
|
|
371
|
+
return f"{self.value_type.__module__}.{self.value_type.__qualname__}"
|
|
394
372
|
|
|
395
373
|
def __parsedoc__(self):
|
|
396
374
|
"""Parse the documentation"""
|
|
@@ -400,7 +378,7 @@ class ObjectType(Type):
|
|
|
400
378
|
self.__initialize__()
|
|
401
379
|
|
|
402
380
|
# Get description from documentation
|
|
403
|
-
__doc__ = self.
|
|
381
|
+
__doc__ = self.value_type.__dict__.get("__doc__", None)
|
|
404
382
|
if __doc__:
|
|
405
383
|
parseddoc = parse(__doc__)
|
|
406
384
|
self._title = parseddoc.short_description
|
|
@@ -430,7 +408,7 @@ class ObjectType(Type):
|
|
|
430
408
|
argname = None
|
|
431
409
|
|
|
432
410
|
def deprecate(self):
|
|
433
|
-
if len(self.
|
|
411
|
+
if len(self.value_type.__bases__) != 1:
|
|
434
412
|
raise RuntimeError(
|
|
435
413
|
"Deprecated configurations must have "
|
|
436
414
|
"only one parent (the new configuration)"
|
|
@@ -439,7 +417,7 @@ class ObjectType(Type):
|
|
|
439
417
|
|
|
440
418
|
# Uses the parent identifier (and saves the deprecated one for path updates)
|
|
441
419
|
self._deprecated_identifier = self.identifier
|
|
442
|
-
parent = self.
|
|
420
|
+
parent = self.value_type.__bases__[0].__getxpmtype__()
|
|
443
421
|
self.identifier = parent.identifier
|
|
444
422
|
self._deprecated = True
|
|
445
423
|
|
|
@@ -454,7 +432,7 @@ class ObjectType(Type):
|
|
|
454
432
|
return self._description
|
|
455
433
|
|
|
456
434
|
@property
|
|
457
|
-
def title(self) ->
|
|
435
|
+
def title(self) -> str:
|
|
458
436
|
self.__parsedoc__()
|
|
459
437
|
return self._title or str(self.identifier)
|
|
460
438
|
|
|
@@ -469,7 +447,7 @@ class ObjectType(Type):
|
|
|
469
447
|
|
|
470
448
|
# The the attribute for the config type
|
|
471
449
|
setattr(
|
|
472
|
-
self.
|
|
450
|
+
self.config_type,
|
|
473
451
|
argument.name,
|
|
474
452
|
property(
|
|
475
453
|
lambda _self: _self.__xpm__.get(argument.name),
|
|
@@ -488,7 +466,7 @@ class ObjectType(Type):
|
|
|
488
466
|
def parents(self) -> Iterator["ObjectType"]:
|
|
489
467
|
from .objects import Config, Task
|
|
490
468
|
|
|
491
|
-
for tp in self.
|
|
469
|
+
for tp in self.value_type.__bases__:
|
|
492
470
|
if issubclass(tp, Config) and tp not in [Config, Task]:
|
|
493
471
|
yield tp.__xpmtype__
|
|
494
472
|
|
|
@@ -504,7 +482,7 @@ class ObjectType(Type):
|
|
|
504
482
|
if not isinstance(value, Config):
|
|
505
483
|
raise ValueError(f"{value} is not an experimaestro type or task")
|
|
506
484
|
|
|
507
|
-
types = self.
|
|
485
|
+
types = self.value_type
|
|
508
486
|
|
|
509
487
|
if not isinstance(value, types):
|
|
510
488
|
raise ValueError(
|
|
@@ -519,7 +497,7 @@ class ObjectType(Type):
|
|
|
519
497
|
|
|
520
498
|
def fullyqualifiedname(self) -> str:
|
|
521
499
|
"""Returns the fully qualified (Python) name"""
|
|
522
|
-
return f"{self.
|
|
500
|
+
return f"{self.value_type.__module__}.{self.value_type.__qualname__}"
|
|
523
501
|
|
|
524
502
|
|
|
525
503
|
class TypeProxy:
|
|
@@ -6,7 +6,7 @@ from typing import ClassVar, Dict, Optional, Set, Type, Union
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
import typing
|
|
8
8
|
from omegaconf import DictConfig, OmegaConf, SCMode
|
|
9
|
-
import
|
|
9
|
+
from importlib.metadata import entry_points
|
|
10
10
|
from experimaestro.utils import logger
|
|
11
11
|
from .base import ConnectorConfiguration, TokenConfiguration
|
|
12
12
|
from .specs import HostRequirement, RequirementUnion
|
|
@@ -75,10 +75,10 @@ class LauncherRegistry:
|
|
|
75
75
|
self.find_launcher_fn = None
|
|
76
76
|
|
|
77
77
|
# Use entry points for connectors and launchers
|
|
78
|
-
for entry_point in
|
|
78
|
+
for entry_point in entry_points(group="experimaestro.connectors"):
|
|
79
79
|
entry_point.load().init_registry(self)
|
|
80
80
|
|
|
81
|
-
for entry_point in
|
|
81
|
+
for entry_point in entry_points(group="experimaestro.tokens"):
|
|
82
82
|
entry_point.load().init_registry(self)
|
|
83
83
|
|
|
84
84
|
# Register the find launcher function if it exists
|
experimaestro/mkdocs/base.py
CHANGED
|
@@ -4,12 +4,11 @@ See https://www.mkdocs.org/user-guide/plugins/ for plugin API documentation
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from collections import defaultdict
|
|
7
|
-
import functools
|
|
8
7
|
import re
|
|
9
8
|
from experimaestro.mkdocs.annotations import shoulddocument
|
|
10
9
|
import requests
|
|
11
10
|
from urllib.parse import urljoin
|
|
12
|
-
from experimaestro.core.types import ObjectType
|
|
11
|
+
from experimaestro.core.types import ObjectType
|
|
13
12
|
import mkdocs
|
|
14
13
|
from pathlib import Path
|
|
15
14
|
from typing import Dict, Iterator, List, Optional, Set, Tuple, Type as TypingType
|
|
@@ -76,7 +75,7 @@ class ObjectLatticeNode:
|
|
|
76
75
|
return f"node({self.objecttype.identifier})"
|
|
77
76
|
|
|
78
77
|
def isAncestor(self, other):
|
|
79
|
-
return issubclass(self.objecttype.
|
|
78
|
+
return issubclass(self.objecttype.config_type, other.objecttype.config_type)
|
|
80
79
|
|
|
81
80
|
def _addChild(self, child: "ObjectLatticeNode"):
|
|
82
81
|
child.parents.add(self)
|
|
@@ -321,7 +320,7 @@ class Documentation(mkdocs.plugins.BasePlugin):
|
|
|
321
320
|
|
|
322
321
|
for node in self.lattice.iter_all():
|
|
323
322
|
if node.objecttype is not None:
|
|
324
|
-
member = node.objecttype.
|
|
323
|
+
member = node.objecttype.value_type
|
|
325
324
|
qname = f"{member.__module__}.{member.__qualname__}"
|
|
326
325
|
path = self.type2path[qname]
|
|
327
326
|
|
|
@@ -354,7 +353,7 @@ class Documentation(mkdocs.plugins.BasePlugin):
|
|
|
354
353
|
# Now, sort according to descendant/ascendant relationship or name
|
|
355
354
|
nodes = set()
|
|
356
355
|
for _node in cfgs:
|
|
357
|
-
if issubclass(_node.objecttype.
|
|
356
|
+
if issubclass(_node.objecttype.config_type, xpmtype.config_type):
|
|
358
357
|
nodes.add(_node)
|
|
359
358
|
|
|
360
359
|
# Removes so they are not generated twice
|
|
@@ -443,11 +442,10 @@ class Documentation(mkdocs.plugins.BasePlugin):
|
|
|
443
442
|
lines.append("\n\n")
|
|
444
443
|
|
|
445
444
|
for name, argument in xpminfo.arguments.items():
|
|
446
|
-
|
|
447
445
|
if isinstance(argument.type, ObjectType):
|
|
448
|
-
|
|
446
|
+
value_type = argument.type.value_type
|
|
449
447
|
typestr = self.getlink(
|
|
450
|
-
page.url, f"{
|
|
448
|
+
page.url, f"{value_type.__module__}.{value_type.__qualname__}"
|
|
451
449
|
)
|
|
452
450
|
else:
|
|
453
451
|
typestr = argument.type.name()
|
experimaestro/notifications.py
CHANGED
|
@@ -12,6 +12,7 @@ from tqdm.auto import tqdm as std_tqdm
|
|
|
12
12
|
|
|
13
13
|
from .utils import logger
|
|
14
14
|
from experimaestro.taskglobals import Env as TaskEnv
|
|
15
|
+
from .progress import FileBasedProgressReporter
|
|
15
16
|
|
|
16
17
|
# --- Progress and other notifications
|
|
17
18
|
|
|
@@ -41,7 +42,7 @@ class LevelInformation:
|
|
|
41
42
|
return result
|
|
42
43
|
|
|
43
44
|
def __repr__(self) -> str:
|
|
44
|
-
return f"[{self.level}] {self.desc} {int(self.progress*1000)/10}%"
|
|
45
|
+
return f"[{self.level}] {self.desc} {int(self.progress * 1000) / 10}%"
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
class ListenerInformation:
|
|
@@ -79,10 +80,14 @@ class Reporter(threading.Thread):
|
|
|
79
80
|
self.progress_threshold = 0.01
|
|
80
81
|
self.cv = threading.Condition()
|
|
81
82
|
|
|
83
|
+
# File-based progress reporter
|
|
84
|
+
self.file_reporter = FileBasedProgressReporter(task_path=path)
|
|
85
|
+
|
|
82
86
|
def stop(self):
|
|
83
87
|
self.stopping = True
|
|
84
88
|
with self.cv:
|
|
85
|
-
self.cv.notifyAll()
|
|
89
|
+
# self.cv.notifyAll()
|
|
90
|
+
self.cv.notify_all()
|
|
86
91
|
|
|
87
92
|
@staticmethod
|
|
88
93
|
def isfatal_httperror(e: Exception, info: ListenerInformation) -> bool:
|
|
@@ -186,7 +191,7 @@ class Reporter(threading.Thread):
|
|
|
186
191
|
try:
|
|
187
192
|
with urlopen(url) as _:
|
|
188
193
|
logger.debug(
|
|
189
|
-
"EOJ
|
|
194
|
+
"EOJ notification sent for %s",
|
|
190
195
|
baseurl,
|
|
191
196
|
)
|
|
192
197
|
except Exception:
|
|
@@ -194,6 +199,8 @@ class Reporter(threading.Thread):
|
|
|
194
199
|
"Could not report EOJ",
|
|
195
200
|
)
|
|
196
201
|
|
|
202
|
+
self.file_reporter.eoj()
|
|
203
|
+
|
|
197
204
|
def set_progress(
|
|
198
205
|
self, progress: float, level: int, desc: Optional[str], console=False
|
|
199
206
|
):
|
|
@@ -212,6 +219,8 @@ class Reporter(threading.Thread):
|
|
|
212
219
|
self.levels[level].desc = desc
|
|
213
220
|
self.levels[level].progress = progress
|
|
214
221
|
|
|
222
|
+
self.file_reporter.set_progress(progress, level, desc)
|
|
223
|
+
|
|
215
224
|
self.cv.notify_all()
|
|
216
225
|
|
|
217
226
|
INSTANCE: ClassVar[Optional["Reporter"]] = None
|