From 474c6d6665d8a3709e19c7bfd3ac047ac72f37a3 Mon Sep 17 00:00:00 2001 From: Kirill Kouzoubov Date: Fri, 31 Jan 2025 11:39:39 +0000 Subject: [PATCH 1/9] fix: tests for az module - accept initialized client instead of credentials - use that to run tests with mocks instead of patching that stopped working when imports were moved into functions --- odc/geo/cog/_az.py | 9 +++++++-- tests/test_az.py | 17 ++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/odc/geo/cog/_az.py b/odc/geo/cog/_az.py index 78c5e212..76eb3cca 100644 --- a/odc/geo/cog/_az.py +++ b/odc/geo/cog/_az.py @@ -37,7 +37,12 @@ class AzMultiPartUpload(AzureLimits, MultiPartUploadBase): # pylint: disable=too-many-instance-attributes def __init__( - self, account_url: str, container: str, blob: str, credential: Any = None + self, + account_url: str, + container: str, + blob: str, + credential: Any = None, + client: Any = None, ): """ Initialise Azure multipart upload. @@ -56,7 +61,7 @@ def __init__( # pylint: disable=import-outside-toplevel,import-error from azure.storage.blob import BlobServiceClient - self.blob_service_client = BlobServiceClient( + self.blob_service_client = client or BlobServiceClient( account_url=account_url, credential=credential ) self.container_client = self.blob_service_client.get_container_client(container) diff --git a/tests/test_az.py b/tests/test_az.py index 9462f091..7ed9f4b7 100644 --- a/tests/test_az.py +++ b/tests/test_az.py @@ -1,8 +1,7 @@ """Tests for the Azure AzMultiPartUpload class.""" import base64 -from unittest.mock import MagicMock, patch - +from unittest.mock import MagicMock import pytest pytest.importorskip("azure.storage.blob") @@ -24,12 +23,12 @@ def test_mpu_init(azure_mpu): assert azure_mpu.credential is None -@patch("odc.geo.cog._az.BlobServiceClient") -def test_azure_multipart_upload(mock_blob_service_client): +def test_azure_multipart_upload(): """Test the full Azure AzMultiPartUpload functionality.""" # Mock Azure Blob SDK client structure mock_blob_client = MagicMock() mock_container_client = MagicMock() + mock_blob_service_client = MagicMock() mock_blob_service_client.return_value.get_container_client.return_value = ( mock_container_client ) @@ -45,7 +44,15 @@ def test_azure_multipart_upload(mock_blob_service_client): credential = "mock-sas-token" # Create an instance of AzMultiPartUpload and call its methods - azure_upload = AzMultiPartUpload(account_url, container, blob, credential) + azure_upload = AzMultiPartUpload( + account_url, + container, + blob, + client=mock_blob_service_client( + account_url=account_url, + credential=credential, + ), + ) upload_id = azure_upload.initiate() part1 = azure_upload.write_part(1, b"first chunk of data") part2 = azure_upload.write_part(2, b"second chunk of data") From 88d3c1b946c7dfeedce7e3d766455a0ad8c0d26e Mon Sep 17 00:00:00 2001 From: Kirill Kouzoubov Date: Fri, 31 Jan 2025 11:56:31 +0000 Subject: [PATCH 2/9] maint: apply black 25.1 --- dev-env.yml | 2 +- odc/geo/__init__.py | 3 +-- odc/geo/_rgba.py | 5 ++--- odc/geo/crs.py | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/dev-env.yml b/dev-env.yml index 2b432f4b..1a61d6f4 100644 --- a/dev-env.yml +++ b/dev-env.yml @@ -23,7 +23,7 @@ dependencies: ## linting tools - autopep8 - autoflake - - black + - black >=25.1 - isort - mypy - pycodestyle diff --git a/odc/geo/__init__.py b/odc/geo/__init__.py index 4aa865c2..66b05f6e 100644 --- a/odc/geo/__init__.py +++ b/odc/geo/__init__.py @@ -2,8 +2,7 @@ # # Copyright (c) 2015-2020 ODC Contributors # SPDX-License-Identifier: Apache-2.0 -""" Geometric shapes and operations on them -""" +"""Geometric shapes and operations on them""" # import order is important here # _crs <-- _geom <-- _geobox <- other # isort: skip_file diff --git a/odc/geo/_rgba.py b/odc/geo/_rgba.py index 5d7c4865..24e7a6de 100644 --- a/odc/geo/_rgba.py +++ b/odc/geo/_rgba.py @@ -1,5 +1,4 @@ -""" Helpers for dealing with RGB(A) images. -""" +"""Helpers for dealing with RGB(A) images.""" import functools from typing import Any, List, Optional, Tuple @@ -193,7 +192,7 @@ def _matplotlib_colorize( if robust: if x.dtype.kind != "f": x = x.astype("float32") - _vmin, _vmax = np.nanpercentile(x, [2, 98]) + _vmin, _vmax = (float(x) for x in np.nanpercentile(x, [2, 98])) # type: ignore # do not override configured values if vmin is None: diff --git a/odc/geo/crs.py b/odc/geo/crs.py index b682fc78..da8a2093 100644 --- a/odc/geo/crs.py +++ b/odc/geo/crs.py @@ -54,7 +54,7 @@ def _make_crs_key(crs_spec: Union[int, str, Hashable, CRSLike]) -> Hashable: @cachetools.cached(_crs_cache, key=_make_crs_key) def _make_crs( - crs_spec: Union[str, int, _CRS, CRSLike] + crs_spec: Union[str, int, _CRS, CRSLike], ) -> Tuple[_CRS, str, Optional[int]]: epsg = EPSG_UNSET if isinstance(crs_spec, str): From 0010f6725e81ead06fc54c1f052cfa1ed4a899f9 Mon Sep 17 00:00:00 2001 From: Kirill Kouzoubov Date: Fri, 31 Jan 2025 12:07:35 +0000 Subject: [PATCH 3/9] maint: explicitly disable defaults channel --- dev-env.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-env.yml b/dev-env.yml index 1a61d6f4..ae9ad6b9 100644 --- a/dev-env.yml +++ b/dev-env.yml @@ -1,5 +1,6 @@ name: odc-geo channels: + - nodefaults - conda-forge dependencies: From f7ba06f945e238d9374a21dfed37a811b76d70ff Mon Sep 17 00:00:00 2001 From: Kirill Kouzoubov Date: Thu, 6 Feb 2025 07:54:00 +0000 Subject: [PATCH 4/9] maint: mypy fixes in odc.geo.geom shapely 2+ has tighter type annotations I guess --- odc/geo/geom.py | 62 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/odc/geo/geom.py b/odc/geo/geom.py index f1e89850..0407e152 100644 --- a/odc/geo/geom.py +++ b/odc/geo/geom.py @@ -2,6 +2,8 @@ # # Copyright (c) 2015-2020 ODC Contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import array import functools import itertools @@ -20,12 +22,14 @@ Sequence, Tuple, Union, + cast, ) import numpy from affine import Affine from pyproj.aoi import AreaOfInterest from shapely import geometry, ops +from shapely.coords import CoordinateSequence from shapely.geometry import base from ._interop import have @@ -443,7 +447,10 @@ def to_geom(x) -> base.BaseGeometry: return to_geom(xx) -def densify(coords: CoordList, resolution: float) -> CoordList: +def densify( + coords: Sequence[Tuple[float, float]] | CoordinateSequence, + resolution: float, +) -> CoordList: """ Adds points so they are at most `resolution` units apart. """ @@ -452,15 +459,16 @@ def densify(coords: CoordList, resolution: float) -> CoordList: def short_enough(p1, p2): return (p1[0] ** 2 + p2[0] ** 2) < d2 - new_coords = [coords[0]] + new_coords: List[Tuple[float, float]] = [cast(Tuple[float, float], coords[0])] for p1, p2 in zip(coords[:-1], coords[1:]): + p1, p2 = (cast(Tuple[float, float], p) for p in (p1, p2)) if not short_enough(p1, p2): segment = geometry.LineString([p1, p2]) segment_length = segment.length d = resolution while d < segment_length: - (pt,) = segment.interpolate(d).coords - new_coords.append(pt) + ((x0, x1),) = segment.interpolate(d).coords + new_coords.append((x0, x1)) d += resolution new_coords.append(p2) @@ -563,13 +571,17 @@ def is_ring(self) -> bool: return self.geom.is_ring @property def boundary(self) -> "Geometry": return Geometry(self.geom.boundary, self.crs) @property - def exterior(self) -> "Geometry": return Geometry(self.geom.exterior, self.crs) + def exterior(self) -> "Geometry": + assert isinstance(self.geom, geometry.Polygon) + return Geometry(self.geom.exterior, self.crs) @property - def interiors(self) -> List["Geometry"]: return [Geometry(g, self.crs) for g in self.geom.interiors] + def interiors(self) -> List["Geometry"]: + assert isinstance(self.geom, geometry.Polygon) + return [Geometry(g, self.crs) for g in self.geom.interiors] @property def centroid(self) -> "Geometry": return Geometry(self.geom.centroid, self.crs) @property - def coords(self) -> CoordList: return list(self.geom.coords) + def coords(self) -> CoordList: return cast(list[tuple[float, float]], list(self.geom.coords)) @property def points(self) -> CoordList: return self.coords @property @@ -612,15 +624,26 @@ def segmentize_shapely(geom: base.BaseGeometry) -> base.BaseGeometry: "MultiPolygon", "MultiLineString", ]: - return type(geom)([segmentize_shapely(g) for g in geom.geoms]) + assert isinstance( + geom, + ( + geometry.GeometryCollection, + geometry.MultiPolygon, + geometry.MultiLineString, + ), + ) + _geoms = [segmentize_shapely(g) for g in geom.geoms] + return type(geom)(_geoms) # type: ignore if geom.geom_type in ["LineString", "LinearRing"]: - return type(geom)(densify(list(geom.coords), resolution)) + assert isinstance(geom, (geometry.LineString, geometry.LinearRing)) + return type(geom)(densify(geom.coords, resolution)) if geom.geom_type == "Polygon": + assert isinstance(geom, geometry.Polygon) return geometry.Polygon( - densify(list(geom.exterior.coords), resolution), - [densify(list(i.coords), resolution) for i in geom.interiors], + densify(geom.exterior.coords, resolution), + [densify(i.coords, resolution) for i in geom.interiors], ) raise ValueError( @@ -637,7 +660,7 @@ def interpolate(self, distance: float) -> "Geometry": """ return Geometry(self.geom.interpolate(distance), self.crs) - def buffer(self, distance: float, resolution: float = 30) -> "Geometry": + def buffer(self, distance: float, resolution: int | None = None) -> "Geometry": return Geometry(self.geom.buffer(distance, resolution=resolution), self.crs) def simplify(self, tolerance: float, preserve_topology: bool = True) -> "Geometry": @@ -676,7 +699,7 @@ def _tr(A, x, y): def _to_crs(self, crs: CRS) -> "Geometry": assert self.crs is not None - return Geometry(ops.transform(self.crs.transformer_to_crs(crs), self.geom), crs) + return Geometry(ops.transform(self.crs.transformer_to_crs(crs), self.geom), crs) # type: ignore def to_crs( self, @@ -856,7 +879,7 @@ def explore( "Please install it before using `.explore()`." ) - from folium import Map, GeoJson + from folium import GeoJson, Map # Create folium Map if required map_kwds = {} if map_kwds is None else map_kwds @@ -993,6 +1016,7 @@ def filter(self, pred: Callable[[float, float], bool]) -> "Geometry": return polygon(pts, self.crs, *inners) if self.geom_type in ("LinearRing", "LineString"): + assert isinstance(self.geom, (geometry.LinearRing, geometry.LineString)) pts = [(x, y) for x, y in self.points if pred(x, y)] if len(pts) == 1: pts = [] # need at least 2 points for this type @@ -1000,19 +1024,21 @@ def filter(self, pred: Callable[[float, float], bool]) -> "Geometry": return Geometry(_geom, self.crs) if self.geom_type == "Point": + assert isinstance(self.geom, geometry.Point) x, y = self.coords[0] if pred(x, y): return self return Geometry(geometry.Point(), self.crs) if self.geom_type == "MultiPoint": + assert isinstance(self.geom, geometry.MultiPoint) pts = [(x, y) for x, y in [pt.points[0] for pt in self.geoms] if pred(x, y)] return multipoint(pts, self.crs) if self.is_multi: _filtered = [g.filter(pred).geom for g in self.geoms] _filtered = [g for g in _filtered if not g.is_empty] - _geom = type(self.geom)(_filtered) + _geom = type(self.geom)(_filtered) # type: ignore return Geometry(_geom, self.crs) raise AssertionError("Unhandled geometry type detected") # pragma: no cover @@ -1260,11 +1286,11 @@ def _multigeom(geoms: List[base.BaseGeometry]) -> base.BaseGeometry: geoms = [g for g in geoms if not g.is_empty] src_type = src_type.pop() if src_type == "Polygon": - return geometry.MultiPolygon(geoms) + return geometry.MultiPolygon(geoms) # type: ignore if src_type == "Point": - return geometry.MultiPoint(geoms) + return geometry.MultiPoint(geoms) # type: ignore if src_type == "LineString": - return geometry.MultiLineString(geoms) + return geometry.MultiLineString(geoms) # type: ignore return geometry.GeometryCollection(geoms) From f3f76decd8095946d23c667822df35e642bc9d52 Mon Sep 17 00:00:00 2001 From: Kirill Kouzoubov Date: Thu, 6 Feb 2025 07:55:23 +0000 Subject: [PATCH 5/9] maint: add test-all extra to packaging --- setup.cfg | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/setup.cfg b/setup.cfg index 3dfd2e9f..024f82dd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -67,8 +67,19 @@ all = test = pytest + pytest-cov + pytest-timeout geopandas + %(warp)s + +test-all = + %(test)s %(tiff)s + %(s3)s + %(az)s + folium + ipyleaflet + matplotlib [options.packages.find] From 495fe0e03dde952f3eb9802b8714bed37049b998 Mon Sep 17 00:00:00 2001 From: Kirill Kouzoubov Date: Fri, 7 Feb 2025 01:14:57 +0000 Subject: [PATCH 6/9] maint: configure pylint for tests - remove pragmas from test code - add .pylintrc in test folder --- tests/.pylintrc | 6 ++++++ tests/conftest.py | 2 -- tests/test_cog.py | 2 -- tests/test_converters.py | 2 -- tests/test_crs.py | 3 --- tests/test_dask_interop.py | 1 - tests/test_gbox_ops.py | 2 -- tests/test_gcp.py | 1 - tests/test_geobox.py | 2 -- tests/test_geom.py | 7 ++----- tests/test_gridspec.py | 3 --- tests/test_map.py | 2 -- tests/test_mpu.py | 1 - tests/test_mpu_fs.py | 2 -- tests/test_types.py | 2 -- tests/test_xr_interop.py | 1 - 16 files changed, 8 insertions(+), 31 deletions(-) create mode 100644 tests/.pylintrc diff --git a/tests/.pylintrc b/tests/.pylintrc new file mode 100644 index 00000000..fbd67f54 --- /dev/null +++ b/tests/.pylintrc @@ -0,0 +1,6 @@ +[MESSAGES CONTROL] +disable = + all + +enable = E,F +max-line-length = 120 diff --git a/tests/conftest.py b/tests/conftest.py index 0e623893..cfcfccf6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,8 +8,6 @@ from odc.geo.geobox import GeoBox from odc.geo.xr import rasterize -# pylint: disable=protected-access,import-outside-toplevel,redefined-outer-name - @pytest.fixture(scope="session") def data_dir(): diff --git a/tests/test_cog.py b/tests/test_cog.py index 1d0991c3..7d15ccc1 100644 --- a/tests/test_cog.py +++ b/tests/test_cog.py @@ -33,8 +33,6 @@ gbox_globe = GridSpec.web_tiles(0)[0, 0] _gbox = GeoBox.from_bbox((-10, -20, 15, 30), 4326, resolution=1) -# pylint: disable=redefined-outer-name - @pytest.fixture def gbox(): diff --git a/tests/test_converters.py b/tests/test_converters.py index dc45ba73..0ebea176 100644 --- a/tests/test_converters.py +++ b/tests/test_converters.py @@ -1,7 +1,5 @@ -# pylint: disable=wrong-import-position,redefined-outer-name from pathlib import Path from unittest.mock import MagicMock -from warnings import catch_warnings, filterwarnings import pytest diff --git a/tests/test_crs.py b/tests/test_crs.py index a3b5195e..b5241248 100644 --- a/tests/test_crs.py +++ b/tests/test_crs.py @@ -24,9 +24,6 @@ from odc.geo.testutils import epsg3577, epsg3857, epsg4326 from odc.geo.types import Unset, xy_ -# pylint: disable=missing-class-docstring,use-implicit-booleaness-not-comparison -# pylint: disable=comparison-with-itself - def test_common_crs(): assert common_crs([]) is None diff --git a/tests/test_dask_interop.py b/tests/test_dask_interop.py index 0a12d804..c2b0e4e7 100644 --- a/tests/test_dask_interop.py +++ b/tests/test_dask_interop.py @@ -1,4 +1,3 @@ -# pylint: disable=wrong-import-position import pytest pytest.importorskip("dask") diff --git a/tests/test_gbox_ops.py b/tests/test_gbox_ops.py index d07fbacc..68861143 100644 --- a/tests/test_gbox_ops.py +++ b/tests/test_gbox_ops.py @@ -11,8 +11,6 @@ from odc.geo.geobox import GeoBox from odc.geo.testutils import epsg3857 -# pylint: disable=pointless-statement,too-many-statements - def test_gbox_ops(): s = GeoBox(wh_(1000, 100), Affine(10, 0, 12340, 0, -10, 316770), epsg3857) diff --git a/tests/test_gcp.py b/tests/test_gcp.py index 1f9a2cff..6fd03279 100644 --- a/tests/test_gcp.py +++ b/tests/test_gcp.py @@ -1,4 +1,3 @@ -# pylint: disable=wrong-import-position, protected-access, redefined-outer-name from pathlib import Path import numpy as np diff --git a/tests/test_geobox.py b/tests/test_geobox.py index 2a6b45e0..488a6f4a 100644 --- a/tests/test_geobox.py +++ b/tests/test_geobox.py @@ -30,8 +30,6 @@ xy_norm, ) -# pylint: disable=pointless-statement,too-many-statements,protected-access - def test_geobox_simple(): t = GeoBox( diff --git a/tests/test_geom.py b/tests/test_geom.py index 7ccea762..5199dd50 100644 --- a/tests/test_geom.py +++ b/tests/test_geom.py @@ -10,8 +10,8 @@ from affine import Affine from pytest import approx -from odc.geo._interop import have from odc.geo import CRS, CRSMismatchError, geom, wh_ +from odc.geo._interop import have from odc.geo.geobox import GeoBox, _round_to_res from odc.geo.geom import ( chop_along_antimeridian, @@ -33,9 +33,6 @@ xy_from_gbox, ) -# pylint: disable=protected-access, pointless-statement -# pylint: disable=too-many-statements,too-many-locals,too-many-lines,unnecessary-lambda-assignment - def test_pickleable(): poly = geom.polygon([(10, 20), (20, 20), (20, 10), (10, 20)], crs=epsg4326) @@ -1037,7 +1034,7 @@ def test_triangulate(crs): ], ) def test_explore_geom(geom_json): - from folium import Map, GeoJson + from folium import GeoJson, Map # Create Geometry geometry = geom.Geometry(geom_json, "EPSG:4326") diff --git a/tests/test_gridspec.py b/tests/test_gridspec.py index 1ba57697..f5679b9b 100644 --- a/tests/test_gridspec.py +++ b/tests/test_gridspec.py @@ -14,9 +14,6 @@ from odc.geo.gridspec import GridSpec from odc.geo.testutils import SAMPLE_WKT_WITHOUT_AUTHORITY -# pylint: disable=protected-access,use-implicit-booleaness-not-comparison -# pylint: disable=comparison-with-itself,unnecessary-comprehension - def test_gridspec(): gs = GridSpec( diff --git a/tests/test_map.py b/tests/test_map.py index 8b2ad9e5..57e8e9df 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -5,8 +5,6 @@ from odc.geo._interop import have from odc.geo.xr import ODCExtensionDa -# pylint: disable=protected-access,import-outside-toplevel - cmap = np.asarray( [ [153, 153, 102, 255], diff --git a/tests/test_mpu.py b/tests/test_mpu.py index d80f569a..a3c0060e 100644 --- a/tests/test_mpu.py +++ b/tests/test_mpu.py @@ -7,7 +7,6 @@ from odc.geo.cog._mpu import MPUChunk, SomeData, mpu_write FakeWriteResult = Tuple[int, SomeData, Dict[str, Any]] -# pylint: disable=unbalanced-tuple-unpacking,redefined-outer-name,import-outside-toplevel class FakeWriter: diff --git a/tests/test_mpu_fs.py b/tests/test_mpu_fs.py index 4036b0f0..d089bf8a 100644 --- a/tests/test_mpu_fs.py +++ b/tests/test_mpu_fs.py @@ -10,8 +10,6 @@ from odc.geo.cog._mpu_fs import MPUFileSink -# pylint: disable=protected-access - def slurp(path: Path) -> bytes: with path.open("rb") as f: diff --git a/tests/test_types.py b/tests/test_types.py index a962576f..b4640abb 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -7,8 +7,6 @@ from odc.geo.geom import point from odc.geo.types import func2map -# pylint: disable=use-implicit-booleaness-not-comparison - def test_basics(): assert xy_(0, 2) == xy_([0, 2]) diff --git a/tests/test_xr_interop.py b/tests/test_xr_interop.py index 50d49146..7da9f16c 100644 --- a/tests/test_xr_interop.py +++ b/tests/test_xr_interop.py @@ -25,7 +25,6 @@ xr_zeros, ) -# pylint: disable=redefined-outer-name,import-outside-toplevel,protected-access TEST_GEOBOXES_SMALL_AXIS_ALIGNED = [ GeoBox.from_bbox((-10, -2, 5, 4), "epsg:4326", tight=True, resolution=0.2), GeoBox.from_bbox((-10, -2, 5, 4), "epsg:3857", tight=True, resolution=1), From 4fa0f2961f81cd5ed7a6621a6fe00371b24ca440 Mon Sep 17 00:00:00 2001 From: Kirill Kouzoubov Date: Fri, 7 Feb 2025 01:21:22 +0000 Subject: [PATCH 7/9] maint: switch to pylint 3 - fixes for pylint 3 series --- dev-env.yml | 3 +-- odc/geo/_map.py | 9 +++++---- odc/geo/_xr_interop.py | 1 + odc/geo/cog/_rio.py | 5 +++-- odc/geo/data/__init__.py | 2 +- odc/geo/geom.py | 8 +------- pyproject.toml | 4 +++- tests/.pylintrc | 2 +- tests/test-env-py310.yml | 4 ++-- 9 files changed, 18 insertions(+), 20 deletions(-) diff --git a/dev-env.yml b/dev-env.yml index ae9ad6b9..75ddfeeb 100644 --- a/dev-env.yml +++ b/dev-env.yml @@ -28,11 +28,10 @@ dependencies: - isort - mypy - pycodestyle - - pylint + - pylint =3 - docutils ## test - pytest - - hypothesis - pytest-cov - pytest-timeout diff --git a/odc/geo/_map.py b/odc/geo/_map.py index 181eb743..2c7e024b 100644 --- a/odc/geo/_map.py +++ b/odc/geo/_map.py @@ -242,12 +242,13 @@ def explore( :return: A :py:mod:`folium` map containing the plotted xarray data. """ # pylint: disable=too-many-arguments, protected-access - - if not have.folium: - raise ModuleNotFoundError( + have.check_or_error( + "folium", + msg=( "'folium' is required but not installed. " "Please install it before using `.explore()`." - ) + ), + ) from folium import LayerControl, Map diff --git a/odc/geo/_xr_interop.py b/odc/geo/_xr_interop.py index 4be83c07..28781f57 100644 --- a/odc/geo/_xr_interop.py +++ b/odc/geo/_xr_interop.py @@ -863,6 +863,7 @@ def _xr_reproject_da( else: dst = numpy.full(dst_shape, fill_value, dtype=dtype) + # pylint: disable=possibly-used-before-assignment dst = rio_reproject( src.values, dst, diff --git a/odc/geo/cog/_rio.py b/odc/geo/cog/_rio.py index 032a4e20..fc29b1c9 100644 --- a/odc/geo/cog/_rio.py +++ b/odc/geo/cog/_rio.py @@ -167,6 +167,7 @@ def _write_cog( else: overview_levels = [2**i for i in range(1, 6)] + path: Path | None = None if fname != ":mem:": path = check_write_path( fname, overwrite @@ -226,7 +227,7 @@ def _write(pix, band, dst): # Deal efficiently with "no overviews needed case" if len(overview_levels) == 0: - if fname == ":mem:": + if path is None: with rasterio.MemoryFile() as mem: with mem.open(driver="GTiff", **rio_opts) as dst: _write(pix, band, dst) @@ -246,7 +247,7 @@ def _write(pix, band, dst): _write(pix, band, tmp) tmp.build_overviews(overview_levels, resampling) - if fname == ":mem:": + if path is None: with rasterio.MemoryFile() as mem2: rio_copy( tmp, diff --git a/odc/geo/data/__init__.py b/odc/geo/data/__init__.py index c9433b47..f49f0f91 100644 --- a/odc/geo/data/__init__.py +++ b/odc/geo/data/__init__.py @@ -1,7 +1,6 @@ import json import lzma import threading - from functools import lru_cache from pathlib import Path from typing import Any, Dict, Optional, Tuple @@ -84,6 +83,7 @@ class Countries(_CachedGeoDataFrame): def frame_by_iso3(self, iso3): df = self._instance + assert df is not None return df[df.ISO_A3 == iso3] diff --git a/odc/geo/geom.py b/odc/geo/geom.py index 0407e152..2fa729b4 100644 --- a/odc/geo/geom.py +++ b/odc/geo/geom.py @@ -872,13 +872,7 @@ def explore( :return: A :py:mod:`folium` map containing the plotted Geometry. """ # pylint: disable=import-outside-toplevel, redefined-builtin - - if not have.folium: - raise ModuleNotFoundError( - "'folium' is required but not installed. " - "Please install it before using `.explore()`." - ) - + have.check_or_error("folium") from folium import GeoJson, Map # Create folium Map if required diff --git a/pyproject.toml b/pyproject.toml index 11e3d5ce..9dc5bfd8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,8 @@ profile = "black" [tool.pylint.messages_control] max-line-length = 120 -max-args = 7 +max-args = 15 +max-positional-arguments = 12 disable = [ "missing-function-docstring", @@ -36,4 +37,5 @@ disable = [ "ungrouped-imports", "wrong-import-position", "too-few-public-methods", + "unsubscriptable-object", ] diff --git a/tests/.pylintrc b/tests/.pylintrc index fbd67f54..e3c2d265 100644 --- a/tests/.pylintrc +++ b/tests/.pylintrc @@ -1,5 +1,5 @@ [MESSAGES CONTROL] -disable = +disable = all enable = E,F diff --git a/tests/test-env-py310.yml b/tests/test-env-py310.yml index 352c7336..2609b54f 100644 --- a/tests/test-env-py310.yml +++ b/tests/test-env-py310.yml @@ -18,11 +18,11 @@ dependencies: ## linting tools - autopep8 - autoflake - - black >=23.7.0 + - black >=25.1.0 - isort - mypy - pycodestyle - - pylint =2.17 + - pylint =3 - types-cachetools - types-certifi From 0801f6cbd19a31d466f2cd781f8dc237e836a584 Mon Sep 17 00:00:00 2001 From: Kirill Kouzoubov Date: Fri, 7 Feb 2025 01:45:40 +0000 Subject: [PATCH 8/9] maint: test env - inlcude types for shapely - use minimal matplotlib - add azure-storage-blob for az tests --- tests/test-env-py310.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test-env-py310.yml b/tests/test-env-py310.yml index 2609b54f..6608c28c 100644 --- a/tests/test-env-py310.yml +++ b/tests/test-env-py310.yml @@ -1,9 +1,11 @@ name: odc-geo-tests-py310 channels: - conda-forge + - nodefaults dependencies: - python =3.10 + - pip >=25 # odc-geo dependencies, optional also - pyproj @@ -33,13 +35,17 @@ dependencies: - geopandas - folium - ipyleaflet - - matplotlib + - matplotlib-inline - ipykernel - ipython - rioxarray + - azure-storage-blob # for docs - sphinx - sphinx_rtd_theme >=1.2.0 - sphinx-autodoc-typehints - jupyter-sphinx >=0.4.0 + - pip: + - -e ../ + - types-shapely From 9fbadf09027f32dff0f80d4bd82c49561a8759fc Mon Sep 17 00:00:00 2001 From: Kirill Kouzoubov Date: Fri, 7 Feb 2025 13:47:58 +1100 Subject: [PATCH 9/9] maint: explicitly add defaults channel this is to shut up the warning --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2b7b9b6c..91f23d32 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -67,6 +67,7 @@ jobs: if: steps.conda_cache.outputs.cache-hit != 'true' with: miniforge-version: latest + channels: conda-forge,defaults activate-environment: "" - name: Dump Conda Environment Info