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.

Files changed (118) hide show
  1. experimaestro/__init__.py +14 -4
  2. experimaestro/__main__.py +3 -423
  3. experimaestro/annotations.py +14 -4
  4. experimaestro/cli/__init__.py +311 -0
  5. experimaestro/{filter.py → cli/filter.py} +23 -9
  6. experimaestro/cli/jobs.py +268 -0
  7. experimaestro/cli/progress.py +269 -0
  8. experimaestro/click.py +0 -35
  9. experimaestro/commandline.py +3 -7
  10. experimaestro/connectors/__init__.py +29 -14
  11. experimaestro/connectors/local.py +19 -10
  12. experimaestro/connectors/ssh.py +27 -8
  13. experimaestro/core/arguments.py +45 -3
  14. experimaestro/core/callbacks.py +52 -0
  15. experimaestro/core/context.py +8 -9
  16. experimaestro/core/identifier.py +310 -0
  17. experimaestro/core/objects/__init__.py +44 -0
  18. experimaestro/core/{objects.py → objects/config.py} +399 -772
  19. experimaestro/core/objects/config_utils.py +58 -0
  20. experimaestro/core/objects/config_walk.py +151 -0
  21. experimaestro/core/objects.pyi +15 -45
  22. experimaestro/core/serialization.py +63 -9
  23. experimaestro/core/serializers.py +1 -8
  24. experimaestro/core/types.py +104 -66
  25. experimaestro/experiments/cli.py +154 -72
  26. experimaestro/experiments/configuration.py +10 -1
  27. experimaestro/generators.py +6 -1
  28. experimaestro/ipc.py +4 -1
  29. experimaestro/launcherfinder/__init__.py +1 -1
  30. experimaestro/launcherfinder/base.py +2 -18
  31. experimaestro/launcherfinder/parser.py +8 -3
  32. experimaestro/launcherfinder/registry.py +52 -140
  33. experimaestro/launcherfinder/specs.py +49 -10
  34. experimaestro/launchers/direct.py +0 -47
  35. experimaestro/launchers/slurm/base.py +54 -14
  36. experimaestro/mkdocs/__init__.py +1 -1
  37. experimaestro/mkdocs/base.py +6 -8
  38. experimaestro/notifications.py +38 -12
  39. experimaestro/progress.py +406 -0
  40. experimaestro/run.py +24 -3
  41. experimaestro/scheduler/__init__.py +18 -1
  42. experimaestro/scheduler/base.py +108 -808
  43. experimaestro/scheduler/dynamic_outputs.py +184 -0
  44. experimaestro/scheduler/experiment.py +387 -0
  45. experimaestro/scheduler/jobs.py +475 -0
  46. experimaestro/scheduler/signal_handler.py +32 -0
  47. experimaestro/scheduler/state.py +75 -0
  48. experimaestro/scheduler/workspace.py +27 -8
  49. experimaestro/scriptbuilder.py +18 -3
  50. experimaestro/server/__init__.py +36 -5
  51. experimaestro/server/data/1815e00441357e01619e.ttf +0 -0
  52. experimaestro/server/data/2463b90d9a316e4e5294.woff2 +0 -0
  53. experimaestro/server/data/2582b0e4bcf85eceead0.ttf +0 -0
  54. experimaestro/server/data/89999bdf5d835c012025.woff2 +0 -0
  55. experimaestro/server/data/914997e1bdfc990d0897.ttf +0 -0
  56. experimaestro/server/data/c210719e60948b211a12.woff2 +0 -0
  57. experimaestro/server/data/index.css +5187 -5068
  58. experimaestro/server/data/index.css.map +1 -1
  59. experimaestro/server/data/index.js +68887 -68064
  60. experimaestro/server/data/index.js.map +1 -1
  61. experimaestro/settings.py +45 -5
  62. experimaestro/sphinx/__init__.py +7 -17
  63. experimaestro/taskglobals.py +7 -2
  64. experimaestro/tests/core/__init__.py +0 -0
  65. experimaestro/tests/core/test_generics.py +206 -0
  66. experimaestro/tests/definitions_types.py +5 -3
  67. experimaestro/tests/launchers/bin/sbatch +34 -7
  68. experimaestro/tests/launchers/bin/srun +5 -0
  69. experimaestro/tests/launchers/common.py +17 -5
  70. experimaestro/tests/launchers/config_slurm/launchers.py +25 -0
  71. experimaestro/tests/restart.py +10 -5
  72. experimaestro/tests/tasks/all.py +23 -10
  73. experimaestro/tests/tasks/foreign.py +2 -4
  74. experimaestro/tests/test_checkers.py +2 -2
  75. experimaestro/tests/test_dependencies.py +11 -17
  76. experimaestro/tests/test_experiment.py +73 -0
  77. experimaestro/tests/test_file_progress.py +425 -0
  78. experimaestro/tests/test_file_progress_integration.py +477 -0
  79. experimaestro/tests/test_findlauncher.py +12 -5
  80. experimaestro/tests/test_forward.py +5 -5
  81. experimaestro/tests/test_generators.py +93 -0
  82. experimaestro/tests/test_identifier.py +182 -158
  83. experimaestro/tests/test_instance.py +19 -27
  84. experimaestro/tests/test_objects.py +13 -20
  85. experimaestro/tests/test_outputs.py +6 -6
  86. experimaestro/tests/test_param.py +68 -30
  87. experimaestro/tests/test_progress.py +4 -4
  88. experimaestro/tests/test_serializers.py +24 -64
  89. experimaestro/tests/test_ssh.py +7 -0
  90. experimaestro/tests/test_tags.py +50 -21
  91. experimaestro/tests/test_tasks.py +42 -51
  92. experimaestro/tests/test_tokens.py +11 -8
  93. experimaestro/tests/test_types.py +24 -21
  94. experimaestro/tests/test_validation.py +67 -110
  95. experimaestro/tests/token_reschedule.py +1 -1
  96. experimaestro/tokens.py +24 -13
  97. experimaestro/tools/diff.py +8 -1
  98. experimaestro/typingutils.py +20 -11
  99. experimaestro/utils/asyncio.py +6 -2
  100. experimaestro/utils/multiprocessing.py +44 -0
  101. experimaestro/utils/resources.py +11 -3
  102. {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/METADATA +28 -36
  103. experimaestro-2.0.0a8.dist-info/RECORD +166 -0
  104. {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/WHEEL +1 -1
  105. {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/entry_points.txt +0 -4
  106. experimaestro/launchers/slurm/cli.py +0 -29
  107. experimaestro/launchers/slurm/configuration.py +0 -597
  108. experimaestro/scheduler/environment.py +0 -94
  109. experimaestro/server/data/016b4a6cdced82ab3aa1.ttf +0 -0
  110. experimaestro/server/data/50701fbb8177c2dde530.ttf +0 -0
  111. experimaestro/server/data/878f31251d960bd6266f.woff2 +0 -0
  112. experimaestro/server/data/b041b1fa4fe241b23445.woff2 +0 -0
  113. experimaestro/server/data/b6879d41b0852f01ed5b.woff2 +0 -0
  114. experimaestro/server/data/d75e3fd1eb12e9bd6655.ttf +0 -0
  115. experimaestro/tests/launchers/config_slurm/launchers.yaml +0 -134
  116. experimaestro/utils/yaml.py +0 -202
  117. experimaestro-1.5.1.dist-info/RECORD +0 -148
  118. {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
- task1 = SimpleTask(x=1).submit()
127
- task2 = SimpleTask(x=1).submit()
128
- assert task1 is task2, f"{id(task1)} != {id(task2)}"
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(run_mode=RunMode.DRY_RUN)
178
- task_old = TaskWithDeprecated(p=OldConfig()).submit(run_mode=RunMode.DRY_RUN)
179
- task_deprecated = TaskWithDeprecated(p=DeprecatedConfig()).submit(
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, param
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 = TokenTask(path=path, x=x).add_dependencies(token.dependency(1)).submit()
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, config
5
- from experimaestro.core.objects import TypeConfig
4
+ from experimaestro import Config, Param
5
+ from typing import Union
6
6
 
7
- from .utils import TemporaryExperiment
8
- from experimaestro.scheduler import JobState
7
+ import pytest
8
+ from experimaestro.core.objects import ConfigMixin
9
9
 
10
10
 
11
11
  def test_multiple_inheritance():
12
- @config()
13
- class A:
12
+ class A(Config):
14
13
  pass
15
14
 
16
- @config()
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.__xpmtype__
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.objecttype == C.__xpmtype__.objecttype
34
+ assert ctype.value_type == C.__getxpmtype__().value_type
40
35
 
41
- assert issubclass(C.__xpmtype__.objecttype, B1.__xpmtype__.basetype)
42
- assert issubclass(C.__xpmtype__.objecttype, B.__xpmtype__.basetype)
43
- assert issubclass(C.__xpmtype__.objecttype, A.__xpmtype__.basetype)
44
- assert not issubclass(C.__xpmtype__.objecttype, TypeConfig)
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
- @config()
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.__xpmtype__
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
- import experimaestro.core.types as types
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
- @argument("value", type=int)
35
- @config()
36
- class A:
37
- pass
25
+ class A(Config):
26
+ value: Param[int]
38
27
 
39
28
 
40
- @argument("a", type=A)
41
- @config()
42
- class B:
43
- pass
29
+ class B(Config):
30
+ a: Param[A]
44
31
 
45
32
 
46
- @pathoption("path", "outdir")
47
- @config()
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 test_simple():
53
- expect_validate(A(value=1))
38
+ def test_validation_simple():
39
+ expect_validate(A.C(value=1))
54
40
 
55
41
 
56
- def test_missing():
57
- expect_notvalidate(A())
42
+ def test_validation_missing():
43
+ expect_notvalidate(A.C())
58
44
 
59
45
 
60
- def test_simple_nested():
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 test_missing_nested():
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 test_type():
73
- @config(valns.type.a)
74
- class A:
58
+ def test_validation_type():
59
+ class A(Config):
60
+ __xpmid__ = valns.type.a
75
61
  pass
76
62
 
77
- @config(valns.type.b)
78
- class B:
79
- pass
63
+ class B(Config):
64
+ __xpmid__ = valns.type.b
80
65
 
81
- @argument("a", A)
82
- @config(valns.type.c)
83
- class C:
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 test_subtype():
95
- @config(valns.subtype.a)
96
- class A:
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
- pass
83
+ __xpmid__ = valns.subtype.a1
102
84
 
103
- @argument("a", A)
104
- @config(valns.subtype.b)
105
- class B:
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 test_path():
112
- """Test of @pathoption"""
92
+ def test_validation_path_generator():
93
+ """Test of path generator"""
113
94
 
114
- @pathoption("value", "file.txt")
115
- @config(valns.path.a)
116
- class A:
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 test_constant():
133
- """Test of @ConstantParam"""
112
+ def test_validation_constant():
113
+ """Test of constant"""
134
114
 
135
- @ConstantParam("value", 1)
136
- @config(valns.constant.a)
137
- class A:
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
- @argument("x", type=int)
149
- @config()
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 test_child():
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
- @pathoption("x", "x")
167
- @config()
168
- class PathParent:
169
- pass
142
+ class PathParent(Config):
143
+ x: Meta[Path] = field(default_factory=PathGenerator("x"))
170
144
 
171
145
 
172
- def test_path_option():
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
- @pytest.mark.parametrize(
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
- @argument("a", int)
199
- @config()
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 test_validation_enum():
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 test_taskargument():
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
  )