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
32 changes: 10 additions & 22 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,17 @@ jobs:
needs: release-please
if: ${{ needs.release-please.outputs.release_created }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6

- uses: actions/setup-python@v2
with:
python-version: "3.11"

- 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: "3.11"
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 }}
62 changes: 20 additions & 42 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,62 +8,40 @@ on:
branches-ignore: []

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: "3.11"

- name: Install poetry
run: pip install poetry

- name: Determine dependencies
run: poetry lock
uses: actions/checkout@v5

- uses: actions/setup-python@v4
with:
python-version: "3.11"
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: "3.11"
- 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: "3.11"
cache: poetry
testing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Install dependencies
run: |
poetry install
- name: Setup pixi
uses: prefix-dev/setup-pixi@v0

- name: Run pytest
run: poetry run coverage run -m pytest tests/tests.py
run: pixi run test -v

- name: Run Coverage
run: poetry run coverage report -m
run: pixi run coverage-report
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ __pycache__/
*.py[cod]
*$py.class

pixi.lock

# C extensions
*.so

Expand Down Expand Up @@ -159,4 +161,4 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

poetry.lock
poetry.lock
80 changes: 61 additions & 19 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,30 +1,72 @@
[tool.poetry]
authors = ["Johannes Köster <johannes.koester@uni-due.de>"]
[project]
authors = [
{ name = "Johannes Köster", email = "johannes.koester@uni-due.de" }
]
description = "This package provides a stable interface for interactions between Snakemake and its software deployment plugins."
license = "MIT"
name = "snakemake-interface-software-deployment-plugins"
packages = [{include = "snakemake_interface_software_deployment_plugins"}]
readme = "README.md"
version = "0.9.0"
requires-python = ">=3.11,<4.0"
dependencies = [
"argparse-dataclass >=2.0.0,<3.0",
"snakemake-interface-common >=1.17.4,<2.0.0"
]

[tool.poetry.dependencies]
argparse-dataclass = "^2.0.0"
python = "^3.11"
snakemake-interface-common = "^1.17.4"
[project.urls]
repository = "https://github.com/snakemake/snakemake-interface-software-deployment-plugins"

[tool.poetry.group.dev.dependencies]
coverage = {extras = ["toml"], version = "^6.3.1"}
flake8-bugbear = "^22.1.11"
pytest = "^7.0"
ruff = "^0.9.9"
snakemake-software-deployment-plugin-envmodules = "^0.1.2"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.pixi.pypi-dependencies]
snakemake-interface-software-deployment-plugins = { path = ".", editable = true }

[tool.pixi.workspace]
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"
pyrefly = ">=0.52.0,<0.53"

[tool.coverage.run]
omit = [".*", "*/site-packages/*"]
[tool.pixi.feature.dev.pypi-dependencies]
snakemake-software-deployment-plugin-envmodules = ">=0.1.2,<1.0"

[tool.pixi.feature.dev.tasks]
format = "ruff format"
lint = "ruff check"
typecheck = "pyrefly 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_interface.py",
]

[tool.coverage.report]
fail_under = 60
exclude_lines = ["pass", "\\.\\.\\."]
fail_under = 63.0

[build-system]
build-backend = "poetry.core.masonry.api"
requires = ["poetry-core"]
[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" }
34 changes: 19 additions & 15 deletions snakemake_interface_software_deployment_plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

from abc import ABC, abstractmethod
from copy import copy
from inspect import getmodule
from dataclasses import dataclass, field
import hashlib
from pathlib import Path
import shutil
from types import ModuleType
from typing import (
Any,
ClassVar,
Expand All @@ -19,6 +21,7 @@
Self,
Tuple,
Type,
TypeVar,
Union,
)
import subprocess as sp
Expand All @@ -43,16 +46,22 @@ class EnvSpecSourceFile:


class EnvSpecBase(ABC):
@classmethod
def module(cls) -> ModuleType:
class_module = getmodule(cls)
assert class_module is not None, f"bug: cannot detect class module of {cls}"
return class_module

def technical_init(self):
"""This has to be called by Snakemake upon initialization"""
self.within: Optional["EnvSpecBase"] = None
self.fallback: Optional["EnvSpecBase"] = None
self.kind: str = self.__class__.__module__.common_settings.provides
self.kind: str = self.module().common_settings.provides
self._obj_hash: Optional[int] = None

@classmethod
def env_cls(cls):
return cls.__module__.EnvBase
return cls.module().EnvBase

@classmethod
@abstractmethod
Expand Down Expand Up @@ -148,19 +157,11 @@ def run(self, cmd: str, **kwargs) -> sp.CompletedProcess:
return sp.run([self.executable] + self.args + [self.command_arg, cmd], **kwargs)


TSettings = TypeVar("TSettings", bound="SoftwareDeploymentSettingsBase")


class EnvBase(ABC):
_cache: ClassVar[Dict[Tuple[Type["EnvBase"], Optional["EnvBase"]], Any]] = {}
spec: EnvSpecBase
within: Optional["EnvBase"]
settings: Optional[SoftwareDeploymentSettingsBase]
shell_executable: ShellExecutable
tempdir: Path
_cache_prefix: Path
_deployment_prefix: Path
_pinfile_prefix: Path
_managed_hash_store: Optional[str] = None
_managed_deployment_hash_store: Optional[str] = None
_obj_hash: Optional[int] = None

def __init__(
self,
Expand All @@ -174,15 +175,18 @@ def __init__(
deployment_prefix: Path,
pinfile_prefix: Path,
):
self.spec: EnvSpecBase = spec
self.within: Optional["EnvBase"] = within
self.spec = spec
self.within = within
self.settings: Optional[SoftwareDeploymentSettingsBase] = settings
self.shell_executable = shell_executable
self.tempdir = tempdir
self.source_cache: Path = source_cache
self._deployment_prefix: Path = deployment_prefix
self._cache_prefix: Path = cache_prefix
self._pinfile_prefix: Path = pinfile_prefix
self._managed_hash_store: Optional[str] = None
self._managed_deployment_hash_store: Optional[str] = None
self._obj_hash: Optional[int] = None
self.__post_init__()

def __post_init__(self) -> None: # noqa B027
Expand Down
4 changes: 2 additions & 2 deletions tests/tests.py → tests/test_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ def get_test_plugin_name(self) -> str:

def validate_plugin(self, plugin: PluginBase):
assert plugin.settings_cls is None
assert plugin.env_cls is not None
assert plugin.env_spec_cls is not None
assert plugin.env_cls is not None # pyrefly: ignore[missing-attribute]
assert plugin.env_spec_cls is not None # pyrefly: ignore[missing-attribute]

def validate_settings(self, settings: SettingsBase, plugin: PluginBase):
# assert isinstance(settings, plugin.settings_cls)
Expand Down