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.
- experimaestro/__init__.py +12 -5
- experimaestro/cli/__init__.py +393 -134
- experimaestro/cli/filter.py +48 -23
- experimaestro/cli/jobs.py +253 -71
- experimaestro/cli/refactor.py +1 -2
- experimaestro/commandline.py +7 -4
- experimaestro/connectors/__init__.py +9 -1
- experimaestro/connectors/local.py +43 -3
- experimaestro/core/arguments.py +18 -18
- experimaestro/core/identifier.py +11 -11
- experimaestro/core/objects/config.py +96 -39
- experimaestro/core/objects/config_walk.py +3 -3
- experimaestro/core/{subparameters.py → partial.py} +16 -16
- experimaestro/core/partial_lock.py +394 -0
- experimaestro/core/types.py +12 -15
- experimaestro/dynamic.py +290 -0
- experimaestro/experiments/__init__.py +6 -2
- experimaestro/experiments/cli.py +223 -52
- experimaestro/experiments/configuration.py +24 -0
- experimaestro/generators.py +5 -5
- experimaestro/ipc.py +118 -1
- experimaestro/launcherfinder/__init__.py +2 -2
- experimaestro/launcherfinder/registry.py +6 -7
- experimaestro/launcherfinder/specs.py +2 -9
- experimaestro/launchers/slurm/__init__.py +2 -2
- experimaestro/launchers/slurm/base.py +62 -0
- experimaestro/locking.py +957 -1
- experimaestro/notifications.py +89 -201
- experimaestro/progress.py +63 -366
- experimaestro/rpyc.py +0 -2
- experimaestro/run.py +29 -2
- experimaestro/scheduler/__init__.py +8 -1
- experimaestro/scheduler/base.py +650 -53
- experimaestro/scheduler/dependencies.py +20 -16
- experimaestro/scheduler/experiment.py +764 -169
- experimaestro/scheduler/interfaces.py +338 -96
- experimaestro/scheduler/jobs.py +58 -20
- experimaestro/scheduler/remote/__init__.py +31 -0
- experimaestro/scheduler/remote/adaptive_sync.py +265 -0
- experimaestro/scheduler/remote/client.py +928 -0
- experimaestro/scheduler/remote/protocol.py +282 -0
- experimaestro/scheduler/remote/server.py +447 -0
- experimaestro/scheduler/remote/sync.py +144 -0
- experimaestro/scheduler/services.py +186 -35
- experimaestro/scheduler/state_provider.py +811 -2157
- experimaestro/scheduler/state_status.py +1247 -0
- experimaestro/scheduler/transient.py +31 -0
- experimaestro/scheduler/workspace.py +1 -1
- experimaestro/scheduler/workspace_state_provider.py +1273 -0
- experimaestro/scriptbuilder.py +4 -4
- experimaestro/settings.py +36 -0
- experimaestro/tests/conftest.py +33 -5
- experimaestro/tests/connectors/bin/executable.py +1 -1
- experimaestro/tests/fixtures/pre_experiment/experiment_check_env.py +16 -0
- experimaestro/tests/fixtures/pre_experiment/experiment_check_mock.py +14 -0
- experimaestro/tests/fixtures/pre_experiment/experiment_simple.py +12 -0
- experimaestro/tests/fixtures/pre_experiment/pre_setup_env.py +5 -0
- experimaestro/tests/fixtures/pre_experiment/pre_setup_error.py +3 -0
- experimaestro/tests/fixtures/pre_experiment/pre_setup_mock.py +8 -0
- experimaestro/tests/launchers/bin/test.py +1 -0
- experimaestro/tests/launchers/test_slurm.py +9 -9
- experimaestro/tests/partial_reschedule.py +46 -0
- experimaestro/tests/restart.py +3 -3
- experimaestro/tests/restart_main.py +1 -0
- experimaestro/tests/scripts/notifyandwait.py +1 -0
- experimaestro/tests/task_partial.py +38 -0
- experimaestro/tests/task_tokens.py +2 -2
- experimaestro/tests/tasks/test_dynamic.py +6 -6
- experimaestro/tests/test_dependencies.py +3 -3
- experimaestro/tests/test_deprecated.py +15 -15
- experimaestro/tests/test_dynamic_locking.py +317 -0
- experimaestro/tests/test_environment.py +24 -14
- experimaestro/tests/test_experiment.py +171 -36
- experimaestro/tests/test_identifier.py +25 -25
- experimaestro/tests/test_identifier_stability.py +3 -5
- experimaestro/tests/test_multitoken.py +2 -4
- experimaestro/tests/{test_subparameters.py → test_partial.py} +25 -25
- experimaestro/tests/test_partial_paths.py +81 -138
- experimaestro/tests/test_pre_experiment.py +219 -0
- experimaestro/tests/test_progress.py +2 -8
- experimaestro/tests/test_remote_state.py +1132 -0
- experimaestro/tests/test_stray_jobs.py +261 -0
- experimaestro/tests/test_tasks.py +1 -2
- experimaestro/tests/test_token_locking.py +52 -67
- experimaestro/tests/test_tokens.py +5 -6
- experimaestro/tests/test_transient.py +225 -0
- experimaestro/tests/test_workspace_state_provider.py +768 -0
- experimaestro/tests/token_reschedule.py +1 -3
- experimaestro/tests/utils.py +2 -7
- experimaestro/tokens.py +227 -372
- experimaestro/tools/diff.py +1 -0
- experimaestro/tools/documentation.py +4 -5
- experimaestro/tools/jobs.py +1 -2
- experimaestro/tui/app.py +459 -1895
- experimaestro/tui/app.tcss +162 -0
- experimaestro/tui/dialogs.py +172 -0
- experimaestro/tui/log_viewer.py +253 -3
- experimaestro/tui/messages.py +137 -0
- experimaestro/tui/utils.py +54 -0
- experimaestro/tui/widgets/__init__.py +23 -0
- experimaestro/tui/widgets/experiments.py +468 -0
- experimaestro/tui/widgets/global_services.py +238 -0
- experimaestro/tui/widgets/jobs.py +972 -0
- experimaestro/tui/widgets/log.py +156 -0
- experimaestro/tui/widgets/orphans.py +363 -0
- experimaestro/tui/widgets/runs.py +185 -0
- experimaestro/tui/widgets/services.py +314 -0
- experimaestro/tui/widgets/stray_jobs.py +528 -0
- experimaestro/utils/__init__.py +1 -1
- experimaestro/utils/environment.py +105 -22
- experimaestro/utils/fswatcher.py +124 -0
- experimaestro/utils/jobs.py +1 -2
- experimaestro/utils/jupyter.py +1 -2
- experimaestro/utils/logging.py +72 -0
- experimaestro/version.py +2 -2
- experimaestro/webui/__init__.py +9 -0
- experimaestro/webui/app.py +117 -0
- experimaestro/{server → webui}/data/index.css +66 -11
- experimaestro/webui/data/index.css.map +1 -0
- experimaestro/{server → webui}/data/index.js +82763 -87217
- experimaestro/webui/data/index.js.map +1 -0
- experimaestro/webui/routes/__init__.py +5 -0
- experimaestro/webui/routes/auth.py +53 -0
- experimaestro/webui/routes/proxy.py +117 -0
- experimaestro/webui/server.py +200 -0
- experimaestro/webui/state_bridge.py +152 -0
- experimaestro/webui/websocket.py +413 -0
- {experimaestro-2.0.0b4.dist-info → experimaestro-2.0.0b17.dist-info}/METADATA +8 -9
- experimaestro-2.0.0b17.dist-info/RECORD +219 -0
- experimaestro/cli/progress.py +0 -269
- experimaestro/scheduler/state.py +0 -75
- experimaestro/scheduler/state_db.py +0 -388
- experimaestro/scheduler/state_sync.py +0 -834
- experimaestro/server/__init__.py +0 -467
- experimaestro/server/data/index.css.map +0 -1
- experimaestro/server/data/index.js.map +0 -1
- experimaestro/tests/test_cli_jobs.py +0 -615
- experimaestro/tests/test_file_progress.py +0 -425
- experimaestro/tests/test_file_progress_integration.py +0 -477
- experimaestro/tests/test_state_db.py +0 -434
- experimaestro-2.0.0b4.dist-info/RECORD +0 -181
- /experimaestro/{server → webui}/data/1815e00441357e01619e.ttf +0 -0
- /experimaestro/{server → webui}/data/2463b90d9a316e4e5294.woff2 +0 -0
- /experimaestro/{server → webui}/data/2582b0e4bcf85eceead0.ttf +0 -0
- /experimaestro/{server → webui}/data/89999bdf5d835c012025.woff2 +0 -0
- /experimaestro/{server → webui}/data/914997e1bdfc990d0897.ttf +0 -0
- /experimaestro/{server → webui}/data/c210719e60948b211a12.woff2 +0 -0
- /experimaestro/{server → webui}/data/favicon.ico +0 -0
- /experimaestro/{server → webui}/data/index.html +0 -0
- /experimaestro/{server → webui}/data/login.html +0 -0
- /experimaestro/{server → webui}/data/manifest.json +0 -0
- {experimaestro-2.0.0b4.dist-info → experimaestro-2.0.0b17.dist-info}/WHEEL +0 -0
- {experimaestro-2.0.0b4.dist-info → experimaestro-2.0.0b17.dist-info}/entry_points.txt +0 -0
- {experimaestro-2.0.0b4.dist-info → experimaestro-2.0.0b17.dist-info}/licenses/LICENSE +0 -0
experimaestro/notifications.py
CHANGED
|
@@ -1,258 +1,143 @@
|
|
|
1
|
-
|
|
1
|
+
"""File-based progress notification system for experimaestro tasks.
|
|
2
|
+
|
|
3
|
+
Progress is reported by writing to job event files, which are then read
|
|
4
|
+
by monitors (TUI, web UI) via file watching.
|
|
5
|
+
"""
|
|
6
|
+
|
|
2
7
|
from dataclasses import dataclass
|
|
3
8
|
from pathlib import Path
|
|
4
9
|
from typing import ClassVar, Dict, Iterator, Optional, TypeVar, overload
|
|
5
|
-
import os.path
|
|
6
|
-
from urllib.request import urlopen
|
|
7
|
-
from urllib.error import HTTPError, URLError
|
|
8
|
-
import threading
|
|
9
10
|
import sys
|
|
10
|
-
import socket
|
|
11
11
|
from tqdm.auto import tqdm as std_tqdm
|
|
12
12
|
|
|
13
13
|
from .utils import logger
|
|
14
14
|
from experimaestro.taskglobals import Env as TaskEnv
|
|
15
15
|
from .progress import FileBasedProgressReporter
|
|
16
16
|
|
|
17
|
-
# --- Progress and other notifications
|
|
18
|
-
|
|
19
17
|
T = TypeVar("T")
|
|
20
18
|
|
|
21
19
|
|
|
22
20
|
@dataclass
|
|
23
21
|
class LevelInformation:
|
|
22
|
+
"""Progress information for a single nesting level"""
|
|
23
|
+
|
|
24
24
|
level: int
|
|
25
25
|
desc: Optional[str]
|
|
26
26
|
progress: float
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
def to_dict(self) -> Dict:
|
|
29
|
+
"""Convert to a dictionary for JSON serialization."""
|
|
30
|
+
return {
|
|
31
|
+
"level": self.level,
|
|
32
|
+
"desc": self.desc,
|
|
33
|
+
"progress": self.progress,
|
|
34
|
+
}
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
) or (self.previous_desc != self.desc)
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_dict(cls, d: Dict) -> "LevelInformation":
|
|
38
|
+
"""Create LevelInformation from a dictionary (e.g., from JSON).
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return
|
|
40
|
+
Args:
|
|
41
|
+
d: Dictionary with keys 'level', 'progress', and optionally 'desc'
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
LevelInformation instance
|
|
45
|
+
"""
|
|
46
|
+
return cls(
|
|
47
|
+
level=d.get("level", 0),
|
|
48
|
+
desc=d.get("desc"),
|
|
49
|
+
progress=d.get("progress", 0),
|
|
50
|
+
)
|
|
43
51
|
|
|
44
52
|
def __repr__(self) -> str:
|
|
45
53
|
return f"[{self.level}] {self.desc} {int(self.progress * 1000) / 10}%"
|
|
46
54
|
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
self.url = url
|
|
51
|
-
self.error_count = 0
|
|
56
|
+
# Type alias for progress information
|
|
57
|
+
ProgressInformation = list[LevelInformation]
|
|
52
58
|
|
|
53
59
|
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
def get_progress_information_from_dict(dicts: list[dict]) -> ProgressInformation:
|
|
61
|
+
"""Convert a list of progress dicts to ProgressInformation.
|
|
56
62
|
|
|
57
|
-
|
|
58
|
-
"""Whether to output to the console if no notification server is up"""
|
|
63
|
+
Handles both dict and LevelInformation items for robustness.
|
|
59
64
|
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
Args:
|
|
66
|
+
dicts: List of dictionaries with 'level', 'progress', 'desc' keys
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
self.path = path / Reporter.NOTIFICATION_FOLDER
|
|
68
|
-
self.path.mkdir(exist_ok=True)
|
|
69
|
-
self.urls: Dict[str, ListenerInformation] = {}
|
|
68
|
+
Returns:
|
|
69
|
+
List of LevelInformation instances (ProgressInformation)
|
|
70
|
+
"""
|
|
71
|
+
return [LevelInformation.from_dict(p) if isinstance(p, dict) else p for p in dicts]
|
|
70
72
|
|
|
71
|
-
# Last check of notification URLs
|
|
72
|
-
self.lastcheck = 0
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
class Reporter:
|
|
75
|
+
"""File-based progress reporter for running tasks.
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
Progress events are written to job event files at:
|
|
78
|
+
.events/jobs/{task_id}/event-{job_id}-*.jsonl
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
These files are watched by monitors (TUI, web UI) to display progress.
|
|
81
|
+
"""
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
def __init__(self, path: Path):
|
|
84
|
+
"""Initialize the file-based reporter.
|
|
82
85
|
|
|
83
|
-
|
|
86
|
+
Args:
|
|
87
|
+
path: The task path ({workspace}/jobs/{task_id}/{job_id}/)
|
|
88
|
+
"""
|
|
89
|
+
self.path = path
|
|
84
90
|
self.file_reporter = FileBasedProgressReporter(task_path=path)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
self.stopping = True
|
|
88
|
-
with self.cv:
|
|
89
|
-
# self.cv.notifyAll()
|
|
90
|
-
self.cv.notify_all()
|
|
91
|
-
|
|
92
|
-
@staticmethod
|
|
93
|
-
def isfatal_httperror(e: Exception, info: ListenerInformation) -> bool:
|
|
94
|
-
"""Returns True if this HTTP error indicates that the server won't recover"""
|
|
95
|
-
if isinstance(e, HTTPError):
|
|
96
|
-
if e.code >= 400 and e.code < 500:
|
|
97
|
-
return True
|
|
98
|
-
elif isinstance(e, URLError):
|
|
99
|
-
if isinstance(e.reason, ConnectionRefusedError):
|
|
100
|
-
return True
|
|
101
|
-
if isinstance(e.reason, socket.gaierror) and e.reason.errno == -2:
|
|
102
|
-
return True
|
|
103
|
-
if isinstance(e.reason, TimeoutError):
|
|
104
|
-
info.error_count += 1
|
|
105
|
-
|
|
106
|
-
# Too many errors
|
|
107
|
-
if info.error_count > 3:
|
|
108
|
-
logger.info("Too many errors with %s", info.error_count)
|
|
109
|
-
return True
|
|
110
|
-
|
|
111
|
-
return False
|
|
112
|
-
|
|
113
|
-
def modified(self):
|
|
114
|
-
return any(level.modified(self) for level in self.levels)
|
|
115
|
-
|
|
116
|
-
def check_urls(self):
|
|
117
|
-
"""Check whether we have new schedulers to notify"""
|
|
118
|
-
# Check if path exists (it might have been deleted during cleanup)
|
|
119
|
-
if not self.path.exists():
|
|
120
|
-
return
|
|
121
|
-
|
|
122
|
-
try:
|
|
123
|
-
mtime = os.path.getmtime(self.path)
|
|
124
|
-
except (OSError, FileNotFoundError):
|
|
125
|
-
# Path was deleted while we were checking
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
if mtime > self.lastcheck:
|
|
129
|
-
for f in self.path.iterdir():
|
|
130
|
-
self.urls[f.name] = ListenerInformation(f.read_text().strip())
|
|
131
|
-
logger.info("Added new notification URL: %s", self.urls[f.name].url)
|
|
132
|
-
f.unlink()
|
|
133
|
-
|
|
134
|
-
try:
|
|
135
|
-
self.lastcheck = os.path.getmtime(self.path)
|
|
136
|
-
except (OSError, FileNotFoundError):
|
|
137
|
-
# Path was deleted during iteration
|
|
138
|
-
return
|
|
139
|
-
|
|
140
|
-
def run(self):
|
|
141
|
-
logger.info("Running notification thread")
|
|
142
|
-
|
|
143
|
-
while True:
|
|
144
|
-
with self.cv:
|
|
145
|
-
self.cv.wait_for(lambda: self.stopping or self.modified())
|
|
146
|
-
if self.stopping:
|
|
147
|
-
break
|
|
148
|
-
|
|
149
|
-
# Notify (out of the CV locking)
|
|
150
|
-
toremove = []
|
|
151
|
-
|
|
152
|
-
# Check if new notification servers are on
|
|
153
|
-
self.check_urls()
|
|
154
|
-
|
|
155
|
-
if self.urls:
|
|
156
|
-
# OK, let's go
|
|
157
|
-
for level in self.levels:
|
|
158
|
-
if level.modified(self):
|
|
159
|
-
params = level.report()
|
|
160
|
-
|
|
161
|
-
# Go over all URLs
|
|
162
|
-
for key, info in self.urls.items():
|
|
163
|
-
baseurl = info.url
|
|
164
|
-
|
|
165
|
-
url = "{}/progress?{}".format(
|
|
166
|
-
baseurl, urllib.parse.urlencode(params)
|
|
167
|
-
)
|
|
168
|
-
logger.debug("Reporting progress %s", params)
|
|
169
|
-
try:
|
|
170
|
-
with urlopen(url) as _:
|
|
171
|
-
logger.debug(
|
|
172
|
-
"Notification send for %s [%s]",
|
|
173
|
-
baseurl,
|
|
174
|
-
level,
|
|
175
|
-
)
|
|
176
|
-
except Exception as e:
|
|
177
|
-
logger.warning(
|
|
178
|
-
"Progress: %s [error while notifying %s]: %s",
|
|
179
|
-
level,
|
|
180
|
-
url,
|
|
181
|
-
e,
|
|
182
|
-
)
|
|
183
|
-
if Reporter.isfatal_httperror(e, info):
|
|
184
|
-
toremove.append(key)
|
|
185
|
-
|
|
186
|
-
# Removes unvalid URLs
|
|
187
|
-
for key in toremove:
|
|
188
|
-
logger.info("Removing notification URL %s", self.urls[key])
|
|
189
|
-
del self.urls[key]
|
|
190
|
-
elif self.console:
|
|
191
|
-
for level in self.levels:
|
|
192
|
-
if level.modified(self):
|
|
193
|
-
params = level.report()
|
|
194
|
-
logger.info("Progress: %s", level)
|
|
91
|
+
self.levels: list[LevelInformation] = [LevelInformation(0, None, -1)]
|
|
92
|
+
self.console = False
|
|
195
93
|
|
|
196
94
|
def eoj(self):
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if self.urls:
|
|
200
|
-
# Go over all URLs
|
|
201
|
-
for key, info in self.urls.items():
|
|
202
|
-
baseurl = info.url
|
|
203
|
-
url = "{}?status=eoj".format(baseurl)
|
|
204
|
-
try:
|
|
205
|
-
with urlopen(url) as _:
|
|
206
|
-
logger.debug(
|
|
207
|
-
"EOJ notification sent for %s",
|
|
208
|
-
baseurl,
|
|
209
|
-
)
|
|
210
|
-
except Exception:
|
|
211
|
-
logger.warning(
|
|
212
|
-
"Could not report EOJ",
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
self.file_reporter.eoj()
|
|
95
|
+
"""End of job notification"""
|
|
96
|
+
self.file_reporter.eoj()
|
|
216
97
|
|
|
217
98
|
def set_progress(
|
|
218
99
|
self, progress: float, level: int, desc: Optional[str], console=False
|
|
219
100
|
):
|
|
220
|
-
"""
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
101
|
+
"""Set progress for a specific level.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
progress: Progress value between 0.0 and 1.0
|
|
105
|
+
level: Nesting level (0 is top level)
|
|
106
|
+
desc: Optional description
|
|
107
|
+
console: If True, also print to console
|
|
108
|
+
"""
|
|
109
|
+
# Update in-memory levels
|
|
110
|
+
self.levels = self.levels[: (level + 1)]
|
|
111
|
+
while level >= len(self.levels):
|
|
112
|
+
self.levels.append(LevelInformation(level, None, 0.0))
|
|
113
|
+
if desc:
|
|
114
|
+
self.levels[level].desc = desc
|
|
115
|
+
self.levels[level].progress = progress
|
|
116
|
+
|
|
117
|
+
# Write to file
|
|
118
|
+
self.file_reporter.set_progress(progress, level, desc)
|
|
119
|
+
|
|
120
|
+
# Optionally log to console
|
|
121
|
+
if console:
|
|
122
|
+
logger.info("Progress: %s", self.levels[level])
|
|
238
123
|
|
|
239
124
|
INSTANCE: ClassVar[Optional["Reporter"]] = None
|
|
240
125
|
|
|
241
126
|
@staticmethod
|
|
242
127
|
def instance():
|
|
128
|
+
"""Get or create the singleton Reporter instance."""
|
|
243
129
|
if Reporter.INSTANCE is None:
|
|
244
130
|
taskpath = TaskEnv.instance().taskpath
|
|
245
131
|
assert taskpath is not None, "Task path is not defined"
|
|
246
132
|
Reporter.INSTANCE = Reporter(taskpath)
|
|
247
|
-
Reporter.INSTANCE.start()
|
|
248
133
|
return Reporter.INSTANCE
|
|
249
134
|
|
|
250
135
|
|
|
251
136
|
def progress(value: float, level=0, desc: Optional[str] = None, console=False):
|
|
252
|
-
"""Report task progress
|
|
137
|
+
"""Report task progress.
|
|
253
138
|
|
|
254
139
|
Call this function from within a running task to report progress.
|
|
255
|
-
Progress is
|
|
140
|
+
Progress is written to job event files and displayed in monitors.
|
|
256
141
|
|
|
257
142
|
Example::
|
|
258
143
|
|
|
@@ -263,7 +148,7 @@ def progress(value: float, level=0, desc: Optional[str] = None, console=False):
|
|
|
263
148
|
:param value: Progress value between 0.0 and 1.0
|
|
264
149
|
:param level: Nesting level for nested progress bars (default: 0)
|
|
265
150
|
:param desc: Optional description of the current operation
|
|
266
|
-
:param console: If True, also print to console
|
|
151
|
+
:param console: If True, also print to console
|
|
267
152
|
"""
|
|
268
153
|
if TaskEnv.instance().slave:
|
|
269
154
|
# Skip if in a slave process
|
|
@@ -276,11 +161,16 @@ def report_eoj():
|
|
|
276
161
|
Reporter.instance().eoj()
|
|
277
162
|
|
|
278
163
|
|
|
164
|
+
def start_of_job():
|
|
165
|
+
"""Notify that the job has started running"""
|
|
166
|
+
Reporter.instance().file_reporter.start_of_job()
|
|
167
|
+
|
|
168
|
+
|
|
279
169
|
class xpm_tqdm(std_tqdm):
|
|
280
170
|
"""Experimaestro-aware tqdm progress bar.
|
|
281
171
|
|
|
282
172
|
A drop-in replacement for ``tqdm`` that automatically reports progress
|
|
283
|
-
to
|
|
173
|
+
to job event files. Use this instead of the standard ``tqdm``
|
|
284
174
|
in your task's ``execute()`` method.
|
|
285
175
|
|
|
286
176
|
Example::
|
|
@@ -294,8 +184,6 @@ class xpm_tqdm(std_tqdm):
|
|
|
294
184
|
"""
|
|
295
185
|
|
|
296
186
|
def __init__(self, iterable=None, file=None, *args, **kwargs):
|
|
297
|
-
# Report progress bar
|
|
298
|
-
# newprogress(title=, pos=abs(self.pos))
|
|
299
187
|
_file = file or sys.stderr
|
|
300
188
|
self.is_tty = hasattr(_file, "isatty") or _file.isatty()
|
|
301
189
|
|
|
@@ -325,7 +213,7 @@ def tqdm(*args, **kwargs):
|
|
|
325
213
|
"""Create an experimaestro-aware progress bar.
|
|
326
214
|
|
|
327
215
|
A drop-in replacement for ``tqdm.tqdm`` that automatically reports progress
|
|
328
|
-
to
|
|
216
|
+
to job event files. Use this in task ``execute()`` methods.
|
|
329
217
|
|
|
330
218
|
Example::
|
|
331
219
|
|