Skip to content

Commit 24c6152

Browse files
authored
Better error message when no backend engine is found. (#5300)
* Better error message when no backend engine is found. I consider this progress towards fixing GH5291 but not a complete fix yet. * Better error message for tutorial datasets * flake8
1 parent 49aa235 commit 24c6152

File tree

4 files changed

+86
-4
lines changed

4 files changed

+86
-4
lines changed

xarray/backends/plugins.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,24 @@ def guess_engine(store_spec):
106106
except Exception:
107107
warnings.warn(f"{engine!r} fails while guessing", RuntimeWarning)
108108

109-
raise ValueError("cannot guess the engine, try passing one explicitly")
109+
installed = [k for k in engines if k != "store"]
110+
if installed:
111+
raise ValueError(
112+
"did not find a match in any of xarray's currently installed IO "
113+
f"backends {installed}. Consider explicitly selecting one of the "
114+
"installed backends via the ``engine`` parameter to "
115+
"xarray.open_dataset(), or installing additional IO dependencies:\n"
116+
"http://xarray.pydata.org/en/stable/getting-started-guide/installing.html\n"
117+
"http://xarray.pydata.org/en/stable/user-guide/io.html"
118+
)
119+
else:
120+
raise ValueError(
121+
"xarray is unable to open this file because it has no currently "
122+
"installed IO backends. Xarray's read/write support requires "
123+
"installing optional dependencies:\n"
124+
"http://xarray.pydata.org/en/stable/getting-started-guide/installing.html\n"
125+
"http://xarray.pydata.org/en/stable/user-guide/io.html"
126+
)
110127

111128

112129
def get_backend(engine):

xarray/tests/test_backends.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2771,7 +2771,9 @@ def test_open_badbytes(self):
27712771
with pytest.raises(ValueError, match=r"HDF5 as bytes"):
27722772
with open_dataset(b"\211HDF\r\n\032\n", engine="h5netcdf"):
27732773
pass
2774-
with pytest.raises(ValueError, match=r"cannot guess the engine"):
2774+
with pytest.raises(
2775+
ValueError, match=r"match in any of xarray's currently installed IO"
2776+
):
27752777
with open_dataset(b"garbage"):
27762778
pass
27772779
with pytest.raises(ValueError, match=r"can only read bytes"):
@@ -2823,7 +2825,10 @@ def test_open_fileobj(self):
28232825
# `raises_regex`?). Ref https://github.com/pydata/xarray/pull/5191
28242826
with open(tmp_file, "rb") as f:
28252827
f.seek(8)
2826-
with pytest.raises(ValueError, match="cannot guess the engine"):
2828+
with pytest.raises(
2829+
ValueError,
2830+
match="match in any of xarray's currently installed IO",
2831+
):
28272832
with pytest.warns(
28282833
RuntimeWarning,
28292834
match=re.escape("'h5netcdf' fails while guessing"),

xarray/tests/test_plugins.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,23 @@ def test_build_engines_sorted():
157157

158158
assert set(indices) < {0, -1}
159159
assert list(backend_entrypoints) == sorted(backend_entrypoints)
160+
161+
162+
@mock.patch(
163+
"xarray.backends.plugins.list_engines",
164+
mock.MagicMock(return_value={"dummy": DummyBackendEntrypointArgs()}),
165+
)
166+
def test_no_matching_engine_found():
167+
with pytest.raises(
168+
ValueError, match="match in any of xarray's currently installed IO"
169+
):
170+
plugins.guess_engine("not-valid")
171+
172+
173+
@mock.patch(
174+
"xarray.backends.plugins.list_engines",
175+
mock.MagicMock(return_value={}),
176+
)
177+
def test_no_engines_installed():
178+
with pytest.raises(ValueError, match="no currently installed IO backends."):
179+
plugins.guess_engine("not-valid")

xarray/tutorial.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,48 @@ def _construct_cache_dir(path):
3636
"RGB.byte": "https://github.com/mapbox/rasterio/raw/1.2.1/tests/data/RGB.byte.tif",
3737
"shade": "https://github.com/mapbox/rasterio/raw/1.2.1/tests/data/shade.tif",
3838
}
39+
file_formats = {
40+
"air_temperature": 3,
41+
"rasm": 3,
42+
"ROMS_example": 4,
43+
"tiny": 3,
44+
"eraint_uvz": 3,
45+
}
46+
47+
48+
def _check_netcdf_engine_installed(name):
49+
version = file_formats.get(name)
50+
if version == 3:
51+
try:
52+
import scipy # noqa
53+
except ImportError:
54+
try:
55+
import netCDF4 # noqa
56+
except ImportError:
57+
raise ImportError(
58+
f"opening tutorial dataset {name} requires either scipy or "
59+
"netCDF4 to be installed."
60+
)
61+
if version == 4:
62+
try:
63+
import h5netcdf # noqa
64+
except ImportError:
65+
try:
66+
import netCDF4 # noqa
67+
except ImportError:
68+
raise ImportError(
69+
f"opening tutorial dataset {name} requires either h5netcdf "
70+
"or netCDF4 to be installed."
71+
)
3972

4073

4174
# idea borrowed from Seaborn
4275
def open_dataset(
4376
name,
4477
cache=True,
4578
cache_dir=None,
79+
*,
80+
engine=None,
4681
**kws,
4782
):
4883
"""
@@ -94,13 +129,18 @@ def open_dataset(
94129
if not path.suffix:
95130
# process the name
96131
default_extension = ".nc"
132+
if engine is None:
133+
_check_netcdf_engine_installed(name)
97134
path = path.with_suffix(default_extension)
135+
elif path.suffix == ".grib":
136+
if engine is None:
137+
engine = "cfgrib"
98138

99139
url = f"{base_url}/raw/{version}/{path.name}"
100140

101141
# retrieve the file
102142
filepath = pooch.retrieve(url=url, known_hash=None, path=cache_dir)
103-
ds = _open_dataset(filepath, **kws)
143+
ds = _open_dataset(filepath, engine=engine, **kws)
104144
if not cache:
105145
ds = ds.load()
106146
pathlib.Path(filepath).unlink()

0 commit comments

Comments
 (0)