Skip to content

Commit de22fb9

Browse files
committed
o Try to get it to work with fallback internal API and use default netcdf engine first for xarray loading - caused due to pydata/xarray#10755
1 parent 6553bc4 commit de22fb9

File tree

5 files changed

+88
-9
lines changed

5 files changed

+88
-9
lines changed

test/core/test_api.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
import uxarray as ux
33
import numpy as np
44
import pytest
5+
import tempfile
6+
import xarray as xr
7+
from unittest.mock import patch
8+
from uxarray.core.utils import _open_dataset_with_fallback
59

610
def test_open_geoflow_dataset(gridpath, datasetpath):
711
"""Loads a single dataset with its grid topology file using uxarray's
@@ -111,4 +115,39 @@ def test_open_dataset_grid_kwargs(gridpath, datasetpath):
111115
gridpath("ugrid", "outCSne30", "outCSne30.ug"),
112116
datasetpath("ugrid", "outCSne30", "outCSne30_var2.nc"),
113117
grid_kwargs={"drop_variables": "Mesh2_face_nodes"}
114-
)
118+
)
119+
120+
121+
def test_open_dataset_with_fallback():
122+
"""Test that the fallback mechanism works when the default engine fails."""
123+
124+
# Create a simple test dataset
125+
with tempfile.NamedTemporaryFile(suffix='.nc', delete=False) as tmp:
126+
data = xr.Dataset({'temp': (['x', 'y'], np.random.rand(5, 5))})
127+
data.to_netcdf(tmp.name)
128+
tmp_path = tmp.name
129+
130+
try:
131+
# Test normal case
132+
ds = _open_dataset_with_fallback(tmp_path)
133+
assert isinstance(ds, xr.Dataset)
134+
assert 'temp' in ds.data_vars
135+
136+
# Test fallback mechanism with mocked failure
137+
original_open = xr.open_dataset
138+
call_count = 0
139+
def mock_open_dataset(*args, **kwargs):
140+
nonlocal call_count
141+
call_count += 1
142+
if call_count == 1 and 'engine' not in kwargs:
143+
raise Exception("Simulated engine failure")
144+
return original_open(*args, **kwargs)
145+
146+
with patch('uxarray.core.utils.xr.open_dataset', side_effect=mock_open_dataset):
147+
ds_fallback = _open_dataset_with_fallback(tmp_path)
148+
assert isinstance(ds_fallback, xr.Dataset)
149+
assert call_count == 2 # First failed, second succeeded
150+
151+
finally:
152+
import os
153+
os.unlink(tmp_path)

uxarray/core/api.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
import numpy as np
88

99
from uxarray.core.dataset import UxDataset
10-
from uxarray.core.utils import _map_dims_to_ugrid, match_chunks_to_ugrid
10+
from uxarray.core.utils import (
11+
_map_dims_to_ugrid,
12+
_open_dataset_with_fallback,
13+
match_chunks_to_ugrid,
14+
)
1115
from uxarray.grid import Grid
1216

1317
if TYPE_CHECKING:
@@ -178,8 +182,6 @@ def open_dataset(
178182
>>> import uxarray as ux
179183
>>> ux_ds = ux.open_dataset("grid_file.nc", "data_file.nc")
180184
"""
181-
import xarray as xr
182-
183185
if grid_kwargs is None:
184186
grid_kwargs = {}
185187

@@ -189,7 +191,7 @@ def open_dataset(
189191
)
190192

191193
# Load the data as a Xarray Dataset
192-
ds = xr.open_dataset(filename_or_obj, chunks=corrected_chunks, **kwargs)
194+
ds = _open_dataset_with_fallback(filename_or_obj, chunks=corrected_chunks, **kwargs)
193195

194196
# Map original dimensions to the UGRID conventions
195197
ds = _map_dims_to_ugrid(ds, uxgrid._source_dims_dict, uxgrid)

uxarray/core/dataset.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import uxarray
1616
from uxarray.core.dataarray import UxDataArray
17-
from uxarray.core.utils import _map_dims_to_ugrid
17+
from uxarray.core.utils import _map_dims_to_ugrid, _open_dataset_with_fallback
1818
from uxarray.formatting_html import dataset_repr
1919
from uxarray.grid import Grid
2020
from uxarray.grid.dual import construct_dual
@@ -326,7 +326,7 @@ def from_healpix(
326326
"""
327327

328328
if not isinstance(ds, xr.Dataset):
329-
ds = xr.open_dataset(ds, **kwargs)
329+
ds = _open_dataset_with_fallback(ds, **kwargs)
330330

331331
if face_dim not in ds.dims:
332332
raise ValueError(

uxarray/core/utils.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,41 @@
33
from uxarray.io.utils import _get_source_dims_dict, _parse_grid_type
44

55

6+
def _open_dataset_with_fallback(filename_or_obj, chunks=None, **kwargs):
7+
"""Internal utility function to open datasets with fallback to netcdf4 engine.
8+
9+
Attempts to use Xarray's default read engine first, which may be "h5netcdf"
10+
or "scipy" after v2025.09.0. If that fails (typically for h5-incompatible files),
11+
falls back to using the "netcdf4" engine.
12+
13+
Parameters
14+
----------
15+
filename_or_obj : str, Path, file-like or DataStore
16+
Strings and Path objects are interpreted as a path to a netCDF file
17+
or an OpenDAP URL and opened with python-netCDF4, unless the filename
18+
ends with .gz, in which case the file is gunzipped and opened with
19+
scipy.io.netcdf (only netCDF3 supported).
20+
chunks : int, dict, 'auto' or None, optional
21+
If chunks is provided, it is used to load the new dataset into dask
22+
arrays.
23+
**kwargs
24+
Additional keyword arguments passed to xr.open_dataset
25+
26+
Returns
27+
-------
28+
xr.Dataset
29+
The opened dataset
30+
"""
31+
try:
32+
# Try opening with xarray's default read engine
33+
return xr.open_dataset(filename_or_obj, chunks=chunks, **kwargs)
34+
except Exception:
35+
# If it fails, use the "netcdf4" engine as backup
36+
return xr.open_dataset(
37+
filename_or_obj, engine="netcdf4", chunks=chunks, **kwargs
38+
)
39+
40+
641
def _map_dims_to_ugrid(
742
ds,
843
_source_dims_dict,
@@ -69,7 +104,7 @@ def match_chunks_to_ugrid(grid_filename_or_obj, chunks):
69104
# No need to rename
70105
return chunks
71106

72-
ds = xr.open_dataset(grid_filename_or_obj, chunks=chunks)
107+
ds = _open_dataset_with_fallback(grid_filename_or_obj, chunks=chunks)
73108
grid_spec, _, _ = _parse_grid_type(ds)
74109

75110
source_dims_dict = _get_source_dims_dict(ds, grid_spec)

uxarray/grid/grid.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
from uxarray.constants import INT_FILL_VALUE
2020
from uxarray.conventions import ugrid
21+
22+
# Import the utility function for opening datasets with fallback
23+
from uxarray.core.utils import _open_dataset_with_fallback
2124
from uxarray.cross_sections import GridCrossSectionAccessor
2225
from uxarray.formatting_html import grid_repr
2326
from uxarray.grid.area import get_all_face_area_from_coords
@@ -348,7 +351,7 @@ def from_file(
348351
grid_ds, source_dims_dict = _read_geodataframe(filename)
349352

350353
elif backend == "xarray":
351-
dataset = xr.open_dataset(filename, **kwargs)
354+
dataset = _open_dataset_with_fallback(filename, **kwargs)
352355
return cls.from_dataset(dataset)
353356

354357
else:

0 commit comments

Comments
 (0)