Skip to content

Typed warnings + XRSPATIAL_GEOTIFF_STRICT for silent geotiff fallbacks (#1662)#1667

Merged
brendancol merged 4 commits into
mainfrom
issue-1662
May 12, 2026
Merged

Typed warnings + XRSPATIAL_GEOTIFF_STRICT for silent geotiff fallbacks (#1662)#1667
brendancol merged 4 commits into
mainfrom
issue-1662

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Summary

Closes #1662.

  • Each except Exception: return None site flagged in the Replace silent except-return-None in geotiff with typed exceptions + strict mode #1662 audit now emits a GeoTIFFFallbackWarning carrying the original exception type and message. Affected helpers: _wkt_to_epsg, _epsg_to_wkt, the VRT source-skip loop in _vrt.py, and eight GPU helpers in _gpu_decode.py (GDS, nvCOMP kvikio + ctypes, nvJPEG decode + encode, nvCOMP from-device-bufs, nvCOMP batch compress, nvJPEG2000 decode + encode).
  • New XRSPATIAL_GEOTIFF_STRICT=1 env var (read by _geotiff_strict_mode()) re-raises instead of warning. The same env var also promotes read_geotiff_gpu(on_gpu_failure='auto') to strict so CI can fail on silent GPU fallbacks without touching every call site.
  • 21 new tests in test_strict_mode_1662.py pin the warn-vs-raise contract for each entry point.
  • Docs reference updated with a "Strict mode" section pointing at the env var.

Out of scope:

  • The inner except Exception: pass cleanup in _try_kvikio_read_tiles (a failed cupy.cuda.Device().synchronize() during error recovery) stays broad; there is no useful next step.
  • The early ImportError guards on optional deps still return None quietly. Those are real "feature not available" branches, not silent fast-path failures.

Test plan

  • pytest xrspatial/geotiff/tests/test_strict_mode_1662.py -v (21 pass)
  • pytest xrspatial/geotiff/tests/ (1364 pass, 3 skipped; one unrelated matplotlib deepcopy recursion in test_features.py excluded)
  • CI matrix
  • Smoke XRSPATIAL_GEOTIFF_STRICT=1 pytest xrspatial/geotiff/tests/ on a box with GPU + kvikio to confirm no spurious strict-mode failures

@github-actions github-actions Bot added the performance PR touches performance-sensitive code label May 12, 2026
@brendancol brendancol requested a review from Copilot May 12, 2026 13:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses issue #1662 by making previously-silent GeoTIFF “fallback” paths visible via a typed warning (GeoTIFFFallbackWarning) and adding an opt-in strict mode (XRSPATIAL_GEOTIFF_STRICT) that converts those fallbacks into raised exceptions (including promoting read_geotiff_gpu(on_gpu_failure='auto') to strict behavior).

Changes:

  • Introduces GeoTIFFFallbackWarning plus _geotiff_strict_mode() and applies warn-vs-raise behavior to _wkt_to_epsg, _epsg_to_wkt, VRT source skips, and multiple GPU helper fallbacks.
  • Adds _warn_or_raise_gpu_fallback() to standardize GPU helper fallback reporting and integrates strict mode into read_geotiff_gpu failure handling.
  • Adds a new regression test module and updates docs with a “Strict mode” section describing XRSPATIAL_GEOTIFF_STRICT.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
xrspatial/geotiff/__init__.py Adds strict-mode env var helper + new GeoTIFFFallbackWarning; strict mode also forces GPU failure policy to raise.
xrspatial/geotiff/_geotags.py Emits GeoTIFFFallbackWarning (or raises in strict mode) when EPSG→WKT resolution fails.
xrspatial/geotiff/_vrt.py Emits GeoTIFFFallbackWarning (or raises in strict mode) when a VRT source tile can’t be read.
xrspatial/geotiff/_gpu_decode.py Adds _warn_or_raise_gpu_fallback() and uses it in several GPU helper except Exception: return None fallback sites.
xrspatial/geotiff/tests/test_strict_mode_1662.py Adds tests pinning the warn-vs-raise contract for strict mode and fallback warning contents.
docs/source/reference/geotiff.rst Documents strict mode and the XRSPATIAL_GEOTIFF_STRICT env var behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +79 to +86
class GeoTIFFFallbackWarning(UserWarning):
"""Warning emitted when a geotiff helper falls back to a slower path.

Raised in the same call sites that would silently return ``None`` under
the historic ``except Exception: return None`` pattern. See issue #1662
for the audit and the ``XRSPATIAL_GEOTIFF_STRICT=1`` env var that
promotes these warnings to exceptions.
"""
Comment thread xrspatial/geotiff/_gpu_decode.py Outdated
Comment on lines +16 to +33
def _warn_or_raise_gpu_fallback(stage: str, exc: BaseException) -> None:
"""Report a GPU helper falling back to None (issue #1662).

Under ``XRSPATIAL_GEOTIFF_STRICT=1`` the original exception is
re-raised so CI catches silent fast-path regressions. In the default
mode a ``GeoTIFFFallbackWarning`` is emitted with the exception type
and message; the caller still receives ``None`` and chooses its own
next step (typically another decoder or CPU fallback).
"""
from . import _geotiff_strict_mode, GeoTIFFFallbackWarning
if _geotiff_strict_mode():
raise exc
warnings.warn(
f"{stage} fell back to None "
f"({type(exc).__name__}: {exc}).",
GeoTIFFFallbackWarning,
stacklevel=3,
)
from __future__ import annotations

import importlib.util
import os
Comment on lines +207 to +246
CUPY_AVAILABLE = importlib.util.find_spec('cupy') is not None


def test_warn_or_raise_gpu_fallback_default_warns(clear_strict_env):
"""Default mode emits one GeoTIFFFallbackWarning carrying type + msg."""
from xrspatial.geotiff._gpu_decode import _warn_or_raise_gpu_fallback

with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
_warn_or_raise_gpu_fallback(
"_try_nvjpeg_batch_decode", RuntimeError("bogus 1662"))

fallback_warnings = [
x for x in w if issubclass(x.category, GeoTIFFFallbackWarning)
]
assert len(fallback_warnings) == 1
msg = str(fallback_warnings[0].message)
assert '_try_nvjpeg_batch_decode' in msg
assert 'RuntimeError' in msg
assert 'bogus 1662' in msg


def test_warn_or_raise_gpu_fallback_strict_reraises(set_strict_env):
"""Strict mode re-raises the original exception."""
from xrspatial.geotiff._gpu_decode import _warn_or_raise_gpu_fallback

with pytest.raises(RuntimeError, match='bogus 1662 strict'):
_warn_or_raise_gpu_fallback(
"_try_nvjpeg_batch_decode", RuntimeError("bogus 1662 strict"))


# ---------------------------------------------------------------------------
# read_geotiff_gpu on_gpu_failure='auto' + env var integration
# ---------------------------------------------------------------------------

@pytest.mark.skipif(
not CUPY_AVAILABLE,
reason="cupy required for read_geotiff_gpu fallback test",
)
def test_read_geotiff_gpu_env_var_promotes_to_strict(set_strict_env, tmp_path):
Comment on lines +246 to +255
def test_read_geotiff_gpu_env_var_promotes_to_strict(set_strict_env, tmp_path):
"""With on_gpu_failure='auto' but XRSPATIAL_GEOTIFF_STRICT=1, a GPU
decode failure surfaces instead of falling back to CPU."""
from xrspatial.geotiff import read_geotiff_gpu

# A non-existent path triggers a failure before any decode runs;
# the env var should still bubble it up.
bogus = str(tmp_path / 'no_such_file_1662_promote.tif')
with pytest.raises(Exception):
read_geotiff_gpu(bogus)
brendancol added a commit that referenced this pull request May 12, 2026
… promotion

Five Copilot review comments on PR #1667:

1. Export GeoTIFFFallbackWarning via __all__ so it surfaces in
   ``from xrspatial.geotiff import *`` and matches the docstring claim
   that it's a user-facing warning type. Update the public-API
   regression test in tests/test_features.py to keep the expected set
   in sync.

2. Preserve the original traceback when ``_warn_or_raise_gpu_fallback``
   would raise in strict mode. The helper used to do ``raise exc`` from
   its own frame, which reset ``__traceback__`` to the helper. It now
   returns ``True`` in strict mode and each call site does a bare
   ``raise`` from its ``except`` block, keeping the original traceback
   intact. All nine call sites in ``_gpu_decode.py`` were updated.

3. Drop the unused ``import os`` from ``test_strict_mode_1662.py``.

4. Tighten the GPU gating in ``test_strict_mode_1662.py`` from a bare
   ``importlib.util.find_spec('cupy')`` check to a ``_gpu_available()``
   helper that also confirms ``cupy.cuda.is_available()`` -- matches
   the pattern used in the other GPU-only geotiff tests.

5. Rewrite ``test_read_geotiff_gpu_env_var_promotes_to_strict`` so it
   actually exercises the env-var promotion. The previous version
   passed a non-existent path which raised regardless of strict mode.
   The new version writes a real on-disk TIF, monkeypatches
   ``gpu_decode_tiles_from_file`` and ``gpu_decode_tiles`` on the
   ``_gpu_decode`` module to raise, then asserts that the default mode
   returns a CuPy DataArray (CPU fallback path) and that
   ``XRSPATIAL_GEOTIFF_STRICT=1`` re-raises the patched error.
…1662)

Each broad `except Exception: return None` site flagged in #1662 now
emits a GeoTIFFFallbackWarning carrying the original exception type and
message. The new XRSPATIAL_GEOTIFF_STRICT=1 env var (read by
_geotiff_strict_mode()) re-raises instead of warning so CI can fail on
silent GPU or VRT fallbacks.

Sites covered:
- _wkt_to_epsg, _epsg_to_wkt: pyproj-missing vs broken-input
- _vrt.py source read skip: surface missing-tile holes
- _gds_read_tiles, _try_nvcomp_batch_decompress (kvikio + ctypes),
  _try_nvjpeg_batch_decode, _nvjpeg_batch_encode,
  _try_nvcomp_from_device_bufs, _nvcomp_batch_compress,
  _try_nvjpeg2k_batch_decode, _nvjpeg2k_batch_encode
- read_geotiff_gpu(on_gpu_failure='auto') stages also honor the env var
21 tests covering the warn-vs-raise contract:
- _geotiff_strict_mode() env var parsing (truthy + falsy values)
- _wkt_to_epsg / _epsg_to_wkt default-warns vs strict-raises
- VRT missing-source default-warns-then-continues vs strict-raises
- _warn_or_raise_gpu_fallback default + strict for GPU helper sites
- read_geotiff_gpu(on_gpu_failure='auto') honors XRSPATIAL_GEOTIFF_STRICT=1
… promotion

Five Copilot review comments on PR #1667:

1. Export GeoTIFFFallbackWarning via __all__ so it surfaces in
   ``from xrspatial.geotiff import *`` and matches the docstring claim
   that it's a user-facing warning type. Update the public-API
   regression test in tests/test_features.py to keep the expected set
   in sync.

2. Preserve the original traceback when ``_warn_or_raise_gpu_fallback``
   would raise in strict mode. The helper used to do ``raise exc`` from
   its own frame, which reset ``__traceback__`` to the helper. It now
   returns ``True`` in strict mode and each call site does a bare
   ``raise`` from its ``except`` block, keeping the original traceback
   intact. All nine call sites in ``_gpu_decode.py`` were updated.

3. Drop the unused ``import os`` from ``test_strict_mode_1662.py``.

4. Tighten the GPU gating in ``test_strict_mode_1662.py`` from a bare
   ``importlib.util.find_spec('cupy')`` check to a ``_gpu_available()``
   helper that also confirms ``cupy.cuda.is_available()`` -- matches
   the pattern used in the other GPU-only geotiff tests.

5. Rewrite ``test_read_geotiff_gpu_env_var_promotes_to_strict`` so it
   actually exercises the env-var promotion. The previous version
   passed a non-existent path which raised regardless of strict mode.
   The new version writes a real on-disk TIF, monkeypatches
   ``gpu_decode_tiles_from_file`` and ``gpu_decode_tiles`` on the
   ``_gpu_decode`` module to raise, then asserts that the default mode
   returns a CuPy DataArray (CPU fallback path) and that
   ``XRSPATIAL_GEOTIFF_STRICT=1`` re-raises the patched error.
@brendancol brendancol merged commit 033b6a2 into main May 12, 2026
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance PR touches performance-sensitive code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace silent except-return-None in geotiff with typed exceptions + strict mode

2 participants