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.
- experimaestro/__init__.py +14 -4
- experimaestro/__main__.py +3 -423
- experimaestro/annotations.py +14 -4
- experimaestro/cli/__init__.py +311 -0
- experimaestro/{filter.py → cli/filter.py} +23 -9
- experimaestro/cli/jobs.py +268 -0
- experimaestro/cli/progress.py +269 -0
- experimaestro/click.py +0 -35
- experimaestro/commandline.py +3 -7
- experimaestro/connectors/__init__.py +29 -14
- experimaestro/connectors/local.py +19 -10
- experimaestro/connectors/ssh.py +27 -8
- experimaestro/core/arguments.py +45 -3
- experimaestro/core/callbacks.py +52 -0
- experimaestro/core/context.py +8 -9
- experimaestro/core/identifier.py +310 -0
- experimaestro/core/objects/__init__.py +44 -0
- experimaestro/core/{objects.py → objects/config.py} +399 -772
- experimaestro/core/objects/config_utils.py +58 -0
- experimaestro/core/objects/config_walk.py +151 -0
- experimaestro/core/objects.pyi +15 -45
- experimaestro/core/serialization.py +63 -9
- experimaestro/core/serializers.py +1 -8
- experimaestro/core/types.py +104 -66
- experimaestro/experiments/cli.py +154 -72
- experimaestro/experiments/configuration.py +10 -1
- experimaestro/generators.py +6 -1
- experimaestro/ipc.py +4 -1
- experimaestro/launcherfinder/__init__.py +1 -1
- experimaestro/launcherfinder/base.py +2 -18
- experimaestro/launcherfinder/parser.py +8 -3
- experimaestro/launcherfinder/registry.py +52 -140
- experimaestro/launcherfinder/specs.py +49 -10
- experimaestro/launchers/direct.py +0 -47
- experimaestro/launchers/slurm/base.py +54 -14
- experimaestro/mkdocs/__init__.py +1 -1
- experimaestro/mkdocs/base.py +6 -8
- experimaestro/notifications.py +38 -12
- experimaestro/progress.py +406 -0
- experimaestro/run.py +24 -3
- experimaestro/scheduler/__init__.py +18 -1
- experimaestro/scheduler/base.py +108 -808
- experimaestro/scheduler/dynamic_outputs.py +184 -0
- experimaestro/scheduler/experiment.py +387 -0
- experimaestro/scheduler/jobs.py +475 -0
- experimaestro/scheduler/signal_handler.py +32 -0
- experimaestro/scheduler/state.py +75 -0
- experimaestro/scheduler/workspace.py +27 -8
- experimaestro/scriptbuilder.py +18 -3
- experimaestro/server/__init__.py +36 -5
- experimaestro/server/data/1815e00441357e01619e.ttf +0 -0
- experimaestro/server/data/2463b90d9a316e4e5294.woff2 +0 -0
- experimaestro/server/data/2582b0e4bcf85eceead0.ttf +0 -0
- experimaestro/server/data/89999bdf5d835c012025.woff2 +0 -0
- experimaestro/server/data/914997e1bdfc990d0897.ttf +0 -0
- experimaestro/server/data/c210719e60948b211a12.woff2 +0 -0
- experimaestro/server/data/index.css +5187 -5068
- experimaestro/server/data/index.css.map +1 -1
- experimaestro/server/data/index.js +68887 -68064
- experimaestro/server/data/index.js.map +1 -1
- experimaestro/settings.py +45 -5
- experimaestro/sphinx/__init__.py +7 -17
- experimaestro/taskglobals.py +7 -2
- experimaestro/tests/core/__init__.py +0 -0
- experimaestro/tests/core/test_generics.py +206 -0
- experimaestro/tests/definitions_types.py +5 -3
- experimaestro/tests/launchers/bin/sbatch +34 -7
- experimaestro/tests/launchers/bin/srun +5 -0
- experimaestro/tests/launchers/common.py +17 -5
- experimaestro/tests/launchers/config_slurm/launchers.py +25 -0
- experimaestro/tests/restart.py +10 -5
- experimaestro/tests/tasks/all.py +23 -10
- experimaestro/tests/tasks/foreign.py +2 -4
- experimaestro/tests/test_checkers.py +2 -2
- experimaestro/tests/test_dependencies.py +11 -17
- experimaestro/tests/test_experiment.py +73 -0
- experimaestro/tests/test_file_progress.py +425 -0
- experimaestro/tests/test_file_progress_integration.py +477 -0
- experimaestro/tests/test_findlauncher.py +12 -5
- experimaestro/tests/test_forward.py +5 -5
- experimaestro/tests/test_generators.py +93 -0
- experimaestro/tests/test_identifier.py +182 -158
- experimaestro/tests/test_instance.py +19 -27
- experimaestro/tests/test_objects.py +13 -20
- experimaestro/tests/test_outputs.py +6 -6
- experimaestro/tests/test_param.py +68 -30
- experimaestro/tests/test_progress.py +4 -4
- experimaestro/tests/test_serializers.py +24 -64
- experimaestro/tests/test_ssh.py +7 -0
- experimaestro/tests/test_tags.py +50 -21
- experimaestro/tests/test_tasks.py +42 -51
- experimaestro/tests/test_tokens.py +11 -8
- experimaestro/tests/test_types.py +24 -21
- experimaestro/tests/test_validation.py +67 -110
- experimaestro/tests/token_reschedule.py +1 -1
- experimaestro/tokens.py +24 -13
- experimaestro/tools/diff.py +8 -1
- experimaestro/typingutils.py +20 -11
- experimaestro/utils/asyncio.py +6 -2
- experimaestro/utils/multiprocessing.py +44 -0
- experimaestro/utils/resources.py +11 -3
- {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/METADATA +28 -36
- experimaestro-2.0.0a8.dist-info/RECORD +166 -0
- {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/WHEEL +1 -1
- {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/entry_points.txt +0 -4
- experimaestro/launchers/slurm/cli.py +0 -29
- experimaestro/launchers/slurm/configuration.py +0 -597
- experimaestro/scheduler/environment.py +0 -94
- experimaestro/server/data/016b4a6cdced82ab3aa1.ttf +0 -0
- experimaestro/server/data/50701fbb8177c2dde530.ttf +0 -0
- experimaestro/server/data/878f31251d960bd6266f.woff2 +0 -0
- experimaestro/server/data/b041b1fa4fe241b23445.woff2 +0 -0
- experimaestro/server/data/b6879d41b0852f01ed5b.woff2 +0 -0
- experimaestro/server/data/d75e3fd1eb12e9bd6655.ttf +0 -0
- experimaestro/tests/launchers/config_slurm/launchers.yaml +0 -134
- experimaestro/utils/yaml.py +0 -202
- experimaestro-1.5.1.dist-info/RECORD +0 -148
- {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info/licenses}/LICENSE +0 -0
|
@@ -29,8 +29,8 @@ from .definitions_types import IntegerTask, FloatTask
|
|
|
29
29
|
|
|
30
30
|
def test_task_types():
|
|
31
31
|
with TemporaryExperiment("simple"):
|
|
32
|
-
assert IntegerTask(value=5).submit().__xpm__.job.wait() == JobState.DONE
|
|
33
|
-
assert FloatTask(value=5.1).submit().__xpm__.job.wait() == JobState.DONE
|
|
32
|
+
assert IntegerTask.C(value=5).submit().__xpm__.job.wait() == JobState.DONE
|
|
33
|
+
assert FloatTask.C(value=5.1).submit().__xpm__.job.wait() == JobState.DONE
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def test_simple_task():
|
|
@@ -38,11 +38,11 @@ def test_simple_task():
|
|
|
38
38
|
assert isinstance(workdir, Path)
|
|
39
39
|
with TemporaryExperiment("helloworld", workdir=workdir, maxwait=20):
|
|
40
40
|
# Submit the tasks
|
|
41
|
-
hello = Say(word="hello").submit()
|
|
42
|
-
world = Say(word="world").submit()
|
|
41
|
+
hello = Say.C(word="hello").submit()
|
|
42
|
+
world = Say.C(word="world").submit()
|
|
43
43
|
|
|
44
44
|
# Concat will depend on the two first tasks
|
|
45
|
-
concat = Concat(strings=[hello, world]).submit()
|
|
45
|
+
concat = Concat.C(strings=[hello, world]).submit()
|
|
46
46
|
|
|
47
47
|
assert concat.__xpm__.job.state == JobState.DONE
|
|
48
48
|
assert Path(concat.stdout()).read_text() == "HELLO WORLD\n"
|
|
@@ -51,16 +51,16 @@ def test_simple_task():
|
|
|
51
51
|
def test_not_submitted():
|
|
52
52
|
"""A not submitted task should not be accepted as an argument"""
|
|
53
53
|
with TemporaryExperiment("helloworld", maxwait=2):
|
|
54
|
-
hello = Say(word="hello")
|
|
54
|
+
hello = Say.C(word="hello")
|
|
55
55
|
with pytest.raises(ValueError):
|
|
56
|
-
Concat(strings=[hello])
|
|
56
|
+
Concat.C(strings=[hello])
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def test_fail_simple():
|
|
60
60
|
"""Failing task... should fail"""
|
|
61
61
|
with pytest.raises(FailedExperiment):
|
|
62
62
|
with TemporaryExperiment("failing", maxwait=20):
|
|
63
|
-
fail = Fail().submit()
|
|
63
|
+
fail = Fail.C().submit()
|
|
64
64
|
fail.touch()
|
|
65
65
|
|
|
66
66
|
|
|
@@ -70,8 +70,8 @@ def test_foreign_type():
|
|
|
70
70
|
# Submit the tasks
|
|
71
71
|
from .tasks.foreign import ForeignClassB2
|
|
72
72
|
|
|
73
|
-
b = ForeignClassB2(x=1, y=2)
|
|
74
|
-
a = ForeignTaskA(b=b).submit()
|
|
73
|
+
b = ForeignClassB2.C(x=1, y=2)
|
|
74
|
+
a = ForeignTaskA.C(b=b).submit()
|
|
75
75
|
|
|
76
76
|
assert a.__xpm__.job.wait() == JobState.DONE
|
|
77
77
|
assert a.stdout().read_text().strip() == "1"
|
|
@@ -81,8 +81,8 @@ def test_fail_dep():
|
|
|
81
81
|
"""Failing task... should cancel dependent"""
|
|
82
82
|
with pytest.raises(FailedExperiment):
|
|
83
83
|
with TemporaryExperiment("failingdep"):
|
|
84
|
-
fail = Fail().submit()
|
|
85
|
-
dep = FailConsumer(fail=fail).submit()
|
|
84
|
+
fail = Fail.C().submit()
|
|
85
|
+
dep = FailConsumer.C(fail=fail).submit()
|
|
86
86
|
fail.touch()
|
|
87
87
|
|
|
88
88
|
assert fail.__xpm__.job.state == JobState.ERROR
|
|
@@ -92,14 +92,14 @@ def test_fail_dep():
|
|
|
92
92
|
def test_unknown_attribute():
|
|
93
93
|
"""No check when setting attributes while executing"""
|
|
94
94
|
with TemporaryExperiment("unknown"):
|
|
95
|
-
method = SetUnknown().submit()
|
|
95
|
+
method = SetUnknown.C().submit()
|
|
96
96
|
|
|
97
97
|
assert method.__xpm__.job.wait() == JobState.DONE
|
|
98
98
|
|
|
99
99
|
|
|
100
100
|
def test_function():
|
|
101
101
|
with TemporaryExperiment("function"):
|
|
102
|
-
method = Method(a=1).submit()
|
|
102
|
+
method = Method.C(a=1).submit()
|
|
103
103
|
|
|
104
104
|
assert method.__xpm__.job.wait() == JobState.DONE
|
|
105
105
|
|
|
@@ -111,7 +111,7 @@ def test_done():
|
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
def restart_function(xp):
|
|
114
|
-
restart.Restart().submit()
|
|
114
|
+
restart.Restart.C().submit()
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
@pytest.mark.parametrize("terminate", restart.TERMINATES_FUNC)
|
|
@@ -123,16 +123,23 @@ def test_restart(terminate):
|
|
|
123
123
|
def test_submitted_twice():
|
|
124
124
|
"""Check that a job cannot be submitted twice within the same experiment"""
|
|
125
125
|
with TemporaryExperiment("duplicate", maxwait=20):
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
|
|
127
|
+
task1 = SimpleTask.C(x=1)
|
|
128
|
+
o1 = task1.submit()
|
|
129
|
+
|
|
130
|
+
task2 = SimpleTask.C(x=1)
|
|
131
|
+
o2 = task2.submit()
|
|
132
|
+
|
|
133
|
+
print(o1)
|
|
134
|
+
assert o1.task is not o2.task
|
|
135
|
+
assert task1.__xpm__.job is task2.__xpm__.job, f"{id(task1)} != {id(task2)}"
|
|
129
136
|
|
|
130
137
|
|
|
131
138
|
def test_configcache():
|
|
132
139
|
"""Test a configuration cache"""
|
|
133
140
|
|
|
134
141
|
with TemporaryExperiment("configcache", maxwait=20):
|
|
135
|
-
task = CacheConfigTask(data=CacheConfig()).submit()
|
|
142
|
+
task = CacheConfigTask.C(data=CacheConfig.C()).submit()
|
|
136
143
|
|
|
137
144
|
assert task.__xpm__.job.wait() == JobState.DONE
|
|
138
145
|
|
|
@@ -174,9 +181,13 @@ def test_tasks_deprecated_inner():
|
|
|
174
181
|
the new class"""
|
|
175
182
|
with TemporaryExperiment("deprecated", maxwait=0) as xp:
|
|
176
183
|
# --- Check that paths are really different first
|
|
177
|
-
task_new = TaskWithDeprecated(p=NewConfig()).submit(
|
|
178
|
-
|
|
179
|
-
|
|
184
|
+
task_new = TaskWithDeprecated.C(p=NewConfig.C()).submit(
|
|
185
|
+
run_mode=RunMode.DRY_RUN
|
|
186
|
+
)
|
|
187
|
+
task_old = TaskWithDeprecated.C(p=OldConfig.C()).submit(
|
|
188
|
+
run_mode=RunMode.DRY_RUN
|
|
189
|
+
)
|
|
190
|
+
task_deprecated = TaskWithDeprecated.C(p=DeprecatedConfig.C()).submit(
|
|
180
191
|
run_mode=RunMode.DRY_RUN
|
|
181
192
|
)
|
|
182
193
|
|
|
@@ -197,7 +208,7 @@ def test_tasks_deprecated_inner():
|
|
|
197
208
|
# --- Now check that automatic linking is performed
|
|
198
209
|
|
|
199
210
|
# Run old task with deprecated configuration
|
|
200
|
-
task_old = TaskWithDeprecated(p=OldConfig()).submit()
|
|
211
|
+
task_old = TaskWithDeprecated.C(p=OldConfig.C()).submit()
|
|
201
212
|
task_old.wait()
|
|
202
213
|
task_old_path = task_old.stdout().parent
|
|
203
214
|
|
|
@@ -229,9 +240,9 @@ def test_tasks_deprecated():
|
|
|
229
240
|
the new class"""
|
|
230
241
|
with TemporaryExperiment("deprecated", maxwait=20) as xp:
|
|
231
242
|
# Check that paths are really different first
|
|
232
|
-
task_new = NewTask(x=1).submit(run_mode=RunMode.DRY_RUN)
|
|
233
|
-
task_old = OldTask(x=1).submit(run_mode=RunMode.DRY_RUN)
|
|
234
|
-
task_deprecated = DeprecatedTask(x=1).submit(run_mode=RunMode.DRY_RUN)
|
|
243
|
+
task_new = NewTask.C(x=1).submit(run_mode=RunMode.DRY_RUN)
|
|
244
|
+
task_old = OldTask.C(x=1).submit(run_mode=RunMode.DRY_RUN)
|
|
245
|
+
task_deprecated = DeprecatedTask.C(x=1).submit(run_mode=RunMode.DRY_RUN)
|
|
235
246
|
|
|
236
247
|
assert (
|
|
237
248
|
task_new.stdout() != task_old.stdout()
|
|
@@ -241,7 +252,7 @@ def test_tasks_deprecated():
|
|
|
241
252
|
), "Deprecated path should be the same as non deprecated"
|
|
242
253
|
|
|
243
254
|
# OK, now check that automatic linking is performed
|
|
244
|
-
task_old = OldTask(x=1).submit()
|
|
255
|
+
task_old = OldTask.C(x=1).submit()
|
|
245
256
|
task_old.wait()
|
|
246
257
|
task_old_path = task_old.stdout().parent
|
|
247
258
|
|
|
@@ -270,7 +281,7 @@ class HookedTask(Task):
|
|
|
270
281
|
|
|
271
282
|
|
|
272
283
|
def test_task_submit_hook():
|
|
273
|
-
result = HookedTask().submit(run_mode=RunMode.DRY_RUN)
|
|
284
|
+
result = HookedTask.C().submit(run_mode=RunMode.DRY_RUN)
|
|
274
285
|
assert (
|
|
275
286
|
result.__xpm__.task.__xpm__.job.environ.get("JAVA_HOME", None)
|
|
276
287
|
== "THE_JAVA_HOME"
|
|
@@ -299,31 +310,11 @@ class MyLightweightTask(Task):
|
|
|
299
310
|
assert self.x.data == 1
|
|
300
311
|
|
|
301
312
|
|
|
302
|
-
def test_task_lightweight():
|
|
303
|
-
with TemporaryExperiment("lightweight", maxwait=20):
|
|
304
|
-
x = LightweightConfig()
|
|
305
|
-
lwtask = LightweightTask(x=x)
|
|
306
|
-
assert (
|
|
307
|
-
MyLightweightTask(x=x).add_pretasks(lwtask).submit().__xpm__.job.wait()
|
|
308
|
-
== JobState.DONE
|
|
309
|
-
), "Pre-tasks should be executed"
|
|
310
|
-
|
|
311
|
-
x_2 = LightweightConfig()
|
|
312
|
-
lwtask_2 = LightweightTask(x=x)
|
|
313
|
-
assert (
|
|
314
|
-
MyLightweightTask(x=x_2.add_pretasks(lwtask_2))
|
|
315
|
-
.add_pretasks(lwtask_2)
|
|
316
|
-
.submit()
|
|
317
|
-
.__xpm__.job.wait()
|
|
318
|
-
== JobState.DONE
|
|
319
|
-
), "Pre-tasks should be run just once"
|
|
320
|
-
|
|
321
|
-
|
|
322
313
|
def test_task_lightweight_init():
|
|
323
314
|
with TemporaryExperiment("lightweight_init", maxwait=20):
|
|
324
|
-
x = LightweightConfig()
|
|
325
|
-
lwtask = LightweightTask(x=x)
|
|
315
|
+
x = LightweightConfig.C()
|
|
316
|
+
lwtask = LightweightTask.C(x=x)
|
|
326
317
|
assert (
|
|
327
|
-
MyLightweightTask(x=x).submit(init_tasks=[lwtask]).__xpm__.job.wait()
|
|
318
|
+
MyLightweightTask.C(x=x).submit(init_tasks=[lwtask]).__xpm__.job.wait()
|
|
328
319
|
== JobState.DONE
|
|
329
320
|
), "Init tasks should be executed"
|
|
@@ -7,7 +7,7 @@ import time
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
9
|
import subprocess
|
|
10
|
-
from experimaestro import Task,
|
|
10
|
+
from experimaestro import Task, Param
|
|
11
11
|
from experimaestro.tokens import CounterToken, TokenFile
|
|
12
12
|
from experimaestro.scheduler import JobState
|
|
13
13
|
from .utils import (
|
|
@@ -32,7 +32,7 @@ def token_experiment(xp, token, ntasks=3):
|
|
|
32
32
|
|
|
33
33
|
tasks = []
|
|
34
34
|
for it in range(ntasks):
|
|
35
|
-
task = TokenTask(path=path, x=it)
|
|
35
|
+
task = TokenTask.C(path=path, x=it)
|
|
36
36
|
if token:
|
|
37
37
|
task.add_dependencies(token.dependency(1))
|
|
38
38
|
tasks.append(task.submit())
|
|
@@ -74,8 +74,9 @@ def test_token_ok():
|
|
|
74
74
|
logging.info("Finished token_ok test")
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
@param("x", type=int)
|
|
78
77
|
class dummy_task(Task):
|
|
78
|
+
x: Param[int]
|
|
79
|
+
|
|
79
80
|
def execute(self):
|
|
80
81
|
pass
|
|
81
82
|
|
|
@@ -85,7 +86,7 @@ def test_token_cleanup():
|
|
|
85
86
|
with TemporaryExperiment("token_cleanup", maxwait=20) as xp:
|
|
86
87
|
token = CounterToken("token-cleanup", xp.workdir / "token-cleanup", 1)
|
|
87
88
|
|
|
88
|
-
task = dummy_task(x=1)
|
|
89
|
+
task = dummy_task.C(x=1)
|
|
89
90
|
dependency = token.dependency(1)
|
|
90
91
|
task.add_dependencies(dependency)
|
|
91
92
|
# Just to create the directory
|
|
@@ -97,7 +98,7 @@ def test_token_cleanup():
|
|
|
97
98
|
# The absence of process should be detected right away
|
|
98
99
|
logging.info("Lock without process")
|
|
99
100
|
TokenFile.create(dependency)
|
|
100
|
-
task2 = dummy_task(x=2)
|
|
101
|
+
task2 = dummy_task.C(x=2)
|
|
101
102
|
task2.add_dependencies(token.dependency(1)).submit()
|
|
102
103
|
xp.wait()
|
|
103
104
|
|
|
@@ -117,7 +118,7 @@ def test_token_cleanup():
|
|
|
117
118
|
p1 = subprocess.Popen(command)
|
|
118
119
|
job.pidpath.write_text(json.dumps({"pid": p1.pid, "type": "local"}))
|
|
119
120
|
|
|
120
|
-
task3 = dummy_task(x=3)
|
|
121
|
+
task3 = dummy_task.C(x=3)
|
|
121
122
|
task3.add_dependencies(token.dependency(1)).submit()
|
|
122
123
|
|
|
123
124
|
# Ends the script "waitforfile.py"
|
|
@@ -135,7 +136,9 @@ def test_token_monitor():
|
|
|
135
136
|
|
|
136
137
|
def run(xp, x, path):
|
|
137
138
|
token = xp.workspace.connector.createtoken("test-token-monitor", 1)
|
|
138
|
-
task =
|
|
139
|
+
task = (
|
|
140
|
+
TokenTask.C(path=path, x=x).add_dependencies(token.dependency(1)).submit()
|
|
141
|
+
)
|
|
139
142
|
return task
|
|
140
143
|
|
|
141
144
|
with TemporaryExperiment("tokens1", maxwait=20, port=0) as xp1, TemporaryExperiment(
|
|
@@ -240,7 +243,7 @@ def test_token_process():
|
|
|
240
243
|
|
|
241
244
|
def restart_function(xp):
|
|
242
245
|
token = CounterToken("restart-token", xp.workdir / "token", 1)
|
|
243
|
-
token(1, restart.Restart()).submit()
|
|
246
|
+
token(1, restart.Restart.C()).submit()
|
|
244
247
|
|
|
245
248
|
|
|
246
249
|
@pytest.mark.parametrize("terminate", restart.TERMINATES_FUNC)
|
|
@@ -1,62 +1,65 @@
|
|
|
1
1
|
# --- Task and types definitions
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from experimaestro import Config,
|
|
5
|
-
from
|
|
4
|
+
from experimaestro import Config, Param
|
|
5
|
+
from typing import Union
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
from experimaestro.
|
|
7
|
+
import pytest
|
|
8
|
+
from experimaestro.core.objects import ConfigMixin
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def test_multiple_inheritance():
|
|
12
|
-
|
|
13
|
-
class A:
|
|
12
|
+
class A(Config):
|
|
14
13
|
pass
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
class B:
|
|
15
|
+
class B(Config):
|
|
18
16
|
pass
|
|
19
17
|
|
|
20
|
-
@config()
|
|
21
18
|
class B1(B):
|
|
22
19
|
pass
|
|
23
20
|
|
|
24
|
-
@config()
|
|
25
21
|
class C1(B1, A):
|
|
26
22
|
pass
|
|
27
23
|
|
|
28
|
-
@config()
|
|
29
24
|
class C2(A, B1):
|
|
30
25
|
pass
|
|
31
26
|
|
|
32
27
|
for C in (C1, C2):
|
|
33
28
|
logging.info("Testing %s", C)
|
|
34
|
-
ctype = C.
|
|
29
|
+
ctype = C.__getxpmtype__()
|
|
35
30
|
assert issubclass(C, A)
|
|
36
31
|
assert issubclass(C, B)
|
|
37
32
|
assert issubclass(C, B1)
|
|
38
33
|
|
|
39
|
-
assert ctype.
|
|
34
|
+
assert ctype.value_type == C.__getxpmtype__().value_type
|
|
40
35
|
|
|
41
|
-
assert issubclass(C.
|
|
42
|
-
assert issubclass(C.
|
|
43
|
-
assert issubclass(C.
|
|
44
|
-
assert not issubclass(C.
|
|
36
|
+
assert issubclass(C.__getxpmtype__().value_type, B1.__getxpmtype__().value_type)
|
|
37
|
+
assert issubclass(C.__getxpmtype__().value_type, B.__getxpmtype__().value_type)
|
|
38
|
+
assert issubclass(C.__getxpmtype__().value_type, A.__getxpmtype__().value_type)
|
|
39
|
+
assert not issubclass(C.__getxpmtype__().value_type, ConfigMixin)
|
|
45
40
|
|
|
46
41
|
|
|
47
42
|
def test_missing_hierarchy():
|
|
48
|
-
|
|
49
|
-
class A:
|
|
43
|
+
class A(Config):
|
|
50
44
|
pass
|
|
51
45
|
|
|
52
46
|
class A1(A):
|
|
53
47
|
pass
|
|
54
48
|
|
|
55
|
-
@config()
|
|
56
49
|
class B(A1):
|
|
57
50
|
pass
|
|
58
51
|
|
|
59
|
-
B.
|
|
52
|
+
B.__getxpmtype__()
|
|
60
53
|
|
|
61
54
|
assert issubclass(B, A)
|
|
62
55
|
assert issubclass(B, A1)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_types_union():
|
|
59
|
+
class A(Config):
|
|
60
|
+
x: Param[Union[int, str]]
|
|
61
|
+
|
|
62
|
+
A.C(x=1)
|
|
63
|
+
A.C(x="hello")
|
|
64
|
+
with pytest.raises(ValueError):
|
|
65
|
+
A.C(x=[])
|
|
@@ -2,18 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from experimaestro import
|
|
6
|
-
config,
|
|
7
|
-
Task,
|
|
8
|
-
Identifier,
|
|
9
|
-
argument,
|
|
10
|
-
pathoption,
|
|
11
|
-
ConstantParam,
|
|
12
|
-
Param,
|
|
13
|
-
Config,
|
|
14
|
-
)
|
|
5
|
+
from experimaestro import Task, field, Identifier, Constant, Param, Config, Meta
|
|
15
6
|
from enum import Enum
|
|
16
|
-
|
|
7
|
+
from experimaestro.generators import PathGenerator
|
|
17
8
|
from experimaestro.scheduler import Job, JobContext
|
|
18
9
|
from experimaestro.scheduler.workspace import RunMode
|
|
19
10
|
from .utils import TemporaryExperiment
|
|
@@ -31,92 +22,81 @@ def expect_notvalidate(value):
|
|
|
31
22
|
value.__xpm__.validate()
|
|
32
23
|
|
|
33
24
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class A:
|
|
37
|
-
pass
|
|
25
|
+
class A(Config):
|
|
26
|
+
value: Param[int]
|
|
38
27
|
|
|
39
28
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class B:
|
|
43
|
-
pass
|
|
29
|
+
class B(Config):
|
|
30
|
+
a: Param[A]
|
|
44
31
|
|
|
45
32
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class C:
|
|
33
|
+
class C(Config):
|
|
34
|
+
path: Meta[Path] = field(default_factory=PathGenerator("outdir"))
|
|
49
35
|
pass
|
|
50
36
|
|
|
51
37
|
|
|
52
|
-
def
|
|
53
|
-
expect_validate(A(value=1))
|
|
38
|
+
def test_validation_simple():
|
|
39
|
+
expect_validate(A.C(value=1))
|
|
54
40
|
|
|
55
41
|
|
|
56
|
-
def
|
|
57
|
-
expect_notvalidate(A())
|
|
42
|
+
def test_validation_missing():
|
|
43
|
+
expect_notvalidate(A.C())
|
|
58
44
|
|
|
59
45
|
|
|
60
|
-
def
|
|
61
|
-
b = B()
|
|
62
|
-
b.a = A(value=1)
|
|
46
|
+
def test_validation_simple_nested():
|
|
47
|
+
b = B.C()
|
|
48
|
+
b.a = A.C(value=1)
|
|
63
49
|
expect_validate(b)
|
|
64
50
|
|
|
65
51
|
|
|
66
|
-
def
|
|
67
|
-
b = B()
|
|
68
|
-
b.a = A()
|
|
52
|
+
def test_validation_missing_nested():
|
|
53
|
+
b = B.C()
|
|
54
|
+
b.a = A.C()
|
|
69
55
|
expect_notvalidate(b)
|
|
70
56
|
|
|
71
57
|
|
|
72
|
-
def
|
|
73
|
-
|
|
74
|
-
|
|
58
|
+
def test_validation_type():
|
|
59
|
+
class A(Config):
|
|
60
|
+
__xpmid__ = valns.type.a
|
|
75
61
|
pass
|
|
76
62
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
pass
|
|
63
|
+
class B(Config):
|
|
64
|
+
__xpmid__ = valns.type.b
|
|
80
65
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
pass
|
|
66
|
+
class C(Config):
|
|
67
|
+
a: Param[A]
|
|
68
|
+
__xpmid__ = valns.type.c
|
|
85
69
|
|
|
86
70
|
with pytest.raises(ValueError):
|
|
87
|
-
C(a=B())
|
|
71
|
+
C.C(a=B.C())
|
|
88
72
|
|
|
89
73
|
with pytest.raises(ValueError):
|
|
90
|
-
c = C()
|
|
91
|
-
c.a = B()
|
|
74
|
+
c = C.C()
|
|
75
|
+
c.a = B.C()
|
|
92
76
|
|
|
93
77
|
|
|
94
|
-
def
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
pass
|
|
78
|
+
def test_validation_subtype():
|
|
79
|
+
class A(Config):
|
|
80
|
+
__xpmid__ = valns.subtype.a
|
|
98
81
|
|
|
99
|
-
@config(valns.subtype.a1)
|
|
100
82
|
class A1(A):
|
|
101
|
-
|
|
83
|
+
__xpmid__ = valns.subtype.a1
|
|
102
84
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
pass
|
|
85
|
+
class B(Config):
|
|
86
|
+
__xpmid__ = valns.subtype.b
|
|
87
|
+
a: Param[A]
|
|
107
88
|
|
|
108
|
-
expect_validate(B(a=A1()))
|
|
89
|
+
expect_validate(B.C(a=A1.C()))
|
|
109
90
|
|
|
110
91
|
|
|
111
|
-
def
|
|
112
|
-
"""Test of
|
|
92
|
+
def test_validation_path_generator():
|
|
93
|
+
"""Test of path generator"""
|
|
113
94
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
pass
|
|
95
|
+
class A(Config):
|
|
96
|
+
__xpmid__ = valns.path.a
|
|
97
|
+
value: Meta[Path] = field(default_factory=PathGenerator("file.txt"))
|
|
118
98
|
|
|
119
|
-
a = A()
|
|
99
|
+
a = A.C()
|
|
120
100
|
a.__xpm__.validate()
|
|
121
101
|
with TemporaryExperiment("constant") as xp:
|
|
122
102
|
jobcontext = Job(a)
|
|
@@ -129,15 +109,14 @@ def test_path():
|
|
|
129
109
|
assert a.value.parents[3] == xp.workspace.path
|
|
130
110
|
|
|
131
111
|
|
|
132
|
-
def
|
|
133
|
-
"""Test of
|
|
112
|
+
def test_validation_constant():
|
|
113
|
+
"""Test of constant"""
|
|
134
114
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
pass
|
|
115
|
+
class A(Config):
|
|
116
|
+
__xpmid__ = valns.constant.a
|
|
117
|
+
value: Constant[int] = 1
|
|
139
118
|
|
|
140
|
-
a = A()
|
|
119
|
+
a = A.C()
|
|
141
120
|
a.__xpm__.validate()
|
|
142
121
|
with TemporaryExperiment("constant"):
|
|
143
122
|
joba = Job(a)
|
|
@@ -145,69 +124,47 @@ def test_constant():
|
|
|
145
124
|
assert a.value == 1
|
|
146
125
|
|
|
147
126
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
class Parent:
|
|
151
|
-
pass
|
|
127
|
+
class Parent(Config):
|
|
128
|
+
x: Param[int]
|
|
152
129
|
|
|
153
130
|
|
|
154
|
-
@config()
|
|
155
131
|
class Child(Parent):
|
|
156
132
|
pass
|
|
157
133
|
|
|
158
134
|
|
|
159
|
-
def
|
|
160
|
-
expect_validate(Child(x=1))
|
|
135
|
+
def test_validation_child():
|
|
136
|
+
expect_validate(Child.C(x=1))
|
|
161
137
|
|
|
162
138
|
|
|
163
139
|
# --- Path argument checks
|
|
164
140
|
|
|
165
141
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
class PathParent:
|
|
169
|
-
pass
|
|
142
|
+
class PathParent(Config):
|
|
143
|
+
x: Meta[Path] = field(default_factory=PathGenerator("x"))
|
|
170
144
|
|
|
171
145
|
|
|
172
|
-
def
|
|
173
|
-
c = PathParent()
|
|
146
|
+
def test_validation_path_option():
|
|
147
|
+
c = PathParent.C()
|
|
174
148
|
expect_validate(c)
|
|
175
149
|
|
|
176
150
|
|
|
177
151
|
# --- Default value
|
|
178
152
|
|
|
179
153
|
|
|
180
|
-
|
|
181
|
-
"value,apitype",
|
|
182
|
-
[(1.5, types.FloatType), (1, types.IntType), (False, types.BoolType)],
|
|
183
|
-
)
|
|
184
|
-
def test_default(value, apitype):
|
|
185
|
-
@argument("default", default=value)
|
|
186
|
-
@config(valns.default[str(type(value))])
|
|
187
|
-
class Default:
|
|
188
|
-
pass
|
|
189
|
-
|
|
190
|
-
value = Default()
|
|
191
|
-
expect_validate(value)
|
|
192
|
-
assert Default.__xpmtype__.arguments["default"].type.__class__ == apitype
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def test_seal():
|
|
154
|
+
def test_validation_seal():
|
|
196
155
|
"""Test value sealing"""
|
|
197
156
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
class A:
|
|
201
|
-
pass
|
|
157
|
+
class A(Config):
|
|
158
|
+
a: Param[int]
|
|
202
159
|
|
|
203
|
-
a = A(a=2)
|
|
160
|
+
a = A.C(a=2)
|
|
204
161
|
a.__xpm__.seal(EmptyContext())
|
|
205
162
|
|
|
206
163
|
with pytest.raises(AttributeError):
|
|
207
164
|
a.a = 1
|
|
208
165
|
|
|
209
166
|
|
|
210
|
-
def
|
|
167
|
+
def test_validation_validation_enum():
|
|
211
168
|
"""Path arguments should be ignored"""
|
|
212
169
|
|
|
213
170
|
class EnumParam(Enum):
|
|
@@ -217,10 +174,10 @@ def test_validation_enum():
|
|
|
217
174
|
class EnumConfig(Config):
|
|
218
175
|
a: Param[EnumParam]
|
|
219
176
|
|
|
220
|
-
expect_validate(EnumConfig(a=EnumParam.FIRST))
|
|
177
|
+
expect_validate(EnumConfig.C(a=EnumParam.FIRST))
|
|
221
178
|
|
|
222
179
|
try:
|
|
223
|
-
EnumConfig(a=1)
|
|
180
|
+
EnumConfig.C(a=1)
|
|
224
181
|
assert False, "Enum value should be rejected"
|
|
225
182
|
except AssertionError:
|
|
226
183
|
pass
|
|
@@ -241,8 +198,8 @@ class TaskConfigConsumer(Config):
|
|
|
241
198
|
x: Param[TaskParentConfig]
|
|
242
199
|
|
|
243
200
|
|
|
244
|
-
def
|
|
245
|
-
x = taskconfig()
|
|
201
|
+
def test_validation_taskargument():
|
|
202
|
+
x = taskconfig.C()
|
|
246
203
|
with TemporaryExperiment("fake"):
|
|
247
204
|
x.submit(run_mode=RunMode.DRY_RUN)
|
|
248
|
-
expect_validate(TaskConfigConsumer(x=x))
|
|
205
|
+
expect_validate(TaskConfigConsumer.C(x=x))
|
|
@@ -31,7 +31,7 @@ if __name__ == "__main__":
|
|
|
31
31
|
logging.info("Reschedule with token [%s]: starting task in %s", x, workdir)
|
|
32
32
|
token = xp.workspace.connector.createtoken("test-token-reschedule", 1)
|
|
33
33
|
task = (
|
|
34
|
-
TokenTask(path=lockingpath, x=int(x))
|
|
34
|
+
TokenTask.C(path=lockingpath, x=int(x))
|
|
35
35
|
.add_dependencies(token.dependency(1))
|
|
36
36
|
.submit()
|
|
37
37
|
)
|