experimaestro 1.8.0rc1__py3-none-any.whl → 1.8.0rc3__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.

@@ -0,0 +1,52 @@
1
+ from collections import defaultdict
2
+ import threading
3
+ from typing import Callable, ClassVar, Optional
4
+ from experimaestro.core.objects import ConfigInformation
5
+ from experimaestro.scheduler import Listener, Job, JobState, experiment
6
+
7
+
8
+ class TaskEventListener(Listener):
9
+ INSTANCE: ClassVar[Optional["TaskEventListener"]] = None
10
+ """The general instance"""
11
+
12
+ def __init__(self):
13
+ self.lock = threading.Lock()
14
+ self.experiments: set[int] = set()
15
+
16
+ self._on_completed: defaultdict[int, Callable] = defaultdict(list)
17
+
18
+ @staticmethod
19
+ def connect(xp: experiment):
20
+ _self = TaskEventListener.instance()
21
+ with _self.lock:
22
+ if id(xp) not in _self.experiments:
23
+ _self.experiments.add(id(xp))
24
+ xp.scheduler.addlistener(_self)
25
+
26
+ @staticmethod
27
+ def instance():
28
+ if TaskEventListener.INSTANCE is None:
29
+ TaskEventListener.INSTANCE = TaskEventListener()
30
+
31
+ return TaskEventListener.INSTANCE
32
+
33
+ def job_state(self, job: Job):
34
+ if job.state == JobState.DONE:
35
+ with self.lock:
36
+ for callback in self._on_completed.get(id(job.config.__xpm__), []):
37
+ callback()
38
+
39
+ @staticmethod
40
+ def on_completed(
41
+ config_information: ConfigInformation, callback: Callable[[], None]
42
+ ):
43
+ instance = TaskEventListener.instance()
44
+
45
+ with instance.lock:
46
+ instance._on_completed[id(config_information)].append(callback)
47
+
48
+ if (
49
+ config_information.job is not None
50
+ and config_information.job == JobState.DONE
51
+ ):
52
+ callback()
@@ -47,6 +47,7 @@ from experimaestro.core.types import DeprecatedAttribute, ObjectType
47
47
  from .context import SerializationContext, SerializedPath, SerializedPathLoader
48
48
 
49
49
  if TYPE_CHECKING:
50
+ from .callbacks import TaskEventListener
50
51
  from experimaestro.scheduler.base import Job
51
52
  from experimaestro.scheduler.workspace import RunMode
52
53
  from experimaestro.launchers import Launcher
@@ -607,6 +608,7 @@ class ConfigInformation:
607
608
 
608
609
  # State information
609
610
  self.job = None
611
+ self._job_listener: "TaskEventListener" | None = None
610
612
 
611
613
  #: True when this configuration was loaded from disk
612
614
  self.loaded = False
@@ -947,6 +949,15 @@ class ConfigInformation:
947
949
  if self.job:
948
950
  self.job.watch_output(watched)
949
951
 
952
+ def on_completed(self, callback: Callable[[], None]):
953
+ """Call a method when the task is completed successfully
954
+
955
+ :param callback: _description_
956
+ """
957
+ from .callbacks import TaskEventListener
958
+
959
+ TaskEventListener.on_completed(self, callback)
960
+
950
961
  def submit(
951
962
  self,
952
963
  workspace: "Workspace",
@@ -957,6 +968,7 @@ class ConfigInformation:
957
968
  ):
958
969
  from experimaestro.scheduler import experiment, JobContext
959
970
  from experimaestro.scheduler.workspace import RunMode
971
+ from .callbacks import TaskEventListener
960
972
 
961
973
  # --- Prepare the object
962
974
 
@@ -1009,6 +1021,7 @@ class ConfigInformation:
1009
1021
  workspace.run_mode if run_mode is None else run_mode
1010
1022
  ) or RunMode.NORMAL
1011
1023
  if run_mode == RunMode.NORMAL:
1024
+ TaskEventListener.connect(experiment.CURRENT)
1012
1025
  other = experiment.CURRENT.submit(self.job)
1013
1026
  if other:
1014
1027
  # Just returns the other task
@@ -2015,6 +2028,9 @@ class Task(LightweightTask):
2015
2028
  """
2016
2029
  self.__xpm__.watch_output(method, callback)
2017
2030
 
2031
+ def on_completed(self, callback: Callable[[], None]):
2032
+ self.__xpm__.on_completed(callback)
2033
+
2018
2034
 
2019
2035
  # --- Utility functions
2020
2036
 
@@ -36,7 +36,7 @@ def state_dict(context: SerializationContext, obj: Any):
36
36
  :param context: The serialization context
37
37
  :param obj: the object to serialize
38
38
  """
39
- objects = []
39
+ objects: list[Any] = []
40
40
  data = json_object(context, obj, objects)
41
41
  return {"objects": objects, "data": data}
42
42
 
@@ -50,11 +50,17 @@ def save_definition(obj: Any, context: SerializationContext, path: Path):
50
50
  def save(obj: Any, save_directory: Optional[Path]):
51
51
  """Saves an object into a disk file
52
52
 
53
+ The serialization process also stores in the given folder the different
54
+ files or folders that are registered as Path parameters (or
55
+ meta-parameters).
56
+
53
57
  :param save_directory: The directory in which the object and its data will
54
58
  be saved (by default, the object is saved in "definition.json")
55
59
  """
56
60
  context = SerializationContext(save_directory=save_directory)
57
- save_definition(obj, context, save_directory / "definition.json")
61
+ save_definition(
62
+ obj, context, save_directory / "definition.json" if save_directory else None
63
+ )
58
64
 
59
65
 
60
66
  def get_data_loader(path: Union[str, Path, SerializedPathLoader]):
@@ -129,3 +135,47 @@ def from_task_dir(
129
135
  content["data"] = {"type": "python", "value": content["objects"][-1]["id"]}
130
136
 
131
137
  return from_state_dict(content, as_instance=as_instance)
138
+
139
+
140
+ def serialize(
141
+ obj: Any, save_directory: Path, *, init_tasks: list["LightweightTask"] = []
142
+ ):
143
+ """Saves an object into a disk file, including initialization tasks
144
+
145
+ The serialization process also stores in the given folder the different
146
+ files or folders that are registered as Path parameters (or
147
+ meta-parameters).
148
+
149
+ :param save_directory: The directory in which the object and its data will
150
+ be saved (by default, the object is saved in "definition.json")
151
+ :param init_tasks: The optional
152
+ """
153
+ context = SerializationContext(save_directory=save_directory)
154
+ save_definition((obj, init_tasks), context, save_directory / "definition.json")
155
+
156
+
157
+ def deserialize(
158
+ path: Union[str, Path, SerializedPathLoader],
159
+ as_instance: bool = False,
160
+ ) -> tuple[Any, List["LightweightTask"]] | Any:
161
+ """Load data from disk, and initialize the object
162
+
163
+ :param path: A directory or a function that transforms relative file path
164
+ into absolute ones
165
+ :param as_instance: returns instances instead of configuration objects
166
+ :returns: either the object (as_instance is true), or a tuple
167
+ """
168
+ data_loader = get_data_loader(path)
169
+
170
+ with data_loader("definition.json").open("rt") as fh:
171
+ content = json.load(fh)
172
+
173
+ object, init_tasks = from_state_dict(content, as_instance=as_instance)
174
+
175
+ if as_instance:
176
+ for init_task in init_tasks:
177
+ init_task.execute()
178
+
179
+ return object
180
+
181
+ return object, init_tasks
@@ -1,3 +1,4 @@
1
+ import datetime
1
2
  import importlib
2
3
  import inspect
3
4
  import json
@@ -292,7 +293,7 @@ def experiments_cli( # noqa: C901
292
293
  sys.exit(0)
293
294
 
294
295
  # Move to an object container
295
- configuration = OmegaConf.to_container(
296
+ xp_configuration: ConfigurationBase = OmegaConf.to_container(
296
297
  configuration, structured_config_mode=SCMode.INSTANTIATE
297
298
  )
298
299
 
@@ -301,11 +302,22 @@ def experiments_cli( # noqa: C901
301
302
 
302
303
  workdir = ws_env.path
303
304
 
304
- logging.info("Using working directory %s", str(workdir.resolve()))
305
+ # --- Sets up the experiment ID
305
306
 
306
307
  # --- Runs the experiment
308
+ if xp_configuration.add_timestamp:
309
+ timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M")
310
+ experiment_id = f"""{xp_configuration.id}-{timestamp}"""
311
+ else:
312
+ experiment_id = xp_configuration.id
313
+
314
+ logging.info(
315
+ "Running experiment %s working directory %s",
316
+ experiment_id,
317
+ str(workdir.resolve()),
318
+ )
307
319
  with experiment(
308
- ws_env, configuration.id, host=host, port=port, run_mode=run_mode
320
+ ws_env, experiment_id, host=host, port=port, run_mode=run_mode
309
321
  ) as xp:
310
322
  # Set up the environment
311
323
  # (1) global settings (2) workspace settings and (3) command line settings
@@ -318,7 +330,7 @@ def experiments_cli( # noqa: C901
318
330
  try:
319
331
  # Run the experiment
320
332
  helper.xp = xp
321
- helper.run(list(args), configuration)
333
+ helper.run(list(args), xp_configuration)
322
334
 
323
335
  # ... and wait
324
336
  xp.wait()
@@ -51,3 +51,6 @@ class ConfigurationBase:
51
51
 
52
52
  description: str = ""
53
53
  """Description of the experiment"""
54
+
55
+ add_timestamp: bool = False
56
+ """Adds a timestamp YYYY_MM_DD-HH_MM to the experiment ID"""
@@ -819,6 +819,7 @@ class experiment:
819
819
  """
820
820
 
821
821
  from experimaestro.server import Server
822
+ from experimaestro.scheduler import Listener
822
823
 
823
824
  settings = get_settings()
824
825
  if not isinstance(env, WorkspaceSettings):
@@ -835,6 +836,7 @@ class experiment:
835
836
  self.xplock = None
836
837
  self.old_experiment = None
837
838
  self.services: Dict[str, Service] = {}
839
+ self._job_listener: Optional[Listener] = None
838
840
 
839
841
  # Get configuration settings
840
842
 
@@ -41,10 +41,33 @@ def test_experiment_history():
41
41
  task_a = TaskA().submit()
42
42
  TaskB(task_a=task_a, x=tag(1)).submit()
43
43
 
44
- # Look at the experiment
45
- xp = get_experiment("experiment", workdir=workdir)
44
+ # Look at the experiment
45
+ xp = get_experiment("experiment", workdir=workdir)
46
46
 
47
- (task_a_info,) = xp.get_jobs(TaskA)
48
- (task_b_info,) = xp.get_jobs(TaskB)
49
- assert task_b_info.tags == {"x": 1}
50
- assert task_b_info.depends_on == [task_a_info]
47
+ (task_a_info,) = xp.get_jobs(TaskA)
48
+ (task_b_info,) = xp.get_jobs(TaskB)
49
+ assert task_b_info.tags == {"x": 1}
50
+ assert task_b_info.depends_on == [task_a_info]
51
+
52
+
53
+ class FlagHandler:
54
+ def __init__(self):
55
+ self.flag = False
56
+
57
+ def set(self):
58
+ self.flag = True
59
+
60
+ def is_set(self):
61
+ return self.flag
62
+
63
+
64
+ def test_experiment_events():
65
+ """Test handlers"""
66
+
67
+ flag = FlagHandler()
68
+ with TemporaryExperiment("experiment"):
69
+ task_a = TaskA()
70
+ task_a.submit()
71
+ task_a.on_completed(flag.set)
72
+
73
+ assert flag.is_set()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: experimaestro
3
- Version: 1.8.0rc1
3
+ Version: 1.8.0rc3
4
4
  Summary: "Experimaestro is a computer science experiment manager"
5
5
  License: GPL-3
6
6
  Keywords: experiment manager
@@ -13,17 +13,18 @@ experimaestro/connectors/local.py,sha256=348akOw69t7LgiTBMPG5McCg821I8qfR5GvYNU1
13
13
  experimaestro/connectors/ssh.py,sha256=5giqvv1y0QQKF-GI0IFUzI_Z5H8Bj9EuL_Szpvk899Q,8600
14
14
  experimaestro/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  experimaestro/core/arguments.py,sha256=7hpkU1f8LJ7JL8kQaD514h9CFSfMotYLsVfMsMmdpWk,6487
16
+ experimaestro/core/callbacks.py,sha256=59JfeUgWcCCdIQ3pvh-xNnoRp9BX8f4iOAkgm16wBzE,1660
16
17
  experimaestro/core/context.py,sha256=41jvgudz4sgMDWrqOhPbgFRJHa3klWKvS3l_L661er0,2600
17
- experimaestro/core/objects.py,sha256=G0_PjcUPmbrQi8lxaxwdTq0dIw2cTCJUHduQ4XEbfFw,67362
18
+ experimaestro/core/objects.py,sha256=yZjLsStxXU1mz21JEYAMfe0UQ5pHHbgweH3E8JS5uWI,67960
18
19
  experimaestro/core/objects.pyi,sha256=NElf7J-1rL2l9Td6fQofRj-UQTtt7d0tlO7NUyewqYI,7283
19
- experimaestro/core/serialization.py,sha256=vYFbeNSgc7Icl9wgmnjZvESCehpfvlweyI_QyldA_CQ,3969
20
+ experimaestro/core/serialization.py,sha256=DB8MvWgE1d9iafgxzDWUG3nUqHjHabkR2o-0xiGSlOU,5674
20
21
  experimaestro/core/serializers.py,sha256=R_CAMyjjfU1oi-eHU6VlEUixJpFayGqEPaYu7VsD9xA,1197
21
22
  experimaestro/core/types.py,sha256=gSLv9F1HszVxI8jla6e-aVVS7q3KBwSzG1MImUHdGMg,21158
22
23
  experimaestro/core/utils.py,sha256=JfC3qGUS9b6FUHc2VxIYUI9ysNpXSQ1LjOBkjfZ8n7o,495
23
24
  experimaestro/exceptions.py,sha256=cUy83WHM3GeynxmMk6QRr5xsnpqUAdAoc-m3KQVrE2o,44
24
25
  experimaestro/experiments/__init__.py,sha256=GcpDUIbCvhnv6rxFdAp4wTffCVNTv-InY6fbQAlTy-o,159
25
- experimaestro/experiments/cli.py,sha256=iQrUjY6mmJpT-tj-Ek1QPbzNs27x4jsKjciCMgaTKZw,9722
26
- experimaestro/experiments/configuration.py,sha256=cFDiUHnUGblJsctAUxAqx0jlM7_Ja_527lzk-4G-44k,1368
26
+ experimaestro/experiments/cli.py,sha256=4jTDzQWtrdm_ZgwdkVmz5WQX9RFVCqxZV5w1e7Yymhs,10085
27
+ experimaestro/experiments/configuration.py,sha256=vVm40BJW5sZNgSDZ0oBzX15jTO9rmOewVYrm0k9iXXY,1466
27
28
  experimaestro/generators.py,sha256=DQsEgdMwRUud9suWr-QGxI3vCO5sywP6MVGZWRNQXkk,1372
28
29
  experimaestro/huggingface.py,sha256=gnVlr6SZnbutYz4PLH0Q77n1TRF-uk-dR-3UFzFqAY0,2956
29
30
  experimaestro/ipc.py,sha256=Xn3tYME83jLEB0nFak3DwEIhpL5IRZpCl3jirBF_jl4,1570
@@ -49,7 +50,7 @@ experimaestro/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
50
  experimaestro/rpyc.py,sha256=ZRKol-3tVoeoUITLNFenLF4dhWBLW_FvSV_GvsypmeI,3605
50
51
  experimaestro/run.py,sha256=58ZlIZ2dQ7a0un2iGiyHJhK14zc18BnpEFDis7OyTPA,5222
51
52
  experimaestro/scheduler/__init__.py,sha256=ERmmOxz_9mUkIuccNbzUa5Y6gVLLVDdyc4cCxbCCUbY,20
52
- experimaestro/scheduler/base.py,sha256=9Vh6S3SVEr6VF_Zp3fCf8Jl_ygnEJPVElu4ghOuSapc,35702
53
+ experimaestro/scheduler/base.py,sha256=jcAtlx-Za9TZBPJuOV9X-gQseDLMYn6VZff_Jcgy594,35809
53
54
  experimaestro/scheduler/dependencies.py,sha256=n9XegwrmjayOIxt3xhuTEBVEBGSq4oeVdzz-FviDGXo,1994
54
55
  experimaestro/scheduler/dynamic_outputs.py,sha256=yYPL98I0nSgDjlE3Sk9dtvovh2PZ6rUDnKjDNnAg1dc,5732
55
56
  experimaestro/scheduler/services.py,sha256=aCKkNZMULlceabqf-kOs_-C7KPINnjU3Q-I00o5x6iY,2189
@@ -117,7 +118,7 @@ experimaestro/tests/tasks/all.py,sha256=OMkHsWZkErCmTajiNO7hNhvnk9eKzJC-VatWgabW
117
118
  experimaestro/tests/tasks/foreign.py,sha256=uhXDOcWozkcm26ybbeEU9RElJpbhjC-zdzmlSKfPcdY,122
118
119
  experimaestro/tests/test_checkers.py,sha256=Kg5frDNRE3pvWVmmYzyk0tJFNO885KOrK48lSu-NlYA,403
119
120
  experimaestro/tests/test_dependencies.py,sha256=xfWrSkvjT45G4FSCL535m1huLT2ghmyW7kvP_XvvCJQ,2005
120
- experimaestro/tests/test_experiment.py,sha256=MXpVsWZpxINBianuTQ8vJ5iwmpIUq-GcS7YwSjLJUxM,1260
121
+ experimaestro/tests/test_experiment.py,sha256=QWF9aHewL9hepagrKKFyfikKn3iiZ_lRRXl1LLttta0,1687
121
122
  experimaestro/tests/test_findlauncher.py,sha256=8tjpR8bLMi6Gjs7KpY2t64izZso6bmY7vIivMflm-Bc,2965
122
123
  experimaestro/tests/test_forward.py,sha256=9y1zYm7hT_Lx5citxnK7n20cMZ2WJbsaEeY5irCZ9U4,735
123
124
  experimaestro/tests/test_identifier.py,sha256=L-r0S0dI9ErOZSmqGOPbbUZBvWJ-uzc3sijoyTPyMYU,13680
@@ -149,8 +150,8 @@ experimaestro/utils/jupyter.py,sha256=JcEo2yQK7x3Cr1tNl5FqGMZOICxCv9DwMvL5xsWdQP
149
150
  experimaestro/utils/resources.py,sha256=j-nvsTFwmgENMoVGOD2Ap-UD3WU85WkI0IgeSszMCX4,1328
150
151
  experimaestro/utils/settings.py,sha256=jpFMqF0DLL4_P1xGal0zVR5cOrdD8O0Y2IOYvnRgN3k,793
151
152
  experimaestro/xpmutils.py,sha256=S21eMbDYsHfvmZ1HmKpq5Pz5O-1HnCLYxKbyTBbASyQ,638
152
- experimaestro-1.8.0rc1.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
153
- experimaestro-1.8.0rc1.dist-info/METADATA,sha256=GKxZQlL20q47h2TzK9Y2ej5ymYPVZ9qjeuXDGM_t6_w,6162
154
- experimaestro-1.8.0rc1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
155
- experimaestro-1.8.0rc1.dist-info/entry_points.txt,sha256=TppTNiz5qm5xm1fhAcdLKdCLMrlL-eQggtCrCI00D9c,446
156
- experimaestro-1.8.0rc1.dist-info/RECORD,,
153
+ experimaestro-1.8.0rc3.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
154
+ experimaestro-1.8.0rc3.dist-info/METADATA,sha256=cnI8ync_pALpUSppAouAA0Clj_ksBwdlMj1m4ZXo3qE,6162
155
+ experimaestro-1.8.0rc3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
156
+ experimaestro-1.8.0rc3.dist-info/entry_points.txt,sha256=TppTNiz5qm5xm1fhAcdLKdCLMrlL-eQggtCrCI00D9c,446
157
+ experimaestro-1.8.0rc3.dist-info/RECORD,,