experimaestro 1.6.1__py3-none-any.whl → 1.15.2__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.
Files changed (98) hide show
  1. experimaestro/__init__.py +14 -3
  2. experimaestro/annotations.py +13 -3
  3. experimaestro/cli/filter.py +19 -5
  4. experimaestro/cli/jobs.py +12 -5
  5. experimaestro/commandline.py +3 -7
  6. experimaestro/connectors/__init__.py +27 -12
  7. experimaestro/connectors/local.py +19 -10
  8. experimaestro/connectors/ssh.py +1 -1
  9. experimaestro/core/arguments.py +35 -3
  10. experimaestro/core/callbacks.py +52 -0
  11. experimaestro/core/context.py +8 -9
  12. experimaestro/core/identifier.py +301 -0
  13. experimaestro/core/objects/__init__.py +44 -0
  14. experimaestro/core/{objects.py → objects/config.py} +364 -716
  15. experimaestro/core/objects/config_utils.py +58 -0
  16. experimaestro/core/objects/config_walk.py +151 -0
  17. experimaestro/core/objects.pyi +15 -45
  18. experimaestro/core/serialization.py +63 -9
  19. experimaestro/core/serializers.py +1 -8
  20. experimaestro/core/types.py +61 -6
  21. experimaestro/experiments/cli.py +79 -29
  22. experimaestro/experiments/configuration.py +3 -0
  23. experimaestro/generators.py +6 -1
  24. experimaestro/ipc.py +4 -1
  25. experimaestro/launcherfinder/parser.py +8 -3
  26. experimaestro/launcherfinder/registry.py +29 -10
  27. experimaestro/launcherfinder/specs.py +49 -10
  28. experimaestro/launchers/slurm/base.py +51 -13
  29. experimaestro/mkdocs/__init__.py +1 -1
  30. experimaestro/notifications.py +2 -1
  31. experimaestro/run.py +3 -1
  32. experimaestro/scheduler/base.py +114 -6
  33. experimaestro/scheduler/dynamic_outputs.py +184 -0
  34. experimaestro/scheduler/state.py +75 -0
  35. experimaestro/scheduler/workspace.py +2 -1
  36. experimaestro/scriptbuilder.py +13 -2
  37. experimaestro/server/data/0c35d18bf06992036b69.woff2 +0 -0
  38. experimaestro/server/data/1815e00441357e01619e.ttf +0 -0
  39. experimaestro/server/data/219aa9140e099e6c72ed.woff2 +0 -0
  40. experimaestro/server/data/2463b90d9a316e4e5294.woff2 +0 -0
  41. experimaestro/server/data/2582b0e4bcf85eceead0.ttf +0 -0
  42. experimaestro/server/data/3a4004a46a653d4b2166.woff +0 -0
  43. experimaestro/server/data/3baa5b8f3469222b822d.woff +0 -0
  44. experimaestro/server/data/4d73cb90e394b34b7670.woff +0 -0
  45. experimaestro/server/data/4ef4218c522f1eb6b5b1.woff2 +0 -0
  46. experimaestro/server/data/5d681e2edae8c60630db.woff +0 -0
  47. experimaestro/server/data/6f420cf17cc0d7676fad.woff2 +0 -0
  48. experimaestro/server/data/89999bdf5d835c012025.woff2 +0 -0
  49. experimaestro/server/data/914997e1bdfc990d0897.ttf +0 -0
  50. experimaestro/server/data/c210719e60948b211a12.woff2 +0 -0
  51. experimaestro/server/data/c380809fd3677d7d6903.woff2 +0 -0
  52. experimaestro/server/data/f882956fd323fd322f31.woff +0 -0
  53. experimaestro/server/data/favicon.ico +0 -0
  54. experimaestro/server/data/index.css +22963 -0
  55. experimaestro/server/data/index.css.map +1 -0
  56. experimaestro/server/data/index.html +27 -0
  57. experimaestro/server/data/index.js +101770 -0
  58. experimaestro/server/data/index.js.map +1 -0
  59. experimaestro/server/data/login.html +22 -0
  60. experimaestro/server/data/manifest.json +15 -0
  61. experimaestro/settings.py +2 -2
  62. experimaestro/sphinx/__init__.py +7 -17
  63. experimaestro/taskglobals.py +7 -2
  64. experimaestro/tests/core/__init__.py +0 -0
  65. experimaestro/tests/core/test_generics.py +206 -0
  66. experimaestro/tests/definitions_types.py +5 -3
  67. experimaestro/tests/launchers/bin/sbatch +34 -7
  68. experimaestro/tests/launchers/bin/srun +5 -0
  69. experimaestro/tests/launchers/common.py +16 -4
  70. experimaestro/tests/restart.py +9 -4
  71. experimaestro/tests/tasks/all.py +23 -10
  72. experimaestro/tests/tasks/foreign.py +2 -4
  73. experimaestro/tests/test_dependencies.py +0 -6
  74. experimaestro/tests/test_experiment.py +73 -0
  75. experimaestro/tests/test_findlauncher.py +11 -4
  76. experimaestro/tests/test_forward.py +5 -5
  77. experimaestro/tests/test_generators.py +93 -0
  78. experimaestro/tests/test_identifier.py +114 -99
  79. experimaestro/tests/test_instance.py +6 -21
  80. experimaestro/tests/test_objects.py +20 -4
  81. experimaestro/tests/test_param.py +60 -22
  82. experimaestro/tests/test_serializers.py +24 -64
  83. experimaestro/tests/test_tags.py +5 -11
  84. experimaestro/tests/test_tasks.py +10 -23
  85. experimaestro/tests/test_tokens.py +3 -2
  86. experimaestro/tests/test_types.py +20 -17
  87. experimaestro/tests/test_validation.py +48 -91
  88. experimaestro/tokens.py +16 -5
  89. experimaestro/typingutils.py +8 -8
  90. experimaestro/utils/asyncio.py +6 -2
  91. experimaestro/utils/multiprocessing.py +44 -0
  92. experimaestro/utils/resources.py +7 -3
  93. {experimaestro-1.6.1.dist-info → experimaestro-1.15.2.dist-info}/METADATA +27 -34
  94. experimaestro-1.15.2.dist-info/RECORD +159 -0
  95. {experimaestro-1.6.1.dist-info → experimaestro-1.15.2.dist-info}/WHEEL +1 -1
  96. experimaestro-1.6.1.dist-info/RECORD +0 -122
  97. {experimaestro-1.6.1.dist-info → experimaestro-1.15.2.dist-info}/entry_points.txt +0 -0
  98. {experimaestro-1.6.1.dist-info → experimaestro-1.15.2.dist-info/licenses}/LICENSE +0 -0
@@ -1,9 +1,10 @@
1
+ import logging
1
2
  from pathlib import Path
2
3
 
3
4
  import pytest
4
5
  from experimaestro import Config, Task, Annotated, copyconfig, default
5
6
  from experimaestro.core.arguments import Param
6
- from experimaestro.core.objects import TypeConfig
7
+ from experimaestro.core.objects import ConfigMixin
7
8
  from experimaestro.core.types import XPMValue
8
9
  from experimaestro.generators import pathgenerator
9
10
  from experimaestro.scheduler.workspace import RunMode
@@ -65,9 +66,9 @@ def test_hierarchy():
65
66
  assert issubclass(B, Config)
66
67
  assert issubclass(C, Config)
67
68
 
68
- assert not issubclass(OA, TypeConfig)
69
- assert not issubclass(OB, TypeConfig)
70
- assert not issubclass(OC, TypeConfig)
69
+ assert not issubclass(OA, ConfigMixin)
70
+ assert not issubclass(OB, ConfigMixin)
71
+ assert not issubclass(OC, ConfigMixin)
71
72
 
72
73
  assert issubclass(C, B)
73
74
 
@@ -91,3 +92,18 @@ def test_copyconfig(xp):
91
92
 
92
93
  assert copy_b.x == b.x
93
94
  assert "path" not in copy_b.__xpm__.values
95
+
96
+
97
+ def test_direct_config_warns(caplog):
98
+ """Test that using a building Config directly raises a warning"""
99
+ message = "Config.__new__ is deprecated"
100
+
101
+ with caplog.at_level(logging.WARNING):
102
+ A(x=3)
103
+ assert message in caplog.text
104
+
105
+ caplog.clear()
106
+
107
+ with caplog.at_level(logging.WARNING):
108
+ A.C(x=3)
109
+ assert message not in caplog.text
@@ -13,14 +13,16 @@ from experimaestro.core.types import DictType, IntType, StrType
13
13
  from enum import Enum
14
14
  import pytest
15
15
  from experimaestro import (
16
- config,
17
16
  Option,
18
17
  Constant,
19
18
  Param,
20
19
  Task,
21
20
  default,
21
+ Meta,
22
22
  Config,
23
23
  pathgenerator,
24
+ PathGenerator,
25
+ field,
24
26
  Annotated,
25
27
  )
26
28
  import experimaestro.core.types as types
@@ -29,25 +31,24 @@ from experimaestro.xpmutils import DirectoryContext
29
31
  # --- Test manual name for configuration
30
32
 
31
33
 
32
- @config("annotations.b")
33
- class B:
34
+ class B(Config):
35
+ __xpmid__ = "annotations.b"
34
36
  pass
35
37
 
36
38
 
37
39
  def test_fullname():
38
- assert str(B.__xpmtype__.identifier) == "annotations.b"
40
+ assert str(B.__getxpmtype__().identifier) == "annotations.b"
39
41
 
40
42
 
41
43
  # --- Automatic name for configuration
42
44
 
43
45
 
44
- @config()
45
- class A:
46
+ class A(Config):
46
47
  pass
47
48
 
48
49
 
49
50
  def test_noname():
50
- assert str(A.__xpmtype__.identifier) == "experimaestro.tests.test_param.a"
51
+ assert str(A.__getxpmtype__().identifier) == "experimaestro.tests.test_param.a"
51
52
 
52
53
 
53
54
  def serializeCycle(config: Config):
@@ -69,8 +70,7 @@ def ArgumentValue(default=None, *, help=""):
69
70
  def test_type_hinting():
70
71
  """Test for type hinting"""
71
72
 
72
- @config()
73
- class MyConfig:
73
+ class MyConfig(Config):
74
74
  """A configuration
75
75
 
76
76
  Attributes:
@@ -89,7 +89,7 @@ def test_type_hinting():
89
89
  path: Annotated[Path, pathgenerator("world")]
90
90
  option: Option[str]
91
91
 
92
- ot = MyConfig.__xpmtype__
92
+ ot = MyConfig.__getxpmtype__()
93
93
 
94
94
  # Check required parameter
95
95
  arg_x = ot.getArgument("x")
@@ -139,7 +139,7 @@ def test_type_hinting():
139
139
 
140
140
  def test_generatedpath():
141
141
  class A(Config):
142
- path: Annotated[Path, pathgenerator("test.txt")]
142
+ path: Meta[Path] = field(default_factory=PathGenerator("test.txt"))
143
143
 
144
144
  class B(Config):
145
145
  a: Param[A]
@@ -179,8 +179,7 @@ def test_config_class():
179
179
 
180
180
 
181
181
  def test_constant():
182
- @config()
183
- class A:
182
+ class A(Config):
184
183
  x: Constant[int] = 2
185
184
 
186
185
  a = A()
@@ -224,16 +223,14 @@ def test_inheritance():
224
223
 
225
224
 
226
225
  def test_redefined_param():
227
- @config()
228
- class A:
226
+ class A(Config):
229
227
  x: Param[int]
230
228
 
231
- @config()
232
- class B:
229
+ class B(Config):
233
230
  x: Param[int] = 3
234
231
 
235
- atx = A.C.__xpmtype__.getArgument("x")
236
- btx = B.C.__xpmtype__.getArgument("x")
232
+ atx = A.C.__getxpmtype__().getArgument("x")
233
+ btx = B.C.__getxpmtype__().getArgument("x")
237
234
 
238
235
  assert atx.required
239
236
 
@@ -258,18 +255,59 @@ def test_param_dict():
258
255
  A(x={"wrong": 1.2})
259
256
 
260
257
 
258
+ # --- Default
259
+
260
+
261
+ class ConfigWithDefault(Config):
262
+ x: Param[int] = field(default=1)
263
+
264
+
265
+ def test_param_default():
266
+ assert ConfigWithDefault().x == 1
267
+
268
+
269
+ class ConfigWithDefaultFactory(Config):
270
+ x: Param[int] = field(default_factory=lambda: 1)
271
+
272
+
273
+ def test_param_default_factory():
274
+ value = ConfigWithDefaultFactory()
275
+ context = DirectoryContext(Path("/__fakepath__"))
276
+ value.__xpm__.seal(context)
277
+ assert value.x == 1
278
+
279
+
261
280
  # --- Task annotations
262
281
 
263
282
 
264
283
  def test_default_mismatch():
265
284
  """Test mismatch between default and type"""
266
285
 
267
- @config()
268
- class A:
286
+ class A(Config):
269
287
  x: Param[int] = 0.2
270
288
 
271
289
  with pytest.raises(TypeError):
272
- A.__xpmtype__.getArgument("x")
290
+ A.__getxpmtype__().getArgument("x")
291
+
292
+
293
+ # --- Handling default with field
294
+
295
+
296
+ def test_param_default_set():
297
+ """Test that the default setting is well set"""
298
+
299
+ class A0(Config):
300
+ x: Param[int] = 2
301
+
302
+ assert A0().instance().x == 2
303
+ assert A0(x=3).instance().x == 3
304
+
305
+ class A(Config):
306
+ x: Param[int] = field(default_factory=lambda: 2)
307
+
308
+ assert A().instance().x == 2
309
+
310
+ assert A(x=3).instance().x == 3
273
311
 
274
312
 
275
313
  # --- Handling help annotations
@@ -1,70 +1,12 @@
1
+ from typing import Optional
1
2
  from experimaestro import (
2
3
  Config,
3
- Task,
4
4
  Param,
5
- SerializationLWTask,
6
- copyconfig,
7
5
  state_dict,
8
6
  from_state_dict,
9
7
  )
10
8
  from experimaestro.core.context import SerializationContext
11
- from experimaestro.core.objects import TypeConfig
12
- from experimaestro.tests.utils import TemporaryExperiment
13
-
14
-
15
- class SubModel(Config):
16
- pass
17
-
18
-
19
- class Model(Config):
20
- submodel: Param[SubModel]
21
-
22
- def __post_init__(self):
23
- self.initialized = False
24
- self.submodel.initialized = False
25
-
26
-
27
- class LoadModel(SerializationLWTask):
28
- def execute(self):
29
- self.value.initialized = True
30
- self.value.submodel.initialized = True
31
-
32
-
33
- class Trainer(Task):
34
- model: Param[Config]
35
-
36
- def task_outputs(self, dep):
37
- model = copyconfig(self.model)
38
- return model.add_pretasks(dep(LoadModel(value=model)))
39
-
40
- def execute(self):
41
- assert not self.model.initialized, "Model not initialized"
42
-
43
-
44
- class Evaluate(Task):
45
- model: Param[Config]
46
- is_submodel: Param[bool] = False
47
-
48
- def execute(self):
49
- assert self.model.initialized, "Model not initialized"
50
- if self.is_submodel:
51
- assert isinstance(self.model, SubModel)
52
- else:
53
- assert isinstance(self.model, Model)
54
-
55
-
56
- def test_serializers_xp():
57
- with TemporaryExperiment("serializers", maxwait=20, port=0):
58
- model = Model(submodel=SubModel())
59
- trained_model: Model = Trainer(model=model).submit()
60
-
61
- # Use the model itself
62
- Evaluate(model=trained_model).submit()
63
-
64
- # Use a submodel
65
- Evaluate(model=trained_model.submodel, is_submodel=True).add_pretasks_from(
66
- trained_model
67
- ).submit()
9
+ from experimaestro.core.objects import ConfigMixin
68
10
 
69
11
 
70
12
  class Object1(Config):
@@ -78,17 +20,35 @@ class Object2(Config):
78
20
  def test_serializers_serialization():
79
21
  context = SerializationContext(save_directory=None)
80
22
 
81
- obj1 = Object1()
82
- obj2 = Object2(object=obj1)
23
+ obj1 = Object1.C()
24
+ obj2 = Object2.C(object=obj1)
83
25
 
84
26
  data = state_dict(context, [obj1, obj2])
85
27
 
86
28
  [obj1, obj2] = from_state_dict(data)
87
- assert isinstance(obj1, Object1) and isinstance(obj1, TypeConfig)
29
+ assert isinstance(obj1, Object1) and isinstance(obj1, ConfigMixin)
88
30
  assert isinstance(obj2, Object2)
89
31
  assert obj2.object is obj1
90
32
 
91
33
  [obj1, obj2] = from_state_dict(data, as_instance=True)
92
- assert isinstance(obj1, Object1) and not isinstance(obj1, TypeConfig)
34
+ assert isinstance(obj1, Object1) and not isinstance(obj1, ConfigMixin)
93
35
  assert isinstance(obj2, Object2)
94
36
  assert obj2.object is obj1
37
+
38
+
39
+ class SubConfig(Config):
40
+ pass
41
+
42
+
43
+ class MultiParamObject(Config):
44
+ opt_a: Param[Optional[int]]
45
+
46
+ x: Param[dict[str, Optional[SubConfig]]]
47
+
48
+
49
+ def test_serializers_types():
50
+ context = SerializationContext(save_directory=None)
51
+
52
+ config = MultiParamObject.C(x={"a": None})
53
+ config.__xpm__.seal(context)
54
+ state_dict(context, config)
@@ -3,8 +3,6 @@ from pathlib import Path
3
3
  from experimaestro import (
4
4
  tag,
5
5
  LightweightTask,
6
- config,
7
- argument,
8
6
  Config,
9
7
  Task,
10
8
  Param,
@@ -13,17 +11,13 @@ from experimaestro.scheduler.workspace import RunMode
13
11
  from experimaestro.xpmutils import DirectoryContext
14
12
 
15
13
 
16
- @argument("x", type=int)
17
- @config()
18
- class Config1:
19
- pass
14
+ class Config1(Config):
15
+ x: Param[int]
20
16
 
21
17
 
22
- @argument("x", type=int)
23
- @argument("c", type=Config1)
24
- @config()
25
- class Config2:
26
- pass
18
+ class Config2(Config):
19
+ x: Param[int]
20
+ c: Param[Config1]
27
21
 
28
22
 
29
23
  def test_tag():
@@ -123,9 +123,16 @@ def test_restart(terminate):
123
123
  def test_submitted_twice():
124
124
  """Check that a job cannot be submitted twice within the same experiment"""
125
125
  with TemporaryExperiment("duplicate", maxwait=20):
126
- task1 = SimpleTask(x=1).submit()
127
- task2 = SimpleTask(x=1).submit()
128
- assert task1 is task2, f"{id(task1)} != {id(task2)}"
126
+
127
+ task1 = SimpleTask.C(x=1)
128
+ o1 = task1.submit()
129
+
130
+ task2 = SimpleTask.C(x=1)
131
+ o2 = task2.submit()
132
+
133
+ print(o1)
134
+ assert o1.task is not o2.task
135
+ assert task1.__xpm__.job is task2.__xpm__.job, f"{id(task1)} != {id(task2)}"
129
136
 
130
137
 
131
138
  def test_configcache():
@@ -299,26 +306,6 @@ class MyLightweightTask(Task):
299
306
  assert self.x.data == 1
300
307
 
301
308
 
302
- def test_task_lightweight():
303
- with TemporaryExperiment("lightweight", maxwait=20):
304
- x = LightweightConfig()
305
- lwtask = LightweightTask(x=x)
306
- assert (
307
- MyLightweightTask(x=x).add_pretasks(lwtask).submit().__xpm__.job.wait()
308
- == JobState.DONE
309
- ), "Pre-tasks should be executed"
310
-
311
- x_2 = LightweightConfig()
312
- lwtask_2 = LightweightTask(x=x)
313
- assert (
314
- MyLightweightTask(x=x_2.add_pretasks(lwtask_2))
315
- .add_pretasks(lwtask_2)
316
- .submit()
317
- .__xpm__.job.wait()
318
- == JobState.DONE
319
- ), "Pre-tasks should be run just once"
320
-
321
-
322
309
  def test_task_lightweight_init():
323
310
  with TemporaryExperiment("lightweight_init", maxwait=20):
324
311
  x = LightweightConfig()
@@ -7,7 +7,7 @@ import time
7
7
  from pathlib import Path
8
8
 
9
9
  import subprocess
10
- from experimaestro import Task, param
10
+ from experimaestro import Task, Param
11
11
  from experimaestro.tokens import CounterToken, TokenFile
12
12
  from experimaestro.scheduler import JobState
13
13
  from .utils import (
@@ -74,8 +74,9 @@ def test_token_ok():
74
74
  logging.info("Finished token_ok test")
75
75
 
76
76
 
77
- @param("x", type=int)
78
77
  class dummy_task(Task):
78
+ x: Param[int]
79
+
79
80
  def execute(self):
80
81
  pass
81
82
 
@@ -1,37 +1,32 @@
1
1
  # --- Task and types definitions
2
2
 
3
3
  import logging
4
- from experimaestro import Config, config
5
- from experimaestro.core.objects import TypeConfig
4
+ from experimaestro import Config, Param
5
+ from typing import Union
6
6
 
7
- from .utils import TemporaryExperiment
8
- from experimaestro.scheduler import JobState
7
+ import pytest
8
+ from experimaestro.core.objects import ConfigMixin
9
9
 
10
10
 
11
11
  def test_multiple_inheritance():
12
- @config()
13
- class A:
12
+ class A(Config):
14
13
  pass
15
14
 
16
- @config()
17
- class B:
15
+ class B(Config):
18
16
  pass
19
17
 
20
- @config()
21
18
  class B1(B):
22
19
  pass
23
20
 
24
- @config()
25
21
  class C1(B1, A):
26
22
  pass
27
23
 
28
- @config()
29
24
  class C2(A, B1):
30
25
  pass
31
26
 
32
27
  for C in (C1, C2):
33
28
  logging.info("Testing %s", C)
34
- ctype = C.__xpmtype__
29
+ ctype = C.C.__xpmtype__
35
30
  assert issubclass(C, A)
36
31
  assert issubclass(C, B)
37
32
  assert issubclass(C, B1)
@@ -41,22 +36,30 @@ def test_multiple_inheritance():
41
36
  assert issubclass(C.__xpmtype__.objecttype, B1.__xpmtype__.basetype)
42
37
  assert issubclass(C.__xpmtype__.objecttype, B.__xpmtype__.basetype)
43
38
  assert issubclass(C.__xpmtype__.objecttype, A.__xpmtype__.basetype)
44
- assert not issubclass(C.__xpmtype__.objecttype, TypeConfig)
39
+ assert not issubclass(C.__xpmtype__.objecttype, ConfigMixin)
45
40
 
46
41
 
47
42
  def test_missing_hierarchy():
48
- @config()
49
- class A:
43
+ class A(Config):
50
44
  pass
51
45
 
52
46
  class A1(A):
53
47
  pass
54
48
 
55
- @config()
56
49
  class B(A1):
57
50
  pass
58
51
 
59
- B.__xpmtype__
52
+ B.C.__xpmtype__
60
53
 
61
54
  assert issubclass(B, A)
62
55
  assert issubclass(B, A1)
56
+
57
+
58
+ def test_types_union():
59
+ class A(Config):
60
+ x: Param[Union[int, str]]
61
+
62
+ A(x=1)
63
+ A(x="hello")
64
+ with pytest.raises(ValueError):
65
+ A(x=[])