Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/sweep-api-consistency-state.csv
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module,last_inspected,issue,severity_max,categories_found,notes
geotiff,2026-05-15,1922,MEDIUM,1,"Sweep 2026-05-15 (deep-sweep-api-consistency-geotiff-2026-05-15-1778854324). 1 MEDIUM Cat 1 finding fixed in this branch: write_geotiff_gpu and to_geotiff disagreed on order of max_z_error / streaming_buffer_bytes kwargs. Both kwargs are keyword-only so no functional break; drift surfaced in inspect.signature, IDE autocomplete, and Sphinx docs against the writers' explicit-parity promise. Fix reorders write_geotiff_gpu to match to_geotiff (streaming_buffer_bytes before max_z_error) and updates the docstring; gpu is the only kwarg to_geotiff has that write_geotiff_gpu does not, so the gap stays. Regression test in test_writer_kwarg_order_1922.py pins kwarg order parity and default-value parity. Prior findings (#1654 #1683 #1684 #1685 #1705 #1715 #1754 #1775 #1810 #1845-followup) all confirmed fixed. Cross-sibling return-type drift (Cat 2): write_vrt returns str while to_geotiff and write_geotiff_gpu return None -- still deferred (LOW, callers do not substitute these writers). Cross-cutting cross-module drift (chunk_size in reproject vs chunks in geotiff; target_crs vs crs) documented but not filed per sweep template (cross-cutting). cuda-validated."
geotiff,2026-05-18,2106,MEDIUM,3,"Sweep 2026-05-18 (deep-sweep-api-consistency-geotiff-2026-05-18-1779164255). 1 MEDIUM Cat 3 finding fixed in this branch: open_geotiff(max_cloud_bytes=...) was the only kwarg on the public reader/writer surface without a Python type annotation. Docstring already declared ``int or None``; the surface and the docs disagreed. Fix adds ``int | None`` to the annotation; default stays the module-internal _MAX_CLOUD_BYTES_SENTINEL. Regression test in test_open_geotiff_max_cloud_bytes_annot_2106.py pins the immediate gap and parametrises over every public reader/writer to catch future ungenerated annotations. Prior sweep findings (#1922/#1935 kwarg ordering, #2052 mask_nodata parity, #2097 GPU MinIsWhite, #2095 zero-band 3D writes, #1946 write_vrt path/vrt_path shim) all confirmed fixed. Cross-sibling return-type drift (Cat 2): write_vrt returns str while to_geotiff and write_geotiff_gpu return path which is str | BinaryIO -- inspected and still LOW (callers do not substitute writers; the return-type drift is documented in each writer's docstring). Cross-cutting cross-module drift (chunk_size in reproject vs chunks in geotiff; target_crs vs crs) documented but not filed per sweep template (cross-cutting). cuda-validated."
reproject,2026-05-10,1570,HIGH,2;5,"Filed cross-module attrs['vertical_crs'] type collision (string vs EPSG int) vs xrspatial.geotiff. Fixed in PR (TBD): reproject now writes EPSG int and preserves friendly token under vertical_datum. MEDIUM kwarg-order drift (transform_precision vs chunk_size) and missing type hints vs geotiff documented but not fixed (cosmetic, kwarg-only)."
2 changes: 1 addition & 1 deletion xrspatial/geotiff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def open_geotiff(source: str | BinaryIO, *,
chunks: int | tuple | None = None,
gpu: bool = False,
max_pixels: int | None = None,
max_cloud_bytes=_MAX_CLOUD_BYTES_SENTINEL,
max_cloud_bytes: int | None = _MAX_CLOUD_BYTES_SENTINEL, # type: ignore[assignment]
on_gpu_failure: str = _ON_GPU_FAILURE_SENTINEL,
missing_sources: str = _MISSING_SOURCES_SENTINEL,
allow_rotated: bool = False,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Regression test for #2106: every kwarg on the public read/write
entry points carries a type annotation.

The original gap: ``open_geotiff(max_cloud_bytes=...)`` had no annotation
on its kwarg, while every other kwarg on the function -- and every kwarg
on every other public reader and writer in ``xrspatial.geotiff`` -- did.
``inspect.signature``, IDE autocomplete, Sphinx, and ``mypy --strict`` all
saw a bare parameter for the only fsspec-related kwarg on the public
read entry point, despite the docstring declaring ``int or None``.

This test fixes the immediate gap (``max_cloud_bytes``) and pins every
other public reader/writer kwarg to a non-empty annotation so a future
addition cannot reopen the surface without surfacing in CI.
"""
from __future__ import annotations

import inspect

import pytest

from xrspatial.geotiff import (
open_geotiff,
read_geotiff_dask,
read_geotiff_gpu,
read_vrt,
to_geotiff,
write_geotiff_gpu,
write_vrt,
)


PUBLIC_ENTRY_POINTS = (
open_geotiff,
read_geotiff_gpu,
read_geotiff_dask,
read_vrt,
to_geotiff,
write_geotiff_gpu,
write_vrt,
)


def test_open_geotiff_max_cloud_bytes_has_type_annotation():
"""Pin the #2106 fix: the kwarg the bug named carries ``int | None``."""
sig = inspect.signature(open_geotiff)
param = sig.parameters["max_cloud_bytes"]
assert param.annotation is not inspect.Parameter.empty, (
"open_geotiff(max_cloud_bytes=...) is missing a type annotation; "
"the docstring declares ``int or None`` so the surface should match."
)
# ``xrspatial.geotiff.__init__`` uses ``from __future__ import
# annotations``, so ``param.annotation`` comes back as the source
# string. Pin the exact PEP 604 form rather than ``"int" in s and
# "None" in s`` -- the looser check would also pass on something
# like ``Mapping[int, None]``.
annotation_repr = str(param.annotation)
assert annotation_repr == "int | None", (
f"open_geotiff(max_cloud_bytes=...) annotation should be exactly "
f"``int | None`` to match the docstring; got {annotation_repr!r}"
)


@pytest.mark.parametrize("fn", PUBLIC_ENTRY_POINTS, ids=lambda f: f.__name__)
def test_public_entry_point_kwargs_have_type_annotations(fn):
"""Every kwarg on the public read/write surface carries an annotation.

Catches future regressions of the same class as #2106: a kwarg added
to one entry point without an annotation while the rest of the
signature has them.
"""
sig = inspect.signature(fn)
missing = [
name
for name, param in sig.parameters.items()
if param.annotation is inspect.Parameter.empty
]
assert missing == [], (
f"{fn.__name__} has kwargs without type annotations: {missing}. "
f"Add ``annotation`` to each so inspect.signature, IDE "
f"autocomplete, Sphinx, and mypy --strict all see the declared "
f"type. The docstring already declares the type for the kwargs "
f"in question (#2106 raised this for max_cloud_bytes)."
)
Loading