experimaestro 1.11.1__py3-none-any.whl → 2.0.0b4__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 (133) hide show
  1. experimaestro/__init__.py +10 -11
  2. experimaestro/annotations.py +167 -206
  3. experimaestro/cli/__init__.py +140 -16
  4. experimaestro/cli/filter.py +42 -74
  5. experimaestro/cli/jobs.py +157 -106
  6. experimaestro/cli/progress.py +269 -0
  7. experimaestro/cli/refactor.py +249 -0
  8. experimaestro/click.py +0 -1
  9. experimaestro/commandline.py +19 -3
  10. experimaestro/connectors/__init__.py +22 -3
  11. experimaestro/connectors/local.py +12 -0
  12. experimaestro/core/arguments.py +192 -37
  13. experimaestro/core/identifier.py +127 -12
  14. experimaestro/core/objects/__init__.py +6 -0
  15. experimaestro/core/objects/config.py +702 -285
  16. experimaestro/core/objects/config_walk.py +24 -6
  17. experimaestro/core/serialization.py +91 -34
  18. experimaestro/core/serializers.py +1 -8
  19. experimaestro/core/subparameters.py +164 -0
  20. experimaestro/core/types.py +198 -83
  21. experimaestro/exceptions.py +26 -0
  22. experimaestro/experiments/cli.py +107 -25
  23. experimaestro/generators.py +50 -9
  24. experimaestro/huggingface.py +3 -1
  25. experimaestro/launcherfinder/parser.py +29 -0
  26. experimaestro/launcherfinder/registry.py +3 -3
  27. experimaestro/launchers/__init__.py +26 -1
  28. experimaestro/launchers/direct.py +12 -0
  29. experimaestro/launchers/slurm/base.py +154 -2
  30. experimaestro/mkdocs/base.py +6 -8
  31. experimaestro/mkdocs/metaloader.py +0 -1
  32. experimaestro/mypy.py +452 -7
  33. experimaestro/notifications.py +75 -16
  34. experimaestro/progress.py +404 -0
  35. experimaestro/rpyc.py +0 -1
  36. experimaestro/run.py +19 -6
  37. experimaestro/scheduler/__init__.py +18 -1
  38. experimaestro/scheduler/base.py +504 -959
  39. experimaestro/scheduler/dependencies.py +43 -28
  40. experimaestro/scheduler/dynamic_outputs.py +259 -130
  41. experimaestro/scheduler/experiment.py +582 -0
  42. experimaestro/scheduler/interfaces.py +474 -0
  43. experimaestro/scheduler/jobs.py +485 -0
  44. experimaestro/scheduler/services.py +186 -12
  45. experimaestro/scheduler/signal_handler.py +32 -0
  46. experimaestro/scheduler/state.py +1 -1
  47. experimaestro/scheduler/state_db.py +388 -0
  48. experimaestro/scheduler/state_provider.py +2345 -0
  49. experimaestro/scheduler/state_sync.py +834 -0
  50. experimaestro/scheduler/workspace.py +52 -10
  51. experimaestro/scriptbuilder.py +7 -0
  52. experimaestro/server/__init__.py +153 -32
  53. experimaestro/server/data/index.css +0 -125
  54. experimaestro/server/data/index.css.map +1 -1
  55. experimaestro/server/data/index.js +194 -58
  56. experimaestro/server/data/index.js.map +1 -1
  57. experimaestro/settings.py +47 -6
  58. experimaestro/sphinx/__init__.py +3 -3
  59. experimaestro/taskglobals.py +20 -0
  60. experimaestro/tests/conftest.py +80 -0
  61. experimaestro/tests/core/test_generics.py +2 -2
  62. experimaestro/tests/identifier_stability.json +45 -0
  63. experimaestro/tests/launchers/bin/sacct +6 -2
  64. experimaestro/tests/launchers/bin/sbatch +4 -2
  65. experimaestro/tests/launchers/common.py +2 -2
  66. experimaestro/tests/launchers/test_slurm.py +80 -0
  67. experimaestro/tests/restart.py +1 -1
  68. experimaestro/tests/tasks/all.py +7 -0
  69. experimaestro/tests/tasks/test_dynamic.py +231 -0
  70. experimaestro/tests/test_checkers.py +2 -2
  71. experimaestro/tests/test_cli_jobs.py +615 -0
  72. experimaestro/tests/test_dependencies.py +11 -17
  73. experimaestro/tests/test_deprecated.py +630 -0
  74. experimaestro/tests/test_environment.py +200 -0
  75. experimaestro/tests/test_experiment.py +3 -3
  76. experimaestro/tests/test_file_progress.py +425 -0
  77. experimaestro/tests/test_file_progress_integration.py +477 -0
  78. experimaestro/tests/test_forward.py +3 -3
  79. experimaestro/tests/test_generators.py +93 -0
  80. experimaestro/tests/test_identifier.py +520 -169
  81. experimaestro/tests/test_identifier_stability.py +458 -0
  82. experimaestro/tests/test_instance.py +16 -21
  83. experimaestro/tests/test_multitoken.py +442 -0
  84. experimaestro/tests/test_mypy.py +433 -0
  85. experimaestro/tests/test_objects.py +314 -30
  86. experimaestro/tests/test_outputs.py +8 -8
  87. experimaestro/tests/test_param.py +22 -26
  88. experimaestro/tests/test_partial_paths.py +231 -0
  89. experimaestro/tests/test_progress.py +2 -50
  90. experimaestro/tests/test_resumable_task.py +480 -0
  91. experimaestro/tests/test_serializers.py +141 -60
  92. experimaestro/tests/test_state_db.py +434 -0
  93. experimaestro/tests/test_subparameters.py +160 -0
  94. experimaestro/tests/test_tags.py +151 -15
  95. experimaestro/tests/test_tasks.py +137 -160
  96. experimaestro/tests/test_token_locking.py +252 -0
  97. experimaestro/tests/test_tokens.py +25 -19
  98. experimaestro/tests/test_types.py +133 -11
  99. experimaestro/tests/test_validation.py +19 -19
  100. experimaestro/tests/test_workspace_triggers.py +158 -0
  101. experimaestro/tests/token_reschedule.py +5 -3
  102. experimaestro/tests/utils.py +2 -2
  103. experimaestro/tokens.py +154 -57
  104. experimaestro/tools/diff.py +8 -1
  105. experimaestro/tui/__init__.py +8 -0
  106. experimaestro/tui/app.py +2303 -0
  107. experimaestro/tui/app.tcss +353 -0
  108. experimaestro/tui/log_viewer.py +228 -0
  109. experimaestro/typingutils.py +11 -2
  110. experimaestro/utils/__init__.py +23 -0
  111. experimaestro/utils/environment.py +148 -0
  112. experimaestro/utils/git.py +129 -0
  113. experimaestro/utils/resources.py +1 -1
  114. experimaestro/version.py +34 -0
  115. {experimaestro-1.11.1.dist-info → experimaestro-2.0.0b4.dist-info}/METADATA +70 -39
  116. experimaestro-2.0.0b4.dist-info/RECORD +181 -0
  117. {experimaestro-1.11.1.dist-info → experimaestro-2.0.0b4.dist-info}/WHEEL +1 -1
  118. experimaestro-2.0.0b4.dist-info/entry_points.txt +16 -0
  119. experimaestro/compat.py +0 -6
  120. experimaestro/core/objects.pyi +0 -225
  121. experimaestro/server/data/0c35d18bf06992036b69.woff2 +0 -0
  122. experimaestro/server/data/219aa9140e099e6c72ed.woff2 +0 -0
  123. experimaestro/server/data/3a4004a46a653d4b2166.woff +0 -0
  124. experimaestro/server/data/3baa5b8f3469222b822d.woff +0 -0
  125. experimaestro/server/data/4d73cb90e394b34b7670.woff +0 -0
  126. experimaestro/server/data/4ef4218c522f1eb6b5b1.woff2 +0 -0
  127. experimaestro/server/data/5d681e2edae8c60630db.woff +0 -0
  128. experimaestro/server/data/6f420cf17cc0d7676fad.woff2 +0 -0
  129. experimaestro/server/data/c380809fd3677d7d6903.woff2 +0 -0
  130. experimaestro/server/data/f882956fd323fd322f31.woff +0 -0
  131. experimaestro-1.11.1.dist-info/RECORD +0 -158
  132. experimaestro-1.11.1.dist-info/entry_points.txt +0 -17
  133. {experimaestro-1.11.1.dist-info → experimaestro-2.0.0b4.dist-info/licenses}/LICENSE +0 -0
@@ -1,11 +1,9 @@
1
- import logging
2
1
  from pathlib import Path
3
2
 
4
3
  import pytest
5
- from experimaestro import Config, Task, Annotated, copyconfig, default
4
+ from experimaestro import Config, Task, Annotated, copyconfig, field
6
5
  from experimaestro.core.arguments import Param
7
6
  from experimaestro.core.objects import ConfigMixin
8
- from experimaestro.core.types import XPMValue
9
7
  from experimaestro.generators import pathgenerator
10
8
  from experimaestro.scheduler.workspace import RunMode
11
9
  from experimaestro.tests.utils import TemporaryExperiment
@@ -18,17 +16,17 @@ def xp():
18
16
 
19
17
 
20
18
  class A(Config):
21
- x: Param[int] = 3
19
+ x: Param[int] = field(ignore_default=3)
22
20
 
23
21
 
24
22
  def test_object_default():
25
23
  """Test plain default value"""
26
- a = A()
24
+ a = A.C()
27
25
  assert a.x == 3
28
26
 
29
27
 
30
28
  class B(Config):
31
- a: Param[A] = A(x=3)
29
+ a: Param[A] = field(ignore_default=A.C(x=3))
32
30
 
33
31
 
34
32
  class C(B):
@@ -40,27 +38,26 @@ class D(B, A):
40
38
 
41
39
 
42
40
  class DefaultAnnotationConfig(Config):
43
- a: Annotated[A, default(A(x=3))]
41
+ a: Param[A] = field(default=A.C(x=3))
44
42
 
45
43
 
46
44
  def test_object_config_default():
47
45
  """Test default configurations as default values"""
48
- b = B()
46
+ b = B.C()
49
47
  assert b.a.x == 3
50
48
 
51
- c = C()
49
+ c = C.C()
52
50
  assert c.a.x == 3
53
51
 
54
- annotationConfig = DefaultAnnotationConfig()
52
+ annotationConfig = DefaultAnnotationConfig.C()
55
53
  assert annotationConfig.a.x == 3
56
54
 
57
55
 
58
56
  def test_hierarchy():
59
57
  """Test if the object hierarchy is OK"""
60
- OA = A.__getxpmtype__().objecttype
61
- OB = B.__getxpmtype__().objecttype
62
- OC = C.__getxpmtype__().objecttype
63
- OD = D.__getxpmtype__().objecttype
58
+ OA = A.__getxpmtype__().value_type
59
+ OB = B.__getxpmtype__().value_type
60
+ OC = C.__getxpmtype__().value_type
64
61
 
65
62
  assert issubclass(A, Config)
66
63
  assert issubclass(B, Config)
@@ -72,11 +69,6 @@ def test_hierarchy():
72
69
 
73
70
  assert issubclass(C, B)
74
71
 
75
- assert OA.__bases__ == (A, XPMValue)
76
- assert OB.__bases__ == (B, XPMValue)
77
- assert OC.__bases__ == (C, B.XPMValue)
78
- assert OD.__bases__ == (D, B.XPMValue, A.XPMValue)
79
-
80
72
 
81
73
  class CopyConfig(Task):
82
74
  path: Annotated[Path, pathgenerator("hello.txt")]
@@ -84,7 +76,7 @@ class CopyConfig(Task):
84
76
 
85
77
 
86
78
  def test_copyconfig(xp):
87
- b = CopyConfig(x=2)
79
+ b = CopyConfig.C(x=2)
88
80
 
89
81
  b.submit()
90
82
 
@@ -94,16 +86,308 @@ def test_copyconfig(xp):
94
86
  assert "path" not in copy_b.__xpm__.values
95
87
 
96
88
 
97
- def test_direct_config_warns(caplog):
98
- """Test that using a building Config directly raises a warning"""
99
- message = "Config.__new__ is deprecated"
89
+ # --- Composition operator tests (GH #33) ---
90
+
91
+
92
+ class CompositionA(Config):
93
+ x: Param[int]
94
+
95
+
96
+ class CompositionSubA(CompositionA):
97
+ """Subclass of CompositionA"""
98
+
99
+ y: Param[int] = field(ignore_default=0)
100
+
101
+
102
+ class CompositionB(Config):
103
+ a: Param[CompositionA]
104
+
105
+
106
+ class CompositionC(Config):
107
+ """Config with two parameters of same type - should be ambiguous"""
108
+
109
+ a1: Param[CompositionA]
110
+ a2: Param[CompositionA]
111
+
112
+
113
+ class CompositionD(Config):
114
+ """Config with no matching parameter"""
115
+
116
+ x: Param[int]
117
+
118
+
119
+ class CompositionE(Config):
120
+ """Config with two parameters, one subclass of the other"""
121
+
122
+ base: Param[CompositionA]
123
+ sub: Param[CompositionSubA]
124
+
125
+
126
+ def test_composition_operator():
127
+ """Test that B() @ A(x=1) is equivalent to B(a=A(x=1))"""
128
+ a = CompositionA.C(x=42)
129
+ b = CompositionB.C() @ a
130
+
131
+ assert b.a is a
132
+ assert b.a.x == 42
133
+
134
+
135
+ def test_composition_operator_chained():
136
+ """Test chaining composition operators
137
+
138
+ Chaining A @ B @ C adds both B and C to A (same outer config).
139
+ For nested structures, use parentheses: A @ (B @ C)
140
+ """
141
+
142
+ class MultiParam(Config):
143
+ a: Param[CompositionA]
144
+ b: Param[CompositionB]
145
+
146
+ # Chaining adds multiple configs to same outer config
147
+ result = MultiParam.C() @ CompositionA.C(x=10) @ CompositionB.C()
148
+
149
+ assert result.a.x == 10
150
+ assert result.b is not None
151
+
152
+
153
+ def test_composition_operator_nested():
154
+ """Test nested composition with parentheses"""
155
+
156
+ class Outer(Config):
157
+ b: Param[CompositionB]
158
+
159
+ # For nested structures, use parentheses
160
+ result = Outer.C() @ (CompositionB.C() @ CompositionA.C(x=10))
161
+
162
+ assert result.b.a.x == 10
163
+
164
+
165
+ def test_composition_operator_ambiguous():
166
+ """Test that ambiguous composition raises ValueError"""
167
+ a = CompositionA.C(x=1)
168
+
169
+ with pytest.raises(ValueError, match="Ambiguous"):
170
+ CompositionC.C() @ a
171
+
172
+
173
+ def test_composition_operator_no_match():
174
+ """Test that composition with no matching param raises ValueError"""
175
+ a = CompositionA.C(x=1)
176
+
177
+ with pytest.raises(ValueError, match="No parameter"):
178
+ CompositionD.C() @ a
179
+
180
+
181
+ def test_composition_operator_subclass():
182
+ """Test composition works with subclasses"""
183
+ sub_a = CompositionSubA.C(x=5, y=10)
184
+ b = CompositionB.C() @ sub_a
185
+
186
+ assert b.a is sub_a
187
+ assert b.a.x == 5
188
+
189
+
190
+ def test_composition_operator_subclass_hierarchy():
191
+ """Test composition when two params have subclass relationship
192
+
193
+ When CompositionSubA is passed, both 'base' (CompositionA) and 'sub'
194
+ (CompositionSubA) match. This should be ambiguous since both accept it.
195
+ """
196
+ sub_a = CompositionSubA.C(x=1, y=2)
197
+
198
+ # SubA matches both base (CompositionA) and sub (CompositionSubA)
199
+ with pytest.raises(ValueError, match="Ambiguous"):
200
+ CompositionE.C() @ sub_a
201
+
202
+
203
+ def test_composition_operator_exact_match():
204
+ """Test composition when base class instance matches only base param"""
205
+ # CompositionA matches only 'base', not 'sub' (which requires SubA)
206
+ a = CompositionA.C(x=1)
207
+ e = CompositionE.C() @ a
208
+
209
+ assert e.base is a
210
+ assert e.base.x == 1
211
+
212
+
213
+ # --- Value class decorator tests (GH #99) ---
214
+
215
+ # Test 1: Basic value class registration
216
+
217
+
218
+ class ValueBasicModel(Config):
219
+ x: Param[int] = field(ignore_default=1)
220
+
221
+
222
+ @ValueBasicModel.value_class()
223
+ class ValueBasicModelImpl(ValueBasicModel):
224
+ def compute(self):
225
+ return self.x * 2
226
+
227
+
228
+ # Test 2: Subclass without explicit value class
229
+
230
+
231
+ class ValueInheritBase(Config):
232
+ x: Param[int] = field(ignore_default=1)
233
+
234
+
235
+ @ValueInheritBase.value_class()
236
+ class ValueInheritBaseImpl(ValueInheritBase):
237
+ pass
238
+
239
+
240
+ class ValueInheritSubNoExplicit(ValueInheritBase):
241
+ """Subclass without explicit value class"""
242
+
243
+ y: Param[int] = field(ignore_default=2)
244
+
245
+
246
+ # Test 3: Value class with proper inheritance
247
+
248
+
249
+ class ValueInheritParent(Config):
250
+ x: Param[int] = field(ignore_default=1)
251
+
252
+
253
+ @ValueInheritParent.value_class()
254
+ class ValueInheritParentImpl(ValueInheritParent):
255
+ def compute(self):
256
+ return self.x * 2
257
+
258
+
259
+ class ValueInheritChild(ValueInheritParent):
260
+ y: Param[int] = field(ignore_default=2)
261
+
262
+
263
+ @ValueInheritChild.value_class()
264
+ class ValueInheritChildImpl(ValueInheritChild, ValueInheritParentImpl):
265
+ def compute_both(self):
266
+ return self.x + self.y
267
+
268
+
269
+ # Test 4: Skip intermediate class (A -> B -> C, only A and C have value classes)
270
+
271
+
272
+ class ValueSkipBase(Config):
273
+ x: Param[int] = field(ignore_default=1)
274
+
275
+
276
+ @ValueSkipBase.value_class()
277
+ class ValueSkipBaseImpl(ValueSkipBase):
278
+ def compute(self):
279
+ return self.x * 2
280
+
281
+
282
+ class ValueSkipIntermediate(ValueSkipBase):
283
+ """Intermediate class without explicit value class"""
284
+
285
+ y: Param[int] = field(ignore_default=2)
286
+
287
+
288
+ class ValueSkipDeep(ValueSkipIntermediate):
289
+ """Deep subclass with value class"""
290
+
291
+ z: Param[int] = field(ignore_default=3)
292
+
293
+
294
+ @ValueSkipDeep.value_class()
295
+ class ValueSkipDeepImpl(ValueSkipDeep, ValueSkipBaseImpl):
296
+ def compute_all(self):
297
+ return self.x + self.y + self.z
298
+
299
+
300
+ # --- Value class tests ---
301
+
302
+
303
+ def test_value_decorator_basic():
304
+ """Test basic value class registration"""
305
+ # XPMValue should return the registered value class
306
+ assert ValueBasicModel.XPMValue is ValueBasicModelImpl
307
+
308
+ # Creating an instance should use the value class
309
+ config = ValueBasicModel.C(x=5)
310
+ instance = config.instance()
311
+
312
+ assert isinstance(instance, ValueBasicModelImpl)
313
+ assert instance.x == 5
314
+ assert instance.compute() == 10
315
+
316
+
317
+ def test_value_decorator_inheritance_no_explicit():
318
+ """Test that subclass without value class uses config class as value"""
319
+ # SubModel has no explicit value class, XPMValue returns the config class
320
+ assert ValueInheritSubNoExplicit.XPMValue is ValueInheritSubNoExplicit
321
+
322
+ config = ValueInheritSubNoExplicit.C(x=3, y=4)
323
+ instance = config.instance()
324
+
325
+ # Instance is created from the config class (no explicit value type)
326
+ assert isinstance(instance, ValueInheritSubNoExplicit)
327
+ assert instance.x == 3
328
+ assert instance.y == 4
329
+
330
+
331
+ def test_value_decorator_inheritance_with_explicit():
332
+ """Test value class with proper inheritance from parent value class"""
333
+ assert ValueInheritChild.XPMValue is ValueInheritChildImpl
334
+
335
+ config = ValueInheritChild.C(x=3, y=4)
336
+ instance = config.instance()
337
+
338
+ assert isinstance(instance, ValueInheritChildImpl)
339
+ assert isinstance(instance, ValueInheritParentImpl)
340
+ assert instance.x == 3
341
+ assert instance.y == 4
342
+ assert instance.compute() == 6 # From parent value class
343
+ assert instance.compute_both() == 7 # From this value class
344
+
345
+
346
+ def test_value_decorator_must_be_subclass():
347
+ """Test that value class must be subclass of config"""
348
+
349
+ class LocalModel(Config):
350
+ x: Param[int]
351
+
352
+ class OtherConfig(Config):
353
+ z: Param[int]
354
+
355
+ with pytest.raises(TypeError, match="must be a subclass of"):
356
+
357
+ @LocalModel.value_class()
358
+ class InvalidValue(OtherConfig): # Not a subclass of LocalModel
359
+ pass
360
+
361
+
362
+ def test_value_decorator_must_inherit_parent_value():
363
+ """Test that value class must inherit from parent value class"""
364
+
365
+ class LocalBase(Config):
366
+ x: Param[int] = field(ignore_default=1)
367
+
368
+ @LocalBase.value_class()
369
+ class LocalBaseImpl(LocalBase):
370
+ pass
371
+
372
+ class LocalChild(LocalBase):
373
+ y: Param[int] = field(ignore_default=2)
374
+
375
+ with pytest.raises(TypeError, match="must be a subclass of.*parent value class"):
376
+
377
+ @LocalChild.value_class()
378
+ class InvalidChildValue(LocalChild): # Missing LocalBaseImpl inheritance
379
+ pass
380
+
100
381
 
101
- with caplog.at_level(logging.WARNING):
102
- A(x=3)
103
- assert message in caplog.text
382
+ def test_value_decorator_skip_intermediate():
383
+ """Test value class when intermediate class has no value class"""
384
+ # ValueSkipBase has impl, ValueSkipIntermediate has none, ValueSkipDeep has impl
385
+ assert ValueSkipDeep.XPMValue is ValueSkipDeepImpl
104
386
 
105
- caplog.clear()
387
+ config = ValueSkipDeep.C(x=1, y=2, z=3)
388
+ instance = config.instance()
106
389
 
107
- with caplog.at_level(logging.WARNING):
108
- A.C(x=3)
109
- assert message not in caplog.text
390
+ assert isinstance(instance, ValueSkipDeepImpl)
391
+ assert isinstance(instance, ValueSkipBaseImpl)
392
+ assert instance.compute() == 2 # From ValueSkipBaseImpl
393
+ assert instance.compute_all() == 6 # From ValueSkipDeepImpl
@@ -1,11 +1,11 @@
1
1
  """Test for task outputs"""
2
2
 
3
- from experimaestro import Config, Task, Param
3
+ from experimaestro import field, Config, Task, Param
4
4
  from experimaestro.scheduler.workspace import RunMode
5
5
 
6
6
 
7
7
  class B(Config):
8
- x: Param[int] = 1
8
+ x: Param[int] = field(ignore_default=1)
9
9
 
10
10
 
11
11
  class A(Config):
@@ -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)
@@ -5,7 +5,6 @@ Test annotation handling for configurations and tasks
5
5
 
6
6
  # Annotation specific tests
7
7
 
8
- import sys
9
8
  from pathlib import Path
10
9
  from typing import Dict, Optional, List
11
10
  from experimaestro.core.context import SerializationContext
@@ -17,7 +16,6 @@ from experimaestro import (
17
16
  Constant,
18
17
  Param,
19
18
  Task,
20
- default,
21
19
  Meta,
22
20
  Config,
23
21
  pathgenerator,
@@ -80,8 +78,8 @@ def test_type_hinting():
80
78
  __xpmid__ = "annotations.class_variable.config"
81
79
 
82
80
  x: Param[int]
83
- y: Param[float] = 2.3
84
- y2: Annotated[float, default(2.3)]
81
+ y: Param[float] = field(ignore_default=2.3)
82
+ y2: Param[float] = field(ignore_default=2.3)
85
83
  z: Param[Optional[float]]
86
84
  t: Param[List[float]]
87
85
  w: Param[int]
@@ -148,7 +146,7 @@ def test_generatedpath():
148
146
  b: Param[B]
149
147
 
150
148
  basepath = Path("/tmp/testconflict")
151
- c = C(b=B(a=A())).instance(DirectoryContext(basepath))
149
+ c = C.C(b=B.C(a=A.C())).instance(DirectoryContext(basepath))
152
150
  assert c.b.a.path.relative_to(basepath) == Path("out/b/a/test.txt")
153
151
 
154
152
 
@@ -158,13 +156,13 @@ def test_config_class():
158
156
  class A(Config):
159
157
  x: Param[int]
160
158
 
161
- a = A(x=1)
159
+ a = A.C(x=1)
162
160
  assert a.x == 1
163
161
 
164
162
  class B(A):
165
163
  y: Param[int]
166
164
 
167
- b = B(x=1, y=2)
165
+ b = B.C(x=1, y=2)
168
166
  assert b.x == 1
169
167
  assert b.y == 2
170
168
 
@@ -174,15 +172,15 @@ def test_config_class():
174
172
  class C(Config):
175
173
  d: Param[D]
176
174
 
177
- c = C(d=D(x=1))
175
+ c = C.C(d=D.C(x=1))
178
176
  assert c.d.x == 1
179
177
 
180
178
 
181
179
  def test_constant():
182
180
  class A(Config):
183
- x: Constant[int] = 2
181
+ x: Constant[int] = field(ignore_default=2)
184
182
 
185
- a = A()
183
+ a = A.C()
186
184
  assert a.x == 2, "Constant value not set"
187
185
 
188
186
  # We should not be able to change the value
@@ -202,7 +200,7 @@ class EnumConfig(Config):
202
200
  def test_param_enum():
203
201
  """Test for enum values"""
204
202
 
205
- a = EnumConfig(x=EnumParam.OTHER)
203
+ a = EnumConfig.C(x=EnumParam.OTHER)
206
204
  _a = serializeCycle(a)
207
205
 
208
206
  assert isinstance(_a, EnumConfig)
@@ -214,9 +212,9 @@ def test_inheritance():
214
212
  x: Param[int]
215
213
 
216
214
  class B(A):
217
- y: Param[int] = 3
215
+ y: Param[int] = field(ignore_default=3)
218
216
 
219
- b = B()
217
+ b = B.C()
220
218
  b.x = 2
221
219
  assert b.__xpm__.values["y"] == 3
222
220
  assert b.__xpm__.values["x"] == 2
@@ -227,7 +225,7 @@ def test_redefined_param():
227
225
  x: Param[int]
228
226
 
229
227
  class B(Config):
230
- x: Param[int] = 3
228
+ x: Param[int] = field(ignore_default=3)
231
229
 
232
230
  atx = A.C.__getxpmtype__().getArgument("x")
233
231
  btx = B.C.__getxpmtype__().getArgument("x")
@@ -247,7 +245,7 @@ def test_param_dict():
247
245
  assert isinstance(xarg.keytype, StrType)
248
246
  assert isinstance(xarg.valuetype, IntType)
249
247
 
250
- A(x={"OK": 1})
248
+ A.C(x={"OK": 1})
251
249
 
252
250
  with pytest.raises(TypeError):
253
251
  A(x={"wrong": "string"})
@@ -263,7 +261,7 @@ class ConfigWithDefault(Config):
263
261
 
264
262
 
265
263
  def test_param_default():
266
- assert ConfigWithDefault().x == 1
264
+ assert ConfigWithDefault.C().x == 1
267
265
 
268
266
 
269
267
  class ConfigWithDefaultFactory(Config):
@@ -271,7 +269,7 @@ class ConfigWithDefaultFactory(Config):
271
269
 
272
270
 
273
271
  def test_param_default_factory():
274
- value = ConfigWithDefaultFactory()
272
+ value = ConfigWithDefaultFactory.C()
275
273
  context = DirectoryContext(Path("/__fakepath__"))
276
274
  value.__xpm__.seal(context)
277
275
  assert value.x == 1
@@ -284,7 +282,7 @@ def test_default_mismatch():
284
282
  """Test mismatch between default and type"""
285
283
 
286
284
  class A(Config):
287
- x: Param[int] = 0.2
285
+ x: Param[int] = field(ignore_default=0.2)
288
286
 
289
287
  with pytest.raises(TypeError):
290
288
  A.__getxpmtype__().getArgument("x")
@@ -297,17 +295,17 @@ def test_param_default_set():
297
295
  """Test that the default setting is well set"""
298
296
 
299
297
  class A0(Config):
300
- x: Param[int] = 2
298
+ x: Param[int] = field(ignore_default=2)
301
299
 
302
- assert A0().instance().x == 2
303
- assert A0(x=3).instance().x == 3
300
+ assert A0.C().instance().x == 2
301
+ assert A0.C(x=3).instance().x == 3
304
302
 
305
303
  class A(Config):
306
304
  x: Param[int] = field(default_factory=lambda: 2)
307
305
 
308
- assert A().instance().x == 2
306
+ assert A.C().instance().x == 2
309
307
 
310
- assert A(x=3).instance().x == 3
308
+ assert A.C(x=3).instance().x == 3
311
309
 
312
310
 
313
311
  # --- Handling help annotations
@@ -336,6 +334,4 @@ def test_help():
336
334
  assert xpmtype.description.strip() == "Long description of A."
337
335
  assert xpmtype.arguments["y"].help == "Parameter y"
338
336
 
339
- # Only python >= 3.9
340
- if sys.version_info.major == 3 and sys.version_info.minor > 8:
341
- assert xpmtype.arguments["x"].help == "Parameter x"
337
+ assert xpmtype.arguments["x"].help == "Parameter x"