diff --git a/.github/workflows/pythonpackage.yaml b/.github/workflows/pythonpackage.yaml index 645f5e6..f787d3e 100644 --- a/.github/workflows/pythonpackage.yaml +++ b/.github/workflows/pythonpackage.yaml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] platform: [ubuntu-latest, macos-latest, windows-latest] exclude: - # No pybind11-rdp wheel available for Python 3.14 on windows diff --git a/README.md b/README.md index 909d15d..416fce6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Python: 3.9, 3.10, 3.11, 3.12, 3.13, 3.14](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)](https://www.python.org) +[![Python: 3.10, 3.11, 3.12, 3.13, 3.14](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)](https://www.python.org) ![PyPI - Wheel](https://img.shields.io/pypi/wheel/pyhgtmap) ![GitHub](https://img.shields.io/github/license/agrenott/pyhgtmap) ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/agrenott/pyhgtmap/pythonpackage.yaml) diff --git a/pyhgtmap/NASASRTMUtil.py b/pyhgtmap/NASASRTMUtil.py index 90a6eb4..9fe2e3a 100644 --- a/pyhgtmap/NASASRTMUtil.py +++ b/pyhgtmap/NASASRTMUtil.py @@ -22,7 +22,9 @@ def calc_bbox(area: str, corrx: float = 0.0, corry: float = 0.0) -> IntBBox: """Parse bounding box string and calculates the appropriate bounding box for the needed files""" min_lon, min_lat, max_lon, max_lat = [ float(value) - inc - for value, inc in zip(area.split(":"), [corrx, corry, corrx, corry]) + for value, inc in zip( + area.split(":"), [corrx, corry, corrx, corry], strict=True + ) ] if min_lon < 0: bbox_min_lon = int(min_lon) if min_lon % 1 == 0 else int(min_lon) - 1 diff --git a/pyhgtmap/hgt/__init__.py b/pyhgtmap/hgt/__init__.py index ae31e42..e1e1caa 100644 --- a/pyhgtmap/hgt/__init__.py +++ b/pyhgtmap/hgt/__init__.py @@ -1,8 +1,7 @@ from __future__ import annotations -from collections.abc import Iterable +from collections.abc import Callable, Iterable from math import isclose -from typing import Callable from pyhgtmap import BBox, Coordinates diff --git a/pyhgtmap/hgt/processor.py b/pyhgtmap/hgt/processor.py index 5e3f7e2..81903da 100644 --- a/pyhgtmap/hgt/processor.py +++ b/pyhgtmap/hgt/processor.py @@ -2,12 +2,13 @@ import logging import multiprocessing -from typing import TYPE_CHECKING, Callable, cast +from typing import TYPE_CHECKING, cast from pyhgtmap.hgt.file import HgtFile from pyhgtmap.output.factory import get_osm_output if TYPE_CHECKING: + from collections.abc import Callable from multiprocessing.context import ForkProcess # type: ignore[attr-defined] from multiprocessing.sharedctypes import Synchronized diff --git a/pyhgtmap/output/__init__.py b/pyhgtmap/output/__init__.py index b82abb8..50f0f37 100644 --- a/pyhgtmap/output/__init__.py +++ b/pyhgtmap/output/__init__.py @@ -1,12 +1,14 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Any, Callable, NamedTuple +from typing import TYPE_CHECKING, Any, NamedTuple import numpy from nptyping import NDArray, Structure if TYPE_CHECKING: + from collections.abc import Callable + from pyhgtmap.hgt.tile import TileContours logger = logging.getLogger(__name__) diff --git a/pyhgtmap/output/o5mUtil.py b/pyhgtmap/output/o5mUtil.py index 4367f21..2c12500 100644 --- a/pyhgtmap/output/o5mUtil.py +++ b/pyhgtmap/output/o5mUtil.py @@ -2,13 +2,15 @@ import ast import time -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import pyhgtmap.output from pyhgtmap import output from pyhgtmap.varint import int2str, join, sint2str, writableInt, writableString if TYPE_CHECKING: + from collections.abc import Callable + from pyhgtmap import BBox from pyhgtmap.hgt.tile import TileContours diff --git a/pyhgtmap/output/osmUtil.py b/pyhgtmap/output/osmUtil.py index 926a476..f503371 100644 --- a/pyhgtmap/output/osmUtil.py +++ b/pyhgtmap/output/osmUtil.py @@ -2,7 +2,7 @@ import datetime import time -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import numpy @@ -11,6 +11,7 @@ from pyhgtmap.varint import writableString if TYPE_CHECKING: + from collections.abc import Callable from io import IOBase from pyhgtmap.hgt.tile import TileContours diff --git a/pyhgtmap/output/pbfUtil.py b/pyhgtmap/output/pbfUtil.py index 8700217..33ebf68 100644 --- a/pyhgtmap/output/pbfUtil.py +++ b/pyhgtmap/output/pbfUtil.py @@ -3,7 +3,7 @@ import logging import os import time -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import npyosmium import npyosmium.io @@ -15,6 +15,8 @@ import pyhgtmap.output if TYPE_CHECKING: + from collections.abc import Callable + from pyhgtmap import BBox from pyhgtmap.hgt.tile import TileContours diff --git a/pyhgtmap/sources/__init__.py b/pyhgtmap/sources/__init__.py index 865f8e6..26a4f42 100644 --- a/pyhgtmap/sources/__init__.py +++ b/pyhgtmap/sources/__init__.py @@ -10,7 +10,8 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING -from class_registry import AutoRegister, ClassRegistry +from class_registry import ClassRegistry +from class_registry.base import AutoRegister if TYPE_CHECKING: import configargparse @@ -23,7 +24,9 @@ # This registry will return a new instance for each get -SOURCES_TYPES_REGISTRY = ClassRegistry(attr_name="NICKNAME", unique=True) +SOURCES_TYPES_REGISTRY: ClassRegistry = ClassRegistry["Source"]( + attr_name="NICKNAME", unique=True +) class ArgparsePassword(argparse.Action): @@ -36,7 +39,7 @@ def __call__(self, parser, namespace, values, option_string=None) -> None: setattr(namespace, self.dest, values) -class Source(ABC, metaclass=AutoRegister(SOURCES_TYPES_REGISTRY)): # type: ignore[metaclass] # Mypy does not understand dynamically-computed metaclasses +class Source(AutoRegister(SOURCES_TYPES_REGISTRY), ABC): # type: ignore[misc] # Mypy does not understand dynamically-computed base classes """HGT source base class""" # Source's 'nickname', used to identify it from the command line and diff --git a/pyhgtmap/sources/pool.py b/pyhgtmap/sources/pool.py index 3b621bd..b04fe11 100644 --- a/pyhgtmap/sources/pool.py +++ b/pyhgtmap/sources/pool.py @@ -6,12 +6,14 @@ from itertools import chain from typing import TYPE_CHECKING, cast -from class_registry import ClassRegistry, ClassRegistryInstanceCache +from class_registry.cache import ClassRegistryInstanceCache from pyhgtmap.sources import SOURCES_TYPES_REGISTRY, Source if TYPE_CHECKING: - from collections.abc import Generator, Iterator + from collections.abc import Generator, Iterable, Iterator + + from class_registry import ClassRegistry from pyhgtmap.configuration import Configuration @@ -26,7 +28,7 @@ class Pool: # Keep a reference on the source registry as the cached version # do not expose all methods... - _inner_registry: ClassRegistry = SOURCES_TYPES_REGISTRY + _inner_registry: ClassRegistry[Source] = SOURCES_TYPES_REGISTRY def __init__( self, cache_dir_root: str, config_dir: str, configuration: Configuration @@ -71,9 +73,9 @@ def __iter__(self) -> Iterator[Source]: yield from cast("Iterator[Source]", self._cached_registry) @classmethod - def registered_sources(cls) -> Generator[type[Source], None, None]: + def registered_sources(cls) -> Iterable[type[Source]]: """Returns a generator of registered sources types.""" - return cls._inner_registry.values() + return cls._inner_registry.classes() # Force import of all implementations to register them in the pool diff --git a/pyproject.toml b/pyproject.toml index 04a509e..ee0b4ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,41 +4,39 @@ requires = ["hatchling", "hatch-vcs"] [project] authors = [ - { name = "Adrian Dempwolff", email = "phyghtmap@aldw.de" }, - { name = "Aurélien Grenotton", email = "agrenott@gmail.com" }, + { name = "Adrian Dempwolff", email = "phyghtmap@aldw.de" }, + { name = "Aurélien Grenotton", email = "agrenott@gmail.com" }, ] classifiers = [ - "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Development Status :: 5 - Production/Stable", - "Environment :: Console", - "Operating System :: POSIX :: Linux", - "Operating System :: MacOS", - "Topic :: Scientific/Engineering :: GIS", + "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS", + "Topic :: Scientific/Engineering :: GIS", ] dependencies = [ - "bs4>=0.0.1", - "colorlog>=6.7.0", - "configargparse>=1.7", - "contourpy>=1.0.7", - "httpx>=0.27.0", - "lxml>=4.9.2", - "matplotlib>=3.4.3", - "matplotlib>=3.10.0 ; python_version >= '3.13'", - "numpy>=1.24.2", - # Use recent numpy version for python >= 3.13, to avoid building numpy from source in the CI - "numpy>=2.3.0 ; python_version >= '3.13'", - "nptyping>=2.5.0 ; python_version < '3.13'", - "np2typing>=2.6.0 ; python_version >= '3.13'", - "npyosmium>=4.2.0", - # Pin phx-class-registry version to 4.x to keep python 3.9 support - # https://class-registry.readthedocs.io/en/latest/upgrading_to_v5.html - "phx-class-registry>=4.0.6, <5", - "pybind11-rdp>=0.1.3", - "PyDrive2>=1.20.0", - "scipy>=1.8.0", - "shapely>=2.0.1", + "bs4>=0.0.1", + "colorlog>=6.7.0", + "configargparse>=1.7", + "contourpy>=1.0.7", + "httpx>=0.27.0", + "lxml>=4.9.2", + "matplotlib>=3.4.3", + "matplotlib>=3.10.0 ; python_version >= '3.13'", + "numpy>=1.24.2", + # Use recent numpy version for python >= 3.13, to avoid building numpy from source in the CI + "numpy>=2.3.0 ; python_version >= '3.13'", + "nptyping>=2.5.0 ; python_version < '3.13'", + "np2typing>=2.6.0 ; python_version >= '3.13'", + "npyosmium>=4.2.0", + "phx-class-registry>=5.0.0", + "pybind11-rdp>=0.1.3", + "PyDrive2>=1.20.0", + "scipy>=1.8.0", + "shapely>=2.0.1", ] description = "Creates OpenStreetMap suitable contour lines from NASA SRTM data" dynamic = ["version"] @@ -46,12 +44,12 @@ keywords = ["osm", "OpenStreetMap", "countour", "SRTM", "elevation"] license = "GPL-2.0-or-later" name = "pyhgtmap" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" [project.optional-dependencies] geotiff = [ - # Do NOT pin GDAL version to ease installing it via OS package manager (due to many dependencies) - "GDAL", + # Do NOT pin GDAL version to ease installing it via OS package manager (due to many dependencies) + "GDAL", ] [project.scripts] @@ -71,40 +69,40 @@ installer = "uv" # Use default env for all dev activities dependencies = [ - "pytest>=7.0.1 ; python_version < '3.13'", - "pytest>=8.4.0 ; python_version >= '3.13'", - "pytest-cov~=7.0.0", - "pytest_httpx>=0.30.0", - "pytest-mpl~=0.16.1", - "pytest-sugar>=0.9.7", - "pytest-xdist>=3.5.0", - "types-beautifulsoup4>=4", - "mypy>=1.0.1", - "mypy-extensions~=1.0.0", - "ruff>=0.6.4", - "uv", - "pip", - "setuptools", + "pytest>=7.0.1 ; python_version < '3.13'", + "pytest>=8.4.0 ; python_version >= '3.13'", + "pytest-cov~=7.0.0", + "pytest_httpx>=0.30.0", + "pytest-mpl~=0.16.1", + "pytest-sugar>=0.9.7", + "pytest-xdist>=3.5.0", + "types-beautifulsoup4>=4", + "mypy>=1.0.1", + "mypy-extensions~=1.0.0", + "ruff>=0.6.4", + "uv", + "pip", + "setuptools", ] [tool.hatch.envs.default.scripts] all = ["style", "typing", "test_cov"] fmt = [ - # Sort imports - https://docs.astral.sh/ruff/formatter/#sorting-imports - "ruff check --select I --fix {args:pyhgtmap tests tools}", - "ruff format {args:pyhgtmap tests tools}", - "style", + # Sort imports - https://docs.astral.sh/ruff/formatter/#sorting-imports + "ruff check --select I --fix {args:pyhgtmap tests tools}", + "ruff format {args:pyhgtmap tests tools}", + "style", ] style = [ - "ruff check {args:pyhgtmap tests tools}", - "ruff format --check --diff {args:pyhgtmap tests tools}", + "ruff check {args:pyhgtmap tests tools}", + "ruff format --check --diff {args:pyhgtmap tests tools}", ] test = "pytest {args:tests}" test_cov = ["pytest --mpl --cov --cov-report xml --cov-report term"] typing = "mypy {args}" [[tool.hatch.envs.test.matrix]] -python = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] +python = ["3.10", "3.11", "3.12", "3.13", "3.14"] [tool.hatch.envs.geotiff] # Env for optional geotiff dependencies diff --git a/ruff.toml b/ruff.toml index ebbcdf1..c13e4fc 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,94 +1,94 @@ lint.ignore = [ - # ruff/black auto-formats lines, trust it - "E501", - # As advised by ruff format - "COM812", - # As advised by ruff format - "ISC001", + # ruff/black auto-formats lines, trust it + "E501", + # As advised by ruff format + "COM812", + # As advised by ruff format + "ISC001", ] lint.select = [ - # pycodestyle - "E", - # Pyflakes - "F", - # pyupgrade - "UP", - # flake8-bugbear - "B", - # flake8-simplify - "SIM", - # isort - "I", - # pep8-naming - #"N", #TODO: enable one day, but clean-up required... - # flake8-2020 - "YTT", - # flake8-bandit - security rules - "S", - # flake8-bugbear - "B", - # flake8-builtins - "A", - # flake8-commas - "COM", - # flake8-comprehensions - "C4", - # flake8-future-annotations - "FA", - # flake8-implicit-str-concat - "ISC", - # flake8-import-conventions - # "ICN", #TODO - # flake8-logging-format - # "G", #TODO - # flake8-pie - "PIE", - # flake8-pyi - "PYI", - # flake8-pytest-style - "PT", - # flake8-quotes - "Q", - # flake8-raise - "RSE", - # flake8-return - # "RET", #TODO - # flake8-self - "SLF", - # flake8-slots - "SLOT", - # flake8-simplify - "SIM", - # flake8-tidy-imports - "TID", - # flake8-type-checking - "TCH", - # flake8-gettext - "INT", - # flake8-unused-arguments - # "ARG", #TODO - # flake8-use-pathlib - # "PTH", #TODO? (replace everything with pathlib) - # pandas-vet - "PD", - # pygrep-hooks - "PGH", - # Pylint - # "PL", # TODO? - # tryceratops - # "TRY", #TODO! - # flynt - "FLY", - # NumPy-specific rules - "NPY", - # Perflint - "PERF", - # Ruff - "RUF", + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + "UP", + # flake8-bugbear + "B", + # flake8-simplify + "SIM", + # isort + "I", + # pep8-naming + #"N", #TODO: enable one day, but clean-up required... + # flake8-2020 + "YTT", + # flake8-bandit - security rules + "S", + # flake8-bugbear + "B", + # flake8-builtins + "A", + # flake8-commas + "COM", + # flake8-comprehensions + "C4", + # flake8-future-annotations + "FA", + # flake8-implicit-str-concat + "ISC", + # flake8-import-conventions + # "ICN", #TODO + # flake8-logging-format + # "G", #TODO + # flake8-pie + "PIE", + # flake8-pyi + "PYI", + # flake8-pytest-style + "PT", + # flake8-quotes + "Q", + # flake8-raise + "RSE", + # flake8-return + # "RET", #TODO + # flake8-self + "SLF", + # flake8-slots + "SLOT", + # flake8-simplify + "SIM", + # flake8-tidy-imports + "TID", + # flake8-type-checking + "TCH", + # flake8-gettext + "INT", + # flake8-unused-arguments + # "ARG", #TODO + # flake8-use-pathlib + # "PTH", #TODO? (replace everything with pathlib) + # pandas-vet + "PD", + # pygrep-hooks + "PGH", + # Pylint + # "PL", # TODO? + # tryceratops + # "TRY", #TODO! + # flynt + "FLY", + # NumPy-specific rules + "NPY", + # Perflint + "PERF", + # Ruff + "RUF", ] -target-version = "py39" +target-version = "py310" # Ignore files not refactored yet exclude = [] diff --git a/tests/hgt/test_processor.py b/tests/hgt/test_processor.py index afdb1c8..a931629 100644 --- a/tests/hgt/test_processor.py +++ b/tests/hgt/test_processor.py @@ -9,7 +9,7 @@ import sys import tempfile from contextlib import contextmanager -from typing import TYPE_CHECKING, Callable, NamedTuple +from typing import TYPE_CHECKING, NamedTuple from unittest import mock from unittest.mock import MagicMock, Mock @@ -24,7 +24,7 @@ from tests import TEST_DATA_PATH if TYPE_CHECKING: - from collections.abc import Generator + from collections.abc import Callable, Generator class OSMDecoder(npyosmium.SimpleHandler): diff --git a/tests/hgt/test_tile.py b/tests/hgt/test_tile.py index 607d510..5330aa5 100644 --- a/tests/hgt/test_tile.py +++ b/tests/hgt/test_tile.py @@ -215,7 +215,7 @@ def _test_draw_contours( plt.tight_layout(pad=0) for elev in range(0, 500, 100): for contour in contour_data.trace(elev)[0]: - x, y = zip(*contour) + x, y = zip(*contour, strict=True) plt.plot(x, y, color="black") # plt.savefig(os.path.join(TEST_DATA_PATH, "toulon_out.png")) return fig diff --git a/tests/sources/test_pool.py b/tests/sources/test_pool.py index b7a9cd4..48d3463 100644 --- a/tests/sources/test_pool.py +++ b/tests/sources/test_pool.py @@ -1,5 +1,5 @@ import pytest -from class_registry.registry import RegistryKeyError +from class_registry import RegistryKeyError from pyhgtmap.configuration import Configuration from pyhgtmap.sources import Source diff --git a/tests/test_output.py b/tests/test_output.py index 2e4a686..37ae18f 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -5,7 +5,7 @@ import os import tempfile from contextlib import suppress -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any import npyosmium import npyosmium.io @@ -19,7 +19,7 @@ from pyhgtmap.output import make_elev_classifier, o5mUtil, osmUtil, pbfUtil if TYPE_CHECKING: - from collections.abc import Iterable + from collections.abc import Callable, Iterable class OSMDecoder(npyosmium.SimpleHandler):