Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better error message when no backend engine is found. #5300

Merged
merged 5 commits into from
May 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion xarray/backends/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,24 @@ def guess_engine(store_spec):
except Exception:
warnings.warn(f"{engine!r} fails while guessing", RuntimeWarning)

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


def get_backend(engine):
Expand Down
9 changes: 7 additions & 2 deletions xarray/tests/test_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -2771,7 +2771,9 @@ def test_open_badbytes(self):
with pytest.raises(ValueError, match=r"HDF5 as bytes"):
with open_dataset(b"\211HDF\r\n\032\n", engine="h5netcdf"):
pass
with pytest.raises(ValueError, match=r"cannot guess the engine"):
with pytest.raises(
ValueError, match=r"match in any of xarray's currently installed IO"
):
with open_dataset(b"garbage"):
pass
with pytest.raises(ValueError, match=r"can only read bytes"):
Expand Down Expand Up @@ -2823,7 +2825,10 @@ def test_open_fileobj(self):
# `raises_regex`?). Ref https://github.com/pydata/xarray/pull/5191
with open(tmp_file, "rb") as f:
f.seek(8)
with pytest.raises(ValueError, match="cannot guess the engine"):
with pytest.raises(
ValueError,
match="match in any of xarray's currently installed IO",
):
with pytest.warns(
RuntimeWarning,
match=re.escape("'h5netcdf' fails while guessing"),
Expand Down
20 changes: 20 additions & 0 deletions xarray/tests/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,23 @@ def test_build_engines_sorted():

assert set(indices) < {0, -1}
assert list(backend_entrypoints) == sorted(backend_entrypoints)


@mock.patch(
"xarray.backends.plugins.list_engines",
mock.MagicMock(return_value={"dummy": DummyBackendEntrypointArgs()}),
)
def test_no_matching_engine_found():
with pytest.raises(
ValueError, match="match in any of xarray's currently installed IO"
):
plugins.guess_engine("not-valid")


@mock.patch(
"xarray.backends.plugins.list_engines",
mock.MagicMock(return_value={}),
)
def test_no_engines_installed():
with pytest.raises(ValueError, match="no currently installed IO backends."):
plugins.guess_engine("not-valid")
42 changes: 41 additions & 1 deletion xarray/tutorial.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,48 @@ def _construct_cache_dir(path):
"RGB.byte": "https://github.com/mapbox/rasterio/raw/1.2.1/tests/data/RGB.byte.tif",
"shade": "https://github.com/mapbox/rasterio/raw/1.2.1/tests/data/shade.tif",
}
file_formats = {
"air_temperature": 3,
"rasm": 3,
"ROMS_example": 4,
"tiny": 3,
"eraint_uvz": 3,
}
Comment on lines +39 to +45
Copy link
Collaborator

Choose a reason for hiding this comment

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

should we also mention the backend in the list of the available datasets for open_dataset?

Copy link
Member Author

Choose a reason for hiding this comment

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

This would be a good idea for future work :)



def _check_netcdf_engine_installed(name):
version = file_formats.get(name)
if version == 3:
try:
import scipy # noqa
except ImportError:
try:
import netCDF4 # noqa
except ImportError:
raise ImportError(
f"opening tutorial dataset {name} requires either scipy or "
"netCDF4 to be installed."
)
if version == 4:
try:
import h5netcdf # noqa
except ImportError:
try:
import netCDF4 # noqa
except ImportError:
raise ImportError(
f"opening tutorial dataset {name} requires either h5netcdf "
"or netCDF4 to be installed."
)


# idea borrowed from Seaborn
def open_dataset(
name,
cache=True,
cache_dir=None,
*,
engine=None,
**kws,
):
"""
Expand Down Expand Up @@ -91,13 +126,18 @@ def open_dataset(
if not path.suffix:
# process the name
default_extension = ".nc"
if engine is None:
_check_netcdf_engine_installed(name)
path = path.with_suffix(default_extension)
elif path.suffix == ".grib":
if engine is None:
engine = "cfgrib"

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

# retrieve the file
filepath = pooch.retrieve(url=url, known_hash=None, path=cache_dir)
ds = _open_dataset(filepath, **kws)
ds = _open_dataset(filepath, engine=engine, **kws)
if not cache:
ds = ds.load()
pathlib.Path(filepath).unlink()
Expand Down