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
@@ -0,0 +1,458 @@
1
+ # Tests for identifier stability across versions
2
+
3
+ import json
4
+ from enum import Enum
5
+ from pathlib import Path
6
+ from typing import Dict, List
7
+
8
+ from experimaestro import (
9
+ Param,
10
+ Config,
11
+ field,
12
+ InstanceConfig,
13
+ Task,
14
+ LightweightTask,
15
+ Option,
16
+ )
17
+ from experimaestro.scheduler.workspace import RunMode
18
+
19
+
20
+ # --- Basic types ---
21
+
22
+
23
+ class ConfigInt(Config):
24
+ """Config with int parameter"""
25
+
26
+ __xpmid__ = "test.stability.ConfigInt"
27
+ x: Param[int]
28
+
29
+
30
+ class ConfigStr(Config):
31
+ """Config with str parameter"""
32
+
33
+ __xpmid__ = "test.stability.ConfigStr"
34
+ s: Param[str]
35
+
36
+
37
+ class ConfigBool(Config):
38
+ """Config with bool parameter"""
39
+
40
+ __xpmid__ = "test.stability.ConfigBool"
41
+ b: Param[bool]
42
+
43
+
44
+ class ConfigFloat(Config):
45
+ """Config with float parameter"""
46
+
47
+ __xpmid__ = "test.stability.ConfigFloat"
48
+ f: Param[float]
49
+
50
+
51
+ # --- Enum ---
52
+
53
+
54
+ class MyEnum(Enum):
55
+ """Test enum"""
56
+
57
+ VALUE_A = 1
58
+ VALUE_B = 2
59
+ VALUE_C = 3
60
+
61
+
62
+ # Override module name to ensure stable identifiers across different import methods
63
+ MyEnum.__module__ = "test_identifier_stability"
64
+
65
+
66
+ class ConfigEnum(Config):
67
+ """Config with enum parameter"""
68
+
69
+ __xpmid__ = "test.stability.ConfigEnum"
70
+ e: Param[MyEnum]
71
+
72
+
73
+ # --- Collections ---
74
+
75
+
76
+ class ConfigList(Config):
77
+ """Config with list parameter"""
78
+
79
+ __xpmid__ = "test.stability.ConfigList"
80
+ items: Param[List[int]]
81
+
82
+
83
+ class ConfigDict(Config):
84
+ """Config with dict parameter"""
85
+
86
+ __xpmid__ = "test.stability.ConfigDict"
87
+ mapping: Param[Dict[str, int]]
88
+
89
+
90
+ class ConfigNestedList(Config):
91
+ """Config with nested list of configs"""
92
+
93
+ __xpmid__ = "test.stability.ConfigNestedList"
94
+ configs: Param[List[ConfigInt]]
95
+
96
+
97
+ class ConfigNestedDict(Config):
98
+ """Config with nested dict of configs"""
99
+
100
+ __xpmid__ = "test.stability.ConfigNestedDict"
101
+ configs: Param[Dict[str, ConfigInt]]
102
+
103
+
104
+ # --- Nested configs ---
105
+
106
+
107
+ class ConfigNested(Config):
108
+ """Config with nested config parameter"""
109
+
110
+ __xpmid__ = "test.stability.ConfigNested"
111
+ inner: Param[ConfigInt]
112
+
113
+
114
+ class ConfigMultiNested(Config):
115
+ """Config with multiple nested configs"""
116
+
117
+ __xpmid__ = "test.stability.ConfigMultiNested"
118
+ a: Param[ConfigInt]
119
+ b: Param[ConfigStr]
120
+ c: Param[ConfigNested]
121
+
122
+
123
+ # --- Options and defaults ---
124
+
125
+
126
+ class ConfigWithOption(Config):
127
+ """Config with option parameter"""
128
+
129
+ __xpmid__ = "test.stability.ConfigWithOption"
130
+ required: Param[int]
131
+ optional: Option[int] = field(ignore_default=42)
132
+
133
+
134
+ class ConfigWithDefault(Config):
135
+ """Config with default parameter"""
136
+
137
+ __xpmid__ = "test.stability.ConfigWithDefault"
138
+ x: Param[int] = field(ignore_default=10)
139
+ y: Param[int]
140
+
141
+
142
+ # --- Tasks ---
143
+
144
+
145
+ class SimpleTask(Task):
146
+ """Simple task"""
147
+
148
+ __xpmid__ = "test.stability.SimpleTask"
149
+ x: Param[int]
150
+
151
+ def execute(self):
152
+ pass
153
+
154
+
155
+ class TaskWithConfig(Task):
156
+ """Task with config parameter"""
157
+
158
+ __xpmid__ = "test.stability.TaskWithConfig"
159
+ config: Param[ConfigInt]
160
+
161
+ def execute(self):
162
+ pass
163
+
164
+
165
+ class TaskWithOutput(Task):
166
+ """Task that outputs a config"""
167
+
168
+ __xpmid__ = "test.stability.TaskWithOutput"
169
+ value: Param[int]
170
+
171
+ def task_outputs(self, dep):
172
+ return ConfigInt.C(x=self.value)
173
+
174
+ def execute(self):
175
+ pass
176
+
177
+
178
+ class MyLightweightTask(LightweightTask):
179
+ """Lightweight task for init_tasks"""
180
+
181
+ __xpmid__ = "test.stability.MyLightweightTask"
182
+ param: Param[int]
183
+
184
+ def execute(self):
185
+ pass
186
+
187
+
188
+ # --- Cycles ---
189
+
190
+
191
+ class CycleA(Config):
192
+ """Config that can reference CycleB"""
193
+
194
+ __xpmid__ = "test.stability.CycleA"
195
+ b: Param["CycleB"]
196
+
197
+
198
+ class CycleB(Config):
199
+ """Config that can reference CycleA"""
200
+
201
+ __xpmid__ = "test.stability.CycleB"
202
+ a: Param["CycleA"]
203
+
204
+
205
+ # --- InstanceConfig ---
206
+
207
+
208
+ class SubModel(InstanceConfig):
209
+ """InstanceConfig for testing instance identity"""
210
+
211
+ __xpmid__ = "test.stability.SubModel"
212
+ value: Param[int] = field(ignore_default=100)
213
+
214
+
215
+ class ModelContainer(Config):
216
+ """Config that contains SubModel instances"""
217
+
218
+ __xpmid__ = "test.stability.ModelContainer"
219
+ m1: Param[SubModel]
220
+ m2: Param[SubModel]
221
+
222
+
223
+ def get_configurations():
224
+ """Return all test configurations with their identifiers
225
+
226
+ Returns a dict mapping test case names to configuration objects
227
+ """
228
+ configs = {}
229
+
230
+ # Basic types
231
+ configs["int_positive"] = ConfigInt.C(x=42)
232
+ configs["int_negative"] = ConfigInt.C(x=-10)
233
+ configs["int_zero"] = ConfigInt.C(x=0)
234
+ configs["str_simple"] = ConfigStr.C(s="hello")
235
+ configs["str_empty"] = ConfigStr.C(s="")
236
+ configs["str_unicode"] = ConfigStr.C(s="héllo wörld 🌍")
237
+ configs["bool_true"] = ConfigBool.C(b=True)
238
+ configs["bool_false"] = ConfigBool.C(b=False)
239
+ configs["float_simple"] = ConfigFloat.C(f=3.14)
240
+ configs["float_negative"] = ConfigFloat.C(f=-2.5)
241
+ configs["float_zero"] = ConfigFloat.C(f=0.0)
242
+
243
+ # Enum
244
+ configs["enum_value_a"] = ConfigEnum.C(e=MyEnum.VALUE_A)
245
+ configs["enum_value_b"] = ConfigEnum.C(e=MyEnum.VALUE_B)
246
+ configs["enum_value_c"] = ConfigEnum.C(e=MyEnum.VALUE_C)
247
+
248
+ # Lists
249
+ configs["list_empty"] = ConfigList.C(items=[])
250
+ configs["list_single"] = ConfigList.C(items=[1])
251
+ configs["list_multiple"] = ConfigList.C(items=[1, 2, 3, 4, 5])
252
+ configs["list_nested_empty"] = ConfigNestedList.C(configs=[])
253
+ configs["list_nested_single"] = ConfigNestedList.C(configs=[ConfigInt.C(x=1)])
254
+ configs["list_nested_multiple"] = ConfigNestedList.C(
255
+ configs=[ConfigInt.C(x=1), ConfigInt.C(x=2), ConfigInt.C(x=3)]
256
+ )
257
+
258
+ # Dicts
259
+ configs["dict_empty"] = ConfigDict.C(mapping={})
260
+ configs["dict_single"] = ConfigDict.C(mapping={"a": 1})
261
+ configs["dict_multiple"] = ConfigDict.C(mapping={"a": 1, "b": 2, "c": 3})
262
+ configs["dict_nested_empty"] = ConfigNestedDict.C(configs={})
263
+ configs["dict_nested_single"] = ConfigNestedDict.C(configs={"x": ConfigInt.C(x=1)})
264
+ configs["dict_nested_multiple"] = ConfigNestedDict.C(
265
+ configs={"a": ConfigInt.C(x=1), "b": ConfigInt.C(x=2), "c": ConfigInt.C(x=3)}
266
+ )
267
+
268
+ # Nested configs
269
+ configs["nested_simple"] = ConfigNested.C(inner=ConfigInt.C(x=100))
270
+ configs["nested_multi"] = ConfigMultiNested.C(
271
+ a=ConfigInt.C(x=1),
272
+ b=ConfigStr.C(s="test"),
273
+ c=ConfigNested.C(inner=ConfigInt.C(x=2)),
274
+ )
275
+
276
+ # Options and defaults
277
+ configs["option_with_default"] = ConfigWithOption.C(required=5)
278
+ configs["option_override"] = ConfigWithOption.C(required=5, optional=100)
279
+ configs["default_with_default"] = ConfigWithDefault.C(y=20)
280
+ configs["default_override"] = ConfigWithDefault.C(x=99, y=20)
281
+
282
+ # Tasks (without submission)
283
+ configs["task_simple"] = SimpleTask.C(x=10)
284
+ configs["task_with_config"] = TaskWithConfig.C(config=ConfigInt.C(x=5))
285
+
286
+ # Tasks with submission (creates task outputs)
287
+ configs["task_submitted"] = SimpleTask.C(x=15).submit(run_mode=RunMode.DRY_RUN)
288
+ configs["task_with_output"] = TaskWithOutput.C(value=25).submit(
289
+ run_mode=RunMode.DRY_RUN
290
+ )
291
+
292
+ # Task using output from another task
293
+ task_output = TaskWithOutput.C(value=30).submit(run_mode=RunMode.DRY_RUN)
294
+ configs["task_using_output"] = TaskWithConfig.C(config=task_output)
295
+
296
+ # Tasks with init_tasks
297
+ configs["task_with_init"] = SimpleTask.C(x=20).submit(
298
+ run_mode=RunMode.DRY_RUN, init_tasks=[MyLightweightTask.C(param=1)]
299
+ )
300
+ configs["task_with_multiple_init"] = SimpleTask.C(x=25).submit(
301
+ run_mode=RunMode.DRY_RUN,
302
+ init_tasks=[MyLightweightTask.C(param=1), MyLightweightTask.C(param=2)],
303
+ )
304
+
305
+ # Cycles
306
+ cycle_a = CycleA.C()
307
+ cycle_b = CycleB.C(a=cycle_a)
308
+ cycle_a.b = cycle_b
309
+ configs["cycle_simple"] = cycle_a
310
+
311
+ # InstanceConfig - test instance identity
312
+ # Single instance used twice (shared) - backwards compatible with regular Config
313
+ sm_single = SubModel.C(value=100)
314
+ configs["instance_shared"] = ModelContainer.C(m1=sm_single, m2=sm_single)
315
+
316
+ # Two separate instances with same parameters - different identifiers
317
+ sm1 = SubModel.C(value=100)
318
+ sm2 = SubModel.C(value=100)
319
+ configs["instance_separate"] = ModelContainer.C(m1=sm1, m2=sm2)
320
+
321
+ # InstanceConfig with different parameter values
322
+ sm3 = SubModel.C(value=200)
323
+ sm4 = SubModel.C(value=300)
324
+ configs["instance_different_values"] = ModelContainer.C(m1=sm3, m2=sm4)
325
+
326
+ return configs
327
+
328
+
329
+ def save_reference(output_file: Path, overwrite: bool = False):
330
+ """Generate and save reference identifiers
331
+
332
+ Args:
333
+ output_file: Path to the JSON file to save
334
+ overwrite: If True, overwrite existing file even if there are changes
335
+ """
336
+ configs = get_configurations()
337
+ reference = {}
338
+
339
+ for name, config in configs.items():
340
+ identifier = config.__xpm__.identifier.all.hex()
341
+ reference[name] = identifier
342
+
343
+ # Check if file exists and compare
344
+ if output_file.exists() and not overwrite:
345
+ existing = load_reference(output_file)
346
+
347
+ changes = []
348
+ for name in sorted(set(existing.keys()) | set(reference.keys())):
349
+ old_id = existing.get(name)
350
+ new_id = reference.get(name)
351
+
352
+ if old_id is None:
353
+ changes.append(f" + {name}: NEW")
354
+ elif new_id is None:
355
+ changes.append(f" - {name}: REMOVED")
356
+ elif old_id != new_id:
357
+ changes.append(f" ! {name}: CHANGED")
358
+ changes.append(f" Old: {old_id}")
359
+ changes.append(f" New: {new_id}")
360
+
361
+ if changes:
362
+ print(
363
+ f"⚠️ WARNING: Reference file has {len([c for c in changes if c.startswith(' ')])} change(s):"
364
+ )
365
+ print("\n".join(changes))
366
+ print("\nTo overwrite, run with --overwrite flag")
367
+ return None
368
+
369
+ # Save to JSON file
370
+ with output_file.open("w") as f:
371
+ json.dump(reference, f, indent=2, sort_keys=True)
372
+
373
+ print(f"✓ Saved {len(reference)} reference identifiers to {output_file}")
374
+ return reference
375
+
376
+
377
+ def load_reference(reference_file: Path) -> Dict[str, str]:
378
+ """Load reference identifiers from JSON file
379
+
380
+ Args:
381
+ reference_file: Path to the JSON file
382
+
383
+ Returns:
384
+ Dictionary mapping test case names to identifier hex strings
385
+ """
386
+ with reference_file.open("r") as f:
387
+ return json.load(f)
388
+
389
+
390
+ def test_identifier_stability():
391
+ """Test that identifiers are stable across experimaestro versions"""
392
+
393
+ # Get the reference file path (same directory as this test file)
394
+ reference_file = Path(__file__).parent / "identifier_stability.json"
395
+
396
+ if not reference_file.exists():
397
+ raise FileNotFoundError(
398
+ f"Reference file {reference_file} not found. "
399
+ f"Run 'python {__file__}' to generate it."
400
+ )
401
+
402
+ # Load reference identifiers
403
+ reference = load_reference(reference_file)
404
+
405
+ # Get current configurations
406
+ configs = get_configurations()
407
+
408
+ # Check each configuration
409
+ mismatches = []
410
+ for name, config in configs.items():
411
+ current_id = config.__xpm__.identifier.all.hex()
412
+ expected_id = reference.get(name)
413
+
414
+ if expected_id is None:
415
+ mismatches.append(
416
+ f" - {name}: NEW (not in reference file)\n Current: {current_id}"
417
+ )
418
+ elif current_id != expected_id:
419
+ mismatches.append(
420
+ f" - {name}: MISMATCH\n"
421
+ f" Expected: {expected_id}\n"
422
+ f" Current: {current_id}"
423
+ )
424
+
425
+ # Check for removed configurations
426
+ for name in reference:
427
+ if name not in configs:
428
+ mismatches.append(f" - {name}: REMOVED (no longer in test suite)")
429
+
430
+ # Report results
431
+ if mismatches:
432
+ error_msg = (
433
+ f"Identifier stability test failed! {len(mismatches)} mismatch(es):\n"
434
+ + "\n".join(mismatches)
435
+ )
436
+ raise AssertionError(error_msg)
437
+
438
+ print(f"✓ All {len(configs)} identifiers are stable")
439
+
440
+
441
+ if __name__ == "__main__":
442
+ import sys
443
+
444
+ # Parse command-line arguments
445
+ overwrite = "--overwrite" in sys.argv
446
+
447
+ # Generate the reference file
448
+ reference_file = Path(__file__).parent / "identifier_stability.json"
449
+ reference = save_reference(reference_file, overwrite=overwrite)
450
+
451
+ if reference is None:
452
+ # Changes detected but not overwriting
453
+ sys.exit(1)
454
+
455
+ # Print summary
456
+ print(f"\nGenerated {len(reference)} reference identifiers:")
457
+ for name in sorted(reference.keys()):
458
+ print(f" - {name}")
@@ -1,11 +1,11 @@
1
1
  from typing import Optional
2
- from experimaestro import Param, Config
2
+ from experimaestro import field, Param, Config
3
3
  from experimaestro.core.objects import ConfigMixin
4
4
  from experimaestro.core.serializers import SerializationLWTask
5
5
 
6
6
 
7
7
  class A(Config):
8
- x: Param[int] = 1
8
+ x: Param[int] = field(ignore_default=1)
9
9
 
10
10
 
11
11
  class A1(A):
@@ -17,16 +17,16 @@ class B(Config):
17
17
 
18
18
 
19
19
  def test_simple_instance():
20
- a = A1(x=1)
21
- b = B(a=a)
20
+ a = A1.C(x=1)
21
+ b = B.C(a=a)
22
22
  b = b.instance()
23
23
 
24
24
  assert not isinstance(b, ConfigMixin)
25
- assert isinstance(b, B.__xpmtype__.objecttype)
25
+ assert isinstance(b, B.__xpmtype__.value_type)
26
26
 
27
27
  assert not isinstance(b.a, ConfigMixin)
28
- assert isinstance(b.a, A1.__xpmtype__.objecttype)
29
- assert isinstance(b.a, A.__xpmtype__.basetype)
28
+ assert isinstance(b.a, A1.__xpmtype__.value_type)
29
+ assert isinstance(b.a, A.__xpmtype__.value_type)
30
30
 
31
31
 
32
32
  # --- Test pre tasks
@@ -46,25 +46,20 @@ class LoadModel(SerializationLWTask):
46
46
  self.value.initialized = True
47
47
 
48
48
 
49
- def test_instance_serialized():
50
- model = Model()
51
- model.add_pretasks(LoadModel(value=model))
52
- trainer = Evaluator(model=model)
53
- instance = trainer.instance()
54
-
55
- assert isinstance(
56
- instance.model, Model
57
- ), f"The model is not a Model but a {type(instance.model).__qualname__}"
58
- assert instance.model.initialized, "The model was not initialized"
59
-
60
-
61
49
  class ConfigWithOptional(Config):
62
- x: Param[int] = 1
50
+ x: Param[int] = field(ignore_default=1)
63
51
  y: Param[Optional[int]]
64
52
 
65
53
 
66
54
  def test_instance_optional():
67
55
  """Test that optional parameters are set to None when calling instance"""
68
- c = ConfigWithOptional().instance()
56
+ c = ConfigWithOptional.C().instance()
69
57
  assert c.x == 1
70
58
  assert c.y is None
59
+
60
+
61
+ def test_instance_keep_config():
62
+ evaluator = Evaluator.C(model=Model.C())
63
+ instance = evaluator.instance(keep=True)
64
+
65
+ assert instance.__config__ is evaluator