diff --git a/.claude/sweep-api-consistency-state.csv b/.claude/sweep-api-consistency-state.csv index 9657668b..fe34f964 100644 --- a/.claude/sweep-api-consistency-state.csv +++ b/.claude/sweep-api-consistency-state.csv @@ -1 +1,2 @@ -module,last_inspected,issue,severity_max,categories_found,notes +module,last_inspected,issue,severity_max,categories_found,notes +geotiff,2026-05-09,1541,HIGH,1;2;3;4;5,"PR adds 4 prod fns to __all__, real DeprecationWarning on plot_geotiff. Other findings (gpu kwarg type drift, sibling param asymmetry, write/dask_data rename, chunks default drift, write docstring drift) listed in issue #1541 for follow-up." diff --git a/xrspatial/geotiff/__init__.py b/xrspatial/geotiff/__init__.py index 4b27f245..36706b20 100644 --- a/xrspatial/geotiff/__init__.py +++ b/xrspatial/geotiff/__init__.py @@ -5,9 +5,27 @@ Public API ---------- open_geotiff(source, ...) - Read a GeoTIFF file to an xarray.DataArray. + Read a GeoTIFF, COG, or VRT file to an xarray.DataArray. Auto-dispatches + to the GPU, dask, or numpy backend based on the ``gpu`` and ``chunks`` + kwargs. +read_geotiff_gpu(source, ...) + GPU-only read returning a CuPy-backed DataArray. ``open_geotiff(..., + gpu=True)`` calls this internally; use the explicit name when you want + the strict-mode failure semantics (``gpu='strict'``) or want to bypass + auto-dispatch. +read_geotiff_dask(source, ...) + Dask-only read returning a windowed lazy DataArray. ``open_geotiff(..., + chunks=N)`` calls this internally. +read_vrt(source, ...) + Read a GDAL Virtual Raster Table (.vrt). ``open_geotiff`` routes ``.vrt`` + paths here automatically; the explicit entry point is useful for + callers that already know they have a VRT. to_geotiff(data, path, ...) - Write an xarray.DataArray as a GeoTIFF or COG. + Write an xarray.DataArray as a GeoTIFF or COG. Auto-dispatches to GPU + when the data is CuPy-backed. +write_geotiff_gpu(data, path, ...) + GPU-only writer using nvCOMP. ``to_geotiff(..., gpu=True)`` calls this + internally. write_vrt(vrt_path, source_files, ...) Generate a VRT mosaic XML from a list of GeoTIFF files. """ @@ -23,7 +41,18 @@ from ._reader import read_to_array from ._writer import write -__all__ = ['open_geotiff', 'to_geotiff', 'write_vrt'] +# All names below are part of the supported public API. ``plot_geotiff`` +# is intentionally omitted: it is deprecated in favour of ``da.xrs.plot()`` +# and emits a ``DeprecationWarning`` when called. +__all__ = [ + 'open_geotiff', + 'read_geotiff_gpu', + 'read_geotiff_dask', + 'read_vrt', + 'to_geotiff', + 'write_geotiff_gpu', + 'write_vrt', +] def _wkt_to_epsg(wkt_or_proj: str) -> int | None: @@ -2246,6 +2275,15 @@ def write_vrt(vrt_path: str, source_files: list[str], **kwargs) -> str: def plot_geotiff(da: xr.DataArray, **kwargs): """Plot a DataArray using its embedded colormap if present. - Deprecated: use ``da.xrs.plot()`` instead. + .. deprecated:: + Use ``da.xrs.plot()`` instead. ``plot_geotiff`` is a thin wrapper + kept for backward compatibility and will be removed in a future + release. """ + warnings.warn( + "plot_geotiff is deprecated and will be removed in a future " + "release. Use ``da.xrs.plot()`` instead.", + DeprecationWarning, + stacklevel=2, + ) return da.xrs.plot(**kwargs) diff --git a/xrspatial/geotiff/tests/test_features.py b/xrspatial/geotiff/tests/test_features.py index 9a5c3972..11d5abe5 100644 --- a/xrspatial/geotiff/tests/test_features.py +++ b/xrspatial/geotiff/tests/test_features.py @@ -2463,7 +2463,7 @@ def test_xrs_plot_no_palette(self, tmp_path): plt.close('all') def test_plot_geotiff_deprecated(self, tmp_path): - """plot_geotiff still works as deprecated wrapper.""" + """plot_geotiff still works but emits a DeprecationWarning.""" import matplotlib matplotlib.use('Agg') import xrspatial.accessor @@ -2477,7 +2477,8 @@ def test_plot_geotiff_deprecated(self, tmp_path): f.write(tiff_data) da = open_geotiff(path) - artist = plot_geotiff(da) + with pytest.warns(DeprecationWarning, match='plot_geotiff is deprecated'): + artist = plot_geotiff(da) assert artist is not None import matplotlib.pyplot as plt plt.close('all') @@ -2629,3 +2630,40 @@ def test_dask_chunk_tuple(self, tmp_path): result = read_geotiff_dask(path, chunks=(5, 10)) computed = result.compute() np.testing.assert_array_equal(computed.values, arr) + + +class TestPublicAPI: + """`__all__` reflects every supported public function and `from + xrspatial.geotiff import *` does not silently drop production names.""" + + def test_all_lists_supported_functions(self): + import xrspatial.geotiff as g + # Frozen list of names that callers / tests treat as part of the + # public API. If any of these gets removed or renamed, that is a + # breaking change and should go through a deprecation cycle. + expected = { + 'open_geotiff', + 'read_geotiff_gpu', + 'read_geotiff_dask', + 'read_vrt', + 'to_geotiff', + 'write_geotiff_gpu', + 'write_vrt', + } + assert set(g.__all__) == expected + + def test_star_import_brings_in_all_public_names(self): + # ``from ... import *`` honours ``__all__``; verify every entry is + # importable that way (catches typos in __all__). + ns: dict = {} + exec('from xrspatial.geotiff import *', ns) + import xrspatial.geotiff as g + for name in g.__all__: + assert name in ns, f"{name} listed in __all__ but not exported" + + def test_plot_geotiff_not_in_all_but_still_importable(self): + # plot_geotiff is intentionally omitted from __all__ (deprecated) + # but stays importable so existing user code keeps working. + import xrspatial.geotiff as g + assert 'plot_geotiff' not in g.__all__ + assert hasattr(g, 'plot_geotiff')