From ab1dbfe8486e0161730494370164fa434bab191c Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sat, 7 Sep 2024 15:12:11 -0700 Subject: [PATCH 1/2] lowlevel: single space between sentences in ModuleNotFoundErrors It reads a bit better. Signed-off-by: Benjamin Gilbert --- openslide/lowlevel.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openslide/lowlevel.py b/openslide/lowlevel.py index c80d8668..c70f0218 100644 --- a/openslide/lowlevel.py +++ b/openslide/lowlevel.py @@ -65,8 +65,8 @@ def try_load(names): return try_load(['libopenslide-1.dll', 'libopenslide-0.dll']) except FileNotFoundError: raise ModuleNotFoundError( - "Couldn't locate OpenSlide DLL. " - "Did you call os.add_dll_directory()? " + "Couldn't locate OpenSlide DLL. " + "Did you call os.add_dll_directory()? " "https://openslide.org/api/python/#installing" ) elif platform.system() == 'Darwin': @@ -81,8 +81,9 @@ def try_load(names): lib = ctypes.util.find_library('openslide') if lib is None: raise ModuleNotFoundError( - "Couldn't locate OpenSlide dylib. Is OpenSlide installed " - "correctly? https://openslide.org/api/python/#installing" + "Couldn't locate OpenSlide dylib. " + "Is OpenSlide installed correctly? " + "https://openslide.org/api/python/#installing" ) return cdll.LoadLibrary(lib) else: From 9e853e6b1b922f7dec6c7717b5f2f4a81f6f2d76 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sat, 7 Sep 2024 15:37:53 -0700 Subject: [PATCH 2/2] Support openslide-bin Prefer loading OpenSlide from the openslide-bin package if available. For ease of use, document openslide-bin as the preferred installation option. Don't document manual installation of openslide-bin binaries on Linux and macOS, since there isn't generally an advantage to doing that, but leave the existing Windows Zip instructions for compatibility and to explain os.add_dll_directory(). Add an explanatory ModuleNotFoundError on Linux if OpenSlide is not found. Signed-off-by: Benjamin Gilbert --- .github/workflows/python.yml | 25 +++++++++++++++++++------ README.md | 12 +++++++++--- doc/index.rst | 14 +++++++++----- openslide/lowlevel.py | 22 +++++++++++++++++++--- 4 files changed, 56 insertions(+), 17 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index f567bed2..c575a6f0 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -44,9 +44,11 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] python-version: [3.8, 3.9, "3.10", "3.11", "3.12", "3.13-dev"] + openslide: [system, wheel] include: - os: ubuntu-latest python-version: "3.12" + openslide: system 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 @@ -86,7 +88,8 @@ jobs: run: | python -m pip install --upgrade pip pip install auditwheel build jinja2 pytest - - name: Install OpenSlide + - name: Install OpenSlide (system) + if: matrix.openslide == 'system' run: | case "${{ matrix.os }}" in ubuntu-latest) @@ -99,6 +102,9 @@ jobs: brew install openslide ;; esac + - name: Install OpenSlide (wheel) + if: matrix.openslide == 'wheel' + run: pip install openslide-bin - name: Build dist run: | if [ -z "${{ matrix.sdist }}" ]; then @@ -124,9 +130,10 @@ jobs: fi mkdir -p "artifacts/whl/${{ needs.pre-commit.outputs.dist-base }}" mv dist/* "artifacts/whl/${{ needs.pre-commit.outputs.dist-base }}" - # save version-specific wheels and oldest abi3 wheel + # from system builds, save version-specific wheels and oldest abi3 wheel python -c 'import sys - if sys.version_info < (3, 12): print("archive_wheel=1")' >> $GITHUB_ENV + if sys.version_info < (3, 12) and "${{ matrix.openslide }}" == "system": + print("archive_wheel=1")' >> $GITHUB_ENV - name: Install run: pip install artifacts/whl/${{ needs.pre-commit.outputs.dist-base }}/*.whl - name: Run tests @@ -158,6 +165,7 @@ jobs: strategy: matrix: python-version: [3.8, 3.9, "3.10", "3.11", "3.12", "3.13-dev"] + openslide: [zip, wheel] steps: - name: Check out repo uses: actions/checkout@v4 @@ -169,7 +177,8 @@ jobs: run: | python -m pip install --upgrade pip pip install build flask pytest - - name: Install OpenSlide + - name: Install OpenSlide (zip) + if: matrix.openslide == 'zip' env: GH_TOKEN: ${{ github.token }} run: | @@ -184,14 +193,18 @@ jobs: --pattern "${zipname}.zip" 7z x ${zipname}.zip echo "OPENSLIDE_PATH=c:\\openslide\\${zipname}\\bin" >> $GITHUB_ENV + - name: Install OpenSlide (wheel) + if: matrix.openslide == 'wheel' + run: pip install openslide-bin - name: Build wheel run: | python -m build -w mkdir -p "artifacts/whl/${{ needs.pre-commit.outputs.dist-base }}" mv dist/*.whl "artifacts/whl/${{ needs.pre-commit.outputs.dist-base }}" - # save version-specific wheels and oldest abi3 wheel + # from zip builds, save version-specific wheels and oldest abi3 wheel python -c 'import sys - if sys.version_info < (3, 12): print("archive_wheel=1")' >> $GITHUB_ENV + if sys.version_info < (3, 12) and "${{ matrix.openslide }}" == "zip": + print("archive_wheel=1")' >> $GITHUB_ENV - name: Install run: pip install artifacts/whl/${{ needs.pre-commit.outputs.dist-base }}/*.whl - name: Run tests diff --git a/README.md b/README.md index a88338cc..b1ec2667 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,15 @@ OpenSlide can read virtual slides in several formats: ## Installation -OpenSlide Python requires [OpenSlide]. For instructions on installing both -components so OpenSlide Python can find OpenSlide, see the package -[documentation][installing]. +OpenSlide Python requires [OpenSlide]. Install both components from PyPI +with: + +```console +pip install openslide-python openslide-bin +``` + +Or, see the [OpenSlide Python documentation][installing] for instructions on +installing so OpenSlide Python can find OpenSlide. [installing]: https://openslide.org/api/python/#installing diff --git a/doc/index.rst b/doc/index.rst index 03bc4ea6..03799050 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -49,17 +49,20 @@ Installing ========== OpenSlide Python requires OpenSlide_, which must be installed separately. +If you intend to use OpenSlide only with Python, the easiest way to get it +is to install the openslide-bin_ Python package with +``pip install openslide-bin``. -On Linux and macOS, the easiest way to get both components is to install_ -with a package manager that packages both, such as Anaconda_, DNF or Apt on -Linux systems, or MacPorts_ on macOS systems. You can also install +On Linux and macOS, you can also install_ both OpenSlide and OpenSlide +Python with a package manager that packages both, such as Anaconda_, DNF or +Apt on Linux systems, or MacPorts_ on macOS systems. Or, you can install OpenSlide Python with pip_ after installing OpenSlide with a package manager or from source_. Except for pip, do not mix OpenSlide and OpenSlide Python from different package managers (for example, OpenSlide from MacPorts and OpenSlide Python from Anaconda), since you'll get library conflicts. -On Windows, download the OpenSlide `Windows binaries`_ and extract them -to a known path. Then, import ``openslide`` inside a +On Windows, you can also download the OpenSlide `Windows binaries`_ and +extract them to a known path. Then, import ``openslide`` inside a ``with os.add_dll_directory()`` statement:: # The path can also be read from a config file, etc. @@ -73,6 +76,7 @@ to a known path. Then, import ``openslide`` inside a else: import openslide +.. _openslide-bin: https://pypi.org/project/openslide-bin/ .. _install: https://openslide.org/download/#distribution-packages .. _Anaconda: https://anaconda.org/ .. _MacPorts: https://www.macports.org/ diff --git a/openslide/lowlevel.py b/openslide/lowlevel.py index c70f0218..8ab11728 100644 --- a/openslide/lowlevel.py +++ b/openslide/lowlevel.py @@ -52,6 +52,13 @@ def _load_library(): + try: + import openslide_bin + + return openslide_bin.libopenslide1 + except (AttributeError, ModuleNotFoundError): + pass + def try_load(names): for name in names: try: @@ -66,7 +73,9 @@ def try_load(names): except FileNotFoundError: raise ModuleNotFoundError( "Couldn't locate OpenSlide DLL. " - "Did you call os.add_dll_directory()? " + "Try `pip install openslide-bin`, " + "or if you're using an OpenSlide binary package, " + "ensure you've called os.add_dll_directory(). " "https://openslide.org/api/python/#installing" ) elif platform.system() == 'Darwin': @@ -82,12 +91,19 @@ def try_load(names): if lib is None: raise ModuleNotFoundError( "Couldn't locate OpenSlide dylib. " - "Is OpenSlide installed correctly? " + "Try `pip install openslide-bin`. " "https://openslide.org/api/python/#installing" ) return cdll.LoadLibrary(lib) else: - return try_load(['libopenslide.so.1', 'libopenslide.so.0']) + try: + return try_load(['libopenslide.so.1', 'libopenslide.so.0']) + except OSError: + raise ModuleNotFoundError( + "Couldn't locate OpenSlide shared library. " + "Try `pip install openslide-bin`. " + "https://openslide.org/api/python/#installing" + ) _lib = _load_library()