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
19 changes: 0 additions & 19 deletions .github/workflows/black.yml

This file was deleted.

163 changes: 163 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
name: CI

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
base-install:
name: Base install without optional dependencies
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Check out source
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Set up Python 3.10
uses: actions/setup-python@v7
with:
python-version: "3.10"
cache: pip
cache-dependency-path: pyproject.toml

- name: Install the base package
run: |
python -m pip install --upgrade pip
python -m pip install .

- name: Verify optional packages were not installed
run: |
python -m pip check
python -c "import importlib.util; assert all(importlib.util.find_spec(name) is None for name in ('cooler', 'dash', 'plotly'))"
python -c "import sys; import moddotplot.moddotplot; assert not {'cooler', 'dash', 'plotly', 'moddotplot.interactive'}.intersection(sys.modules)"
python -m moddotplot --help

tests:
name: Tests (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
python-version:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
env:
MPLBACKEND: Agg

steps:
- name: Check out source
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: pyproject.toml

- name: Install package and test dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --editable ".[test,interactive,cooler]"

- name: Run unit and integration tests
run: >-
python -m pytest
--cov=moddotplot
--cov-report=term-missing
--cov-report=xml
--junitxml=junit.xml

- name: Store test reports
if: always()
uses: actions/upload-artifact@v7
with:
name: test-reports-python-${{ matrix.python-version }}
path: |
coverage.xml
junit.xml
if-no-files-found: warn

style:
name: Formatting
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Check out source
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.14"
cache: pip
cache-dependency-path: pyproject.toml

- name: Install Black
run: python -m pip install black

- name: Check formatting
run: python -m black --check src tests setup.py

package:
name: Build and validate package
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Check out source
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.14"
cache: pip
cache-dependency-path: pyproject.toml

- name: Install packaging tools
run: python -m pip install build twine

- name: Build wheel and source distribution
run: python -m build

- name: Validate distribution metadata
run: python -m twine check --strict dist/*

- name: Install and inspect the wheel
run: |
python -c "import pathlib; wheel = next(pathlib.Path('dist').glob('*.whl')); assert 'cp38-abi3' in wheel.name, wheel.name"
python -m pip install --no-deps --force-reinstall dist/*.whl
python -c "import importlib.metadata as m, pathlib, tomllib; from packaging.specifiers import SpecifierSet; expected = tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']; actual = m.metadata('ModDotPlot'); assert m.version('ModDotPlot') == expected['version']; assert SpecifierSet(actual['Requires-Python']) == SpecifierSet(expected['requires-python'])"
python -c "from moddotplot import _nthash; hashes, mask = _nthash.hash_kmers('ACGTACGT', 3, True); assert len(hashes) == 48 and mask == b''"

- name: Store distributions
uses: actions/upload-artifact@v7
with:
name: python-package-distributions
path: dist/
if-no-files-found: error
183 changes: 143 additions & 40 deletions .github/workflows/publish-to-pypi.yml
Original file line number Diff line number Diff line change
@@ -1,53 +1,156 @@
name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI
name: Publish release to PyPI

on: push
on:
push:
tags:
- "v*"

permissions:
contents: read

jobs:
build:
name: Build distribution 📦
verify:
name: Verify release and run tests
runs-on: ubuntu-latest
timeout-minutes: 45
env:
MPLBACKEND: Agg

steps:
- name: Check out tagged source
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml

- name: Verify tag matches the package version
env:
RELEASE_TAG: ${{ github.ref_name }}
run: >-
python -c
"import os, pathlib, tomllib;
version = tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'];
tag = os.environ['RELEASE_TAG'];
assert tag == f'v{version}', f'tag {tag!r} does not match package version v{version}'"

- name: Install package and tests
run: |
python -m pip install --upgrade pip
python -m pip install ".[test,interactive,cooler]"

- name: Run the release test suite
run: python -m pytest

sdist:
name: Build source distribution
needs: verify
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Check out tagged source
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.12"

- name: Install packaging tools
run: python -m pip install build twine

- name: Build source distribution
run: python -m build --sdist --outdir dist

- name: Validate distribution metadata
run: python -m twine check --strict dist/*

- name: Store source distribution for publication
uses: actions/upload-artifact@v7
with:
name: source-distribution
path: dist/
if-no-files-found: error
retention-days: 1

wheels:
name: Build wheel (${{ matrix.os }})
needs: verify
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-15-intel
- windows-latest

steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Install pypa/build
run: >-
python3 -m
pip install
build
--user
- name: Build a binary wheel and a source tarball
run: python3 -m build
- name: Store the distribution packages
uses: actions/upload-artifact@v4
with:
name: python-package-distributions
path: dist/

publish-to-pypi:
name: >-
Publish Python 🐍 distribution 📦 to PyPI
if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
- name: Check out tagged source
uses: actions/checkout@v7
with:
persist-credentials: false

# The extension uses CPython's stable ABI. Building once with CPython 3.10
# produces a cp38-abi3 wheel that supports every Python version declared
# by the package without compiling the same binary repeatedly.
- name: Build stable-ABI wheel
uses: pypa/cibuildwheel@v4.2.0
env:
CIBW_BUILD: "cp310-*"
CIBW_SKIP: "*-musllinux_*"
CIBW_ARCHS_MACOS: universal2
CIBW_TEST_COMMAND: >-
python -c "from moddotplot import _nthash;
hashes, mask = _nthash.hash_kmers('ACGTACGT', 3, True);
assert len(hashes) == 48 and mask == b''"
with:
output-dir: wheelhouse

- name: Store wheel for publication
uses: actions/upload-artifact@v7
with:
name: wheel-${{ matrix.os }}
path: wheelhouse/
if-no-files-found: error
retention-days: 1

publish:
name: Publish distributions to PyPI
needs:
- build
- sdist
- wheels
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/ModDotPlot
url: https://pypi.org/project/ModDotPlot
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing
id-token: write

steps:
- name: Download all the dists
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
- name: Download validated distributions
uses: actions/download-artifact@v8
with:
path: dist/
pattern: "*"
merge-multiple: true

- name: Check distribution metadata
run: |
python -m pip install twine
python -m twine check --strict dist/*

- name: Publish distributions with Trusted Publishing
uses: pypa/gh-action-pypi-publish@release/v1
with:
attestations: true
print-hash: true
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
__pycache__/
*.py[cod]
*.so
build/
*.egg-info/
.pytest_cache/
Loading
Loading