From e3f8846a6f8d6303ac050f4cea72e70e3f5e373f Mon Sep 17 00:00:00 2001 From: Remis Date: Tue, 1 Sep 2026 20:03:35 +0100 Subject: [PATCH] Publish to PyPI on a v* tag dcio not being on PyPI has become a running cost. It appears in EKDIST's install_requires, where pip cannot resolve it; in two CI workflows that install it from git before anything else; and in HJCFIT's ImportError text. Publishing removes all three. Adds an MIT LICENSE. dcio had none at all, which meant "all rights reserved" by default -- nobody could legally use what was about to be uploaded. MIT rather than a copyleft licence because dcio sits underneath SCALCS (GPL-2.0-only) and HJCFIT (GPL-3.0), and those two are mutually incompatible: any GPL choice for the foundation would conflict with one of its own consumers. EKDIST is already MIT. Completes the packaging metadata: readme, licence, classifiers, keywords and project URLs. Without a readme the PyPI page would have been a bare title. The workflow holds no API token; PyPI verifies the workflow's OIDC identity instead. A version number cannot be reused once uploaded, so publishing runs only on a v* tag. Every push still builds and checks, so a tag is only ever cut from something already known to build. Three build gates: twine check --strict, an assertion that exactly one py3-none-any wheel is produced (dcio is pure Python and should stay that way), and a build of the wheel from the unpacked sdist alone, which is the only way to catch a file missing from the source distribution. Verified locally: both artifacts build, twine check passes, the wheel carries all four subpackages and the licence, and the Qt viewer is correctly excluded. Co-Authored-By: Claude Opus 5 --- .github/workflows/release.yml | 100 ++++++++++++++++++++++++++++++++++ LICENSE | 21 +++++++ pyproject.toml | 28 +++++++++- 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release.yml create mode 100644 LICENSE diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1e1f107 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,100 @@ +# Publishing dcio to PyPI. +# +# The repository holds no API token. PyPI verifies this workflow's OIDC +# identity instead (Trusted Publishing), which means there is no secret to +# leak, rotate, or hand to anyone. +# +# A version number cannot be reused once uploaded, so publishing runs only on +# a v* tag, never on an ordinary push. Every push still builds and checks the +# artifacts, so a tag is only ever cut from something already known to build. + +name: Release + +on: + push: + branches: [master] + tags: ['v*'] + pull_request: + branches: [master] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build: + name: build sdist and wheel + runs-on: ubuntu-24.04 + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-python@v6 + with: + python-version: '3.12' + + - name: Build + run: | + python -m pip install --upgrade pip build twine + python -m build + + # twine check catches the metadata problems that make a PyPI page render + # badly or an upload fail outright -- a README that is not valid + # reStructuredText or Markdown, a missing long_description content type. + - name: Check metadata + run: | + twine check --strict dist/* + ls -la dist/ + + # dcio is pure Python, so exactly one wheel serves every platform. If + # that ever stops being true the wheel filename changes, and this notices. + - name: One universal wheel + run: | + n=$(ls dist/*.whl | wc -l) + test "$n" -eq 1 || { echo "expected 1 wheel, got $n"; exit 1; } + ls dist/*-py3-none-any.whl >/dev/null || { + echo "wheel is not py3-none-any:"; ls dist/*.whl; exit 1; } + echo "one universal wheel, as expected" + + # The sdist must be able to build the wheel on its own: a file missing + # from it is invisible until someone installs from source. + - name: Wheel builds from the sdist alone + run: | + mkdir -p /tmp/fromsdist && tar -xzf dist/*.tar.gz -C /tmp/fromsdist + cd /tmp/fromsdist/*/ + python -m build --wheel --outdir /tmp/sdist-wheel + ls -la /tmp/sdist-wheel + + - uses: actions/upload-artifact@v7 + with: + name: dist + path: dist/ + + publish: + name: publish to PyPI + if: startsWith(github.ref, 'refs/tags/v') + needs: [build] + runs-on: ubuntu-24.04 + + environment: + name: pypi + url: https://pypi.org/p/dcio + + permissions: + id-token: write + + steps: + - uses: actions/download-artifact@v7 + with: + name: dist + path: dist + + - name: What is about to be published + run: ls -la dist/ + + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9fef9ee --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Remis Lape + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/pyproject.toml b/pyproject.toml index 15fed25..943ef67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,10 +5,36 @@ build-backend = "setuptools.build_meta" [project] name = "dcio" version = "0.1.0" -description = "I/O library for electrophysiology file formats used by the DCProgs suite" +description = "I/O and record layer for electrophysiology file formats used by the DCPROGS suite" +readme = "README.md" +license = "MIT" +license-files = ["LICENSE"] authors = [{ name = "Remis Lape" }] requires-python = ">=3.11" dependencies = ["numpy>=2.0"] +keywords = [ + "electrophysiology", + "single-channel", + "ion-channel", + "patch-clamp", + "scn", + "dcprogs", +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering :: Bio-Informatics", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Operating System :: OS Independent", +] + +[project.urls] +Homepage = "https://github.com/DCPROGS/dcio" +Repository = "https://github.com/DCPROGS/dcio" +Issues = "https://github.com/DCPROGS/dcio/issues" [project.optional-dependencies] dev = ["pytest>=7.4", "pytest-cov"]