Reject VRT sources under stable_only=True (#2443)#2447
Merged
Conversation
Add a `stable_only: bool = False` kwarg to `open_geotiff`, `read_geotiff_dask`, `read_geotiff_gpu`, and `read_vrt`. When True, the VRT path raises a typed `VRTStableSourcesOnlyError` (a `GeoTIFFAmbiguousMetadataError` subclass) before any pixel decode. The message names the offending VRT path and the `allow_experimental_codecs` unlock so callers can opt into the broader tier set explicitly. Flips the `test_release_gate_negative_mixed_tier_vrt_children` xfail from epic #2342.
brendancol
commented
May 26, 2026
Contributor
Author
brendancol
left a comment
There was a problem hiding this comment.
PR Review: Reject VRT sources under stable_only=True (#2443)
Blockers (must fix before merge)
None.
Suggestions (should fix, not blocking)
None substantive. The PR mirrors the #2442 precedent closely.
Nits (optional improvements)
xrspatial/geotiff/_validation.py:715-737: the validator does not verify the source is actually a VRT.read_vrtis the only caller and is contractually VRT-only, so this is defensive at worst -- but the message says"VRT source '{source}'"unconditionally, which would mislabel a non-VRT path if someone called_validate_stable_only_vrtdirectly from a future code path. Optional: tighten the docstring to say "the caller must have already classified source as a VRT," or sniffsource.lower().endswith('.vrt')and pass through silently otherwise.xrspatial/geotiff/tests/test_vrt_stable_only_2443.py:65-79: the "unlock" test asserts that withallow_experimental_codecs=Truewe do not getVRTStableSourcesOnlyError, but does not pin the downstream error (stillVRTUnsupportedErrorfor the no-bands case). Slight risk that a future refactor silently broadens the unlock past intent. Optional: assertpytest.raises(VRTUnsupportedError)instead ofException.
What looks good
- Matches the typed-error-plus-opt-in-kwarg shape from PR #2442 / #2441: subclass of
GeoTIFFAmbiguousMetadataError, kwarg threaded through every public reader, canonical-order test updated alongside. - Error message satisfies both release-gate assertions (names the opt-in flag and cites
release_gate_geotiffplus#2342). - Gate fires at the top of
read_vrt, before_validate_dispatch_kwargs, so the typed error is the first raise the caller sees. - New test file covers all four entry points (eager VRT, direct VRT, dask VRT, the unlock path) plus the subclass invariant.
Checklist
- Algorithm matches reference: matches the release-gate test contract.
- All implemented backends produce consistent results: validator runs in
read_vrt; both routing paths (open_geotiffandread_geotiff_dask) forward the kwarg. - NaN handling is correct: N/A (validation-only).
- Edge cases are covered by tests: default-false no-op, unlock path, subclass invariant.
- Dask chunk boundaries handled correctly: N/A.
- No premature materialization or unnecessary copies: validator raises before any decode.
- Benchmark exists or is not needed: not needed.
- README feature matrix updated (if applicable): not applicable.
- Docstrings present and accurate: every entry point's docstring carries the new kwarg.
…rts (#2443) - ``_validate_stable_only_vrt`` now passes through silently when the source is not a ``.vrt`` path, so a future call site that forwards a non-VRT path cannot trip a mislabeled "VRT source" rejection. The docstring spells out the pass-through contract. - ``test_vrt_stable_only_2443.py`` pins the downstream ``VRTUnsupportedError`` on the default-false and ``allow_experimental_codecs``-unlock paths so a future refactor cannot silently broaden either branch past intent.
brendancol
commented
May 26, 2026
Contributor
Author
brendancol
left a comment
There was a problem hiding this comment.
Follow-up Review: nits addressed (commit 2a642dd)
Disposition of original findings
- Nit 1 (validator does not check for VRT extension): Fixed.
_validate_stable_only_vrtnow passes through silently whensourceis not a string ending in.vrt(case-insensitive). Docstring updated to spell out the pass-through. - Nit 2 (unlock test asserts only
Exception): Fixed. Both the default-false andallow_experimental_codecsunlock tests now assertpytest.raises(VRTUnsupportedError)so a future refactor cannot silently broaden either branch.
Verification
pytest xrspatial/geotiff/tests/test_vrt_stable_only_2443.py xrspatial/geotiff/tests/release_gates/test_stable_features.py::test_release_gate_negative_mixed_tier_vrt_children xrspatial/geotiff/tests/test_features.py::TestPublicAPI xrspatial/geotiff/tests/test_reader_kwarg_order_1935.py-- 16 passed, 0 failed.
No further blockers, suggestions, or nits.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
stable_only: bool = Falsetoopen_geotiff,read_geotiff_dask,read_geotiff_gpu, andread_vrt. When True on a VRT source, the read raisesVRTStableSourcesOnlyError(aGeoTIFFAmbiguousMetadataErrorsubclass) at graph-build / eager-read setup time, before any pixel decode.stable_onlyflag, and theallow_experimental_codecsunlock, and citesdocs/source/reference/release_gate_geotiff.rstplus epic Epic: Conservative VRT support contract for GeoTIFF release #2342.test_release_gate_negative_mixed_tier_vrt_childrenfrom xfail to a regular pass.Backend coverage
Numpy, cupy, dask+numpy, dask+cupy. VRT routing happens in
open_geotiffandread_geotiff_dask; both entry points forward the kwarg toread_vrt, which carries the validator call. The GPU eager path does not open VRT sources directly, so the flag is a no-op there (kept on the signature for cross-backend symmetry, per the canonical-order test).Test plan
pytest xrspatial/geotiff/tests/test_vrt_stable_only_2443.py(7 new tests: default rejection on each entry point, default-false no-op,allow_experimental_codecsunlock, error subclass invariant)pytest xrspatial/geotiff/tests/release_gates/test_stable_features.py::test_release_gate_negative_mixed_tier_vrt_children(xfail flipped to pass)pytest xrspatial/geotiff -n auto(5811 passed, 4 unrelated xfailed)pytest xrspatial -n auto(10675 passed; two*_dask_temp_cleanupfailures are pre-existing flakes unrelated to this change)Closes #2443.