experimaestro 1.5.1__py3-none-any.whl → 2.0.0a8__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.

Files changed (118) hide show
  1. experimaestro/__init__.py +14 -4
  2. experimaestro/__main__.py +3 -423
  3. experimaestro/annotations.py +14 -4
  4. experimaestro/cli/__init__.py +311 -0
  5. experimaestro/{filter.py → cli/filter.py} +23 -9
  6. experimaestro/cli/jobs.py +268 -0
  7. experimaestro/cli/progress.py +269 -0
  8. experimaestro/click.py +0 -35
  9. experimaestro/commandline.py +3 -7
  10. experimaestro/connectors/__init__.py +29 -14
  11. experimaestro/connectors/local.py +19 -10
  12. experimaestro/connectors/ssh.py +27 -8
  13. experimaestro/core/arguments.py +45 -3
  14. experimaestro/core/callbacks.py +52 -0
  15. experimaestro/core/context.py +8 -9
  16. experimaestro/core/identifier.py +310 -0
  17. experimaestro/core/objects/__init__.py +44 -0
  18. experimaestro/core/{objects.py → objects/config.py} +399 -772
  19. experimaestro/core/objects/config_utils.py +58 -0
  20. experimaestro/core/objects/config_walk.py +151 -0
  21. experimaestro/core/objects.pyi +15 -45
  22. experimaestro/core/serialization.py +63 -9
  23. experimaestro/core/serializers.py +1 -8
  24. experimaestro/core/types.py +104 -66
  25. experimaestro/experiments/cli.py +154 -72
  26. experimaestro/experiments/configuration.py +10 -1
  27. experimaestro/generators.py +6 -1
  28. experimaestro/ipc.py +4 -1
  29. experimaestro/launcherfinder/__init__.py +1 -1
  30. experimaestro/launcherfinder/base.py +2 -18
  31. experimaestro/launcherfinder/parser.py +8 -3
  32. experimaestro/launcherfinder/registry.py +52 -140
  33. experimaestro/launcherfinder/specs.py +49 -10
  34. experimaestro/launchers/direct.py +0 -47
  35. experimaestro/launchers/slurm/base.py +54 -14
  36. experimaestro/mkdocs/__init__.py +1 -1
  37. experimaestro/mkdocs/base.py +6 -8
  38. experimaestro/notifications.py +38 -12
  39. experimaestro/progress.py +406 -0
  40. experimaestro/run.py +24 -3
  41. experimaestro/scheduler/__init__.py +18 -1
  42. experimaestro/scheduler/base.py +108 -808
  43. experimaestro/scheduler/dynamic_outputs.py +184 -0
  44. experimaestro/scheduler/experiment.py +387 -0
  45. experimaestro/scheduler/jobs.py +475 -0
  46. experimaestro/scheduler/signal_handler.py +32 -0
  47. experimaestro/scheduler/state.py +75 -0
  48. experimaestro/scheduler/workspace.py +27 -8
  49. experimaestro/scriptbuilder.py +18 -3
  50. experimaestro/server/__init__.py +36 -5
  51. experimaestro/server/data/1815e00441357e01619e.ttf +0 -0
  52. experimaestro/server/data/2463b90d9a316e4e5294.woff2 +0 -0
  53. experimaestro/server/data/2582b0e4bcf85eceead0.ttf +0 -0
  54. experimaestro/server/data/89999bdf5d835c012025.woff2 +0 -0
  55. experimaestro/server/data/914997e1bdfc990d0897.ttf +0 -0
  56. experimaestro/server/data/c210719e60948b211a12.woff2 +0 -0
  57. experimaestro/server/data/index.css +5187 -5068
  58. experimaestro/server/data/index.css.map +1 -1
  59. experimaestro/server/data/index.js +68887 -68064
  60. experimaestro/server/data/index.js.map +1 -1
  61. experimaestro/settings.py +45 -5
  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 +17 -5
  70. experimaestro/tests/launchers/config_slurm/launchers.py +25 -0
  71. experimaestro/tests/restart.py +10 -5
  72. experimaestro/tests/tasks/all.py +23 -10
  73. experimaestro/tests/tasks/foreign.py +2 -4
  74. experimaestro/tests/test_checkers.py +2 -2
  75. experimaestro/tests/test_dependencies.py +11 -17
  76. experimaestro/tests/test_experiment.py +73 -0
  77. experimaestro/tests/test_file_progress.py +425 -0
  78. experimaestro/tests/test_file_progress_integration.py +477 -0
  79. experimaestro/tests/test_findlauncher.py +12 -5
  80. experimaestro/tests/test_forward.py +5 -5
  81. experimaestro/tests/test_generators.py +93 -0
  82. experimaestro/tests/test_identifier.py +182 -158
  83. experimaestro/tests/test_instance.py +19 -27
  84. experimaestro/tests/test_objects.py +13 -20
  85. experimaestro/tests/test_outputs.py +6 -6
  86. experimaestro/tests/test_param.py +68 -30
  87. experimaestro/tests/test_progress.py +4 -4
  88. experimaestro/tests/test_serializers.py +24 -64
  89. experimaestro/tests/test_ssh.py +7 -0
  90. experimaestro/tests/test_tags.py +50 -21
  91. experimaestro/tests/test_tasks.py +42 -51
  92. experimaestro/tests/test_tokens.py +11 -8
  93. experimaestro/tests/test_types.py +24 -21
  94. experimaestro/tests/test_validation.py +67 -110
  95. experimaestro/tests/token_reschedule.py +1 -1
  96. experimaestro/tokens.py +24 -13
  97. experimaestro/tools/diff.py +8 -1
  98. experimaestro/typingutils.py +20 -11
  99. experimaestro/utils/asyncio.py +6 -2
  100. experimaestro/utils/multiprocessing.py +44 -0
  101. experimaestro/utils/resources.py +11 -3
  102. {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/METADATA +28 -36
  103. experimaestro-2.0.0a8.dist-info/RECORD +166 -0
  104. {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/WHEEL +1 -1
  105. {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/entry_points.txt +0 -4
  106. experimaestro/launchers/slurm/cli.py +0 -29
  107. experimaestro/launchers/slurm/configuration.py +0 -597
  108. experimaestro/scheduler/environment.py +0 -94
  109. experimaestro/server/data/016b4a6cdced82ab3aa1.ttf +0 -0
  110. experimaestro/server/data/50701fbb8177c2dde530.ttf +0 -0
  111. experimaestro/server/data/878f31251d960bd6266f.woff2 +0 -0
  112. experimaestro/server/data/b041b1fa4fe241b23445.woff2 +0 -0
  113. experimaestro/server/data/b6879d41b0852f01ed5b.woff2 +0 -0
  114. experimaestro/server/data/d75e3fd1eb12e9bd6655.ttf +0 -0
  115. experimaestro/tests/launchers/config_slurm/launchers.yaml +0 -134
  116. experimaestro/utils/yaml.py +0 -202
  117. experimaestro-1.5.1.dist-info/RECORD +0 -148
  118. {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info/licenses}/LICENSE +0 -0
@@ -3,8 +3,7 @@ from pathlib import Path
3
3
  import pytest
4
4
  from experimaestro import Config, Task, Annotated, copyconfig, default
5
5
  from experimaestro.core.arguments import Param
6
- from experimaestro.core.objects import TypeConfig
7
- from experimaestro.core.types import XPMValue
6
+ from experimaestro.core.objects import ConfigMixin
8
7
  from experimaestro.generators import pathgenerator
9
8
  from experimaestro.scheduler.workspace import RunMode
10
9
  from experimaestro.tests.utils import TemporaryExperiment
@@ -27,7 +26,7 @@ def test_object_default():
27
26
 
28
27
 
29
28
  class B(Config):
30
- a: Param[A] = A(x=3)
29
+ a: Param[A] = A.C(x=3)
31
30
 
32
31
 
33
32
  class C(B):
@@ -39,43 +38,37 @@ class D(B, A):
39
38
 
40
39
 
41
40
  class DefaultAnnotationConfig(Config):
42
- a: Annotated[A, default(A(x=3))]
41
+ a: Annotated[A, default(A.C(x=3))]
43
42
 
44
43
 
45
44
  def test_object_config_default():
46
45
  """Test default configurations as default values"""
47
- b = B()
46
+ b = B.C()
48
47
  assert b.a.x == 3
49
48
 
50
- c = C()
49
+ c = C.C()
51
50
  assert c.a.x == 3
52
51
 
53
- annotationConfig = DefaultAnnotationConfig()
52
+ annotationConfig = DefaultAnnotationConfig.C()
54
53
  assert annotationConfig.a.x == 3
55
54
 
56
55
 
57
56
  def test_hierarchy():
58
57
  """Test if the object hierarchy is OK"""
59
- OA = A.__getxpmtype__().objecttype
60
- OB = B.__getxpmtype__().objecttype
61
- OC = C.__getxpmtype__().objecttype
62
- OD = D.__getxpmtype__().objecttype
58
+ OA = A.__getxpmtype__().value_type
59
+ OB = B.__getxpmtype__().value_type
60
+ OC = C.__getxpmtype__().value_type
63
61
 
64
62
  assert issubclass(A, Config)
65
63
  assert issubclass(B, Config)
66
64
  assert issubclass(C, Config)
67
65
 
68
- assert not issubclass(OA, TypeConfig)
69
- assert not issubclass(OB, TypeConfig)
70
- assert not issubclass(OC, TypeConfig)
66
+ assert not issubclass(OA, ConfigMixin)
67
+ assert not issubclass(OB, ConfigMixin)
68
+ assert not issubclass(OC, ConfigMixin)
71
69
 
72
70
  assert issubclass(C, B)
73
71
 
74
- assert OA.__bases__ == (A, XPMValue)
75
- assert OB.__bases__ == (B, XPMValue)
76
- assert OC.__bases__ == (C, B.XPMValue)
77
- assert OD.__bases__ == (D, B.XPMValue, A.XPMValue)
78
-
79
72
 
80
73
  class CopyConfig(Task):
81
74
  path: Annotated[Path, pathgenerator("hello.txt")]
@@ -83,7 +76,7 @@ class CopyConfig(Task):
83
76
 
84
77
 
85
78
  def test_copyconfig(xp):
86
- b = CopyConfig(x=2)
79
+ b = CopyConfig.C(x=2)
87
80
 
88
81
  b.submit()
89
82
 
@@ -32,17 +32,17 @@ class MainB(Task):
32
32
 
33
33
 
34
34
  def test_output_taskoutput():
35
- a = A(b=B())
36
- output, ioutput = Main(a=a).submit(run_mode=RunMode.DRY_RUN)
35
+ a = A.C(b=B.C())
36
+ output, ioutput = Main.C(a=a).submit(run_mode=RunMode.DRY_RUN)
37
37
 
38
38
  # Direct
39
- Main(a=output)
39
+ Main.C(a=output)
40
40
 
41
41
  # Via getattr
42
- Main(a=A(b=output.b))
42
+ Main.C(a=A.C(b=output.b))
43
43
 
44
44
  # Via getitem
45
- Main(a=ioutput["a"])
45
+ Main.C(a=ioutput["a"])
46
46
 
47
47
  # Now, submits
48
- Main(a=output).submit(run_mode=RunMode.DRY_RUN)
48
+ Main.C(a=output).submit(run_mode=RunMode.DRY_RUN)
@@ -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]
@@ -148,7 +148,7 @@ def test_generatedpath():
148
148
  b: Param[B]
149
149
 
150
150
  basepath = Path("/tmp/testconflict")
151
- c = C(b=B(a=A())).instance(DirectoryContext(basepath))
151
+ c = C.C(b=B.C(a=A.C())).instance(DirectoryContext(basepath))
152
152
  assert c.b.a.path.relative_to(basepath) == Path("out/b/a/test.txt")
153
153
 
154
154
 
@@ -158,13 +158,13 @@ def test_config_class():
158
158
  class A(Config):
159
159
  x: Param[int]
160
160
 
161
- a = A(x=1)
161
+ a = A.C(x=1)
162
162
  assert a.x == 1
163
163
 
164
164
  class B(A):
165
165
  y: Param[int]
166
166
 
167
- b = B(x=1, y=2)
167
+ b = B.C(x=1, y=2)
168
168
  assert b.x == 1
169
169
  assert b.y == 2
170
170
 
@@ -174,16 +174,15 @@ def test_config_class():
174
174
  class C(Config):
175
175
  d: Param[D]
176
176
 
177
- c = C(d=D(x=1))
177
+ c = C.C(d=D.C(x=1))
178
178
  assert c.d.x == 1
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
- a = A()
185
+ a = A.C()
187
186
  assert a.x == 2, "Constant value not set"
188
187
 
189
188
  # We should not be able to change the value
@@ -203,7 +202,7 @@ class EnumConfig(Config):
203
202
  def test_param_enum():
204
203
  """Test for enum values"""
205
204
 
206
- a = EnumConfig(x=EnumParam.OTHER)
205
+ a = EnumConfig.C(x=EnumParam.OTHER)
207
206
  _a = serializeCycle(a)
208
207
 
209
208
  assert isinstance(_a, EnumConfig)
@@ -217,23 +216,21 @@ def test_inheritance():
217
216
  class B(A):
218
217
  y: Param[int] = 3
219
218
 
220
- b = B()
219
+ b = B.C()
221
220
  b.x = 2
222
221
  assert b.__xpm__.values["y"] == 3
223
222
  assert b.__xpm__.values["x"] == 2
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
 
@@ -250,7 +247,7 @@ def test_param_dict():
250
247
  assert isinstance(xarg.keytype, StrType)
251
248
  assert isinstance(xarg.valuetype, IntType)
252
249
 
253
- A(x={"OK": 1})
250
+ A.C(x={"OK": 1})
254
251
 
255
252
  with pytest.raises(TypeError):
256
253
  A(x={"wrong": "string"})
@@ -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.C().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.C()
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.C().instance().x == 2
303
+ assert A0.C(x=3).instance().x == 3
304
+
305
+ class A(Config):
306
+ x: Param[int] = field(default_factory=lambda: 2)
307
+
308
+ assert A.C().instance().x == 2
309
+
310
+ assert A.C(x=3).instance().x == 3
273
311
 
274
312
 
275
313
  # --- Handling help annotations
@@ -72,7 +72,7 @@ def test_progress_basic():
72
72
  listener = ProgressListener()
73
73
  xp.scheduler.addlistener(listener)
74
74
 
75
- out = ProgressingTask().submit()
75
+ out = ProgressingTask.C().submit()
76
76
  path = out.path # type: Path
77
77
  job = out.__xpm__.job
78
78
 
@@ -103,7 +103,7 @@ def test_progress_multiple():
103
103
  listener1 = ProgressListener()
104
104
  xp1.scheduler.addlistener(listener1)
105
105
 
106
- out = ProgressingTask().submit()
106
+ out = ProgressingTask.C().submit()
107
107
  path = out.path # type: Path
108
108
  job = out.__xpm__.job
109
109
 
@@ -122,7 +122,7 @@ def test_progress_multiple():
122
122
  listener2 = ProgressListener()
123
123
  xp2.scheduler.addlistener(listener2)
124
124
 
125
- out = ProgressingTask().submit()
125
+ out = ProgressingTask.C().submit()
126
126
  job = out.__xpm__.job # type: CommandLineJob
127
127
  logger.info("Waiting for job to start (2)")
128
128
  while job.state.notstarted():
@@ -217,7 +217,7 @@ def test_progress_nested():
217
217
  listener = ProgressListener()
218
218
  xp.scheduler.addlistener(listener)
219
219
 
220
- out = NestedProgressingTask().submit()
220
+ out = NestedProgressingTask.C().submit()
221
221
  job = out.__xpm__.job
222
222
  path = out.path # type: Path
223
223
 
@@ -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)
@@ -1,14 +1,18 @@
1
+ import sys
2
+ import pytest
1
3
  from experimaestro.connectors.ssh import SshPath
2
4
 
3
5
  # --- Test SSH path and SSH path manipulation
4
6
 
5
7
 
8
+ @pytest.mark.skipif(sys.version_info >= (3, 12), reason="requires python3.10 or higher")
6
9
  def test_absolute():
7
10
  path = SshPath("ssh://host//a/path")
8
11
  assert path.host == "host"
9
12
  assert path.is_absolute()
10
13
 
11
14
 
15
+ @pytest.mark.skipif(sys.version_info >= (3, 12), reason="requires python3.10 or higher")
12
16
  def test_relative():
13
17
  path = SshPath("ssh://host")
14
18
  assert path.host == "host"
@@ -17,6 +21,7 @@ def test_relative():
17
21
  assert not path.is_absolute()
18
22
 
19
23
 
24
+ @pytest.mark.skipif(sys.version_info >= (3, 12), reason="requires python3.10 or higher")
20
25
  def test_relative_withpath():
21
26
  path = SshPath("ssh://host/relative/path")
22
27
  assert path.host == "host"
@@ -24,6 +29,7 @@ def test_relative_withpath():
24
29
  assert not path.is_absolute()
25
30
 
26
31
 
32
+ @pytest.mark.skipif(sys.version_info >= (3, 12), reason="requires python3.10 or higher")
27
33
  def test_relative_absolute():
28
34
  path = SshPath("ssh://host") / "/absolute/path"
29
35
  assert path.host == "host"
@@ -31,6 +37,7 @@ def test_relative_absolute():
31
37
  assert path.is_absolute()
32
38
 
33
39
 
40
+ @pytest.mark.skipif(sys.version_info >= (3, 12), reason="requires python3.10 or higher")
34
41
  def test_relative_compose():
35
42
  path = SshPath("ssh://host/abc") / "relative/path"
36
43
  assert path.host == "host"
@@ -2,8 +2,7 @@ from typing import Dict
2
2
  from pathlib import Path
3
3
  from experimaestro import (
4
4
  tag,
5
- config,
6
- argument,
5
+ LightweightTask,
7
6
  Config,
8
7
  Task,
9
8
  Param,
@@ -12,34 +11,30 @@ from experimaestro.scheduler.workspace import RunMode
12
11
  from experimaestro.xpmutils import DirectoryContext
13
12
 
14
13
 
15
- @argument("x", type=int)
16
- @config()
17
- class Config1:
18
- pass
14
+ class Config1(Config):
15
+ x: Param[int]
19
16
 
20
17
 
21
- @argument("x", type=int)
22
- @argument("c", type=Config1)
23
- @config()
24
- class Config2:
25
- pass
18
+ class Config2(Config):
19
+ x: Param[int]
20
+ c: Param[Config1]
26
21
 
27
22
 
28
23
  def test_tag():
29
- c = Config1(x=5)
24
+ c = Config1.C(x=5)
30
25
  c.tag("x", 5)
31
26
  assert c.tags() == {"x": 5}
32
27
 
33
28
 
34
29
  def test_taggedvalue():
35
- c = Config1(x=tag(5))
30
+ c = Config1.C(x=tag(5))
36
31
  assert c.tags() == {"x": 5}
37
32
 
38
33
 
39
34
  def test_tagcontain():
40
35
  """Test that tags are not propagated to the upper configurations"""
41
- c1 = Config1(x=5)
42
- c2 = Config2(c=c1, x=tag(3)).tag("out", 1)
36
+ c1 = Config1.C(x=5)
37
+ c2 = Config2.C(c=c1, x=tag(3)).tag("out", 1)
43
38
  assert c1.tags() == {}
44
39
  assert c2.tags() == {"x": 3, "out": 1}
45
40
 
@@ -55,20 +50,54 @@ def test_inneroutput():
55
50
  class Evaluate(Task):
56
51
  task: Param[MyTask]
57
52
 
58
- output = Output().tag("hello", "world")
59
- task = MyTask(outputs={}, mainoutput=output)
53
+ output = Output.C().tag("hello", "world")
54
+ task = MyTask.C(outputs={}, mainoutput=output)
60
55
  task.submit(run_mode=RunMode.DRY_RUN)
61
56
  assert output.tags() == {"hello": "world"}
62
57
 
63
- output = Output().tag("hello", "world")
64
- task = MyTask(outputs={"a": output}, mainoutput=Output())
58
+ output = Output.C().tag("hello", "world")
59
+ task = MyTask.C(outputs={"a": output}, mainoutput=Output.C())
65
60
  task.submit(run_mode=RunMode.DRY_RUN)
66
61
  assert output.tags() == {"hello": "world"}
67
62
 
68
- evaluate = Evaluate(task=task).submit(run_mode=RunMode.DRY_RUN)
63
+ evaluate = Evaluate.C(task=task).submit(run_mode=RunMode.DRY_RUN)
69
64
  assert evaluate.__xpm__.tags() == {"hello": "world"}
70
65
 
71
66
 
67
+ def test_tags_init_tasks():
68
+ """Test tags within init tasks"""
69
+
70
+ class MyTask(Task):
71
+ pass
72
+
73
+ class InitTask(LightweightTask):
74
+ pass
75
+
76
+ class MyConfig(Config):
77
+ pass
78
+
79
+ class TaskWithOutput(Task):
80
+ x: Param[MyConfig]
81
+
82
+ def task_outputs(self, dep) -> MyConfig:
83
+ return dep(MyConfig.C())
84
+
85
+ init_task = InitTask.C().tag("hello", "world")
86
+ task = MyTask.C()
87
+ result = task.submit(run_mode=RunMode.DRY_RUN, init_tasks=[init_task])
88
+ assert result.tags() == {"hello": "world"}
89
+
90
+ other_task = TaskWithOutput.C(x=MyConfig.C().tag("hello", "world"))
91
+ assert other_task.tags() == {"hello": "world"}
92
+
93
+ result = other_task.submit(run_mode=RunMode.DRY_RUN)
94
+ assert isinstance(result, MyConfig)
95
+ assert result.tags() == {"hello": "world"}
96
+
97
+ result = MyTask.C().submit(run_mode=RunMode.DRY_RUN, init_tasks=[result])
98
+ assert result.tags() == {"hello": "world"}
99
+
100
+
72
101
  class TaskDirectoryContext(DirectoryContext):
73
102
  def __init__(self, task, path):
74
103
  super().__init__(path)
@@ -86,6 +115,6 @@ def test_objects_tags():
86
115
  x: Param[int]
87
116
 
88
117
  context = DirectoryContext(Path("/__fakepath__"))
89
- a = A(x=tag(1))
118
+ a = A.C(x=tag(1))
90
119
  a.__xpm__.seal(context)
91
120
  assert a.__xpm__.tags() == {"x": 1}