experimaestro 0.23.0__py2.py3-none-any.whl → 0.24.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of experimaestro might be problematic. Click here for more details.
- experimaestro/__init__.py +3 -3
- experimaestro/__main__.py +2 -2
- experimaestro/core/objects.py +167 -129
- experimaestro/core/objects.pyi +2 -1
- experimaestro/core/serializers.py +52 -0
- experimaestro/huggingface.py +2 -2
- experimaestro/tests/test_identifier.py +33 -6
- experimaestro/tests/test_instance.py +18 -15
- experimaestro/tests/test_outputs.py +2 -40
- experimaestro/tests/test_progress.py +7 -9
- experimaestro/tests/test_serializers.py +54 -0
- experimaestro/utils/jobs.py +2 -2
- experimaestro/version.py +2 -2
- {experimaestro-0.23.0.dist-info → experimaestro-0.24.0.dist-info}/METADATA +18 -1
- {experimaestro-0.23.0.dist-info → experimaestro-0.24.0.dist-info}/RECORD +19 -18
- experimaestro/tests/test_serialization.py +0 -45
- {experimaestro-0.23.0.dist-info → experimaestro-0.24.0.dist-info}/LICENSE +0 -0
- {experimaestro-0.23.0.dist-info → experimaestro-0.24.0.dist-info}/WHEEL +0 -0
- {experimaestro-0.23.0.dist-info → experimaestro-0.24.0.dist-info}/entry_points.txt +0 -0
- {experimaestro-0.23.0.dist-info → experimaestro-0.24.0.dist-info}/top_level.txt +0 -0
experimaestro/__init__.py
CHANGED
|
@@ -44,11 +44,11 @@ from .core.objects import (
|
|
|
44
44
|
Config,
|
|
45
45
|
copyconfig,
|
|
46
46
|
setmeta,
|
|
47
|
-
|
|
47
|
+
ConfigWrapper,
|
|
48
|
+
ConfigWrapper as TaskOutput, # maintains compatibility
|
|
48
49
|
Task,
|
|
49
|
-
SerializedConfig,
|
|
50
|
-
Serialized,
|
|
51
50
|
)
|
|
51
|
+
from .core.serializers import SerializedConfig, PathBasedSerializedConfig
|
|
52
52
|
from .core.types import Any, SubmitHook, submit_hook_decorator
|
|
53
53
|
from .launchers import Launcher
|
|
54
54
|
from .scheduler.environment import Environment
|
experimaestro/__main__.py
CHANGED
|
@@ -12,7 +12,7 @@ import subprocess
|
|
|
12
12
|
from termcolor import colored, cprint
|
|
13
13
|
|
|
14
14
|
import experimaestro
|
|
15
|
-
from experimaestro.core.objects import
|
|
15
|
+
from experimaestro.core.objects import ConfigWrapper
|
|
16
16
|
|
|
17
17
|
# --- Command line main options
|
|
18
18
|
logging.basicConfig(level=logging.INFO)
|
|
@@ -142,7 +142,7 @@ def diff(path: Path):
|
|
|
142
142
|
if new_id != old_id:
|
|
143
143
|
print(f"{path} differ: {new_id} vs {old_id}")
|
|
144
144
|
|
|
145
|
-
if isinstance(value,
|
|
145
|
+
if isinstance(value, ConfigWrapper):
|
|
146
146
|
check(f"{path}.<task>", value.__xpm__.task, new_value.__xpm__.task)
|
|
147
147
|
else:
|
|
148
148
|
for arg in value.__xpmtype__.arguments.values():
|
experimaestro/core/objects.py
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from functools import cached_property
|
|
4
4
|
import json
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from types import NoneType
|
|
8
|
+
except Exception:
|
|
9
|
+
# compatibility: python-3.8
|
|
10
|
+
NoneType = type(None)
|
|
5
11
|
from termcolor import cprint
|
|
6
12
|
import os
|
|
7
13
|
from pathlib import Path
|
|
@@ -14,7 +20,6 @@ import inspect
|
|
|
14
20
|
import importlib
|
|
15
21
|
from typing import (
|
|
16
22
|
Any,
|
|
17
|
-
Callable,
|
|
18
23
|
ClassVar,
|
|
19
24
|
Dict,
|
|
20
25
|
List,
|
|
@@ -169,7 +174,7 @@ class HashComputer:
|
|
|
169
174
|
for key, value in items:
|
|
170
175
|
self.update(key, subparam=subparam)
|
|
171
176
|
self.update(value, subparam=subparam)
|
|
172
|
-
elif isinstance(value,
|
|
177
|
+
elif isinstance(value, ConfigWrapper):
|
|
173
178
|
# Add the task ID...
|
|
174
179
|
self.update(value.__xpm__.task, subparam=subparam)
|
|
175
180
|
|
|
@@ -278,8 +283,14 @@ def updatedependencies(
|
|
|
278
283
|
elif isinstance(value, (list, set)):
|
|
279
284
|
for el in value:
|
|
280
285
|
updatedependencies(dependencies, el, path, taskids)
|
|
281
|
-
elif isinstance(value,
|
|
282
|
-
|
|
286
|
+
elif isinstance(value, ConfigWrapper):
|
|
287
|
+
# Add the base value (if any)
|
|
288
|
+
if value.__xpm__.base is not None:
|
|
289
|
+
value.__xpm__.base.__xpm__.updatedependencies(dependencies, path, taskids)
|
|
290
|
+
|
|
291
|
+
# Add the task (if any)
|
|
292
|
+
if value.__xpm__.task is not None:
|
|
293
|
+
dependencies.add(value.__xpm__.task.__xpm__.dependency())
|
|
283
294
|
elif isinstance(value, (dict,)):
|
|
284
295
|
for key, val in value.items():
|
|
285
296
|
updatedependencies(dependencies, key, path, taskids)
|
|
@@ -419,9 +430,13 @@ class ConfigWalk:
|
|
|
419
430
|
result[key] = self(value)
|
|
420
431
|
return result
|
|
421
432
|
|
|
422
|
-
if isinstance(x,
|
|
433
|
+
if isinstance(x, ConfigWrapper):
|
|
423
434
|
# Process task if different
|
|
424
|
-
if
|
|
435
|
+
if (
|
|
436
|
+
x.__xpm__.task is not None
|
|
437
|
+
and self.recurse_task
|
|
438
|
+
and x.__xpm__.task is not x.__unwrap__()
|
|
439
|
+
):
|
|
425
440
|
self(x.__xpm__.task)
|
|
426
441
|
|
|
427
442
|
# Processed the wrapped config
|
|
@@ -430,6 +445,9 @@ class ConfigWalk:
|
|
|
430
445
|
if isinstance(x, (float, int, str, Path, Enum)):
|
|
431
446
|
return x
|
|
432
447
|
|
|
448
|
+
if isinstance(x, Proxy):
|
|
449
|
+
return self(x.__unwrap__())
|
|
450
|
+
|
|
433
451
|
raise NotImplementedError(f"Cannot handle a value of type {type(x)}")
|
|
434
452
|
|
|
435
453
|
|
|
@@ -448,7 +466,7 @@ class ConfigInformation:
|
|
|
448
466
|
"""Forces this configuration to be a meta-parameter"""
|
|
449
467
|
|
|
450
468
|
# Set to true when loading from JSON
|
|
451
|
-
LOADING = False
|
|
469
|
+
LOADING: ClassVar[bool] = False
|
|
452
470
|
|
|
453
471
|
def __init__(self, pyobject: "TypeConfig"):
|
|
454
472
|
# The underlying pyobject and XPM type
|
|
@@ -477,6 +495,7 @@ class ConfigInformation:
|
|
|
477
495
|
self._meta = None
|
|
478
496
|
|
|
479
497
|
def set_meta(self, value: Optional[bool]):
|
|
498
|
+
"""Sets the meta flag"""
|
|
480
499
|
assert not self._sealed, "Configuration is sealed"
|
|
481
500
|
self._meta = value
|
|
482
501
|
|
|
@@ -672,12 +691,17 @@ class ConfigInformation:
|
|
|
672
691
|
hook(job)
|
|
673
692
|
|
|
674
693
|
def submit(
|
|
675
|
-
self, workspace: "Workspace", launcher: "Launcher", run_mode=None
|
|
676
|
-
) -> "
|
|
694
|
+
self, workspace: "Workspace", launcher: "Launcher", *, run_mode=None, pre=None
|
|
695
|
+
) -> "ConfigWrapper":
|
|
677
696
|
from experimaestro.scheduler import experiment, JobContext
|
|
678
697
|
from experimaestro.scheduler.workspace import RunMode
|
|
679
698
|
|
|
699
|
+
# --- Handle default values
|
|
700
|
+
|
|
701
|
+
pre = pre or []
|
|
702
|
+
|
|
680
703
|
# --- Prepare the object
|
|
704
|
+
|
|
681
705
|
if self.job:
|
|
682
706
|
raise Exception("task %s was already submitted" % self)
|
|
683
707
|
if not self.xpmtype.task:
|
|
@@ -685,6 +709,7 @@ class ConfigInformation:
|
|
|
685
709
|
|
|
686
710
|
# --- Submit the job
|
|
687
711
|
|
|
712
|
+
# Creates a new job
|
|
688
713
|
self.job = self.xpmtype.task(
|
|
689
714
|
self.pyobject, launcher=launcher, workspace=workspace, run_mode=run_mode
|
|
690
715
|
)
|
|
@@ -759,16 +784,20 @@ class ConfigInformation:
|
|
|
759
784
|
hints = get_type_hints(self.pyobject.config)
|
|
760
785
|
config = hints["return"](**config)
|
|
761
786
|
config.__xpm__.validate()
|
|
762
|
-
self._taskoutput =
|
|
787
|
+
self._taskoutput = ConfigWrapper.__create_taskoutput__(
|
|
788
|
+
config, self.pyobject
|
|
789
|
+
)
|
|
763
790
|
|
|
764
791
|
# New way to handle outputs
|
|
765
792
|
elif hasattr(self.pyobject, "taskoutputs"):
|
|
766
793
|
value = self.pyobject.taskoutputs()
|
|
767
|
-
self._taskoutput =
|
|
794
|
+
self._taskoutput = ConfigWrapper.__create_taskoutput__(value, self.pyobject)
|
|
768
795
|
|
|
769
796
|
# Otherwise, the output is just the config
|
|
770
797
|
else:
|
|
771
|
-
self._taskoutput =
|
|
798
|
+
self._taskoutput = ConfigWrapper.__create_taskoutput__(
|
|
799
|
+
self.pyobject, self.pyobject
|
|
800
|
+
)
|
|
772
801
|
|
|
773
802
|
return self._taskoutput
|
|
774
803
|
|
|
@@ -810,20 +839,13 @@ class ConfigInformation:
|
|
|
810
839
|
"value": value.name,
|
|
811
840
|
}
|
|
812
841
|
|
|
813
|
-
elif isinstance(value,
|
|
814
|
-
# Reference to a serialized object
|
|
815
|
-
return {
|
|
816
|
-
"type": "serialized",
|
|
817
|
-
"value": id(value.__xpm__.serialized.loader),
|
|
818
|
-
"path": [c.toJSON() for c in value.__xpm__.path],
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
elif isinstance(value, TaskOutput):
|
|
842
|
+
elif isinstance(value, ConfigWrapper):
|
|
822
843
|
return {
|
|
823
844
|
"type": "python",
|
|
824
845
|
"value": id(value.__unwrap__()),
|
|
825
846
|
# We add the task for identifier computation
|
|
826
847
|
"task": id(value.__xpm__.task),
|
|
848
|
+
"path": [c.toJSON() for c in value.__xpm__.path or []],
|
|
827
849
|
}
|
|
828
850
|
|
|
829
851
|
elif isinstance(value, Config):
|
|
@@ -892,25 +914,18 @@ class ConfigInformation:
|
|
|
892
914
|
def __collect_objects__(value, objects: List[Dict], context: SerializationContext):
|
|
893
915
|
"""Serialize all needed configuration objects, looking at sub
|
|
894
916
|
configurations if necessary"""
|
|
895
|
-
# objects
|
|
896
|
-
if isinstance(value, SerializedTaskOutput):
|
|
897
|
-
loader = value.__xpm__.serialized.loader
|
|
898
|
-
if id(loader) not in context.serialized:
|
|
899
|
-
objects.append(
|
|
900
|
-
{
|
|
901
|
-
"id": id(loader),
|
|
902
|
-
"serialized": True,
|
|
903
|
-
"module": loader.__class__.__module__,
|
|
904
|
-
"type": loader.__class__.__qualname__,
|
|
905
|
-
"value": loader.toJSON(),
|
|
906
|
-
}
|
|
907
|
-
)
|
|
908
|
-
return
|
|
909
|
-
|
|
910
917
|
# Unwrap if needed
|
|
911
|
-
if isinstance(value,
|
|
918
|
+
if isinstance(value, ConfigWrapper):
|
|
912
919
|
# We will need to output the task configuration objects
|
|
913
|
-
|
|
920
|
+
if value.__xpm__.task is not None:
|
|
921
|
+
ConfigInformation.__collect_objects__(
|
|
922
|
+
value.__xpm__.task, objects, context
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
if value.__xpm__.base is not None:
|
|
926
|
+
ConfigInformation.__collect_objects__(
|
|
927
|
+
value.__xpm__.base, objects, context
|
|
928
|
+
)
|
|
914
929
|
|
|
915
930
|
# Unwrap the value to output it
|
|
916
931
|
value = value.__unwrap__()
|
|
@@ -1040,7 +1055,7 @@ class ConfigInformation:
|
|
|
1040
1055
|
if not as_instance:
|
|
1041
1056
|
if task_id := value.get("task", None):
|
|
1042
1057
|
task = objects[task_id]
|
|
1043
|
-
return
|
|
1058
|
+
return ConfigWrapper.__create_taskoutput__(obj, task)
|
|
1044
1059
|
return obj
|
|
1045
1060
|
|
|
1046
1061
|
if value["type"] == "serialized":
|
|
@@ -1102,6 +1117,7 @@ class ConfigInformation:
|
|
|
1102
1117
|
o = None
|
|
1103
1118
|
objects = {}
|
|
1104
1119
|
import experimaestro.taskglobals as taskglobals
|
|
1120
|
+
from .serializers import SerializedConfig
|
|
1105
1121
|
|
|
1106
1122
|
for definition in definitions:
|
|
1107
1123
|
module_name = definition["module"]
|
|
@@ -1184,7 +1200,10 @@ class ConfigInformation:
|
|
|
1184
1200
|
assert isinstance(v, Path), "Excepted Path, got {type(v)}"
|
|
1185
1201
|
|
|
1186
1202
|
if as_instance:
|
|
1203
|
+
# Unwrap the value if needed
|
|
1204
|
+
v = unwrap(v)
|
|
1187
1205
|
setattr(o, name, v)
|
|
1206
|
+
|
|
1188
1207
|
assert (
|
|
1189
1208
|
getattr(o, name) is v
|
|
1190
1209
|
), f"Problem with deserialization {name} of {o.__class__}"
|
|
@@ -1194,6 +1213,8 @@ class ConfigInformation:
|
|
|
1194
1213
|
if as_instance:
|
|
1195
1214
|
# Calls post-init
|
|
1196
1215
|
o.__post_init__()
|
|
1216
|
+
if isinstance(o, SerializedConfig):
|
|
1217
|
+
o.initialize()
|
|
1197
1218
|
else:
|
|
1198
1219
|
# Seal and set the identifier
|
|
1199
1220
|
if not discard_id:
|
|
@@ -1247,6 +1268,13 @@ class ConfigInformation:
|
|
|
1247
1268
|
# Call __post_init__
|
|
1248
1269
|
o.__post_init__()
|
|
1249
1270
|
|
|
1271
|
+
# Process a serialized configuration
|
|
1272
|
+
from .serializers import SerializedConfig
|
|
1273
|
+
|
|
1274
|
+
if isinstance(o, SerializedConfig):
|
|
1275
|
+
o.initialize()
|
|
1276
|
+
o = o.__unwrap__()
|
|
1277
|
+
|
|
1250
1278
|
return o
|
|
1251
1279
|
|
|
1252
1280
|
def fromConfig(self, context: ConfigWalkContext):
|
|
@@ -1406,15 +1434,18 @@ class TypeConfig:
|
|
|
1406
1434
|
), f"{context.__class__} is not an instance of ConfigWalkContext"
|
|
1407
1435
|
return self.__xpm__.fromConfig(context) # type: ignore
|
|
1408
1436
|
|
|
1409
|
-
def submit(
|
|
1437
|
+
def submit(
|
|
1438
|
+
self, *, workspace=None, launcher=None, run_mode: "RunMode" = None, pre=None
|
|
1439
|
+
):
|
|
1410
1440
|
"""Submit this task
|
|
1411
1441
|
|
|
1412
1442
|
:param workspace: the workspace, defaults to None
|
|
1413
1443
|
:param launcher: The launcher, defaults to None
|
|
1414
1444
|
:param run_mode: Run mode (if None, uses the workspace default)
|
|
1415
|
-
:
|
|
1445
|
+
:param pre: Pre-tasks to execute before
|
|
1446
|
+
:return: a :py:class:ConfigWrapper object
|
|
1416
1447
|
"""
|
|
1417
|
-
return self.__xpm__.submit(workspace, launcher, run_mode=run_mode)
|
|
1448
|
+
return self.__xpm__.submit(workspace, launcher, run_mode=run_mode, pre=pre)
|
|
1418
1449
|
|
|
1419
1450
|
def stdout(self):
|
|
1420
1451
|
return self.__xpm__.job.stdout
|
|
@@ -1515,9 +1546,6 @@ class Task(Config):
|
|
|
1515
1546
|
raise NotImplementedError()
|
|
1516
1547
|
|
|
1517
1548
|
|
|
1518
|
-
# --- Output proxy
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
1549
|
class Proxy:
|
|
1522
1550
|
"""A proxy for a value"""
|
|
1523
1551
|
|
|
@@ -1525,7 +1553,29 @@ class Proxy:
|
|
|
1525
1553
|
raise NotImplementedError()
|
|
1526
1554
|
|
|
1527
1555
|
|
|
1556
|
+
def unwrap(v: Any):
|
|
1557
|
+
"""Unwrap all proxies"""
|
|
1558
|
+
while isinstance(v, Proxy):
|
|
1559
|
+
v = v.__unwrap__()
|
|
1560
|
+
return v
|
|
1561
|
+
|
|
1562
|
+
|
|
1563
|
+
class AttrAccessor:
|
|
1564
|
+
"""Access an attribute"""
|
|
1565
|
+
|
|
1566
|
+
def __init__(self, key: str):
|
|
1567
|
+
self.key = key
|
|
1568
|
+
|
|
1569
|
+
def get(self, value):
|
|
1570
|
+
return getattr(value, self.key)
|
|
1571
|
+
|
|
1572
|
+
def toJSON(self):
|
|
1573
|
+
return {"type": "attr", "name": self.key}
|
|
1574
|
+
|
|
1575
|
+
|
|
1528
1576
|
class ItemAccessor:
|
|
1577
|
+
"""Access an array item"""
|
|
1578
|
+
|
|
1529
1579
|
def __init__(self, key: Any):
|
|
1530
1580
|
self.key = key
|
|
1531
1581
|
|
|
@@ -1536,49 +1586,65 @@ class ItemAccessor:
|
|
|
1536
1586
|
return value.__getitem__(self.key)
|
|
1537
1587
|
|
|
1538
1588
|
|
|
1539
|
-
class
|
|
1540
|
-
|
|
1541
|
-
self.key = key
|
|
1542
|
-
self.default = default
|
|
1543
|
-
|
|
1544
|
-
def get(self, value):
|
|
1545
|
-
return getattr(value, self.key, self.default)
|
|
1546
|
-
|
|
1547
|
-
def toJSON(self):
|
|
1548
|
-
return {"type": "attr", "name": self.key}
|
|
1589
|
+
class ConfigWrapperInfo:
|
|
1590
|
+
"""Global information about the Configuration wrapper"""
|
|
1549
1591
|
|
|
1592
|
+
def __init__(
|
|
1593
|
+
self,
|
|
1594
|
+
value: Any,
|
|
1595
|
+
*,
|
|
1596
|
+
task: Optional[Task] = None,
|
|
1597
|
+
parent: Optional["ConfigWrapperInfo"] = None,
|
|
1598
|
+
base: Optional[Config] = None,
|
|
1599
|
+
path: Optional[List[Path]] = None,
|
|
1600
|
+
):
|
|
1601
|
+
# Current value
|
|
1602
|
+
self.value = value
|
|
1603
|
+
self.parent = parent
|
|
1550
1604
|
|
|
1551
|
-
|
|
1552
|
-
|
|
1605
|
+
# The task
|
|
1606
|
+
if isinstance(value, Task):
|
|
1607
|
+
self.task = value
|
|
1608
|
+
else:
|
|
1609
|
+
self.task = task
|
|
1553
1610
|
|
|
1554
|
-
|
|
1555
|
-
|
|
1611
|
+
# Holds serialized config information
|
|
1612
|
+
from .serializers import SerializedConfig
|
|
1556
1613
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1614
|
+
if isinstance(value, SerializedConfig):
|
|
1615
|
+
self.base = value
|
|
1616
|
+
self.value = value.config
|
|
1617
|
+
self.path = [AttrAccessor("config")]
|
|
1618
|
+
else:
|
|
1619
|
+
self.base = base
|
|
1620
|
+
self.path = path
|
|
1559
1621
|
|
|
1622
|
+
def __state_dict__(self):
|
|
1623
|
+
return {"task": self.task, "base": self.base, "path": self.path}
|
|
1560
1624
|
|
|
1561
|
-
|
|
1562
|
-
|
|
1625
|
+
def __getitem__(self, key: Any):
|
|
1626
|
+
kwargs = self.__state_dict__()
|
|
1627
|
+
value = self.value.__getitem__(key)
|
|
1563
1628
|
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
"""
|
|
1629
|
+
if self.path:
|
|
1630
|
+
kwargs["path"].append(ItemAccessor(key))
|
|
1567
1631
|
|
|
1568
|
-
|
|
1569
|
-
"""The configuration that will be serialized"""
|
|
1632
|
+
return ConfigWrapperInfo(value, **kwargs)
|
|
1570
1633
|
|
|
1571
|
-
def
|
|
1572
|
-
|
|
1573
|
-
self.
|
|
1634
|
+
def __getattr__(self, key: str, *default) -> Any:
|
|
1635
|
+
kwargs = self.__state_dict__()
|
|
1636
|
+
value = getattr(self.value, key, *default)
|
|
1637
|
+
if self.path:
|
|
1638
|
+
kwargs["path"].append(AttrAccessor(key, *default))
|
|
1639
|
+
return ConfigWrapperInfo(value, parent=self, **kwargs)
|
|
1574
1640
|
|
|
1641
|
+
def wrap(self):
|
|
1642
|
+
"""Wrap a value if needed"""
|
|
1643
|
+
if isinstance(self.value, (str, int, float, Path, bool, NoneType)):
|
|
1644
|
+
return self.value
|
|
1575
1645
|
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
self.task = task
|
|
1579
|
-
self.value = None
|
|
1580
|
-
self.path = None
|
|
1581
|
-
self.serialized = None
|
|
1646
|
+
# Returns a new config wrapper
|
|
1647
|
+
return ConfigWrapper(self)
|
|
1582
1648
|
|
|
1583
1649
|
@property
|
|
1584
1650
|
def identifier(self):
|
|
@@ -1589,92 +1655,64 @@ class TaskOutputInfo:
|
|
|
1589
1655
|
return self.task.__xpm__.job
|
|
1590
1656
|
|
|
1591
1657
|
def tags(self):
|
|
1658
|
+
"""Returns the tags of the task"""
|
|
1592
1659
|
tags = self.task.__xpm__.tags()
|
|
1593
1660
|
return tags
|
|
1594
1661
|
|
|
1595
1662
|
def stdout(self):
|
|
1663
|
+
"""Returns the standard output of the associated task"""
|
|
1596
1664
|
return self.task.__xpm__.job.stdout
|
|
1597
1665
|
|
|
1598
1666
|
def stderr(self):
|
|
1667
|
+
"""Returns the standard error of the associated task"""
|
|
1599
1668
|
return self.task.__xpm__.job.stderr
|
|
1600
1669
|
|
|
1601
1670
|
def wait(self):
|
|
1671
|
+
"""Wait for the task to end
|
|
1672
|
+
|
|
1673
|
+
:return: True if the task completed without error
|
|
1674
|
+
"""
|
|
1602
1675
|
from experimaestro.scheduler import JobState
|
|
1603
1676
|
|
|
1604
1677
|
return self.task.__xpm__.job.wait() == JobState.DONE
|
|
1605
1678
|
|
|
1606
1679
|
|
|
1607
|
-
|
|
1680
|
+
# Cleanup into just one
|
|
1681
|
+
class ConfigWrapper(Proxy):
|
|
1608
1682
|
"""Task proxy
|
|
1609
1683
|
|
|
1610
1684
|
This is used when accessing properties *after* having submitted a task,
|
|
1611
|
-
to keep track of the dependencies
|
|
1685
|
+
to keep track of the dependencies, and/or as an accessor when dealing with
|
|
1686
|
+
a serialized config
|
|
1612
1687
|
"""
|
|
1613
1688
|
|
|
1614
|
-
def __init__(self,
|
|
1615
|
-
self.__xpm__ =
|
|
1616
|
-
task if isinstance(task, TaskOutputInfo) else TaskOutputInfo(task)
|
|
1617
|
-
)
|
|
1618
|
-
self.__xpm__.value = value
|
|
1619
|
-
|
|
1620
|
-
def _wrap(self, value):
|
|
1621
|
-
if isinstance(value, SerializedConfig):
|
|
1622
|
-
return SerializedTaskOutput(value.pyobject, value, self.__xpm__.task, [])
|
|
1623
|
-
|
|
1624
|
-
if isinstance(value, (str, int, float, Path, bool)):
|
|
1625
|
-
# No need to wrap if direct
|
|
1626
|
-
return value
|
|
1689
|
+
def __init__(self, info: ConfigWrapperInfo):
|
|
1690
|
+
self.__xpm__ = info
|
|
1627
1691
|
|
|
1628
|
-
|
|
1692
|
+
@staticmethod
|
|
1693
|
+
def __create_taskoutput__(value: Any, task: Task):
|
|
1694
|
+
return ConfigWrapperInfo(value, task=task).wrap()
|
|
1629
1695
|
|
|
1630
1696
|
def __getitem__(self, key: Any):
|
|
1631
|
-
return self.
|
|
1697
|
+
return self.__xpm__[key].wrap()
|
|
1632
1698
|
|
|
1633
|
-
def __getattr__(self, key: str, default
|
|
1634
|
-
return self.
|
|
1699
|
+
def __getattr__(self, key: str, *default) -> Any:
|
|
1700
|
+
return self.__xpm__.__getattr__(key, *default).wrap()
|
|
1635
1701
|
|
|
1636
1702
|
def __unwrap__(self):
|
|
1637
1703
|
return self.__xpm__.value
|
|
1638
1704
|
|
|
1639
1705
|
def __call__(self, *args, **kwargs):
|
|
1640
1706
|
assert callable(self.__xpm__.value), "Attribute is not a function"
|
|
1641
|
-
__self__ = TaskOutput(self.__xpm__.value.__self__, self.__xpm__.task)
|
|
1642
|
-
return self.__xpm__.value.__func__(__self__, *args, **kwargs)
|
|
1643
|
-
|
|
1644
1707
|
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
Here, we need to keep track of the path to the value we need
|
|
1649
|
-
"""
|
|
1650
|
-
|
|
1651
|
-
def __init__(
|
|
1652
|
-
self, value, serialized: SerializedConfig, task: Task, path: List[Any]
|
|
1653
|
-
):
|
|
1654
|
-
super().__init__(value, task)
|
|
1655
|
-
self.__xpm__.serialized = serialized
|
|
1656
|
-
self.__xpm__.path = path
|
|
1657
|
-
|
|
1658
|
-
def __getitem__(self, key: Any):
|
|
1659
|
-
value = self.__xpm__.value.__getitem__(key)
|
|
1660
|
-
return SerializedTaskOutput(
|
|
1661
|
-
value, self.serialized, self.__xpm__.task, self.path + [ItemAccessor(key)]
|
|
1662
|
-
)
|
|
1663
|
-
|
|
1664
|
-
def __getattr__(self, key: str, default=None) -> Any:
|
|
1665
|
-
value = getattr(self.__xpm__.value, key, default)
|
|
1666
|
-
return SerializedTaskOutput(
|
|
1667
|
-
value,
|
|
1668
|
-
self.__xpm__.serialized,
|
|
1669
|
-
self.__xpm__.task,
|
|
1670
|
-
self.__xpm__.path + [AttrAccessor(key, default)],
|
|
1671
|
-
)
|
|
1708
|
+
__self__ = self.__xpm__.parent.wrap()
|
|
1709
|
+
return self.__xpm__.value.__func__(__self__, *args, **kwargs)
|
|
1672
1710
|
|
|
1673
1711
|
|
|
1674
1712
|
# --- Utility functions
|
|
1675
1713
|
|
|
1676
1714
|
|
|
1677
|
-
def copyconfig(config_or_output: Union[Config,
|
|
1715
|
+
def copyconfig(config_or_output: Union[Config, ConfigWrapper], **kwargs):
|
|
1678
1716
|
"""Copy a configuration or task output
|
|
1679
1717
|
|
|
1680
1718
|
Useful to modify a configuration that can be potentially
|
|
@@ -1682,7 +1720,7 @@ def copyconfig(config_or_output: Union[Config, TaskOutput], **kwargs):
|
|
|
1682
1720
|
a task output).
|
|
1683
1721
|
"""
|
|
1684
1722
|
|
|
1685
|
-
if isinstance(config_or_output,
|
|
1723
|
+
if isinstance(config_or_output, ConfigWrapper):
|
|
1686
1724
|
output = config_or_output
|
|
1687
1725
|
config = config_or_output.__unwrap__()
|
|
1688
1726
|
assert isinstance(config, Config)
|
|
@@ -1702,7 +1740,7 @@ def copyconfig(config_or_output: Union[Config, TaskOutput], **kwargs):
|
|
|
1702
1740
|
return copy
|
|
1703
1741
|
|
|
1704
1742
|
# wrap in Task output
|
|
1705
|
-
return
|
|
1743
|
+
return ConfigWrapper(copy, output.__xpm__)
|
|
1706
1744
|
|
|
1707
1745
|
|
|
1708
1746
|
def setmeta(config: Config, flag: bool):
|
experimaestro/core/objects.pyi
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List, TypeVar
|
|
4
|
+
|
|
5
|
+
from experimaestro import Param
|
|
6
|
+
|
|
7
|
+
from .objects import Config, Proxy
|
|
8
|
+
from .arguments import DataPath
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SerializedConfig(Config, Proxy, ABC):
|
|
14
|
+
"""A serializable configuration
|
|
15
|
+
|
|
16
|
+
This can be used to define a loading mechanism when instanciating the
|
|
17
|
+
configuration
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
config: Param[Config]
|
|
21
|
+
"""The configuration that will be serialized"""
|
|
22
|
+
|
|
23
|
+
registered: List[Config]
|
|
24
|
+
"""(execution only) List of configurations that use this serialized config"""
|
|
25
|
+
|
|
26
|
+
def __post_init__(self):
|
|
27
|
+
super().__post_init__()
|
|
28
|
+
self.registered = []
|
|
29
|
+
|
|
30
|
+
def register(self, config: Config):
|
|
31
|
+
self.registered.append(config)
|
|
32
|
+
|
|
33
|
+
def __unwrap__(self):
|
|
34
|
+
return self.config
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def initialize(self):
|
|
38
|
+
"""Initialize the object
|
|
39
|
+
|
|
40
|
+
This might imply loading saved data (e.g. learned models)
|
|
41
|
+
"""
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class PathBasedSerializedConfig(SerializedConfig):
|
|
46
|
+
"""A path based serialized configuration
|
|
47
|
+
|
|
48
|
+
The most common case it to have
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
path: DataPath[Path]
|
|
52
|
+
"""Path containing the data"""
|
experimaestro/huggingface.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
from typing import Optional, Union
|
|
3
|
-
from experimaestro import Config,
|
|
3
|
+
from experimaestro import Config, ConfigWrapper
|
|
4
4
|
from experimaestro.core.context import SerializedPath
|
|
5
5
|
from experimaestro.core.objects import ConfigInformation
|
|
6
6
|
from huggingface_hub import ModelHubMixin, hf_hub_download, snapshot_download
|
|
@@ -11,7 +11,7 @@ class ExperimaestroHFHub(ModelHubMixin):
|
|
|
11
11
|
"""Defines models that can be uploaded/downloaded from the Hub"""
|
|
12
12
|
|
|
13
13
|
def __init__(
|
|
14
|
-
self, config: Union[Config,
|
|
14
|
+
self, config: Union[Config, ConfigWrapper], variant: Optional[str] = None
|
|
15
15
|
):
|
|
16
16
|
self.config = config if isinstance(config, Config) else config.__unwrap__()
|
|
17
17
|
self.variant = variant
|
|
@@ -17,7 +17,8 @@ from experimaestro import (
|
|
|
17
17
|
Annotated,
|
|
18
18
|
Task,
|
|
19
19
|
)
|
|
20
|
-
from experimaestro.core.objects import ConfigInformation,
|
|
20
|
+
from experimaestro.core.objects import ConfigInformation, ConfigWrapper, setmeta
|
|
21
|
+
from experimaestro.core.serializers import SerializedConfig
|
|
21
22
|
from experimaestro.scheduler.workspace import RunMode
|
|
22
23
|
|
|
23
24
|
|
|
@@ -56,7 +57,7 @@ class Values:
|
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
def getidentifier(x):
|
|
59
|
-
if isinstance(x,
|
|
60
|
+
if isinstance(x, ConfigWrapper):
|
|
60
61
|
return x.__xpm__.identifier.all
|
|
61
62
|
return x.__xpm__.identifier.all
|
|
62
63
|
|
|
@@ -387,6 +388,32 @@ def test_identifier_meta_default_array():
|
|
|
387
388
|
)
|
|
388
389
|
|
|
389
390
|
|
|
391
|
+
# --- Check ConfigWrapper
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class Model(Config):
|
|
395
|
+
def __post_init__(self):
|
|
396
|
+
self.initialized = False
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class Trainer(Config):
|
|
400
|
+
model: Param[Model]
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
class SerializedModel(SerializedConfig):
|
|
404
|
+
def initialize(self):
|
|
405
|
+
self.config.initialized = True
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def test_identifier_serialized_config():
|
|
409
|
+
trainer1 = Trainer(model=Model())
|
|
410
|
+
trainer2 = Trainer(model=SerializedModel(config=Model()))
|
|
411
|
+
assert_notequal(trainer1, trainer2)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
# --- Check configuration reloads
|
|
415
|
+
|
|
416
|
+
|
|
390
417
|
def check_reload(config):
|
|
391
418
|
old_identifier = config.__xpm__.identifier.all
|
|
392
419
|
|
|
@@ -412,14 +439,14 @@ def test_identifier_reload_config():
|
|
|
412
439
|
check_reload(IdentifierReloadConfig(id="123"))
|
|
413
440
|
|
|
414
441
|
|
|
415
|
-
class
|
|
442
|
+
class IdentifierReloadConfigWrapper(Task):
|
|
416
443
|
id: Param[str]
|
|
417
444
|
|
|
418
445
|
def taskoutputs(self):
|
|
419
446
|
return IdentifierReloadConfig(id=self.id)
|
|
420
447
|
|
|
421
448
|
|
|
422
|
-
class
|
|
449
|
+
class IdentifierReloadConfigWrapperDerived(Config):
|
|
423
450
|
task: Param[IdentifierReloadConfig]
|
|
424
451
|
|
|
425
452
|
|
|
@@ -427,8 +454,8 @@ def test_identifier_reload_taskoutput():
|
|
|
427
454
|
"""When using a task output, the identifier should not be different"""
|
|
428
455
|
|
|
429
456
|
# Creates the configuration
|
|
430
|
-
task =
|
|
431
|
-
config =
|
|
457
|
+
task = IdentifierReloadConfigWrapper(id="123").submit(run_mode=RunMode.DRY_RUN)
|
|
458
|
+
config = IdentifierReloadConfigWrapperDerived(task=task)
|
|
432
459
|
check_reload(config)
|
|
433
460
|
|
|
434
461
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
from experimaestro import config, Param, Config
|
|
3
3
|
from experimaestro.core.objects import TypeConfig
|
|
4
|
+
from experimaestro.core.serializers import SerializedConfig
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
@config()
|
|
@@ -31,27 +32,29 @@ def test_simple_instance():
|
|
|
31
32
|
assert isinstance(b.a, A.__xpmtype__.basetype)
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
class Model(Config):
|
|
36
|
+
def __post_init__(self):
|
|
37
|
+
self.initialized = False
|
|
38
|
+
|
|
38
39
|
|
|
40
|
+
class Trainer(Config):
|
|
41
|
+
model: Param[Model]
|
|
39
42
|
|
|
40
|
-
class TestSerialization:
|
|
41
|
-
"""Test that a config can be serialized during execution"""
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
class SerializedModel(SerializedConfig):
|
|
45
|
+
def initialize(self):
|
|
46
|
+
self.config.initialized = True
|
|
45
47
|
|
|
46
|
-
a = SerializedConfig(x=2).instance()
|
|
47
|
-
assert not isinstance(a, TypeConfig)
|
|
48
|
-
assert isinstance(a, SerializedConfig)
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
def test_instance_serialized():
|
|
50
|
+
model = SerializedModel(config=Model())
|
|
51
|
+
trainer = Trainer(model=model)
|
|
52
|
+
instance = trainer.instance()
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
assert isinstance(
|
|
55
|
+
instance.model, Model
|
|
56
|
+
), f"The model is not a Model but a {type(instance.model).__qualname__}"
|
|
57
|
+
assert instance.model.initialized, "The model was not initialized"
|
|
55
58
|
|
|
56
59
|
|
|
57
60
|
class ConfigWithOptional(Config):
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"""Test for task outputs"""
|
|
2
2
|
|
|
3
|
-
from experimaestro import Config, Task, Param
|
|
4
|
-
from experimaestro.core.objects import SerializedConfig, Serialized, TaskOutput
|
|
3
|
+
from experimaestro import Config, Task, Param, ConfigWrapper
|
|
5
4
|
from experimaestro.scheduler.workspace import RunMode
|
|
6
|
-
from experimaestro.tests.utils import TemporaryExperiment
|
|
7
5
|
|
|
8
6
|
|
|
9
7
|
class B(Config):
|
|
@@ -14,19 +12,12 @@ class A(Config):
|
|
|
14
12
|
b: Param[B]
|
|
15
13
|
|
|
16
14
|
|
|
17
|
-
class LoaderA(Serialized):
|
|
18
|
-
@staticmethod
|
|
19
|
-
def fromJSON(x) -> A:
|
|
20
|
-
return A(b=B(x=x)).instance()
|
|
21
|
-
|
|
22
|
-
|
|
23
15
|
class Main(Task):
|
|
24
16
|
a: Param[A]
|
|
25
17
|
|
|
26
18
|
def taskoutputs(self):
|
|
27
19
|
return self.a, {
|
|
28
20
|
"a": self.a,
|
|
29
|
-
"serialized": SerializedConfig(self.a, LoaderA(self.a.b.x)),
|
|
30
21
|
}
|
|
31
22
|
|
|
32
23
|
def execute(self):
|
|
@@ -44,8 +35,7 @@ def test_output_taskoutput():
|
|
|
44
35
|
a = A(b=B())
|
|
45
36
|
output, ioutput = Main(a=a).submit(run_mode=RunMode.DRY_RUN)
|
|
46
37
|
|
|
47
|
-
assert isinstance(
|
|
48
|
-
assert isinstance(output, TaskOutput), "outputs should be task proxies"
|
|
38
|
+
assert isinstance(output, ConfigWrapper), "outputs should be task proxies"
|
|
49
39
|
|
|
50
40
|
# Direct
|
|
51
41
|
Main(a=output)
|
|
@@ -58,31 +48,3 @@ def test_output_taskoutput():
|
|
|
58
48
|
|
|
59
49
|
# Now, submits
|
|
60
50
|
Main(a=output).submit(run_mode=RunMode.DRY_RUN)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def test_output_serialization():
|
|
64
|
-
"""Test output serialization"""
|
|
65
|
-
|
|
66
|
-
with TemporaryExperiment("output_serialization", maxwait=5) as xp:
|
|
67
|
-
a = A(b=B(x=2))
|
|
68
|
-
|
|
69
|
-
main0 = Main(a=a)
|
|
70
|
-
output, ioutput = main0.submit()
|
|
71
|
-
|
|
72
|
-
# Direct
|
|
73
|
-
serialized_a = ioutput["serialized"]
|
|
74
|
-
main1 = Main(a=serialized_a)
|
|
75
|
-
main1.submit()
|
|
76
|
-
|
|
77
|
-
# Indirect (via attribute)
|
|
78
|
-
serialized_a = ioutput["serialized"]
|
|
79
|
-
main2 = Main(a=A(b=serialized_a.b))
|
|
80
|
-
main2.submit()
|
|
81
|
-
|
|
82
|
-
xp.wait()
|
|
83
|
-
|
|
84
|
-
for main in (main1, main2):
|
|
85
|
-
assert main.__xpm__.job.stdout.read_text().strip() == "2"
|
|
86
|
-
assert len(main.__xpm__.job.dependencies) == 1
|
|
87
|
-
dep = next(iter(main.__xpm__.job.dependencies))
|
|
88
|
-
assert dep.origin.config is main0
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# Test that progress notification work
|
|
2
2
|
from copy import copy
|
|
3
|
-
import logging
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
import time
|
|
6
5
|
import fasteners
|
|
7
6
|
from typing import List, Tuple, Union
|
|
8
|
-
from experimaestro.commandline import CommandLineJob
|
|
9
7
|
from experimaestro import Task, Annotated, pathgenerator, progress, tqdm
|
|
10
|
-
from experimaestro.core.objects import
|
|
8
|
+
from experimaestro.core.objects import ConfigWrapper, logger
|
|
11
9
|
from experimaestro.notifications import LevelInformation
|
|
12
10
|
from experimaestro.scheduler import Job, Listener
|
|
13
11
|
from queue import Queue
|
|
@@ -74,7 +72,7 @@ def test_progress_basic():
|
|
|
74
72
|
listener = ProgressListener()
|
|
75
73
|
xp.scheduler.addlistener(listener)
|
|
76
74
|
|
|
77
|
-
out = ProgressingTask().submit()
|
|
75
|
+
out: ConfigWrapper = ProgressingTask().submit()
|
|
78
76
|
path = out.path # type: Path
|
|
79
77
|
job = out.__xpm__.job
|
|
80
78
|
|
|
@@ -87,9 +85,9 @@ def test_progress_basic():
|
|
|
87
85
|
for v in progresses:
|
|
88
86
|
writeprogress(path, v)
|
|
89
87
|
if v < 1:
|
|
90
|
-
|
|
91
|
-
logger.info("Got %s",
|
|
92
|
-
assert
|
|
88
|
+
info = listener.progresses.get()[0]
|
|
89
|
+
logger.info("Got %s", info)
|
|
90
|
+
assert info.progress == v
|
|
93
91
|
|
|
94
92
|
|
|
95
93
|
def test_progress_multiple():
|
|
@@ -105,7 +103,7 @@ def test_progress_multiple():
|
|
|
105
103
|
listener1 = ProgressListener()
|
|
106
104
|
xp1.scheduler.addlistener(listener1)
|
|
107
105
|
|
|
108
|
-
out = ProgressingTask().submit() # type:
|
|
106
|
+
out = ProgressingTask().submit() # type: ConfigWrapper
|
|
109
107
|
path = out.path # type: Path
|
|
110
108
|
job = out.__xpm__.job
|
|
111
109
|
|
|
@@ -219,7 +217,7 @@ def test_progress_nested():
|
|
|
219
217
|
listener = ProgressListener()
|
|
220
218
|
xp.scheduler.addlistener(listener)
|
|
221
219
|
|
|
222
|
-
out = NestedProgressingTask().submit() # type:
|
|
220
|
+
out = NestedProgressingTask().submit() # type: ConfigWrapper
|
|
223
221
|
job = out.__xpm__.job
|
|
224
222
|
path = out.path # type: Path
|
|
225
223
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from experimaestro import Config, Task, Param, PathBasedSerializedConfig, copyconfig
|
|
2
|
+
from experimaestro.tests.utils import TemporaryExperiment
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SubModel(Config):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Model(Config):
|
|
10
|
+
submodel: Param[SubModel]
|
|
11
|
+
|
|
12
|
+
def __post_init__(self):
|
|
13
|
+
self.initialized = False
|
|
14
|
+
self.submodel.initialized = False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SerializedModel(PathBasedSerializedConfig):
|
|
18
|
+
def initialize(self):
|
|
19
|
+
self.config.initialized = True
|
|
20
|
+
self.config.submodel.initialized = True
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Trainer(Task):
|
|
24
|
+
model: Param[Config]
|
|
25
|
+
|
|
26
|
+
def taskoutputs(self):
|
|
27
|
+
return SerializedModel(config=copyconfig(self.model))
|
|
28
|
+
|
|
29
|
+
def execute(self):
|
|
30
|
+
assert not self.model.initialized, "Model not initialized"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Evaluate(Task):
|
|
34
|
+
model: Param[Config]
|
|
35
|
+
is_submodel: Param[bool] = False
|
|
36
|
+
|
|
37
|
+
def execute(self):
|
|
38
|
+
assert self.model.initialized, "Model not initialized"
|
|
39
|
+
if self.is_submodel:
|
|
40
|
+
assert isinstance(self.model, SubModel)
|
|
41
|
+
else:
|
|
42
|
+
assert isinstance(self.model, Model)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_serializers_xp():
|
|
46
|
+
with TemporaryExperiment("serializers", maxwait=10, port=0):
|
|
47
|
+
model = Model(submodel=SubModel())
|
|
48
|
+
trained_model: Model = Trainer(model=model).submit()
|
|
49
|
+
|
|
50
|
+
# Use the model itself
|
|
51
|
+
Evaluate(model=trained_model).submit()
|
|
52
|
+
|
|
53
|
+
# Use a submodel
|
|
54
|
+
Evaluate(model=trained_model.submodel, is_submodel=True).submit()
|
experimaestro/utils/jobs.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import time
|
|
2
2
|
from experimaestro.scheduler import JobState
|
|
3
|
-
from experimaestro.core.objects import
|
|
3
|
+
from experimaestro.core.objects import ConfigWrapper
|
|
4
4
|
from experimaestro.scheduler import Listener
|
|
5
5
|
from threading import Condition
|
|
6
6
|
from tqdm.autonotebook import tqdm
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def jobmonitor(*outputs:
|
|
9
|
+
def jobmonitor(*outputs: ConfigWrapper):
|
|
10
10
|
"""Follow the progress of a list of jobs (in order)"""
|
|
11
11
|
|
|
12
12
|
cv = Condition()
|
experimaestro/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: experimaestro
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24.0
|
|
4
4
|
Summary: "Experimaestro is a computer science experiment manager"
|
|
5
5
|
Home-page: https://github.com/experimaestro/experimaestro-python
|
|
6
6
|
Author: Benjamin Piwowarski
|
|
@@ -33,12 +33,14 @@ Requires-Dist: pyparsing
|
|
|
33
33
|
Requires-Dist: humanfriendly
|
|
34
34
|
Requires-Dist: huggingface-hub (~=0.11.1)
|
|
35
35
|
Requires-Dist: gevent
|
|
36
|
+
Requires-Dist: gevent-websocket
|
|
36
37
|
Requires-Dist: flask
|
|
37
38
|
Requires-Dist: flask-socketio
|
|
38
39
|
Requires-Dist: Arpeggio (>=2.0)
|
|
39
40
|
Requires-Dist: watchdog (>2.0.0)
|
|
40
41
|
Requires-Dist: marshmallow
|
|
41
42
|
Requires-Dist: fabric
|
|
43
|
+
Requires-Dist: decorator
|
|
42
44
|
Requires-Dist: rpyc
|
|
43
45
|
Requires-Dist: typing-extensions (>=4.2) ; python_version < "3.11"
|
|
44
46
|
Requires-Dist: cached-property ; python_version < "3.9"
|
|
@@ -155,6 +157,21 @@ if __name__ == "__main__":
|
|
|
155
157
|
|
|
156
158
|
which can be launched with `python test.py /tmp/helloworld-workdir`
|
|
157
159
|
|
|
160
|
+
## 0.24.0 (2023-05-23)
|
|
161
|
+
|
|
162
|
+
### Feat
|
|
163
|
+
|
|
164
|
+
- serialized configurations
|
|
165
|
+
|
|
166
|
+
### Fix
|
|
167
|
+
|
|
168
|
+
- requirement for fabric
|
|
169
|
+
- add gevent-websocket for supporting websockets
|
|
170
|
+
|
|
171
|
+
### Refactor
|
|
172
|
+
|
|
173
|
+
- Changed TaskOutput to ConfigWrapper
|
|
174
|
+
|
|
158
175
|
## 0.23.0 (2023-04-07)
|
|
159
176
|
|
|
160
177
|
### Feat
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
experimaestro/__init__.py,sha256=
|
|
2
|
-
experimaestro/__main__.py,sha256=
|
|
1
|
+
experimaestro/__init__.py,sha256=QJipxUiZN_zgzcl1gf3lK67MCWj_-uHJbc6t_LqAIbA,1502
|
|
2
|
+
experimaestro/__main__.py,sha256=wLQ3GUriDiNL2tWWQ1lLbxHrl79OK95we9UH307rSB8,11198
|
|
3
3
|
experimaestro/annotations.py,sha256=laaAy2C57SJTPeRuD0B5jRUN0TgZYckO3fjBIR9laCs,8603
|
|
4
4
|
experimaestro/checkers.py,sha256=ZCMbnE_GFC5compWjt-fuHhPImi9fCPjImF8Ow9NqK8,696
|
|
5
5
|
experimaestro/click.py,sha256=HTfjm4poqfG69MIYztrPWEei5OVnd154UCr2pJm6yp8,2390
|
|
@@ -7,7 +7,7 @@ experimaestro/commandline.py,sha256=NS1ubme8DTJtDS2uWwdHLQiZsl6TSK1LkNxu39c3-cw,
|
|
|
7
7
|
experimaestro/compat.py,sha256=dQqE2ZNHLM2wtdfp7fBRYMfC33qNehVf9J6FGRBUQhs,171
|
|
8
8
|
experimaestro/filter.py,sha256=Qp8ObI3fBGYJjNo2TMu7N3d1rVTdYXP9PVBLFg5JUHk,5775
|
|
9
9
|
experimaestro/generators.py,sha256=9NQ_TfDfASkArLnO4PF7s5Yoo9KWjlna2DCPzk5gJOI,1230
|
|
10
|
-
experimaestro/huggingface.py,sha256=
|
|
10
|
+
experimaestro/huggingface.py,sha256=HBgQFa544TyudhWo1bxCO_AuR2wHlmUT7Xhvu7q1Y5I,3062
|
|
11
11
|
experimaestro/ipc.py,sha256=0sVzK8mZ4VCRQ5L3piCqTm1o-AVfTfJEXW26P6SOFrY,1694
|
|
12
12
|
experimaestro/locking.py,sha256=hPT-LuDGZTijpbme8O0kEoB9a3WjdVzI2h31OT44UxE,1477
|
|
13
13
|
experimaestro/mypy.py,sha256=M39VFuDrab-ymlCDIF5jys9oKpTwnuBPzb1T8Un5J3s,285
|
|
@@ -19,7 +19,7 @@ experimaestro/settings.py,sha256=E6nFyrUB6uBYQlQARczr_YKUn06zRoZ_RbPgDj6jHaU,914
|
|
|
19
19
|
experimaestro/taskglobals.py,sha256=aBjPpo4HQp6E6M3GQ8L6PR4rK2Lu0kD5dS1WjnaGgDc,499
|
|
20
20
|
experimaestro/tokens.py,sha256=aHT6lN4YLF0ujXl0nNu1sSSx-2ebrYiZFfrFvvmsQ1s,14681
|
|
21
21
|
experimaestro/typingutils.py,sha256=gkEhJ9Fir05xh5v45pYKmrGYiLCykGtlU8sL-ydFXS8,1785
|
|
22
|
-
experimaestro/version.py,sha256=
|
|
22
|
+
experimaestro/version.py,sha256=ZVPm3jk9go-IY_2KTuk6z9VsibssyAAJYCsrpb1eX2w,162
|
|
23
23
|
experimaestro/xpmutils.py,sha256=S21eMbDYsHfvmZ1HmKpq5Pz5O-1HnCLYxKbyTBbASyQ,638
|
|
24
24
|
experimaestro/connectors/__init__.py,sha256=hxcBSeVLk_7oyiIlS3l-9dGg1NGtShwvRD1tS7f8D2M,5461
|
|
25
25
|
experimaestro/connectors/local.py,sha256=w4IN_pxiPh9jb5u4yIH569ZbVHb6i2LfGhdsGFQ625o,5760
|
|
@@ -27,8 +27,9 @@ experimaestro/connectors/ssh.py,sha256=rQv_j3yQ5g-HyX4tvO_E0qD6xkrZyTq5oRYQEC1Wh
|
|
|
27
27
|
experimaestro/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
28
|
experimaestro/core/arguments.py,sha256=KzvPfO92deWsgjhrPULjFuNpr8aCDMbLhCoz5xafrtg,5824
|
|
29
29
|
experimaestro/core/context.py,sha256=70ZhDBobGDl0wEyvZyqyrnWagLs7EiRf0cBSXp1N3Lc,2479
|
|
30
|
-
experimaestro/core/objects.py,sha256=
|
|
31
|
-
experimaestro/core/objects.pyi,sha256=
|
|
30
|
+
experimaestro/core/objects.py,sha256=SoFKiqOTxQE2zMrlLAsHUBQQ_4nhTKfIPQ9TUD083Rc,57211
|
|
31
|
+
experimaestro/core/objects.pyi,sha256=yo_-tJpHbv-7m56Jr706Zpgtt6rGpEEEfZwer2AgLiw,7885
|
|
32
|
+
experimaestro/core/serializers.py,sha256=xmgdkCJVDTlhsHIHMZ8gxf0cyqAWu_wCZTZFOyht_Dc,1187
|
|
32
33
|
experimaestro/core/types.py,sha256=f8y-N0hKsqxgX_l__Vh5xIh4tudw2Ws2RkECHB3Ki44,19099
|
|
33
34
|
experimaestro/launcherfinder/__init__.py,sha256=sM9U0dDvUkyRIEv5QnU9XZCn5uJ6Q6x4OAbWPmN9qcs,148
|
|
34
35
|
experimaestro/launcherfinder/base.py,sha256=NptPJ0e8CktdhOPejocSfI_B4mloeH_EmJrbXruUCSA,1020
|
|
@@ -62,13 +63,13 @@ experimaestro/tests/task_tokens.py,sha256=vgqUa-S_YC2Id9pGOSv40qFTwq1WGZkFhr556Z
|
|
|
62
63
|
experimaestro/tests/test_checkers.py,sha256=SJ6E60Df6mpXdRvrjIAOVsJpfUgk6O9toOh6gIVc-8k,409
|
|
63
64
|
experimaestro/tests/test_findlauncher.py,sha256=yvp1YoYIfLnt8D2mLTYvM6mzd4iDrETpBudMRtsz_ls,2938
|
|
64
65
|
experimaestro/tests/test_forward.py,sha256=oxhEnH_ovOjxXNH7-Gvqw2IFaJqoFBBCGF_OLjPh488,810
|
|
65
|
-
experimaestro/tests/test_identifier.py,sha256=
|
|
66
|
-
experimaestro/tests/test_instance.py,sha256=
|
|
66
|
+
experimaestro/tests/test_identifier.py,sha256=fsW1YrXrkgHqkDh1wfu0MuY-MbLuUpRT3ro76GH_Lr8,11055
|
|
67
|
+
experimaestro/tests/test_instance.py,sha256=gW8itFn-YRN5r2VU9Dq7FlOMcEMAXAR4lJ8XHMEg5Vk,1494
|
|
67
68
|
experimaestro/tests/test_objects.py,sha256=kotxkhZXI05q6uCX_IvNh0QVXcNXdAR6GIVzx4A47xk,1191
|
|
68
|
-
experimaestro/tests/test_outputs.py,sha256=
|
|
69
|
+
experimaestro/tests/test_outputs.py,sha256=FtAQ0f5Nyg0MVwPrI6_2mcP478NscsGZV6tN4xpxQyk,870
|
|
69
70
|
experimaestro/tests/test_param.py,sha256=ONEydx4bFZKOijf86s30S2WuCsfOG9-rBY4gAWiJ7W8,6781
|
|
70
|
-
experimaestro/tests/test_progress.py,sha256=
|
|
71
|
-
experimaestro/tests/
|
|
71
|
+
experimaestro/tests/test_progress.py,sha256=wELD8Qj8b7J2d5KYU8k2wzSmHMMn2o67rXDc_iDx3-A,7656
|
|
72
|
+
experimaestro/tests/test_serializers.py,sha256=5bWSRL99keY8qCYwr_orLEf2T24qGRRjDI1TJFjBKlo,1424
|
|
72
73
|
experimaestro/tests/test_snippets.py,sha256=Q_SrQ5GJ3v5SN-gtLSlgqxykONSS0eW9vk4R4TsnpIg,3073
|
|
73
74
|
experimaestro/tests/test_ssh.py,sha256=JhwsS4lJWQeMhtnDfJhQqJ5dwEThqvcNBBgUq1EWvk0,979
|
|
74
75
|
experimaestro/tests/test_tags.py,sha256=cs69ENFEnlkBX_LErql4OCY5JFPCLILkia8Gntqlh18,2669
|
|
@@ -101,14 +102,14 @@ experimaestro/tools/diff.py,sha256=VZwoq_kzO8znuckxACrWRyWiRQnOJnNvFXt9_nycAxw,3
|
|
|
101
102
|
experimaestro/tools/jobs.py,sha256=63bXhJ7RGdczLU_nxu2skGn-9dwgr4r5pD23qH4WeBA,3516
|
|
102
103
|
experimaestro/utils/__init__.py,sha256=BdYguxAbR1jOQPV36OgGA31itaMvBJ6WVPV6b4Jn4xw,2434
|
|
103
104
|
experimaestro/utils/asyncio.py,sha256=zEQQqZW6uHGnFknp_udt9WjjtqLNNMWun9TPL6FOF64,601
|
|
104
|
-
experimaestro/utils/jobs.py,sha256=
|
|
105
|
+
experimaestro/utils/jobs.py,sha256=664eh22X_BWXoJWfIFT9rtRjM9QbOJhKkN5CyWs6H-4,2456
|
|
105
106
|
experimaestro/utils/jupyter.py,sha256=JcEo2yQK7x3Cr1tNl5FqGMZOICxCv9DwMvL5xsWdQPk,2183
|
|
106
107
|
experimaestro/utils/resources.py,sha256=gDjkrRjo7GULWyXmNXm_u1uqzEIAoAvJydICk56nOQw,1006
|
|
107
108
|
experimaestro/utils/settings.py,sha256=jpFMqF0DLL4_P1xGal0zVR5cOrdD8O0Y2IOYvnRgN3k,793
|
|
108
109
|
experimaestro/utils/yaml.py,sha256=jEjqXqUtJ333wNUdIc0o3LGvdsTQ9AKW9a9CCd-bmGU,6766
|
|
109
|
-
experimaestro-0.
|
|
110
|
-
experimaestro-0.
|
|
111
|
-
experimaestro-0.
|
|
112
|
-
experimaestro-0.
|
|
113
|
-
experimaestro-0.
|
|
114
|
-
experimaestro-0.
|
|
110
|
+
experimaestro-0.24.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
111
|
+
experimaestro-0.24.0.dist-info/METADATA,sha256=iJuR6W-vAanMpQMFk2I9sNuk1zkbQdsGIJH-iOBCEeQ,6732
|
|
112
|
+
experimaestro-0.24.0.dist-info/WHEEL,sha256=a-zpFRIJzOq5QfuhBzbhiA1eHTzNCJn8OdRvhdNX0Rk,110
|
|
113
|
+
experimaestro-0.24.0.dist-info/entry_points.txt,sha256=VsHkdvdBt9feNcnofvFyNbEDNjKU3BTSVPJcAGWNgko,591
|
|
114
|
+
experimaestro-0.24.0.dist-info/top_level.txt,sha256=siYS2iOls_-f2iUoulKaaXuQQHroIDN36S8YAI1CFZk,14
|
|
115
|
+
experimaestro-0.24.0.dist-info/RECORD,,
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# Test post-experimental serialization
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from experimaestro import Config, DataPath, Task, Param
|
|
5
|
-
from experimaestro.core.objects import ConfigInformation
|
|
6
|
-
from experimaestro.scheduler.workspace import RunMode
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class A(Config):
|
|
10
|
-
path: DataPath[Path]
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class TaskA(Task):
|
|
14
|
-
id: Param[str]
|
|
15
|
-
|
|
16
|
-
def taskoutputs(self):
|
|
17
|
-
return A()
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def test_serialization_simple(tmp_path_factory):
|
|
21
|
-
dir = tmp_path_factory.mktemp("ser")
|
|
22
|
-
|
|
23
|
-
a = A(path=Path(__file__))
|
|
24
|
-
a.__xpm__.serialize(dir)
|
|
25
|
-
|
|
26
|
-
des_a = ConfigInformation.deserialize(dir)
|
|
27
|
-
assert des_a.path != Path(__file__)
|
|
28
|
-
assert des_a.path.read_text() == Path(__file__).read_text()
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def test_serialization_identifier(tmp_path_factory):
|
|
32
|
-
dir = tmp_path_factory.mktemp("ser")
|
|
33
|
-
|
|
34
|
-
a = TaskA(id="id").submit(run_mode=RunMode.DRY_RUN)
|
|
35
|
-
a = a.__unwrap__()
|
|
36
|
-
a.__xpm__.serialize(dir)
|
|
37
|
-
|
|
38
|
-
des_a = ConfigInformation.deserialize(dir)
|
|
39
|
-
|
|
40
|
-
des_a_id = des_a.__identifier__()
|
|
41
|
-
|
|
42
|
-
assert des_a_id.all == a.__identifier__().all, (
|
|
43
|
-
"Identifier don't match: "
|
|
44
|
-
f"expected {a.__identifier__().all.hex()}, got {des_a_id.all.hex()}"
|
|
45
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|