Skip to content

Raise the underlying cause when geospatial data cannot be read - #1694

Open
rajeeja wants to merge 4 commits into
mainfrom
rajeeja/geopandas-read-error
Open

Raise the underlying cause when geospatial data cannot be read#1694
rajeeja wants to merge 4 commits into
mainfrom
rajeeja/geopandas-read-error

Conversation

@rajeeja

@rajeeja rajeeja commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Closes #1693

  • _gpd_read caught every read failure, printed it, and fell through, leaving gdf unbound so the next line raised UnboundLocalError instead of the real cause; dropped the try/except entirely so geopandas raises directly (also surfaces FileNotFoundError for free).
  • geopandas was unpinned and pre-1.0 releases don't require pyogrio, so an install could end up with no file-IO backend at all; pinned geopandas>=1.0.
  • _open_dataset_with_fallback now chains the fallback engine's error onto the default engine's, so a file neither engine can open reports both reasons.
  • FESOM2 ASCII parsers raised FileNotFoundError("TODO: "); now name the missing file/directory.
  • Assuming WGS84 for CRS-less data and skipping an unsupported geometry type are now warnings.warn instead of print; the latter previously produced a grid silently missing a face.
  • Removed _is_structured's speculative stdout diagnostics, which fired on every valid MPAS/Exodus/SCRIP file.

@rajeeja rajeeja self-assigned this Aug 19, 2026
@rajeeja
rajeeja requested a review from Sevans711 August 19, 2026 22:10

@Sevans711 Sevans711 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good find, I definitely agree with this premise of needing better error handling here; the version on main is not using best practices for error handling in a few different ways: (1) try multiple things at once; (2) catch error and print but don't re-raise, and (3) then continue with the code which will crash with confusing UnboundLocalError if an error occurred previously.

I am not 100% certain that GridInvalidError is the way to go here. What if the file simply doesn't exist, for example? Then it should be a FileNotFoundError. I think at least this case needs to be handled separately. Perhaps changing "except Exception" to "except ValueError" would be a cleaner way to do this. I tend to be skeptical of "except Exception" unless there is a clear reason for wanting to catch every error type.

More on that point, though: what is the motivation for catching errors here at all? Why not just allow gpd.read_file(...) to fail directly with its own error message?

The previous handler caught every read failure, printed it, and left gdf
unbound, so the next line died with UnboundLocalError instead of the real
cause. Wrapping the failure in GridInvalidError hid the backend's own
exception type, and no other reader in uxarray/io wraps its backend's
errors, so drop the try/except and let gpd.read_file raise.

The underlying problem was packaging: geopandas was unpinned and releases
before 1.0 do not require pyogrio, so an install could end up with
geopandas and no file-IO backend at all, making every read_file call fail
with ImportError.

The regression test now skips without pyogrio, since it would otherwise
pass on that ImportError rather than on an actual parse failure.
The geopandas reader and the netCDF fallback shared the same pattern as
issue #1693: a failure was caught and reported to stdout, or replaced by a
later one, so the actual cause never reached the caller.

_open_dataset_with_fallback now chains the fallback engine's error onto the
default engine's, so a file that neither engine can open reports both
reasons rather than only the second.

The two FESOM2 ASCII parsers raised FileNotFoundError("TODO: "), which named
neither the missing file nor the directory searched.

Assuming WGS84 for CRS-less geospatial data and skipping a geometry type the
reader does not support are both warnings now. The latter silently produced
a grid with a missing face, which is a wrong result rather than a diagnostic.

_is_structured is called speculatively for every dataset before any other
format check, so its stdout diagnostics fired for perfectly valid MPAS,
Exodus and SCRIP files. They are removed; a negative result is the normal
case and _parse_grid_type already raises an actionable error.
@rajeeja

rajeeja commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

I hit this by accident — testing something unrelated and saw four failures in test_geopandas.py. Looked like a broken env at first, but geopandas was unpinned in pyproject.toml, and pre-1.0 geopandas doesn't require pyogrio, so my install landed on geopandas 0.9 with no file-IO backend at all — neither pyogrio nor fiona is mentioned anywhere in the repo or ci/environment.yml. Real message was the 'read_file' function requires the 'pyogrio' or 'fiona' package, but neither is installed; the old code printed it and carried on, which is how it became an UnboundLocalError.

You're right that we shouldn't be catching here at all — none of the other readers do; _esmf, _scrip, and io/utils only raise GridInvalidError once the data is parsed and actually wrong. Dropped the try/except and let geopandas raise, which also gets FileNotFoundError for free. except ValueError wouldn't have helped either way, since the real failures are ImportError or pyogrio's DataSourceError / fiona's DriverError — moot now that the catch is gone.

Pinned geopandas>=1.0 and fixed my test too — it was passing on the ImportError rather than a parse failure, green for the wrong reason.

@Sevans711 Sevans711 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks pretty reasonable as an overall set of changes to make, but I left a variety of inline comments to be addressed, including one point where I just needed a bit more clarification on the reason for the changes. Thank you for doing this work to improve the clarity and error handling throughout the code!

Flagging #1617 as related (may want to delay any work there on uxarray/io error messages until this merges, since this touches multiple errors in uxarray/io).

Comment thread test/io/test_fesom.py Outdated

def test_parse_nod2d_missing_file_names_the_file(tmp_path):
"""A missing 'nod2d.out' must say which file is missing and where."""
from uxarray.io._fesom2 import _parse_nod2d

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would have a slight preference for imports like this to be at top of file, rather than within each test, for the test suite, especially for a small test suite file like this! I'm not sure what the benefit is of putting these imports directly inside the test functions.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(This applies to a variety of imports throughout your new test functions, but I'm only leaving this one comment rather than making a new comment thread for each one.)

Comment thread test/io/test_geopandas.py Outdated
otherwise read_file raises ImportError and the test would pass for the
wrong reason.
"""
import pytest

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

pytest import should definitely go at top of file; that would match the style of all other pytest imports throughout uxarray test suite.

Comment thread test/io/test_geopandas.py Outdated
"""Assuming WGS84 for CRS-less data is a guess and must be announced."""
import pytest
gpd = pytest.importorskip("geopandas")
from shapely.geometry import Polygon

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

shapely import should probably go at top of file for pytest test suite, but isn't as necessary to move it. It would avoid the need to re-import in multiple tests at least.

Comment thread test/io/test_geopandas.py Outdated
"""
import pytest

pytest.importorskip("pyogrio")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Don't introduce importorskip to the test suite; no other tests are using importorskip, and, like you mentioned in your comment, you expect pyogrio to be installed when uxarray has been installed with all dependencies. The test suite only gets run in an environment with all dependencies installed.

Comment thread test/io/test_geopandas.py Outdated
"""Dropping a geometry silently would yield a grid missing a face with no
indication that anything was skipped."""
import pytest
gpd = pytest.importorskip("geopandas")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(See previous comments, don't introduce importorskip)

Comment thread uxarray/core/utils.py
# Try opening with xarray's default read engine
return xr.open_dataset(filename_or_obj, chunks=chunks, **kwargs)
except Exception:
except Exception as default_engine_error:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Apologies if you already clarified this in comment thread but could you clarify why this change is being added as part of this PR? (Might not need to change anything in response to my comment here, just not fully understanding yet how this helps to fix the original issue.)

Comment thread uxarray/io/_fesom2.py Outdated
raise FileNotFoundError("TODO: ")
raise FileNotFoundError(
f"Expected a FESOM2 ASCII grid directory containing 'nod2d.out', "
f"but no such file exists under {grid_path!r}."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Minor suggestion: here and below, I recommend to use os.path.abspath(grid_path) in the error messages. Avoids confusion for any users with file structures like: run1/gridfolder/... and run2/gridfolder/... just showing "gridfolder" in the error message.

Move test-local imports to module scope, drop the importorskip calls
now that the full test suite always runs with all dependencies
installed, and report absolute paths in fesom2's missing-file errors
so users with same-named subfolders under different run directories
aren't confused by identical-looking relative messages.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

_read_geodataframe raises UnboundLocalError when the file cannot be read

2 participants