snakemake-executor-plugin-sge 0.6.0__tar.gz → 0.6.2__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.
- {snakemake_executor_plugin_sge-0.6.0 → snakemake_executor_plugin_sge-0.6.2}/PKG-INFO +1 -1
- {snakemake_executor_plugin_sge-0.6.0 → snakemake_executor_plugin_sge-0.6.2}/pyproject.toml +1 -1
- {snakemake_executor_plugin_sge-0.6.0 → snakemake_executor_plugin_sge-0.6.2}/snakemake_executor_plugin_sge/__init__.py +47 -19
- {snakemake_executor_plugin_sge-0.6.0 → snakemake_executor_plugin_sge-0.6.2}/README.md +0 -0
- {snakemake_executor_plugin_sge-0.6.0 → snakemake_executor_plugin_sge-0.6.2}/snakemake_executor_plugin_sge/job_cancellation.py +0 -0
- {snakemake_executor_plugin_sge-0.6.0 → snakemake_executor_plugin_sge-0.6.2}/snakemake_executor_plugin_sge/job_status_query.py +0 -0
- {snakemake_executor_plugin_sge-0.6.0 → snakemake_executor_plugin_sge-0.6.2}/snakemake_executor_plugin_sge/submit_string.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "snakemake-executor-plugin-sge"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.2"
|
|
4
4
|
description = "A Snakemake executor plugin for submitting jobs to SGE/UGE/OGS clusters."
|
|
5
5
|
authors = ["Stylianos Charalampous <stylianosc@github.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -153,7 +153,7 @@ class ExecutorSettings(ExecutorSettingsBase):
|
|
|
153
153
|
metadata={
|
|
154
154
|
"help": (
|
|
155
155
|
"Directory for SGE log files. Defaults to "
|
|
156
|
-
"'.sge_logs' relative to the working directory. "
|
|
156
|
+
"'.snakemake/sge_logs' relative to the working directory. "
|
|
157
157
|
"Absolute paths are used as-is; relative paths are resolved "
|
|
158
158
|
"against the workflow working directory."
|
|
159
159
|
),
|
|
@@ -306,7 +306,7 @@ def _resolve_logdir(workflow) -> Path:
|
|
|
306
306
|
elif logdir:
|
|
307
307
|
return Path(workflow.workdir_init) / logdir
|
|
308
308
|
else:
|
|
309
|
-
return (Path(workflow.workdir_init) / ".sge_logs").resolve()
|
|
309
|
+
return (Path(workflow.workdir_init) / ".snakemake" / "sge_logs").resolve()
|
|
310
310
|
|
|
311
311
|
|
|
312
312
|
def _wildcard_sort_key(job: JobExecutorInterface):
|
|
@@ -351,8 +351,8 @@ class Executor(RemoteExecutor):
|
|
|
351
351
|
)
|
|
352
352
|
self.logger.info(f"SGE run ID: {self.run_uuid}")
|
|
353
353
|
|
|
354
|
-
self.
|
|
355
|
-
self.
|
|
354
|
+
self.sge_logdir_default = _resolve_logdir(self.workflow)
|
|
355
|
+
self.sge_logdir_default.mkdir(parents=True, exist_ok=True)
|
|
356
356
|
|
|
357
357
|
self._job_submission_executor = ThreadPoolExecutor(
|
|
358
358
|
max_workers=4, thread_name_prefix="sge_job_submit"
|
|
@@ -616,18 +616,37 @@ class Executor(RemoteExecutor):
|
|
|
616
616
|
dep_ids.append(base_id)
|
|
617
617
|
return dep_ids
|
|
618
618
|
|
|
619
|
+
def _get_job_logdir(self, job: JobExecutorInterface) -> Path:
|
|
620
|
+
"""Get the SGE log directory for a job.
|
|
621
|
+
|
|
622
|
+
If the job specifies a workdir resource, place logs in
|
|
623
|
+
{workdir}/.snakemake/sge_logs. Otherwise use the default logdir.
|
|
624
|
+
"""
|
|
625
|
+
workdir = job.resources.get("workdir") if hasattr(job, "resources") else None
|
|
626
|
+
if workdir:
|
|
627
|
+
return (Path(workdir) / ".snakemake" / "sge_logs").resolve()
|
|
628
|
+
return self.sge_logdir_default
|
|
629
|
+
|
|
619
630
|
def run_job(self, job: JobExecutorInterface) -> None:
|
|
620
631
|
"""Submit a single job via qsub."""
|
|
621
|
-
#
|
|
632
|
+
# Determine job-specific log directory (uses workdir resource if available)
|
|
633
|
+
job_logdir = self._get_job_logdir(job)
|
|
634
|
+
job_logdir.mkdir(parents=True, exist_ok=True)
|
|
635
|
+
|
|
636
|
+
# Log files are stored directly in job_logdir with job ID
|
|
622
637
|
# stdout: $JOB_ID.log, stderr: $JOB_ID.error
|
|
623
|
-
log_stdout =
|
|
624
|
-
log_stderr =
|
|
638
|
+
log_stdout = job_logdir / "$JOB_ID.log"
|
|
639
|
+
log_stderr = job_logdir / "$JOB_ID.error"
|
|
640
|
+
|
|
641
|
+
# Use job's workdir resource if available, else fall back to workflow workdir
|
|
642
|
+
workdir = job.resources.get("workdir") if hasattr(job, "resources") else None
|
|
643
|
+
workdir = workdir or self.workflow.workdir_init
|
|
625
644
|
|
|
626
645
|
job_params = {
|
|
627
646
|
"run_uuid": self.run_uuid,
|
|
628
647
|
"log_stdout": log_stdout,
|
|
629
648
|
"log_stderr": log_stderr,
|
|
630
|
-
"workdir":
|
|
649
|
+
"workdir": workdir,
|
|
631
650
|
}
|
|
632
651
|
|
|
633
652
|
# Resolve upstream SGE job IDs for -hold_jid (needed for --immediate-submit)
|
|
@@ -674,15 +693,15 @@ class Executor(RemoteExecutor):
|
|
|
674
693
|
|
|
675
694
|
self.logger.info(
|
|
676
695
|
f"Job {job.jobid} submitted as SGE job {sge_jobid} "
|
|
677
|
-
f"(log: {
|
|
696
|
+
f"(log: {job_logdir})"
|
|
678
697
|
)
|
|
679
698
|
self._submitted_job_ids.append(sge_jobid)
|
|
680
699
|
# Record the job→SGE-id mapping BEFORE notifying Snakemake so any
|
|
681
700
|
# downstream submission triggered by the report sees it.
|
|
682
701
|
self._job_to_sge[job] = (sge_jobid, None)
|
|
683
702
|
# Resolve the actual log path now that we have the job ID
|
|
684
|
-
log_stdout_resolved =
|
|
685
|
-
log_stderr_resolved =
|
|
703
|
+
log_stdout_resolved = job_logdir / f"{sge_jobid}.log"
|
|
704
|
+
log_stderr_resolved = job_logdir / f"{sge_jobid}.error"
|
|
686
705
|
self._report_submission_threadsafe(
|
|
687
706
|
SubmittedJobInfo(
|
|
688
707
|
job,
|
|
@@ -724,8 +743,12 @@ class Executor(RemoteExecutor):
|
|
|
724
743
|
else f"rule_{jobs[0].name}"
|
|
725
744
|
)
|
|
726
745
|
|
|
746
|
+
# Determine array-specific log directory from first job's workdir resource
|
|
747
|
+
first_job_logdir = self._get_job_logdir(jobs[0])
|
|
748
|
+
first_job_logdir.mkdir(parents=True, exist_ok=True)
|
|
749
|
+
|
|
727
750
|
# Helper files (manifests, scripts, task maps) are stored in .meta subdirectory
|
|
728
|
-
meta_dir =
|
|
751
|
+
meta_dir = first_job_logdir / ".meta" / group_or_rule
|
|
729
752
|
meta_dir.mkdir(parents=True, exist_ok=True)
|
|
730
753
|
|
|
731
754
|
# Determine the global starting task index for this batch.
|
|
@@ -828,11 +851,15 @@ class Executor(RemoteExecutor):
|
|
|
828
851
|
script_path.write_text(script_content)
|
|
829
852
|
script_path.chmod(0o755)
|
|
830
853
|
|
|
854
|
+
# Use first job's workdir resource if available, else fall back to workflow workdir
|
|
855
|
+
workdir = jobs[0].resources.get("workdir") if hasattr(jobs[0], "resources") else None
|
|
856
|
+
workdir = workdir or self.workflow.workdir_init
|
|
857
|
+
|
|
831
858
|
job_params = {
|
|
832
859
|
"run_uuid": self.run_uuid,
|
|
833
|
-
"log_stdout":
|
|
834
|
-
"log_stderr":
|
|
835
|
-
"workdir":
|
|
860
|
+
"log_stdout": first_job_logdir / "$JOB_ID.$TASK_ID.log",
|
|
861
|
+
"log_stderr": first_job_logdir / "$JOB_ID.$TASK_ID.error",
|
|
862
|
+
"workdir": workdir,
|
|
836
863
|
"array_range": f"{chunk_start}-{chunk_end}",
|
|
837
864
|
}
|
|
838
865
|
|
|
@@ -921,8 +948,9 @@ class Executor(RemoteExecutor):
|
|
|
921
948
|
# Register each task with Snakemake
|
|
922
949
|
for task_idx, job in enumerate(chunk_jobs, start=chunk_start):
|
|
923
950
|
external_id = f"{sge_jobid}.{task_idx}"
|
|
924
|
-
|
|
925
|
-
|
|
951
|
+
# Each job in the array may have its own logdir; use the first job's for the array
|
|
952
|
+
log_o = first_job_logdir / f"{sge_jobid}.{task_idx}.log"
|
|
953
|
+
log_e = first_job_logdir / f"{sge_jobid}.{task_idx}.error"
|
|
926
954
|
self._report_submission_threadsafe(
|
|
927
955
|
SubmittedJobInfo(
|
|
928
956
|
job,
|
|
@@ -1052,7 +1080,7 @@ class Executor(RemoteExecutor):
|
|
|
1052
1080
|
self.logger.debug(
|
|
1053
1081
|
f"Cleaning SGE log files older than {age_cutoff} day(s)."
|
|
1054
1082
|
)
|
|
1055
|
-
for path in self.
|
|
1083
|
+
for path in self.sge_logdir_default.rglob("*"):
|
|
1056
1084
|
if path.is_file():
|
|
1057
1085
|
try:
|
|
1058
1086
|
if now - path.stat().st_mtime > cutoff_secs:
|
|
@@ -1060,7 +1088,7 @@ class Executor(RemoteExecutor):
|
|
|
1060
1088
|
except OSError as exc:
|
|
1061
1089
|
self.logger.warning(f"Could not delete log {path}: {exc}")
|
|
1062
1090
|
# Clean up empty directories
|
|
1063
|
-
for path in sorted(self.
|
|
1091
|
+
for path in sorted(self.sge_logdir_default.rglob("*"), reverse=True):
|
|
1064
1092
|
if path.is_dir():
|
|
1065
1093
|
try:
|
|
1066
1094
|
path.rmdir() # Only removes if empty
|
|
File without changes
|
|
File without changes
|
|
File without changes
|