From e822ab562b1a2456260ad5f436171e1ea71cb9dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:44:57 +0000 Subject: [PATCH 1/2] Initial plan From 1191f2ef1bda47ae09939e925e3d9cb45ddda004 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 23:10:10 +0000 Subject: [PATCH 2/2] Fix code quality issues: wrong license header/docstring, deprecated rich-click API, unused R parameter, DESeq2 PATH check, curly-quote parser bug, test registry pollution Co-authored-by: cokelaer <778821+cokelaer@users.noreply.github.com> --- test/conftest.py | 15 +++++++++++++++ test/test_parser.py | 14 +++++++++++++- versionix/parser.py | 20 ++++++++++++++------ versionix/registry.py | 6 +----- versionix/scripts.py | 38 +++++++++++++------------------------- 5 files changed, 56 insertions(+), 37 deletions(-) create mode 100644 test/conftest.py diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..27fe30c --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,15 @@ +import pytest +import versionix.registry as _registry + + +@pytest.fixture(autouse=True) +def _inject_test_registry_entries(): + """Inject test-only entries into the registry for the duration of each test.""" + test_entries = { + "for_testing_empty_parsers": {"options": "", "parsers": []}, + "for_testing_no_parsers": {"options": ""}, + } + _registry.metadata.update(test_entries) + yield + for key in test_entries: + _registry.metadata.pop(key, None) diff --git a/test/test_parser.py b/test/test_parser.py index bc38bf4..a260aab 100644 --- a/test/test_parser.py +++ b/test/test_parser.py @@ -105,7 +105,6 @@ def test_script(fp, mocker): print(result) runner.invoke(main, ["--registered"]) - runner.invoke(main, ["--stats"]) runner.invoke( main, ) @@ -121,6 +120,19 @@ def test_DESeq2_error(fp, mocker): assert True +def test_DESeq2_uses_rscript_caller(fp, mocker): + # DESeq2 is registered with a Rscript caller; the PATH check should look for Rscript, not DESeq2 + def which_side_effect(cmd): + return "/usr/bin/Rscript" if cmd == "Rscript" else None + + mocker.patch("versionix.parser.shutil.which", side_effect=which_side_effect) + fp.register( + 'Rscript -e "library(DESeq2);packageVersion(\'DESeq2\')"', + stdout=["[1] '1.42.0'"], + ) + assert get_version("DESeq2") == "1.42.0" + + def test_empty_parsers(fp, mocker): try: mocker.patch("shutil.which", return_value=True) diff --git a/versionix/parser.py b/versionix/parser.py index f90a625..deffc73 100644 --- a/versionix/parser.py +++ b/versionix/parser.py @@ -122,7 +122,7 @@ def _get_container_runner(container): sys.exit(1) -def get_version(standalone, verbose=True, R=False, container=None): +def get_version(standalone, verbose=True, container=None): """Main entry point that returns the version of an existing executable""" # we should use check_output in case the standalone opens a GUI (e.g. fastqc --WRONG pops up the GUI) @@ -135,10 +135,18 @@ def get_version(standalone, verbose=True, R=False, container=None): standalone.startswith("singularity") or standalone.startswith("apptainer") or standalone.startswith("docker") ): # pragma: no cover pass - elif shutil.which(standalone) is None: - if verbose: - logger.error(f"{standalone} command not found in your environment") - sys.exit(1) + else: + # For registered tools with a custom caller, check the caller binary rather than + # the standalone name (e.g. DESeq2 is invoked via `Rscript`, not a `DESeq2` binary). + if standalone in metadata: + caller = metadata[standalone].get("caller", standalone) + check_cmd = shlex.split(caller)[0] + else: + check_cmd = standalone + if shutil.which(check_cmd) is None: + if verbose: + logger.error(f"{check_cmd} command not found in your environment") + sys.exit(1) # is it registered ? if standalone in metadata.keys(): @@ -174,7 +182,7 @@ def search_registered(standalone, container_runner=None): raise ValueError(f"parsers for {standalone} is incorrect. Must be a list of valid parsers") try: - cmd = f"{caller} {options}" + cmd = f"{caller} {options}".strip() if container_runner: cmd = f"{container_runner} {cmd}" p = subprocess.run(cmd, capture_output=True, universal_newlines=True, shell=True) diff --git a/versionix/registry.py b/versionix/registry.py index f9c99e5..30fbbf8 100644 --- a/versionix/registry.py +++ b/versionix/registry.py @@ -18,7 +18,7 @@ "circlator": {"options": "version", "parsers": [lambda x: x.stdout.strip()]}, "DESeq2": { "caller": "Rscript -e \"library(DESeq2);packageVersion('DESeq2')\"", - "parsers": [lambda x: x.stdout.split()[1].strip("’‘")], + "parsers": [lambda x: x.stdout.split()[1].strip("'")], }, # dot -v hangs so we need to register this command. "dot": {"options": "-V", "parsers": [lambda x: x.stderr.split()[4].strip()]}, @@ -45,8 +45,4 @@ # # "parser": parser_split_2 # }, # "vt": {"options": "--version", "parser": parser_split_2_strip_v}, - "for_testing_empty_parsers": {"options": "", "parsers": []}, - "for_testing_no_parsers": { - "options": "", - }, } diff --git a/versionix/scripts.py b/versionix/scripts.py index 73d886b..59ec73a 100755 --- a/versionix/scripts.py +++ b/versionix/scripts.py @@ -1,25 +1,15 @@ -########################################################################### -# Versionix is a project to manage reproducible containers # -# # -# Authors: see CONTRIBUTORS.rst # -# Copyright © 2023 Sequana dev team # -# See the COPYRIGHT file for details # -# # -# Versionix is free software: you can redistribute it and/or modify # -# it under the terms of the GNU General Public License as published by # -# the Free Software Foundation, either version 3 of the License, or # -# (at your option) any later version. # -# # -# Versionix is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program (COPYING file). # -# If not, see . # -########################################################################### -""".. rubric:: Standalone application dedicated to Damona""" +# +# This file is part of Versionix software +# +# Copyright (c) 2023 - Sequana Dev Team (https://sequana.readthedocs.io) +# +# Distributed under the terms of the 3-clause BSD license. +# The full license is in the LICENSE file, distributed with this software. +# +# Website: https://github.com/sequana/versionix +# Contributors: https://github.com/sequana/versionix/graphs/contributors +############################################################################## +""".. rubric:: Standalone application (CLI entry point) for Versionix""" import sys import colorlog @@ -32,9 +22,7 @@ CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) -click.rich_click.USE_MARKDOWN = True -click.rich_click.SHOW_METAVARS_COLUMN = True -click.rich_click.APPEND_METAVARS_HELP = True +click.rich_click.TEXT_MARKUP = "markdown" click.rich_click.STYLE_ERRORS_SUGGESTION = "magenta italic" click.rich_click.SHOW_ARGUMENTS = True