diff --git a/.bandit.yaml b/.bandit.yaml deleted file mode 100644 index 1bf9b483e..000000000 --- a/.bandit.yaml +++ /dev/null @@ -1,2 +0,0 @@ -skips: - - B101 # Use of assert detected. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4655b0308..ade08cb01 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -63,32 +63,24 @@ repos: rev: v0.1.8 hooks: - id: ripsecrets - - repo: https://github.com/PyCQA/autoflake - rev: v2.3.1 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.1 hooks: - - id: autoflake - - repo: https://github.com/asottile/pyupgrade - rev: v3.19.0 - hooks: - - id: pyupgrade + - id: ruff-format args: - - --py39-plus - - repo: https://github.com/PyCQA/isort - rev: 5.13.2 - hooks: - - id: isort - - repo: https://github.com/psf/black - rev: 24.10.0 + - --line-length=110 + - repo: https://github.com/PyCQA/prospector + rev: v1.13.3 hooks: - - id: black - #- repo: https://github.com/PyCQA/prospector - # rev: v1.8.4 - # hooks: - # - id: prospector - # args: - # - --tool=pydocstyle - # - --die-on-tool-error - # - --output-format=pylint + - id: prospector + args: + - --tool=ruff + - --die-on-tool-error + - --output-format=pylint + additional_dependencies: + - prospector-profile-duplicated==1.8.0 # pypi + - prospector-profile-utils==1.13.0 # pypi + - ruff==0.8.1 # pypi - repo: https://github.com/sbrunner/jsonschema-validator rev: 0.3.2 hooks: diff --git a/.prospector.yaml b/.prospector.yaml index ca0e528a9..635368baa 100644 --- a/.prospector.yaml +++ b/.prospector.yaml @@ -1,10 +1,9 @@ inherits: - duplicated - utils:base + - utils:fix - utils:no-design-checks - -strictness: veryhigh -doc-warnings: false + - utils:unsafe ignore-patterns: - ^tilecloud/scripts/tc_.*.py @@ -14,6 +13,7 @@ pylint: disable: - cyclic-import # see: https://github.com/PyCQA/pylint/issues/850 -bandit: - options: - config: .bandit.yaml +ruff: + disable: + - D102 # Missing docstring in public method + - D107 # Missing docstring in `__init__` diff --git a/bin/favicon.py b/bin/favicon.py index 2aec4f361..b2ff24819 100644 --- a/bin/favicon.py +++ b/bin/favicon.py @@ -5,7 +5,7 @@ from shapely.ops import cascaded_union -def box(minx: float, miny: float, maxx: float, maxy: float) -> Polygon: +def _box(minx: float, miny: float, maxx: float, maxy: float) -> Polygon: return Polygon(((minx, miny), (maxx, miny), (maxx, maxy), (minx, maxy))) @@ -13,7 +13,7 @@ def box(minx: float, miny: float, maxx: float, maxy: float) -> Polygon: GOLDEN_RATIO = (1 + math.sqrt(5)) / 2 Y0 = int(3 - (2 + GOLDEN_RATIO) / 2) - 0.5 - rectangle = box(1, Y0, 5, 2 + Y0) + rectangle = _box(1, Y0, 5, 2 + Y0) circle1 = Point(1, 1 + Y0).buffer(1) circle2 = Point(1 + 1 / GOLDEN_RATIO, 2 + Y0).buffer(1 / GOLDEN_RATIO) circle3 = Point(5 - GOLDEN_RATIO, 2 + Y0).buffer(GOLDEN_RATIO) diff --git a/poetry.lock b/poetry.lock index 0395c0785..304f32084 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "astroid" @@ -64,13 +64,13 @@ typing-extensions = ">=4.0.0" [[package]] name = "azure-storage-blob" -version = "12.23.1" +version = "12.24.0" description = "Microsoft Azure Blob Storage Client Library for Python" optional = true python-versions = ">=3.8" files = [ - {file = "azure_storage_blob-12.23.1-py3-none-any.whl", hash = "sha256:1c2238aa841d1545f42714a5017c010366137a44a0605da2d45f770174bfc6b4"}, - {file = "azure_storage_blob-12.23.1.tar.gz", hash = "sha256:a587e54d4e39d2a27bd75109db164ffa2058fe194061e5446c5a89bca918272f"}, + {file = "azure_storage_blob-12.24.0-py3-none-any.whl", hash = "sha256:4f0bb4592ea79a2d986063696514c781c9e62be240f09f6397986e01755bc071"}, + {file = "azure_storage_blob-12.24.0.tar.gz", hash = "sha256:eaaaa1507c8c363d6e1d1342bd549938fdf1adec9b1ada8658c8f5bf3aea844e"}, ] [package.dependencies] @@ -1186,19 +1186,18 @@ twisted = ["twisted"] [[package]] name = "prospector" -version = "1.12.1" +version = "1.13.3" description = "Prospector is a tool to analyse Python code by aggregating the result of other tools." optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "prospector-1.12.1-py3-none-any.whl", hash = "sha256:e2440b51f40626cbaea80edd97263d8c0a71a79e729415fb505096d4d39e2287"}, - {file = "prospector-1.12.1.tar.gz", hash = "sha256:b9bb4bcdd77b943c597ee4f374960e851cdd2a0b4b60eaeeaf0da465facafc60"}, + {file = "prospector-1.13.3-py3-none-any.whl", hash = "sha256:e7261c222ba54bc8aef8c9e31b2dcf5ed2a656c9b76fc0cceab294cd5ec3db6b"}, + {file = "prospector-1.13.3.tar.gz", hash = "sha256:36ccb13f69aa27c5c4682afd4cc46219b9e0c80723e30dc64ff30c71f7b877a1"}, ] [package.dependencies] bandit = {version = ">=1.5.1", optional = true, markers = "extra == \"with-bandit\" or extra == \"with_everything\""} dodgy = ">=0.2.1,<0.3.0" -flake8 = "*" GitPython = ">=3.1.27,<4.0.0" mccabe = ">=0.7.0,<0.8.0" mypy = {version = ">=0.600", optional = true, markers = "extra == \"with-mypy\" or extra == \"with_everything\""} @@ -1213,40 +1212,45 @@ pylint-django = ">=2.6.1" pylint-flask = "0.6" pyroma = {version = ">=2.4", optional = true, markers = "extra == \"with-pyroma\" or extra == \"with_everything\""} PyYAML = "*" -requirements-detector = ">=1.3.1" +requirements-detector = ">=1.3.2" +ruff = {version = "*", optional = true, markers = "extra == \"with-ruff\" or extra == \"with_everything\""} setoptconf-tmp = ">=0.3.1,<0.4.0" toml = ">=0.10.2,<0.11.0" [package.extras] with-bandit = ["bandit (>=1.5.1)"] -with-everything = ["bandit (>=1.5.1)", "mypy (>=0.600)", "pyright (>=1.1.3)", "pyroma (>=2.4)", "vulture (>=1.5)"] +with-everything = ["bandit (>=1.5.1)", "mypy (>=0.600)", "pyright (>=1.1.3)", "pyroma (>=2.4)", "ruff", "vulture (>=1.5)"] with-mypy = ["mypy (>=0.600)"] with-pyright = ["pyright (>=1.1.3)"] with-pyroma = ["pyroma (>=2.4)"] +with-ruff = ["ruff"] with-vulture = ["vulture (>=1.5)"] [[package]] name = "prospector-profile-duplicated" -version = "1.6.0" +version = "1.8.0" description = "Profile that can be used to disable the duplicated or conflict rules between Prospector and other tools" optional = false python-versions = "*" files = [ - {file = "prospector_profile_duplicated-1.6.0-py2.py3-none-any.whl", hash = "sha256:bf6a6aae0c7de48043b95e4d42e23ccd090c6c7115b6ee8c8ca472ffb1a2022b"}, - {file = "prospector_profile_duplicated-1.6.0.tar.gz", hash = "sha256:9c2d541076537405e8b2484cb6222276a2df17492391b6af1b192695770aab83"}, + {file = "prospector_profile_duplicated-1.8.0-py2.py3-none-any.whl", hash = "sha256:c36ca647848739e1ca52cf220b00ba1fbdf7105217d5534a5bf2e65279ad6011"}, + {file = "prospector_profile_duplicated-1.8.0.tar.gz", hash = "sha256:1565109b016fac05c61dbce38b952bfce3ff054d0c5493d1acad1fc8e9909704"}, ] [[package]] name = "prospector-profile-utils" -version = "1.9.1" +version = "1.13.0" description = "Some utility Prospector profiles." optional = false -python-versions = "*" +python-versions = "<4.0,>=3.9" files = [ - {file = "prospector_profile_utils-1.9.1-py2.py3-none-any.whl", hash = "sha256:b458d8c4d59bdb1547e4630a2c6de4971946c4f0999443db6a9eef6d216b26b8"}, - {file = "prospector_profile_utils-1.9.1.tar.gz", hash = "sha256:008efa6797a85233fd8093dcb9d86f5fa5d89673e431c15cb1496a91c9b2c601"}, + {file = "prospector_profile_utils-1.13.0-py3-none-any.whl", hash = "sha256:f991f8f2709c5a2c693d0e0a672a8f3ce2db3f1767b4dc2fbe167395c7dbdbb3"}, + {file = "prospector_profile_utils-1.13.0.tar.gz", hash = "sha256:2f98ddda31c0fa024134fe687e9828855f3597ea52de7a710d1aa508de8fda0a"}, ] +[package.dependencies] +prospector = ">=1.13.0" + [[package]] name = "pycairo" version = "1.27.0" @@ -1755,6 +1759,33 @@ typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.1 [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "ruff" +version = "0.8.1" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5"}, + {file = "ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087"}, + {file = "ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5"}, + {file = "ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790"}, + {file = "ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6"}, + {file = "ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737"}, + {file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"}, +] + [[package]] name = "s3transfer" version = "0.10.4" @@ -2250,4 +2281,4 @@ wsgi = ["pyramid"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "b4c2f6cf2a662ef1b5aea6b57afbb43ee01a6efb702d0f44c011fda2a997409d" +content-hash = "dd591de2933b9cd4fce5f263e5ede977439be2acbeda37d61d9d41511e73709d" diff --git a/pyproject.toml b/pyproject.toml index b8ad2ee2d..3a3b70db0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ tc-viewer = "tilecloud.scripts.tc_viewer:main" [tool.poetry.dependencies] python = ">=3.10,<3.13" -azure-storage-blob = { version = "12.23.1", optional = true } +azure-storage-blob = { version = "12.24.0", optional = true } azure-identity = { version = "1.19.0", optional = true } boto3 = { version = "1.35.71", optional = true } bottle = "0.13.2" @@ -71,9 +71,9 @@ prometheus = ["prometheus_client"] all = ["azure-storage-blob", "azure-identity", "boto3", "pyramid", "redis", "prometheus_client"] [tool.poetry.group.dev.dependencies] -prospector = { version = "1.12.1", extras = ["with_bandit", "with_mypy", "with_pyroma"] } -prospector-profile-duplicated = "1.6.0" -prospector-profile-utils = "1.9.1" +prospector = { version = "1.13.3", extras = ["with_bandit", "with_mypy", "with_pyroma", "with_ruff"] } +prospector-profile-duplicated = "1.8.0" +prospector-profile-utils = "1.13.0" pytest = "8.3.4" pytest-cov = "6.0.0" types-boto = "2.49.18.20241019" diff --git a/tilecloud/__init__.py b/tilecloud/__init__.py index f4c22a863..5087f6939 100644 --- a/tilecloud/__init__.py +++ b/tilecloud/__init__.py @@ -17,15 +17,16 @@ class NotSupportedOperation(Exception): - pass + """Raised when an operation is not supported.""" -def cmp(a: Any, b: Any) -> int: # pylint: disable=invalid-name +def _cmp(a: Any, b: Any) -> int: # pylint: disable=invalid-name return cast(bool, a > b) - cast(bool, a < b) def consume( - iterator: Iterator[Optional["Tile"]], n: Optional[int] = None # pylint: disable=invalid-name + iterator: Iterator[Optional["Tile"]], + n: Optional[int] = None, # pylint: disable=invalid-name ) -> None: # pragma: no cover """ Advance the iterator n-steps ahead. @@ -42,18 +43,16 @@ def consume( class Bounds: - """ - Uni-dimensional integer bounds. - """ + """Uni-dimensional integer bounds.""" def __init__(self, start: Optional[int] = None, stop: Optional[int] = None) -> None: """ Construct a :class:`Bounds` object. Arguments: - start: Start stop: Stop + """ self.start = start if stop is None: @@ -62,7 +61,7 @@ def __init__(self, start: Optional[int] = None, stop: Optional[int] = None) -> N self.stop = stop def __cmp__(self, other: "Bounds") -> int: - return cmp(self.start, other.start) or cmp(self.stop, other.stop) + return _cmp(self.start, other.start) or _cmp(self.stop, other.stop) def __lt__(self, other: "Bounds") -> bool: return [self.start, self.stop] < [other.start, other.stop] @@ -77,8 +76,8 @@ def __contains__(self, key: int) -> bool: Return ``True`` if ``self`` contains ``key``. Arguments: - key: Key + """ if self.start is None: return False @@ -86,9 +85,7 @@ def __contains__(self, key: int) -> bool: return self.start <= key < self.stop def __len__(self) -> int: - """ - Return the number of unique elements. - """ + """Return the number of unique elements.""" if self.start is None: return 0 assert self.stop is not None @@ -105,9 +102,7 @@ def __repr__(self) -> str: # pragma: no cover return f"{self.__class__.__name__}({self.start}, {self.stop})" def add(self, value: int) -> "Bounds": - """ - Extends self to include value. - """ + """Extend self to include value.""" if self.start is None: self.start = value self.stop = value + 1 @@ -118,9 +113,7 @@ def add(self, value: int) -> "Bounds": return self def update(self, other: "Bounds") -> "Bounds": - """ - Merges other into self. - """ + """Merge other into self.""" if self.start is None: self.start = other.start self.stop = other.stop @@ -134,9 +127,7 @@ def update(self, other: "Bounds") -> "Bounds": return self def union(self, other: "Bounds") -> "Bounds": - """ - Returns a new Bounds which is the union of self and other. - """ + """Get a new Bounds which is the union of self and other.""" if self and other: assert self.start is not None assert self.stop is not None @@ -151,6 +142,12 @@ def union(self, other: "Bounds") -> "Bounds": class BoundingPyramid: + """ + The bounding pyramid. + + Used to generate a list of tiles in a pyramid. + """ + def __init__( self, bounds: Optional[dict[int, tuple[Bounds, Bounds]]] = None, tilegrid: Optional[Any] = None ) -> None: @@ -162,9 +159,7 @@ def __init__( self.tilegrid = GoogleTileGrid def __contains__(self, tilecoord: "TileCoord") -> bool: - """ - Returns True if tilecoord is in self. - """ + """Get True if tilecoord is in self.""" if tilecoord.z not in self.bounds: return False xbounds, ybounds = self.bounds[tilecoord.z] @@ -176,21 +171,15 @@ def __eq__(self, other: object) -> bool: return self.tilegrid == other.tilegrid and self.bounds == other.bounds def __iter__(self) -> Iterator["TileCoord"]: - """ - Generates every TileCoord in self, in increasing z, x, and y order. - """ + """Generate every TileCoord in self, in increasing z, x, and y order.""" return self.itertopdown() def __len__(self) -> int: - """ - Returns the total number of TileCoords in self. - """ + """Get the total number of TileCoords in self.""" return sum(len(xbounds) * len(ybounds) for xbounds, ybounds in self.bounds.values()) def add(self, tilecoord: "TileCoord") -> "BoundingPyramid": - """ - Extends self to include tilecoord. - """ + """Extend self to include tilecoord.""" if tilecoord.z in self.bounds: xbounds, ybounds = self.bounds[tilecoord.z] xbounds.add(tilecoord.x) @@ -259,15 +248,11 @@ def metatilecoords(self, n: int = 8) -> Iterator["TileCoord"]: # pylint: disabl x += n # pylint: disable=invalid-name def zget(self, z: int) -> tuple[Bounds, Bounds]: # pylint: disable=invalid-name - """ - Return the tuple (xbounds, ybounds) at level z. - """ + """Get the tuple (xbounds, ybounds) at level z.""" return self.bounds[z] def ziter(self, z: int) -> Iterator["TileCoord"]: # pylint: disable=invalid-name - """ - Generates every TileCoord in self at level z. - """ + """Generate every TileCoord in self at level z.""" if z in self.bounds: xbounds, ybounds = self.bounds[z] for x in xbounds: # pylint: disable=invalid-name @@ -322,9 +307,7 @@ def full(cls, zmin: Optional[int] = None, zmax: Optional[int] = None) -> "Boundi class Tile: - """ - An actual tile with optional metadata. - """ + """An actual tile with optional metadata.""" def __init__( self, @@ -336,15 +319,16 @@ def __init__( **kwargs: Any, ) -> None: """ - Construct a :class:`Tile`. + Construct. Arguments: - - tilecoord: Tile coordinate - content_encoding: Content encoding - content_type: Content type - data: Data + tilecoord: The tile coordinate + content_encoding: The content encoding + content_type: The image content-type + data: The tile data + metadata: The layer metadata kwargs: The metadata attributes + """ self.tilecoord = tilecoord self.content_encoding = content_encoding @@ -361,7 +345,7 @@ def __cmp__(self, other: "Tile") -> int: Tile comparison is done by comparing their coordinates. """ - return cmp(self.tilecoord, other.tilecoord) + return _cmp(self.tilecoord, other.tilecoord) def __lt__(self, other: "Tile") -> bool: return self.tilecoord < other.tilecoord @@ -372,9 +356,7 @@ def __eq__(self, other: object) -> bool: return self.tilecoord == other.tilecoord def __repr__(self) -> str: # pragma: no cover - """ - Return a string representation for debugging. - """ + """Return a string representation for debugging.""" keys = sorted(self.__dict__.keys()) attrs = "".join(f" {key}={self.__dict__[key]}" for key in keys) return f"" @@ -392,20 +374,18 @@ def __dict2__(self) -> dict[str, Any]: class TileCoord: - """ - A tile coordinate. - """ + """A tile coordinate.""" def __init__(self, z: int, x: int, y: int, n: int = 1) -> None: # pylint: disable=invalid-name """ Construct a TileCoord. Attributes: - z: Zoom level x: X coordinate y: Y coordinate n: Tile size + """ self.z = z # pylint: disable=invalid-name self.x = x # pylint: disable=invalid-name @@ -419,7 +399,9 @@ def __cmp__(self, other: "TileCoord") -> int: :class:`TileCoord`s are compared in order of their size and ``z``, ``x`` and ``y`` coordinates. """ - return cmp(self.n, other.n) or cmp(self.z, other.z) or cmp(self.x, other.x) or cmp(self.y, other.y) + return ( + _cmp(self.n, other.n) or _cmp(self.z, other.z) or _cmp(self.x, other.x) or _cmp(self.y, other.y) + ) def __lt__(self, other: "TileCoord") -> bool: return [self.n, self.z, self.x, self.y] < [other.n, other.z, other.x, other.y] @@ -439,25 +421,19 @@ def __hash__(self) -> int: return ((self.x // self.n) << self.z) ^ (self.y // self.n) def __iter__(self) -> Iterator["TileCoord"]: - """ - Yield each TileCoord. - """ + """Yield each TileCoord.""" for i in range(0, self.n): for j in range(0, self.n): yield TileCoord(self.z, self.x + i, self.y + j) def __repr__(self) -> str: # pragma: no cover - """ - Return a string representation for debugging. - """ + """Return a string representation for debugging.""" if self.n == 1: return f"{self.__class__.__name__}({self.z}, {self.x}, {self.y})" return f"{self.__class__.__name__}({self.z}, {self.x}, {self.x}, {self.n})" def __str__(self) -> str: - """ - Return a string representation. - """ + """Return a string representation.""" if self.n == 1: return f"{self.z}/{self.x}/{self.y}" return f"{self.z}/{self.x}/{self.y}:+{self.n}/+{self.n}" @@ -482,9 +458,7 @@ def from_tuple(cls, tpl: builtins.tuple[int, int, int]) -> "TileCoord": class TileGrid: - """ - Lays out tiles at multiple zoom levels. - """ + """Lays out tiles at multiple zoom levels.""" def __init__( self, @@ -497,64 +471,54 @@ def __init__( self.flip_y = flip_y def children(self, tilecoord: TileCoord) -> Iterator[TileCoord]: - """ - Generates all the children of tilecoord. - """ + """Generate all the children of tilecoord.""" raise NotImplementedError def extent(self, tilecoord: TileCoord, border: float = 0) -> tuple[float, float, float, float]: - """ - Returns the extent of the tile at tilecoord. - """ + """Get the extent of the tile at tilecoord.""" raise NotImplementedError def fill_down( - self, z: int, bounds: tuple[Bounds, Bounds] # pylint: disable=invalid-name + self, + z: int, + bounds: tuple[Bounds, Bounds], # pylint: disable=invalid-name ) -> tuple[Bounds, Bounds]: raise NotImplementedError def fill_up( - self, z: int, bounds: tuple[Bounds, Bounds] # pylint: disable=invalid-name + self, + z: int, + bounds: tuple[Bounds, Bounds], # pylint: disable=invalid-name ) -> tuple[Bounds, Bounds]: raise NotImplementedError def parent(self, tilecoord: TileCoord) -> Optional[TileCoord]: - """ - Returns the parent of tilecoord. - """ + """Get the parent of tilecoord.""" raise NotImplementedError def roots(self) -> Iterator[TileCoord]: - """ - Generates all the root tiles. - """ + """Generate all the root tiles.""" raise NotImplementedError def tilecoord(self, z: int, x: float, y: float) -> TileCoord: # pylint: disable=invalid-name - """ - Returns the TileCoord for location (x, y) at level z. - """ + """Get the TileCoord for location (x, y) at level z.""" raise NotImplementedError def zs(self) -> Iterable[int]: # pylint: disable=invalid-name - """ - Generates all zs. - """ + """Generate all zs.""" raise NotImplementedError class TileLayout: - """ - Maps tile coordinates to filenames and vice versa. - """ + """Maps tile coordinates to filenames and vice versa.""" def filename(self, tilecoord: TileCoord, metadata: Optional[Any] = None) -> str: """ Return the filename for the given tile coordinate. Attributes: - tilecoord: Tile coordinate + """ raise NotImplementedError @@ -563,16 +527,14 @@ def tilecoord(self, filename: str) -> TileCoord: Return the tile coordinate for the given filename. Attributes: - filename: Filename + """ raise NotImplementedError class TileStore: - """ - A tile store. - """ + """A tile store.""" def __init__( self, @@ -584,10 +546,10 @@ def __init__( Construct a :class:`TileStore`. Attributes: - bounding_pyramid: Bounding pyramid content_type: Default content type for tiles in this store kwargs: Extra attributes + """ self.bounding_pyramid = bounding_pyramid self.content_type = content_type @@ -599,17 +561,15 @@ def __contains__(self, tile: Tile) -> bool: Return true if this store contains ``tile``. Attributes: - tile: Tile + """ if tile and self.bounding_pyramid: return tile.tilecoord in self.bounding_pyramid return False def __len__(self) -> int: - """ - Returns the total number of tiles in the store. - """ + """Get the total number of tiles in the store.""" return reduce(lambda x, _: x + 1, ifilter(None, self.list()), 0) def delete(self, tiles: Iterable[Tile]) -> Iterator[Tile]: @@ -617,8 +577,8 @@ def delete(self, tiles: Iterable[Tile]) -> Iterator[Tile]: Delete ``tiles`` from the store. Attributes: - tiles: Input tilestream + """ return map(self.delete_one, ifilter(None, tiles)) @@ -627,8 +587,8 @@ def delete_one(self, tile: Tile) -> Tile: Delete ``tile`` and return ``tile``. Attributes: - tile: Tile + """ raise NotImplementedError @@ -637,28 +597,26 @@ def get(self, tiles: Iterable[Optional[Tile]]) -> Iterator[Optional[Tile]]: Add data to each of ``tiles``. Attributes: - tiles: Tilestream + """ return map(self.get_one, ifilter(None, tiles)) def get_all(self) -> Iterator[Optional[Tile]]: - """ - Generate all the tiles in the store with their data. - """ + """Generate all the tiles in the store with their data.""" return map(self.get_one, ifilter(None, self.list())) def get_bounding_pyramid(self) -> BoundingPyramid: - """ - Returns the bounding pyramid that encloses all tiles in the store. - """ + """Get the bounding pyramid that encloses all tiles in the store.""" return reduce( BoundingPyramid.add, map(attrgetter("tilecoord"), ifilter(None, self.list())), BoundingPyramid() ) def get_cheap_bounding_pyramid(self) -> Optional[BoundingPyramid]: """ - Returns a bounding pyramid that is cheap to calculate, or ``None`` if it is not possible to calculate + Get a bounding pyramid that is cheap to calculate. + + Or ``None`` if it is not possible to calculate a bounding pyramid cheaply. """ return None @@ -668,15 +626,13 @@ def get_one(self, tile: Tile) -> Optional[Tile]: Add data to ``tile``, or return ``None`` if ``tile`` is not in the store. Attributes: - tile: Tile + """ raise NotImplementedError def list(self) -> Iterable[Tile]: - """ - Generate all the tiles in the store, but without their data. - """ + """Generate all the tiles in the store, but without their data.""" if self.bounding_pyramid is not None: for tilecoord in self.bounding_pyramid: yield Tile(tilecoord) @@ -686,8 +642,8 @@ def put(self, tiles: Iterable[Tile]) -> Iterator[Tile]: Store ``tiles`` in the store. Attributes: - tiles: Tilestream + """ return map(self.put_one, ifilter(None, tiles)) @@ -696,8 +652,8 @@ def put_one(self, tile: Tile) -> Tile: Store ``tile`` in the store. Attributes: - tile: Tile + """ raise NotImplementedError @@ -707,7 +663,6 @@ def load(name: str, allows_no_contenttype: bool = False) -> "TileStore": # prag Construct a :class:`TileStore` from a name. Attributes: - name: Name The following shortcuts are available: @@ -731,6 +686,7 @@ def load(name: str, allows_no_contenttype: bool = False) -> "TileStore": # prag .zip + """ if name == "null://": from tilecloud.store.null import NullTileStore @@ -804,4 +760,4 @@ def load(name: str, allows_no_contenttype: bool = False) -> "TileStore": # prag module = __import__(name) components = name.split(".") module = reduce(getattr, components[1:], module) - return cast(TileStore, getattr(module, "tilestore")) + return cast(TileStore, module.tilestore) diff --git a/tilecloud/filter/benchmark.py b/tilecloud/filter/benchmark.py index 95564dce6..5a19506be 100644 --- a/tilecloud/filter/benchmark.py +++ b/tilecloud/filter/benchmark.py @@ -11,6 +11,8 @@ class Statistics: + """Compute statistics on a sequence of numbers.""" + def __init__(self, format_pattern: str = "%f"): self.format = format_pattern self.n = 0 # pylint: disable=invalid-name @@ -47,6 +49,8 @@ def standard_deviation(self) -> float: class Benchmark: + """Create a filter for benchmarking tiles.""" + def __init__(self, attr: str = "benchmark"): self.attr = attr self.statisticss: dict[str, Statistics] = {} @@ -76,6 +80,8 @@ def callback(tile: Tile) -> Tile: class StatsCountTiles: + """Create a filter for counting all tiles.""" + def __call__(self, tile: Tile) -> Tile: if tile: _TILES_COUNTER.inc() @@ -83,6 +89,8 @@ def __call__(self, tile: Tile) -> Tile: class StatsCountErrors: + """Create a filter for counting all tiles with errors.""" + def __call__(self, tile: Tile) -> Tile: if tile and tile.error: _TILES_ERROR_COUINTER.inc() diff --git a/tilecloud/filter/error.py b/tilecloud/filter/error.py index e487f22a9..9969398f9 100644 --- a/tilecloud/filter/error.py +++ b/tilecloud/filter/error.py @@ -1,6 +1,4 @@ -""" -This module includes filters for dealing with errors in tiles. -""" +"""Module includes filters for dealing with errors in tiles.""" from typing import Optional @@ -9,9 +7,7 @@ class CollectErrors: - """ - Create a filter for collecting tiles with errors in an attribute called ``errors``. - """ + """Create a filter for collecting tiles with errors in an attribute called ``errors``.""" def __init__(self) -> None: self.errors: list[Tile] = [] @@ -23,9 +19,7 @@ def __call__(self, tile: Tile) -> Tile: class DropErrors: - """ - Create a filter for dropping all tiles with errors. - """ + """Create a filter for dropping all tiles with errors.""" def __call__(self, tile: Tile) -> Optional[Tile]: if not tile or tile.error: @@ -34,9 +28,7 @@ def __call__(self, tile: Tile) -> Optional[Tile]: class LogErrors(Logger): - """ - Create a filter for logging all tiles with errors. - """ + """Create a filter for logging all tiles with errors.""" def __call__(self, tile: Optional[Tile]) -> Optional[Tile]: if tile and tile.error: @@ -46,7 +38,9 @@ def __call__(self, tile: Optional[Tile]) -> Optional[Tile]: class MaximumConsecutiveErrors: """ - Create a filter that raises a :class:`TooManyErrors` exception when there are ``max_consecutive_errors`` + Create a filter that limit the consecutive errors. + + Raises a :class:`TooManyErrors` exception when there are ``max_consecutive_errors`` consecutive errors. max_consecutive_errors: @@ -70,13 +64,14 @@ def __call__(self, tile: Tile) -> Tile: class MaximumErrorRate: """ - Create a filter that raises a :class:`TooManyErrors` exception when the total error rate exceeds - ``max_error_rate``. + Create a filter that limit the error rate. + + Raises a :class:`TooManyErrors` exception when the total error rate exceeds ``max_error_rate``. - max_error_rate: + max_error_rate: The maximum error rate. Once exceeded a :class:`TooManyErrors` exception is raised. - min_tiles: + min_tiles: The minimum number of received tiles before a :class:`TooManyErrors` exception can be raised. Defaults to 8. """ @@ -121,6 +116,4 @@ def __call__(self, tile: Tile) -> Tile: class TooManyErrors(RuntimeError): - """ - TooManyErrors exception class. - """ + """TooManyErrors exception class.""" diff --git a/tilecloud/filter/gzip_.py b/tilecloud/filter/gzip_.py index 225c8c74d..fb25a4686 100644 --- a/tilecloud/filter/gzip_.py +++ b/tilecloud/filter/gzip_.py @@ -28,9 +28,7 @@ def __call__(self, tile: Tile) -> Tile: class GzipDecompressor: - """ - Create a filter that decompresses a tile with gzip. - """ + """Create a filter that decompresses a tile with gzip.""" def __call__(self, tile: Tile) -> Tile: assert tile.data is not None diff --git a/tilecloud/filter/image.py b/tilecloud/filter/image.py index e8d8c3a9b..7d366a899 100644 --- a/tilecloud/filter/image.py +++ b/tilecloud/filter/image.py @@ -1,5 +1,5 @@ """ -This module includes filters doing manipulations on the tile image. +Module includes filters doing manipulations on the tile image. It requires the PIL lib. """ diff --git a/tilecloud/filter/inboundingpyramid.py b/tilecloud/filter/inboundingpyramid.py index 9276a678b..4ef1a749b 100644 --- a/tilecloud/filter/inboundingpyramid.py +++ b/tilecloud/filter/inboundingpyramid.py @@ -5,10 +5,11 @@ class InBoundingPyramid: """ - Creates a filter that filters out tiles that are not in the specified bounding pyramid. When called the - filter returns ``None`` if the tile is not in the bounding pyramid. + Create a filter that filters out tiles that are not in the specified bounding pyramid. - bounding_pyramid: + When called the filter returns ``None`` if the tile is not in the bounding pyramid. + + bounding_pyramid: A :class:`tilecloud.BoundingPyramid` object. """ diff --git a/tilecloud/filter/logger.py b/tilecloud/filter/logger.py index 69d8b9fd5..a0c142100 100644 --- a/tilecloud/filter/logger.py +++ b/tilecloud/filter/logger.py @@ -5,6 +5,8 @@ class Logger: + """Log tiles.""" + def __init__(self, logger: logging.Logger, level: int, msgformat: str, *args: Any, **kwargs: Any): self.logger = logger self.level = level diff --git a/tilecloud/filter/optipng.py b/tilecloud/filter/optipng.py index eee770f63..4fad184ec 100644 --- a/tilecloud/filter/optipng.py +++ b/tilecloud/filter/optipng.py @@ -6,6 +6,8 @@ class OptiPNG: + """Optimize PNG tiles with optipng.""" + def __init__(self, options: list[str], arg0: str = "/usr/bin/optipng"): self.args = [arg0, "-q"] + list(options) diff --git a/tilecloud/filter/rate.py b/tilecloud/filter/rate.py index 333d98d24..6feda9aa3 100644 --- a/tilecloud/filter/rate.py +++ b/tilecloud/filter/rate.py @@ -5,6 +5,8 @@ class RateLimit: + """Rate limit the number of tiles per second.""" + def __init__(self, rate: float): self.rate = rate self.count = 0 diff --git a/tilecloud/grid/free.py b/tilecloud/grid/free.py index 308c4a4d0..5c55c5d27 100644 --- a/tilecloud/grid/free.py +++ b/tilecloud/grid/free.py @@ -6,6 +6,12 @@ class FreeTileGrid(TileGrid): + """ + A free tile grid. + + There is no correspondence between the tiles of the different zoom levels. + """ + def __init__( self, resolutions: Sequence[Union[int, float]], diff --git a/tilecloud/grid/quad.py b/tilecloud/grid/quad.py index dd86923c5..8e9f52976 100644 --- a/tilecloud/grid/quad.py +++ b/tilecloud/grid/quad.py @@ -6,6 +6,12 @@ class QuadTileGrid(TileGrid): + """ + A quad tile grid. + + Each tiles aer separate in exactly 4 tiles in the next zoom level. + """ + def __init__( self, max_extent: Optional[tuple[float, float, float, float]] = None, diff --git a/tilecloud/layout/i3d.py b/tilecloud/layout/i3d.py index 0b29265b4..32fe0453a 100644 --- a/tilecloud/layout/i3d.py +++ b/tilecloud/layout/i3d.py @@ -7,9 +7,7 @@ class I3DTileLayout(RETileLayout): - """ - I3D (FHNW/OpenWebGlobe) tile layout. - """ + """I3D (FHNW/OpenWebGlobe) tile layout.""" PATTERN = r"(?:[0-3]{2}/)*[0-3]{1,2}" RE = re.compile(PATTERN + r"\Z") diff --git a/tilecloud/layout/osm.py b/tilecloud/layout/osm.py index fd88e61ba..00968f381 100644 --- a/tilecloud/layout/osm.py +++ b/tilecloud/layout/osm.py @@ -7,9 +7,7 @@ class OSMTileLayout(RETileLayout): - """ - OpenStreetMap tile layout. - """ + """OpenStreetMap tile layout.""" PATTERN = r"[0-9]+/[0-9]+/[0-9]+" RE = re.compile(r"([0-9]+)/([0-9]+)/([0-9]+)\Z") diff --git a/tilecloud/layout/re_.py b/tilecloud/layout/re_.py index 8ffe3c43c..a12db836d 100644 --- a/tilecloud/layout/re_.py +++ b/tilecloud/layout/re_.py @@ -4,6 +4,8 @@ class RETileLayout(TileLayout): + """Regular expression tile layout.""" + def __init__(self, pattern: str, filename_re: Pattern[str]) -> None: self.pattern = pattern self.filename_re = filename_re diff --git a/tilecloud/layout/template.py b/tilecloud/layout/template.py index be6e2c276..b874d4e4c 100644 --- a/tilecloud/layout/template.py +++ b/tilecloud/layout/template.py @@ -7,6 +7,8 @@ class TemplateTileLayout(RETileLayout): + """Template for tile layout.""" + def __init__(self, template: str) -> None: self.template = template self.prefix = None diff --git a/tilecloud/layout/tilecache.py b/tilecloud/layout/tilecache.py index c194d024c..b59af17fd 100644 --- a/tilecloud/layout/tilecache.py +++ b/tilecloud/layout/tilecache.py @@ -7,9 +7,7 @@ class TileCacheDiskLayout(RETileLayout): - """ - TileCache disk layout. - """ + """TileCache disk layout.""" PATTERN = r"[0-9]{2}(?:/[0-9]{3}){6}" RE = re.compile(r"([0-9]{2})/([0-9]{3})/([0-9]{3})/([0-9]{3})/([0-9]{3})/([0-9]{3})/([0-9]{3})") diff --git a/tilecloud/layout/wms.py b/tilecloud/layout/wms.py index bdadb4071..3f33772a5 100644 --- a/tilecloud/layout/wms.py +++ b/tilecloud/layout/wms.py @@ -5,6 +5,8 @@ class WMSTileLayout(TileLayout): + """WMS tile layout.""" + def __init__( self, url: str, diff --git a/tilecloud/layout/wmts.py b/tilecloud/layout/wmts.py index 5719f2a58..74922d344 100644 --- a/tilecloud/layout/wmts.py +++ b/tilecloud/layout/wmts.py @@ -5,6 +5,8 @@ class WMTSTileLayout(TileLayout): + """WMTS tile layout.""" + def __init__( self, url: str = "", diff --git a/tilecloud/layout/wrapped.py b/tilecloud/layout/wrapped.py index c225394be..9f940ca34 100644 --- a/tilecloud/layout/wrapped.py +++ b/tilecloud/layout/wrapped.py @@ -6,9 +6,7 @@ class WrappedTileLayout(TileLayout): - """ - A tile layout with an option prefix and/or suffix. - """ + """A tile layout with an option prefix and/or suffix.""" def __init__(self, tilelayout: RETileLayout, prefix: str = "", suffix: str = "") -> None: self.tilelayout = tilelayout diff --git a/tilecloud/lib/memcached.py b/tilecloud/lib/memcached.py index 1a7142e74..9cbb4db42 100644 --- a/tilecloud/lib/memcached.py +++ b/tilecloud/lib/memcached.py @@ -4,10 +4,12 @@ class MemcachedError(RuntimeError): - pass + """A memcached error.""" class MemcachedClient: + """A simple memcached client.""" + VALUE_RE = re.compile(rb"VALUE\s+(?P\S+)\s+(?P\d+)\s+(?P\d+)(?:\s+(?P\d+))?\Z") def __init__(self, host: str = "localhost", port: int = 11211): diff --git a/tilecloud/lib/sqlite3_.py b/tilecloud/lib/sqlite3_.py index 0e08aba5d..f0f1dcc02 100644 --- a/tilecloud/lib/sqlite3_.py +++ b/tilecloud/lib/sqlite3_.py @@ -5,7 +5,7 @@ from tilecloud import TileCoord -def query(connection: Connection, *args: Any) -> Cursor: +def _query(connection: Connection, *args: Any) -> Cursor: cursor = connection.cursor() cursor.execute(*args) return cursor @@ -18,9 +18,7 @@ def query(connection: Connection, *args: Any) -> Cursor: class SQLiteDict(Base): - """ - A dict facade for an SQLite table. - """ + """A dict facade for an SQLite table.""" def __init__( self, @@ -30,42 +28,42 @@ def __init__( ) -> None: self.connection = connection self.commit = commit - query(self.connection, self.CREATE_TABLE_SQL) # type: ignore + _query(self.connection, self.CREATE_TABLE_SQL) # type: ignore if self.commit: self.connection.commit() # Convert a Dict to a Mapping self.update({k: v for k, v in kwargs.items()}) # pylint: disable=unnecessary-comprehension def __contains__(self, key: Union[str, TileCoord]) -> bool: # type: ignore - return query(self.connection, self.CONTAINS_SQL, self._packkey(key)).fetchone()[0] # type: ignore + return _query(self.connection, self.CONTAINS_SQL, self._packkey(key)).fetchone()[0] # type: ignore def __delitem__(self, key: Union[str, TileCoord]) -> None: - query(self.connection, self.DELITEM_SQL, self._packkey(key)) # type: ignore + _query(self.connection, self.DELITEM_SQL, self._packkey(key)) # type: ignore if self.commit: self.connection.commit() def __getitem__(self, key: Union[str, TileCoord]) -> Optional[bytes]: - row = query(self.connection, self.GETITEM_SQL, self._packkey(key)).fetchone() # type: ignore + row = _query(self.connection, self.GETITEM_SQL, self._packkey(key)).fetchone() # type: ignore if row is None: return None return self._unpackvalue(row) def __iter__(self) -> Iterator[str]: - return map(self._unpackkey, query(self.connection, self.ITER_SQL)) # type: ignore + return map(self._unpackkey, _query(self.connection, self.ITER_SQL)) # type: ignore def __len__(self) -> int: - return query(self.connection, self.LEN_SQL).fetchone()[0] # type: ignore + return _query(self.connection, self.LEN_SQL).fetchone()[0] # type: ignore def __setitem__(self, key: Union[str, TileCoord], value: Any) -> None: - query(self.connection, self.SETITEM_SQL, self._packitem(key, value)) # type: ignore + _query(self.connection, self.SETITEM_SQL, self._packitem(key, value)) # type: ignore if self.commit: self.connection.commit() def iteritems(self) -> Iterator[Cursor]: - return map(self._unpackitem, query(self.connection, self.ITERITEMS_SQL)) # type: ignore + return map(self._unpackitem, _query(self.connection, self.ITERITEMS_SQL)) # type: ignore def itervalues(self) -> Iterator[tuple[bytes]]: - return map(self._unpackvalue, query(self.connection, self.ITERVALUES_SQL)) # type: ignore + return map(self._unpackvalue, _query(self.connection, self.ITERVALUES_SQL)) # type: ignore def keys(self) -> KeysView[str]: return set(iter(self)) # type: ignore diff --git a/tilecloud/lib/wmts.py b/tilecloud/lib/wmts.py index b899be15f..82d8a0ada 100644 --- a/tilecloud/lib/wmts.py +++ b/tilecloud/lib/wmts.py @@ -9,13 +9,13 @@ METERS_PER_UNIT = {"feet": 3.28084, "meters": 1, "degrees": 111118.752, "inch": 39.3700787} -def to_wsg84(srs: str, x: float, y: float) -> tuple[float, float]: # pylint: disable=invalid-name +def _to_wsg84(srs: str, x: float, y: float) -> tuple[float, float]: # pylint: disable=invalid-name return cast( tuple[float, float], transform(Proj(init=srs.lower()), Proj(proj="latlong", datum="WGS84"), x, y) ) -class TileMatrixSet(TypedDict): +class _TileMatrixSet(TypedDict): name: str srs: str units: str @@ -25,7 +25,7 @@ class TileMatrixSet(TypedDict): yorigin: str -class Matrix(TypedDict): +class _Matrix(TypedDict): id: int tilewidth: int tileheight: int @@ -37,16 +37,16 @@ class Matrix(TypedDict): topleft: str -class MatrixSet(TypedDict): +class _MatrixSet(TypedDict): crs: str - matrices: list[Matrix] + matrices: list[_Matrix] -def matrix_sets(tile_matrix_set: TileMatrixSet) -> dict[str, MatrixSet]: - sets: dict[str, MatrixSet] = {} +def _matrix_sets(tile_matrix_set: _TileMatrixSet) -> dict[str, _MatrixSet]: + sets: dict[str, _MatrixSet] = {} tile_size = int(tile_matrix_set["tile_size"]) units = tile_matrix_set["units"] - matrix_set: MatrixSet = {"crs": tile_matrix_set["srs"].replace(":", "::"), "matrices": []} + matrix_set: _MatrixSet = {"crs": tile_matrix_set["srs"].replace(":", "::"), "matrices": []} for i, resolution in enumerate(tile_matrix_set["resolutions"]): col = int(ceil(((tile_matrix_set["bbox"][2] - tile_matrix_set["bbox"][0]) / tile_size) / resolution)) row = int(ceil(((tile_matrix_set["bbox"][3] - tile_matrix_set["bbox"][1]) / tile_size) / resolution)) @@ -55,7 +55,7 @@ def matrix_sets(tile_matrix_set: TileMatrixSet) -> dict[str, MatrixSet]: else: maxy = tile_matrix_set["bbox"][1] + (row * tile_size * resolution) - matrix: Matrix = { + matrix: _Matrix = { "id": i, "tilewidth": tile_size, "tileheight": tile_size, @@ -72,7 +72,9 @@ def matrix_sets(tile_matrix_set: TileMatrixSet) -> dict[str, MatrixSet]: return sets -class Layer(TypedDict): +class _Layer(TypedDict): + """Layer definition for the WMTS GetCapabilities XML.""" + extension: str dimension_key: str dimension_default: str @@ -80,9 +82,11 @@ class Layer(TypedDict): matrix_set: str -def get_capabilities(layers: list[Layer], tile_matrix_set: TileMatrixSet, wmts_gettile: str) -> str: +def _get_capabilities(layers: list[_Layer], tile_matrix_set: _TileMatrixSet, wmts_gettile: str) -> str: """ - layers is an array of dict that contains: + Generate the WMTS GetCapabilities XML. + + Layers is an array of dict that contains: extension: the tiles extension like 'png' dimension_key: the used dimension key @@ -103,7 +107,7 @@ def get_capabilities(layers: list[Layer], tile_matrix_set: TileMatrixSet, wmts_g jinja2_template( WMTS_GET_CAPABILITIES_TEMPLATE, layers=layers, - matrix_sets=matrix_sets(tile_matrix_set), + matrix_sets=_matrix_sets(tile_matrix_set), wmts_gettile=wmts_gettile, tile_matrix_set=tile_matrix_set["name"], ), diff --git a/tilecloud/store/azure_storage_blob.py b/tilecloud/store/azure_storage_blob.py index abbcde226..532710310 100644 --- a/tilecloud/store/azure_storage_blob.py +++ b/tilecloud/store/azure_storage_blob.py @@ -12,9 +12,7 @@ class AzureStorageBlobTileStore(TileStore): - """ - Tiles stored in Azure storage blob. - """ + """Tiles stored in Azure storage blob.""" def __init__( self, diff --git a/tilecloud/store/boundingpyramid.py b/tilecloud/store/boundingpyramid.py index 804525e7e..341b597c8 100644 --- a/tilecloud/store/boundingpyramid.py +++ b/tilecloud/store/boundingpyramid.py @@ -4,9 +4,7 @@ class BoundingPyramidTileStore(TileStore): - """ - All tiles in a bounding box. - """ + """All tiles in a bounding box.""" def __init__(self, bounding_pyramid: Optional[BoundingPyramid] = None, **kwargs: Any): TileStore.__init__(self, **kwargs) diff --git a/tilecloud/store/bsddb.py b/tilecloud/store/bsddb.py index 9de302719..c30d49ef2 100644 --- a/tilecloud/store/bsddb.py +++ b/tilecloud/store/bsddb.py @@ -7,6 +7,8 @@ class BSDDBTileStore(TileStore): + """Tiles stored in a BSDDB database.""" + def __init__(self, db: bsddb.DB, **kwargs: Any): self.db = db # pylint: disable=invalid-name TileStore.__init__(self, **kwargs) diff --git a/tilecloud/store/debug.py b/tilecloud/store/debug.py index fc4bd5ba6..a8113e794 100644 --- a/tilecloud/store/debug.py +++ b/tilecloud/store/debug.py @@ -10,6 +10,8 @@ class DebugTileStore(TileStore): + """A tile store that generates a debug image.""" + def __init__(self, color: tuple[int, int, int] = (0, 0, 0), **kwargs: Any): TileStore.__init__(self, content_type="image/png", **kwargs) self.color = color diff --git a/tilecloud/store/dict.py b/tilecloud/store/dict.py index 9ca479ce4..0aa34411a 100644 --- a/tilecloud/store/dict.py +++ b/tilecloud/store/dict.py @@ -5,6 +5,8 @@ class DictTileStore(TileStore): + """Tiles stored in a dictionary.""" + def __init__(self, tiles: Optional[Any] = None, **kwargs: Any) -> None: self.tiles = tiles or {} TileStore.__init__(self, **kwargs) @@ -26,7 +28,7 @@ def get_one(self, tile: Tile) -> Optional[Tile]: return None def list(self) -> Iterator[Tile]: - for tilecoord in self.tiles.keys(): + for tilecoord in self.tiles: yield Tile(tilecoord) def put_one(self, tile: Tile) -> Tile: diff --git a/tilecloud/store/filesystem.py b/tilecloud/store/filesystem.py index 1591f91b4..0da98a289 100644 --- a/tilecloud/store/filesystem.py +++ b/tilecloud/store/filesystem.py @@ -8,9 +8,7 @@ class FilesystemTileStore(TileStore): - """ - Tiles stored in a filesystem. - """ + """Tiles stored in a filesystem.""" def __init__(self, tilelayout: TileLayout, **kwargs: Any): TileStore.__init__(self, **kwargs) diff --git a/tilecloud/store/filtered.py b/tilecloud/store/filtered.py index 5db19d959..b1972c227 100644 --- a/tilecloud/store/filtered.py +++ b/tilecloud/store/filtered.py @@ -5,6 +5,8 @@ class FilteredTileStore(TileStore): + """A tile store that filter the tiles.""" + def __init__(self, tilestore: TileStore, filters: list[Callable[[Optional[Tile]], Tile]], **kwargs: Any): TileStore.__init__(self, **kwargs) self.tilestore = tilestore diff --git a/tilecloud/store/findfirst.py b/tilecloud/store/findfirst.py index 5fa8e12df..e6106a80c 100644 --- a/tilecloud/store/findfirst.py +++ b/tilecloud/store/findfirst.py @@ -5,6 +5,8 @@ class FindFirstTileStore(TileStore): + """A tile store used to get the first tile.""" + def __init__(self, tilestores: Iterator[TileStore], **kwargs: Any): TileStore.__init__(self, **kwargs) self.tilestores = tilestores diff --git a/tilecloud/store/log.py b/tilecloud/store/log.py index 1583700b6..ef11e25f5 100644 --- a/tilecloud/store/log.py +++ b/tilecloud/store/log.py @@ -7,9 +7,7 @@ class LogTileStore(TileStore): - """ - Generates all tile coordinates matching the specified layout from file. - """ + """Generates all tile coordinates matching the specified layout from file.""" def __init__(self, tilelayout: RETileLayout, file: IO[str], **kwargs: Any): TileStore.__init__(self, **kwargs) @@ -21,7 +19,7 @@ def get_one(self, tile: Tile) -> Tile: return tile def list(self) -> Iterator[Tile]: - # FIXME warn that this consumes file + # FIXME warn that this consumes file # pylint: disable=fixme filename_re = re.compile(self.tilelayout.pattern) for line in self.file: match = filename_re.search(line) diff --git a/tilecloud/store/mapnik_.py b/tilecloud/store/mapnik_.py index 8f1404b94..ad2a60c42 100644 --- a/tilecloud/store/mapnik_.py +++ b/tilecloud/store/mapnik_.py @@ -30,7 +30,7 @@ def __init__( **kwargs: Any, ): """ - Constructs a MapnikTileStore. + Construct a MapnikTileStore. tilegrid: the tilegrid. mapfile: the file used to render the tiles. diff --git a/tilecloud/store/mask.py b/tilecloud/store/mask.py index 6c3157a92..510295738 100644 --- a/tilecloud/store/mask.py +++ b/tilecloud/store/mask.py @@ -4,13 +4,18 @@ import PIL.Image import PIL.ImageFile -from tilecloud import BoundingPyramid, Bounds, NotSupportedOperation, Tile, TileCoord, TileStore +from tilecloud import ( + BoundingPyramid, + Bounds, + NotSupportedOperation, + Tile, + TileCoord, + TileStore, +) class MaskTileStore(TileStore): - """ - A black and white image representing present and absent tiles. - """ + """A black and white image representing present and absent tiles.""" image: Union[PIL.Image.Image, PIL.ImageFile.ImageFile] diff --git a/tilecloud/store/mbtiles.py b/tilecloud/store/mbtiles.py index 2f11c7ce8..2018523b4 100644 --- a/tilecloud/store/mbtiles.py +++ b/tilecloud/store/mbtiles.py @@ -7,13 +7,11 @@ from typing import Any, Optional from tilecloud import BoundingPyramid, Bounds, Tile, TileCoord, TileStore -from tilecloud.lib.sqlite3_ import SQLiteDict, query +from tilecloud.lib.sqlite3_ import SQLiteDict, _query class Metadata(SQLiteDict): - """ - A dict facade for the metadata table. - """ + """A dict facade for the metadata table.""" CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS metadata (name text, value text, PRIMARY KEY (name))" CONTAINS_SQL = "SELECT COUNT(*) FROM metadata WHERE name = ?" @@ -27,9 +25,7 @@ class Metadata(SQLiteDict): class Tiles(SQLiteDict): - """ - A dict facade for the tiles table. - """ + """A dict facade for the tiles table.""" CREATE_TABLE_SQL = ( "CREATE TABLE IF NOT EXISTS tiles (zoom_level integer, tile_column integer, " @@ -70,9 +66,7 @@ def _unpackkey(self, row: tuple[int, int, int]) -> TileCoord: class MBTilesTileStore(TileStore): - """ - A MBTiles tile store. - """ + """A MBTiles tile store.""" BOUNDING_PYRAMID_SQL = ( "SELECT zoom_level, MIN(tile_column), MAX(tile_column) + 1, " @@ -110,7 +104,7 @@ def get_all(self) -> Iterator[Tile]: def get_cheap_bounding_pyramid(self) -> BoundingPyramid: bounds = {} - for z, xstart, xstop, ystart, ystop in query( # pylint: disable=invalid-name + for z, xstart, xstop, ystart, ystop in _query( # pylint: disable=invalid-name self.connection, self.BOUNDING_PYRAMID_SQL ): bounds[z] = (Bounds(xstart, xstop), Bounds(ystart, ystop)) @@ -133,6 +127,6 @@ def put_one(self, tile: Tile) -> Tile: return tile def set_metadata_zooms(self) -> None: - for minzoom, maxzoom in query(self.connection, self.SET_METADATA_ZOOMS_SQL): + for minzoom, maxzoom in _query(self.connection, self.SET_METADATA_ZOOMS_SQL): self.metadata["minzoom"] = minzoom self.metadata["maxzoom"] = maxzoom diff --git a/tilecloud/store/memcached.py b/tilecloud/store/memcached.py index 4dfe703a4..945d59e58 100644 --- a/tilecloud/store/memcached.py +++ b/tilecloud/store/memcached.py @@ -5,6 +5,8 @@ class MemcachedTileStore(TileStore): + """A tile store that create a tiles cache in a Memcached server.""" + def __init__( self, client: tilecloud.lib.memcached.MemcachedClient, diff --git a/tilecloud/store/metatile.py b/tilecloud/store/metatile.py index 111606bbd..654a1fb52 100644 --- a/tilecloud/store/metatile.py +++ b/tilecloud/store/metatile.py @@ -9,6 +9,8 @@ class MetaTileSplitterTileStore(TileStore): + """A tile store that splits metatiles into tiles.""" + def __init__(self, format_pattern: str, tile_size: int = 256, border: int = 0, **kwargs: Any) -> None: self.format = format_pattern self.tile_size = tile_size diff --git a/tilecloud/store/null.py b/tilecloud/store/null.py index d76af2aee..8061775f0 100644 --- a/tilecloud/store/null.py +++ b/tilecloud/store/null.py @@ -4,9 +4,7 @@ class NullTileStore(TileStore): - """ - A TileStore that does nothing. - """ + """A TileStore that does nothing.""" def __contains__(self, tile: Tile) -> bool: return False diff --git a/tilecloud/store/queue.py b/tilecloud/store/queue.py index aa993191e..05ced9280 100644 --- a/tilecloud/store/queue.py +++ b/tilecloud/store/queue.py @@ -6,6 +6,7 @@ def encode_message(tile: Tile) -> str: + """Encode a tile to a string message.""" metadata = dict(tile.metadata) if "sqs_message" in metadata: del metadata["sqs_message"] @@ -21,6 +22,7 @@ def encode_message(tile: Tile) -> str: def decode_message(text: str, **kwargs: Any) -> Tile: + """Decode a tile from a string message.""" body = json.loads(base64.b64decode(text).decode("utf-8")) z = body.get("z") # pylint: disable=invalid-name x = body.get("x") # pylint: disable=invalid-name diff --git a/tilecloud/store/redis.py b/tilecloud/store/redis.py index 5b4e18b18..4f9154fca 100644 --- a/tilecloud/store/redis.py +++ b/tilecloud/store/redis.py @@ -31,9 +31,7 @@ class RedisTileStore(TileStore): - """ - Redis queue. - """ + """Redis queue.""" _master: Redis _slave: Redis @@ -187,9 +185,7 @@ def delete_one(self, tile: Tile) -> Tile: return tile def delete_all(self) -> None: - """ - Used only by tests. - """ + """Delete the queue completely, used only by tests.""" logger.debug("Delete all tiles from Redis stream name: %s", self._name) self._master.xtrim(name=self._name, maxlen=0) # xtrim doesn't empty the group claims. So we have to delete and re-create groups @@ -319,9 +315,7 @@ def _claim_olds(self) -> tuple[Iterable[tuple[bytes, Any]], bool]: return [], has_pendings def get_status(self) -> dict[str, Union[str, int]]: - """ - Returns a map of stats. - """ + """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] tiles_in_error = self._get_errors() diff --git a/tilecloud/store/renderingtheworld.py b/tilecloud/store/renderingtheworld.py index cff9d51b0..dabbc42bc 100644 --- a/tilecloud/store/renderingtheworld.py +++ b/tilecloud/store/renderingtheworld.py @@ -1,21 +1,19 @@ from collections import deque from collections.abc import Iterator -from typing import Callable, Deque, Optional +from typing import Callable, Optional from tilecloud import NotSupportedOperation, Tile, TileGrid, TileStore from tilecloud.grid.quad import QuadTileGrid class RenderingTheWorldTileStore(TileStore): - """ - http://mapbox.com/blog/rendering-the-world/ - """ + """https://mapbox.com/blog/rendering-the-world/.""" def __init__( self, subdivide: Callable[[Tile], bool], tilegrid: Optional[TileGrid] = None, - queue: Optional[Deque[Tile]] = None, + queue: Optional[deque[Tile]] = None, seeds: tuple[Tile, ...] = (), ): super().__init__() diff --git a/tilecloud/store/s3.py b/tilecloud/store/s3.py index ef37fee1f..864029e18 100644 --- a/tilecloud/store/s3.py +++ b/tilecloud/store/s3.py @@ -16,9 +16,7 @@ class S3TileStore(TileStore): - """ - Tiles stored in Amazon S3. - """ + """Tiles stored in Amazon S3.""" def __init__( self, @@ -30,7 +28,7 @@ def __init__( **kwargs: Any, ) -> None: self._s3_host = s3_host - self._client: Optional["botocore.client.S3"] = None + self._client: Optional[botocore.client.S3] = None self.bucket = bucket self.tilelayout = tilelayout self.dry_run = dry_run @@ -111,6 +109,7 @@ def _get_status(s3_client_exception: botocore.exceptions.ClientError) -> int: def get_client(s3_host: Optional[str]) -> "botocore.client.S3": + """Get a client for S3.""" config = botocore.config.Config(connect_timeout=CLIENT_TIMEOUT, read_timeout=CLIENT_TIMEOUT) with lock: return boto3.client( diff --git a/tilecloud/store/searchup.py b/tilecloud/store/searchup.py index bd2063f72..a4c7b2baa 100644 --- a/tilecloud/store/searchup.py +++ b/tilecloud/store/searchup.py @@ -4,6 +4,8 @@ class SearchUpTileStore(TileStore): + """A tile store that searches up the tile grid for a tile.""" + def __init__(self, tilestore: TileStore, tilegrid: TileGrid): super().__init__() self.tilestore = tilestore diff --git a/tilecloud/store/sqs.py b/tilecloud/store/sqs.py index ff2835851..6e91eaed8 100644 --- a/tilecloud/store/sqs.py +++ b/tilecloud/store/sqs.py @@ -14,7 +14,7 @@ logger = logging.getLogger(__name__) -def maybe_stop(queue: "botocore.client.SQS") -> bool: +def _maybe_stop(queue: "botocore.client.SQS") -> bool: try: queue.load() except botocore.exceptions.EndpointConnectionError: @@ -30,10 +30,12 @@ def maybe_stop(queue: "botocore.client.SQS") -> bool: class SQSTileStore(TileStore): + """A tile store that store the tiles queue in Amazon SQS.""" + def __init__( self, queue: "botocore.client.SQS", - on_empty: Callable[["botocore.client.SQS"], bool] = maybe_stop, + on_empty: Callable[["botocore.client.SQS"], bool] = _maybe_stop, **kwargs: Any, ): TileStore.__init__(self, **kwargs) @@ -111,9 +113,7 @@ def _send_buffer(self, tiles: builtins.list[Tile]) -> None: tile.error = exception def get_status(self) -> dict[str, str]: - """ - Returns a map of stats. - """ + """Return a map of stats.""" self.queue.load() attributes = dict(self.queue.attributes) return { diff --git a/tilecloud/store/tilecache.py b/tilecloud/store/tilecache.py index a40ac41aa..475b7f979 100644 --- a/tilecloud/store/tilecache.py +++ b/tilecloud/store/tilecache.py @@ -6,6 +6,8 @@ class TileCacheDiskTileStore(FilesystemTileStore): + """A tile store that reads and writes tiles from a TileCache disk layout.""" + def __init__(self, prefix: str = "", suffix: str = "", **kwargs: Any): tilelayout = WrappedTileLayout(TileCacheDiskLayout(), prefix=prefix, suffix=suffix) FilesystemTileStore.__init__(self, tilelayout, **kwargs) diff --git a/tilecloud/store/tilejson.py b/tilecloud/store/tilejson.py index e2f2be211..ec93d2ab3 100644 --- a/tilecloud/store/tilejson.py +++ b/tilecloud/store/tilejson.py @@ -1,5 +1,5 @@ -# FIXME port to requests -# FIXME rename url1 and url2 to url when pyflakes grows a second brain cell +# FIXME port to requests # pylint: disable=fixme +# FIXME rename url1 and url2 to url when pyflakes grows a second brain cell # pylint: disable=fixme # https://github.com/mapbox/TileJSON import json @@ -17,11 +17,13 @@ class TileJSONTileStore(URLTileStore): + """A tile store for tiles in JSON data.""" + KEYS = "name description version attribution template legend center".split() def __init__(self, tile_json: str, urls_key: str = "tiles", **kwargs: Any): - # FIXME schema - # FIXME version 1.0.0 support + # FIXME schema # pylint: disable=fixme + # FIXME version 1.0.0 support # pylint: disable=fixme tile = json.loads(tile_json) assert "tiles" in tile assert isinstance(tile["tiles"], list) diff --git a/tilecloud/store/url.py b/tilecloud/store/url.py index 8da148862..8868fd45a 100644 --- a/tilecloud/store/url.py +++ b/tilecloud/store/url.py @@ -10,6 +10,8 @@ class URLTileStore(TileStore): + """A tile store that reads and writes tiles from a formatted URL.""" + def __init__( self, tilelayouts: Iterable[TileLayout], @@ -27,9 +29,8 @@ def __init__( def get_one(self, tile: Tile) -> Optional[Tile]: if tile is None: return None - if self.bounding_pyramid is not None: - if tile.tilecoord not in self.bounding_pyramid: - return None + if self.bounding_pyramid is not None and tile.tilecoord not in self.bounding_pyramid: + return None tilelayout = self.tilelayouts[hash(tile.tilecoord) % len(self.tilelayouts)] try: url = tilelayout.filename(tile.tilecoord, tile.metadata) diff --git a/tilecloud/store/wmts.py b/tilecloud/store/wmts.py index eae5a01da..a0fb5fc20 100644 --- a/tilecloud/store/wmts.py +++ b/tilecloud/store/wmts.py @@ -6,6 +6,8 @@ class WMTSTileStore(URLTileStore): + """A tile store that reads and writes tiles in WMTS format.""" + def __init__( self, url: str = "", diff --git a/tilecloud/store/zip.py b/tilecloud/store/zip.py index 2d5c54893..af488e3e3 100644 --- a/tilecloud/store/zip.py +++ b/tilecloud/store/zip.py @@ -12,9 +12,9 @@ class ZipTileStore(TileStore): - def __init__( - self, zipfile: zipfile.ZipFile, layout: Optional[TileLayout] = None, **kwargs: Any - ): # pylint: disable=redefined-outer-name + """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 TileStore.__init__(self, **kwargs) self.zipfile = zipfile if layout is None: