From 554de8a0e3cefc2d5c13c46beec7d5fb8ffc026e Mon Sep 17 00:00:00 2001 From: Thomas Cokelaer Date: Sat, 21 Mar 2026 14:50:46 +0100 Subject: [PATCH 1/2] cleanup and increase test suite. --- .github/workflows/main.yml | 6 +++--- .github/workflows/pypi.yml | 14 +++++++------ README.rst | 28 +++++++++++++++++++++++++- pyproject.toml | 5 ++++- test/test_misc.py | 4 ++++ test/test_parser.py | 41 ++++++++++++++++++++++---------------- versionix/logging.py | 14 ++++++------- versionix/parser.py | 6 ++---- versionix/scripts.py | 5 +---- 9 files changed, 79 insertions(+), 44 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 80b8470..64e56dd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,7 @@ on: jobs: build-linux: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: max-parallel: 5 matrix: @@ -29,7 +29,7 @@ jobs: sudo rm -rf /usr/share/dotnet sudo rm -rf "$AGENT_TOOLSDIRECTORY" - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: docker-practice/actions-setup-docker@master - name: Test docker run: | @@ -45,7 +45,7 @@ jobs: singularity version - name: Set up Python 3.X - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 435f810..eb70597 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -1,5 +1,5 @@ name: Publish to PyPI -on: +on: workflow_dispatch: push: tags: @@ -8,15 +8,15 @@ on: jobs: build-n-publish: name: Build and publish to PyPI and TestPyPI - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@main + - uses: actions/checkout@v4 - name: Set up Python 3.8 - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: 3.8 - - name: Install package + - name: Install package run: | pip install build poetry @@ -24,13 +24,15 @@ jobs: run: | rm -rf dist; poetry build - + - name: Publish distribution to Test PyPI + if: startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.TEST_PYPI_API_TOKEN }} repository_url: https://test.pypi.org/legacy/ + - name: Publish distribution to PyPI if: startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/README.rst b/README.rst index 637de79..11cf364 100644 --- a/README.rst +++ b/README.rst @@ -21,6 +21,10 @@ Versionix :target: https://coveralls.io/github/sequana/versionix?branch=main :alt: Coverage +.. image:: https://app.codacy.com/project/badge/Grade/7828229d14404f02b14eae651f8a5b20 + :target: https://app.codacy.com/gh/cokelaer/versionix/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade + :alt: Codacy grade + .. image:: https://img.shields.io/badge/license-BSD--3--Clause-blue.svg :target: https://github.com/sequana/versionix/blob/main/LICENSE :alt: License @@ -62,6 +66,7 @@ Key features - 🐍 **Pure Python** — no compiled dependencies; works on any platform - ⚡ **Fast** — single subprocess call, minimal overhead - 🔗 **Library API** — importable ``get_version()`` function for use in your own code +- 📦 **Container support** — introspect versions from local Singularity/Apptainer image files Installation ============ @@ -98,16 +103,36 @@ Full help:: .. image:: doc/versionix_usage.png :alt: versionix --help output +Container introspection +----------------------- + +You can retrieve the version of a tool that is packaged inside a local +Singularity/Apptainer image file (``.img`` or ``.sif``) using the ``--from`` +option:: + + versionix sniffles --from sniffles_2.7.3.img + versionix fastqc --from fastqc.sif + +If ``apptainer`` is available it is preferred over ``singularity``; both are +tried automatically. + Python API ---------- You can also call ``get_version`` directly from Python:: - from versionix.parser import get_version + from versionix import get_version version = get_version("fastqc") print(version) # e.g. "0.11.9" +Pass a ``container`` argument to introspect a tool inside a local image file:: + + from versionix import get_version + + version = get_version("sniffles", container="sniffles_2.7.3.img") + + How it works ============ @@ -148,6 +173,7 @@ Changelog ========= ======================================================================== Version Description ========= ======================================================================== +0.99.5 Update doc 0.99.4 allow introspection of apptainers 0.99.3 Maintenance release 0.99.2 Handle cases where e.g. --version is returned to the stderr (instead of diff --git a/pyproject.toml b/pyproject.toml index 7ec8ad1..1d119d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "versionix" -version = "0.99.4" +version = "0.99.5" description = "Get version of any tools" authors = ["Sequana Team"] license = "BSD-3" @@ -45,6 +45,9 @@ ipython = "^8.0.1" pytest-mock = "^3.7.0" pytest-subprocess = "^1.5.0" +[tool.pytest.ini_options] +addopts = "-p no:asyncio" + [tool.poetry.scripts] versionix = "versionix.scripts:main" diff --git a/test/test_misc.py b/test/test_misc.py index 0d746fe..626557e 100644 --- a/test/test_misc.py +++ b/test/test_misc.py @@ -1 +1,5 @@ from versionix import version + + +def test_version(): + assert isinstance(version, str) and len(version) > 0 diff --git a/test/test_parser.py b/test/test_parser.py index a260aab..000a256 100644 --- a/test/test_parser.py +++ b/test/test_parser.py @@ -1,6 +1,8 @@ import sys -from versionix.parser import get_version as get_version +import pytest + +from versionix.parser import get_version from versionix.scripts import main # for testing we do not want to install all those tools/software so @@ -112,12 +114,9 @@ def test_script(fp, mocker): def test_DESeq2_error(fp, mocker): # registered tool but if not installed, should raise a SystemExit error - try: - mocker.patch("shutil.which", return_value=None) + mocker.patch("shutil.which", return_value=None) + with pytest.raises(SystemExit): get_version("DESeq2") - assert False - except SystemExit: - assert True def test_DESeq2_uses_rscript_caller(fp, mocker): @@ -127,28 +126,22 @@ def which_side_effect(cmd): mocker.patch("versionix.parser.shutil.which", side_effect=which_side_effect) fp.register( - 'Rscript -e "library(DESeq2);packageVersion(\'DESeq2\')"', + "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) + mocker.patch("shutil.which", return_value=True) + with pytest.raises(ValueError): get_version("for_testing_empty_parsers") - assert False - except ValueError: - assert True def test_no_parser(fp, mocker): - try: - mocker.patch("shutil.which", return_value=True) + mocker.patch("shutil.which", return_value=True) + with pytest.raises(ValueError): get_version("for_testing_no_parsers") - assert False - except ValueError: - assert True def test_from_singularity_img(fp, mocker): @@ -213,6 +206,20 @@ def which_side_effect(cmd): assert get_version("dot", container="sequana_0.18.img") == "2.40.1" +def test_no_singularity_or_apptainer_in_path(fp, mocker): + mocker.patch("versionix.parser.shutil.which", return_value=None) + mocker.patch("versionix.parser.os.path.isfile", return_value=False) + with pytest.raises(SystemExit): + get_version("fastqc", container="myimage.sif") + + +def test_no_docker_in_path(fp, mocker): + mocker.patch("versionix.parser.shutil.which", return_value=None) + mocker.patch("versionix.parser.os.path.isfile", return_value=False) + with pytest.raises(SystemExit): + get_version("fastqc", container="myimage:latest") + + def test_script_from_container(fp, mocker): from click.testing import CliRunner diff --git a/versionix/logging.py b/versionix/logging.py index b3208c9..856501c 100644 --- a/versionix/logging.py +++ b/versionix/logging.py @@ -1,15 +1,13 @@ -# -*- python -*- # -# This file is part of easydev software +# This file is part of Versionix software # -# Copyright (c) 2011-2024 +# Copyright (c) 2023 - Sequana Dev Team (https://sequana.readthedocs.io) # -# File author(s): Thomas Cokelaer -# -# Distributed under the BSD3 License. -# -# Website: https://github.com/cokelaer/easydev +# 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 ############################################################################## # import logging import colorlog diff --git a/versionix/parser.py b/versionix/parser.py index deffc73..78f33a1 100644 --- a/versionix/parser.py +++ b/versionix/parser.py @@ -18,9 +18,7 @@ import colorlog -from versionix import logger - -logger = colorlog.getLogger(logger.name) +logger = colorlog.getLogger("versionix") from .blacklist import blacklist @@ -149,7 +147,7 @@ def get_version(standalone, verbose=True, container=None): sys.exit(1) # is it registered ? - if standalone in metadata.keys(): + if standalone in metadata: version = search_registered(standalone, container_runner=container_runner) return version diff --git a/versionix/scripts.py b/versionix/scripts.py index 59ec73a..664fa72 100755 --- a/versionix/scripts.py +++ b/versionix/scripts.py @@ -12,7 +12,6 @@ """.. rubric:: Standalone application (CLI entry point) for Versionix""" import sys -import colorlog import rich_click as click from versionix import version @@ -26,8 +25,6 @@ click.rich_click.STYLE_ERRORS_SUGGESTION = "magenta italic" click.rich_click.SHOW_ARGUMENTS = True -logger = colorlog.getLogger(__name__) - @click.command() @click.argument("standalone", required=False, type=click.STRING, default=None) @@ -75,8 +72,8 @@ def main(**kwargs): click.echo(f"{name}") else: if kwargs["standalone"] is None: - from rich.panel import Panel from rich.console import Console + from rich.panel import Panel console = Console() console.print( From 1ba071da5ff045b84bf9aaec7945ae9f439871d5 Mon Sep 17 00:00:00 2001 From: Thomas Cokelaer Date: Sat, 21 Mar 2026 14:51:19 +0100 Subject: [PATCH 2/2] add codacy --- .codacy.yaml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .codacy.yaml diff --git a/.codacy.yaml b/.codacy.yaml new file mode 100644 index 0000000..c903265 --- /dev/null +++ b/.codacy.yaml @@ -0,0 +1,2 @@ +exclude_paths: + - "test/**"