experimaestro 2.0.0a8__py3-none-any.whl → 2.0.0b8__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 (122) hide show
  1. experimaestro/__init__.py +10 -11
  2. experimaestro/annotations.py +167 -206
  3. experimaestro/cli/__init__.py +278 -7
  4. experimaestro/cli/filter.py +42 -74
  5. experimaestro/cli/jobs.py +157 -106
  6. experimaestro/cli/refactor.py +249 -0
  7. experimaestro/click.py +0 -1
  8. experimaestro/commandline.py +19 -3
  9. experimaestro/connectors/__init__.py +20 -1
  10. experimaestro/connectors/local.py +12 -0
  11. experimaestro/core/arguments.py +182 -46
  12. experimaestro/core/identifier.py +107 -6
  13. experimaestro/core/objects/__init__.py +6 -0
  14. experimaestro/core/objects/config.py +542 -25
  15. experimaestro/core/objects/config_walk.py +20 -0
  16. experimaestro/core/serialization.py +91 -34
  17. experimaestro/core/subparameters.py +164 -0
  18. experimaestro/core/types.py +175 -38
  19. experimaestro/exceptions.py +26 -0
  20. experimaestro/experiments/cli.py +111 -25
  21. experimaestro/generators.py +50 -9
  22. experimaestro/huggingface.py +3 -1
  23. experimaestro/launcherfinder/parser.py +29 -0
  24. experimaestro/launchers/__init__.py +26 -1
  25. experimaestro/launchers/direct.py +12 -0
  26. experimaestro/launchers/slurm/base.py +154 -2
  27. experimaestro/mkdocs/metaloader.py +0 -1
  28. experimaestro/mypy.py +452 -7
  29. experimaestro/notifications.py +63 -13
  30. experimaestro/progress.py +0 -2
  31. experimaestro/rpyc.py +0 -1
  32. experimaestro/run.py +19 -6
  33. experimaestro/scheduler/base.py +510 -125
  34. experimaestro/scheduler/dependencies.py +43 -28
  35. experimaestro/scheduler/dynamic_outputs.py +259 -130
  36. experimaestro/scheduler/experiment.py +256 -31
  37. experimaestro/scheduler/interfaces.py +501 -0
  38. experimaestro/scheduler/jobs.py +216 -206
  39. experimaestro/scheduler/remote/__init__.py +31 -0
  40. experimaestro/scheduler/remote/client.py +874 -0
  41. experimaestro/scheduler/remote/protocol.py +467 -0
  42. experimaestro/scheduler/remote/server.py +423 -0
  43. experimaestro/scheduler/remote/sync.py +144 -0
  44. experimaestro/scheduler/services.py +323 -23
  45. experimaestro/scheduler/state_db.py +437 -0
  46. experimaestro/scheduler/state_provider.py +2766 -0
  47. experimaestro/scheduler/state_sync.py +891 -0
  48. experimaestro/scheduler/workspace.py +52 -10
  49. experimaestro/scriptbuilder.py +7 -0
  50. experimaestro/server/__init__.py +147 -57
  51. experimaestro/server/data/index.css +0 -125
  52. experimaestro/server/data/index.css.map +1 -1
  53. experimaestro/server/data/index.js +194 -58
  54. experimaestro/server/data/index.js.map +1 -1
  55. experimaestro/settings.py +44 -5
  56. experimaestro/sphinx/__init__.py +3 -3
  57. experimaestro/taskglobals.py +20 -0
  58. experimaestro/tests/conftest.py +80 -0
  59. experimaestro/tests/core/test_generics.py +2 -2
  60. experimaestro/tests/identifier_stability.json +45 -0
  61. experimaestro/tests/launchers/bin/sacct +6 -2
  62. experimaestro/tests/launchers/bin/sbatch +4 -2
  63. experimaestro/tests/launchers/test_slurm.py +80 -0
  64. experimaestro/tests/tasks/test_dynamic.py +231 -0
  65. experimaestro/tests/test_cli_jobs.py +615 -0
  66. experimaestro/tests/test_deprecated.py +630 -0
  67. experimaestro/tests/test_environment.py +200 -0
  68. experimaestro/tests/test_file_progress_integration.py +1 -1
  69. experimaestro/tests/test_forward.py +3 -3
  70. experimaestro/tests/test_identifier.py +372 -41
  71. experimaestro/tests/test_identifier_stability.py +458 -0
  72. experimaestro/tests/test_instance.py +3 -3
  73. experimaestro/tests/test_multitoken.py +442 -0
  74. experimaestro/tests/test_mypy.py +433 -0
  75. experimaestro/tests/test_objects.py +312 -5
  76. experimaestro/tests/test_outputs.py +2 -2
  77. experimaestro/tests/test_param.py +8 -12
  78. experimaestro/tests/test_partial_paths.py +231 -0
  79. experimaestro/tests/test_progress.py +0 -48
  80. experimaestro/tests/test_remote_state.py +671 -0
  81. experimaestro/tests/test_resumable_task.py +480 -0
  82. experimaestro/tests/test_serializers.py +141 -1
  83. experimaestro/tests/test_state_db.py +434 -0
  84. experimaestro/tests/test_subparameters.py +160 -0
  85. experimaestro/tests/test_tags.py +136 -0
  86. experimaestro/tests/test_tasks.py +107 -121
  87. experimaestro/tests/test_token_locking.py +252 -0
  88. experimaestro/tests/test_tokens.py +17 -13
  89. experimaestro/tests/test_types.py +123 -1
  90. experimaestro/tests/test_workspace_triggers.py +158 -0
  91. experimaestro/tests/token_reschedule.py +4 -2
  92. experimaestro/tests/utils.py +2 -2
  93. experimaestro/tokens.py +154 -57
  94. experimaestro/tools/diff.py +1 -1
  95. experimaestro/tui/__init__.py +8 -0
  96. experimaestro/tui/app.py +2395 -0
  97. experimaestro/tui/app.tcss +353 -0
  98. experimaestro/tui/log_viewer.py +228 -0
  99. experimaestro/utils/__init__.py +23 -0
  100. experimaestro/utils/environment.py +148 -0
  101. experimaestro/utils/git.py +129 -0
  102. experimaestro/utils/resources.py +1 -1
  103. experimaestro/version.py +34 -0
  104. {experimaestro-2.0.0a8.dist-info → experimaestro-2.0.0b8.dist-info}/METADATA +68 -38
  105. experimaestro-2.0.0b8.dist-info/RECORD +187 -0
  106. {experimaestro-2.0.0a8.dist-info → experimaestro-2.0.0b8.dist-info}/WHEEL +1 -1
  107. experimaestro-2.0.0b8.dist-info/entry_points.txt +16 -0
  108. experimaestro/compat.py +0 -6
  109. experimaestro/core/objects.pyi +0 -221
  110. experimaestro/server/data/0c35d18bf06992036b69.woff2 +0 -0
  111. experimaestro/server/data/219aa9140e099e6c72ed.woff2 +0 -0
  112. experimaestro/server/data/3a4004a46a653d4b2166.woff +0 -0
  113. experimaestro/server/data/3baa5b8f3469222b822d.woff +0 -0
  114. experimaestro/server/data/4d73cb90e394b34b7670.woff +0 -0
  115. experimaestro/server/data/4ef4218c522f1eb6b5b1.woff2 +0 -0
  116. experimaestro/server/data/5d681e2edae8c60630db.woff +0 -0
  117. experimaestro/server/data/6f420cf17cc0d7676fad.woff2 +0 -0
  118. experimaestro/server/data/c380809fd3677d7d6903.woff2 +0 -0
  119. experimaestro/server/data/f882956fd323fd322f31.woff +0 -0
  120. experimaestro-2.0.0a8.dist-info/RECORD +0 -166
  121. experimaestro-2.0.0a8.dist-info/entry_points.txt +0 -17
  122. {experimaestro-2.0.0a8.dist-info → experimaestro-2.0.0b8.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,433 @@
1
+ """Tests for the mypy plugin.
2
+
3
+ These tests verify that the mypy plugin can be loaded and provides
4
+ basic type inference for experimaestro types.
5
+ """
6
+
7
+ import subprocess
8
+ import tempfile
9
+ from pathlib import Path
10
+
11
+
12
+ def run_mypy(code: str, use_plugin: bool = True) -> tuple[int, str, str]:
13
+ """Run mypy on the given code and return (exit_code, stdout, stderr)."""
14
+ with tempfile.TemporaryDirectory() as tmpdir:
15
+ # Write test code
16
+ test_file = Path(tmpdir) / "test_code.py"
17
+ test_file.write_text(code)
18
+
19
+ # Write mypy config if using plugin
20
+ if use_plugin:
21
+ config_file = Path(tmpdir) / "mypy.ini"
22
+ config_file.write_text("[mypy]\nplugins = experimaestro.mypy\n")
23
+ args = ["mypy", str(test_file), "--config-file", str(config_file)]
24
+ else:
25
+ args = ["mypy", str(test_file)]
26
+
27
+ result = subprocess.run(
28
+ args,
29
+ capture_output=True,
30
+ text=True,
31
+ )
32
+ return result.returncode, result.stdout, result.stderr
33
+
34
+
35
+ def test_mypy_plugin_loads():
36
+ """Test that the mypy plugin can be loaded without errors."""
37
+ code = """
38
+ from experimaestro import Config, Param
39
+
40
+ class Model(Config):
41
+ hidden_size: Param[int]
42
+ """
43
+ _, stdout, stderr = run_mypy(code)
44
+ # Plugin should load without crashing
45
+ assert "INTERNAL ERROR" not in stdout
46
+ assert "INTERNAL ERROR" not in stderr
47
+
48
+
49
+ def test_mypy_plugin_import():
50
+ """Test that the plugin module can be imported."""
51
+ from experimaestro.mypy import plugin, ExperimaestroPlugin
52
+
53
+ # Verify the plugin function returns the plugin class
54
+ # The version argument is required by mypy API but unused
55
+ assert plugin("1.0") == ExperimaestroPlugin
56
+
57
+
58
+ def test_mypy_plugin_is_config_subclass():
59
+ """Test the is_config_subclass helper function."""
60
+ from experimaestro.mypy import is_config_subclass
61
+
62
+ # Note: This tests the function signature, not full functionality
63
+ # (full functionality requires mypy TypeInfo objects)
64
+ assert callable(is_config_subclass)
65
+
66
+
67
+ def test_mypy_config_c_type_inference():
68
+ """Test that Config.C type inference works with the plugin."""
69
+ code = """
70
+ from experimaestro import Config, Task, Param
71
+
72
+ class Model(Config):
73
+ hidden_size: Param[int]
74
+
75
+ class TrainTask(Task):
76
+ model: Param[Model]
77
+
78
+ def execute(self):
79
+ pass
80
+
81
+ # Test that .C returns the correct type
82
+ model = Model.C # type: ignore[call-arg]
83
+ task = TrainTask.C # type: ignore[call-arg]
84
+
85
+ # These would fail without the plugin (would be Any)
86
+ reveal_type(model) # Should show Type[Model]
87
+ reveal_type(task) # Should show Type[TrainTask]
88
+ """
89
+ _, stdout, stderr = run_mypy(code)
90
+
91
+ # Plugin should load without crashing
92
+ assert "INTERNAL ERROR" not in stdout
93
+ assert "INTERNAL ERROR" not in stderr
94
+
95
+ # Check that types are inferred (not "Any")
96
+ assert "Model" in stdout
97
+ assert "TrainTask" in stdout
98
+
99
+
100
+ def test_mypy_config_c_parameters():
101
+ """Test that Config.C accepts proper parameters."""
102
+ code = """
103
+ from experimaestro import Config, Task, Param
104
+
105
+ class Model(Config):
106
+ hidden_size: Param[int]
107
+
108
+ class TrainTask(Task):
109
+ model: Param[Model]
110
+ epochs: Param[int]
111
+
112
+ def execute(self):
113
+ pass
114
+
115
+ # Test parameter hints
116
+ model = Model.C(hidden_size=256)
117
+ reveal_type(model.hidden_size) # Should be int
118
+
119
+ task = TrainTask.C(model=model, epochs=10)
120
+ reveal_type(task.epochs) # Should be int
121
+ reveal_type(task.model) # Should be Model
122
+ """
123
+ _, stdout, stderr = run_mypy(code)
124
+
125
+ # Plugin should load without crashing
126
+ assert "INTERNAL ERROR" not in stdout
127
+ assert "INTERNAL ERROR" not in stderr
128
+
129
+ # Check that attribute types are inferred
130
+ assert "int" in stdout
131
+
132
+
133
+ def test_mypy_submit_return_type():
134
+ """Test that submit() returns the correct type."""
135
+ code = """
136
+ from experimaestro import Task, Param
137
+
138
+ class MyTask(Task):
139
+ x: Param[int]
140
+
141
+ def execute(self):
142
+ pass
143
+
144
+ task = MyTask.C(x=1)
145
+ result = task.submit()
146
+ reveal_type(result) # Should be MyTask
147
+ """
148
+ _, stdout, stderr = run_mypy(code)
149
+
150
+ # Plugin should load without crashing
151
+ assert "INTERNAL ERROR" not in stdout
152
+ assert "INTERNAL ERROR" not in stderr
153
+
154
+ # Check that submit() returns the task type
155
+ assert "MyTask" in stdout
156
+
157
+
158
+ def test_mypy_config_composition():
159
+ """Test that configs can be composed together."""
160
+ code = """
161
+ from experimaestro import Config, Task, Param
162
+
163
+ class Model(Config):
164
+ hidden_size: Param[int]
165
+
166
+ class Train(Task):
167
+ model: Param[Model]
168
+ epochs: Param[int]
169
+
170
+ def execute(self):
171
+ pass
172
+
173
+ # Create a model config
174
+ model = Model.C(hidden_size=256)
175
+
176
+ # Pass it to another config - should type-check
177
+ train = Train.C(model=model, epochs=10)
178
+ reveal_type(train.model) # Should be Model
179
+ """
180
+ _, stdout, stderr = run_mypy(code)
181
+
182
+ # Plugin should load without crashing
183
+ assert "INTERNAL ERROR" not in stdout
184
+ assert "INTERNAL ERROR" not in stderr
185
+
186
+ # Check that model attribute is typed correctly
187
+ assert "Model" in stdout
188
+
189
+
190
+ def test_mypy_default_values():
191
+ """Test that fields with defaults are optional."""
192
+ code = """
193
+ from experimaestro import Config, Param
194
+
195
+ class Settings(Config):
196
+ required_field: Param[int]
197
+ optional_field: Param[int] = 10 # Has default
198
+
199
+ # Should work without optional_field
200
+ config = Settings.C(required_field=5)
201
+ reveal_type(config.required_field)
202
+ reveal_type(config.optional_field)
203
+ """
204
+ _, stdout, stderr = run_mypy(code)
205
+
206
+ # Plugin should load without crashing
207
+ assert "INTERNAL ERROR" not in stdout
208
+ assert "INTERNAL ERROR" not in stderr
209
+
210
+ # Both fields should be int
211
+ assert "int" in stdout
212
+
213
+
214
+ def test_mypy_configmixin_methods():
215
+ """Test that ConfigMixin methods are available on Config subclasses."""
216
+ code = """
217
+ from experimaestro import Config, Task, Param
218
+
219
+ class Model(Config):
220
+ hidden_size: Param[int]
221
+
222
+ class TrainTask(Task):
223
+ model: Param[Model]
224
+
225
+ def execute(self):
226
+ pass
227
+
228
+ # Test ConfigMixin methods are available
229
+ model = Model.C(hidden_size=256)
230
+
231
+ # tag() method from ConfigMixin - should return self
232
+ tagged = model.tag("version", "1.0")
233
+ reveal_type(tagged) # Should be Model
234
+
235
+ # tags() method from ConfigMixin
236
+ t = model.tags()
237
+ reveal_type(t) # Should return tags dict
238
+ """
239
+ _, stdout, stderr = run_mypy(code)
240
+
241
+ # Plugin should load without crashing
242
+ assert "INTERNAL ERROR" not in stdout
243
+ assert "INTERNAL ERROR" not in stderr
244
+
245
+ # Check that Model is inferred for tagged (tag returns self)
246
+ assert "Model" in stdout
247
+
248
+
249
+ def test_mypy_task_outputs_return_type():
250
+ """Test that submit() returns task_outputs type when defined."""
251
+ code = """
252
+ from experimaestro import Task, Config, Param
253
+
254
+ class Output(Config):
255
+ value: Param[int]
256
+
257
+ class TaskWithOutputs(Task):
258
+ x: Param[int]
259
+
260
+ def task_outputs(self, marker) -> Output:
261
+ return marker(Output.C(value=self.x))
262
+
263
+ def execute(self):
264
+ pass
265
+
266
+ # submit() should return Output (from task_outputs annotation)
267
+ task = TaskWithOutputs.C(x=1)
268
+ result = task.submit()
269
+ reveal_type(result) # Should be Output
270
+ """
271
+ _, stdout, stderr = run_mypy(code)
272
+
273
+ # Plugin should load without crashing
274
+ assert "INTERNAL ERROR" not in stdout
275
+ assert "INTERNAL ERROR" not in stderr
276
+
277
+ # Check that submit returns the task_outputs return type
278
+ # The reveal_type should show Output
279
+ assert "Output" in stdout or "TaskWithOutputs" in stdout
280
+
281
+
282
+ def test_mypy_submit_with_arguments():
283
+ """Test that submit() accepts launcher and init_tasks arguments."""
284
+ code = """
285
+ from experimaestro import Task, Param
286
+
287
+ class MyTask(Task):
288
+ x: Param[int]
289
+
290
+ def execute(self):
291
+ pass
292
+
293
+ task = MyTask.C(x=1)
294
+
295
+ # These should all be valid (no errors)
296
+ result1 = task.submit()
297
+ result2 = task.submit(launcher=None)
298
+ result3 = task.submit(init_tasks=[])
299
+ result4 = task.submit(workspace=None, launcher=None, run_mode=None, max_retries=5)
300
+ """
301
+ _, stdout, stderr = run_mypy(code)
302
+
303
+ # Plugin should load without crashing
304
+ assert "INTERNAL ERROR" not in stdout
305
+ assert "INTERNAL ERROR" not in stderr
306
+
307
+ # Should not have "Unexpected keyword argument" errors
308
+ assert "Unexpected keyword argument" not in stdout
309
+
310
+
311
+ def test_mypy_multiple_inheritance_with_module():
312
+ """Test that multiple inheritance with non-Config bases works.
313
+
314
+ Classes like CNN(Config, nn.Module) should only expose Param fields,
315
+ not attributes from nn.Module like 'training'.
316
+ """
317
+ code = """
318
+ from experimaestro import Config, Param
319
+
320
+ # Simulate nn.Module-like class that has a 'training' attribute
321
+ class ModuleBase:
322
+ training: bool = True
323
+ _some_internal: int = 0
324
+
325
+ class Model(Config, ModuleBase):
326
+ hidden_size: Param[int]
327
+ kernel_size: Param[int] = 3 # Has default
328
+
329
+ # Should work with just experimaestro Param fields
330
+ # Should NOT require 'training' argument from ModuleBase
331
+ model = Model.C(hidden_size=256)
332
+ reveal_type(model.hidden_size) # Should be int
333
+ """
334
+ _, stdout, stderr = run_mypy(code)
335
+
336
+ # Plugin should load without crashing
337
+ assert "INTERNAL ERROR" not in stdout
338
+ assert "INTERNAL ERROR" not in stderr
339
+
340
+ # Should NOT have "Missing named argument 'training'" error
341
+ assert "Missing named argument" not in stdout
342
+ assert "training" not in stdout.lower() or "Revealed type" in stdout
343
+
344
+
345
+ def test_mypy_only_param_and_meta_in_init():
346
+ """Test that only Param and Meta fields are in __init__, not Constant or other attributes."""
347
+ code = """
348
+ from experimaestro import Config, Task, Param, Meta, Constant
349
+
350
+ class MyTask(Task):
351
+ # These SHOULD be in __init__
352
+ required_param: Param[int]
353
+ optional_param: Param[str] = "default"
354
+ meta_field: Meta[int] = 42
355
+
356
+ # These should NOT be in __init__
357
+ version: Constant[str] = "1.0"
358
+ regular_class_attr: str = "not a param"
359
+
360
+ def execute(self):
361
+ pass
362
+
363
+ # Should work with only Param and Meta fields
364
+ task = MyTask.C(required_param=10)
365
+ reveal_type(task)
366
+ """
367
+ _, stdout, stderr = run_mypy(code)
368
+
369
+ # Plugin should load without crashing
370
+ assert "INTERNAL ERROR" not in stdout
371
+ assert "INTERNAL ERROR" not in stderr
372
+
373
+ # Should NOT complain about missing 'version' or 'regular_class_attr'
374
+ assert "Missing named argument" not in stdout
375
+ assert "version" not in stdout.lower() or "Revealed type" in stdout
376
+
377
+
378
+ def test_mypy_constant_not_in_init():
379
+ """Test that Constant fields are explicitly excluded from __init__."""
380
+ code = """
381
+ from experimaestro import Task, Param, Constant
382
+
383
+ class VersionedTask(Task):
384
+ data: Param[str]
385
+ version: Constant[str] = "2.0"
386
+ algorithm_version: Constant[int] = 3
387
+
388
+ def execute(self):
389
+ pass
390
+
391
+ # Should only require 'data', not version fields
392
+ task = VersionedTask.C(data="test")
393
+
394
+ # Passing version should be an error (not in __init__)
395
+ bad_task = VersionedTask.C(data="test", version="3.0") # Should error
396
+ """
397
+ _, stdout, stderr = run_mypy(code)
398
+
399
+ # Plugin should load without crashing
400
+ assert "INTERNAL ERROR" not in stdout
401
+ assert "INTERNAL ERROR" not in stderr
402
+
403
+ # Should have an error about 'version' being unexpected
404
+ assert "version" in stdout.lower()
405
+
406
+
407
+ def test_mypy_meta_fields_optional():
408
+ """Test that Meta fields are always optional (have implicit defaults)."""
409
+ code = """
410
+ from experimaestro import Task, Param, Meta
411
+ from pathlib import Path
412
+
413
+ class TaskWithMeta(Task):
414
+ required: Param[int]
415
+ # Meta fields should always be optional even without explicit default
416
+ output_path: Meta[Path]
417
+ log_level: Meta[str] = "INFO"
418
+
419
+ def execute(self):
420
+ pass
421
+
422
+ # Should work without providing Meta fields
423
+ task = TaskWithMeta.C(required=5)
424
+ reveal_type(task)
425
+ """
426
+ _, stdout, stderr = run_mypy(code)
427
+
428
+ # Plugin should load without crashing
429
+ assert "INTERNAL ERROR" not in stdout
430
+ assert "INTERNAL ERROR" not in stderr
431
+
432
+ # Should NOT complain about missing output_path
433
+ assert "Missing named argument" not in stdout