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
61 changes: 61 additions & 0 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: release_to_pypi

# CONTRIBUTING.md documents releasing as `git tag vX.Y.Z && git push --tags`;
# that tag push is what triggers this workflow. poetry-dynamic-versioning reads
# the version straight off the tag, so nothing in the repo needs bumping.
on:
push:
tags: ["v*"]

permissions:
contents: read

jobs:
build:
name: Build distributions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# poetry-dynamic-versioning shells out to `git describe`, so the tag
# must be in the clone - a clone without tags silently builds 0.0.0.
# actions/checkout does fetch the triggering tag even at depth 1; the
# full history is what keeps the commit-distance part of development
# versions correct, and it is what upstream recommends.
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install build tooling
run: |
python -m pip install --upgrade pip
pip install build
- name: Build sdist and wheel
run: python -m build
- name: Check metadata renders on PyPI
run: |
pip install twine
twine check dist/*
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

publish:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
# Trusted Publishing: PyPI verifies this workflow's OIDC identity, so there
# is no API token to store or rotate. Configure the publisher once at
# https://pypi.org/manage/account/publishing/ (see CONTRIBUTING.md).
environment:
name: pypi
url: https://pypi.org/p/verpex
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1
17 changes: 15 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -e .
pip install pytest
pip install pytest coverage
- name: Test
run: python -m pytest -v
# Coverage settings (source, omissions) live in pyproject.toml.
run: |
coverage run -m pytest -v
coverage xml
- name: Upload coverage to Codecov
# One upload per commit is enough: the matrix differs only in interpreter
# version, not in which lines run.
if: matrix.python-version == '3.11'
uses: codecov/codecov-action@v5
with:
files: coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
# A Codecov outage should not turn the test suite red.
fail_ci_if_error: false
39 changes: 38 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ treat the names as a public interface: rename only deliberately, and update

## Releasing

The version is not written in `pyproject.toml` — it is derived from the latest git
The version is not written in `pyproject.toml` - it is derived from the latest git
tag at build time by
[poetry-dynamic-versioning](https://github.com/mtkennerly/poetry-dynamic-versioning).
`version = "0.0.0"` is a placeholder.
Expand All @@ -146,3 +146,40 @@ git tag v0.1.0 && git push --tags
Between tags the version reads as `0.1.0.post<n>.dev0+<sha>`. A repository with **no
tags at all** builds as `0.0.0.post<n>.dev0+<sha>`, so tag once after the initial
commit.

Pushing a `v*` tag triggers `.github/workflows/python-publish.yml`, which builds an
sdist and a wheel, runs `twine check` on them, and uploads to PyPI. The checkout in
that workflow uses `fetch-depth: 0` because poetry-dynamic-versioning calls
`git describe`: a clone that carries no tags builds as `0.0.0` without failing, so the
mistake would only surface on PyPI.

### One-time PyPI setup

Publishing uses [Trusted Publishing](https://docs.pypi.org/trusted-publishers/), so
there is no API token in the repository secrets. Before the first release, add a
pending publisher at <https://pypi.org/manage/account/publishing/>:

| Field | Value |
| --- | --- |
| PyPI project name | `verpex` |
| Owner | `Hendrik-code` |
| Repository name | `verpex` |
| Workflow name | `python-publish.yml` |
| Environment name | `pypi` |

Then create a `pypi` environment under the repository's *Settings -> Environments*.
Restricting it to tag pushes means a release cannot be published from a branch.

### Coverage

`tests.yml` runs the suite under `coverage` and uploads `coverage.xml` to Codecov
from the Python 3.11 matrix entry. Add the repository at
<https://app.codecov.io/gh/Hendrik-code/verpex> and store its upload token as the
`CODECOV_TOKEN` repository secret. Thresholds live in `codecov.yml` and are
informational, so a coverage drop annotates a pull request without blocking it.

Locally:

```bash
coverage run -m pytest && coverage report
```
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
</h1><br>

[![Paper](https://img.shields.io/badge/Paper-DT4H%202026-B31B1B)](#citation)
[![Stable Version](https://img.shields.io/pypi/v/verpex?label=stable)](https://pypi.python.org/pypi/verpex/)
[![Python Versions](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue)](https://www.python.org/downloads/)
[![tests](https://github.com/Hendrik-code/verpex/actions/workflows/tests.yml/badge.svg)](https://github.com/Hendrik-code/verpex/actions/workflows/tests.yml)
[![lint](https://github.com/Hendrik-code/verpex/actions/workflows/lint.yml/badge.svg)](https://github.com/Hendrik-code/verpex/actions/workflows/lint.yml)
[![codecov](https://codecov.io/gh/Hendrik-code/verpex/graph/badge.svg)](https://codecov.io/gh/Hendrik-code/verpex)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Built on TPTBox](https://img.shields.io/badge/built%20on-TPTBox-4c8eda)](https://github.com/Hendrik-code/TPTBox)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
Expand Down
22 changes: 22 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Coverage gates. Both are informational for now: Codecov reports the numbers on
# every PR but does not fail the check, so the badge can go up before the suite
# is broad enough to defend a hard threshold.
coverage:
status:
project:
default:
target: auto
threshold: 1%
informational: true
patch:
default:
target: auto
informational: true

comment:
layout: "condensed_header, diff, files"
require_changes: true

ignore:
- "unit_tests/**"
- "verpex/data/transforms.py" # vendored MONAI, kept diffable against upstream
16 changes: 15 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pytest = ">=8.0"
poetry-dynamic-versioning = ">=1.0.0,<2.0.0"
ruff = ">=0.11"
pre-commit = "*"
coverage = ">=7.0"
coverage = ">=7.2"
mypy = ">=1.8"
pandas-stubs = "*"
types-PyYAML = "*"
Expand Down Expand Up @@ -120,6 +120,20 @@ max-complexity = 20
[tool.pytest.ini_options]
testpaths = ["unit_tests"]

[tool.coverage.run]
source = ["verpex"]
branch = true
# Vendored MONAI transforms - exempted here the same way they are for ruff and mypy.
omit = ["verpex/data/transforms.py"]

[tool.coverage.report]
exclude_also = [
"if TYPE_CHECKING:",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"@(abc\\.)?abstractmethod",
]

[tool.mypy]
python_version = "3.10"
files = ["verpex"]
Expand Down
Loading