From 0a241f4db5c57c6302401629d736bdea379f15cd Mon Sep 17 00:00:00 2001 From: Jonas Teuwen Date: Sun, 14 Jun 2026 13:24:18 +0200 Subject: [PATCH 1/2] fix(build): release script improved GitOrigin-RevId: f1982292b6227da1990aa5c19ea771aecd2a46a0 --- .github/workflows/release.yml | 73 +++++++++++++++++++++++++++++++++-- MODULE.bazel | 2 +- requirements.in | 2 + requirements_darwin.txt | 67 ++++++++++++++++++++++++++++++++ requirements_linux.txt | 67 ++++++++++++++++++++++++++++++++ 5 files changed, 207 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a39cc6fe..99cefcf1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,9 +13,10 @@ # - smoke-wheels: install the freshly built wheel on the real target and run # tools/smoke_test_python.py (import + geometry op) for every Python. # - publish-pypi / publish-testpypi: upload via Trusted Publishing (OIDC). -# -# Like fimage and unlike fastslide, NO artifacts are attached to a GitHub -# Release: PyPI is the sole distribution channel for dlup. +# - github-release: needs every build/smoke/publish job, so a release is only +# cut when all selected platforms succeed. Attaches the wheels + sdist (with +# SLSA build-provenance attestation), mirroring fastslide. PyPI remains the +# primary install channel; the GitHub Release is a secondary, signed mirror. # # NOTE: dlup depends on fastslide and fimage-python at runtime, so a full PyPI # release (and its smoke tests) requires both of those to already be published @@ -303,3 +304,69 @@ jobs: packages-dir: dist repository-url: https://test.pypi.org/legacy/ skip-existing: true + github-release: + name: Publish GitHub Release + needs: [smoke-wheels, build-sdist, publish-pypi, publish-testpypi] + # publish-pypi / publish-testpypi are mutually exclusive, so one is always + # skipped. A skipped need is not success(), which would skip this job too; + # gate on "nothing failed/cancelled" instead so the skipped sibling is OK. + if: ${{ !cancelled() && !failure() }} + runs-on: ubuntu-24.04 + permissions: + contents: write # create the release + id-token: write # SLSA provenance signing + attestations: write # record the attestation + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Download wheels + uses: actions/download-artifact@v6 + with: + path: artifacts/wheels + pattern: "wheels-*" + merge-multiple: true + - name: Download sdist + uses: actions/download-artifact@v6 + with: + name: sdist + path: artifacts/sdist + - name: List collected artifacts + shell: bash + run: | + echo "Wheels:"; ls -l artifacts/wheels + echo "Sdist:"; ls -l artifacts/sdist + - name: Generate build provenance attestation + uses: actions/attest-build-provenance@v3 + with: + subject-path: | + artifacts/wheels/*.whl + artifacts/sdist/*.tar.gz + # Full release -> tag == version, marked latest. Snapshot -> a prerelease + # tagged -dev.. --clobber lets a re-run replace the + # assets on an existing tag (mirrors the publish steps' skip-existing). + - name: Publish + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + version="$(sed -n 's/^version *= *"\(.*\)"/\1/p' pyproject.toml | head -n1)" + if [ -z "$version" ]; then + echo "Could not read version from pyproject.toml" >&2 + exit 1 + fi + notes="dlup artifacts for \`${version}\`. Python wheels published to PyPI (also attached here), with the sdist as the source fallback." + if [[ "${{ inputs.release }}" == "true" ]]; then + tag="$version" + flags=(--latest --title "dlup $tag") + else + tag="${version}-dev.${GITHUB_SHA::7}" + flags=(--prerelease --title "dlup $tag") + fi + assets=(artifacts/wheels/*.whl artifacts/sdist/*.tar.gz) + if gh release view "$tag" >/dev/null 2>&1; then + gh release upload "$tag" "${assets[@]}" --clobber + else + gh release create "$tag" "${assets[@]}" \ + "${flags[@]}" --notes "$notes" --generate-notes + fi diff --git a/MODULE.bazel b/MODULE.bazel index 8737be5e..afe33752 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -81,7 +81,7 @@ git_override( bazel_dep(name = "fimage", version = "0.1.2") git_override( module_name = "fimage", - commit = "136bfbe5ca6ce8cdd2557a5b2b068bc71a5bd866", + commit = "327dead92b02ed6292131d915aa0a0f439dcdd56", remote = "https://github.com/NKI-AI/fimage.git", ) diff --git a/requirements.in b/requirements.in index 9a804f03..656b4494 100644 --- a/requirements.in +++ b/requirements.in @@ -7,6 +7,8 @@ pillow shapely tifffile xsdata +fastslide==0.7.4 +fimage-python==0.1.2 # OpenSlide backend. openslide-bin ships the prebuilt OpenSlide C library as a # wheel (Linux/macOS/Windows), so `import openslide` works in Bazel's sandbox diff --git a/requirements_darwin.txt b/requirements_darwin.txt index 54ab73be..e3e74e64 100644 --- a/requirements_darwin.txt +++ b/requirements_darwin.txt @@ -217,6 +217,72 @@ cycler==0.12.1 \ --hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 \ --hash=sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c # via matplotlib +fastslide==0.7.4 \ + --hash=sha256:07496c5a0ca90c76bd538621d9146d68ee55c5c7b4e1fb52b26ea8f708be42f5 \ + --hash=sha256:0d97e601d32cc5608d997e67aef4ea2f39e91c58cae5362e64c71b5a640de189 \ + --hash=sha256:178f14ad6e7ee638e730a5b0b942f3a74076fd0ea98ce48e4c0748ff3ef6bbee \ + --hash=sha256:180d2ea5b9ff449ff2a9437d7a707374a3b211116e8552b6c57fe46ac69e117f \ + --hash=sha256:20e434d846353e3a1fd78aeecfab9339fd93256a10661436a7e5144d4fc34154 \ + --hash=sha256:23148ceb5b6889cb38dfc38ef7c9fbe1a8b20386bd08dfdfa40af5c5717cab89 \ + --hash=sha256:2598c08c0b02158569cbfed9c7c33c38079813a3da65551ca2b1f8bfccb1c8f6 \ + --hash=sha256:2dc48dcf1f05470ab3af9e4e17fa0223e7505a16486dbc28845e0f55a8311641 \ + --hash=sha256:386df9ecba74cafe137338ff9f2e647198f2af625700c3b555603f23cf3603e1 \ + --hash=sha256:3b67a4c1fed9e5b37e25ebd2fbae0e8acb2d18de99fa33c8e36cd3bba5037c8d \ + --hash=sha256:53bd2af03e72fad7b4eeb3e740d4cd1bcfab8444c69075d8b3e1fded61a4d3b2 \ + --hash=sha256:5b9b596c1852d7d9b110696b2caa45e4ee7889a0664222e64b79f99510d33b71 \ + --hash=sha256:5d930bb7ea1a06040c1770d0281768523ac9a2d80c0aa287093a41f0b6a1ddf4 \ + --hash=sha256:5e98e97baf88c1fba1756f21c6b20bc67a3c464aea48759b75c15d59eb32f059 \ + --hash=sha256:61d52a01d0f09ca6646dfa6d48a4052a85c00d467cb4eaa44cd9303a27f31eb1 \ + --hash=sha256:6834834a76a9dd8685a419f1b6388c4278b5f223a75b29a7873bcb825c3a7193 \ + --hash=sha256:8a63882576783674258e2dd87c5cb3e299a8eff22f06f02ce8c7422903a7186e \ + --hash=sha256:8cb3f3deadc9ad012bd38e2015400ed29ae57b52cd5fe8ca09876edafe25ffbc \ + --hash=sha256:94103b460e29fe3053bf4ee4d821a6687f0d9f1e3e599bb561ea384a6a9df449 \ + --hash=sha256:997a12cbd564dcbe45e36eadb507baa8798fe8e729e80386d470b7882c324cd0 \ + --hash=sha256:9f2a24596452bfe7d203567abb6c27c58900e5269fa9dc10b8e62523710e24a2 \ + --hash=sha256:9fd14e296dca3e36c4665b4b603d5d374a6e6f15cdecce526bf7d6f892ee4577 \ + --hash=sha256:a183bd87d30f1dbca1cdb5fb6a13499d3891073249bf578eac5006134dbf0b19 \ + --hash=sha256:b3c69eb8c61052d1ec40d6dc19d7bfac0fdb984b4a13a58a268e0a492b978e4a \ + --hash=sha256:bb27b763919a7c9ef0052a75e9ba37dffc4a2b9916e6c67fabace2372fbe5d95 \ + --hash=sha256:bc57e028816cad6d173271a11acdb4ab610082bf5c028531e261386df5ac06ae \ + --hash=sha256:cc7364855446152df4e10c82e08d16ab66df5074bfdf8ab658689b1ebe6a77e4 \ + --hash=sha256:e28c709ddaae5128e0229aa63c013ac1c4942e78f377d26f2d2652dd26d7d1a4 \ + --hash=sha256:e8372e2c2f6c590e530a6889e39509658b63d20c7486a28031bbac9359929177 \ + --hash=sha256:f04e71fcde493182ae7ca5089ef1cec6c6459aba573a14a120dc80dcd8b3473a \ + --hash=sha256:f6ae451ca65f404e9e0caa5908b6cc1cc5692e8d532d179b79994b57fcf48d9f + # via -r requirements.in +fimage-python==0.1.2 \ + --hash=sha256:0ec259ccf20a16fd5d92b9ff3688a0684886bbecb56c98c526e2135bd7144147 \ + --hash=sha256:1380ca673563db8657993a31f4b31ff87bad51ead80a1d55cfe8c666ccf1aa04 \ + --hash=sha256:141aeb3854a6a9490ba9e1cf8bee27347ce8537d2a3f6fc2939ebb1d481a719c \ + --hash=sha256:1fe3ee4bf0181b961b0a7fbd388d5ab7ba88d936fc58232dca3ac422b872dfb9 \ + --hash=sha256:281fd45114e0ebad8dd9f9bf9707a758ca5425e0109ad7d393e3ffad6ba0c61f \ + --hash=sha256:326cdf24b0e06603ed22c8c1ca5d662c93c070ba39b771bcc61a3cc22d987d22 \ + --hash=sha256:3432f95c152825bf2acbefa43332613a584e9ca9e65e77e106b2fd1739bf7976 \ + --hash=sha256:3c6be2ddfa652303b511185951f3f149158681daadf5215d716ef1a7ef832079 \ + --hash=sha256:4d900d1d54bddf908f9cf6693e220ceb1bfe2ec99204591c88a566b8da526f02 \ + --hash=sha256:4dba1f0c6605855c2e10c7ead3b86cc7d01761c0dcdc2bd5ad2387abb00a2722 \ + --hash=sha256:5a529e58838cbfa8aaed5f8ebf554b476ab6a8667036e11e0cc534d983e3ffdf \ + --hash=sha256:62f3c1afaf639c60a42034afaa9acd922895b1796d86d48cd8be67557966de39 \ + --hash=sha256:77cebd6077cb8fc63af238f3415e5e70b03ea0b0ae333d7c6271b039bf667a62 \ + --hash=sha256:7e9f334f6d18b1befaa6f8f4b9d6f1998ee31b0e27609de83c4e2d0155dd5f46 \ + --hash=sha256:84ef119b9e5b1d8196e4fac641610fec5684c4c29326349f54bd6b0f823f4042 \ + --hash=sha256:865dff155225067a12b0efe137fddc6256346499f4902b449318df8b10ac1f46 \ + --hash=sha256:9230779d64350859400cd19846f3627c8b373b76e9d1a05e70a8d9a1c24e5211 \ + --hash=sha256:939c69efbe0d3403370a089deccfff84ca5124487fced265f9cd8c6e001e487d \ + --hash=sha256:a34c2bc3e26ad7172976f66c71a43516fd80b5d2dfb4530597c4e55e59c7a3bc \ + --hash=sha256:a893ebe5729ded01c625f83da5ca04f254d86fdf98caaaf090cc11d1a197a740 \ + --hash=sha256:a9697c7e72f72157b668ea9f7d7ed2fcacadc8fa5de13055ae067c3f96352b40 \ + --hash=sha256:c5d0d19ae774ab50106b6ce4afceb6695a8711825f387bb6807daa76bd1ce6e8 \ + --hash=sha256:cc23c8324a3322158ee73bad2afbd12b953208a5d654218149491559fd433435 \ + --hash=sha256:cf7649b74a157b3bf83fdd91fc54c688da4f1a8642c7b5270f2dcc9fc715fdd1 \ + --hash=sha256:d26d916cccb1e06b81e8ee84a002534d6643263622377116b69c6e3ca75445b4 \ + --hash=sha256:d3c6975db0bdc9c51df032d6fd8251576145c22235b8a873b1eb4b4c10c84b5e \ + --hash=sha256:d86c2c9c3d9f6f7def874623f3bfce3288bf6a8d43ddb86abcf6607b9018251c \ + --hash=sha256:da75688feac06214a3e2735ef2edfaf20abb9dccadf65ca3d68a4902ebf36cf2 \ + --hash=sha256:e988944a10820c70d6e30a6efe298adc974f471dca101539c66f80d50310a7a8 \ + --hash=sha256:f017a4c89ca4a8c7ff912970113c3a0ec63eb49aa04c4a8b90ffaf12556917c6 \ + --hash=sha256:f90726086d7c53299d749907be9fc4c3d1c37200d7fd185de6d218d6d939bf5d + # via -r requirements.in fonttools==4.63.0 \ --hash=sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69 \ --hash=sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c \ @@ -953,6 +1019,7 @@ numpy==2.4.6 \ # via # -r requirements.in # contourpy + # fimage-python # matplotlib # shapely # tifffile diff --git a/requirements_linux.txt b/requirements_linux.txt index e2720506..77b2d00d 100644 --- a/requirements_linux.txt +++ b/requirements_linux.txt @@ -217,6 +217,72 @@ cycler==0.12.1 \ --hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 \ --hash=sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c # via matplotlib +fastslide==0.7.4 \ + --hash=sha256:07496c5a0ca90c76bd538621d9146d68ee55c5c7b4e1fb52b26ea8f708be42f5 \ + --hash=sha256:0d97e601d32cc5608d997e67aef4ea2f39e91c58cae5362e64c71b5a640de189 \ + --hash=sha256:178f14ad6e7ee638e730a5b0b942f3a74076fd0ea98ce48e4c0748ff3ef6bbee \ + --hash=sha256:180d2ea5b9ff449ff2a9437d7a707374a3b211116e8552b6c57fe46ac69e117f \ + --hash=sha256:20e434d846353e3a1fd78aeecfab9339fd93256a10661436a7e5144d4fc34154 \ + --hash=sha256:23148ceb5b6889cb38dfc38ef7c9fbe1a8b20386bd08dfdfa40af5c5717cab89 \ + --hash=sha256:2598c08c0b02158569cbfed9c7c33c38079813a3da65551ca2b1f8bfccb1c8f6 \ + --hash=sha256:2dc48dcf1f05470ab3af9e4e17fa0223e7505a16486dbc28845e0f55a8311641 \ + --hash=sha256:386df9ecba74cafe137338ff9f2e647198f2af625700c3b555603f23cf3603e1 \ + --hash=sha256:3b67a4c1fed9e5b37e25ebd2fbae0e8acb2d18de99fa33c8e36cd3bba5037c8d \ + --hash=sha256:53bd2af03e72fad7b4eeb3e740d4cd1bcfab8444c69075d8b3e1fded61a4d3b2 \ + --hash=sha256:5b9b596c1852d7d9b110696b2caa45e4ee7889a0664222e64b79f99510d33b71 \ + --hash=sha256:5d930bb7ea1a06040c1770d0281768523ac9a2d80c0aa287093a41f0b6a1ddf4 \ + --hash=sha256:5e98e97baf88c1fba1756f21c6b20bc67a3c464aea48759b75c15d59eb32f059 \ + --hash=sha256:61d52a01d0f09ca6646dfa6d48a4052a85c00d467cb4eaa44cd9303a27f31eb1 \ + --hash=sha256:6834834a76a9dd8685a419f1b6388c4278b5f223a75b29a7873bcb825c3a7193 \ + --hash=sha256:8a63882576783674258e2dd87c5cb3e299a8eff22f06f02ce8c7422903a7186e \ + --hash=sha256:8cb3f3deadc9ad012bd38e2015400ed29ae57b52cd5fe8ca09876edafe25ffbc \ + --hash=sha256:94103b460e29fe3053bf4ee4d821a6687f0d9f1e3e599bb561ea384a6a9df449 \ + --hash=sha256:997a12cbd564dcbe45e36eadb507baa8798fe8e729e80386d470b7882c324cd0 \ + --hash=sha256:9f2a24596452bfe7d203567abb6c27c58900e5269fa9dc10b8e62523710e24a2 \ + --hash=sha256:9fd14e296dca3e36c4665b4b603d5d374a6e6f15cdecce526bf7d6f892ee4577 \ + --hash=sha256:a183bd87d30f1dbca1cdb5fb6a13499d3891073249bf578eac5006134dbf0b19 \ + --hash=sha256:b3c69eb8c61052d1ec40d6dc19d7bfac0fdb984b4a13a58a268e0a492b978e4a \ + --hash=sha256:bb27b763919a7c9ef0052a75e9ba37dffc4a2b9916e6c67fabace2372fbe5d95 \ + --hash=sha256:bc57e028816cad6d173271a11acdb4ab610082bf5c028531e261386df5ac06ae \ + --hash=sha256:cc7364855446152df4e10c82e08d16ab66df5074bfdf8ab658689b1ebe6a77e4 \ + --hash=sha256:e28c709ddaae5128e0229aa63c013ac1c4942e78f377d26f2d2652dd26d7d1a4 \ + --hash=sha256:e8372e2c2f6c590e530a6889e39509658b63d20c7486a28031bbac9359929177 \ + --hash=sha256:f04e71fcde493182ae7ca5089ef1cec6c6459aba573a14a120dc80dcd8b3473a \ + --hash=sha256:f6ae451ca65f404e9e0caa5908b6cc1cc5692e8d532d179b79994b57fcf48d9f + # via -r requirements.in +fimage-python==0.1.2 \ + --hash=sha256:0ec259ccf20a16fd5d92b9ff3688a0684886bbecb56c98c526e2135bd7144147 \ + --hash=sha256:1380ca673563db8657993a31f4b31ff87bad51ead80a1d55cfe8c666ccf1aa04 \ + --hash=sha256:141aeb3854a6a9490ba9e1cf8bee27347ce8537d2a3f6fc2939ebb1d481a719c \ + --hash=sha256:1fe3ee4bf0181b961b0a7fbd388d5ab7ba88d936fc58232dca3ac422b872dfb9 \ + --hash=sha256:281fd45114e0ebad8dd9f9bf9707a758ca5425e0109ad7d393e3ffad6ba0c61f \ + --hash=sha256:326cdf24b0e06603ed22c8c1ca5d662c93c070ba39b771bcc61a3cc22d987d22 \ + --hash=sha256:3432f95c152825bf2acbefa43332613a584e9ca9e65e77e106b2fd1739bf7976 \ + --hash=sha256:3c6be2ddfa652303b511185951f3f149158681daadf5215d716ef1a7ef832079 \ + --hash=sha256:4d900d1d54bddf908f9cf6693e220ceb1bfe2ec99204591c88a566b8da526f02 \ + --hash=sha256:4dba1f0c6605855c2e10c7ead3b86cc7d01761c0dcdc2bd5ad2387abb00a2722 \ + --hash=sha256:5a529e58838cbfa8aaed5f8ebf554b476ab6a8667036e11e0cc534d983e3ffdf \ + --hash=sha256:62f3c1afaf639c60a42034afaa9acd922895b1796d86d48cd8be67557966de39 \ + --hash=sha256:77cebd6077cb8fc63af238f3415e5e70b03ea0b0ae333d7c6271b039bf667a62 \ + --hash=sha256:7e9f334f6d18b1befaa6f8f4b9d6f1998ee31b0e27609de83c4e2d0155dd5f46 \ + --hash=sha256:84ef119b9e5b1d8196e4fac641610fec5684c4c29326349f54bd6b0f823f4042 \ + --hash=sha256:865dff155225067a12b0efe137fddc6256346499f4902b449318df8b10ac1f46 \ + --hash=sha256:9230779d64350859400cd19846f3627c8b373b76e9d1a05e70a8d9a1c24e5211 \ + --hash=sha256:939c69efbe0d3403370a089deccfff84ca5124487fced265f9cd8c6e001e487d \ + --hash=sha256:a34c2bc3e26ad7172976f66c71a43516fd80b5d2dfb4530597c4e55e59c7a3bc \ + --hash=sha256:a893ebe5729ded01c625f83da5ca04f254d86fdf98caaaf090cc11d1a197a740 \ + --hash=sha256:a9697c7e72f72157b668ea9f7d7ed2fcacadc8fa5de13055ae067c3f96352b40 \ + --hash=sha256:c5d0d19ae774ab50106b6ce4afceb6695a8711825f387bb6807daa76bd1ce6e8 \ + --hash=sha256:cc23c8324a3322158ee73bad2afbd12b953208a5d654218149491559fd433435 \ + --hash=sha256:cf7649b74a157b3bf83fdd91fc54c688da4f1a8642c7b5270f2dcc9fc715fdd1 \ + --hash=sha256:d26d916cccb1e06b81e8ee84a002534d6643263622377116b69c6e3ca75445b4 \ + --hash=sha256:d3c6975db0bdc9c51df032d6fd8251576145c22235b8a873b1eb4b4c10c84b5e \ + --hash=sha256:d86c2c9c3d9f6f7def874623f3bfce3288bf6a8d43ddb86abcf6607b9018251c \ + --hash=sha256:da75688feac06214a3e2735ef2edfaf20abb9dccadf65ca3d68a4902ebf36cf2 \ + --hash=sha256:e988944a10820c70d6e30a6efe298adc974f471dca101539c66f80d50310a7a8 \ + --hash=sha256:f017a4c89ca4a8c7ff912970113c3a0ec63eb49aa04c4a8b90ffaf12556917c6 \ + --hash=sha256:f90726086d7c53299d749907be9fc4c3d1c37200d7fd185de6d218d6d939bf5d + # via -r requirements.in fonttools==4.63.0 \ --hash=sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69 \ --hash=sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c \ @@ -953,6 +1019,7 @@ numpy==2.4.6 \ # via # -r requirements.in # contourpy + # fimage-python # matplotlib # shapely # tifffile From 76e799345bb700c8c02c67b6ac8a04f91760d78a Mon Sep 17 00:00:00 2001 From: Jonas Teuwen Date: Sun, 14 Jun 2026 14:13:17 +0200 Subject: [PATCH 2/2] fix(build): openslide not necessarily required GitOrigin-RevId: 555a089bf4c49f12d20f999b4a41ff916d43079e --- .github/workflows/release.yml | 4 ++-- BUILD.bazel | 22 +++++++++++++++++++++- dlup/backends/__init__.py | 5 ++++- dlup/utils/imports.py | 25 ++++++++++++++++++++++++- 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 99cefcf1..e654863e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -159,7 +159,7 @@ jobs: - name: Checkout uses: actions/checkout@v5 - name: Set up uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 - name: Build sdist shell: bash run: uv build --sdist --out-dir artifacts/sdist @@ -219,7 +219,7 @@ jobs: run: softwareupdate --install-rosetta --agree-to-license - name: Set up uv if: steps.gate.outputs.run == 'true' - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 - name: Download wheels if: steps.gate.outputs.run == 'true' uses: actions/download-artifact@v6 diff --git a/BUILD.bazel b/BUILD.bazel index 109d0367..9c9ab10e 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -43,10 +43,16 @@ DLUP_VERSION = "0.9.2" # We intentionally avoid depending on Bazel `requirement(...)` targets here so # the wheel build can work across multiple Python versions without requiring a # per-Python locked wheel set in the repo. +# +# Keep this in sync with pyproject.toml: openslide is an OPTIONAL backend, so +# openslide-python is NOT a hard dependency. It lives in the `openslide` extra +# below alongside the prebuilt `openslide-bin` (which ships libopenslide). The +# wheel built here (Linux/macOS, via Bazel) must expose the same `openslide` +# extra as the meson-python wheel (Windows), so `pip install dlup[openslide]` +# behaves identically on every platform. DLUP_WHEEL_REQUIRES = [ "fastslide", "fimage-python", - "openslide-python", "numpy", "packaging", "pillow", @@ -55,6 +61,19 @@ DLUP_WHEEL_REQUIRES = [ "xsdata", ] +# Optional dependency groups (PEP 508 extras), mirroring +# pyproject.toml [project.optional-dependencies]. +DLUP_WHEEL_EXTRA_REQUIRES = { + "openslide": [ + "openslide-python>=1.4", + "openslide-bin", + ], + "dev": [ + "pytest>=7.0", + "pytest-mock", + ], +} + # Platform-specific compatibility - supports Linux, macOS, and Windows. COMPATIBLE_TARGETS = select({ "@platforms//os:linux": [], @@ -261,6 +280,7 @@ multi_version_py_wheel( platform = WHEEL_PLATFORM, python_requires = ">=3.10", python_versions = PYTHON_VERSIONS, + extra_requires = DLUP_WHEEL_EXTRA_REQUIRES, requires = DLUP_WHEEL_REQUIRES, strip_path_prefixes = [], summary = "Deep Learning Utilities for Pathology", diff --git a/dlup/backends/__init__.py b/dlup/backends/__init__.py index ee8b4596..14b902df 100644 --- a/dlup/backends/__init__.py +++ b/dlup/backends/__init__.py @@ -18,7 +18,10 @@ if not OPENSLIDE_AVAILABLE: warnings.warn( - "Openslide is not available. OpenSlide backend will not be available. To install it, run `pip install openslide-python`." + "OpenSlide is not available. The OpenSlide backend will be disabled. This means either the " + "`openslide-python` wrapper is not installed or the native `libopenslide` library could not be " + "loaded. To enable it, install both with `pip install dlup[openslide]` (which pulls in the " + "prebuilt `openslide-bin`)." ) else: from .openslide_backend import OpenSlideSlide as OpenSlideSlide # noqa: F401 diff --git a/dlup/utils/imports.py b/dlup/utils/imports.py index c692eddd..b056b099 100644 --- a/dlup/utils/imports.py +++ b/dlup/utils/imports.py @@ -34,10 +34,33 @@ def _module_available(module_path: str) -> bool: return False +def _openslide_available() -> bool: + r"""Check whether the OpenSlide backend can actually be used. + + Unlike a plain ``find_spec`` probe, this performs a real import: the + ``openslide-python`` wrapper ``dlopen``s the native ``libopenslide`` at + import time, so the wrapper package being present does not guarantee a + working backend. When the native library is missing the wrapper raises + ``OSError``/``ModuleNotFoundError`` on import, in which case the backend is + treated as unavailable rather than crashing every ``import dlup``. + + Returns: + ``True`` only if ``import openslide`` succeeds (wrapper *and* native + library present), ``False`` otherwise. + """ + if not _module_available("openslide"): + return False + try: + import openslide # noqa: F401 # pylint: disable=import-outside-toplevel,unused-import + except (ImportError, OSError): + return False + return True + + PYTORCH_AVAILABLE = _module_available("pytorch") PYHALOXML_AVAILABLE = _module_available("pyhaloxml") DARWIN_SDK_AVAILABLE = _module_available("darwin") SHAPELY_AVAILABLE = _module_available("shapely") -OPENSLIDE_AVAILABLE = _module_available("openslide") +OPENSLIDE_AVAILABLE = _openslide_available() TIFFFILE_AVAILABLE = _module_available("tifffile") AIOHTTP_AVAILABLE = _module_available("aiohttp")