From 0158ec16c79c400a0c72e28f375a846adaffd4df Mon Sep 17 00:00:00 2001 From: ahdamin Date: Tue, 1 Sep 2026 18:56:38 +0200 Subject: [PATCH 1/2] fix: support configured nextflow directories --- cg/models/cg_config.py | 10 +++++ .../configurator/implementations/nextflow.py | 9 +++- .../test_nextflow_configurator.py | 45 +++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/cg/models/cg_config.py b/cg/models/cg_config.py index dc4ad2da493..213904669aa 100644 --- a/cg/models/cg_config.py +++ b/cg/models/cg_config.py @@ -257,6 +257,7 @@ class MipConfig(BaseModel): class NalloConfig(CommonAppConfig): binary_path: str | None = None + case_run_directory: str | None = None conda_binary: str | None = None conda_env: str config: str @@ -277,6 +278,7 @@ class NalloConfig(CommonAppConfig): slurm: SlurmConfig tower_workflow: str variant_catalog: Path + work_dir: str | None = None workflow_bin_path: str @@ -299,6 +301,7 @@ class GCNVCallerFiles(BaseModel): class RarediseaseConfig(CommonAppConfig): binary_path: str | None = None + case_run_directory: str | None = None conda_binary: str | None = None conda_env: str config: str @@ -323,11 +326,13 @@ class RarediseaseConfig(CommonAppConfig): tower_workflow: str variant_catalog: Path verifybamid_svd: VerifybamidSvdFilesSet + work_dir: str | None = None workflow_bin_path: str class TomteConfig(CommonAppConfig): binary_path: str | None = None + case_run_directory: str | None = None conda_binary: str | None = None conda_env: str config: str @@ -342,11 +347,13 @@ class TomteConfig(CommonAppConfig): root: str slurm: SlurmConfig tower_workflow: str + work_dir: str | None = None workflow_bin_path: str class RnafusionConfig(CommonAppConfig): binary_path: str + case_run_directory: str | None = None conda_binary: str | None = None conda_env: str config: str @@ -362,11 +369,13 @@ class RnafusionConfig(CommonAppConfig): root: str slurm: SlurmConfig tower_workflow: str + work_dir: str | None = None workflow_bin_path: str class TaxprofilerConfig(CommonAppConfig): binary_path: str + case_run_directory: str | None = None conda_binary: str | None = None conda_env: str config: str @@ -381,6 +390,7 @@ class TaxprofilerConfig(CommonAppConfig): root: str slurm: SlurmConfig tower_workflow: str + work_dir: str | None = None workflow_bin_path: str diff --git a/cg/services/analysis_starter/configurator/implementations/nextflow.py b/cg/services/analysis_starter/configurator/implementations/nextflow.py index 86c8746485f..d9c67410d00 100644 --- a/cg/services/analysis_starter/configurator/implementations/nextflow.py +++ b/cg/services/analysis_starter/configurator/implementations/nextflow.py @@ -31,6 +31,8 @@ def __init__( pipeline_extension: PipelineExtension = PipelineExtension(), ): self.root_dir: str = pipeline_config.root + self.case_run_directory: str | None = getattr(pipeline_config, "case_run_directory", None) + self.work_dir: str | None = getattr(pipeline_config, "work_dir", None) self.pipeline_repository = pipeline_config.repository self.pipeline_revision = pipeline_config.revision self.config_profiles = [pipeline_config.profile] @@ -115,7 +117,8 @@ def _get_sample_sheet_path(self, case_id: str) -> Path: def _get_case_run_directory(self, case_id: str) -> Path: """Path to case working directory.""" - return Path(self.root_dir, case_id) + run_directory: str = self.case_run_directory or self.root_dir + return Path(run_directory, case_id) def _create_case_directory(self, case_id: str) -> None: """Create case working directory.""" @@ -123,7 +126,9 @@ def _create_case_directory(self, case_id: str) -> None: case_path.mkdir(parents=True, exist_ok=True) def _get_work_dir(self, case_id: str) -> Path: - return Path(self.root_dir, case_id, "work") + if self.work_dir: + return Path(self.work_dir, case_id) + return Path(self._get_case_run_directory(case_id=case_id), "work") def _ensure_required_config_files_exist(self, config: NextflowCaseConfig) -> None: """ diff --git a/tests/services/analysis_starter/test_nextflow_configurator.py b/tests/services/analysis_starter/test_nextflow_configurator.py index 23b72974dd8..188f0f2678f 100644 --- a/tests/services/analysis_starter/test_nextflow_configurator.py +++ b/tests/services/analysis_starter/test_nextflow_configurator.py @@ -1,5 +1,6 @@ from datetime import datetime from pathlib import Path +from types import SimpleNamespace from typing import Callable, cast from unittest.mock import Mock, create_autospec @@ -105,6 +106,50 @@ def test_get_config( ) +def test_get_config_uses_configured_case_and_work_directories( + nextflow_case_id: str, + mocker: MockerFixture, +): + pipeline_config = SimpleNamespace( + root="/root", + case_run_directory="/launch/raredisease", + work_dir="/work/raredisease", + repository="https://repo.scilifelab.se", + revision="rev123", + profile="profile", + pre_run_script="some_script.sh", + ) + store_mock = create_autospec(Store) + store_mock.get_case_workflow = Mock(return_value=Workflow.RAREDISEASE) + store_mock.get_case_priority = Mock(return_value=SlurmQos.NORMAL) + pipeline_extension = create_autospec(PipelineExtension) + configurator = NextflowConfigurator( + config_file_creator=create_autospec(NextflowConfigFileCreator), + params_file_creator=create_autospec(RarediseaseParamsFileCreator), + pipeline_config=pipeline_config, + sample_sheet_creator=create_autospec(RarediseaseSampleSheetCreator), + store=store_mock, + pipeline_extension=pipeline_extension, + ) + mocker.patch.object(Path, "exists", return_value=True) + + case_config = configurator.get_config(case_id=nextflow_case_id) + + expected_case_directory = Path("/launch/raredisease", nextflow_case_id) + assert ( + case_config.nextflow_config_file + == Path(expected_case_directory, f"{nextflow_case_id}_nextflow_config.json").as_posix() + ) + assert ( + case_config.params_file + == Path(expected_case_directory, f"{nextflow_case_id}_params_file.yaml").as_posix() + ) + assert case_config.work_dir == Path("/work/raredisease", nextflow_case_id).as_posix() + cast(Mock, pipeline_extension.do_required_files_exist).assert_called_once_with( + case_run_directory=expected_case_directory + ) + + def test_get_config_missing_required_files(mocker: MockerFixture): # GIVEN a nextflow configurator From f5a4a870036c1375277a201d05a2505d727c55b2 Mon Sep 17 00:00:00 2001 From: ahdamin Date: Sat, 5 Sep 2026 15:35:21 +0200 Subject: [PATCH 2/2] fix: silence override cycle warning --- cg/apps/demultiplex/sample_sheet/override_cycles_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cg/apps/demultiplex/sample_sheet/override_cycles_validator.py b/cg/apps/demultiplex/sample_sheet/override_cycles_validator.py index 88e2a0b50ac..b0c2099d0e1 100644 --- a/cg/apps/demultiplex/sample_sheet/override_cycles_validator.py +++ b/cg/apps/demultiplex/sample_sheet/override_cycles_validator.py @@ -41,7 +41,7 @@ def set_attributes_from_sample(self, sample: dict[str, str]) -> None: def _is_index_cycle_value_following_pattern( pattern: str, index_cycle: str, run_cycles: int, index_sequence: str ) -> bool: - """ + r""" Returns whether an index cycle string is following a valid cycle regex pattern and has consistent values. Valid patterns are 'I(\d+)N(\d+)' and 'N(\d+)I(\d+)'. Having consistent values means that the sum of the number of index characters (I) and the number of ignored