experimaestro 2.0.0a8__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.
- experimaestro/__init__.py +10 -11
- experimaestro/annotations.py +167 -206
- experimaestro/cli/__init__.py +130 -5
- experimaestro/cli/filter.py +42 -74
- experimaestro/cli/jobs.py +157 -106
- experimaestro/cli/refactor.py +249 -0
- experimaestro/click.py +0 -1
- experimaestro/commandline.py +19 -3
- experimaestro/connectors/__init__.py +20 -1
- experimaestro/connectors/local.py +12 -0
- experimaestro/core/arguments.py +182 -46
- experimaestro/core/identifier.py +107 -6
- experimaestro/core/objects/__init__.py +6 -0
- experimaestro/core/objects/config.py +542 -25
- experimaestro/core/objects/config_walk.py +20 -0
- experimaestro/core/serialization.py +91 -34
- experimaestro/core/subparameters.py +164 -0
- experimaestro/core/types.py +175 -38
- experimaestro/exceptions.py +26 -0
- experimaestro/experiments/cli.py +107 -25
- experimaestro/generators.py +50 -9
- experimaestro/huggingface.py +3 -1
- experimaestro/launcherfinder/parser.py +29 -0
- experimaestro/launchers/__init__.py +26 -1
- experimaestro/launchers/direct.py +12 -0
- experimaestro/launchers/slurm/base.py +154 -2
- experimaestro/mkdocs/metaloader.py +0 -1
- experimaestro/mypy.py +452 -7
- experimaestro/notifications.py +63 -13
- experimaestro/progress.py +0 -2
- experimaestro/rpyc.py +0 -1
- experimaestro/run.py +19 -6
- experimaestro/scheduler/base.py +489 -125
- experimaestro/scheduler/dependencies.py +43 -28
- experimaestro/scheduler/dynamic_outputs.py +259 -130
- experimaestro/scheduler/experiment.py +225 -30
- experimaestro/scheduler/interfaces.py +474 -0
- experimaestro/scheduler/jobs.py +216 -206
- experimaestro/scheduler/services.py +186 -12
- experimaestro/scheduler/state_db.py +388 -0
- experimaestro/scheduler/state_provider.py +2345 -0
- experimaestro/scheduler/state_sync.py +834 -0
- experimaestro/scheduler/workspace.py +52 -10
- experimaestro/scriptbuilder.py +7 -0
- experimaestro/server/__init__.py +147 -57
- experimaestro/server/data/index.css +0 -125
- experimaestro/server/data/index.css.map +1 -1
- experimaestro/server/data/index.js +194 -58
- experimaestro/server/data/index.js.map +1 -1
- experimaestro/settings.py +44 -5
- experimaestro/sphinx/__init__.py +3 -3
- experimaestro/taskglobals.py +20 -0
- experimaestro/tests/conftest.py +80 -0
- experimaestro/tests/core/test_generics.py +2 -2
- experimaestro/tests/identifier_stability.json +45 -0
- experimaestro/tests/launchers/bin/sacct +6 -2
- experimaestro/tests/launchers/bin/sbatch +4 -2
- experimaestro/tests/launchers/test_slurm.py +80 -0
- experimaestro/tests/tasks/test_dynamic.py +231 -0
- experimaestro/tests/test_cli_jobs.py +615 -0
- experimaestro/tests/test_deprecated.py +630 -0
- experimaestro/tests/test_environment.py +200 -0
- experimaestro/tests/test_file_progress_integration.py +1 -1
- experimaestro/tests/test_forward.py +3 -3
- experimaestro/tests/test_identifier.py +372 -41
- experimaestro/tests/test_identifier_stability.py +458 -0
- experimaestro/tests/test_instance.py +3 -3
- experimaestro/tests/test_multitoken.py +442 -0
- experimaestro/tests/test_mypy.py +433 -0
- experimaestro/tests/test_objects.py +312 -5
- experimaestro/tests/test_outputs.py +2 -2
- experimaestro/tests/test_param.py +8 -12
- experimaestro/tests/test_partial_paths.py +231 -0
- experimaestro/tests/test_progress.py +0 -48
- experimaestro/tests/test_resumable_task.py +480 -0
- experimaestro/tests/test_serializers.py +141 -1
- experimaestro/tests/test_state_db.py +434 -0
- experimaestro/tests/test_subparameters.py +160 -0
- experimaestro/tests/test_tags.py +136 -0
- experimaestro/tests/test_tasks.py +107 -121
- experimaestro/tests/test_token_locking.py +252 -0
- experimaestro/tests/test_tokens.py +17 -13
- experimaestro/tests/test_types.py +123 -1
- experimaestro/tests/test_workspace_triggers.py +158 -0
- experimaestro/tests/token_reschedule.py +4 -2
- experimaestro/tests/utils.py +2 -2
- experimaestro/tokens.py +154 -57
- experimaestro/tools/diff.py +1 -1
- experimaestro/tui/__init__.py +8 -0
- experimaestro/tui/app.py +2303 -0
- experimaestro/tui/app.tcss +353 -0
- experimaestro/tui/log_viewer.py +228 -0
- experimaestro/utils/__init__.py +23 -0
- experimaestro/utils/environment.py +148 -0
- experimaestro/utils/git.py +129 -0
- experimaestro/utils/resources.py +1 -1
- experimaestro/version.py +34 -0
- {experimaestro-2.0.0a8.dist-info → experimaestro-2.0.0b4.dist-info}/METADATA +68 -38
- experimaestro-2.0.0b4.dist-info/RECORD +181 -0
- {experimaestro-2.0.0a8.dist-info → experimaestro-2.0.0b4.dist-info}/WHEEL +1 -1
- experimaestro-2.0.0b4.dist-info/entry_points.txt +16 -0
- experimaestro/compat.py +0 -6
- experimaestro/core/objects.pyi +0 -221
- experimaestro/server/data/0c35d18bf06992036b69.woff2 +0 -0
- experimaestro/server/data/219aa9140e099e6c72ed.woff2 +0 -0
- experimaestro/server/data/3a4004a46a653d4b2166.woff +0 -0
- experimaestro/server/data/3baa5b8f3469222b822d.woff +0 -0
- experimaestro/server/data/4d73cb90e394b34b7670.woff +0 -0
- experimaestro/server/data/4ef4218c522f1eb6b5b1.woff2 +0 -0
- experimaestro/server/data/5d681e2edae8c60630db.woff +0 -0
- experimaestro/server/data/6f420cf17cc0d7676fad.woff2 +0 -0
- experimaestro/server/data/c380809fd3677d7d6903.woff2 +0 -0
- experimaestro/server/data/f882956fd323fd322f31.woff +0 -0
- experimaestro-2.0.0a8.dist-info/RECORD +0 -166
- experimaestro-2.0.0a8.dist-info/entry_points.txt +0 -17
- {experimaestro-2.0.0a8.dist-info → experimaestro-2.0.0b4.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
|