diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d7e7fd..6b6d7aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,62 +6,38 @@ on: - main pull_request: -env: - PYTHON_VERSION: 3.11 - jobs: - linting: + formatting: runs-on: ubuntu-latest steps: - name: Check out the code - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Install poetry - run: pip install poetry + uses: actions/checkout@v5 - - name: Determine dependencies - run: poetry lock - - - uses: actions/setup-python@v4 - with: - python-version: ${{ env.PYTHON_VERSION }} - cache: poetry - - - name: Install Dependencies using Poetry - run: poetry install + - name: Setup pixi + uses: prefix-dev/setup-pixi@v0 - name: Check formatting - run: poetry run ruff format --check . - - - name: Lint - run: poetry run ruff check . + run: pixi run format --check . - testing: + linting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 - with: - python-version: ${{ env.PYTHON_VERSION }} + - name: Check out the code + uses: actions/checkout@v5 - - name: Install poetry - run: pip install poetry + - name: Setup pixi + uses: prefix-dev/setup-pixi@v0 - - name: Determine dependencies - run: poetry lock + - name: Check code + run: pixi run lint - - uses: actions/setup-python@v4 - with: - python-version: ${{ env.PYTHON_VERSION }} - cache: poetry + testing: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 - - name: Install dependencies - run: poetry install + - name: Setup pixi + uses: prefix-dev/setup-pixi@v0 - name: Setup envmodules run: | @@ -69,10 +45,10 @@ jobs: sudo apt-get install -y environment-modules - name: Run pytest + shell: bash -el {0} run: | - source /etc/profile.d/modules.sh - module --help - poetry run coverage run -m pytest + command -v module + pixi run test -v - name: Run Coverage - run: poetry run coverage report -m + run: pixi run coverage-report diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 76eaa51..658ae7d 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -18,36 +18,23 @@ jobs: id: release with: release-type: python - package-name: publish: runs-on: ubuntu-latest needs: release-please if: ${{ needs.release-please.outputs.release_created }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - - uses: actions/setup-python@v4 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Install poetry - run: pip install poetry - - - name: Determine dependencies - run: poetry lock + - name: Setup pixi + uses: prefix-dev/setup-pixi@v0 - - uses: actions/setup-python@v4 - with: - python-version: ${{ env.PYTHON_VERSION }} - cache: poetry - - - name: Install Dependencies using Poetry + - name: Build source and wheel distribution + check build run: | - poetry install + pixi run check-build - - name: Publish to PyPi - env: - PYPI_USERNAME: __token__ - PYPI_PASSWORD: ${{ secrets.PYPI_TOKEN }} - run: poetry publish --build --username $PYPI_USERNAME --password $PYPI_PASSWORD + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_TOKEN }} diff --git a/.gitignore b/.gitignore index bcc3d59..d2b486f 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,4 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -poetry.lock \ No newline at end of file +pixi.lock \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8720d46..61ff967 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,26 +1,67 @@ [project] name = "snakemake-software-deployment-plugin-envmodules" version = "0.1.4" -description = "" +authors = [{ name = "Johannes Köster", email = "johannes.koester@uni-due.de" }] +description = "Software deployment plugin for Snakemake using environment modules." readme = "README.md" requires-python = ">=3.11,<4.0" -dependencies = ["snakemake-interface-software-deployment-plugins >=0.6.1,<1.0.0"] -repository = "https://github.com/your/plugin" +dependencies = [ + "snakemake-interface-common (>=1.17.4,<2.0.0)", + "snakemake-interface-software-deployment-plugins (>=0.7.7,<1.0)", +] + +[tool.pixi.pypi-dependencies] +snakemake-software-deployment-plugin-envmodules = { path = ".", editable = true } +#snakemake-interface-software-deployment-plugins = { path = "../snakemake-interface-software-deployment-plugins", editable = true } + +[project.urls] +repository = "https://github.com/snakemake/snakemake-software-deployment-plugin-envmodules" documentation = "https://snakemake.github.io/snakemake-plugin-catalog/plugins/software-deployment/envmodules.html" -[[project.authors]] -name = "Johannes Koester" -email = "johannes.koester@uni-due.de" [build-system] -requires = [ "poetry-core>=2.0.0,<3.0.0",] -build-backend = "poetry.core.masonry.api" - -[tool.poetry] -[[tool.poetry.packages]] -include = "snakemake_software_deployment_plugin_envmodules" -from = "src" - -[tool.poetry.group.dev.dependencies] -coverage = "^7.6.12" -pytest = "^8.3.5" -ruff = "^0.9.9" +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.pixi.project] +channels = ["conda-forge"] +platforms = ["osx-arm64", "linux-64"] + +[tool.pixi.environments] +dev = { features = ["dev"] } +publish = { features = ["publish"] } + +[tool.pixi.feature.dev.dependencies] +pytest = ">=8.3.5,<9" +ruff = ">=0.10.0,<0.11" +pytest-cov = ">=6.0.0,<7" + +[tool.pixi.feature.dev.tasks] +format = "ruff format" +lint = "ruff check" +qc = { depends-on = ["format", "lint"] } +coverage-report = "coverage report -m" + +[tool.pixi.feature.dev.tasks.test] +cmd = [ + "pytest", + "--cov=snakemake_software_deployment_plugin_envmodules", + "--cov-report=xml:coverage-report/coverage.xml", + "--cov-report=term-missing", + "tests/test_plugin.py", +] + +[tool.coverage.report] +exclude_lines = ["pass", "\\.\\.\\."] +fail_under = 70.0 + +# Publish +[tool.pixi.feature.publish.dependencies] +twine = ">=6.1.0,<7" +python-build = ">=1.2.2,<2" + + +[tool.pixi.feature.publish.tasks] +build = { cmd = "python -m build", description = "Build the package into the dist/ directory" } +check-build = { cmd = "python -m twine check dist/*", depends-on = [ + "build", +], description = "Check that the package can be uploaded" } diff --git a/src/snakemake_software_deployment_plugin_envmodules/__init__.py b/src/snakemake_software_deployment_plugin_envmodules/__init__.py index a71c45c..2fb266c 100644 --- a/src/snakemake_software_deployment_plugin_envmodules/__init__.py +++ b/src/snakemake_software_deployment_plugin_envmodules/__init__.py @@ -1,4 +1,5 @@ -from typing import Iterable, Tuple +import shlex +from typing import Iterable import subprocess as sp from snakemake_interface_software_deployment_plugins import ( EnvBase, @@ -15,7 +16,7 @@ class EnvSpec(EnvSpecBase): def __init__(self, *names: str): super().__init__() - self.names: Tuple[str] = names + self.names: tuple[str, ...] = tuple(names) def identity_attributes(self) -> Iterable[str]: # The identity of the env spec is given by the names of the modules. @@ -25,6 +26,9 @@ def source_path_attributes(self) -> Iterable[str]: # no paths involved here return () + def __str__(self) -> str: + return ",".join(self.names) + class Env(EnvBase): def __post_init__(self): @@ -33,11 +37,12 @@ def __post_init__(self): @EnvBase.once def check(self) -> None: - if self.run_cmd("type module", stdout=sp.PIPE, stderr=sp.PIPE).returncode != 0: + res = self.run_cmd("type module", stdout=sp.PIPE, stderr=sp.STDOUT) + if res.returncode != 0: raise WorkflowError( "The module command is not available. " - "Please make sure that the environment modules are " - "available on your system." + "Please make sure that environment modules are " + f"available on your system: {res.stdout.decode()}" ) def decorate_shellcmd(self, cmd: str) -> str: @@ -45,7 +50,7 @@ def decorate_shellcmd(self, cmd: str) -> str: # Unclear why that happens. # one might have to say 'shopt -s expand_aliases;', but that did not # help either... - return f"module purge && module load {' '.join(self.spec.names)}; {cmd}" + return f"module purge && module load {' '.join(shlex.quote(name) for name in self.spec.names)} && {cmd}" def record_hash(self, hash_object) -> None: # We just hash the names here as the best thing we can do for envmodules diff --git a/tests/test_plugin.py b/tests/test_plugin.py index b27bb8b..af22d47 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,19 +1,20 @@ import os from typing import Optional, Type -from snakemake_interface_software_deployment_plugins import EnvBase +from snakemake_interface_software_deployment_plugins import EnvBase, EnvSpecBase from snakemake_interface_software_deployment_plugins.tests import ( TestSoftwareDeploymentBase, ) from snakemake_interface_software_deployment_plugins.settings import ( SoftwareDeploymentSettingsBase, ) -from snakemake_software_deployment_plugin_envmodules import Env, EnvSpec, EnvSpecBase +from snakemake_software_deployment_plugin_envmodules import Env, EnvSpec os.environ["MODULEPATH"] = "tests/modules" -class TestSoftwareDeployment(TestSoftwareDeploymentBase): +class Test(TestSoftwareDeploymentBase): __test__ = True # activate automatic testing + shell_executable = ["bash", "-l", "-c"] def get_software_deployment_provider_settings( self, @@ -30,6 +31,17 @@ def get_env_spec(self) -> EnvSpecBase: # testing. return EnvSpec("somecmd") + def get_settings_cls(self) -> Optional[Type[SoftwareDeploymentSettingsBase]]: + # Return the settings class that should be used for this plugin. + return None + + def get_settings( + self, + ) -> Optional[SoftwareDeploymentSettingsBase]: + # If your plugin has settings, return a valid settings object here. + # Otherwise, return None. + return None + def get_test_cmd(self) -> str: # Return a command that should be executable without error in the environment return "somecmd"