snakemake-executor-plugin-slurm 1.7.0__py3-none-any.whl → 1.9.0__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 snakemake-executor-plugin-slurm might be problematic. Click here for more details.
- snakemake_executor_plugin_slurm/__init__.py +38 -38
- snakemake_executor_plugin_slurm/efficiency_report.py +56 -13
- snakemake_executor_plugin_slurm/submit_string.py +66 -18
- snakemake_executor_plugin_slurm/validation.py +75 -0
- {snakemake_executor_plugin_slurm-1.7.0.dist-info → snakemake_executor_plugin_slurm-1.9.0.dist-info}/METADATA +4 -2
- snakemake_executor_plugin_slurm-1.9.0.dist-info/RECORD +9 -0
- {snakemake_executor_plugin_slurm-1.7.0.dist-info → snakemake_executor_plugin_slurm-1.9.0.dist-info}/WHEEL +1 -1
- snakemake_executor_plugin_slurm-1.7.0.dist-info/RECORD +0 -8
- {snakemake_executor_plugin_slurm-1.7.0.dist-info → snakemake_executor_plugin_slurm-1.9.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -18,6 +18,7 @@ import uuid
|
|
|
18
18
|
|
|
19
19
|
from snakemake_interface_executor_plugins.executors.base import SubmittedJobInfo
|
|
20
20
|
from snakemake_interface_executor_plugins.executors.remote import RemoteExecutor
|
|
21
|
+
|
|
21
22
|
from snakemake_interface_executor_plugins.settings import (
|
|
22
23
|
ExecutorSettingsBase,
|
|
23
24
|
CommonSettings,
|
|
@@ -34,6 +35,7 @@ from .utils import (
|
|
|
34
35
|
)
|
|
35
36
|
from .efficiency_report import create_efficiency_report
|
|
36
37
|
from .submit_string import get_submit_command
|
|
38
|
+
from .validation import validate_slurm_extra
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
@dataclass
|
|
@@ -307,7 +309,7 @@ class Executor(RemoteExecutor):
|
|
|
307
309
|
"run_uuid": self.run_uuid,
|
|
308
310
|
"slurm_logfile": slurm_logfile,
|
|
309
311
|
"comment_str": comment_str,
|
|
310
|
-
"account": self.get_account_arg(job),
|
|
312
|
+
"account": next(self.get_account_arg(job)),
|
|
311
313
|
"partition": self.get_partition_arg(job),
|
|
312
314
|
"workdir": self.workflow.workdir_init,
|
|
313
315
|
}
|
|
@@ -339,17 +341,6 @@ class Executor(RemoteExecutor):
|
|
|
339
341
|
"- submitting without. This might or might not work on your cluster."
|
|
340
342
|
)
|
|
341
343
|
|
|
342
|
-
# MPI job
|
|
343
|
-
if job.resources.get("mpi", False):
|
|
344
|
-
if not job.resources.get("tasks_per_node") and not job.resources.get(
|
|
345
|
-
"nodes"
|
|
346
|
-
):
|
|
347
|
-
self.logger.warning(
|
|
348
|
-
"MPI job detected, but no 'tasks_per_node' or 'nodes' "
|
|
349
|
-
"specified. Assuming 'tasks_per_node=1'."
|
|
350
|
-
"Probably not what you want."
|
|
351
|
-
)
|
|
352
|
-
|
|
353
344
|
exec_job = self.format_job_exec(job)
|
|
354
345
|
|
|
355
346
|
# and finally the job to execute with all the snakemake parameters
|
|
@@ -668,10 +659,22 @@ We leave it to SLURM to resume your job(s)"""
|
|
|
668
659
|
else raises an error - implicetly.
|
|
669
660
|
"""
|
|
670
661
|
if job.resources.get("slurm_account"):
|
|
671
|
-
#
|
|
672
|
-
#
|
|
673
|
-
|
|
674
|
-
|
|
662
|
+
# split the account upon ',' and whitespace, to allow
|
|
663
|
+
# multiple accounts being given
|
|
664
|
+
accounts = [
|
|
665
|
+
a for a in re.split(r"[,\s]+", job.resources.slurm_account) if a
|
|
666
|
+
]
|
|
667
|
+
for account in accounts:
|
|
668
|
+
# here, we check whether the given or guessed account is valid
|
|
669
|
+
# if not, a WorkflowError is raised
|
|
670
|
+
self.test_account(account)
|
|
671
|
+
# sbatch only allows one account per submission
|
|
672
|
+
# yield one after the other, if multiple were given
|
|
673
|
+
# we have to quote the account, because it might
|
|
674
|
+
# contain build-in shell commands - see issue #354
|
|
675
|
+
for account in accounts:
|
|
676
|
+
self.test_account(account)
|
|
677
|
+
yield f" -A {shlex.quote(account)}"
|
|
675
678
|
else:
|
|
676
679
|
if self._fallback_account_arg is None:
|
|
677
680
|
self.logger.warning("No SLURM account given, trying to guess.")
|
|
@@ -679,7 +682,7 @@ We leave it to SLURM to resume your job(s)"""
|
|
|
679
682
|
if account:
|
|
680
683
|
self.logger.warning(f"Guessed SLURM account: {account}")
|
|
681
684
|
self.test_account(f"{account}")
|
|
682
|
-
self._fallback_account_arg = f" -A {account}"
|
|
685
|
+
self._fallback_account_arg = f" -A {shlex.quote(account)}"
|
|
683
686
|
else:
|
|
684
687
|
self.logger.warning(
|
|
685
688
|
"Unable to guess SLURM account. Trying to proceed without."
|
|
@@ -687,7 +690,7 @@ We leave it to SLURM to resume your job(s)"""
|
|
|
687
690
|
self._fallback_account_arg = (
|
|
688
691
|
"" # no account specific args for sbatch
|
|
689
692
|
)
|
|
690
|
-
|
|
693
|
+
yield self._fallback_account_arg
|
|
691
694
|
|
|
692
695
|
def get_partition_arg(self, job: JobExecutorInterface):
|
|
693
696
|
"""
|
|
@@ -702,7 +705,9 @@ We leave it to SLURM to resume your job(s)"""
|
|
|
702
705
|
self._fallback_partition = self.get_default_partition(job)
|
|
703
706
|
partition = self._fallback_partition
|
|
704
707
|
if partition:
|
|
705
|
-
|
|
708
|
+
# we have to quote the partition, because it might
|
|
709
|
+
# contain build-in shell commands
|
|
710
|
+
return f" -p {shlex.quote(partition)}"
|
|
706
711
|
else:
|
|
707
712
|
return ""
|
|
708
713
|
|
|
@@ -730,32 +735,35 @@ We leave it to SLURM to resume your job(s)"""
|
|
|
730
735
|
"""
|
|
731
736
|
tests whether the given account is registered, raises an error, if not
|
|
732
737
|
"""
|
|
733
|
-
|
|
738
|
+
# first we need to test with sacctmgr because sshare might not
|
|
739
|
+
# work in a multicluster environment
|
|
740
|
+
cmd = f'sacctmgr -n -s list user "{os.environ["USER"]}" format=account%256'
|
|
741
|
+
sacctmgr_report = sshare_report = ""
|
|
734
742
|
try:
|
|
735
743
|
accounts = subprocess.check_output(
|
|
736
744
|
cmd, shell=True, text=True, stderr=subprocess.PIPE
|
|
737
745
|
)
|
|
738
746
|
except subprocess.CalledProcessError as e:
|
|
739
|
-
|
|
747
|
+
sacctmgr_report = (
|
|
740
748
|
"Unable to test the validity of the given or guessed"
|
|
741
|
-
f" SLURM account '{account}' with
|
|
749
|
+
f" SLURM account '{account}' with sacctmgr: {e.stderr}."
|
|
742
750
|
)
|
|
743
751
|
accounts = ""
|
|
744
752
|
|
|
745
753
|
if not accounts.strip():
|
|
746
|
-
cmd =
|
|
754
|
+
cmd = "sshare -U --format Account%256 --noheader"
|
|
747
755
|
try:
|
|
748
756
|
accounts = subprocess.check_output(
|
|
749
757
|
cmd, shell=True, text=True, stderr=subprocess.PIPE
|
|
750
758
|
)
|
|
751
759
|
except subprocess.CalledProcessError as e:
|
|
752
|
-
|
|
760
|
+
sshare_report = (
|
|
753
761
|
"Unable to test the validity of the given or guessed "
|
|
754
|
-
f"SLURM account '{account}' with
|
|
762
|
+
f"SLURM account '{account}' with sshare: {e.stderr}."
|
|
755
763
|
)
|
|
756
764
|
raise WorkflowError(
|
|
757
|
-
f"The '
|
|
758
|
-
f"and likewise '
|
|
765
|
+
f"The 'sacctmgr' reported: '{sacctmgr_report}' "
|
|
766
|
+
f"and likewise 'sshare' reported: '{sshare_report}'."
|
|
759
767
|
)
|
|
760
768
|
|
|
761
769
|
# The set() has been introduced during review to eliminate
|
|
@@ -764,7 +772,7 @@ We leave it to SLURM to resume your job(s)"""
|
|
|
764
772
|
|
|
765
773
|
if not accounts:
|
|
766
774
|
self.logger.warning(
|
|
767
|
-
f"Both '
|
|
775
|
+
f"Both 'sacctmgr' and 'sshare' returned empty results for account "
|
|
768
776
|
f"'{account}'. Proceeding without account validation."
|
|
769
777
|
)
|
|
770
778
|
return ""
|
|
@@ -804,13 +812,5 @@ We leave it to SLURM to resume your job(s)"""
|
|
|
804
812
|
return ""
|
|
805
813
|
|
|
806
814
|
def check_slurm_extra(self, job):
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
raise WorkflowError(
|
|
810
|
-
"The --job-name option is not allowed in the 'slurm_extra' parameter. "
|
|
811
|
-
"The job name is set by snakemake and must not be overwritten. "
|
|
812
|
-
"It is internally used to check the stati of the all submitted jobs "
|
|
813
|
-
"by this workflow."
|
|
814
|
-
"Please consult the documentation if you are unsure how to "
|
|
815
|
-
"query the status of your jobs."
|
|
816
|
-
)
|
|
815
|
+
"""Validate that slurm_extra doesn't contain executor-managed options."""
|
|
816
|
+
validate_slurm_extra(job)
|
|
@@ -3,24 +3,67 @@ import pandas as pd
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
import subprocess
|
|
5
5
|
import shlex
|
|
6
|
-
|
|
7
|
-
import os # only temporarily needed for printf debugging
|
|
6
|
+
from datetime import datetime
|
|
8
7
|
import numpy as np
|
|
8
|
+
import os
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def time_to_seconds(time_str):
|
|
12
|
-
"""
|
|
13
|
-
|
|
12
|
+
"""
|
|
13
|
+
Convert SLURM sacct time format to seconds.
|
|
14
|
+
|
|
15
|
+
Handles sacct output formats:
|
|
16
|
+
- Elapsed: [D-]HH:MM:SS or [DD-]HH:MM:SS (no fractional seconds)
|
|
17
|
+
- TotalCPU: [D-][HH:]MM:SS or [DD-][HH:]MM:SS (with fractional seconds)
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
- "1-12:30:45" -> 131445 seconds (1 day + 12h 30m 45s)
|
|
21
|
+
- "23:59:59" -> 86399 seconds
|
|
22
|
+
- "45:30" -> 2730 seconds (45 minutes 30 seconds)
|
|
23
|
+
- "30.5" -> 30.5 seconds (fractional seconds for TotalCPU)
|
|
24
|
+
"""
|
|
25
|
+
if (
|
|
26
|
+
pd.isna(time_str)
|
|
27
|
+
or str(time_str).strip() == ""
|
|
28
|
+
or str(time_str).strip() == "invalid"
|
|
29
|
+
):
|
|
14
30
|
return 0
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
|
|
32
|
+
time_str = str(time_str).strip()
|
|
33
|
+
|
|
34
|
+
# Try different SLURM time formats with datetime parsing
|
|
35
|
+
time_formats = [
|
|
36
|
+
"%d-%H:%M:%S.%f", # D-HH:MM:SS.ffffff (with fractional seconds)
|
|
37
|
+
"%d-%H:%M:%S", # D-HH:MM:SS
|
|
38
|
+
"%d-%M:%S", # D-MM:SS
|
|
39
|
+
"%d-%M:%S.%f", # D-MM:SS.ffffff (with fractional seconds)
|
|
40
|
+
"%H:%M:%S.%f", # HH:MM:SS.ffffff (with fractional seconds)
|
|
41
|
+
"%H:%M:%S", # HH:MM:SS
|
|
42
|
+
"%M:%S.%f", # MM:SS.ffffff (with fractional seconds)
|
|
43
|
+
"%M:%S", # MM:SS
|
|
44
|
+
"%S.%f", # SS.ffffff (with fractional seconds)
|
|
45
|
+
"%S", # SS
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
for fmt in time_formats:
|
|
49
|
+
try:
|
|
50
|
+
time_obj = datetime.strptime(time_str, fmt)
|
|
51
|
+
|
|
52
|
+
total_seconds = (
|
|
53
|
+
time_obj.hour * 3600
|
|
54
|
+
+ time_obj.minute * 60
|
|
55
|
+
+ time_obj.second
|
|
56
|
+
+ time_obj.microsecond / 1000000
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Add days if present (datetime treats day 1 as the first day)
|
|
60
|
+
if fmt.startswith("%d-"):
|
|
61
|
+
total_seconds += time_obj.day * 86400
|
|
62
|
+
|
|
63
|
+
return total_seconds
|
|
64
|
+
except ValueError:
|
|
65
|
+
continue
|
|
66
|
+
return 0 # If all parsing attempts fail, return 0
|
|
24
67
|
|
|
25
68
|
|
|
26
69
|
def parse_maxrss(maxrss):
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
from snakemake_interface_common.exceptions import WorkflowError
|
|
1
2
|
from snakemake_executor_plugin_slurm_jobstep import get_cpu_setting
|
|
2
3
|
from types import SimpleNamespace
|
|
4
|
+
import shlex
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def safe_quote(value):
|
|
8
|
+
"""
|
|
9
|
+
Safely quote a parameter value using shlex.quote.
|
|
10
|
+
Handles None values and converts to string if needed.
|
|
11
|
+
Returns empty quotes for empty strings.
|
|
12
|
+
"""
|
|
13
|
+
str_value = str(value)
|
|
14
|
+
if str_value == "":
|
|
15
|
+
return "''"
|
|
16
|
+
return shlex.quote(str_value)
|
|
3
17
|
|
|
4
18
|
|
|
5
19
|
def get_submit_command(job, params):
|
|
@@ -10,37 +24,41 @@ def get_submit_command(job, params):
|
|
|
10
24
|
params = SimpleNamespace(**params)
|
|
11
25
|
|
|
12
26
|
call = (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
f"--job-name {params.run_uuid} "
|
|
16
|
-
f
|
|
17
|
-
|
|
18
|
-
f
|
|
27
|
+
"sbatch "
|
|
28
|
+
"--parsable "
|
|
29
|
+
f"--job-name {safe_quote(params.run_uuid)} "
|
|
30
|
+
f"--output {safe_quote(params.slurm_logfile)} "
|
|
31
|
+
"--export=ALL "
|
|
32
|
+
f"--comment {safe_quote(params.comment_str)}"
|
|
19
33
|
)
|
|
20
34
|
|
|
21
35
|
# No accout or partition checking is required, here.
|
|
22
36
|
# Checking is done in the submit function.
|
|
23
37
|
|
|
24
38
|
# here, only the string is used, as it already contains
|
|
25
|
-
#
|
|
39
|
+
# "-A '{account_name}'"
|
|
26
40
|
call += f" {params.account}"
|
|
27
41
|
# here, only the string is used, as it already contains
|
|
28
|
-
#
|
|
42
|
+
# "- p '{partition_name}'"
|
|
29
43
|
call += f" {params.partition}"
|
|
30
44
|
|
|
31
45
|
if job.resources.get("clusters"):
|
|
32
|
-
call += f" --clusters {job.resources.clusters}"
|
|
46
|
+
call += f" --clusters {safe_quote(job.resources.clusters)}"
|
|
33
47
|
|
|
34
48
|
if job.resources.get("runtime"):
|
|
35
|
-
call += f" -t {job.resources.runtime}"
|
|
49
|
+
call += f" -t {safe_quote(job.resources.runtime)}"
|
|
36
50
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
# Both, constraint and qos are optional.
|
|
52
|
+
# If not set, they will not be added to the sbatch call.
|
|
53
|
+
# If explicitly set to an empty string,
|
|
54
|
+
# `--constraint ''` or `--qos ''` will be added.
|
|
55
|
+
constraint = job.resources.get("constraint")
|
|
56
|
+
if constraint is not None:
|
|
57
|
+
call += f" -C {safe_quote(constraint)}"
|
|
41
58
|
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
qos = job.resources.get("qos")
|
|
60
|
+
if qos is not None:
|
|
61
|
+
call += f" --qos={safe_quote(qos)}"
|
|
44
62
|
|
|
45
63
|
if job.resources.get("mem_mb_per_cpu"):
|
|
46
64
|
call += f" --mem-per-cpu {job.resources.mem_mb_per_cpu}"
|
|
@@ -66,7 +84,36 @@ def get_submit_command(job, params):
|
|
|
66
84
|
else:
|
|
67
85
|
# fixes #40 - set ntasks regardless of mpi, because
|
|
68
86
|
# SLURM v22.05 will require it for all jobs
|
|
69
|
-
|
|
87
|
+
# if the job is a MPI job, ntasks will be set later
|
|
88
|
+
if not job.resources.get("mpi", False):
|
|
89
|
+
call += f" --ntasks={job.resources.get('tasks') or 1}"
|
|
90
|
+
|
|
91
|
+
# if the job is an MPI job, we need to have some task setting:
|
|
92
|
+
if job.resources.get("mpi", False):
|
|
93
|
+
if not job.resources.get("tasks_per_node") and not job.resources.get("tasks"):
|
|
94
|
+
raise WorkflowError(
|
|
95
|
+
"For MPI jobs, please specify either "
|
|
96
|
+
"'tasks_per_node' or 'tasks' (at least one is required)."
|
|
97
|
+
)
|
|
98
|
+
# raise an error if both task settings are used
|
|
99
|
+
if job.resources.get("tasks_per_node") and job.resources.get("tasks"):
|
|
100
|
+
raise WorkflowError(
|
|
101
|
+
"For MPI jobs, please specify either 'tasks_per_node' or 'tasks', "
|
|
102
|
+
"but not both."
|
|
103
|
+
)
|
|
104
|
+
if job.resources.get("tasks_per_node"):
|
|
105
|
+
if job.resources.get("tasks_per_node") <= 1:
|
|
106
|
+
raise WorkflowError(
|
|
107
|
+
"For MPI jobs, 'tasks_per_node' must be greater than 1."
|
|
108
|
+
)
|
|
109
|
+
call += f" --ntasks-per-node={job.resources.tasks_per_node}"
|
|
110
|
+
elif job.resources.get("tasks"):
|
|
111
|
+
if job.resources.get("tasks") == 1:
|
|
112
|
+
raise WorkflowError("For MPI jobs, 'tasks' must be greater than 1.")
|
|
113
|
+
call += f" --ntasks={job.resources.tasks}"
|
|
114
|
+
# nodes CAN be set independently of tasks or tasks_per_node
|
|
115
|
+
# this is at a user's discretion. The nodes flag might already
|
|
116
|
+
# be set above, if the user specified it.
|
|
70
117
|
|
|
71
118
|
# we need to set cpus-per-task OR cpus-per-gpu, the function
|
|
72
119
|
# will return a string with the corresponding value
|
|
@@ -77,6 +124,7 @@ def get_submit_command(job, params):
|
|
|
77
124
|
# ensure that workdir is set correctly
|
|
78
125
|
# use short argument as this is the same in all slurm versions
|
|
79
126
|
# (see https://github.com/snakemake/snakemake/issues/2014)
|
|
80
|
-
|
|
127
|
+
if params.workdir:
|
|
128
|
+
call += f" -D {safe_quote(params.workdir)}"
|
|
81
129
|
|
|
82
130
|
return call
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SLURM parameter validation functions for the Snakemake executor plugin.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from snakemake_interface_common.exceptions import WorkflowError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_forbidden_slurm_options():
|
|
10
|
+
"""
|
|
11
|
+
Return a dictionary of forbidden SLURM options that the executor manages.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
dict: Mapping of regex patterns to human-readable option names
|
|
15
|
+
"""
|
|
16
|
+
return {
|
|
17
|
+
# Job identification and output
|
|
18
|
+
r"--job-name[=\s]|-J\s?": "job name",
|
|
19
|
+
r"--output[=\s]|-o\s": "output file",
|
|
20
|
+
r"--error[=\s]|-e\s": "error file",
|
|
21
|
+
r"--parsable": "parsable output",
|
|
22
|
+
r"--export[=\s]": "environment export",
|
|
23
|
+
r"--comment[=\s]": "job comment",
|
|
24
|
+
r"--workdir[=\s]|-D\s": "working directory",
|
|
25
|
+
# Account and partition
|
|
26
|
+
r"--account[=\s]|-A\s": "account",
|
|
27
|
+
r"--partition[=\s]|-p\s": "partition",
|
|
28
|
+
# Memory options
|
|
29
|
+
r"--mem[=\s]": "memory",
|
|
30
|
+
r"--mem-per-cpu[=\s]": "memory per CPU",
|
|
31
|
+
# CPU and task options
|
|
32
|
+
r"--ntasks[=\s]|-n\s": "number of tasks",
|
|
33
|
+
r"--ntasks-per-gpu[=\s]": "tasks per GPU",
|
|
34
|
+
r"--cpus-per-task[=\s]|-c\s": "CPUs per task",
|
|
35
|
+
r"--cpus-per-gpu[=\s]": "CPUs per GPU",
|
|
36
|
+
# Time and resource constraints
|
|
37
|
+
r"--time[=\s]|-t\s": "runtime/time limit",
|
|
38
|
+
r"--constraint[=\s]|-C\s": "node constraints",
|
|
39
|
+
r"--qos[=\s]": "quality of service",
|
|
40
|
+
r"--nodes[=\s]|-N\s": "number of nodes",
|
|
41
|
+
r"--clusters[=\s]": "cluster specification",
|
|
42
|
+
# GPU options
|
|
43
|
+
r"--gres[=\s]": "generic resources (GRES)",
|
|
44
|
+
r"--gpus[=\s]": "GPU allocation",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def validate_slurm_extra(job):
|
|
49
|
+
"""
|
|
50
|
+
Validate that slurm_extra doesn't contain executor-managed options.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
job: Snakemake job object with resources attribute
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
WorkflowError: If forbidden SLURM options are found in slurm_extra
|
|
57
|
+
"""
|
|
58
|
+
# skip testing if no slurm_extra is set
|
|
59
|
+
slurm_extra = getattr(job.resources, "slurm_extra", None)
|
|
60
|
+
if not slurm_extra:
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
forbidden_options = get_forbidden_slurm_options()
|
|
64
|
+
|
|
65
|
+
for pattern, option_name in forbidden_options.items():
|
|
66
|
+
if re.search(pattern, slurm_extra):
|
|
67
|
+
raise WorkflowError(
|
|
68
|
+
f"The --{option_name.replace(' ', '-')} option is not "
|
|
69
|
+
f"allowed in the 'slurm_extra' parameter. "
|
|
70
|
+
f"The {option_name} is set by the snakemake executor plugin "
|
|
71
|
+
f"and must not be overwritten. "
|
|
72
|
+
f"Please use the appropriate snakemake resource "
|
|
73
|
+
f"specification instead. "
|
|
74
|
+
f"Consult the documentation for proper resource configuration."
|
|
75
|
+
)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: snakemake-executor-plugin-slurm
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: A Snakemake executor plugin for submitting jobs to a SLURM cluster.
|
|
5
5
|
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
6
7
|
Keywords: snakemake,plugin,executor,cluster,slurm
|
|
7
8
|
Author: Christian Meesters
|
|
8
9
|
Author-email: meesters@uni-mainz.de
|
|
@@ -12,6 +13,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
17
|
Requires-Dist: numpy (>=1.26.4,<3)
|
|
16
18
|
Requires-Dist: pandas (>=2.2.3,<3.0.0)
|
|
17
19
|
Requires-Dist: snakemake-executor-plugin-slurm-jobstep (>=0.3.0,<0.4.0)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
snakemake_executor_plugin_slurm/__init__.py,sha256=YUFJMWKlrbId4VgRreaea3B0JXJ_xTB1zNBJgAZ59Vw,33953
|
|
2
|
+
snakemake_executor_plugin_slurm/efficiency_report.py,sha256=3F6AqkKOZBn9MxI7T27u3FF3-OAxY_ee_toHXnTGirs,8812
|
|
3
|
+
snakemake_executor_plugin_slurm/submit_string.py,sha256=HL2QyT7-_HRu4yg_7tJSs1VtxGebNODOV8OQd2hYirg,5021
|
|
4
|
+
snakemake_executor_plugin_slurm/utils.py,sha256=7XVXtzu7bg_89wWZisW-Zk7TNQyEgK4v_y4Y3F9uOwc,4491
|
|
5
|
+
snakemake_executor_plugin_slurm/validation.py,sha256=hyWQWDjqHDLhO7caksYILTKjA2Ak91CMJeFwEcltYFo,2702
|
|
6
|
+
snakemake_executor_plugin_slurm-1.9.0.dist-info/METADATA,sha256=UxDm36Px5LDOM8PtDP3LMv8mHyWUzuuNqVD2CW1CA_Y,1507
|
|
7
|
+
snakemake_executor_plugin_slurm-1.9.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
8
|
+
snakemake_executor_plugin_slurm-1.9.0.dist-info/licenses/LICENSE,sha256=YVc4xTLWMqGfFL36120k7rzXtsT6e4RkJsh68VVn12s,1076
|
|
9
|
+
snakemake_executor_plugin_slurm-1.9.0.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
snakemake_executor_plugin_slurm/__init__.py,sha256=BOuvCB954jBaxqoh4ezFBYLStptsrESbPduDXMT0K9Q,33916
|
|
2
|
-
snakemake_executor_plugin_slurm/efficiency_report.py,sha256=crPfJDK4NojfRbu_wEw3ZmC3suMRABr5r-1rO5q3WEo,7429
|
|
3
|
-
snakemake_executor_plugin_slurm/submit_string.py,sha256=Cn9qopyQwBqs1MvZFxSyRR_7mZzCVj8_vO_JNzbiqew,2896
|
|
4
|
-
snakemake_executor_plugin_slurm/utils.py,sha256=7XVXtzu7bg_89wWZisW-Zk7TNQyEgK4v_y4Y3F9uOwc,4491
|
|
5
|
-
snakemake_executor_plugin_slurm-1.7.0.dist-info/LICENSE,sha256=YVc4xTLWMqGfFL36120k7rzXtsT6e4RkJsh68VVn12s,1076
|
|
6
|
-
snakemake_executor_plugin_slurm-1.7.0.dist-info/METADATA,sha256=XiUbU8KRQKmnK55EVkfrEKRYny3I4IF2WpbrfP43Acc,1434
|
|
7
|
-
snakemake_executor_plugin_slurm-1.7.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
8
|
-
snakemake_executor_plugin_slurm-1.7.0.dist-info/RECORD,,
|
|
File without changes
|