experimaestro 2.0.0b4__py3-none-any.whl → 2.0.0b17__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 (154) hide show
  1. experimaestro/__init__.py +12 -5
  2. experimaestro/cli/__init__.py +393 -134
  3. experimaestro/cli/filter.py +48 -23
  4. experimaestro/cli/jobs.py +253 -71
  5. experimaestro/cli/refactor.py +1 -2
  6. experimaestro/commandline.py +7 -4
  7. experimaestro/connectors/__init__.py +9 -1
  8. experimaestro/connectors/local.py +43 -3
  9. experimaestro/core/arguments.py +18 -18
  10. experimaestro/core/identifier.py +11 -11
  11. experimaestro/core/objects/config.py +96 -39
  12. experimaestro/core/objects/config_walk.py +3 -3
  13. experimaestro/core/{subparameters.py → partial.py} +16 -16
  14. experimaestro/core/partial_lock.py +394 -0
  15. experimaestro/core/types.py +12 -15
  16. experimaestro/dynamic.py +290 -0
  17. experimaestro/experiments/__init__.py +6 -2
  18. experimaestro/experiments/cli.py +223 -52
  19. experimaestro/experiments/configuration.py +24 -0
  20. experimaestro/generators.py +5 -5
  21. experimaestro/ipc.py +118 -1
  22. experimaestro/launcherfinder/__init__.py +2 -2
  23. experimaestro/launcherfinder/registry.py +6 -7
  24. experimaestro/launcherfinder/specs.py +2 -9
  25. experimaestro/launchers/slurm/__init__.py +2 -2
  26. experimaestro/launchers/slurm/base.py +62 -0
  27. experimaestro/locking.py +957 -1
  28. experimaestro/notifications.py +89 -201
  29. experimaestro/progress.py +63 -366
  30. experimaestro/rpyc.py +0 -2
  31. experimaestro/run.py +29 -2
  32. experimaestro/scheduler/__init__.py +8 -1
  33. experimaestro/scheduler/base.py +650 -53
  34. experimaestro/scheduler/dependencies.py +20 -16
  35. experimaestro/scheduler/experiment.py +764 -169
  36. experimaestro/scheduler/interfaces.py +338 -96
  37. experimaestro/scheduler/jobs.py +58 -20
  38. experimaestro/scheduler/remote/__init__.py +31 -0
  39. experimaestro/scheduler/remote/adaptive_sync.py +265 -0
  40. experimaestro/scheduler/remote/client.py +928 -0
  41. experimaestro/scheduler/remote/protocol.py +282 -0
  42. experimaestro/scheduler/remote/server.py +447 -0
  43. experimaestro/scheduler/remote/sync.py +144 -0
  44. experimaestro/scheduler/services.py +186 -35
  45. experimaestro/scheduler/state_provider.py +811 -2157
  46. experimaestro/scheduler/state_status.py +1247 -0
  47. experimaestro/scheduler/transient.py +31 -0
  48. experimaestro/scheduler/workspace.py +1 -1
  49. experimaestro/scheduler/workspace_state_provider.py +1273 -0
  50. experimaestro/scriptbuilder.py +4 -4
  51. experimaestro/settings.py +36 -0
  52. experimaestro/tests/conftest.py +33 -5
  53. experimaestro/tests/connectors/bin/executable.py +1 -1
  54. experimaestro/tests/fixtures/pre_experiment/experiment_check_env.py +16 -0
  55. experimaestro/tests/fixtures/pre_experiment/experiment_check_mock.py +14 -0
  56. experimaestro/tests/fixtures/pre_experiment/experiment_simple.py +12 -0
  57. experimaestro/tests/fixtures/pre_experiment/pre_setup_env.py +5 -0
  58. experimaestro/tests/fixtures/pre_experiment/pre_setup_error.py +3 -0
  59. experimaestro/tests/fixtures/pre_experiment/pre_setup_mock.py +8 -0
  60. experimaestro/tests/launchers/bin/test.py +1 -0
  61. experimaestro/tests/launchers/test_slurm.py +9 -9
  62. experimaestro/tests/partial_reschedule.py +46 -0
  63. experimaestro/tests/restart.py +3 -3
  64. experimaestro/tests/restart_main.py +1 -0
  65. experimaestro/tests/scripts/notifyandwait.py +1 -0
  66. experimaestro/tests/task_partial.py +38 -0
  67. experimaestro/tests/task_tokens.py +2 -2
  68. experimaestro/tests/tasks/test_dynamic.py +6 -6
  69. experimaestro/tests/test_dependencies.py +3 -3
  70. experimaestro/tests/test_deprecated.py +15 -15
  71. experimaestro/tests/test_dynamic_locking.py +317 -0
  72. experimaestro/tests/test_environment.py +24 -14
  73. experimaestro/tests/test_experiment.py +171 -36
  74. experimaestro/tests/test_identifier.py +25 -25
  75. experimaestro/tests/test_identifier_stability.py +3 -5
  76. experimaestro/tests/test_multitoken.py +2 -4
  77. experimaestro/tests/{test_subparameters.py → test_partial.py} +25 -25
  78. experimaestro/tests/test_partial_paths.py +81 -138
  79. experimaestro/tests/test_pre_experiment.py +219 -0
  80. experimaestro/tests/test_progress.py +2 -8
  81. experimaestro/tests/test_remote_state.py +1132 -0
  82. experimaestro/tests/test_stray_jobs.py +261 -0
  83. experimaestro/tests/test_tasks.py +1 -2
  84. experimaestro/tests/test_token_locking.py +52 -67
  85. experimaestro/tests/test_tokens.py +5 -6
  86. experimaestro/tests/test_transient.py +225 -0
  87. experimaestro/tests/test_workspace_state_provider.py +768 -0
  88. experimaestro/tests/token_reschedule.py +1 -3
  89. experimaestro/tests/utils.py +2 -7
  90. experimaestro/tokens.py +227 -372
  91. experimaestro/tools/diff.py +1 -0
  92. experimaestro/tools/documentation.py +4 -5
  93. experimaestro/tools/jobs.py +1 -2
  94. experimaestro/tui/app.py +459 -1895
  95. experimaestro/tui/app.tcss +162 -0
  96. experimaestro/tui/dialogs.py +172 -0
  97. experimaestro/tui/log_viewer.py +253 -3
  98. experimaestro/tui/messages.py +137 -0
  99. experimaestro/tui/utils.py +54 -0
  100. experimaestro/tui/widgets/__init__.py +23 -0
  101. experimaestro/tui/widgets/experiments.py +468 -0
  102. experimaestro/tui/widgets/global_services.py +238 -0
  103. experimaestro/tui/widgets/jobs.py +972 -0
  104. experimaestro/tui/widgets/log.py +156 -0
  105. experimaestro/tui/widgets/orphans.py +363 -0
  106. experimaestro/tui/widgets/runs.py +185 -0
  107. experimaestro/tui/widgets/services.py +314 -0
  108. experimaestro/tui/widgets/stray_jobs.py +528 -0
  109. experimaestro/utils/__init__.py +1 -1
  110. experimaestro/utils/environment.py +105 -22
  111. experimaestro/utils/fswatcher.py +124 -0
  112. experimaestro/utils/jobs.py +1 -2
  113. experimaestro/utils/jupyter.py +1 -2
  114. experimaestro/utils/logging.py +72 -0
  115. experimaestro/version.py +2 -2
  116. experimaestro/webui/__init__.py +9 -0
  117. experimaestro/webui/app.py +117 -0
  118. experimaestro/{server → webui}/data/index.css +66 -11
  119. experimaestro/webui/data/index.css.map +1 -0
  120. experimaestro/{server → webui}/data/index.js +82763 -87217
  121. experimaestro/webui/data/index.js.map +1 -0
  122. experimaestro/webui/routes/__init__.py +5 -0
  123. experimaestro/webui/routes/auth.py +53 -0
  124. experimaestro/webui/routes/proxy.py +117 -0
  125. experimaestro/webui/server.py +200 -0
  126. experimaestro/webui/state_bridge.py +152 -0
  127. experimaestro/webui/websocket.py +413 -0
  128. {experimaestro-2.0.0b4.dist-info → experimaestro-2.0.0b17.dist-info}/METADATA +8 -9
  129. experimaestro-2.0.0b17.dist-info/RECORD +219 -0
  130. experimaestro/cli/progress.py +0 -269
  131. experimaestro/scheduler/state.py +0 -75
  132. experimaestro/scheduler/state_db.py +0 -388
  133. experimaestro/scheduler/state_sync.py +0 -834
  134. experimaestro/server/__init__.py +0 -467
  135. experimaestro/server/data/index.css.map +0 -1
  136. experimaestro/server/data/index.js.map +0 -1
  137. experimaestro/tests/test_cli_jobs.py +0 -615
  138. experimaestro/tests/test_file_progress.py +0 -425
  139. experimaestro/tests/test_file_progress_integration.py +0 -477
  140. experimaestro/tests/test_state_db.py +0 -434
  141. experimaestro-2.0.0b4.dist-info/RECORD +0 -181
  142. /experimaestro/{server → webui}/data/1815e00441357e01619e.ttf +0 -0
  143. /experimaestro/{server → webui}/data/2463b90d9a316e4e5294.woff2 +0 -0
  144. /experimaestro/{server → webui}/data/2582b0e4bcf85eceead0.ttf +0 -0
  145. /experimaestro/{server → webui}/data/89999bdf5d835c012025.woff2 +0 -0
  146. /experimaestro/{server → webui}/data/914997e1bdfc990d0897.ttf +0 -0
  147. /experimaestro/{server → webui}/data/c210719e60948b211a12.woff2 +0 -0
  148. /experimaestro/{server → webui}/data/favicon.ico +0 -0
  149. /experimaestro/{server → webui}/data/index.html +0 -0
  150. /experimaestro/{server → webui}/data/login.html +0 -0
  151. /experimaestro/{server → webui}/data/manifest.json +0 -0
  152. {experimaestro-2.0.0b4.dist-info → experimaestro-2.0.0b17.dist-info}/WHEEL +0 -0
  153. {experimaestro-2.0.0b4.dist-info → experimaestro-2.0.0b17.dist-info}/entry_points.txt +0 -0
  154. {experimaestro-2.0.0b4.dist-info → experimaestro-2.0.0b17.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,219 @@
1
+ """Tests for pre_experiment feature in run-experiment CLI"""
2
+
3
+ import pytest
4
+ from pathlib import Path
5
+ from click.testing import CliRunner
6
+
7
+ from experimaestro.cli import cli
8
+
9
+
10
+ # --- Test fixture files as separate modules ---
11
+
12
+ # Pre-experiment scripts
13
+ PRE_SETUP_ENV = (
14
+ Path(__file__).parent / "fixtures" / "pre_experiment" / "pre_setup_env.py"
15
+ )
16
+ PRE_SETUP_MOCK = (
17
+ Path(__file__).parent / "fixtures" / "pre_experiment" / "pre_setup_mock.py"
18
+ )
19
+ PRE_SETUP_ERROR = (
20
+ Path(__file__).parent / "fixtures" / "pre_experiment" / "pre_setup_error.py"
21
+ )
22
+
23
+ # Experiment files
24
+ EXP_CHECK_ENV = (
25
+ Path(__file__).parent / "fixtures" / "pre_experiment" / "experiment_check_env.py"
26
+ )
27
+ EXP_CHECK_MOCK = (
28
+ Path(__file__).parent / "fixtures" / "pre_experiment" / "experiment_check_mock.py"
29
+ )
30
+ EXP_SIMPLE = (
31
+ Path(__file__).parent / "fixtures" / "pre_experiment" / "experiment_simple.py"
32
+ )
33
+
34
+
35
+ @pytest.fixture
36
+ def experiment_dir(tmp_path):
37
+ """Create a directory with experiment files"""
38
+ exp_dir = tmp_path / "experiment"
39
+ exp_dir.mkdir()
40
+
41
+ workdir = tmp_path / "workdir"
42
+ workdir.mkdir()
43
+
44
+ return exp_dir, workdir
45
+
46
+
47
+ def _create_yaml(exp_dir, experiment_id, file_path, pre_experiment_path=None):
48
+ """Helper to create a YAML config file"""
49
+ yaml_file = exp_dir / "config.yaml"
50
+ content = f"id: {experiment_id}\nfile: {file_path}\n"
51
+ if pre_experiment_path:
52
+ content += f"pre_experiment: {pre_experiment_path}\n"
53
+ yaml_file.write_text(content)
54
+ return yaml_file
55
+
56
+
57
+ def test_pre_experiment_sets_env_var(experiment_dir):
58
+ """Test that pre_experiment script can set environment variables"""
59
+ exp_dir, workdir = experiment_dir
60
+
61
+ # Copy fixture files
62
+ import shutil
63
+
64
+ shutil.copy(PRE_SETUP_ENV, exp_dir / "pre_setup.py")
65
+ shutil.copy(EXP_CHECK_ENV, exp_dir / "experiment.py")
66
+
67
+ yaml_file = _create_yaml(
68
+ exp_dir, "test-pre-experiment", "experiment", "pre_setup.py"
69
+ )
70
+
71
+ runner = CliRunner(env={"XPM_TEST_PRE_EXPERIMENT": ""})
72
+ result = runner.invoke(
73
+ cli,
74
+ [
75
+ "run-experiment",
76
+ "--workdir",
77
+ str(workdir),
78
+ "--run-mode",
79
+ "DRY_RUN",
80
+ str(yaml_file),
81
+ ],
82
+ )
83
+
84
+ assert "PRE_EXPERIMENT_TEST_PASSED" in result.output, (
85
+ f"Pre-experiment did not execute correctly. Output: {result.output}"
86
+ )
87
+ assert result.exit_code == 0, f"CLI failed with: {result.output}"
88
+
89
+
90
+ def test_pre_experiment_file_not_found(experiment_dir):
91
+ """Test error handling when pre_experiment file doesn't exist"""
92
+ exp_dir, workdir = experiment_dir
93
+
94
+ import shutil
95
+
96
+ shutil.copy(EXP_SIMPLE, exp_dir / "experiment.py")
97
+
98
+ yaml_file = _create_yaml(
99
+ exp_dir, "test-pre-experiment-missing", "experiment", "nonexistent.py"
100
+ )
101
+
102
+ runner = CliRunner()
103
+ result = runner.invoke(
104
+ cli,
105
+ [
106
+ "run-experiment",
107
+ "--workdir",
108
+ str(workdir),
109
+ "--run-mode",
110
+ "DRY_RUN",
111
+ str(yaml_file),
112
+ ],
113
+ )
114
+
115
+ assert result.exit_code != 0, "Should fail when pre_experiment file doesn't exist"
116
+ assert "not found" in result.output.lower(), (
117
+ f"Should mention file not found. Output: {result.output}"
118
+ )
119
+
120
+
121
+ def test_pre_experiment_execution_error(experiment_dir):
122
+ """Test error handling when pre_experiment script has an error"""
123
+ exp_dir, workdir = experiment_dir
124
+
125
+ import shutil
126
+
127
+ shutil.copy(PRE_SETUP_ERROR, exp_dir / "pre_setup.py")
128
+ shutil.copy(EXP_SIMPLE, exp_dir / "experiment.py")
129
+
130
+ yaml_file = _create_yaml(
131
+ exp_dir, "test-pre-experiment-error", "experiment", "pre_setup.py"
132
+ )
133
+
134
+ runner = CliRunner()
135
+ result = runner.invoke(
136
+ cli,
137
+ [
138
+ "run-experiment",
139
+ "--workdir",
140
+ str(workdir),
141
+ "--run-mode",
142
+ "DRY_RUN",
143
+ str(yaml_file),
144
+ ],
145
+ )
146
+
147
+ assert result.exit_code != 0, "Should fail when pre_experiment has an error"
148
+ assert (
149
+ "failed to execute" in result.output.lower()
150
+ or "intentional error" in result.output.lower()
151
+ ), f"Should show execution error. Output: {result.output}"
152
+
153
+
154
+ def test_pre_experiment_relative_path(experiment_dir):
155
+ """Test that pre_experiment relative paths are resolved from YAML file location"""
156
+ exp_dir, workdir = experiment_dir
157
+
158
+ # Create a subdirectory for the pre_experiment script
159
+ setup_dir = exp_dir / "setup"
160
+ setup_dir.mkdir()
161
+
162
+ import shutil
163
+
164
+ shutil.copy(PRE_SETUP_ENV, setup_dir / "init.py")
165
+ shutil.copy(EXP_CHECK_ENV, exp_dir / "experiment.py")
166
+
167
+ yaml_file = _create_yaml(
168
+ exp_dir, "test-pre-experiment-relative", "experiment", "setup/init.py"
169
+ )
170
+
171
+ runner = CliRunner(env={"XPM_TEST_PRE_EXPERIMENT": ""})
172
+ result = runner.invoke(
173
+ cli,
174
+ [
175
+ "run-experiment",
176
+ "--workdir",
177
+ str(workdir),
178
+ "--run-mode",
179
+ "DRY_RUN",
180
+ str(yaml_file),
181
+ ],
182
+ )
183
+
184
+ assert "PRE_EXPERIMENT_TEST_PASSED" in result.output, (
185
+ f"Relative path resolution failed. Output: {result.output}"
186
+ )
187
+ assert result.exit_code == 0, f"CLI failed with: {result.output}"
188
+
189
+
190
+ def test_pre_experiment_mocking_modules(experiment_dir):
191
+ """Test that pre_experiment can mock modules before experiment import"""
192
+ exp_dir, workdir = experiment_dir
193
+
194
+ import shutil
195
+
196
+ shutil.copy(PRE_SETUP_MOCK, exp_dir / "pre_setup.py")
197
+ shutil.copy(EXP_CHECK_MOCK, exp_dir / "experiment.py")
198
+
199
+ yaml_file = _create_yaml(
200
+ exp_dir, "test-pre-experiment-mock", "experiment", "pre_setup.py"
201
+ )
202
+
203
+ runner = CliRunner()
204
+ result = runner.invoke(
205
+ cli,
206
+ [
207
+ "run-experiment",
208
+ "--workdir",
209
+ str(workdir),
210
+ "--run-mode",
211
+ "DRY_RUN",
212
+ str(yaml_file),
213
+ ],
214
+ )
215
+
216
+ assert "MOCK_MODULE_TEST_PASSED" in result.output, (
217
+ f"Module mocking failed. Output: {result.output}"
218
+ )
219
+ assert result.exit_code == 0, f"CLI failed with: {result.output}"
@@ -65,10 +65,7 @@ class ProgressListener(Listener):
65
65
 
66
66
  def test_progress_basic():
67
67
  """Test that we get all the progress reports"""
68
- with TemporaryExperiment("progress-basic", maxwait=5, port=0) as xp:
69
- assert xp.server is not None
70
- assert xp.server.port > 0
71
-
68
+ with TemporaryExperiment("progress-basic", maxwait=5) as xp:
72
69
  listener = ProgressListener()
73
70
  xp.scheduler.addlistener(listener)
74
71
 
@@ -162,10 +159,7 @@ def check_nested(
162
159
 
163
160
  def test_progress_nested():
164
161
  """Test that we get all the progress reports"""
165
- with TemporaryExperiment("progress-nested", maxwait=20, port=0) as xp:
166
- assert xp.server is not None
167
- assert xp.server.port > 0
168
-
162
+ with TemporaryExperiment("progress-nested", maxwait=20) as xp:
169
163
  listener = ProgressListener()
170
164
  xp.scheduler.addlistener(listener)
171
165