From 265dd5dc2cbb9868cb9cd691ffefd68b167998b3 Mon Sep 17 00:00:00 2001 From: Jonas Kittner Date: Thu, 24 Oct 2024 11:57:27 +0200 Subject: [PATCH] make terracotta python 3.12 compatible (#346) * make terracotta python 3.12 compatible fix deprecation/removal of pkg_resources and rasterio is_tiled * implement is_tiled as a separate function * drop support for python 3.8 --- .github/workflows/test.yml | 4 ++-- docs/get-started.rst | 2 +- docs/index.rst | 2 +- environment.yml | 2 +- setup.py | 4 ++-- terracotta/cmaps/get_cmaps.py | 10 ++++------ terracotta/cog.py | 20 ++++++++++++++++++-- tests/cmaps/test_get_cmap.py | 13 ++++++++----- 8 files changed, 37 insertions(+), 20 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 04d2ab55..8900b7e0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: matrix: os: [ubuntu-latest] - python-version: ["3.8", "3.11"] + python-version: ["3.9", "3.12"] defaults: run: @@ -107,7 +107,7 @@ jobs: matrix: os: [macos-latest, windows-latest] - python-version: ["3.8", "3.11"] + python-version: ["3.9", "3.12"] defaults: run: diff --git a/docs/get-started.rst b/docs/get-started.rst index f6f7b70f..91f937f7 100644 --- a/docs/get-started.rst +++ b/docs/get-started.rst @@ -15,7 +15,7 @@ to create a new environment containing all dependencies and Terracotta: $ conda env create -f environment.yml -If you already have Python 3.8 (or above) installed, you can just run +If you already have Python 3.9 (or above) installed, you can just run .. code-block:: bash diff --git a/docs/index.rst b/docs/index.rst index 03f95362..7b0cade7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,7 +23,7 @@ Try out a live demo `here =3.8 + - python>=3.9 - numpy - rasterio>=1.3.0 - shapely diff --git a/setup.py b/setup.py index f415947c..cd3b5576 100644 --- a/setup.py +++ b/setup.py @@ -38,10 +38,10 @@ "Development Status :: 4 - Beta", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: MIT License", - "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", "Framework :: Flask", "Operating System :: Microsoft :: Windows :: Windows 10", "Operating System :: MacOS :: MacOS X", @@ -53,7 +53,7 @@ ], # module packages=find_packages(exclude=["docs", "tests"]), - python_requires=">=3.8", + python_requires=">=3.9", use_scm_version={"write_to": "terracotta/_version.py"}, # dependencies setup_requires=[ diff --git a/terracotta/cmaps/get_cmaps.py b/terracotta/cmaps/get_cmaps.py index 9c119d96..2e36c635 100644 --- a/terracotta/cmaps/get_cmaps.py +++ b/terracotta/cmaps/get_cmaps.py @@ -3,20 +3,18 @@ Define an interface to retrieve stored color maps. """ +import importlib.resources from typing import Dict import os -from pkg_resources import resource_filename, Requirement, DistributionNotFound - import numpy as np + SUFFIX = "_rgba.npy" EXTRA_CMAP_FOLDER = os.environ.get("TC_EXTRA_CMAP_FOLDER", "") try: - PACKAGE_DIR = resource_filename( - Requirement.parse("terracotta"), "terracotta/cmaps/data" - ) -except DistributionNotFound: + PACKAGE_DIR = str(importlib.resources.files("terracotta") / "cmaps/data") +except ModuleNotFoundError: # terracotta was not installed, fall back to file system PACKAGE_DIR = os.path.join(os.path.dirname(__file__), "data") diff --git a/terracotta/cog.py b/terracotta/cog.py index b0fe8ce5..68c2ce80 100644 --- a/terracotta/cog.py +++ b/terracotta/cog.py @@ -9,6 +9,7 @@ import rasterio from rasterio.env import GDALVersion +from rasterio._base import DatasetBase ValidationInfo = Tuple[List[str], List[str], Dict[str, Any]] @@ -22,6 +23,21 @@ def validate(src_path: str, strict: bool = True) -> bool: return not errors +def is_tiled(src: DatasetBase) -> bool: + """ + Check if a rasterio dataset is tiled. + + Implementation copied from + https://github.com/rasterio/rasterio/blob/74ccaf126d08fc6eca3eacd7cb20ac8bb155ee3b/rasterio/_base.pyx#L1006-L1017 + Since this was deprecated in rasterio + + :param src: rasterio dataset + """ + # It's rare but possible that a dataset's bands have different block structure. + # Therefore we check them all against the width of the dataset. + return src.block_shapes and all(src.width != w for _, w in src.block_shapes) + + def check_raster_file(src_path: str) -> ValidationInfo: # pragma: no cover """ Implementation from @@ -51,7 +67,7 @@ def check_raster_file(src_path: str) -> ValidationInfo: # pragma: no cover overviews = src.overviews(1) if src.width > 512 and src.height > 512: - if not src.is_tiled: + if not is_tiled(src): errors.append( "The file is greater than 512xH or 512xW, but is not tiled" ) @@ -166,7 +182,7 @@ def check_raster_file(src_path: str) -> ValidationInfo: # pragma: no cover for ix, dec in enumerate(overviews): with rasterio.open(src_path, OVERVIEW_LEVEL=ix) as ovr_dst: if ovr_dst.width > 512 and ovr_dst.height > 512: - if not ovr_dst.is_tiled: + if not is_tiled(ovr_dst): errors.append("Overview of index {} is not tiled".format(ix)) return errors, warnings, details diff --git a/tests/cmaps/test_get_cmap.py b/tests/cmaps/test_get_cmap.py index c81f15ca..4434cc87 100755 --- a/tests/cmaps/test_get_cmap.py +++ b/tests/cmaps/test_get_cmap.py @@ -24,19 +24,22 @@ def test_get_cmap(): def test_get_cmap_filesystem(monkeypatch): - import pkg_resources import importlib + import importlib.resources import terracotta.cmaps.get_cmaps def throw_error(*args, **kwargs): - raise pkg_resources.DistributionNotFound("monkeypatched") + raise ModuleNotFoundError("monkeypatched") with monkeypatch.context() as m: - m.setattr(pkg_resources.Requirement, "parse", throw_error) + m.setattr(importlib.resources, "files", throw_error) - with pytest.raises(pkg_resources.DistributionNotFound): - pkg_resources.Requirement.parse("terracotta") + with pytest.raises(ModuleNotFoundError) as exc_info: + importlib.resources.files("terracotta") + + (msg,) = exc_info.value.args + assert msg == "monkeypatched" importlib.reload(terracotta.cmaps.get_cmaps)