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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stepup-queue
3
- Version: 1.1.0
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"
@@ -29,6 +29,7 @@ classifiers = [
29
29
  dependencies = [
30
30
  # Ensure changes to these dependencies are reflected in .github/requirements-old.txt
31
31
  "path>=16.14.0",
32
+ "rich>=13.0.0",
32
33
  "stepup>=3.2.0,<4.0.0",
33
34
  ]
34
35
  dynamic = ["version"]
@@ -1,5 +1,5 @@
1
1
  # StepUp Queue integrates queued jobs into a StepUp workflow.
2
- # © 2025 Toon Verstraelen
2
+ # Copyright 2025-2026 Toon Verstraelen
3
3
  #
4
4
  # This file is part of StepUp Queue.
5
5
  #
@@ -1,5 +1,5 @@
1
1
  # StepUp Queue integrates queued jobs into a StepUp workflow.
2
- # © 2025 Toon Verstraelen
2
+ # Copyright 2025-2026 Toon Verstraelen
3
3
  #
4
4
  # This file is part of StepUp Queue.
5
5
  #
@@ -1,5 +1,5 @@
1
1
  # StepUp Queue integrates queued jobs into a StepUp workflow.
2
- # © 2025 Toon Verstraelen
2
+ # Copyright 2025-2026 Toon Verstraelen
3
3
  #
4
4
  # This file is part of StepUp Queue.
5
5
  #
@@ -1,5 +1,5 @@
1
1
  # StepUp Queue integrates queued jobs into a StepUp workflow.
2
- # © 2025 Toon Verstraelen
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, verbose=True):
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
- print(" ".join(command_args))
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
- command = "scancel"
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, False)
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
- # © 2025 Toon Verstraelen
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, verbose=True):
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"Warning: Could not read job status from {path_log}: {e}")
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, False)
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
- # © 2025 Toon Verstraelen
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 = read_log(path_log, validate_inp_digest) if path_log.is_file() else []
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, do_inp_digest: bool = True) -> list[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
- inp_digest = next(f).strip()
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 do_inp_digest:
133
- check_log_inp_digest(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(line: str):
287
+ def check_log_inp_digest(actual: str, expected: str):
283
288
  """Validate the log input digest, abort if there is a mismatch."""
284
- inp_digest = os.getenv("STEPUP_STEP_INP_DIGEST")
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"Expected: {inp_digest}\nFound: {line}"
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
- # © 2025 Toon Verstraelen
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], verbose: bool = False) -> 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
- verbose
37
- Whether to print warnings when paths do not exist or are not directories.
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 verbose:
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 verbose:
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.0
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,4 +1,5 @@
1
1
  path>=16.14.0
2
+ rich>=13.0.0
2
3
  stepup<4.0.0,>=3.2.0
3
4
 
4
5
  [dev]
File without changes
File without changes
File without changes
File without changes