From 7b9c4c44546cf818364aaa73846b3e380373f199 Mon Sep 17 00:00:00 2001 From: Mike Taves Date: Wed, 12 Nov 2025 13:38:36 +1300 Subject: [PATCH] Use ruff to format and check code; remove isort --- examples/benchmark.py | 1 + pyproject.toml | 25 +++++++++++++++++++++---- src/rasterstats/__init__.py | 6 +++--- src/rasterstats/cli.py | 5 +++-- src/rasterstats/io.py | 4 ++-- src/rasterstats/main.py | 8 +++++--- src/rasterstats/point.py | 6 +++--- src/rasterstats/utils.py | 4 +--- tests/test_cli.py | 1 + tests/test_io.py | 12 ++++++------ tests/test_zonal.py | 5 ++++- 11 files changed, 50 insertions(+), 27 deletions(-) diff --git a/examples/benchmark.py b/examples/benchmark.py index 90b9eb7..34f6227 100644 --- a/examples/benchmark.py +++ b/examples/benchmark.py @@ -15,6 +15,7 @@ 1bc8711 130.93s MacBook Pro (Retina, 15-inch, Mid 2014) 2.2GHz i7, 16GB RAM 2277962 80.68s MacBook Pro (Retina, 15-inch, Mid 2014) 2.2GHz i7, 16GB RAM """ + import time from rasterstats import zonal_stats diff --git a/pyproject.toml b/pyproject.toml index 8a04d3c..23c76b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,11 @@ dependencies = [ progress = [ "tqdm" ] +docs = [ + "numpydoc", + "sphinx", + "sphinx-rtd-theme", +] test = [ "coverage", "geopandas", @@ -52,7 +57,7 @@ test = [ ] dev = [ "rasterstats[test]", - "numpydoc", + "ruff", "twine", ] @@ -81,6 +86,18 @@ version = {attr = "rasterstats._version.__version__"} [tool.hatch.version] path = "src/rasterstats/_version.py" -[tool.isort] -profile = "black" -known_first_party = ["rasterstats"] +[tool.ruff.lint] +select = [ + "E", # pycodestyle + "F", # Pyflakes + "I", # isort + "RUF", # Ruff-specific rules + "UP", # pyupgrade +] +ignore = [ + "RUF005", # Consider iterable unpacking instead of concatenation +] + +[tool.ruff] +# TODO: files in docs/notebooks/ use old versions and are incompatible with modern tools +extend-exclude = ["*.ipynb"] diff --git a/src/rasterstats/__init__.py b/src/rasterstats/__init__.py index 202b000..8133fb4 100644 --- a/src/rasterstats/__init__.py +++ b/src/rasterstats/__init__.py @@ -6,10 +6,10 @@ __all__ = [ "__version__", - "gen_zonal_stats", + "cli", "gen_point_query", + "gen_zonal_stats", + "point_query", "raster_stats", "zonal_stats", - "point_query", - "cli", ] diff --git a/src/rasterstats/cli.py b/src/rasterstats/cli.py index b318f01..0a413bf 100644 --- a/src/rasterstats/cli.py +++ b/src/rasterstats/cli.py @@ -43,8 +43,9 @@ def zonalstats( The input arguments to zonalstats should be valid GeoJSON Features. (see cligj) - The output GeoJSON will be mostly unchanged but have additional properties per feature - describing the summary statistics (min, max, mean, etc.) of the underlying raster dataset. + The output GeoJSON will be mostly unchanged but have additional properties per + feature describing the summary statistics (min, max, mean, etc.) of the underlying + raster dataset. The raster is specified by the required -r/--raster argument. diff --git a/src/rasterstats/io.py b/src/rasterstats/io.py index 72ca31f..7ae8d87 100644 --- a/src/rasterstats/io.py +++ b/src/rasterstats/io.py @@ -86,7 +86,7 @@ def parse_feature(obj): except (AssertionError, TypeError): pass - raise ValueError("Can't parse %s as a geojson Feature object" % obj) + raise ValueError(f"Can't parse {obj} as a geojson Feature object") def read_features(obj, layer=0): @@ -302,7 +302,7 @@ def read(self, bounds=None, window=None, masked=False, boundless=True): masked: boolean return a masked numpy array, default: False boundless: boolean - allow window/bounds that extend beyond the dataset’s extent, default: True + allow window/bounds that extend beyond the dataset's extent, default: True partially or completely filled arrays will be returned as appropriate. Returns diff --git a/src/rasterstats/main.py b/src/rasterstats/main.py index 11c6241..43d9dc1 100644 --- a/src/rasterstats/main.py +++ b/src/rasterstats/main.py @@ -25,7 +25,7 @@ def raster_stats(*args, **kwargs): """Deprecated. Use zonal_stats instead.""" warnings.warn( - "'raster_stats' is an alias to 'zonal_stats'" " and will disappear in 1.0", + "'raster_stats' is an alias to 'zonal_stats' and will disappear in 1.0", DeprecationWarning, ) return zonal_stats(*args, **kwargs) @@ -43,7 +43,9 @@ def zonal_stats(*args, **kwargs): if progress: if tqdm is None: raise ValueError( - "You specified progress=True, but tqdm is not installed in the environment. You can do pip install rasterstats[progress] to install tqdm!" + "You specified progress=True, but tqdm is not installed in " + "the environment. " + "You can do pip install rasterstats[progress] to install tqdm!" ) stats = gen_zonal_stats(*args, **kwargs) total = len(args[0]) @@ -140,7 +142,7 @@ def gen_zonal_stats( Use with `prefix` to ensure unique and meaningful property names. boundless: boolean - Allow features that extend beyond the raster dataset’s extent, default: True + Allow features that extend beyond the raster dataset's extent, default: True Cells outside dataset extents are treated as nodata. Returns diff --git a/src/rasterstats/point.py b/src/rasterstats/point.py index 43bc7eb..f262c6c 100644 --- a/src/rasterstats/point.py +++ b/src/rasterstats/point.py @@ -15,7 +15,7 @@ def point_window_unitxy(x, y, affine): ((row1, row2), (col1, col2)), (unitx, unity) """ fcol, frow = ~affine * (x, y) - r, c = int(round(frow)), int(round(fcol)) + r, c = round(frow), round(fcol) # The new source window for our 2x2 array new_win = ((r - 1, r + 1), (c - 1, c + 1)) @@ -50,7 +50,7 @@ def bilinear(arr, x, y): if hasattr(arr, "count") and arr.count() != 4: # a masked array with at least one nodata # fall back to nearest neighbor - val = arr[int(round(1 - y)), int(round(x))] + val = arr[round(1 - y), round(x)] if val is masked: return None else: @@ -158,7 +158,7 @@ def gen_point_query( point query values appended as additional properties. boundless: boolean - Allow features that extend beyond the raster dataset’s extent, default: True + Allow features that extend beyond the raster dataset's extent, default: True Cells outside dataset extents are treated as nodata. Returns diff --git a/src/rasterstats/utils.py b/src/rasterstats/utils.py index a00a27d..57fb11f 100644 --- a/src/rasterstats/utils.py +++ b/src/rasterstats/utils.py @@ -93,9 +93,7 @@ def check_stats(stats, categorical): if x.startswith("percentile_"): get_percentile(x) elif x not in VALID_STATS: - raise ValueError( - "Stat `%s` not valid; " "must be one of \n %r" % (x, VALID_STATS) - ) + raise ValueError(f"Stat {x!r} not valid; must be one of \n {VALID_STATS}") run_count = False if categorical or "majority" in stats or "minority" in stats or "unique" in stats: diff --git a/tests/test_cli.py b/tests/test_cli.py index ccd7314..1fd9418 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -11,6 +11,7 @@ data_dir = Path(__file__).parent / "data" + def test_cli_feature(): raster = str(data_dir / "slope.tif") vector = str(data_dir / "feature.geojson") diff --git a/tests/test_io.py b/tests/test_io.py index 824a503..3199658 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -8,10 +8,10 @@ from shapely.geometry import shape from rasterstats.io import ( # todo parse_feature - fiona_generator, Raster, boundless_array, bounds_window, + fiona_generator, read_featurecollection, read_features, rowcol, @@ -47,7 +47,7 @@ def _test_read_features(indata): def _test_read_features_single(indata): # single (first target geom) - geom = shape(list(read_features(indata))[0]["geometry"]) + geom = shape(next(iter(read_features(indata)))["geometry"]) assert geom.equals_exact(target_geoms[0], eps) @@ -280,9 +280,9 @@ def test_Raster(): r2 = Raster(arr, affine, nodata, band=1).read(bounds) with pytest.raises(ValueError): - r3 = Raster(arr, affine, nodata, band=1).read() + Raster(arr, affine, nodata, band=1).read() with pytest.raises(ValueError): - r4 = Raster(arr, affine, nodata, band=1).read(bounds=1, window=1) + Raster(arr, affine, nodata, band=1).read(bounds=1, window=1) # If the abstraction is correct, the arrays are equal assert np.array_equal(r1.array, r2.array) @@ -301,7 +301,7 @@ def test_Raster_boundless_disabled(): # rasterio src fails outside extent with pytest.raises(ValueError): - r1 = Raster(raster, band=1).read(outside_bounds, boundless=False) + Raster(raster, band=1).read(outside_bounds, boundless=False) # rasterio src works inside extent r2 = Raster(raster, band=1).read(bounds, boundless=False) @@ -316,7 +316,7 @@ def test_Raster_boundless_disabled(): # ndarray src fails outside extent with pytest.raises(ValueError): - r4 = Raster(arr, affine, nodata, band=1).read(outside_bounds, boundless=False) + Raster(arr, affine, nodata, band=1).read(outside_bounds, boundless=False) # If the abstraction is correct, the arrays are equal assert np.array_equal(r2.array, r3.array) diff --git a/tests/test_zonal.py b/tests/test_zonal.py index 888d2ba..c906a53 100644 --- a/tests/test_zonal.py +++ b/tests/test_zonal.py @@ -307,6 +307,7 @@ def mymean_prop(x, prop): for i in range(len(stats)): assert stats[i]["mymean_prop"] == stats[i]["mean"] * (i + 1) + def test_add_stats_prop_and_array(): polygons = data_dir / "polygons.shp" @@ -315,7 +316,9 @@ def mymean_prop_and_array(x, prop, rv_array): assert rv_array is not None return np.ma.mean(x) * prop["id"] - stats = zonal_stats(polygons, raster, add_stats={"mymean_prop_and_array": mymean_prop_and_array}) + stats = zonal_stats( + polygons, raster, add_stats={"mymean_prop_and_array": mymean_prop_and_array} + ) for i in range(len(stats)): assert stats[i]["mymean_prop_and_array"] == stats[i]["mean"] * (i + 1)