From e9cc7d541f8f29602e888ecfb322cdd93007b206 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Wed, 1 Nov 2023 02:55:43 -0500 Subject: [PATCH 1/8] templates/release: procedure updates Update Fedora and check that it propagated to the Copr before announcing. Also announce to forum.image.sc. Signed-off-by: Benjamin Gilbert --- .github/ISSUE_TEMPLATE/release.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md index 8c533e99..c480d1d0 100644 --- a/.github/ISSUE_TEMPLATE/release.md +++ b/.github/ISSUE_TEMPLATE/release.md @@ -12,6 +12,8 @@ - [ ] `cd` into website checkout; `rm -r api/python && unzip /path/to/downloaded/openslide-python-docs.zip && mv openslide-python-docs-* api/python` - [ ] Update website: `_data/releases.yaml`, `_includes/news.md` - [ ] Update Ubuntu PPA -- [ ] Send mail to -announce and -users - [ ] Update Fedora and EPEL packages +- [ ] Check that [Copr package](https://copr.fedorainfracloud.org/coprs/g/openslide/openslide/builds/) built successfully +- [ ] Send mail to -announce and -users +- [ ] Post to [forum.image.sc](https://forum.image.sc/c/announcements/10) - [ ] Update MacPorts package From ef6178554d6692ee5d50d537b33aad09d6cc389b Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Wed, 1 Nov 2023 05:58:01 -0500 Subject: [PATCH 2/8] workflows: fail on curl error 7z will fail if given an HTTP error page, so this is only a cleanup. Signed-off-by: Benjamin Gilbert --- .github/workflows/python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index d5d8287a..680042f7 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -86,7 +86,7 @@ jobs: mkdir -p c:\\openslide cd c:\\openslide zipname=openslide-win64-${BIN_RELEASE} - curl -LO "https://github.com/openslide/openslide-bin/releases/download/v${BIN_RELEASE}/${zipname}.zip" + curl -LfO "https://github.com/openslide/openslide-bin/releases/download/v${BIN_RELEASE}/${zipname}.zip" 7z x ${zipname}.zip echo "OPENSLIDE_PATH=c:\\openslide\\${zipname}\\bin" >> $GITHUB_ENV - name: Build wheel From b893ba288c257e895a2f8ca9e95cfe17661587d2 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Wed, 1 Nov 2023 00:28:03 -0500 Subject: [PATCH 3/8] Switch to PEP 621 project metadata Drop obsolete test_suite and zip_safe settings. Combine multi-word keywords. Add repo and documentation URLs. PEP 621 support requires setuptools >= 61. Closes: https://github.com/openslide/openslide-python/issues/167 Signed-off-by: Benjamin Gilbert --- .gitignore | 1 - pyproject.toml | 45 +++++++++++++++++++++++++++++++++++++++++++++ setup.py | 47 ----------------------------------------------- 3 files changed, 45 insertions(+), 48 deletions(-) create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore index 87d0e176..d3dc441a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ /build /dist -/MANIFEST /*.egg-info *.pyc diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..0bb61d7b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,45 @@ +[project] +name = "openslide-python" +maintainers = [ + {name = "OpenSlide project", email = "openslide-users@lists.andrew.cmu.edu"} +] +description = "Python interface to OpenSlide" +readme = "README.md" +license = {text = "GNU Lesser General Public License, version 2.1"} +keywords = ["OpenSlide", "whole-slide image", "virtual slide", "library"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: Healthcare Industry", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Scientific/Engineering :: Bio-Informatics", +] +requires-python = ">= 3.8" +dependencies = ["Pillow"] +dynamic = ["version"] + +[project.urls] +Homepage = "https://openslide.org/" +Documentation = "https://openslide.org/api/python/" +Repository = "https://github.com/openslide/openslide-python" + +[tool.setuptools] +packages = ["openslide"] + +[tool.setuptools.dynamic] +version = {attr = "openslide._version.__version__"} + +[build-system] +requires = ["setuptools >= 61.0.0"] +build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py index 34a71afc..8f821a95 100644 --- a/setup.py +++ b/setup.py @@ -1,54 +1,7 @@ -import os - from setuptools import Extension, setup -# Load version string -_verfile = os.path.join(os.path.dirname(__file__), 'openslide', '_version.py') -with open(_verfile) as _fh: - exec(_fh.read()) # instantiates __version__ - -with open('README.md') as _fh: - _long_description = _fh.read() - setup( - name='openslide-python', - version=__version__, # noqa: F821 undefined-name __version__ - packages=[ - 'openslide', - ], ext_modules=[ Extension('openslide._convert', ['openslide/_convert.c']), ], - test_suite='tests', - maintainer='OpenSlide project', - maintainer_email='openslide-users@lists.andrew.cmu.edu', - description='Python interface to OpenSlide', - long_description=_long_description, - long_description_content_type='text/markdown', - license='GNU Lesser General Public License, version 2.1', - keywords='openslide whole-slide image virtual slide library', - url='https://openslide.org/', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Intended Audience :: Healthcare Industry', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Topic :: Scientific/Engineering :: Bio-Informatics', - ], - python_requires='>=3.8', - install_requires=[ - 'Pillow', - ], - zip_safe=True, ) From 37f6fb914c617802e1e3bcf676a0e4939b809ec1 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Wed, 1 Nov 2023 01:45:39 -0500 Subject: [PATCH 4/8] workflows: build with Python `build` package It's the build tool currently recommended by PyPA. Since it builds in a venv, we can stop installing some dependencies. Signed-off-by: Benjamin Gilbert --- .github/workflows/python.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 680042f7..48ed8458 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -41,7 +41,7 @@ jobs: - name: Install Python tools run: | python -m pip install --upgrade pip - pip install jinja2 pytest setuptools + pip install build jinja2 pytest - name: Install OpenSlide run: | case "${{ matrix.os }}" in @@ -52,8 +52,10 @@ jobs: brew install openslide ;; esac + - name: Build wheel + run: python -m build -w - name: Install - run: pip install . + run: pip install dist/*.whl - name: Run tests run: pytest -v - name: Tile slide @@ -80,7 +82,7 @@ jobs: - name: Install Python tools run: | python -m pip install --upgrade pip - pip install flask Pillow pytest setuptools wheel + pip install build flask pytest - name: Install OpenSlide run: | mkdir -p c:\\openslide @@ -91,13 +93,13 @@ jobs: echo "OPENSLIDE_PATH=c:\\openslide\\${zipname}\\bin" >> $GITHUB_ENV - name: Build wheel run: | - python setup.py bdist_wheel + python -m build -w basename=openslide-python-wheels-$GITHUB_RUN_NUMBER-$(echo $GITHUB_SHA | cut -c-10) mkdir -p "artifacts/${basename}" mv dist/*.whl "artifacts/${basename}" echo "basename=${basename}" >> $GITHUB_ENV - name: Install - run: pip install -e . + run: pip install artifacts/${basename}/*.whl - name: Run tests # Reads OPENSLIDE_PATH run: pytest -v From d6af87d4ec4ab70d0f8b7e3f75ffcd6420d546d8 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Wed, 1 Nov 2023 02:11:48 -0500 Subject: [PATCH 5/8] workflows: build wheels for Linux and macOS The convert module doesn't use any libc APIs, so we can build on any Linux distro and auditwheel will tag the wheel as the oldest manylinux. auditwheel requires setuptools on Python 3.12 for distutils. Fixes: https://github.com/openslide/openslide-python/issues/126 Fixes: https://github.com/openslide/openslide-python/issues/187 Signed-off-by: Benjamin Gilbert --- .github/workflows/python.yml | 57 ++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 48ed8458..e293ff24 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -31,17 +31,45 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] + include: + # Python 3.8 is too old to support universal binaries, and + # setup-python's Python 3.9 and 3.10 won't build them. Use the + # last upstream patch releases that ship with installers. + # https://github.com/actions/setup-python/issues/439#issuecomment-1247646682 + - os: macos-latest + python-version: "3.9" + upstream-python: 3.9.13 + - os: macos-latest + python-version: "3.10" + upstream-python: 3.10.11 steps: - name: Check out repo uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} + if: matrix.upstream-python == null uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + - name: Set up Python ${{ matrix.python-version }} (macOS fallback) + if: matrix.upstream-python != null + run: | + pkgdir="${{ runner.temp }}/python" + mkdir -p "$pkgdir/bin" + pkg="$pkgdir/python.pkg" + curl -Lfo "$pkg" \ + "https://www.python.org/ftp/python/${{ matrix.upstream-python }}/python-${{ matrix.upstream-python }}-macos11.pkg" + sudo installer -pkg "$pkg" -target / + for bin in python pip; do + ln -s /usr/local/bin/${bin}3 $pkgdir/bin/${bin} + done + export PATH="$pkgdir/bin:$PATH" + echo "PATH=$PATH" >> $GITHUB_ENV + python -V + pip -V - name: Install Python tools run: | python -m pip install --upgrade pip - pip install build jinja2 pytest + pip install auditwheel build jinja2 pytest setuptools - name: Install OpenSlide run: | case "${{ matrix.os }}" in @@ -53,13 +81,36 @@ jobs: ;; esac - name: Build wheel - run: python -m build -w + run: | + python -m build -w + case "${{ matrix.os }}" in + ubuntu-*) + mkdir old + mv dist/*.whl old/ + auditwheel repair --only-plat -w dist old/*whl + ;; + macos-*) + if [ "${{ matrix.python-version }}" != 3.8 -a ! -e dist/*universal2* ]; then + echo "Wheel is not universal:" + ls dist + exit 1 + fi + esac + basename=openslide-python-wheels-$GITHUB_RUN_NUMBER-$(echo $GITHUB_SHA | cut -c-10) + mkdir -p "artifacts/${basename}" + mv dist/* "artifacts/${basename}" + echo "basename=${basename}" >> $GITHUB_ENV - name: Install - run: pip install dist/*.whl + run: pip install artifacts/${basename}/*.whl - name: Run tests run: pytest -v - name: Tile slide run: python examples/deepzoom/deepzoom_tile.py --viewer -o tiled tests/fixtures/small.svs + - name: Archive wheel + uses: actions/upload-artifact@v3 + with: + name: ${{ env.basename }} + path: artifacts windows: name: Windows needs: pre-commit From 3e2c3b506df0064a73f4246de3f96cb7f4e7d5ff Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Wed, 1 Nov 2023 05:57:53 -0500 Subject: [PATCH 6/8] workflows: generate sdist tarball in CI By omitting the `python -m build` -w argument in one job, verify that the source tarball can successfully build a wheel. Use that source tarball in releases rather than building it on a developer's machine. Signed-off-by: Benjamin Gilbert --- .github/ISSUE_TEMPLATE/release.md | 1 - .github/workflows/python.yml | 8 +++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md index c480d1d0..9bdb55c1 100644 --- a/.github/ISSUE_TEMPLATE/release.md +++ b/.github/ISSUE_TEMPLATE/release.md @@ -5,7 +5,6 @@ - [ ] `git clean -dxf && mkdir dist` - [ ] Find the [workflow run](https://github.com/openslide/openslide-python/actions) for the tag; download its docs and wheels artifacts - [ ] `unzip /path/to/downloaded/openslide-python-wheels.zip && mv openslide-python-wheels-*/* dist/` -- [ ] `python setup.py sdist` - [ ] `twine upload dist/*` - [ ] Recompress tarball with `xz` - [ ] Attach release notes to [GitHub release](https://github.com/openslide/openslide-python/releases/new); upload tarballs and wheels diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index e293ff24..296f2a0d 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -32,6 +32,9 @@ jobs: os: [ubuntu-latest, macos-latest] python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] include: + - os: ubuntu-latest + python-version: "3.12" + sdist: sdist # Python 3.8 is too old to support universal binaries, and # setup-python's Python 3.9 and 3.10 won't build them. Use the # last upstream patch releases that ship with installers. @@ -82,7 +85,10 @@ jobs: esac - name: Build wheel run: | - python -m build -w + if [ -z "${{ matrix.sdist }}" ]; then + wheel_only=-w + fi + python -m build $wheel_only case "${{ matrix.os }}" in ubuntu-*) mkdir old From fe10747e108040da9889c43794eb8686db8ea395 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Wed, 1 Nov 2023 15:23:46 -0500 Subject: [PATCH 7/8] pre-commit-config: bump versions Signed-off-by: Benjamin Gilbert --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a1043487..7145ebb4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ exclude: '^(COPYING\.LESSER|examples/deepzoom/static/.*\.js)$' repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-added-large-files - id: check-merge-conflict @@ -14,7 +14,7 @@ repos: exclude: '^\.github/.*\.md$' - repo: https://github.com/asottile/pyupgrade - rev: v3.14.0 + rev: v3.15.0 hooks: - id: pyupgrade name: Modernize python code @@ -27,7 +27,7 @@ repos: name: Reorder python imports with isort - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 23.10.1 hooks: - id: black name: Format python code with black From 83b18c75a25f89459253b6338322af0d11c183d4 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Wed, 1 Nov 2023 15:33:41 -0500 Subject: [PATCH 8/8] Move isort, pytest, rstcheck configs into pyproject.toml pre-commit will not support pyproject.toml, and flake8 doesn't support it yet. Signed-off-by: Benjamin Gilbert --- .isort.cfg | 3 --- .pre-commit-config.yaml | 2 +- .rstcheck.cfg | 2 -- MANIFEST.in | 2 +- pyproject.toml | 15 +++++++++++++++ pytest.ini | 7 ------- 6 files changed, 17 insertions(+), 14 deletions(-) delete mode 100644 .isort.cfg delete mode 100644 .rstcheck.cfg delete mode 100644 pytest.ini diff --git a/.isort.cfg b/.isort.cfg deleted file mode 100644 index 716bf5fc..00000000 --- a/.isort.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[settings] -profile = black -force_sort_within_sections = true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7145ebb4..6ecd122d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -57,7 +57,7 @@ repos: hooks: - id: rstcheck name: Validate reStructuredText syntax - additional_dependencies: [sphinx] + additional_dependencies: [sphinx, toml] - repo: meta hooks: diff --git a/.rstcheck.cfg b/.rstcheck.cfg deleted file mode 100644 index 38ae5093..00000000 --- a/.rstcheck.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[rstcheck] -ignore_messages=(Hyperlink target ".*" is not referenced\.$) diff --git a/MANIFEST.in b/MANIFEST.in index b84f5159..2aa1e1d6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include *.md pytest.ini +include *.md recursive-include doc *.py *.rst recursive-include examples *.html *.js *.png *.py recursive-include tests *.dcm *.png *.py *.svs *.tiff diff --git a/pyproject.toml b/pyproject.toml index 0bb61d7b..53914a58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,21 @@ packages = ["openslide"] [tool.setuptools.dynamic] version = {attr = "openslide._version.__version__"} +[tool.isort] +profile = "black" +force_sort_within_sections = true + +[tool.pytest.ini_options] +minversion = "7.0" +# don't try to import openslide from the source directory, since it doesn't +# have the compiled extension module +addopts = "--import-mode importlib" +# allow tests to import common module +pythonpath = "tests" + +[tool.rstcheck] +ignore_messages = "(Hyperlink target \".*\" is not referenced\\.$)" + [build-system] requires = ["setuptools >= 61.0.0"] build-backend = "setuptools.build_meta" diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 8581b237..00000000 --- a/pytest.ini +++ /dev/null @@ -1,7 +0,0 @@ -[pytest] -minversion = 7.0 -# don't try to import openslide from the source directory, since it doesn't -# have the compiled extension module -addopts = --import-mode importlib -# allow tests to import common module -pythonpath = tests