From f6a1ca14bc3d3cb8b29cfee23ab87158c263e698 Mon Sep 17 00:00:00 2001 From: Dewey Dunnington Date: Fri, 25 Sep 2026 09:12:58 -0500 Subject: [PATCH 1/5] remove kernel from public export, sort imports --- .../src/geoarrow/pyarrow/__init__.py | 161 +++++++++--------- 1 file changed, 76 insertions(+), 85 deletions(-) diff --git a/geoarrow-pyarrow/src/geoarrow/pyarrow/__init__.py b/geoarrow-pyarrow/src/geoarrow/pyarrow/__init__.py index aabe9eb..1d5f8c2 100644 --- a/geoarrow-pyarrow/src/geoarrow/pyarrow/__init__.py +++ b/geoarrow-pyarrow/src/geoarrow/pyarrow/__init__.py @@ -7,128 +7,119 @@ >>> import geoarrow.pyarrow as ga """ -from geoarrow.types._version import __version__, __version_tuple__ # NOQA: F401 - -from geoarrow.types import ( - GeometryType, - Dimensions, - CoordType, - EdgeType, - Encoding, - OGC_CRS84, +from geoarrow.pyarrow import _scalar +from geoarrow.pyarrow._array import array +from geoarrow.pyarrow._compute import ( + as_geoarrow, + as_wkb, + as_wkt, + box, + box_agg, + format_wkt, + infer_type_common, + make_point, + parse_all, + point_coords, + rechunk, + to_geopandas, + unique_geometry_types, + with_coord_type, + with_crs, + with_dimensions, + with_edge_type, + with_geometry_type, ) - from geoarrow.pyarrow._type import ( GeometryExtensionType, - WktType, - WkbType, - PointType, LinestringType, - PolygonType, - MultiPointType, MultiLinestringType, + MultiPointType, MultiPolygonType, - wkb, + PointType, + PolygonType, + WkbType, + WktType, + extension_type, + geometry_type_common, large_wkb, - wkb_view, - wkt, large_wkt, - wkt_view, - point, linestring, - polygon, - multipoint, multilinestring, + multipoint, multipolygon, - extension_type, - geometry_type_common, + point, + polygon, + wkb, + wkb_view, + wkt, + wkt_view, ) - +from geoarrow.types import ( + OGC_CRS84, + CoordType, + Dimensions, + EdgeType, + Encoding, + GeometryType, +) +from geoarrow.types._version import __version__, __version_tuple__ # NOQA: F401 from geoarrow.types.type_pyarrow import ( register_extension_types, unregister_extension_types, ) -from geoarrow.pyarrow._kernel import Kernel - -from geoarrow.pyarrow._array import array - -from geoarrow.pyarrow import _scalar - -from geoarrow.pyarrow._compute import ( - parse_all, - as_wkt, - as_wkb, - infer_type_common, - as_geoarrow, - format_wkt, - make_point, - unique_geometry_types, - box, - box_agg, - with_coord_type, - with_crs, - with_dimensions, - with_edge_type, - with_geometry_type, - rechunk, - point_coords, - to_geopandas, -) - __all__ = [ - "GeometryType", - "Dimensions", + "OGC_CRS84", "CoordType", + "Dimensions", "EdgeType", "Encoding", "GeometryExtensionType", - "WktType", - "WkbType", - "OGC_CRS84", - "PointType", + "GeometryType", "LinestringType", - "PolygonType", - "MultiPointType", "MultiLinestringType", + "MultiPointType", "MultiPolygonType", - "wkb", + "PointType", + "PolygonType", + "WkbType", + "WktType", + "_scalar", + "array", + "as_geoarrow", + "as_wkb", + "as_wkt", + "box", + "box_agg", + "extension_type", + "format_wkt", + "geometry_type_common", + "infer_type_common", "large_wkb", - "wkb_view", - "wkt", "large_wkt", - "wkt_view", - "point", "linestring", - "polygon", - "multipoint", + "make_point", "multilinestring", + "multipoint", "multipolygon", - "extension_type", - "geometry_type_common", - "register_extension_types", - "unregister_extension_types", - "Kernel", - "array", "parse_all", - "as_wkt", - "as_wkb", - "infer_type_common", - "as_geoarrow", - "format_wkt", - "make_point", + "point", + "point_coords", + "polygon", + "rechunk", + "register_extension_types", + "to_geopandas", "unique_geometry_types", - "box", - "box_agg", + "unregister_extension_types", "with_coord_type", "with_crs", "with_dimensions", "with_edge_type", "with_geometry_type", - "rechunk", - "point_coords", - "to_geopandas", - "_scalar", + "wkb", + "wkb_view", + "wkt", + "wkt_view", ] try: From 71141cfda1afa5fd48f62a5fc17481f1559b8007 Mon Sep 17 00:00:00 2001 From: Dewey Dunnington Date: Fri, 25 Sep 2026 09:55:22 -0500 Subject: [PATCH 2/5] update min to 3.9, reduce lint --- geoarrow-pyarrow/pyproject.toml | 2 +- .../src/geoarrow/pyarrow/__init__.py | 11 ++++-- .../src/geoarrow/pyarrow/_array.py | 21 ++++++----- .../src/geoarrow/pyarrow/_compute.py | 28 +++++++------- .../src/geoarrow/pyarrow/_kernel.py | 4 +- .../src/geoarrow/pyarrow/_scalar.py | 13 ++++--- .../src/geoarrow/pyarrow/_type.py | 11 +++--- .../src/geoarrow/pyarrow/dataset.py | 7 ++-- geoarrow-pyarrow/src/geoarrow/pyarrow/io.py | 4 +- geoarrow-pyarrow/tests/test_compute.py | 10 ++--- geoarrow-pyarrow/tests/test_dataset.py | 7 ++-- geoarrow-pyarrow/tests/test_geopandas.py | 5 +-- geoarrow-pyarrow/tests/test_io.py | 16 ++++---- geoarrow-pyarrow/tests/test_pyarrow.py | 37 +++++++++---------- 14 files changed, 88 insertions(+), 88 deletions(-) diff --git a/geoarrow-pyarrow/pyproject.toml b/geoarrow-pyarrow/pyproject.toml index e37da0d..a8ae9dd 100644 --- a/geoarrow-pyarrow/pyproject.toml +++ b/geoarrow-pyarrow/pyproject.toml @@ -22,7 +22,7 @@ dynamic = ["version"] description = "" authors = [{name = "Dewey Dunnington", email = "dewey@dunnington.ca"}] license = {text = "Apache-2.0"} -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = ["pyarrow >= 14.0.2", "geoarrow-types >= 0.3.0", "geoarrow-c >= 0.3.0"] [project.optional-dependencies] diff --git a/geoarrow-pyarrow/src/geoarrow/pyarrow/__init__.py b/geoarrow-pyarrow/src/geoarrow/pyarrow/__init__.py index 1d5f8c2..7a04e0d 100644 --- a/geoarrow-pyarrow/src/geoarrow/pyarrow/__init__.py +++ b/geoarrow-pyarrow/src/geoarrow/pyarrow/__init__.py @@ -124,12 +124,15 @@ try: register_extension_types() -except Exception as e: +except RuntimeError as exc: import warnings warnings.warn( "Failed to register one or more extension types.\n" - "If this warning appears from pytest, you may have to re-run with --import-mode=importlib.\n" - "You may also be able to run `unregister_extension_types()` and `register_extension_types()`.\n" - f"The original error was {e}" + "If this warning appears from pytest, re-run with --import-mode=importlib.\n" + "You may also try `unregister_extension_types()` followed by " + "`register_extension_types()`.\n" + f"The original error was: {exc}", + RuntimeWarning, + stacklevel=2, ) diff --git a/geoarrow-pyarrow/src/geoarrow/pyarrow/_array.py b/geoarrow-pyarrow/src/geoarrow/pyarrow/_array.py index 2f14b0b..3b3441d 100644 --- a/geoarrow-pyarrow/src/geoarrow/pyarrow/_array.py +++ b/geoarrow-pyarrow/src/geoarrow/pyarrow/_array.py @@ -1,14 +1,14 @@ -import pyarrow as pa - from geoarrow.pyarrow._kernel import Kernel, _geoarrow_c from geoarrow.pyarrow._type import ( GeometryExtensionType, - wkb, - wkt, large_wkb, large_wkt, + wkb, + wkt, ) +import pyarrow as pa + class GeometryExtensionArray(pa.ExtensionArray): def geobuffers(self): @@ -54,11 +54,12 @@ def __repr__(self): kernel = Kernel.format_wkt(self.type, max_element_size_bytes=max_width) head = kernel.push(head) tail = kernel.push(tail) - except Exception as e: - err = f"* 1 or more display values failed to parse\n* {str(e)}" + # Keep repr() usable for any failure in PyArrow or the native formatter. + except Exception as exc: # noqa: BLE001 + err = f"* 1 or more display values failed to parse\n* {exc}" type_name = type(self).__name__ super_repr = super().__repr__() - return f"{type_name}:{repr(self.type)}[{len(self)}]\n{err}\n{super_repr}" + return f"{type_name}:{self.type!r}[{len(self)}]\n{err}\n{super_repr}" head_str = [f"<{item.as_py()}>" for item in head] tail_str = [f"<{item.as_py()}>" for item in tail] @@ -74,14 +75,14 @@ def __repr__(self): tail_str = "\n".join(tail_str) items_str = f"{head_str}\n{mid}\n{tail_str}" - return f"{type_name}:{repr(self.type)}[{len(self)}]\n{items_str}".strip() + return f"{type_name}:{self.type!r}[{len(self)}]\n{items_str}".strip() class BoxArray(GeometryExtensionArray): def __repr__(self): type_name = type(self).__name__ items_str = "\n".join(repr(item.bounds) for item in self) - return f"{type_name}:{repr(self.type)}[{len(self)}]\n{items_str}".strip() + return f"{type_name}:{self.type!r}[{len(self)}]\n{items_str}".strip() def array_cls_from_name(name): @@ -124,7 +125,7 @@ def array(obj, type_=None, *args, **kwargs) -> GeometryExtensionArray: obj = obj.to_wkb(flavor="iso") # Convert obj to array if it isn't already one - if isinstance(obj, pa.Array) or isinstance(obj, pa.ChunkedArray): + if isinstance(obj, (pa.Array, pa.ChunkedArray)): arr = obj else: arr = pa.array(obj, *args, **kwargs) diff --git a/geoarrow-pyarrow/src/geoarrow/pyarrow/_compute.py b/geoarrow-pyarrow/src/geoarrow/pyarrow/_compute.py index 4546c5c..98cfb65 100644 --- a/geoarrow-pyarrow/src/geoarrow/pyarrow/_compute.py +++ b/geoarrow-pyarrow/src/geoarrow/pyarrow/_compute.py @@ -1,24 +1,24 @@ -import pyarrow as pa -import pyarrow.compute as pc - +from geoarrow.pyarrow import _type +from geoarrow.pyarrow._array import array +from geoarrow.pyarrow._kernel import Kernel, _geoarrow_c from geoarrow.types import ( - type_spec, - Encoding, CoordType, Dimensions, EdgeType, + Encoding, GeometryType, TypeSpec, + type_spec, ) -from geoarrow.pyarrow import _type -from geoarrow.pyarrow._array import array -from geoarrow.pyarrow._kernel import Kernel, _geoarrow_c + +import pyarrow as pa +import pyarrow.compute as pc def obj_as_array_or_chunked(obj_in): - if ( - isinstance(obj_in, pa.Array) or isinstance(obj_in, pa.ChunkedArray) - ) and isinstance(obj_in.type, _type.GeometryExtensionType): + if (isinstance(obj_in, (pa.Array, pa.ChunkedArray))) and isinstance( + obj_in.type, _type.GeometryExtensionType + ): return obj_in else: return array(obj_in) @@ -66,11 +66,9 @@ def parse_all(obj): obj = obj_as_array_or_chunked(obj) # Non-wkb or wkt types are a no-op here since they don't need parsing - if isinstance(obj.type, _type.WkbType) or isinstance(obj.type, _type.WktType): + if isinstance(obj.type, (_type.WkbType, _type.WktType)): push_all(Kernel.visit_void_agg, obj, result=False) - return None - def unique_geometry_types(obj): """Compute unique geometry types from ``obj`` as a struct with columns @@ -405,7 +403,7 @@ def _box_agg_point_struct(arrays): } # Apparently pyarrow reorders dict keys when inferring scalar types? - storage_type = pa.struct([(nm, pa.float64()) for nm in out_dict.keys()]) + storage_type = pa.struct([(nm, pa.float64()) for nm in out_dict]) storage_array = pa.array([out_dict], storage_type) return _type.types.box().to_pyarrow().wrap_array(storage_array)[0] diff --git a/geoarrow-pyarrow/src/geoarrow/pyarrow/_kernel.py b/geoarrow-pyarrow/src/geoarrow/pyarrow/_kernel.py index 5ab39a4..e39929c 100644 --- a/geoarrow-pyarrow/src/geoarrow/pyarrow/_kernel.py +++ b/geoarrow-pyarrow/src/geoarrow/pyarrow/_kernel.py @@ -1,9 +1,9 @@ import sys -import pyarrow as pa -from geoarrow.types import box as box_spec from geoarrow.pyarrow._type import GeometryExtensionType +from geoarrow.types import box as box_spec +import pyarrow as pa _lazy_lib = None _geoarrow_c_version = None diff --git a/geoarrow-pyarrow/src/geoarrow/pyarrow/_scalar.py b/geoarrow-pyarrow/src/geoarrow/pyarrow/_scalar.py index 5824575..c78fdbe 100644 --- a/geoarrow-pyarrow/src/geoarrow/pyarrow/_scalar.py +++ b/geoarrow-pyarrow/src/geoarrow/pyarrow/_scalar.py @@ -29,13 +29,14 @@ def __repr__(self): kernel = Kernel.format_wkt(self.type, max_element_size_bytes=max_width) array_formatted = kernel.push(self._array1()) string_formatted = array_formatted[0].as_py() - except Exception: + # Keep repr() usable for any failure in PyArrow or the native formatter. + except Exception: # noqa: BLE001 string_formatted = "" if len(string_formatted) >= max_width: string_formatted = string_formatted[: (max_width - 3)] + "..." - return f"{type(self).__name__}:{repr(self.type)}\n<{string_formatted}>" + return f"{type(self).__name__}:{self.type!r}\n<{string_formatted}>" def _array1(self): return self.type.wrap_array(pa.array([self.value])) @@ -106,19 +107,19 @@ def ymax(self) -> float: @property def zmin(self) -> Optional[float]: - return self.bounds["zmin"] if "zmin" in self.bounds else None + return self.bounds.get("zmin", None) @property def zmax(self) -> Optional[float]: - return self.bounds["zmax"] if "zmax" in self.bounds else None + return self.bounds.get("zmax", None) @property def mmin(self) -> Optional[float]: - return self.bounds["mmin"] if "mmin" in self.bounds else None + return self.bounds.get("mmin", None) @property def mmax(self) -> Optional[float]: - return self.bounds["mmax"] if "mmax" in self.bounds else None + return self.bounds.get("mmax", None) def __repr__(self) -> str: return f"BoxScalar({self.bounds})" diff --git a/geoarrow-pyarrow/src/geoarrow/pyarrow/_type.py b/geoarrow-pyarrow/src/geoarrow/pyarrow/_type.py index 0eb1782..3461187 100644 --- a/geoarrow-pyarrow/src/geoarrow/pyarrow/_type.py +++ b/geoarrow-pyarrow/src/geoarrow/pyarrow/_type.py @@ -1,20 +1,21 @@ -from typing import Iterable -import pyarrow as pa +from collections.abc import Iterable from geoarrow import types from geoarrow.types.type_pyarrow import ( GeometryExtensionType, - PointType, LinestringType, - PolygonType, - MultiPointType, MultiLinestringType, + MultiPointType, MultiPolygonType, + PointType, + PolygonType, WkbType, WktType, extension_type, ) +import pyarrow as pa + def wkb() -> WkbType: """Well-known binary with a maximum array size of 2 GB per chunk. diff --git a/geoarrow-pyarrow/src/geoarrow/pyarrow/dataset.py b/geoarrow-pyarrow/src/geoarrow/pyarrow/dataset.py index 82cc3a1..c0fed01 100644 --- a/geoarrow-pyarrow/src/geoarrow/pyarrow/dataset.py +++ b/geoarrow-pyarrow/src/geoarrow/pyarrow/dataset.py @@ -6,14 +6,15 @@ from concurrent.futures import ThreadPoolExecutor, wait +from geoarrow.c.lib import CoordType +from geoarrow.pyarrow._kernel import Kernel +from geoarrow.pyarrow._type import GeometryExtensionType, wkb, wkt + import pyarrow as _pa import pyarrow.compute as _compute import pyarrow.dataset as _ds import pyarrow.parquet as _pq import pyarrow.types as _types -from geoarrow.c.lib import CoordType -from geoarrow.pyarrow._kernel import Kernel -from geoarrow.pyarrow._type import GeometryExtensionType, wkb, wkt class GeoDataset: diff --git a/geoarrow-pyarrow/src/geoarrow/pyarrow/io.py b/geoarrow-pyarrow/src/geoarrow/pyarrow/io.py index c3b3242..fc247ed 100644 --- a/geoarrow-pyarrow/src/geoarrow/pyarrow/io.py +++ b/geoarrow-pyarrow/src/geoarrow/pyarrow/io.py @@ -9,11 +9,11 @@ import json import geoarrow.pyarrow as _ga +from geoarrow.pyarrow import _compute, _type + import pyarrow as _pa import pyarrow.parquet as _pq import pyarrow.types as _types -from geoarrow.pyarrow import _type -from geoarrow.pyarrow import _compute def read_pyogrio_table(*args, **kwargs): diff --git a/geoarrow-pyarrow/tests/test_compute.py b/geoarrow-pyarrow/tests/test_compute.py index 232d753..b089b63 100644 --- a/geoarrow-pyarrow/tests/test_compute.py +++ b/geoarrow-pyarrow/tests/test_compute.py @@ -1,14 +1,12 @@ import math -import pyarrow as pa +import geoarrow.pyarrow as ga import numpy as np +import pyarrow as pa import pytest - from geoarrow import types -import geoarrow.pyarrow as ga -import geoarrow.pyarrow._kernel as _kernel -import geoarrow.pyarrow._compute as _compute -import geoarrow.c.lib as lib +from geoarrow.c import lib +from geoarrow.pyarrow import _compute, _kernel def test_as_array_or_chunked(): diff --git a/geoarrow-pyarrow/tests/test_dataset.py b/geoarrow-pyarrow/tests/test_dataset.py index ec752b0..e2f3eb3 100644 --- a/geoarrow-pyarrow/tests/test_dataset.py +++ b/geoarrow-pyarrow/tests/test_dataset.py @@ -1,13 +1,12 @@ from tempfile import TemporaryDirectory +import geoarrow.pyarrow as ga +import geoarrow.pyarrow.dataset as gads import pyarrow as pa import pyarrow.dataset as ds import pyarrow.parquet as pq import pytest -import geoarrow.pyarrow as ga -import geoarrow.pyarrow.dataset as gads - def test_geodataset_column_name_guessing(): table = pa.table([ga.array(["POINT (0.5 1.5)"])], ["geometry"]) @@ -35,7 +34,7 @@ def test_geodataset_column_type_guessing(): with pytest.raises(TypeError): table = pa.table([[123]], ["geometry"]) geods = gads.dataset(table, geometry_columns=["geometry"]) - geods.geometry_types + _ = geods.geometry_types def test_geodataset_in_memory(): diff --git a/geoarrow-pyarrow/tests/test_geopandas.py b/geoarrow-pyarrow/tests/test_geopandas.py index 66e9609..8f1cd1f 100644 --- a/geoarrow-pyarrow/tests/test_geopandas.py +++ b/geoarrow-pyarrow/tests/test_geopandas.py @@ -1,8 +1,7 @@ -import pytest +import geoarrow.pyarrow as ga import pyarrow as pa +import pytest from geoarrow import types -import geoarrow.pyarrow as ga - geopandas = pytest.importorskip("geopandas") diff --git a/geoarrow-pyarrow/tests/test_io.py b/geoarrow-pyarrow/tests/test_io.py index 1ef5d83..f884782 100644 --- a/geoarrow-pyarrow/tests/test_io.py +++ b/geoarrow-pyarrow/tests/test_io.py @@ -1,13 +1,13 @@ -import pytest -import tempfile -import os import json +import os +import tempfile +import geoarrow.pyarrow as ga import pyarrow as pa -from pyarrow import parquet +import pytest from geoarrow import types -import geoarrow.pyarrow as ga from geoarrow.pyarrow import io +from pyarrow import parquet def test_readpyogrio_table_gpkg(): @@ -46,7 +46,7 @@ def test_write_geoparquet_table_default(): io.write_geoparquet_table(tab, temp_pq, geometry_encoding=None) tab2 = parquet.read_table(temp_pq) assert b"geo" in tab2.schema.metadata - ga.as_wkt(ga.point().wrap_array(tab2["geometry"])).to_pylist() == [ + assert ga.as_wkt(ga.point().wrap_array(tab2["geometry"])).to_pylist() == [ "POINT (0 1)" ] @@ -75,7 +75,7 @@ def test_write_geoparquet_table_geoarrow(): meta = json.loads(tab2.schema.metadata[b"geo"]) assert meta["version"] == "1.1.0" assert meta["columns"]["geometry"]["encoding"] == "point" - ga.as_wkt(ga.point().wrap_array(tab2["geometry"])).to_pylist() == [ + assert ga.as_wkt(ga.point().wrap_array(tab2["geometry"])).to_pylist() == [ "POINT (0 1)" ] @@ -107,7 +107,7 @@ def test_read_geoparquet_table_geoarrow(): tab, temp_pq, geometry_encoding=io.geoparquet_encoding_geoarrow() ) tab2 = io.read_geoparquet_table(temp_pq) - tab2["geometry"].type == ga.point() + assert tab2["geometry"].type == ga.point() def test_geoparquet_column_spec_from_type_geom_type(): diff --git a/geoarrow-pyarrow/tests/test_pyarrow.py b/geoarrow-pyarrow/tests/test_pyarrow.py index 589c478..fd116f1 100644 --- a/geoarrow-pyarrow/tests/test_pyarrow.py +++ b/geoarrow-pyarrow/tests/test_pyarrow.py @@ -1,16 +1,15 @@ -import sys import re +import sys from math import inf -import pyarrow as pa +import geoarrow.pyarrow as ga import numpy as np +import pyarrow as pa import pytest - -import geoarrow.c.lib as lib from geoarrow import types -import geoarrow.pyarrow as ga -import geoarrow.pyarrow._type as _type -import geoarrow.pyarrow._array as _array +from geoarrow.c import lib +from geoarrow.pyarrow import _array, _type +from geoarrow.pyarrow._kernel import Kernel def test_version(): @@ -224,17 +223,17 @@ def test_scalar_repr(): def test_kernel_void(): with pytest.raises(TypeError): - kernel = ga.Kernel.void(pa.int32()) + kernel = Kernel.void(pa.int32()) kernel.push(5) array = ga.array(["POINT (30 10)"]) - kernel = ga.Kernel.void(array.type) + kernel = Kernel.void(array.type) out = kernel.push(array) assert out.type == pa.null() assert len(out) == 1 array = ga.array(["POINT (30 10)", "POINT (31 11)"]) - kernel = ga.Kernel.void_agg(array.type) + kernel = Kernel.void_agg(array.type) assert kernel.push(array) is None out = kernel.finish() assert out.type == pa.null() @@ -243,14 +242,14 @@ def test_kernel_void(): def test_kernel_as(): array = ga.array(["POINT (30 10)"], ga.wkt().with_crs(types.OGC_CRS84)) - kernel = ga.Kernel.as_wkt(array.type) + kernel = Kernel.as_wkt(array.type) out = kernel.push(array) assert out.type.extension_name == "geoarrow.wkt" assert out.type.crs.to_json_dict() == types.OGC_CRS84.to_json_dict() assert isinstance(out, _array.GeometryExtensionArray) array = ga.array(["POINT (30 10)"], ga.wkt().with_crs(types.OGC_CRS84)) - kernel = ga.Kernel.as_wkb(array.type) + kernel = Kernel.as_wkb(array.type) out = kernel.push(array) assert out.type.extension_name == "geoarrow.wkb" assert out.type.crs.to_json_dict() == types.OGC_CRS84.to_json_dict() @@ -261,7 +260,7 @@ def test_kernel_as(): assert out[0].as_py() == wkb_item array = ga.array(["POINT (30 10)"], ga.wkt().with_crs(types.OGC_CRS84)) - kernel = ga.Kernel.as_geoarrow(array.type, 1) + kernel = Kernel.as_geoarrow(array.type, 1) out = kernel.push(array) assert out.type.extension_name == "geoarrow.point" assert out.type.crs.to_json_dict() == types.OGC_CRS84.to_json_dict() @@ -270,7 +269,7 @@ def test_kernel_as(): def test_kernel_format(): array = ga.array(["POINT (30.12345 10.12345)"]) - kernel = ga.Kernel.format_wkt(array.type, precision=3, max_element_size_bytes=15) + kernel = Kernel.format_wkt(array.type, precision=3, max_element_size_bytes=15) out = kernel.push(array) assert out.type == pa.string() @@ -279,7 +278,7 @@ def test_kernel_format(): def test_kernel_unique_geometry_types(): array = ga.array(["POINT (0 1)", "POINT (30 10)", "LINESTRING Z (0 1 2, 3 4 5)"]) - kernel = ga.Kernel.unique_geometry_types_agg(array.type) + kernel = Kernel.unique_geometry_types_agg(array.type) kernel.push(array) out = kernel.finish() @@ -290,7 +289,7 @@ def test_kernel_unique_geometry_types(): def test_kernel_box(): array = ga.array(["POINT (0 1)", "POINT (30 10)", "LINESTRING EMPTY"]) - kernel = ga.Kernel.box(array.type) + kernel = Kernel.box(array.type) out = kernel.push(array) assert out[0].as_py() == {"xmin": 0, "xmax": 0, "ymin": 1, "ymax": 1} @@ -300,7 +299,7 @@ def test_kernel_box(): def test_kernel_box_agg(): array = ga.array(["POINT (0 1)", "POINT (30 10)", "LINESTRING EMPTY"]) - kernel = ga.Kernel.box_agg(array.type) + kernel = Kernel.box_agg(array.type) kernel.push(array) out = kernel.finish() @@ -309,14 +308,14 @@ def test_kernel_box_agg(): def test_kernel_visit_void(): array = ga.array(["POINT (30 10)"], ga.wkt()) - kernel = ga.Kernel.visit_void_agg(array.type) + kernel = Kernel.visit_void_agg(array.type) assert kernel.push(array) is None out = kernel.finish() assert out.type == pa.null() assert len(out) == 1 array = ga.array(["POINT (30 10)", "NOT VALID WKT AT ALL"], ga.wkt()) - kernel = ga.Kernel.visit_void_agg(array.type) + kernel = Kernel.visit_void_agg(array.type) with pytest.raises(lib.GeoArrowCException): kernel.push(array) out = kernel.finish() From 586f282e65744242fa5912adedfd4a54ddd69ac9 Mon Sep 17 00:00:00 2001 From: Dewey Dunnington Date: Fri, 25 Sep 2026 10:15:00 -0500 Subject: [PATCH 3/5] update matrix --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 75ed9c9..8a1cef9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -17,7 +17,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] steps: - uses: actions/checkout@v4 From 76e045447ed1c7b194edaa742f0a41262d9b62dd Mon Sep 17 00:00:00 2001 From: Dewey Dunnington Date: Fri, 25 Sep 2026 10:19:23 -0500 Subject: [PATCH 4/5] update some versions --- .github/workflows/docs.yaml | 6 +++--- .github/workflows/pre-commit.yaml | 6 +++--- .github/workflows/test.yaml | 16 ++++++++-------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 1304bab..f7fc29f 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -13,16 +13,16 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - uses: r-lib/actions/setup-pandoc@v2 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v7 with: - python-version: '3.11' + python-version: '3.14' cache: 'pip' - name: Install docs requirements diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 51e8052..5ebf203 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -16,13 +16,13 @@ jobs: name: "pre-commit" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 with: fetch-depth: 0 persist-credentials: false - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v7 - name: pre-commit (cache) - uses: actions/cache@v3 + uses: actions/cache@v6 with: path: ~/.cache/pre-commit key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8a1cef9..171dbc9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -20,12 +20,12 @@ jobs: python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} cache: 'pip' @@ -58,19 +58,19 @@ jobs: pytest geoarrow-pandas/tests -v -s - name: Run doctests (geoarrow-pyarrow) - if: success() && matrix.python-version == '3.13' + if: success() && matrix.python-version == '3.14' run: | cd geoarrow-pyarrow pytest --pyargs geoarrow.pyarrow --doctest-modules --import-mode=importlib - name: Run doctests (geoarrow-types) - if: success() && matrix.python-version == '3.13' + if: success() && matrix.python-version == '3.14' run: | cd geoarrow-types pytest --pyargs geoarrow.types --doctest-modules --import-mode=importlib - name: Run doctests (geoarrow-pandas) - if: success() && matrix.python-version == '3.13' + if: success() && matrix.python-version == '3.14' run: | cd geoarrow-pandas pytest --pyargs geoarrow.pandas --doctest-modules --import-mode=importlib @@ -121,14 +121,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v7 with: - python-version: '3.13' + python-version: '3.14' cache: 'pip' - name: Install (geoarrow-pyarrow) From 5702d343642793a9ea441fd43bf8d34e3d45d752 Mon Sep 17 00:00:00 2001 From: Dewey Dunnington Date: Fri, 25 Sep 2026 10:38:35 -0500 Subject: [PATCH 5/5] do the other packages too --- .github/workflows/test.yaml | 40 ------------ geoarrow-pandas/pyproject.toml | 2 +- geoarrow-pandas/src/geoarrow/pandas/lib.py | 52 +++++++++++++--- .../tests/test_geoarrow_pandas_suite.py | 23 +++++-- geoarrow-types/pyproject.toml | 2 +- geoarrow-types/src/geoarrow/types/__init__.py | 62 +++++++++---------- .../src/geoarrow/types/constants.py | 4 +- geoarrow-types/src/geoarrow/types/crs.py | 12 +--- .../src/geoarrow/types/type_pyarrow.py | 19 +++--- .../src/geoarrow/types/type_spec.py | 8 +-- 10 files changed, 107 insertions(+), 117 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 171dbc9..3d58772 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -75,46 +75,6 @@ jobs: cd geoarrow-pandas pytest --pyargs geoarrow.pandas --doctest-modules --import-mode=importlib - # This is a test of geoarrow-types on Python 3.7 (which implies pyarrow 12 - # since this is the last supported version there). Python 3.7 is still the - # runtime available on some hosted platforms (e.g., it is the minimum required - # version for apache-sedona Python) - oldest-supported: - runs-on: ubuntu-latest - container: - image: python:3.7 - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - fetch-tags: true - - - name: Check git setup - run: | - git config --global --add safe.directory "$(pwd)" - git describe --long --match='geoarrow-types-*' - - # setuptools_scm available for Python 3.7 does not support version_file - # (it can still be installed on Python 3.7, it just can't be built there - # without this modification) - - name: Patch pyproject.toml - run: | - cd geoarrow-types - sed -i.bak '/^version_file/d' pyproject.toml - echo '__version__ = "0.0.0"' > src/geoarrow/types/_version.py - echo '__version_tuple__ = (0, 0, 0)' >> src/geoarrow/types/_version.py - - - name: Install (geoarrow-types) - run: | - pip install --upgrade setuptools setuptools_scm - cd geoarrow-types - pip install ".[test]" - - - name: Run tests (geoarrow-types) - run: | - pytest geoarrow-types/tests -v -s - coverage: needs: [test] diff --git a/geoarrow-pandas/pyproject.toml b/geoarrow-pandas/pyproject.toml index 80fd251..4660c57 100644 --- a/geoarrow-pandas/pyproject.toml +++ b/geoarrow-pandas/pyproject.toml @@ -22,7 +22,7 @@ dynamic = ["version"] description = "" authors = [{name = "Dewey Dunnington", email = "dewey@dunnington.ca"}] license = {text = "Apache-2.0"} -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = ["geoarrow-pyarrow", "pandas", "pyarrow"] [project.optional-dependencies] diff --git a/geoarrow-pandas/src/geoarrow/pandas/lib.py b/geoarrow-pandas/src/geoarrow/pandas/lib.py index 2042458..01378db 100644 --- a/geoarrow-pandas/src/geoarrow/pandas/lib.py +++ b/geoarrow-pandas/src/geoarrow/pandas/lib.py @@ -1,9 +1,11 @@ import re as _re -import pandas as _pd -import pyarrow as _pa -import numpy as _np -from geoarrow.types import TypeSpec, type_spec, Encoding + import geoarrow.pyarrow as _ga +import numpy as _np +import pyarrow as _pa +from geoarrow.types import Encoding, TypeSpec, type_spec + +import pandas as _pd class GeoArrowExtensionScalar(bytes): @@ -79,6 +81,10 @@ class GeoArrowExtensionArray(_pd.api.extensions.ExtensionArray): not instantiate this class directly. """ + # Added to pandas' ExtensionArray base class in pandas 3.0. Defining it + # here keeps the behavior consistent when using older pandas versions. + _readonly = False + def __init__(self, obj, type=None): if type is not None: self._dtype = GeoArrowExtensionDtype(type) @@ -105,10 +111,12 @@ def __getitem__(self, item): else: return None elif isinstance(item, slice): - return GeoArrowExtensionArray(self._data[item]) - elif isinstance(item, list): - return self.take(item) - elif hasattr(item, "dtype") and item.dtype.kind == "i": + result = GeoArrowExtensionArray(self._data[item]) + result._readonly = self._readonly + return result + elif ( + isinstance(item, list) or hasattr(item, "dtype") and item.dtype.kind == "i" + ): return self.take(item) elif hasattr(item, "dtype") and item.dtype.kind == "b": if len(item) != len(self): @@ -127,6 +135,32 @@ def __getitem__(self, item): "only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices" ) + def __setitem__(self, key, value): + if self._readonly: + raise ValueError("Cannot modify read-only array") + + values = list(self) + indices = _np.arange(len(self))[key] + + if _np.isscalar(indices): + values[int(indices)] = value + else: + indices = indices.tolist() + if _pd.api.types.is_scalar(value): + for index in indices: + values[index] = value + else: + replacements = list(value) + if len(replacements) != len(indices): + raise ValueError( + "Length of values does not match length of indexer" + ) + for index, replacement in zip(indices, replacements): + values[index] = replacement + + replacement = self._from_sequence(values, dtype=self.dtype) + self._data = replacement._data + def __contains__(self, item: object): for scalar in self: if scalar == item: @@ -362,7 +396,7 @@ def construct_from_string(cls, string): ) from e def __repr__(self): - return f"{type(self).__name__}({repr(self._parent)})" + return f"{type(self).__name__}({self._parent!r})" def __str__(self): ext_name = self._parent.extension_name diff --git a/geoarrow-pandas/tests/test_geoarrow_pandas_suite.py b/geoarrow-pandas/tests/test_geoarrow_pandas_suite.py index 4903823..560b7c5 100644 --- a/geoarrow-pandas/tests/test_geoarrow_pandas_suite.py +++ b/geoarrow-pandas/tests/test_geoarrow_pandas_suite.py @@ -1,13 +1,14 @@ -import pytest -from pandas.tests.extension import base -import geoarrow.pandas as gapd -import geoarrow.pyarrow as ga import operator +import geoarrow.pandas as gapd +import geoarrow.pyarrow as ga +import pandas as pd +import pytest from pandas import ( Series, options, ) +from pandas.tests.extension import base @pytest.fixture @@ -24,7 +25,9 @@ def data(): * data[0] and data[1] should both be non missing * data[0] and data[1] should not be equal """ - strings = [f"POINT ({i} {i + 1})" for i in range(100)] + # pandas 3.0 changed the required fixture length from 100 to 10. + fixture_size = 10 if int(pd.__version__.split(".")[0]) >= 3 else 100 + strings = [f"POINT ({i} {i + 1})" for i in range(fixture_size)] return gapd.GeoArrowExtensionArray(ga.array(strings)) @@ -209,6 +212,16 @@ def using_copy_on_write() -> bool: return options.mode.copy_on_write and options.mode.data_manager == "block" +@pytest.fixture(params=[True, False]) +def using_nan_is_na(request): + """Whether pandas considers NaN and NA equivalent.""" + if int(pd.__version__.split(".")[0]) >= 3: + with pd.option_context("future.distinguish_nan_and_na", not request.param): + yield request.param + else: + yield request.param + + class TestGeoArrowDtype(base.BaseDtypeTests): pass diff --git a/geoarrow-types/pyproject.toml b/geoarrow-types/pyproject.toml index 03a0d49..82d3d79 100644 --- a/geoarrow-types/pyproject.toml +++ b/geoarrow-types/pyproject.toml @@ -22,7 +22,7 @@ dynamic = ["version"] description = "" authors = [{name = "Dewey Dunnington", email = "dewey@dunnington.ca"}] license = {text = "Apache-2.0"} -requires-python = ">=3.7" +requires-python = ">=3.9" dependencies = [] [project.optional-dependencies] diff --git a/geoarrow-types/src/geoarrow/types/__init__.py b/geoarrow-types/src/geoarrow/types/__init__.py index 6cbea64..fca9484 100644 --- a/geoarrow-types/src/geoarrow/types/__init__.py +++ b/geoarrow-types/src/geoarrow/types/__init__.py @@ -1,57 +1,53 @@ from geoarrow.types._version import __version__, __version_tuple__ # NOQA: F401 - from geoarrow.types.constants import ( - Encoding, - GeometryType, - Dimensions, CoordType, + Dimensions, EdgeType, + Encoding, + GeometryType, ) - -from geoarrow.types.crs import Crs, OGC_CRS84 - +from geoarrow.types.crs import OGC_CRS84, Crs from geoarrow.types.type_spec import ( TypeSpec, - type_spec, - wkb, + box, + geoarrow, large_wkb, - wkt, large_wkt, - wkb_view, - wkt_view, - box, - point, linestring, - polygon, - multipoint, multilinestring, + multipoint, multipolygon, - geoarrow, + point, + polygon, + type_spec, + wkb, + wkb_view, + wkt, + wkt_view, ) - __all__ = [ - "Encoding", - "GeometryType", - "Dimensions", + "OGC_CRS84", "CoordType", - "EdgeType", "Crs", - "OGC_CRS84", + "Dimensions", + "EdgeType", + "Encoding", + "GeometryType", "TypeSpec", - "type_spec", - "wkb", + "box", + "geoarrow", "large_wkb", - "wkt", "large_wkt", - "wkb_view", - "wkt_view", - "geoarrow", - "box", - "point", "linestring", - "polygon", - "multipoint", "multilinestring", + "multipoint", "multipolygon", + "point", + "polygon", + "type_spec", + "wkb", + "wkb_view", + "wkt", + "wkt_view", ] diff --git a/geoarrow-types/src/geoarrow/types/constants.py b/geoarrow-types/src/geoarrow/types/constants.py index 56c37b6..fd35555 100644 --- a/geoarrow-types/src/geoarrow/types/constants.py +++ b/geoarrow-types/src/geoarrow/types/constants.py @@ -53,9 +53,7 @@ def _coalesce_unspecified2(cls, lhs, rhs): def _common2(cls, lhs, rhs): if lhs == cls.UNSPECIFIED: return rhs - elif rhs == cls.UNSPECIFIED: - return lhs - elif lhs == rhs: + elif rhs == cls.UNSPECIFIED or lhs == rhs: return lhs elif (lhs, rhs) in _VALUE_COMMON_HELPER: return _VALUE_COMMON_HELPER[(lhs, rhs)] diff --git a/geoarrow-types/src/geoarrow/types/crs.py b/geoarrow-types/src/geoarrow/types/crs.py index e0e422e..6c450a9 100644 --- a/geoarrow-types/src/geoarrow/types/crs.py +++ b/geoarrow-types/src/geoarrow/types/crs.py @@ -1,13 +1,7 @@ -from copy import deepcopy import json -from typing import Union, Mapping, Optional - -try: - from typing import Protocol -except ImportError: - - class Protocol: - pass +from collections.abc import Mapping +from copy import deepcopy +from typing import Optional, Protocol, Union class Crs(Protocol): diff --git a/geoarrow-types/src/geoarrow/types/type_pyarrow.py b/geoarrow-types/src/geoarrow/types/type_pyarrow.py index 29fa09c..74b50cd 100644 --- a/geoarrow-types/src/geoarrow/types/type_pyarrow.py +++ b/geoarrow-types/src/geoarrow/types/type_pyarrow.py @@ -1,17 +1,16 @@ from contextlib import contextmanager from typing import Optional -from geoarrow.types.type_spec import TypeSpec, type_spec -from geoarrow.types.crs import Crs +import pyarrow as pa from geoarrow.types.constants import ( - Encoding, - GeometryType, - Dimensions, CoordType, + Dimensions, EdgeType, + Encoding, + GeometryType, ) - -import pyarrow as pa +from geoarrow.types.crs import Crs +from geoarrow.types.type_spec import TypeSpec, type_spec from pyarrow import types as pa_types @@ -425,8 +424,6 @@ def extension_types_registered() -> bool: Returns ``True`` if the GeoArrow extension types were registered by this module or ``False`` otherwise. """ - global _extension_types_registered - return _extension_types_registered @@ -925,8 +922,6 @@ def _generate_union_type_id_mapping(): def _add_union_types_to_native_storage_types(): - global _NATIVE_STORAGE_TYPES - for coord_type in ALL_COORD_TYPES: for dimension in ALL_DIMENSIONS: _NATIVE_STORAGE_TYPES[ @@ -970,7 +965,7 @@ def _spec_short_repr(spec, ext_name): type_prefix = "" if spec.crs is not None: - crs = f" <{repr(spec.crs)}>" + crs = f" <{spec.crs!r}>" else: crs = "" diff --git a/geoarrow-types/src/geoarrow/types/type_spec.py b/geoarrow-types/src/geoarrow/types/type_spec.py index c8af38b..6a8d692 100644 --- a/geoarrow-types/src/geoarrow/types/type_spec.py +++ b/geoarrow-types/src/geoarrow/types/type_spec.py @@ -1,15 +1,15 @@ import json from typing import NamedTuple, Optional +from geoarrow.types import crs from geoarrow.types.constants import ( - Encoding, - GeometryType, + CoordType, Dimensions, EdgeType, - CoordType, + Encoding, + GeometryType, ) from geoarrow.types.crs import Crs -from geoarrow.types import crs _UNSPECIFIED_CRS_ARG = crs.UnspecifiedCrs()