stepup-queue 1.1.0__tar.gz → 1.1.1__tar.gz
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.
- {stepup_queue-1.1.0/stepup_queue.egg-info → stepup_queue-1.1.1}/PKG-INFO +2 -1
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/pyproject.toml +1 -0
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/stepup/queue/__init__.py +1 -1
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/stepup/queue/actions.py +1 -1
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/stepup/queue/api.py +1 -1
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/stepup/queue/canceljobs.py +28 -11
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/stepup/queue/removejobs.py +12 -6
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/stepup/queue/sbatch.py +19 -17
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/stepup/queue/utils.py +9 -8
- {stepup_queue-1.1.0 → stepup_queue-1.1.1/stepup_queue.egg-info}/PKG-INFO +2 -1
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/stepup_queue.egg-info/requires.txt +1 -0
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/LICENSE +0 -0
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/MANIFEST.in +0 -0
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/README.md +0 -0
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/setup.cfg +0 -0
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/stepup_queue.egg-info/SOURCES.txt +0 -0
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/stepup_queue.egg-info/dependency_links.txt +0 -0
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/stepup_queue.egg-info/entry_points.txt +0 -0
- {stepup_queue-1.1.0 → stepup_queue-1.1.1}/stepup_queue.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stepup-queue
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: StepUp Queue integrates queued jobs into a StepUp workflow.
|
|
5
5
|
Author-email: Toon Verstraelen <toon.verstraelen@ugent.be>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -25,6 +25,7 @@ Requires-Python: >=3.11
|
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
License-File: LICENSE
|
|
27
27
|
Requires-Dist: path>=16.14.0
|
|
28
|
+
Requires-Dist: rich>=13.0.0
|
|
28
29
|
Requires-Dist: stepup<4.0.0,>=3.2.0
|
|
29
30
|
Provides-Extra: dev
|
|
30
31
|
Requires-Dist: psutil; extra == "dev"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# StepUp Queue integrates queued jobs into a StepUp workflow.
|
|
2
|
-
#
|
|
2
|
+
# Copyright 2025-2026 Toon Verstraelen
|
|
3
3
|
#
|
|
4
4
|
# This file is part of StepUp Queue.
|
|
5
5
|
#
|
|
@@ -24,6 +24,7 @@ import subprocess
|
|
|
24
24
|
import sys
|
|
25
25
|
|
|
26
26
|
from path import Path
|
|
27
|
+
from rich.console import Console
|
|
27
28
|
|
|
28
29
|
from .sbatch import DONE_STATES, parse_sbatch, read_log, read_status
|
|
29
30
|
from .utils import search_jobs
|
|
@@ -31,12 +32,17 @@ from .utils import search_jobs
|
|
|
31
32
|
|
|
32
33
|
def canceljobs_tool(args: argparse.Namespace):
|
|
33
34
|
"""Iterate over all slurmjob.log files, read the SLURM job IDs, and cancel them."""
|
|
35
|
+
console = Console(highlight=False)
|
|
36
|
+
if not args.commit:
|
|
37
|
+
console.print("[yellow]# Note: No jobs are actually cancelled.[/]")
|
|
38
|
+
console.print("[yellow]# Use the --commit option to execute the cancellations.[/]")
|
|
39
|
+
|
|
34
40
|
jobs = {}
|
|
35
|
-
for path_log in search_jobs(args.paths,
|
|
41
|
+
for path_log in search_jobs(args.paths, console):
|
|
36
42
|
try:
|
|
37
43
|
job_id, cluster, status = read_jobid_cluster_status(path_log)
|
|
38
44
|
except ValueError as e:
|
|
39
|
-
print(f"# WARNING: Could not read job ID from {path_log}: {e}")
|
|
45
|
+
console.print(f"[red]# WARNING: Could not read job ID from {path_log}: {e}[/]")
|
|
40
46
|
continue
|
|
41
47
|
if args.all or status not in DONE_STATES:
|
|
42
48
|
jobs.setdefault(cluster, []).append((job_id, path_log, status))
|
|
@@ -56,24 +62,22 @@ def canceljobs_tool(args: argparse.Namespace):
|
|
|
56
62
|
command_args.extend(str(job_id) for job_id, _, _ in cancel_jobs)
|
|
57
63
|
|
|
58
64
|
# Using subprocess.run for better control and error handling
|
|
59
|
-
|
|
65
|
+
print_cancel_command(
|
|
66
|
+
console, [job_id for job_id, _, _ in cancel_jobs], cluster, None
|
|
67
|
+
)
|
|
60
68
|
result = subprocess.run(command_args, check=False)
|
|
61
69
|
all_good &= result.returncode == 0
|
|
62
70
|
else:
|
|
63
71
|
for job_id, path_log, status in cluster_jobs:
|
|
64
|
-
|
|
65
|
-
if cluster is not None:
|
|
66
|
-
command += f" -M {cluster}"
|
|
67
|
-
command += f" {job_id} # {path_log} {status}"
|
|
68
|
-
print(command)
|
|
72
|
+
print_cancel_command(console, [job_id], cluster, f"{path_log} {status}")
|
|
69
73
|
if not all_good:
|
|
70
|
-
print("Some jobs could not be cancelled. See messages above.")
|
|
74
|
+
console.print("[red]Some jobs could not be cancelled. See messages above.[/]")
|
|
71
75
|
sys.exit(1)
|
|
72
76
|
|
|
73
77
|
|
|
74
78
|
def read_jobid_cluster_status(path_log: str) -> tuple[int, str | None, str | None]:
|
|
75
79
|
"""Read the job ID, cluster, and job status from the job log file."""
|
|
76
|
-
lines = read_log(path_log,
|
|
80
|
+
lines = read_log(path_log, None)
|
|
77
81
|
if len(lines) < 1:
|
|
78
82
|
raise ValueError(f"Incomplete file: {path_log}.")
|
|
79
83
|
words = lines[0].split()
|
|
@@ -115,3 +119,16 @@ def canceljobs_subcommand(subparser: argparse.ArgumentParser) -> callable:
|
|
|
115
119
|
help="Select all jobs, including the ones that seem to be done already.",
|
|
116
120
|
)
|
|
117
121
|
return canceljobs_tool
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def print_cancel_command(
|
|
125
|
+
console: Console, job_ids: list[int], cluster: str | None, comment: str | None
|
|
126
|
+
) -> str:
|
|
127
|
+
"""Print the job cancellation command."""
|
|
128
|
+
parts = ["[green]scancel[/]"]
|
|
129
|
+
if cluster is not None:
|
|
130
|
+
parts.append(f"[cyan]-M {cluster}[/]")
|
|
131
|
+
parts.extend(str(job_id) for job_id in job_ids)
|
|
132
|
+
if comment is not None:
|
|
133
|
+
parts.append(f" [bright_black]# {comment}[/]")
|
|
134
|
+
console.print(" ".join(parts))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# StepUp Queue integrates queued jobs into a StepUp workflow.
|
|
2
|
-
#
|
|
2
|
+
# Copyright 2025-2026 Toon Verstraelen
|
|
3
3
|
#
|
|
4
4
|
# This file is part of StepUp Queue.
|
|
5
5
|
#
|
|
@@ -23,6 +23,7 @@ import argparse
|
|
|
23
23
|
import shutil
|
|
24
24
|
|
|
25
25
|
from path import Path
|
|
26
|
+
from rich.console import Console
|
|
26
27
|
|
|
27
28
|
from .sbatch import read_log, read_status
|
|
28
29
|
from .utils import search_jobs
|
|
@@ -45,26 +46,31 @@ FAILED_STATES = {
|
|
|
45
46
|
|
|
46
47
|
def removejobs_tool(args: argparse.Namespace):
|
|
47
48
|
"""Iterate over all slurmjob.log files and remove their parent job directories."""
|
|
49
|
+
console = Console(highlight=False)
|
|
50
|
+
if not args.commit:
|
|
51
|
+
console.print("[yellow]# Note: No job directories are actually removed.[/]")
|
|
52
|
+
console.print("[yellow]# Use the --commit option to execute the removals.[/]")
|
|
53
|
+
|
|
48
54
|
jobs = []
|
|
49
|
-
for path_log in search_jobs(args.paths,
|
|
55
|
+
for path_log in search_jobs(args.paths, console):
|
|
50
56
|
try:
|
|
51
57
|
status = read_last_status(path_log)
|
|
52
58
|
except ValueError as e:
|
|
53
|
-
print(f"
|
|
59
|
+
console.print(f"[red]# WARNING: Could not read job status from {path_log}: {e}[/]")
|
|
54
60
|
status = None
|
|
55
61
|
if args.all or status in FAILED_STATES:
|
|
56
62
|
jobs.append((path_log, status))
|
|
57
63
|
|
|
58
64
|
for path_log, status in jobs:
|
|
59
|
-
command = f"rm -rf {path_log.parent} # state={status}"
|
|
60
|
-
print(command)
|
|
65
|
+
command = f"[cyan]rm -rf[/] {path_log.parent} [bright_black]# state={status}[/]"
|
|
66
|
+
console.print(command)
|
|
61
67
|
if args.commit:
|
|
62
68
|
shutil.rmtree(path_log.parent)
|
|
63
69
|
|
|
64
70
|
|
|
65
71
|
def read_last_status(path_log: str) -> str | None:
|
|
66
72
|
"""Read the last job status from the job log file."""
|
|
67
|
-
lines = read_log(path_log,
|
|
73
|
+
lines = read_log(path_log, None)
|
|
68
74
|
return read_status(lines[-1:])[1]
|
|
69
75
|
|
|
70
76
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# StepUp Queue integrates queued jobs into a StepUp workflow.
|
|
2
|
-
#
|
|
2
|
+
# Copyright 2025-2026 Toon Verstraelen
|
|
3
3
|
#
|
|
4
4
|
# This file is part of StepUp Queue.
|
|
5
5
|
#
|
|
@@ -68,9 +68,17 @@ def submit_once_and_wait(
|
|
|
68
68
|
The return code of the job.
|
|
69
69
|
0 if successful, 1 if the job failed.
|
|
70
70
|
"""
|
|
71
|
+
inp_digest = os.getenv("STEPUP_STEP_INP_DIGEST")
|
|
72
|
+
if inp_digest is None:
|
|
73
|
+
raise ValueError("The environment variable STEPUP_STEP_INP_DIGEST is not set.")
|
|
74
|
+
|
|
71
75
|
# Read previously logged job states
|
|
72
76
|
path_log = Path("slurmjob.log")
|
|
73
|
-
previous_lines =
|
|
77
|
+
previous_lines = (
|
|
78
|
+
read_log(path_log, inp_digest if validate_inp_digest else None)
|
|
79
|
+
if path_log.is_file()
|
|
80
|
+
else []
|
|
81
|
+
)
|
|
74
82
|
|
|
75
83
|
# Go through or skip states.
|
|
76
84
|
submit_time, status = read_status(previous_lines)
|
|
@@ -79,7 +87,7 @@ def submit_once_and_wait(
|
|
|
79
87
|
submit_time = time.time()
|
|
80
88
|
sbatch_stdout = submit_job(work_thread, job_ext, sbatch_rc)
|
|
81
89
|
# Create a new log file after submitting the job.
|
|
82
|
-
_init_log(path_log)
|
|
90
|
+
_init_log(path_log, inp_digest)
|
|
83
91
|
log_status(path_log, f"Submitted {sbatch_stdout}")
|
|
84
92
|
rndsleep()
|
|
85
93
|
else:
|
|
@@ -117,7 +125,7 @@ def submit_once_and_wait(
|
|
|
117
125
|
raise RuntimeError(f"Job ended with status '{status}'.")
|
|
118
126
|
|
|
119
127
|
|
|
120
|
-
def read_log(path_log: str,
|
|
128
|
+
def read_log(path_log: str, expected_inp_digest: str | None = None) -> list[str]:
|
|
121
129
|
"""Read lines from a previously created log file."""
|
|
122
130
|
lines = []
|
|
123
131
|
with open(path_log) as f:
|
|
@@ -126,11 +134,11 @@ def read_log(path_log: str, do_inp_digest: bool = True) -> list[str]:
|
|
|
126
134
|
except StopIteration as exc:
|
|
127
135
|
raise ValueError("Existing log file is empty.") from exc
|
|
128
136
|
try:
|
|
129
|
-
|
|
137
|
+
actual_inp_digest = next(f).strip()
|
|
130
138
|
except StopIteration as exc:
|
|
131
139
|
raise ValueError("Existing log file has no input digest.") from exc
|
|
132
|
-
if
|
|
133
|
-
check_log_inp_digest(
|
|
140
|
+
if expected_inp_digest is not None:
|
|
141
|
+
check_log_inp_digest(actual_inp_digest, expected_inp_digest)
|
|
134
142
|
for line in f:
|
|
135
143
|
line = line.strip()
|
|
136
144
|
lines.append(line)
|
|
@@ -145,11 +153,8 @@ def check_log_version(line: str):
|
|
|
145
153
|
)
|
|
146
154
|
|
|
147
155
|
|
|
148
|
-
def _init_log(path_log: str):
|
|
156
|
+
def _init_log(path_log: str, inp_digest: str):
|
|
149
157
|
"""Initialize a new log file."""
|
|
150
|
-
inp_digest = os.getenv("STEPUP_STEP_INP_DIGEST")
|
|
151
|
-
if inp_digest is None:
|
|
152
|
-
raise ValueError("The environment variable STEPUP_STEP_INP_DIGEST is not set.")
|
|
153
158
|
with open(path_log, "w") as fh:
|
|
154
159
|
print(FIRST_LINE, file=fh)
|
|
155
160
|
print(inp_digest, file=fh)
|
|
@@ -279,15 +284,12 @@ class InpDigestError(ValueError):
|
|
|
279
284
|
"""The input digest in the log file does not match the one in the environment."""
|
|
280
285
|
|
|
281
286
|
|
|
282
|
-
def check_log_inp_digest(
|
|
287
|
+
def check_log_inp_digest(actual: str, expected: str):
|
|
283
288
|
"""Validate the log input digest, abort if there is a mismatch."""
|
|
284
|
-
|
|
285
|
-
if inp_digest is None:
|
|
286
|
-
raise ValueError("The environment variable STEPUP_STEP_INP_DIGEST is not set.")
|
|
287
|
-
if line != inp_digest:
|
|
289
|
+
if actual != expected:
|
|
288
290
|
raise InpDigestError(
|
|
289
291
|
"The second line of the log contains the wrong input digest.\n"
|
|
290
|
-
f"
|
|
292
|
+
f"Actual: {actual}\nExpected: {expected}\n"
|
|
291
293
|
)
|
|
292
294
|
|
|
293
295
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# StepUp Queue integrates queued jobs into a StepUp workflow.
|
|
2
|
-
#
|
|
2
|
+
# Copyright 2025-2026 Toon Verstraelen
|
|
3
3
|
#
|
|
4
4
|
# This file is part of StepUp Queue.
|
|
5
5
|
#
|
|
@@ -22,19 +22,20 @@
|
|
|
22
22
|
from itertools import chain
|
|
23
23
|
|
|
24
24
|
from path import Path
|
|
25
|
+
from rich.console import Console
|
|
25
26
|
|
|
26
27
|
__all__ = ("search_jobs",)
|
|
27
28
|
|
|
28
29
|
|
|
29
|
-
def search_jobs(paths: list[Path],
|
|
30
|
+
def search_jobs(paths: list[Path], console: Console | None = None) -> list[Path]:
|
|
30
31
|
"""Recursively search for slurmjob.log files in the specified directories.
|
|
31
32
|
|
|
32
33
|
Parameters
|
|
33
34
|
----------
|
|
34
35
|
paths
|
|
35
36
|
List of directories to search in.
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
console
|
|
38
|
+
Rich console for printing warnings. If None, no warnings are printed.
|
|
38
39
|
|
|
39
40
|
Returns
|
|
40
41
|
-------
|
|
@@ -44,12 +45,12 @@ def search_jobs(paths: list[Path], verbose: bool = False) -> list[Path]:
|
|
|
44
45
|
paths_log = set()
|
|
45
46
|
for path in paths:
|
|
46
47
|
if not path.exists():
|
|
47
|
-
if
|
|
48
|
-
print(f"# WARNING: Path {path} does not exist.")
|
|
48
|
+
if console is not None:
|
|
49
|
+
console.print(f"[red]# WARNING: Path {path} does not exist.[/]")
|
|
49
50
|
continue
|
|
50
51
|
if not path.is_dir():
|
|
51
|
-
if
|
|
52
|
-
print(f"# WARNING: Path {path} is not a directory.")
|
|
52
|
+
if console is not None:
|
|
53
|
+
console.print(f"[red]# WARNING: Path {path} is not a directory.[/]")
|
|
53
54
|
continue
|
|
54
55
|
for path_sub in chain([path], path.walkdirs()):
|
|
55
56
|
path_log = path_sub / "slurmjob.log"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stepup-queue
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: StepUp Queue integrates queued jobs into a StepUp workflow.
|
|
5
5
|
Author-email: Toon Verstraelen <toon.verstraelen@ugent.be>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -25,6 +25,7 @@ Requires-Python: >=3.11
|
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
License-File: LICENSE
|
|
27
27
|
Requires-Dist: path>=16.14.0
|
|
28
|
+
Requires-Dist: rich>=13.0.0
|
|
28
29
|
Requires-Dist: stepup<4.0.0,>=3.2.0
|
|
29
30
|
Provides-Extra: dev
|
|
30
31
|
Requires-Dist: psutil; extra == "dev"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|