From 60684fe7f2bfc6c1ea488050b8a8f55c2891a600 Mon Sep 17 00:00:00 2001 From: mashehu Date: Thu, 2 Oct 2025 10:48:24 +0200 Subject: [PATCH 1/4] use same logic for super-tool selection in modules lint and bump-version --- nf_core/modules/bump_versions.py | 8 +------- nf_core/modules/lint/__init__.py | 2 +- nf_core/modules/modules_utils.py | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/nf_core/modules/bump_versions.py b/nf_core/modules/bump_versions.py index 87b60917c3..4a4fa79636 100644 --- a/nf_core/modules/bump_versions.py +++ b/nf_core/modules/bump_versions.py @@ -116,13 +116,7 @@ def bump_versions( raise nf_core.modules.modules_utils.ModuleExceptionError( "You cannot specify a tool and request all tools to be bumped." ) - # First try to find an exact match - exact_matches = [m for m in nfcore_modules if m.component_name == module] - if exact_matches: - nfcore_modules = exact_matches - else: - # If no exact match, look for modules that start with the given name (subtools) - nfcore_modules = [m for m in nfcore_modules if m.component_name.startswith(module + "/")] + nfcore_modules = nf_core.modules.modules_utils.filter_modules_by_name(nfcore_modules, module) if len(nfcore_modules) == 0: raise nf_core.modules.modules_utils.ModuleExceptionError( diff --git a/nf_core/modules/lint/__init__.py b/nf_core/modules/lint/__init__.py index 5cc9b8bf64..1b126ca550 100644 --- a/nf_core/modules/lint/__init__.py +++ b/nf_core/modules/lint/__init__.py @@ -143,7 +143,7 @@ def lint( if all_modules: raise LintExceptionError("You cannot specify a tool and request all tools to be linted.") local_modules = [] - remote_modules = [m for m in self.all_remote_components if m.component_name == module] + remote_modules = nf_core.modules.modules_utils.filter_modules_by_name(self.all_remote_components, module) if len(remote_modules) == 0: raise LintExceptionError(f"Could not find the specified module: '{module}'") else: diff --git a/nf_core/modules/modules_utils.py b/nf_core/modules/modules_utils.py index aa3d455bb1..b28a8afc8d 100644 --- a/nf_core/modules/modules_utils.py +++ b/nf_core/modules/modules_utils.py @@ -115,3 +115,23 @@ def load_edam(): if extension not in edam_formats: edam_formats[extension] = (fields[0], fields[1]) # URL, name return edam_formats + + +def filter_modules_by_name(modules: list[NFCoreComponent], module_name: str) -> list[NFCoreComponent]: + """ + Filter modules by name, supporting exact matches and tool family matching. + + Args: + modules (list[NFCoreComponent]): List of modules to filter + module_name (str): The module name or prefix to match + + Returns: + list[NFCoreComponent]: List of matching modules + """ + # First try to find an exact match + exact_matches = [m for m in modules if m.component_name == module_name] + if exact_matches: + return exact_matches + # If no exact match, look for modules that start with the given name (subtools) + print(modules) + return [m for m in modules if m.component_name.startswith(module_name)] From 4bf5a16f583745dcdd33ca1d21fb6faf23456221 Mon Sep 17 00:00:00 2001 From: nf-core-bot Date: Thu, 9 Oct 2025 14:33:53 +0000 Subject: [PATCH 2/4] [automated] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index adfe9528b6..f89e1a9a66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Support modules with `exec:` blocks ([#3633](https://github.com/nf-core/tools/pull/3633)) - feat: nf-core modules bump-version supports specifying the toolkit ([#3608](https://github.com/nf-core/tools/pull/3608)) +- use same logic for super-tool selection in modules lint and bump-version ([#3823](https://github.com/nf-core/tools/pull/3823)) ### Subworkflows From ee4d319a4bed9820fb035776e1fef0325be3b1da Mon Sep 17 00:00:00 2001 From: mashehu Date: Thu, 9 Oct 2025 17:28:35 +0200 Subject: [PATCH 3/4] add tests --- nf_core/modules/modules_utils.py | 1 - tests/modules/test_modules_utils.py | 65 +++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/nf_core/modules/modules_utils.py b/nf_core/modules/modules_utils.py index b28a8afc8d..5d091993b1 100644 --- a/nf_core/modules/modules_utils.py +++ b/nf_core/modules/modules_utils.py @@ -133,5 +133,4 @@ def filter_modules_by_name(modules: list[NFCoreComponent], module_name: str) -> if exact_matches: return exact_matches # If no exact match, look for modules that start with the given name (subtools) - print(modules) return [m for m in modules if m.component_name.startswith(module_name)] diff --git a/tests/modules/test_modules_utils.py b/tests/modules/test_modules_utils.py index 763725337b..009f177c1d 100644 --- a/tests/modules/test_modules_utils.py +++ b/tests/modules/test_modules_utils.py @@ -18,3 +18,68 @@ def test_get_installed_modules_with_files(self): _, nfcore_modules = nf_core.modules.modules_utils.get_installed_modules(self.nfcore_modules) assert len(nfcore_modules) == 1 + + def test_filter_modules_by_name_exact_match(self): + """Test filtering modules by name with an exact match""" + # install bpipe/test + _, nfcore_modules = nf_core.modules.modules_utils.get_installed_modules(self.nfcore_modules) + + # Test exact match + filtered = nf_core.modules.modules_utils.filter_modules_by_name(nfcore_modules, "bpipe/test") + assert len(filtered) == 1 + assert filtered[0].component_name == "bpipe/test" + + def test_filter_modules_by_name_tool_family(self): + """Test filtering modules by name to get all subtools of a super-tool""" + # Create some mock samtools subtools in the modules directory + samtools_dir = self.nfcore_modules / "modules" / "nf-core" / "samtools" + + for subtool in ["view", "sort", "index"]: + subtool_dir = samtools_dir / subtool + subtool_dir.mkdir(parents=True, exist_ok=True) + (subtool_dir / "main.nf").touch() + + # Get the modules + _, nfcore_modules = nf_core.modules.modules_utils.get_installed_modules(self.nfcore_modules) + + # Test filtering by tool family (super-tool) + filtered = nf_core.modules.modules_utils.filter_modules_by_name(nfcore_modules, "samtools") + assert len(filtered) == 3 + assert all(m.component_name.startswith("samtools") for m in filtered) + assert set(m.component_name for m in filtered) == {"samtools/view", "samtools/sort", "samtools/index"} + + def test_filter_modules_by_name_exact_match_preferred(self): + """Test that exact matches are preferred over prefix matches""" + # Create a samtools super-tool and its subtools + samtools_dir = self.nfcore_modules / "modules" / "nf-core" / "samtools" + samtools_dir.mkdir(parents=True, exist_ok=True) + (samtools_dir / "main.nf").touch() + + # Create subtools + for subtool in ["view", "sort"]: + subtool_dir = samtools_dir / subtool + subtool_dir.mkdir(parents=True, exist_ok=True) + (subtool_dir / "main.nf").touch() + + # Get the modules + _, nfcore_modules = nf_core.modules.modules_utils.get_installed_modules(self.nfcore_modules) + + # Test that exact match is returned when it exists + filtered = nf_core.modules.modules_utils.filter_modules_by_name(nfcore_modules, "samtools") + assert len(filtered) == 1 + assert filtered[0].component_name == "samtools" + + def test_filter_modules_by_name_no_match(self): + """Test filtering modules by name with no matches""" + _, nfcore_modules = nf_core.modules.modules_utils.get_installed_modules(self.nfcore_modules) + + # Test no match + filtered = nf_core.modules.modules_utils.filter_modules_by_name(nfcore_modules, "nonexistent") + assert len(filtered) == 0 + + def test_filter_modules_by_name_empty_list(self): + """Test filtering an empty list of modules""" + modules = [] + + filtered = nf_core.modules.modules_utils.filter_modules_by_name(modules, "fastqc") + assert len(filtered) == 0 From 22be19252900314aaf046a7604fc8fdfac67e9a9 Mon Sep 17 00:00:00 2001 From: mashehu Date: Fri, 10 Oct 2025 16:33:36 +0200 Subject: [PATCH 4/4] remove redundant asserts --- tests/modules/test_modules_utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/modules/test_modules_utils.py b/tests/modules/test_modules_utils.py index 009f177c1d..9a19f06159 100644 --- a/tests/modules/test_modules_utils.py +++ b/tests/modules/test_modules_utils.py @@ -44,8 +44,7 @@ def test_filter_modules_by_name_tool_family(self): # Test filtering by tool family (super-tool) filtered = nf_core.modules.modules_utils.filter_modules_by_name(nfcore_modules, "samtools") - assert len(filtered) == 3 - assert all(m.component_name.startswith("samtools") for m in filtered) + assert set(m.component_name for m in filtered) == {"samtools/view", "samtools/sort", "samtools/index"} def test_filter_modules_by_name_exact_match_preferred(self):