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 CHANGED
@@ -44,11 +44,11 @@ from .core.objects import (
44
44
  Config,
45
45
  copyconfig,
46
46
  setmeta,
47
- TaskOutput,
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 TaskOutput
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, TaskOutput):
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():
@@ -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, TaskOutput):
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, TaskOutput):
282
- dependencies.add(value.__xpm__.task.__xpm__.dependency())
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, TaskOutput):
433
+ if isinstance(x, ConfigWrapper):
423
434
  # Process task if different
424
- if self.recurse_task and x.__xpm__.task is not x.__unwrap__():
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
- ) -> "TaskOutput":
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 = TaskOutput(config, self.pyobject)
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 = TaskOutput(value, self.pyobject)
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 = TaskOutput(self.pyobject, self.pyobject)
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, SerializedTaskOutput):
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, TaskOutput):
918
+ if isinstance(value, ConfigWrapper):
912
919
  # We will need to output the task configuration objects
913
- ConfigInformation.__collect_objects__(value.__xpm__.task, objects, context)
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 TaskOutput(obj, task)
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(self, *, workspace=None, launcher=None, run_mode: "RunMode" = None):
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
- :return: a :py:class:TaskOutput object
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 AttrAccessor:
1540
- def __init__(self, key: Any, default: Any):
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
- class Serialized:
1552
- """Simple serialization object"""
1605
+ # The task
1606
+ if isinstance(value, Task):
1607
+ self.task = value
1608
+ else:
1609
+ self.task = task
1553
1610
 
1554
- def __init__(self, value):
1555
- self.value = value
1611
+ # Holds serialized config information
1612
+ from .serializers import SerializedConfig
1556
1613
 
1557
- def toJSON(self):
1558
- return self.value
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
- class SerializedConfig:
1562
- """A serializable configuration
1625
+ def __getitem__(self, key: Any):
1626
+ kwargs = self.__state_dict__()
1627
+ value = self.value.__getitem__(key)
1563
1628
 
1564
- This can be used to define a loading mechanism when instanciating the
1565
- configuration
1566
- """
1629
+ if self.path:
1630
+ kwargs["path"].append(ItemAccessor(key))
1567
1631
 
1568
- pyobject: Config
1569
- """The configuration that will be serialized"""
1632
+ return ConfigWrapperInfo(value, **kwargs)
1570
1633
 
1571
- def __init__(self, pyobject: Config, loader: Callable[[Path], Config]):
1572
- self.pyobject = pyobject
1573
- self.loader = loader
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
- class TaskOutputInfo:
1577
- def __init__(self, task: Task):
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
- class TaskOutput(Proxy):
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, value: Any, task: Union[Task, TaskOutputInfo]):
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
- return TaskOutput(value, self.__xpm__.task)
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._wrap(self.__xpm__.value.__getitem__(key))
1697
+ return self.__xpm__[key].wrap()
1632
1698
 
1633
- def __getattr__(self, key: str, default=None) -> Any:
1634
- return self._wrap(getattr(self.__xpm__.value, key, default))
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
- class SerializedTaskOutput(TaskOutput):
1646
- """Used when serializing a configuration
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, TaskOutput], **kwargs):
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, TaskOutput):
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 TaskOutput(copy, output.__xpm__)
1743
+ return ConfigWrapper(copy, output.__xpm__)
1706
1744
 
1707
1745
 
1708
1746
  def setmeta(config: Config, flag: bool):
@@ -181,7 +181,8 @@ class TypeConfig:
181
181
  *,
182
182
  workspace: Incomplete | None = ...,
183
183
  launcher: Incomplete | None = ...,
184
- run_mode: RunMode = ...
184
+ run_mode: RunMode = ...,
185
+ pre: List[Task] = []
185
186
  ): ...
186
187
  def stdout(self): ...
187
188
  def stderr(self): ...
@@ -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"""
@@ -1,6 +1,6 @@
1
1
  from pathlib import Path
2
2
  from typing import Optional, Union
3
- from experimaestro import Config, TaskOutput
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, TaskOutput], variant: Optional[str] = None
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, TaskOutput, setmeta
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, TaskOutput):
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 IdentifierReloadTaskOutput(Task):
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 IdentifierReloadTaskOutputDerived(Config):
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 = IdentifierReloadTaskOutput(id="123").submit(run_mode=RunMode.DRY_RUN)
431
- config = IdentifierReloadTaskOutputDerived(task=task)
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
- @config()
35
- class SerializedConfig:
36
- x: Param[int] = 1
37
- pass
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
- def test_instance(self):
44
- import pickle
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
- s_a = pickle.dumps(a)
49
+ def test_instance_serialized():
50
+ model = SerializedModel(config=Model())
51
+ trainer = Trainer(model=model)
52
+ instance = trainer.instance()
51
53
 
52
- deserialized = pickle.loads(s_a)
53
- assert not isinstance(deserialized, TypeConfig)
54
- assert deserialized.x == 2
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(ioutput["serialized"], TaskOutput)
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 TaskOutput, logger
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() # type: TaskOutput
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
- l = listener.progresses.get()[0]
91
- logger.info("Got %s", l)
92
- assert l.progress == v
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: TaskOutput
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: TaskOutput
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()
@@ -1,12 +1,12 @@
1
1
  import time
2
2
  from experimaestro.scheduler import JobState
3
- from experimaestro.core.objects import TaskOutput
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: TaskOutput):
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,4 +1,4 @@
1
1
  # file generated by setuptools_scm
2
2
  # don't change, don't track in version control
3
- __version__ = version = '0.23.0'
4
- __version_tuple__ = version_tuple = (0, 23, 0)
3
+ __version__ = version = '0.24.0'
4
+ __version_tuple__ = version_tuple = (0, 24, 0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: experimaestro
3
- Version: 0.23.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=FvCfQt53icBffG2vqLb9zTdBkB04cQhLbiabPmY1yVs,1403
2
- experimaestro/__main__.py,sha256=DcrhIXrMACuPp40CO-PAKTytHhPaozQwqnGIQ8NfoHs,11192
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=2f0MqNxfth90R7kO6VVJ3K5tXdx95w2d1ljggrL6l_E,3056
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=Ktf2waZZK1xthVXoT6iRjDhQfoED_kinKyheuED-ztk,162
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=IijmRTPTVMsUKHgVugOJam-Ml_ms83vFfy4C1igN6mc,56141
31
- experimaestro/core/objects.pyi,sha256=vMy3EWCSwZNky8zr5i80C9lAkx4ON8Y46dcBNQpdE58,7855
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=xg0AgPqim1hq9nAvODROP9Vv7atLL1DFAKXbcEsjp4k,10488
66
- experimaestro/tests/test_instance.py,sha256=m_4AdxKfa5Zzgn3w6_AOhT9wGrHEhZdDt1BmCEOtY6M,1355
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=UrO_QB50lDkVH2gNBco31kCRdga1bpX79sLqE_22hco,2028
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=ZrDHtlEhXcLUYOK3Zn8rNkhWwUXvYs42SVeMTHW1xco,7711
71
- experimaestro/tests/test_serialization.py,sha256=U1YlaWjxCFO86VOkfKplOtIr0ZHOUJOy9jfoLGLm2qM,1132
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=srnme_mrUl2jdokqRBZM7wWFQH_aq61ceEpiJMRxix8,2450
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.23.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
110
- experimaestro-0.23.0.dist-info/METADATA,sha256=SK-V-y7c9HJeXvhxyicyC2k9u8C6POr_0Jlu4ycZ1Ds,6475
111
- experimaestro-0.23.0.dist-info/WHEEL,sha256=a-zpFRIJzOq5QfuhBzbhiA1eHTzNCJn8OdRvhdNX0Rk,110
112
- experimaestro-0.23.0.dist-info/entry_points.txt,sha256=VsHkdvdBt9feNcnofvFyNbEDNjKU3BTSVPJcAGWNgko,591
113
- experimaestro-0.23.0.dist-info/top_level.txt,sha256=siYS2iOls_-f2iUoulKaaXuQQHroIDN36S8YAI1CFZk,14
114
- experimaestro-0.23.0.dist-info/RECORD,,
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
- )