From 7f2a309a63f80d61ffd7a9ba2452d89939131b23 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 2 Mar 2025 18:37:26 +0000 Subject: [PATCH 1/5] Update CI dependencies --- .pre-commit-config.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9cebb514..61b46d8e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,15 +19,15 @@ repos: - id: trailing-whitespace - id: mixed-line-ending - repo: https://github.com/sbrunner/hooks - rev: 1.2.1 + rev: 1.3.0 hooks: - id: copyright - id: poetry-check additional_dependencies: - - poetry==2.0.1 # pypi + - poetry==2.1.1 # pypi - id: poetry-lock additional_dependencies: - - poetry==2.0.1 # pypi + - poetry==2.1.1 # pypi - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: @@ -70,7 +70,7 @@ repos: args: - --line-length=110 - repo: https://github.com/PyCQA/prospector - rev: v1.14.1 + rev: v1.15.2 hooks: - id: prospector args: @@ -79,7 +79,7 @@ repos: - --output-format=pylint additional_dependencies: - prospector-profile-duplicated==1.10.4 # pypi - - prospector-profile-utils==1.17.0 # pypi + - prospector-profile-utils==1.21.9 # pypi - ruff==0.9.9 # pypi - repo: https://github.com/sbrunner/jsonschema-validator rev: 1.0.0 @@ -87,6 +87,6 @@ repos: - id: jsonschema-validator files: ^ci/config\.yaml$ - repo: https://github.com/renovatebot/pre-commit-hooks - rev: 39.157.0 + rev: 39.185.0 hooks: - id: renovate-config-validator From 2058a834e90ff9210a38ff0376e174ca109e7569 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:38:22 +0000 Subject: [PATCH 2/5] Update CI dependencies --- .pre-commit-config.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 61b46d8e..080158f9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -80,7 +80,6 @@ repos: additional_dependencies: - prospector-profile-duplicated==1.10.4 # pypi - prospector-profile-utils==1.21.9 # pypi - - ruff==0.9.9 # pypi - repo: https://github.com/sbrunner/jsonschema-validator rev: 1.0.0 hooks: From 94be4afa9660fb58e1d1104ffc62d7176192d55a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Brunner?= Date: Mon, 3 Mar 2025 16:31:33 +0100 Subject: [PATCH 3/5] Update the CI --- .github/publish.yaml | 7 ++ .github/renovate.json5 | 87 +++---------------- .github/workflows/main.yaml | 14 ++- .../workflows/pull-request-automation.yaml | 1 - .pre-commit-config.yaml | 34 ++++++-- .prospector.yaml | 10 ++- .whitesource | 14 --- Dockerfile | 8 +- ci/config.yaml | 5 -- ci/requirements.txt | 3 +- docker-compose.yaml | 3 - pyproject.toml | 63 +++++++------- 12 files changed, 95 insertions(+), 154 deletions(-) create mode 100644 .github/publish.yaml delete mode 100644 .whitesource delete mode 100644 ci/config.yaml diff --git a/.github/publish.yaml b/.github/publish.yaml new file mode 100644 index 00000000..b6710003 --- /dev/null +++ b/.github/publish.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/camptocamp/tag-publish/0.13.3/tag_publish/schema.json + +pypi: + packages: + - {} +dispatch: + - {} diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 3689ad2e..cdc1814b 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,58 +1,18 @@ { - extends: ['config:base'], - timezone: 'Europe/Zurich', - schedule: 'after 5pm on the first day of the month', - labels: ['dependencies'], - separateMajorMinor: true, - separateMinorPatch: true, - prHourlyLimit: 0, - prConcurrentLimit: 0, - lockFileMaintenance: { - enabled: true, - automerge: true, - schedule: 'after 5pm on the first day of the month', - }, - 'pre-commit': { enabled: true }, - regexManagers: [ - /** Do updates on pre-commit additional dependencies */ - { - fileMatch: ['^\\.pre\\-commit\\-config\\.yaml$'], - matchStrings: [" +- '?(?[^' @=]+)(@|==)(?[^' @=]+)'? # (?.+)"], - }, - /** Do update on the schema present in the ci/config.yaml */ - { - fileMatch: ['^ci/config\\.yaml$'], - matchStrings: [ - '.*https://raw\\.githubusercontent\\.com/(?[^\\s]+)/(?[0-9\\.]+)/.*', - ], - datasourceTemplate: 'github-tags', - }, - /** Python version in actions/setup-python action */ - { - fileMatch: ['^\\.github/workflows/.*\\.yaml$'], - matchStrings: [' python-version: [\'"](?[0-9\\.]+)[\'"]'], - datasourceTemplate: 'python-version', - depNameTemplate: 'python', - }, + extends: [ + 'github>camptocamp/gs-renovate-config-preset:base.json5#0.8.3', + 'github>camptocamp/gs-renovate-config-preset:group.json5#0.8.3', + 'github>camptocamp/gs-renovate-config-preset:ci.json5#0.8.3', + 'github>camptocamp/gs-renovate-config-preset:preset.json5#0.8.3', + 'github>camptocamp/gs-renovate-config-preset:pre-commit.json5#0.8.3', + 'github>camptocamp/gs-renovate-config-preset:python.json5#0.8.3', + 'github>camptocamp/gs-renovate-config-preset:security.json5#0.8.3', + 'github>camptocamp/gs-renovate-config-preset:docker.json5#0.8.3', + 'github>camptocamp/gs-renovate-config-preset:own.json5#0.8.3', + 'github>camptocamp/gs-renovate-config-preset:json-schema.json5#0.8.3', + 'github>camptocamp/gs-renovate-config-preset:shellcheck.json5#0.8.3', ], packageRules: [ - /** Auto merge the dev dependency update */ - { - matchDepTypes: ['devDependencies'], - automerge: true, - }, - /** Group and auto merge the patch updates */ - { - matchUpdateTypes: ['patch'], - groupName: 'all patch versions', - automerge: true, - }, - /** Group and auto merge the minor updates */ - { - matchUpdateTypes: ['minor'], - groupName: 'all minor versions', - automerge: true, - }, { matchDatasources: ['docker'], versioning: 'regex:^(?.*)-(?\\d+)\\.(?\\d+)\\.(?\\d+)?$', @@ -60,39 +20,16 @@ /** Ungroup Gdal */ groupName: 'gdal', }, - /** Group Poetry packages */ - { - matchPackagePrefixes: ['poetry-'], - groupName: 'Poetry', - automerge: true, - matchDepNames: ['poetry', 'pip'], - }, - /** Support the 4 parts of shellcheck-py version with a v prefix */ - { - versioning: 'regex:^v(?\\d+)\\.(?\\d+)\\.(?\\d+)\\.(?\\d+)$', - matchDepNames: ['shellcheck-py/shellcheck-py'], - }, /** Disable update of Python version in pyproject.toml */ { matchFiles: ['pyproject.toml'], enabled: false, matchDepNames: ['python'], }, - /** Group and auto merge the CI dependencies */ - { - matchFileNames: ['.github/**', '.pre-commit-config.yaml', 'ci/**'], - groupName: 'CI dependencies', - automerge: true, - }, /** Ungroup pycairo */ { matchDepNames: ['pycairo'], groupName: 'pycairo', }, - /** Ungroup Python dependencies */ - { - matchDepNames: ['python'], - groupName: 'Python', - }, ], } diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 15d82fb0..761ebbb3 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -40,8 +40,8 @@ jobs: path: ~/.cache/pre-commit key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} restore-keys: "pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}\npre-commit-" - - run: pre-commit run --all-files - - run: git diff --exit-code --patch > /tmp/pre-commit.patch || true + - run: pre-commit run --all-files --color=always + - run: git diff --exit-code --patch > /tmp/pre-commit.patch; git diff --color; git reset --hard || true if: failure() - uses: actions/upload-artifact@v4 with: @@ -60,12 +60,14 @@ jobs: if: always() - name: Publish - run: c2cciutils-publish + run: tag-publish if: | env.HAS_SECRETS == 'HAS_SECRETS' && github.event_name == 'push' && ( github.ref_type == 'tag' || github.ref_name == 'master' ) - - run: git diff --exit-code --patch > /tmp/dpkg-versions.patch || true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: git diff --exit-code --patch > /tmp/dpkg-versions.patch; git diff --color; git reset --hard || true if: failure() - uses: actions/upload-artifact@v4 with: @@ -73,3 +75,7 @@ jobs: path: /tmp/dpkg-versions.patch retention-days: 1 if: failure() +permissions: + contents: write + packages: write + id-token: write diff --git a/.github/workflows/pull-request-automation.yaml b/.github/workflows/pull-request-automation.yaml index 6ef14ff1..cf6935d6 100644 --- a/.github/workflows/pull-request-automation.yaml +++ b/.github/workflows/pull-request-automation.yaml @@ -5,7 +5,6 @@ on: types: - opened - reopened - jobs: auto-merge: name: Auto reviews pull requests from bots diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 080158f9..d59fb91d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,12 +22,10 @@ repos: rev: 1.3.0 hooks: - id: copyright - - id: poetry-check - additional_dependencies: - - poetry==2.1.1 # pypi - - id: poetry-lock + - id: poetry2-lock additional_dependencies: - poetry==2.1.1 # pypi + - id: canonicalize - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: @@ -56,9 +54,6 @@ repos: args: - --builtin-schema - github-workflows-require-timeout - - id: check-renovate - additional_dependencies: - - pyjson5==1.6.8 # pypi - repo: https://github.com/sirwart/ripsecrets rev: v0.1.8 hooks: @@ -74,18 +69,39 @@ repos: hooks: - id: prospector args: - - --tool=ruff + - --profile=utils:pre-commit - --die-on-tool-error - --output-format=pylint additional_dependencies: - prospector-profile-duplicated==1.10.4 # pypi - prospector-profile-utils==1.21.9 # pypi + exclude: |- + (?x)( + ^tests?/? + |/tests?(/|$) + |.*/tests(/|$) + |(^|/)test_[_a-zA-Z0-9]+.py$ + |(^|/)[_a-zA-Z0-9]+_tests?.py$ + |(^|/)tests?.py$ + ) + - id: prospector + args: + - --die-on-tool-error + - --output-format=pylint + - --profile=utils:tests + - --profile=utils:pre-commit + additional_dependencies: + - prospector-profile-utils==1.21.4 # pypi - repo: https://github.com/sbrunner/jsonschema-validator rev: 1.0.0 hooks: - id: jsonschema-validator - files: ^ci/config\.yaml$ + files: ^\.github/publish\.yaml$ - repo: https://github.com/renovatebot/pre-commit-hooks rev: 39.185.0 hooks: - id: renovate-config-validator + - repo: https://github.com/sbrunner/python-versions-hook + rev: 0.8.0 + hooks: + - id: python-versions diff --git a/.prospector.yaml b/.prospector.yaml index 67a51acf..997fce9f 100644 --- a/.prospector.yaml +++ b/.prospector.yaml @@ -3,7 +3,6 @@ inherits: - utils:base - utils:fix - utils:no-design-checks - - utils:unsafe ignore-patterns: - ^tilecloud/scripts/tc_.*.py @@ -15,7 +14,10 @@ pylint: disable: - cyclic-import # see: https://github.com/PyCQA/pylint/issues/850 +mypy: + options: + python-version: '3.10' + ruff: - disable: - - D102 # Missing docstring in public method - - D107 # Missing docstring in `__init__` + options: + target-version: py310 diff --git a/.whitesource b/.whitesource deleted file mode 100644 index 705c7540..00000000 --- a/.whitesource +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scanSettings": { - "baseBranches": [] - }, - "checkRunSettings": { - "vulnerableCheckRunConclusionLevel": "failure", - "displayMode": "diff", - "useMendCheckNames": true - }, - "issueSettings": { - "minSeverityLevel": "LOW", - "issueType": "DEPENDENCY" - } -} diff --git a/Dockerfile b/Dockerfile index ef44c97a..8e130cdd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -FROM ghcr.io/osgeo/gdal:ubuntu-small-3.10.2 as base-all -LABEL maintainer Camptocamp "info@camptocamp.com" +FROM ghcr.io/osgeo/gdal:ubuntu-small-3.10.2 AS base-all +LABEL org.opencontainers.image.authors="Camptocamp " SHELL ["/bin/bash", "-o", "pipefail", "-cux"] RUN --mount=type=cache,target=/var/lib/apt/lists \ @@ -13,7 +13,7 @@ ENV PATH=/venv/bin:$PATH # Used to convert the locked packages by poetry to pip requirements format # We don't directly use `poetry install` because it force to use a virtual environment. -FROM base-all as poetry +FROM base-all AS poetry # Install Poetry WORKDIR /poetry @@ -27,7 +27,7 @@ ENV POETRY_DYNAMIC_VERSIONING_BYPASS=0.0.0 RUN poetry export --extras=all --with=dev --output=/poetry/requirements-dev.txt # Base, the biggest thing is to install the Python packages -FROM base-all as base +FROM base-all AS base WORKDIR /app diff --git a/ci/config.yaml b/ci/config.yaml deleted file mode 100644 index 49db7f4d..00000000 --- a/ci/config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/camptocamp/c2cciutils/1.7.3/c2cciutils/schema.json - -publish: - docker: - images: [] diff --git a/ci/requirements.txt b/ci/requirements.txt index 1a0817e0..9470ea83 100644 --- a/ci/requirements.txt +++ b/ci/requirements.txt @@ -1,6 +1,7 @@ -c2cciutils[checks,publish]==1.7.3 +c2cciutils==1.7.3 pre-commit==4.1.0 poetry-dynamic-versioning==1.7.1 poetry-plugin-export==1.9.0 poetry-plugin-tweak-dependencies-version==1.5.2 poetry-plugin-drop-python-upper-constraint==0.1.0 +tag-publish==0.13.3 diff --git a/docker-compose.yaml b/docker-compose.yaml index 872862b2..01658eb4 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,6 +1,3 @@ ---- -version: '2.1' - services: redis: image: redis:7 diff --git a/pyproject.toml b/pyproject.toml index 8cc9eff1..2ed2b40b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,19 +1,9 @@ -[tool.black] +[tool.ruff] +target-version = "py310" line-length = 110 -target-version = ['py39'] -[tool.mypy] -python_version = "3.9" -warn_redundant_casts = true -warn_unused_ignores = true -ignore_missing_imports = true -strict_optional = true -strict = true - -[tool.isort] -known_first_party = "tilecloud" -profile = "black" -line_length = 110 +[tool.ruff.lint.pydocstyle] +convention = "numpy" [tool.poetry] name = "tilecloud" @@ -26,14 +16,17 @@ license = "BSD-2-Clause" keywords = ["gis", "tilecloud"] packages = [{ include = "tilecloud" }] classifiers = [ - "Development Status :: 6 - Mature", - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Topic :: Scientific/Engineering :: GIS", - "Typing :: Typed", + 'Development Status :: 6 - Mature', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Topic :: Scientific/Engineering :: GIS', + 'Typing :: Typed', ] include = ["tilecloud_chain/py.typed"] @@ -83,23 +76,15 @@ pycairo = "1.27.0" Shapely = "2.0.7" urllib3 = "2.3.0" # Lock the used version to avoid lock issue -[build-system] -requires = [ - "poetry-core>=1.3.0", - "poetry-dynamic-versioning", - "poetry-plugin-tweak-dependencies-version>=1.1.0", - "poetry-plugin-tweak-dependencies-version", - "poetry-plugin-drop-python-upper-constraint" -] -build-backend = "poetry.core.masonry.api" - [tool.poetry-dynamic-versioning] enable = true vcs = "git" pattern = "^(?P\\d+(\\.\\d+)*)" format-jinja = """ -{%- if env.get("VERSION_TYPE") == "version_branch" -%} -{{serialize_pep440(bump_version(base, 1 if env.get("IS_MASTER") == "TRUE" else 2), dev=distance)}} +{%- if env.get("VERSION_TYPE") == "default_branch" -%} +{{serialize_pep440(bump_version(base, 1), dev=distance)}} +{%- elif env.get("VERSION_TYPE") == "stabilization_branch" -%} +{{serialize_pep440(bump_version(base, 2), dev=distance)}} {%- elif distance == 0 -%} {{serialize_pep440(base)}} {%- else -%} @@ -109,3 +94,13 @@ format-jinja = """ [tool.poetry-plugin-tweak-dependencies-version] default = "present" + +[build-system] +requires = [ + "poetry-core>=1.3.0", + "poetry-dynamic-versioning", + "poetry-plugin-tweak-dependencies-version>=1.1.0", + "poetry-plugin-tweak-dependencies-version", + "poetry-plugin-drop-python-upper-constraint" +] +build-backend = "poetry.core.masonry.api" From 91b9ca02c3e19cea412a6024caa48e4cc65677bd Mon Sep 17 00:00:00 2001 From: "geo-ghci-int[bot]" <146321879+geo-ghci-int[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 15:52:18 +0000 Subject: [PATCH 4/5] Apply pre-commit fix From the artifact of the previous workflow run --- tilecloud/__init__.py | 44 +++++++++++++-------------- tilecloud/filter/benchmark.py | 12 ++++---- tilecloud/filter/consistenthash.py | 3 +- tilecloud/filter/contenttype.py | 3 +- tilecloud/filter/error.py | 5 ++- tilecloud/filter/image.py | 4 +-- tilecloud/filter/inboundingpyramid.py | 3 +- tilecloud/filter/logger.py | 4 +-- tilecloud/filter/rate.py | 3 +- tilecloud/grid/free.py | 11 +++---- tilecloud/grid/quad.py | 9 +++--- tilecloud/layout/i3d.py | 3 +- tilecloud/layout/osm.py | 4 +-- tilecloud/layout/template.py | 4 +-- tilecloud/layout/tilecache.py | 4 +-- tilecloud/layout/wms.py | 6 ++-- tilecloud/layout/wmts.py | 13 ++++---- tilecloud/layout/wrapped.py | 4 +-- tilecloud/lib/memcached.py | 3 +- tilecloud/store/azure_storage_blob.py | 10 +++--- tilecloud/store/boundingpyramid.py | 6 ++-- tilecloud/store/bsddb.py | 4 +-- tilecloud/store/debug.py | 4 +-- tilecloud/store/dict.py | 6 ++-- tilecloud/store/filesystem.py | 4 +-- tilecloud/store/filtered.py | 11 ++++--- tilecloud/store/findfirst.py | 4 +-- tilecloud/store/mapnik_.py | 8 ++--- tilecloud/store/mask.py | 10 +++--- tilecloud/store/mbtiles.py | 6 ++-- tilecloud/store/memcached.py | 4 +-- tilecloud/store/metatile.py | 4 +-- tilecloud/store/redis.py | 8 ++--- tilecloud/store/renderingtheworld.py | 9 +++--- tilecloud/store/s3.py | 12 ++++---- tilecloud/store/searchup.py | 3 +- tilecloud/store/sqs.py | 4 +-- tilecloud/store/url.py | 6 ++-- tilecloud/store/wmts.py | 11 ++++--- tilecloud/store/zip.py | 6 ++-- tilecloud/tests/test_redis.py | 4 +-- 41 files changed, 138 insertions(+), 148 deletions(-) diff --git a/tilecloud/__init__.py b/tilecloud/__init__.py index 1194718b..9517c7e7 100644 --- a/tilecloud/__init__.py +++ b/tilecloud/__init__.py @@ -26,7 +26,7 @@ def _cmp(a: Any, b: Any) -> int: # pylint: disable=invalid-name def consume( iterator: Iterator[Optional["Tile"]], - n: Optional[int] = None, # pylint: disable=invalid-name + n: int | None = None, # pylint: disable=invalid-name ) -> None: # pragma: no cover """ Advance the iterator n-steps ahead. @@ -45,7 +45,7 @@ def consume( class Bounds: """Uni-dimensional integer bounds.""" - def __init__(self, start: Optional[int] = None, stop: Optional[int] = None) -> None: + def __init__(self, start: int | None = None, stop: int | None = None) -> None: """ Construct a :class:`Bounds` object. @@ -149,7 +149,7 @@ class BoundingPyramid: """ def __init__( - self, bounds: Optional[dict[int, tuple[Bounds, Bounds]]] = None, tilegrid: Optional[Any] = None + self, bounds: dict[int, tuple[Bounds, Bounds]] | None = None, tilegrid: Any | None = None ) -> None: self.bounds = bounds or {} self.tilegrid = tilegrid @@ -197,8 +197,8 @@ def add_bounds(self, z: int, bounds: tuple[Bounds, Bounds]) -> None: # pylint: def fill( self, - zs: Optional[Iterable[int]] = None, # pylint: disable=invalid-name - extent: Optional[tuple[float, float, float, float]] = None, + zs: Iterable[int] | None = None, # pylint: disable=invalid-name + extent: tuple[float, float, float, float] | None = None, ) -> None: if zs is None: assert self.tilegrid is not None @@ -211,7 +211,7 @@ def fill( self.add(self.tilegrid.tilecoord(z, minx, miny)) self.add(self.tilegrid.tilecoord(z, maxx, maxy)) - def fill_down(self, bottom: int, start: Optional[Any] = None) -> None: + def fill_down(self, bottom: int, start: Any | None = None) -> None: if start is None: start = max(self.bounds) assert self.tilegrid is not None @@ -300,7 +300,7 @@ def from_string(cls, s: str) -> "BoundingPyramid": # pylint: disable=invalid-na return result @classmethod - def full(cls, zmin: Optional[int] = None, zmax: Optional[int] = None) -> "BoundingPyramid": + def full(cls, zmin: int | None = None, zmax: int | None = None) -> "BoundingPyramid": assert zmax is not None zs = (zmax,) if zmin is None else range(zmin, zmax + 1) # pylint: disable=invalid-name return cls({z: (Bounds(0, 1 << z), Bounds(0, 1 << z)) for z in zs}) @@ -312,10 +312,10 @@ class Tile: def __init__( self, tilecoord: "TileCoord", - content_encoding: Optional[Any] = None, - content_type: Optional[str] = None, - data: Optional[bytes] = None, - metadata: Optional[dict[str, str]] = None, + content_encoding: Any | None = None, + content_type: str | None = None, + data: bytes | None = None, + metadata: dict[str, str] | None = None, **kwargs: Any, ) -> None: """ @@ -334,7 +334,7 @@ def __init__( self.content_encoding = content_encoding self.content_type = content_type self.data = data - self.error: Optional[Union[Exception, str]] = None + self.error: Exception | str | None = None self.metadata = metadata if metadata is not None else {} for key, value in kwargs.items(): setattr(self, key, value) @@ -462,8 +462,8 @@ class TileGrid: def __init__( self, - max_extent: Optional[tuple[float, float, float, float]] = None, - tile_size: Optional[float] = None, + max_extent: tuple[float, float, float, float] | None = None, + tile_size: float | None = None, flip_y: bool = False, ) -> None: self.max_extent = max_extent or (0.0, 0.0, 1.0, 1.0) @@ -492,7 +492,7 @@ def fill_up( ) -> tuple[Bounds, Bounds]: raise NotImplementedError - def parent(self, tilecoord: TileCoord) -> Optional[TileCoord]: + def parent(self, tilecoord: TileCoord) -> TileCoord | None: """Get the parent of tilecoord.""" raise NotImplementedError @@ -512,7 +512,7 @@ def zs(self) -> Iterable[int]: # pylint: disable=invalid-name class TileLayout: """Maps tile coordinates to filenames and vice versa.""" - def filename(self, tilecoord: TileCoord, metadata: Optional[Any] = None) -> str: + def filename(self, tilecoord: TileCoord, metadata: Any | None = None) -> str: """ Return the filename for the given tile coordinate. @@ -538,8 +538,8 @@ class TileStore: def __init__( self, - bounding_pyramid: Optional[BoundingPyramid] = None, - content_type: Optional[str] = None, + bounding_pyramid: BoundingPyramid | None = None, + content_type: str | None = None, **kwargs: Any, ) -> None: """ @@ -592,7 +592,7 @@ def delete_one(self, tile: Tile) -> Tile: """ raise NotImplementedError - def get(self, tiles: Iterable[Optional[Tile]]) -> Iterator[Optional[Tile]]: + def get(self, tiles: Iterable[Tile | None]) -> Iterator[Tile | None]: """ Add data to each of ``tiles``. @@ -602,7 +602,7 @@ def get(self, tiles: Iterable[Optional[Tile]]) -> Iterator[Optional[Tile]]: """ return map(self.get_one, ifilter(None, tiles)) - def get_all(self) -> Iterator[Optional[Tile]]: + def get_all(self) -> Iterator[Tile | None]: """Generate all the tiles in the store with their data.""" return map(self.get_one, ifilter(None, self.list())) @@ -612,7 +612,7 @@ def get_bounding_pyramid(self) -> BoundingPyramid: BoundingPyramid.add, map(attrgetter("tilecoord"), ifilter(None, self.list())), BoundingPyramid() ) - def get_cheap_bounding_pyramid(self) -> Optional[BoundingPyramid]: + def get_cheap_bounding_pyramid(self) -> BoundingPyramid | None: """ Get a bounding pyramid that is cheap to calculate. @@ -621,7 +621,7 @@ def get_cheap_bounding_pyramid(self) -> Optional[BoundingPyramid]: """ return None - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: """ Add data to ``tile``, or return ``None`` if ``tile`` is not in the store. diff --git a/tilecloud/filter/benchmark.py b/tilecloud/filter/benchmark.py index 5a19506b..7dcc18b5 100644 --- a/tilecloud/filter/benchmark.py +++ b/tilecloud/filter/benchmark.py @@ -1,6 +1,6 @@ import math import time -from typing import Callable, Optional +from collections.abc import Callable from prometheus_client import Counter @@ -18,8 +18,8 @@ def __init__(self, format_pattern: str = "%f"): self.n = 0 # pylint: disable=invalid-name self.sum = 0.0 self.sum_of_squares = 0.0 - self.minimum: Optional[float] = None - self.maximum: Optional[float] = None + self.minimum: float | None = None + self.maximum: float | None = None def add(self, x: float) -> None: # pylint: disable=invalid-name self.n += 1 @@ -36,7 +36,7 @@ def __str__(self) -> str: return " ".join(result) @property - def mean(self) -> Optional[float]: + def mean(self) -> float | None: return self.sum / self.n if self.n else None @property @@ -55,10 +55,10 @@ def __init__(self, attr: str = "benchmark"): self.attr = attr self.statisticss: dict[str, Statistics] = {} - def sample(self, key: Optional[str] = None) -> Callable[[Tile], Tile]: + def sample(self, key: str | None = None) -> Callable[[Tile], Tile]: if key: if key in self.statisticss: - statistics: Optional[Statistics] = self.statisticss[key] + statistics: Statistics | None = self.statisticss[key] else: statistics = Statistics("%.3fs") self.statisticss[key] = statistics diff --git a/tilecloud/filter/consistenthash.py b/tilecloud/filter/consistenthash.py index 2eee276c..0d83e029 100644 --- a/tilecloud/filter/consistenthash.py +++ b/tilecloud/filter/consistenthash.py @@ -1,4 +1,3 @@ -from typing import Optional from tilecloud import Tile @@ -22,7 +21,7 @@ def __init__(self, n: int, i: int) -> None: # pylint: disable=invalid-name self.n = n # pylint: disable=invalid-name self.i = i - def __call__(self, tile: Tile) -> Optional[Tile]: + def __call__(self, tile: Tile) -> Tile | None: if hash(tile.tilecoord) % self.n == self.i: return tile return None diff --git a/tilecloud/filter/contenttype.py b/tilecloud/filter/contenttype.py index c27cd79e..5ea21b28 100644 --- a/tilecloud/filter/contenttype.py +++ b/tilecloud/filter/contenttype.py @@ -1,4 +1,3 @@ -from typing import Optional from tilecloud import Tile @@ -12,7 +11,7 @@ class ContentTypeAdder: that the content type will be determined based on the tile data. """ - def __init__(self, content_type: Optional[str] = None) -> None: + def __init__(self, content_type: str | None = None) -> None: self.content_type = content_type def __call__(self, tile: Tile) -> Tile: diff --git a/tilecloud/filter/error.py b/tilecloud/filter/error.py index 9969398f..1d061ccd 100644 --- a/tilecloud/filter/error.py +++ b/tilecloud/filter/error.py @@ -1,6 +1,5 @@ """Module includes filters for dealing with errors in tiles.""" -from typing import Optional from tilecloud import Tile from tilecloud.filter.logger import Logger @@ -21,7 +20,7 @@ def __call__(self, tile: Tile) -> Tile: class DropErrors: """Create a filter for dropping all tiles with errors.""" - def __call__(self, tile: Tile) -> Optional[Tile]: + def __call__(self, tile: Tile) -> Tile | None: if not tile or tile.error: return None return tile @@ -30,7 +29,7 @@ def __call__(self, tile: Tile) -> Optional[Tile]: class LogErrors(Logger): """Create a filter for logging all tiles with errors.""" - def __call__(self, tile: Optional[Tile]) -> Optional[Tile]: + def __call__(self, tile: Tile | None) -> Tile | None: if tile and tile.error: Logger.__call__(self, tile) return tile diff --git a/tilecloud/filter/image.py b/tilecloud/filter/image.py index 7d366a89..bad85e38 100644 --- a/tilecloud/filter/image.py +++ b/tilecloud/filter/image.py @@ -5,7 +5,7 @@ """ from io import BytesIO -from typing import Any, Optional +from typing import Any import PIL.Image import PIL.ImageFilter @@ -55,7 +55,7 @@ class MergeFilter: other tile. Default is ``None``. """ - def __init__(self, tilestores: list[TileStore], content_type: Optional[str] = None, **kwargs: Any): + def __init__(self, tilestores: list[TileStore], content_type: str | None = None, **kwargs: Any): self.tilestores = list(tilestores) self.content_type = content_type self.kwargs = kwargs diff --git a/tilecloud/filter/inboundingpyramid.py b/tilecloud/filter/inboundingpyramid.py index 4ef1a749..4ff5415d 100644 --- a/tilecloud/filter/inboundingpyramid.py +++ b/tilecloud/filter/inboundingpyramid.py @@ -1,4 +1,3 @@ -from typing import Optional from tilecloud import BoundingPyramid, Tile @@ -16,7 +15,7 @@ class InBoundingPyramid: def __init__(self, bounding_pyramid: BoundingPyramid): self.bounding_pyramid = bounding_pyramid - def __call__(self, tile: Tile) -> Optional[Tile]: + def __call__(self, tile: Tile) -> Tile | None: if tile is None or tile.tilecoord not in self.bounding_pyramid: return None return tile diff --git a/tilecloud/filter/logger.py b/tilecloud/filter/logger.py index 32929298..01fa428b 100644 --- a/tilecloud/filter/logger.py +++ b/tilecloud/filter/logger.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Optional +from typing import Any from tilecloud import Tile @@ -14,7 +14,7 @@ def __init__(self, logger: logging.Logger, level: int, msgformat: str, *args: An self.args = args self.kwargs = kwargs - def __call__(self, tile: Optional[Tile]) -> Optional[Tile]: + def __call__(self, tile: Tile | None) -> Tile | None: if tile is not None: variables = {} variables.update(tile.__dict2__) diff --git a/tilecloud/filter/rate.py b/tilecloud/filter/rate.py index 6feda9aa..ec965a0f 100644 --- a/tilecloud/filter/rate.py +++ b/tilecloud/filter/rate.py @@ -1,5 +1,4 @@ import time -from typing import Optional from tilecloud import Tile @@ -10,7 +9,7 @@ class RateLimit: def __init__(self, rate: float): self.rate = rate self.count = 0 - self.start: Optional[float] = None + self.start: float | None = None def __call__(self, tile: Tile) -> Tile: if tile: diff --git a/tilecloud/grid/free.py b/tilecloud/grid/free.py index 5c55c5d2..b1f163d9 100644 --- a/tilecloud/grid/free.py +++ b/tilecloud/grid/free.py @@ -1,6 +1,5 @@ from collections.abc import Iterator, Sequence from math import floor -from typing import Optional, Union from tilecloud import Bounds, NotSupportedOperation, TileCoord, TileGrid @@ -14,9 +13,9 @@ class FreeTileGrid(TileGrid): def __init__( self, - resolutions: Sequence[Union[int, float]], - max_extent: Optional[Union[tuple[int, int, int, int], tuple[float, float, float, float]]] = None, - tile_size: Optional[float] = None, + resolutions: Sequence[int | float], + max_extent: tuple[int, int, int, int] | tuple[float, float, float, float] | None = None, + tile_size: float | None = None, scale: int = 1, flip_y: bool = False, ) -> None: @@ -25,7 +24,7 @@ def __init__( assert all(isinstance(r, (int, float)) for r in resolutions) self.resolutions = resolutions self.scale = float(scale) - self.parent_zs: list[Optional[int]] = [] + self.parent_zs: list[int | None] = [] self.child_zs: list[list[int]] = [] for i, resolution in enumerate(self.resolutions): for parent in range(i - 1, -1, -1): @@ -74,7 +73,7 @@ def extent(self, tilecoord: TileCoord, border: float = 0) -> tuple[float, float, ) return minx, miny, maxx, maxy - def parent(self, tilecoord: TileCoord) -> Optional[TileCoord]: + def parent(self, tilecoord: TileCoord) -> TileCoord | None: parent_z = self.parent_zs[tilecoord.z] if parent_z is None: return None diff --git a/tilecloud/grid/quad.py b/tilecloud/grid/quad.py index 807ff621..950381e5 100644 --- a/tilecloud/grid/quad.py +++ b/tilecloud/grid/quad.py @@ -1,6 +1,5 @@ from collections.abc import Iterable, Iterator from itertools import count -from typing import Optional from tilecloud import Bounds, TileCoord, TileGrid @@ -14,9 +13,9 @@ class QuadTileGrid(TileGrid): def __init__( self, - max_extent: Optional[tuple[float, float, float, float]] = None, - tile_size: Optional[int] = None, - max_zoom: Optional[int] = None, + max_extent: tuple[float, float, float, float] | None = None, + tile_size: int | None = None, + max_zoom: int | None = None, flip_y: bool = False, ) -> None: TileGrid.__init__(self, max_extent=max_extent, tile_size=tile_size, flip_y=flip_y) @@ -68,7 +67,7 @@ def fill_up(self, z: int, bounds: tuple[Bounds, Bounds]) -> tuple[Bounds, Bounds Bounds(ybounds.start // 2, max(ybounds.stop // 2, 1)), ) - def parent(self, tilecoord: TileCoord) -> Optional[TileCoord]: + def parent(self, tilecoord: TileCoord) -> TileCoord | None: if tilecoord.z == 0: return None return TileCoord(tilecoord.z - 1, int(tilecoord.x // 2), int(tilecoord.y // 2)) diff --git a/tilecloud/layout/i3d.py b/tilecloud/layout/i3d.py index 32fe0453..b0b94fe8 100644 --- a/tilecloud/layout/i3d.py +++ b/tilecloud/layout/i3d.py @@ -1,6 +1,5 @@ import re from re import Match -from typing import Optional from tilecloud import TileCoord from tilecloud.layout.re_ import RETileLayout @@ -15,7 +14,7 @@ class I3DTileLayout(RETileLayout): def __init__(self) -> None: RETileLayout.__init__(self, self.PATTERN, self.RE) - def filename(self, tilecoord: TileCoord, metadata: Optional[dict[str, str]] = None) -> str: + def filename(self, tilecoord: TileCoord, metadata: dict[str, str] | None = None) -> str: return "/".join(re.findall(r"[0-3]{1,2}", I3DTileLayout.quadcode_from_tilecoord(tilecoord))) @staticmethod diff --git a/tilecloud/layout/osm.py b/tilecloud/layout/osm.py index 00968f38..fb8f078f 100644 --- a/tilecloud/layout/osm.py +++ b/tilecloud/layout/osm.py @@ -1,6 +1,6 @@ import re from re import Match -from typing import Any, Optional +from typing import Any from tilecloud import TileCoord from tilecloud.layout.re_ import RETileLayout @@ -15,7 +15,7 @@ class OSMTileLayout(RETileLayout): def __init__(self) -> None: RETileLayout.__init__(self, self.PATTERN, self.RE) - def filename(self, tilecoord: TileCoord, metadata: Optional[Any] = None) -> str: + def filename(self, tilecoord: TileCoord, metadata: Any | None = None) -> str: return f"{tilecoord.z}/{tilecoord.x}/{tilecoord.y}" @staticmethod diff --git a/tilecloud/layout/template.py b/tilecloud/layout/template.py index b874d4e4..4027dc7c 100644 --- a/tilecloud/layout/template.py +++ b/tilecloud/layout/template.py @@ -1,6 +1,6 @@ import re from re import Match -from typing import Any, Optional +from typing import Any from tilecloud import TileCoord from tilecloud.layout.re_ import RETileLayout @@ -29,7 +29,7 @@ def __init__(self, template: str) -> None: filename_re = re.compile("".join(filename_patterns)) RETileLayout.__init__(self, pattern, filename_re) - def filename(self, tilecoord: TileCoord, metadata: Optional[Any] = None) -> str: + def filename(self, tilecoord: TileCoord, metadata: Any | None = None) -> str: return self.template % {"z": tilecoord.z, "x": tilecoord.x, "y": tilecoord.y} @staticmethod diff --git a/tilecloud/layout/tilecache.py b/tilecloud/layout/tilecache.py index b59af17f..57b03b9d 100644 --- a/tilecloud/layout/tilecache.py +++ b/tilecloud/layout/tilecache.py @@ -1,6 +1,6 @@ import re from re import Match -from typing import Any, Optional +from typing import Any from tilecloud import TileCoord from tilecloud.layout.re_ import RETileLayout @@ -15,7 +15,7 @@ class TileCacheDiskLayout(RETileLayout): def __init__(self) -> None: RETileLayout.__init__(self, self.PATTERN, self.RE) - def filename(self, tilecoord: TileCoord, metadata: Optional[Any] = None) -> str: + def filename(self, tilecoord: TileCoord, metadata: Any | None = None) -> str: zoom_string = f"{tilecoord.z:02d}" x_string = f"{tilecoord.x:09f}" y_string = f"{tilecoord.y:09f}" diff --git a/tilecloud/layout/wms.py b/tilecloud/layout/wms.py index 3f33772a..ee54d022 100644 --- a/tilecloud/layout/wms.py +++ b/tilecloud/layout/wms.py @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Any from urllib.parse import urlencode from tilecloud import NotSupportedOperation, TileCoord, TileGrid, TileLayout @@ -15,7 +15,7 @@ def __init__( format_pattern: str, tilegrid: TileGrid, border: int = 0, - params: Optional[dict[str, str]] = None, + params: dict[str, str] | None = None, ) -> None: if params is None: params = {} @@ -36,7 +36,7 @@ def __init__( if params.get("FILTER", None) is not None: self.params["FILTER"] = params["FILTER"].format(**params) - def filename(self, tilecoord: TileCoord, metadata: Optional[Any] = None) -> str: + def filename(self, tilecoord: TileCoord, metadata: Any | None = None) -> str: metadata = {} if metadata is None else metadata bbox = self.tilegrid.extent(tilecoord, self.border) size = tilecoord.n * self.tilegrid.tile_size + 2 * self.border diff --git a/tilecloud/layout/wmts.py b/tilecloud/layout/wmts.py index 74922d34..cb4a5346 100644 --- a/tilecloud/layout/wmts.py +++ b/tilecloud/layout/wmts.py @@ -1,5 +1,4 @@ -from collections.abc import Iterable -from typing import Callable, Optional +from collections.abc import Callable, Iterable from tilecloud import NotSupportedOperation, TileCoord, TileLayout @@ -10,10 +9,10 @@ class WMTSTileLayout(TileLayout): def __init__( self, url: str = "", - layer: Optional[str] = None, - style: Optional[str] = None, - format_pattern: Optional[str] = None, - tile_matrix_set: Optional[str] = None, + layer: str | None = None, + style: str | None = None, + format_pattern: str | None = None, + tile_matrix_set: str | None = None, tile_matrix: Callable[[int], str] = str, dimensions_name: Iterable[str] = (), request_encoding: str = "KVP", @@ -37,7 +36,7 @@ def __init__( elif self.url and self.url[-1] != "/": self.url += "/" - def filename(self, tilecoord: TileCoord, metadata: Optional[dict[str, str]] = None) -> str: + def filename(self, tilecoord: TileCoord, metadata: dict[str, str] | None = None) -> str: metadata = {} if metadata is None else metadata # Careful the order is important for the REST request encoding query: list[tuple[str, str]] = [] diff --git a/tilecloud/layout/wrapped.py b/tilecloud/layout/wrapped.py index 9f940ca3..052352f5 100644 --- a/tilecloud/layout/wrapped.py +++ b/tilecloud/layout/wrapped.py @@ -1,5 +1,5 @@ import re -from typing import Any, Optional +from typing import Any from tilecloud import TileCoord, TileLayout from tilecloud.layout.re_ import RETileLayout @@ -18,7 +18,7 @@ def __init__(self, tilelayout: RETileLayout, prefix: str = "", suffix: str = "") filename_pattern = "".join((prefix_re, r"(", self.tilelayout.pattern, r")", suffix_re, r"\Z")) self.filename_re = re.compile(filename_pattern) - def filename(self, tilecoord: TileCoord, metadata: Optional[Any] = None) -> str: + def filename(self, tilecoord: TileCoord, metadata: Any | None = None) -> str: return "".join((self.prefix, self.tilelayout.filename(tilecoord, metadata), self.suffix)) def tilecoord(self, filename: str) -> TileCoord: diff --git a/tilecloud/lib/memcached.py b/tilecloud/lib/memcached.py index 9cbb4db4..e6e25734 100644 --- a/tilecloud/lib/memcached.py +++ b/tilecloud/lib/memcached.py @@ -1,6 +1,5 @@ import re import socket -from typing import Optional class MemcachedError(RuntimeError): @@ -25,7 +24,7 @@ def delete(self, key: str) -> bool: return False raise MemcachedError(line) - def get(self, key: str) -> tuple[Optional[int], Optional[bytes], Optional[int]]: + def get(self, key: str) -> tuple[int | None, bytes | None, int | None]: self.writeline(f"get {key}".encode()) line = self.readline() if line == b"END": diff --git a/tilecloud/store/azure_storage_blob.py b/tilecloud/store/azure_storage_blob.py index 0eb3d11c..b9ee7d99 100644 --- a/tilecloud/store/azure_storage_blob.py +++ b/tilecloud/store/azure_storage_blob.py @@ -1,7 +1,7 @@ import logging import os from collections.abc import Iterator -from typing import Any, Optional +from typing import Any from azure.identity import DefaultAzureCredential from azure.storage.blob import BlobServiceClient, ContainerClient, ContentSettings @@ -17,10 +17,10 @@ class AzureStorageBlobTileStore(TileStore): def __init__( self, tilelayout: TileLayout, - container: Optional[str] = None, + container: str | None = None, dry_run: bool = False, - cache_control: Optional[str] = None, - container_client: Optional[ContainerClient] = None, + cache_control: str | None = None, + container_client: ContainerClient | None = None, **kwargs: Any, ): if container_client is None: @@ -68,7 +68,7 @@ def delete_one(self, tile: Tile) -> Tile: tile.error = exc return tile - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: key_name = self.tilelayout.filename(tile.tilecoord, tile.metadata) try: blob = self.container_client.get_blob_client(blob=key_name) diff --git a/tilecloud/store/boundingpyramid.py b/tilecloud/store/boundingpyramid.py index 341b597c..4966effe 100644 --- a/tilecloud/store/boundingpyramid.py +++ b/tilecloud/store/boundingpyramid.py @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Any from tilecloud import BoundingPyramid, NotSupportedOperation, Tile, TileStore @@ -6,11 +6,11 @@ class BoundingPyramidTileStore(TileStore): """All tiles in a bounding box.""" - def __init__(self, bounding_pyramid: Optional[BoundingPyramid] = None, **kwargs: Any): + def __init__(self, bounding_pyramid: BoundingPyramid | None = None, **kwargs: Any): TileStore.__init__(self, **kwargs) self.bounding_pyramid = bounding_pyramid or BoundingPyramid() - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: if tile and tile.tilecoord in self.get_cheap_bounding_pyramid(): return tile return None diff --git a/tilecloud/store/bsddb.py b/tilecloud/store/bsddb.py index c30d49ef..fd321607 100644 --- a/tilecloud/store/bsddb.py +++ b/tilecloud/store/bsddb.py @@ -1,5 +1,5 @@ from collections.abc import Iterator -from typing import Any, Optional +from typing import Any import bsddb3 as bsddb # pylint: disable=import-error @@ -30,7 +30,7 @@ def get_all(self) -> Iterator[Tile]: tile = Tile(TileCoord.from_string(key), content_type=self.content_type, data=data) yield tile - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: try: tile.content_type = self.content_type tile.data = self.db[str(tile.tilecoord).encode("utf-8")] diff --git a/tilecloud/store/debug.py b/tilecloud/store/debug.py index a8113e79..75f4f555 100644 --- a/tilecloud/store/debug.py +++ b/tilecloud/store/debug.py @@ -1,5 +1,5 @@ from io import BytesIO -from typing import Any, Optional +from typing import Any import PIL.Image import PIL.ImageDraw @@ -16,7 +16,7 @@ def __init__(self, color: tuple[int, int, int] = (0, 0, 0), **kwargs: Any): TileStore.__init__(self, content_type="image/png", **kwargs) self.color = color - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: image = PIL.Image.new("RGBA", (256, 256), (0, 0, 0, 0)) draw = PIL.ImageDraw.Draw(image) draw.line([(0, 255), (0, 0), (255, 0)], fill=self.color) diff --git a/tilecloud/store/dict.py b/tilecloud/store/dict.py index 0aa34411..d073f51a 100644 --- a/tilecloud/store/dict.py +++ b/tilecloud/store/dict.py @@ -1,5 +1,5 @@ from collections.abc import Iterator -from typing import Any, Optional +from typing import Any from tilecloud import Tile, TileStore @@ -7,7 +7,7 @@ class DictTileStore(TileStore): """Tiles stored in a dictionary.""" - def __init__(self, tiles: Optional[Any] = None, **kwargs: Any) -> None: + def __init__(self, tiles: Any | None = None, **kwargs: Any) -> None: self.tiles = tiles or {} TileStore.__init__(self, **kwargs) @@ -21,7 +21,7 @@ def delete_one(self, tile: Tile) -> Tile: del self.tiles[tile.tilecoord] return tile - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: if tile and tile.tilecoord in self.tiles: tile.__dict__.update(self.tiles[tile.tilecoord]) return tile diff --git a/tilecloud/store/filesystem.py b/tilecloud/store/filesystem.py index ed7ad8b4..53bbda5f 100644 --- a/tilecloud/store/filesystem.py +++ b/tilecloud/store/filesystem.py @@ -3,7 +3,7 @@ import os import os.path from collections.abc import Iterator -from typing import Any, Optional +from typing import Any from tilecloud import Tile, TileLayout, TileStore @@ -35,7 +35,7 @@ def get_all(self) -> Iterator[Tile]: tile.data = file.read() yield tile - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: try: filename = self.tilelayout.filename(tile.tilecoord, tile.metadata) except Exception as exception: # pylint: disable=broad-except diff --git a/tilecloud/store/filtered.py b/tilecloud/store/filtered.py index b1972c22..56a6675c 100644 --- a/tilecloud/store/filtered.py +++ b/tilecloud/store/filtered.py @@ -1,5 +1,6 @@ +from collections.abc import Callable from functools import reduce -from typing import Any, Callable, Optional +from typing import Any from tilecloud import NotSupportedOperation, Tile, TileStore @@ -7,15 +8,15 @@ class FilteredTileStore(TileStore): """A tile store that filter the tiles.""" - def __init__(self, tilestore: TileStore, filters: list[Callable[[Optional[Tile]], Tile]], **kwargs: Any): + def __init__(self, tilestore: TileStore, filters: list[Callable[[Tile | None], Tile]], **kwargs: Any): TileStore.__init__(self, **kwargs) self.tilestore = tilestore self.filters = filters - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: def reduce_function( - tile: Optional[Tile], filter_pattern: Callable[[Optional[Tile]], Tile] - ) -> Optional[Tile]: + tile: Tile | None, filter_pattern: Callable[[Tile | None], Tile] + ) -> Tile | None: return filter_pattern(tile) return reduce(reduce_function, self.filters, self.tilestore.get_one(tile)) diff --git a/tilecloud/store/findfirst.py b/tilecloud/store/findfirst.py index e6106a80..b02b0bfb 100644 --- a/tilecloud/store/findfirst.py +++ b/tilecloud/store/findfirst.py @@ -1,5 +1,5 @@ from collections.abc import Iterator -from typing import Any, Optional +from typing import Any from tilecloud import NotSupportedOperation, Tile, TileStore @@ -11,7 +11,7 @@ def __init__(self, tilestores: Iterator[TileStore], **kwargs: Any): TileStore.__init__(self, **kwargs) self.tilestores = tilestores - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: return next(filter(None, (store.get_one(tile) for store in self.tilestores)), None) def put_one(self, tile: Tile) -> Tile: diff --git a/tilecloud/store/mapnik_.py b/tilecloud/store/mapnik_.py index ad2a60c4..a7b5df39 100644 --- a/tilecloud/store/mapnik_.py +++ b/tilecloud/store/mapnik_.py @@ -1,5 +1,5 @@ from json import dumps -from typing import Any, Optional +from typing import Any from tilecloud import NotSupportedOperation, Tile, TileGrid, TileStore @@ -24,9 +24,9 @@ def __init__( image_buffer: int = 0, output_format: str = "png256", resolution: int = 2, - layers_fields: Optional[dict[str, list[str]]] = None, + layers_fields: dict[str, list[str]] | None = None, drop_empty_utfgrid: bool = False, - proj4_literal: Optional[str] = None, + proj4_literal: str | None = None, **kwargs: Any, ): """ @@ -60,7 +60,7 @@ def __init__( if proj4_literal is not None: self.mapnik.srs = proj4_literal - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: bbox = self.tilegrid.extent(tile.tilecoord, self.buffer) bbox2d = mapnik.Box2d(bbox[0], bbox[1], bbox[2], bbox[3]) diff --git a/tilecloud/store/mask.py b/tilecloud/store/mask.py index 51029573..b1439f89 100644 --- a/tilecloud/store/mask.py +++ b/tilecloud/store/mask.py @@ -1,5 +1,5 @@ from collections.abc import Iterator -from typing import Any, Optional, Union +from typing import Any import PIL.Image import PIL.ImageFile @@ -17,9 +17,9 @@ class MaskTileStore(TileStore): """A black and white image representing present and absent tiles.""" - image: Union[PIL.Image.Image, PIL.ImageFile.ImageFile] + image: PIL.Image.Image | PIL.ImageFile.ImageFile - def __init__(self, z: int, bounds: tuple[Bounds, Bounds], file: Optional[str] = None, **kwargs: Any): + def __init__(self, z: int, bounds: tuple[Bounds, Bounds], file: str | None = None, **kwargs: Any): TileStore.__init__(self, **kwargs) self.zoom = z self.xbounds, self.ybounds = bounds @@ -66,8 +66,8 @@ def put_one(self, tile: Tile) -> Tile: self.pixels[x, y] = 1 # type: ignore[index] return tile - def save(self, file: str, format_pattern: Optional[str], **kwargs: Any) -> None: + def save(self, file: str, format_pattern: str | None, **kwargs: Any) -> None: self.image.save(file, format_pattern, **kwargs) - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: raise NotSupportedOperation() diff --git a/tilecloud/store/mbtiles.py b/tilecloud/store/mbtiles.py index adc1fb76..88ed70a4 100644 --- a/tilecloud/store/mbtiles.py +++ b/tilecloud/store/mbtiles.py @@ -4,7 +4,7 @@ import sqlite3 from collections.abc import Iterator from sqlite3 import Connection -from typing import Any, Optional +from typing import Any from tilecloud import BoundingPyramid, Bounds, Tile, TileCoord, TileStore from tilecloud.lib.sqlite3_ import SQLiteDict, _query @@ -46,7 +46,7 @@ def __init__(self, tilecoord_in_topleft: bool, *args: Any, **kwargs: Any) -> Non self.tilecoord_in_topleft = tilecoord_in_topleft SQLiteDict.__init__(self, *args, **kwargs) - def _packitem(self, key: TileCoord, value: Optional[bytes]) -> tuple[int, int, int, Optional[memoryview]]: + def _packitem(self, key: TileCoord, value: bytes | None) -> tuple[int, int, int, memoryview | None]: y = key.y if self.tilecoord_in_topleft else (1 << key.z) - key.y - 1 # pylint: disable=invalid-name return (key.z, key.x, y, sqlite3.Binary(value) if value is not None else None) @@ -110,7 +110,7 @@ def get_cheap_bounding_pyramid(self) -> BoundingPyramid: bounds[z] = (Bounds(xstart, xstop), Bounds(ystart, ystop)) return BoundingPyramid(bounds) - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: try: tile.data = self.tiles[tile.tilecoord] except KeyError: diff --git a/tilecloud/store/memcached.py b/tilecloud/store/memcached.py index 945d59e5..03baf479 100644 --- a/tilecloud/store/memcached.py +++ b/tilecloud/store/memcached.py @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Any import tilecloud.lib.memcached from tilecloud import Tile, TileLayout, TileStore @@ -25,7 +25,7 @@ def __contains__(self, tile: Tile) -> bool: flags, _, _ = self.client.get(self.tilelayout.filename(tile.tilecoord, tile.metadata)) return flags is not None - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: flags, value, cas = self.client.get(self.tilelayout.filename(tile.tilecoord, tile.metadata)) tile.memcached_flags = flags # type: ignore tile.data = value diff --git a/tilecloud/store/metatile.py b/tilecloud/store/metatile.py index 654a1fb5..cd40806f 100644 --- a/tilecloud/store/metatile.py +++ b/tilecloud/store/metatile.py @@ -1,6 +1,6 @@ from collections.abc import Iterable, Iterator from io import BytesIO -from typing import Any, Optional +from typing import Any from PIL import Image @@ -17,7 +17,7 @@ def __init__(self, format_pattern: str, tile_size: int = 256, border: int = 0, * self.border = border TileStore.__init__(self, **kwargs) - def get(self, tiles: Iterable[Optional[Tile]]) -> Iterator[Tile]: + def get(self, tiles: Iterable[Tile | None]) -> Iterator[Tile]: for metatile in tiles: if not metatile: continue diff --git a/tilecloud/store/redis.py b/tilecloud/store/redis.py index 2474842d..e070378d 100644 --- a/tilecloud/store/redis.py +++ b/tilecloud/store/redis.py @@ -4,7 +4,7 @@ import sys import time from collections.abc import Iterable, Iterator -from typing import TYPE_CHECKING, Any, Optional, Union +from typing import TYPE_CHECKING, Any import redis.sentinel from prometheus_client import Counter, Gauge @@ -38,7 +38,7 @@ class RedisTileStore(TileStore): def __init__( self, - url: Optional[str] = None, + url: str | None = None, name: str = "tilecloud", stop_if_empty: bool = True, timeout: int = 5, @@ -48,7 +48,7 @@ def __init__( max_errors_nb: int = 100, pending_count: int = 10, pending_max_count: int = sys.maxsize, - sentinels: Optional[list[tuple[str, int]]] = None, + sentinels: list[tuple[str, int]] | None = None, service_name: str = "mymaster", sentinel_kwargs: Any = None, connection_kwargs: Any = None, @@ -313,7 +313,7 @@ def _claim_olds(self) -> tuple[Iterable[tuple[bytes, Any]], bool]: # Empty means there are pending jobs, but they are not old enough to be stolen return [], has_pendings - def get_status(self) -> dict[str, Union[str, int]]: + def get_status(self) -> dict[str, str | int]: """Get a map of stats.""" nb_messages = self._slave.xlen(self._name) pending = self._slave.xpending(self._name, STREAM_GROUP) # type: ignore[no-untyped-call] diff --git a/tilecloud/store/renderingtheworld.py b/tilecloud/store/renderingtheworld.py index dabbc42b..a5e3a415 100644 --- a/tilecloud/store/renderingtheworld.py +++ b/tilecloud/store/renderingtheworld.py @@ -1,6 +1,5 @@ from collections import deque -from collections.abc import Iterator -from typing import Callable, Optional +from collections.abc import Callable, Iterator from tilecloud import NotSupportedOperation, Tile, TileGrid, TileStore from tilecloud.grid.quad import QuadTileGrid @@ -12,8 +11,8 @@ class RenderingTheWorldTileStore(TileStore): def __init__( self, subdivide: Callable[[Tile], bool], - tilegrid: Optional[TileGrid] = None, - queue: Optional[deque[Tile]] = None, + tilegrid: TileGrid | None = None, + queue: deque[Tile] | None = None, seeds: tuple[Tile, ...] = (), ): super().__init__() @@ -40,7 +39,7 @@ def put_one(self, tile: Tile) -> Tile: self.queue.append(Tile(tilecoord)) return tile - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: raise NotSupportedOperation() def delete_one(self, tile: Tile) -> Tile: diff --git a/tilecloud/store/s3.py b/tilecloud/store/s3.py index bd5e3d37..b1e61d9d 100644 --- a/tilecloud/store/s3.py +++ b/tilecloud/store/s3.py @@ -1,7 +1,7 @@ import logging import threading from collections.abc import Iterator -from typing import Any, Optional, cast +from typing import Any, cast import boto3 import botocore.client @@ -23,12 +23,12 @@ def __init__( bucket: str, tilelayout: TileLayout, dry_run: bool = False, - s3_host: Optional[Any] = None, - cache_control: Optional[Any] = None, + s3_host: Any | None = None, + cache_control: Any | None = None, **kwargs: Any, ) -> None: self._s3_host = s3_host - self._client: Optional[botocore.client.S3] = None # pylint: disable=no-member + self._client: botocore.client.S3 | None = None # pylint: disable=no-member self.bucket = bucket self.tilelayout = tilelayout self.dry_run = dry_run @@ -57,7 +57,7 @@ def delete_one(self, tile: Tile) -> Tile: tile.error = exc return tile - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: key_name = self.tilelayout.filename(tile.tilecoord, tile.metadata) try: response = self.client.get_object(Bucket=self.bucket, Key=key_name) @@ -111,7 +111,7 @@ def _get_status(s3_client_exception: botocore.exceptions.ClientError) -> int: return cast(int, s3_client_exception.response["ResponseMetadata"]["HTTPStatusCode"]) -def get_client(s3_host: Optional[str]) -> "botocore.client.S3": +def get_client(s3_host: str | None) -> "botocore.client.S3": """Get a client for S3.""" config = botocore.config.Config(connect_timeout=_CLIENT_TIMEOUT, read_timeout=_CLIENT_TIMEOUT) with _LOCK: diff --git a/tilecloud/store/searchup.py b/tilecloud/store/searchup.py index a4c7b2ba..0529a371 100644 --- a/tilecloud/store/searchup.py +++ b/tilecloud/store/searchup.py @@ -1,4 +1,3 @@ -from typing import Optional from tilecloud import NotSupportedOperation, Tile, TileGrid, TileStore @@ -11,7 +10,7 @@ def __init__(self, tilestore: TileStore, tilegrid: TileGrid): self.tilestore = tilestore self.tilegrid = tilegrid - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: if not tile: return None test_tile = Tile(tile.tilecoord) diff --git a/tilecloud/store/sqs.py b/tilecloud/store/sqs.py index e00e33f0..3134b668 100644 --- a/tilecloud/store/sqs.py +++ b/tilecloud/store/sqs.py @@ -1,8 +1,8 @@ import builtins import logging import time -from collections.abc import Iterable, Iterator -from typing import Any, Callable +from collections.abc import Callable, Iterable, Iterator +from typing import Any import botocore.client import botocore.exceptions diff --git a/tilecloud/store/url.py b/tilecloud/store/url.py index c8397e30..a2a14016 100644 --- a/tilecloud/store/url.py +++ b/tilecloud/store/url.py @@ -1,6 +1,6 @@ import logging from collections.abc import Iterable -from typing import Any, Optional +from typing import Any import requests @@ -15,7 +15,7 @@ class URLTileStore(TileStore): def __init__( self, tilelayouts: Iterable[TileLayout], - headers: Optional[Any] = None, + headers: Any | None = None, allows_no_contenttype: bool = False, **kwargs: Any, ) -> None: @@ -26,7 +26,7 @@ def __init__( if headers is not None: self.session.headers.update(headers) - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: if tile is None: return None if self.bounding_pyramid is not None and tile.tilecoord not in self.bounding_pyramid: diff --git a/tilecloud/store/wmts.py b/tilecloud/store/wmts.py index a0fb5fc2..5edfa650 100644 --- a/tilecloud/store/wmts.py +++ b/tilecloud/store/wmts.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Optional +from collections.abc import Callable +from typing import Any from tilecloud import NotSupportedOperation, Tile from tilecloud.layout.wmts import WMTSTileLayout @@ -11,10 +12,10 @@ class WMTSTileStore(URLTileStore): def __init__( self, url: str = "", - layer: Optional[str] = None, - style: Optional[str] = None, - format_pattern: Optional[str] = None, - tile_matrix_set: Optional[str] = None, + layer: str | None = None, + style: str | None = None, + format_pattern: str | None = None, + tile_matrix_set: str | None = None, tile_matrix: Callable[[int], str] = str, **kwargs: Any, ): diff --git a/tilecloud/store/zip.py b/tilecloud/store/zip.py index af488e3e..14dc06fb 100644 --- a/tilecloud/store/zip.py +++ b/tilecloud/store/zip.py @@ -4,7 +4,7 @@ from collections import defaultdict from collections.abc import Iterator from datetime import datetime -from typing import Any, Optional +from typing import Any from tilecloud import NotSupportedOperation, Tile, TileLayout, TileStore from tilecloud.layout.osm import OSMTileLayout @@ -14,7 +14,7 @@ class ZipTileStore(TileStore): """A tile store that reads and writes tiles from a zip file.""" - def __init__(self, zipfile: zipfile.ZipFile, layout: Optional[TileLayout] = None, **kwargs: Any): # pylint: disable=redefined-outer-name + def __init__(self, zipfile: zipfile.ZipFile, layout: TileLayout | None = None, **kwargs: Any): # pylint: disable=redefined-outer-name TileStore.__init__(self, **kwargs) self.zipfile = zipfile if layout is None: @@ -41,7 +41,7 @@ def __contains__(self, tile: Tile) -> bool: except KeyError: return False - def get_one(self, tile: Tile) -> Optional[Tile]: + def get_one(self, tile: Tile) -> Tile | None: if tile is None: return None if hasattr(tile, "zipinfo"): diff --git a/tilecloud/tests/test_redis.py b/tilecloud/tests/test_redis.py index e15c2a2a..fc7d61d1 100644 --- a/tilecloud/tests/test_redis.py +++ b/tilecloud/tests/test_redis.py @@ -18,7 +18,7 @@ ) -@pytest.fixture() +@pytest.fixture def store(): store = RedisTileStore( url, @@ -30,7 +30,7 @@ def store(): max_errors_age=1, ) store.delete_all() - yield store + return store @skip_no_redis From 4799f52c03c8e7b7a673b88f7f027fe13335046e Mon Sep 17 00:00:00 2001 From: "geo-ghci-int[bot]" <146321879+geo-ghci-int[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 15:55:11 +0000 Subject: [PATCH 5/5] Apply pre-commit fix From the artifact of the previous workflow run --- tilecloud/filter/consistenthash.py | 1 - tilecloud/filter/contenttype.py | 1 - tilecloud/filter/error.py | 1 - tilecloud/filter/inboundingpyramid.py | 1 - tilecloud/store/filtered.py | 4 +--- tilecloud/store/searchup.py | 1 - 6 files changed, 1 insertion(+), 8 deletions(-) diff --git a/tilecloud/filter/consistenthash.py b/tilecloud/filter/consistenthash.py index 0d83e029..f0ecc33b 100644 --- a/tilecloud/filter/consistenthash.py +++ b/tilecloud/filter/consistenthash.py @@ -1,4 +1,3 @@ - from tilecloud import Tile diff --git a/tilecloud/filter/contenttype.py b/tilecloud/filter/contenttype.py index 5ea21b28..231d4d04 100644 --- a/tilecloud/filter/contenttype.py +++ b/tilecloud/filter/contenttype.py @@ -1,4 +1,3 @@ - from tilecloud import Tile diff --git a/tilecloud/filter/error.py b/tilecloud/filter/error.py index 1d061ccd..f838c8c4 100644 --- a/tilecloud/filter/error.py +++ b/tilecloud/filter/error.py @@ -1,6 +1,5 @@ """Module includes filters for dealing with errors in tiles.""" - from tilecloud import Tile from tilecloud.filter.logger import Logger diff --git a/tilecloud/filter/inboundingpyramid.py b/tilecloud/filter/inboundingpyramid.py index 4ff5415d..14baed49 100644 --- a/tilecloud/filter/inboundingpyramid.py +++ b/tilecloud/filter/inboundingpyramid.py @@ -1,4 +1,3 @@ - from tilecloud import BoundingPyramid, Tile diff --git a/tilecloud/store/filtered.py b/tilecloud/store/filtered.py index 56a6675c..8b6ced8e 100644 --- a/tilecloud/store/filtered.py +++ b/tilecloud/store/filtered.py @@ -14,9 +14,7 @@ def __init__(self, tilestore: TileStore, filters: list[Callable[[Tile | None], T self.filters = filters def get_one(self, tile: Tile) -> Tile | None: - def reduce_function( - tile: Tile | None, filter_pattern: Callable[[Tile | None], Tile] - ) -> Tile | None: + def reduce_function(tile: Tile | None, filter_pattern: Callable[[Tile | None], Tile]) -> Tile | None: return filter_pattern(tile) return reduce(reduce_function, self.filters, self.tilestore.get_one(tile)) diff --git a/tilecloud/store/searchup.py b/tilecloud/store/searchup.py index 0529a371..fca5a115 100644 --- a/tilecloud/store/searchup.py +++ b/tilecloud/store/searchup.py @@ -1,4 +1,3 @@ - from tilecloud import NotSupportedOperation, Tile, TileGrid, TileStore