Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .codacy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
exclude_paths:
- "test/**"
6 changes: 3 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ on:

jobs:
build-linux:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
max-parallel: 5
matrix:
Expand All @@ -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: |
Expand All @@ -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 }}

Expand Down
14 changes: 8 additions & 6 deletions .github/workflows/pypi.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Publish to PyPI
on:
on:
workflow_dispatch:
push:
tags:
Expand All @@ -8,29 +8,31 @@ 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

- name: Build source tarball
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
Expand Down
28 changes: 27 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
============
Expand Down Expand Up @@ -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
============

Expand Down Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"

4 changes: 4 additions & 0 deletions test/test_misc.py
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
from versionix import version


def test_version():
assert isinstance(version, str) and len(version) > 0
41 changes: 24 additions & 17 deletions test/test_parser.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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

Expand Down
14 changes: 6 additions & 8 deletions versionix/logging.py
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
#
# 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
Expand Down
6 changes: 2 additions & 4 deletions versionix/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@

import colorlog

from versionix import logger

logger = colorlog.getLogger(logger.name)
logger = colorlog.getLogger("versionix")


from .blacklist import blacklist
Expand Down Expand Up @@ -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

Expand Down
5 changes: 1 addition & 4 deletions versionix/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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(
Expand Down
Loading