From a943e0a4d2a799814c60a609ae2d401842f30c35 Mon Sep 17 00:00:00 2001 From: mishugeb Date: Mon, 27 Apr 2026 23:50:35 -0400 Subject: [PATCH 1/3] Modernize packaging and add release automation --- .github/workflows/release.yml | 87 ++++++++++++++++++++++++++++++ .gitignore | 4 +- RELEASE_ADMIN.md | 94 +++++++++++++++++++++++++++++++++ SigProfilerExtractor/version.py | 27 ++++++++++ pyproject.toml | 8 ++- setup.py | 93 +++++--------------------------- 6 files changed, 231 insertions(+), 82 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 RELEASE_ADMIN.md create mode 100644 SigProfilerExtractor/version.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e1a072d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,87 @@ +name: Release + +on: + push: + tags: + - "v*" + release: + types: + - published + workflow_dispatch: + +jobs: + build: + name: Build distributions + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install build tooling + run: | + python -m pip install --upgrade pip build twine + + - name: Build distributions + run: | + python -m build + + - name: Check distributions + run: | + python -m twine check dist/* + + - name: Upload distributions + uses: actions/upload-artifact@v7 + with: + name: python-package-distributions + path: dist/ + if-no-files-found: error + + publish-testpypi: + name: Publish to TestPyPI + needs: build + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + environment: + name: testpypi + url: https://test.pypi.org/p/SigProfilerExtractor + permissions: + id-token: write + steps: + - name: Download distributions + uses: actions/download-artifact@v5 + with: + name: python-package-distributions + path: dist/ + + - name: Publish package distributions to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + publish-pypi: + name: Publish to PyPI + needs: build + if: github.event_name == 'release' && github.event.action == 'published' + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/SigProfilerExtractor + permissions: + id-token: write + steps: + - name: Download distributions + uses: actions/download-artifact@v5 + with: + name: python-package-distributions + path: dist/ + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore index 8ae1e3d..cfc6b27 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST -SigProfilerExtractor/version.py +SigProfilerExtractor/_version.py # PyInstaller # Usually these files are written by a python script from a template @@ -139,4 +139,4 @@ test_vcf_output/ test_matrix_*_output/ SigProfilerExtractor/data/VCFInput/logs/ SigProfilerExtractor/data/VCFInput/input/ -SigProfilerExtractor/data/VCFInput/output/ \ No newline at end of file +SigProfilerExtractor/data/VCFInput/output/ diff --git a/RELEASE_ADMIN.md b/RELEASE_ADMIN.md new file mode 100644 index 0000000..945f443 --- /dev/null +++ b/RELEASE_ADMIN.md @@ -0,0 +1,94 @@ +# Release Admin + +This branch adds tag-driven Python package publishing with `setuptools_scm` and GitHub Actions. + +## Versioning model + +- Package versions come from git tags. +- A tag like `v1.2.8` produces package version `1.2.8`. +- Untagged commits on development branches produce dev versions such as `1.2.9.dev1+g.d`. + +## Workflow behavior + +The release workflow is in [.github/workflows/release.yml](./.github/workflows/release.yml). + +- Push a tag matching `v*`: + - build sdist and wheel + - run `twine check` + - publish to TestPyPI +- Publish a GitHub Release: + - build sdist and wheel + - run `twine check` + - publish to PyPI + +This keeps TestPyPI as the first stop and PyPI as the explicit release action. + +## GitHub environment setup + +Create these GitHub environments in the repository: + +1. `testpypi` +2. `pypi` + +Recommended: + +- add required reviewers for `pypi` +- optionally leave `testpypi` without reviewers for faster dry runs + +Repository path: + +- `Settings -> Environments` + +## Trusted publishing setup + +Use PyPI trusted publishing instead of API tokens. + +### TestPyPI + +In TestPyPI: + +1. Go to account settings for trusted publishers. +2. Add a publisher for this repository. +3. Use: + - Owner: `SigProfilerSuite` + - Repository: `SigProfilerExtractor` + - Workflow name: `release.yml` + - Environment name: `testpypi` + +### PyPI + +In PyPI: + +1. Go to project publishing settings for trusted publishers. +2. Add a publisher for this repository. +3. Use: + - Owner: `SigProfilerSuite` + - Repository: `SigProfilerExtractor` + - Workflow name: `release.yml` + - Environment name: `pypi` + +If the project does not yet exist on TestPyPI or PyPI, create the trusted publisher entry from the publisher setup page for a new project with the project name: + +- `SigProfilerExtractor` + +## Release flow + +### TestPyPI dry run + +1. Merge release-ready changes into `master`. +2. Create and push a tag: + - `git tag -a v1.2.8 -m "v1.2.8"` + - `git push origin v1.2.8` +3. Confirm TestPyPI publish succeeds. + +### PyPI publish + +1. Create a GitHub Release from the same tag `v1.2.8`. +2. Publish the GitHub Release. +3. Confirm the PyPI publish job succeeds. + +## Notes + +- Do not hardcode a package version in `setup.py`. +- If you need the package version locally, create a tag or inspect the generated dev version. +- The CI workflow remains separate from publishing; only the release workflow handles package uploads. diff --git a/SigProfilerExtractor/version.py b/SigProfilerExtractor/version.py new file mode 100644 index 0000000..206dd6e --- /dev/null +++ b/SigProfilerExtractor/version.py @@ -0,0 +1,27 @@ +try: + from ._version import version as _scm_version +except Exception: + _scm_version = None + +try: + from importlib.metadata import PackageNotFoundError, version as _dist_version +except Exception: # pragma: no cover + PackageNotFoundError = Exception # type: ignore + + def _dist_version(_name: str) -> str: # type: ignore + raise PackageNotFoundError() + + +def _resolve_version() -> str: + if _scm_version is not None: + return _scm_version + + try: + return _dist_version("SigProfilerExtractor") + except PackageNotFoundError: + return "0+unknown" + + +short_version = _resolve_version() +version = short_version +Update = "Managed by setuptools_scm from git tags" diff --git a/pyproject.toml b/pyproject.toml index aa9739a..5ad4ff7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,9 @@ [build-system] -requires = ["setuptools>=61", "wheel", "build"] +requires = ["setuptools>=69", "setuptools_scm[toml]>=8", "wheel"] build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +tag_regex = "^v(?P.+)$" +version_scheme = "guess-next-dev" +local_scheme = "node-and-date" +fallback_version = "0+unknown" diff --git a/setup.py b/setup.py index 05bb67f..2e7b3bc 100644 --- a/setup.py +++ b/setup.py @@ -1,40 +1,11 @@ -from setuptools import setup -import shutil -import os -import sys -import subprocess +from pathlib import Path -# remove the dist folder first if exists -if os.path.exists("dist"): - shutil.rmtree("dist") +from setuptools import find_namespace_packages, setup -VERSION = "1.2.7" +LONG_DESCRIPTION = Path("README.md").read_text(encoding="utf-8") -with open("README.md") as f: - long_description = f.read() - - -def write_version_py(filename="SigProfilerExtractor/version.py"): - # Copied from numpy setup.py - cnt = """ -# THIS FILE IS GENERATED FROM SIGPROFILEREXTRACTOR SETUP.PY -short_version = '%(version)s' -version = '%(version)s' -Update = 'v1.2.7: Removed nimfa dependency and implemented NNDSVD directly for NumPy 2.0 compatibility' - - """ - fh = open(filename, "w") - fh.write( - cnt - % { - "version": VERSION, - } - ) - fh.close() - - -requirements = [ +INSTALL_REQUIRES = [ "scipy>=1.6.3", "torch>=1.8.1", "numpy>=2.0.0", @@ -47,61 +18,25 @@ def write_version_py(filename="SigProfilerExtractor/version.py"): "psutil>=5.6.1", ] -operating_system = sys.platform -print(operating_system) -if operating_system in ["win32", "cygwin", "windows"]: - requirements.remove("matplotlib>=3.3.0") - requirements.remove("torch==1.5.1") - print("Trying to install pytorch!") - code = 1 - try: - code = subprocess.call( - [ - "pip", - "install", - "torch===1.5.1+cpu", - "-f", - "https://download.pytorch.org/whl/torch_stable.html", - ] - ) - if code != 0: - raise Exception("Torch instalation failed !") - except: - try: - code = subprocess.call( - [ - "pip3", - "install", - "torch===1.5.1+cpu", - "-f", - "https://download.pytorch.org/whl/torch_stable.html", - ] - ) - if code != 0: - raise Exception("Torch instalation failed !") - except: - print( - "Failed to install pytorch, please install pytorch manually be following the simple instructions over at: https://pytorch.org/get-started/locally/" - ) - if code == 0: - print( - "Successfully installed pytorch version! (If you need the GPU version, please install it manually, checkout the mindsdb docs and the pytroch docs if you need help)" - ) +VERSION_TEMPLATE = "version = '{version}'\n" -write_version_py() setup( name="SigProfilerExtractor", - version=VERSION, + use_scm_version={ + "write_to": "SigProfilerExtractor/_version.py", + "write_to_template": VERSION_TEMPLATE, + "fallback_version": "0+unknown", + }, description="Extracts mutational signatures from mutational catalogues", - long_description=long_description, - long_description_content_type="text/markdown", # This is important! + long_description=LONG_DESCRIPTION, + long_description_content_type="text/markdown", url="https://github.com/SigProfilerSuite/SigProfilerExtractor.git", author="S Mishu Ashiqul Islam", author_email="m0islam@ucsd.edu", license="UCSD", - packages=["SigProfilerExtractor"], - install_requires=requirements, + packages=find_namespace_packages(include=["SigProfilerExtractor*"]), + install_requires=INSTALL_REQUIRES, include_package_data=True, python_requires=">=3.9", entry_points={ From 970484a0da3c312aa66d30d1c4d63ffba533cc26 Mon Sep 17 00:00:00 2001 From: mishugeb Date: Sat, 23 May 2026 23:39:10 -0400 Subject: [PATCH 2/3] Harden setuptools_scm release version handling --- .github/workflows/ci.yml | 4 +++- .github/workflows/release.yml | 5 +++++ RELEASE_ADMIN.md | 26 +++++++++++++++----------- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c8a78d..69421f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,7 +80,9 @@ jobs: run: | echo "Starting Docker deployment to GHCR for sigprofilersuite..." - VERSION_TAG=$(grep "VERSION = " setup.py | cut -d'"' -f2) + python -m pip install --upgrade setuptools_scm + VERSION_TAG=$(python -m setuptools_scm) + VERSION_TAG=${VERSION_TAG//+/-} # Get the repository name and convert it to lowercase REPO_NAME=$(basename ${{ github.repository }} | tr '[:upper:]' '[:lower:]') diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e1a072d..75064d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,6 +37,11 @@ jobs: run: | python -m twine check dist/* + - name: Verify package version + run: | + python -m pip install dist/*.whl + python -c "import SigProfilerExtractor; print(SigProfilerExtractor.__version__)" + - name: Upload distributions uses: actions/upload-artifact@v7 with: diff --git a/RELEASE_ADMIN.md b/RELEASE_ADMIN.md index 945f443..f184e3e 100644 --- a/RELEASE_ADMIN.md +++ b/RELEASE_ADMIN.md @@ -12,16 +12,16 @@ This branch adds tag-driven Python package publishing with `setuptools_scm` and The release workflow is in [.github/workflows/release.yml](./.github/workflows/release.yml). -- Push a tag matching `v*`: - - build sdist and wheel - - run `twine check` - - publish to TestPyPI -- Publish a GitHub Release: - - build sdist and wheel - - run `twine check` - - publish to PyPI +- Push a git tag whose name starts with `v`, such as `v1.2.8`: + - this is a git event + - it happens when you run `git push origin v1.2.8` + - it triggers build + `twine check` + publish to TestPyPI +- Publish a GitHub Release for an existing tag such as `v1.2.8`: + - this is a GitHub UI/API release event, not just a git tag event + - it happens when you create or publish a Release on GitHub from that tag + - it triggers build + `twine check` + publish to PyPI -This keeps TestPyPI as the first stop and PyPI as the explicit release action. +This split keeps TestPyPI as the first stop and PyPI as the explicit release action. ## GitHub environment setup @@ -73,7 +73,7 @@ If the project does not yet exist on TestPyPI or PyPI, create the trusted publis ## Release flow -### TestPyPI dry run +### Step 1: TestPyPI dry run by pushing a tag 1. Merge release-ready changes into `master`. 2. Create and push a tag: @@ -81,12 +81,16 @@ If the project does not yet exist on TestPyPI or PyPI, create the trusted publis - `git push origin v1.2.8` 3. Confirm TestPyPI publish succeeds. -### PyPI publish +This step does not require a GitHub Release yet. It only requires the git tag to exist on GitHub. + +### Step 2: PyPI publish by publishing a GitHub Release 1. Create a GitHub Release from the same tag `v1.2.8`. 2. Publish the GitHub Release. 3. Confirm the PyPI publish job succeeds. +This step uses the already-pushed tag, but the trigger is the Release publication event on GitHub. + ## Notes - Do not hardcode a package version in `setup.py`. From 49f8ef24990a2004ae979107c639b42fff690295 Mon Sep 17 00:00:00 2001 From: mishugeb Date: Sun, 24 May 2026 19:18:13 -0400 Subject: [PATCH 3/3] Remove release admin notes from repository --- RELEASE_ADMIN.md | 98 ------------------------------------------------ 1 file changed, 98 deletions(-) delete mode 100644 RELEASE_ADMIN.md diff --git a/RELEASE_ADMIN.md b/RELEASE_ADMIN.md deleted file mode 100644 index f184e3e..0000000 --- a/RELEASE_ADMIN.md +++ /dev/null @@ -1,98 +0,0 @@ -# Release Admin - -This branch adds tag-driven Python package publishing with `setuptools_scm` and GitHub Actions. - -## Versioning model - -- Package versions come from git tags. -- A tag like `v1.2.8` produces package version `1.2.8`. -- Untagged commits on development branches produce dev versions such as `1.2.9.dev1+g.d`. - -## Workflow behavior - -The release workflow is in [.github/workflows/release.yml](./.github/workflows/release.yml). - -- Push a git tag whose name starts with `v`, such as `v1.2.8`: - - this is a git event - - it happens when you run `git push origin v1.2.8` - - it triggers build + `twine check` + publish to TestPyPI -- Publish a GitHub Release for an existing tag such as `v1.2.8`: - - this is a GitHub UI/API release event, not just a git tag event - - it happens when you create or publish a Release on GitHub from that tag - - it triggers build + `twine check` + publish to PyPI - -This split keeps TestPyPI as the first stop and PyPI as the explicit release action. - -## GitHub environment setup - -Create these GitHub environments in the repository: - -1. `testpypi` -2. `pypi` - -Recommended: - -- add required reviewers for `pypi` -- optionally leave `testpypi` without reviewers for faster dry runs - -Repository path: - -- `Settings -> Environments` - -## Trusted publishing setup - -Use PyPI trusted publishing instead of API tokens. - -### TestPyPI - -In TestPyPI: - -1. Go to account settings for trusted publishers. -2. Add a publisher for this repository. -3. Use: - - Owner: `SigProfilerSuite` - - Repository: `SigProfilerExtractor` - - Workflow name: `release.yml` - - Environment name: `testpypi` - -### PyPI - -In PyPI: - -1. Go to project publishing settings for trusted publishers. -2. Add a publisher for this repository. -3. Use: - - Owner: `SigProfilerSuite` - - Repository: `SigProfilerExtractor` - - Workflow name: `release.yml` - - Environment name: `pypi` - -If the project does not yet exist on TestPyPI or PyPI, create the trusted publisher entry from the publisher setup page for a new project with the project name: - -- `SigProfilerExtractor` - -## Release flow - -### Step 1: TestPyPI dry run by pushing a tag - -1. Merge release-ready changes into `master`. -2. Create and push a tag: - - `git tag -a v1.2.8 -m "v1.2.8"` - - `git push origin v1.2.8` -3. Confirm TestPyPI publish succeeds. - -This step does not require a GitHub Release yet. It only requires the git tag to exist on GitHub. - -### Step 2: PyPI publish by publishing a GitHub Release - -1. Create a GitHub Release from the same tag `v1.2.8`. -2. Publish the GitHub Release. -3. Confirm the PyPI publish job succeeds. - -This step uses the already-pushed tag, but the trigger is the Release publication event on GitHub. - -## Notes - -- Do not hardcode a package version in `setup.py`. -- If you need the package version locally, create a tag or inspect the generated dev version. -- The CI workflow remains separate from publishing; only the release workflow handles package uploads.