snakemake-executor-plugin-slurm 1.6.1__py3-none-any.whl → 1.8.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.

@@ -140,6 +140,14 @@ class ExecutorSettings(ExecutorSettingsBase):
140
140
  "This flag has no effect, if not set.",
141
141
  },
142
142
  )
143
+ qos: Optional[str] = field(
144
+ default=None,
145
+ metadata={
146
+ "help": "If set, the given QoS will be used for job submission.",
147
+ "env_var": False,
148
+ "required": False,
149
+ },
150
+ )
143
151
  reservation: Optional[str] = field(
144
152
  default=None,
145
153
  metadata={
@@ -299,7 +307,7 @@ class Executor(RemoteExecutor):
299
307
  "run_uuid": self.run_uuid,
300
308
  "slurm_logfile": slurm_logfile,
301
309
  "comment_str": comment_str,
302
- "account": self.get_account_arg(job),
310
+ "account": next(self.get_account_arg(job)),
303
311
  "partition": self.get_partition_arg(job),
304
312
  "workdir": self.workflow.workdir_init,
305
313
  }
@@ -309,6 +317,9 @@ class Executor(RemoteExecutor):
309
317
  if self.workflow.executor_settings.requeue:
310
318
  call += " --requeue"
311
319
 
320
+ if self.workflow.executor_settings.qos:
321
+ call += f" --qos={self.workflow.executor_settings.qos}"
322
+
312
323
  if self.workflow.executor_settings.reservation:
313
324
  call += f" --reservation={self.workflow.executor_settings.reservation}"
314
325
 
@@ -657,10 +668,21 @@ We leave it to SLURM to resume your job(s)"""
657
668
  else raises an error - implicetly.
658
669
  """
659
670
  if job.resources.get("slurm_account"):
660
- # here, we check whether the given or guessed account is valid
661
- # if not, a WorkflowError is raised
662
- self.test_account(job.resources.slurm_account)
663
- return f" -A '{job.resources.slurm_account}'"
671
+ # split the account upon ',' and whitespace, to allow
672
+ # multiple accounts being given
673
+ accounts = [
674
+ a for a in re.split(r"[,\s]+", job.resources.slurm_account) if a
675
+ ]
676
+ for account in accounts:
677
+ # here, we check whether the given or guessed account is valid
678
+ # if not, a WorkflowError is raised
679
+ self.test_account(account)
680
+ # sbatch only allows one account per submission
681
+ # yield one after the other, if multiple were given
682
+ # we have to quote the account, because it might
683
+ # contain build-in shell commands - see issue #354
684
+ for account in accounts:
685
+ yield f" -A {shlex.quote(account)}"
664
686
  else:
665
687
  if self._fallback_account_arg is None:
666
688
  self.logger.warning("No SLURM account given, trying to guess.")
@@ -668,7 +690,7 @@ We leave it to SLURM to resume your job(s)"""
668
690
  if account:
669
691
  self.logger.warning(f"Guessed SLURM account: {account}")
670
692
  self.test_account(f"{account}")
671
- self._fallback_account_arg = f" -A {account}"
693
+ self._fallback_account_arg = f" -A {shlex.quote(account)}"
672
694
  else:
673
695
  self.logger.warning(
674
696
  "Unable to guess SLURM account. Trying to proceed without."
@@ -676,7 +698,7 @@ We leave it to SLURM to resume your job(s)"""
676
698
  self._fallback_account_arg = (
677
699
  "" # no account specific args for sbatch
678
700
  )
679
- return self._fallback_account_arg
701
+ yield self._fallback_account_arg
680
702
 
681
703
  def get_partition_arg(self, job: JobExecutorInterface):
682
704
  """
@@ -691,7 +713,9 @@ We leave it to SLURM to resume your job(s)"""
691
713
  self._fallback_partition = self.get_default_partition(job)
692
714
  partition = self._fallback_partition
693
715
  if partition:
694
- return f" -p {partition}"
716
+ # we have to quote the partition, because it might
717
+ # contain build-in shell commands
718
+ return f" -p {shlex.quote(partition)}"
695
719
  else:
696
720
  return ""
697
721
 
@@ -719,32 +743,35 @@ We leave it to SLURM to resume your job(s)"""
719
743
  """
720
744
  tests whether the given account is registered, raises an error, if not
721
745
  """
722
- cmd = "sshare -U --format Account%256 --noheader"
746
+ # first we need to test with sacctmgr because sshare might not
747
+ # work in a multicluster environment
748
+ cmd = f'sacctmgr -n -s list user "{os.environ["USER"]}" format=account%256'
749
+ sacctmgr_report = sshare_report = ""
723
750
  try:
724
751
  accounts = subprocess.check_output(
725
752
  cmd, shell=True, text=True, stderr=subprocess.PIPE
726
753
  )
727
754
  except subprocess.CalledProcessError as e:
728
- sshare_report = (
755
+ sacctmgr_report = (
729
756
  "Unable to test the validity of the given or guessed"
730
- f" SLURM account '{account}' with sshare: {e.stderr}."
757
+ f" SLURM account '{account}' with sacctmgr: {e.stderr}."
731
758
  )
732
759
  accounts = ""
733
760
 
734
761
  if not accounts.strip():
735
- cmd = f'sacctmgr -n -s list user "{os.environ["USER"]}" format=account%256'
762
+ cmd = "sshare -U --format Account%256 --noheader"
736
763
  try:
737
764
  accounts = subprocess.check_output(
738
765
  cmd, shell=True, text=True, stderr=subprocess.PIPE
739
766
  )
740
767
  except subprocess.CalledProcessError as e:
741
- sacctmgr_report = (
768
+ sshare_report = (
742
769
  "Unable to test the validity of the given or guessed "
743
- f"SLURM account '{account}' with sacctmgr: {e.stderr}."
770
+ f"SLURM account '{account}' with sshare: {e.stderr}."
744
771
  )
745
772
  raise WorkflowError(
746
- f"The 'sshare' reported: '{sshare_report}' "
747
- f"and likewise 'sacctmgr' reported: '{sacctmgr_report}'."
773
+ f"The 'sacctmgr' reported: '{sacctmgr_report}' "
774
+ f"and likewise 'sshare' reported: '{sshare_report}'."
748
775
  )
749
776
 
750
777
  # The set() has been introduced during review to eliminate
@@ -753,7 +780,7 @@ We leave it to SLURM to resume your job(s)"""
753
780
 
754
781
  if not accounts:
755
782
  self.logger.warning(
756
- f"Both 'sshare' and 'sacctmgr' returned empty results for account "
783
+ f"Both 'sacctmgr' and 'sshare' returned empty results for account "
757
784
  f"'{account}'. Proceeding without account validation."
758
785
  )
759
786
  return ""
@@ -1,5 +1,18 @@
1
1
  from snakemake_executor_plugin_slurm_jobstep import get_cpu_setting
2
2
  from types import SimpleNamespace
3
+ import shlex
4
+
5
+
6
+ def safe_quote(value):
7
+ """
8
+ Safely quote a parameter value using shlex.quote.
9
+ Handles None values and converts to string if needed.
10
+ Returns empty quotes for empty strings.
11
+ """
12
+ str_value = str(value)
13
+ if str_value == "":
14
+ return "''"
15
+ return shlex.quote(str_value)
3
16
 
4
17
 
5
18
  def get_submit_command(job, params):
@@ -10,37 +23,41 @@ def get_submit_command(job, params):
10
23
  params = SimpleNamespace(**params)
11
24
 
12
25
  call = (
13
- f"sbatch "
14
- f"--parsable "
15
- f"--job-name {params.run_uuid} "
16
- f'--output "{params.slurm_logfile}" '
17
- f"--export=ALL "
18
- f'--comment "{params.comment_str}"'
26
+ "sbatch "
27
+ "--parsable "
28
+ f"--job-name {safe_quote(params.run_uuid)} "
29
+ f"--output {safe_quote(params.slurm_logfile)} "
30
+ "--export=ALL "
31
+ f"--comment {safe_quote(params.comment_str)}"
19
32
  )
20
33
 
21
34
  # No accout or partition checking is required, here.
22
35
  # Checking is done in the submit function.
23
36
 
24
37
  # here, only the string is used, as it already contains
25
- # '-A {account_name}'
38
+ # "-A '{account_name}'"
26
39
  call += f" {params.account}"
27
40
  # here, only the string is used, as it already contains
28
- # '- p {partition_name}'
41
+ # "- p '{partition_name}'"
29
42
  call += f" {params.partition}"
30
43
 
31
44
  if job.resources.get("clusters"):
32
- call += f" --clusters {job.resources.clusters}"
45
+ call += f" --clusters {safe_quote(job.resources.clusters)}"
33
46
 
34
47
  if job.resources.get("runtime"):
35
- call += f" -t {job.resources.runtime}"
48
+ call += f" -t {safe_quote(job.resources.runtime)}"
36
49
 
37
- if job.resources.get("constraint") or isinstance(
38
- job.resources.get("constraint"), str
39
- ):
40
- call += f" -C '{job.resources.get('constraint')}'"
50
+ # Both, constraint and qos are optional.
51
+ # If not set, they will not be added to the sbatch call.
52
+ # If explicitly set to an empty string,
53
+ # `--constraint ''` or `--qos ''` will be added.
54
+ constraint = job.resources.get("constraint")
55
+ if constraint is not None:
56
+ call += f" -C {safe_quote(constraint)}"
41
57
 
42
- if job.resources.get("qos") or isinstance(job.resources.get("qos"), str):
43
- call += f" --qos='{job.resources.qos}'"
58
+ qos = job.resources.get("qos")
59
+ if qos is not None:
60
+ call += f" --qos={safe_quote(qos)}"
44
61
 
45
62
  if job.resources.get("mem_mb_per_cpu"):
46
63
  call += f" --mem-per-cpu {job.resources.mem_mb_per_cpu}"
@@ -77,6 +94,7 @@ def get_submit_command(job, params):
77
94
  # ensure that workdir is set correctly
78
95
  # use short argument as this is the same in all slurm versions
79
96
  # (see https://github.com/snakemake/snakemake/issues/2014)
80
- call += f" -D '{params.workdir}'"
97
+ if params.workdir:
98
+ call += f" -D {safe_quote(params.workdir)}"
81
99
 
82
100
  return call
@@ -1,8 +1,9 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: snakemake-executor-plugin-slurm
3
- Version: 1.6.1
3
+ Version: 1.8.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,8 @@
1
+ snakemake_executor_plugin_slurm/__init__.py,sha256=G40Rbm62GFQF7bo38OJBFEFAxa4BLYeNO66wPQuki-c,34755
2
+ snakemake_executor_plugin_slurm/efficiency_report.py,sha256=crPfJDK4NojfRbu_wEw3ZmC3suMRABr5r-1rO5q3WEo,7429
3
+ snakemake_executor_plugin_slurm/submit_string.py,sha256=12DaJ9BIqEMC19dKuViPU8W5mZRjBRtNDQQu7M-iEjM,3448
4
+ snakemake_executor_plugin_slurm/utils.py,sha256=7XVXtzu7bg_89wWZisW-Zk7TNQyEgK4v_y4Y3F9uOwc,4491
5
+ snakemake_executor_plugin_slurm-1.8.0.dist-info/METADATA,sha256=8-ktC0sGhHDPOAyxpFl1LwWc7wPueZ2PxREKrQguhME,1507
6
+ snakemake_executor_plugin_slurm-1.8.0.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
7
+ snakemake_executor_plugin_slurm-1.8.0.dist-info/licenses/LICENSE,sha256=YVc4xTLWMqGfFL36120k7rzXtsT6e4RkJsh68VVn12s,1076
8
+ snakemake_executor_plugin_slurm-1.8.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.3
2
+ Generator: poetry-core 2.2.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,8 +0,0 @@
1
- snakemake_executor_plugin_slurm/__init__.py,sha256=gQA7SwXAcpXAovDC7jlCKczjd6L3gLb-n37rGSm1MY8,33570
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.6.1.dist-info/LICENSE,sha256=YVc4xTLWMqGfFL36120k7rzXtsT6e4RkJsh68VVn12s,1076
6
- snakemake_executor_plugin_slurm-1.6.1.dist-info/METADATA,sha256=12IQgnU3tuz2cQ6N6iqhyzSmR7BD_mt9HWfenEse2r4,1434
7
- snakemake_executor_plugin_slurm-1.6.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
8
- snakemake_executor_plugin_slurm-1.6.1.dist-info/RECORD,,