Skip to content

Commit

Permalink
Improve error message for guess engine (#5455)
Browse files Browse the repository at this point in the history
* fix normalize_path in pynio_.py

* draft: refactor to improve not found engine error

* fix backend registration and tests

* fix not workng jet

* fix error call guess_can_open

* fix

* fix

* add tests

* update message error engine not fuond

* update tests

* fix if else

* fix message error and tests

* revert changes in error messages

* revert changes in error messages
  • Loading branch information
aurghs authored Jun 23, 2021
1 parent 1f5c633 commit eea7673
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 40 deletions.
5 changes: 3 additions & 2 deletions xarray/backends/cfgrib_.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ def get_encoding(self):


class CfgribfBackendEntrypoint(BackendEntrypoint):
available = has_cfgrib

def guess_can_open(self, filename_or_obj):
try:
_, ext = os.path.splitext(filename_or_obj)
Expand Down Expand Up @@ -147,5 +149,4 @@ def open_dataset(
return ds


if has_cfgrib:
BACKEND_ENTRYPOINTS["cfgrib"] = CfgribfBackendEntrypoint
BACKEND_ENTRYPOINTS["cfgrib"] = CfgribfBackendEntrypoint
5 changes: 3 additions & 2 deletions xarray/backends/h5netcdf_.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ def close(self, **kwargs):


class H5netcdfBackendEntrypoint(BackendEntrypoint):
available = has_h5netcdf

def guess_can_open(self, filename_or_obj):
magic_number = try_read_magic_number_from_file_or_path(filename_or_obj)
if magic_number is not None:
Expand Down Expand Up @@ -394,5 +396,4 @@ def open_dataset(
return ds


if has_h5netcdf:
BACKEND_ENTRYPOINTS["h5netcdf"] = H5netcdfBackendEntrypoint
BACKEND_ENTRYPOINTS["h5netcdf"] = H5netcdfBackendEntrypoint
5 changes: 3 additions & 2 deletions xarray/backends/netCDF4_.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,8 @@ def close(self, **kwargs):


class NetCDF4BackendEntrypoint(BackendEntrypoint):
available = has_netcdf4

def guess_can_open(self, filename_or_obj):
if isinstance(filename_or_obj, str) and is_remote_uri(filename_or_obj):
return True
Expand Down Expand Up @@ -573,5 +575,4 @@ def open_dataset(
return ds


if has_netcdf4:
BACKEND_ENTRYPOINTS["netcdf4"] = NetCDF4BackendEntrypoint
BACKEND_ENTRYPOINTS["netcdf4"] = NetCDF4BackendEntrypoint
58 changes: 40 additions & 18 deletions xarray/backends/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ def sort_backends(backend_entrypoints):


def build_engines(pkg_entrypoints):
backend_entrypoints = BACKEND_ENTRYPOINTS.copy()
backend_entrypoints = {}
for backend_name, backend in BACKEND_ENTRYPOINTS.items():
if backend.available:
backend_entrypoints[backend_name] = backend
pkg_entrypoints = remove_duplicates(pkg_entrypoints)
external_backend_entrypoints = backends_dict_from_pkg(pkg_entrypoints)
backend_entrypoints.update(external_backend_entrypoints)
Expand All @@ -101,30 +104,49 @@ def guess_engine(store_spec):

for engine, backend in engines.items():
try:
if backend.guess_can_open and backend.guess_can_open(store_spec):
if backend.guess_can_open(store_spec):
return engine
except Exception:
warnings.warn(f"{engine!r} fails while guessing", RuntimeWarning)

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"
)
compatible_engines = []
for engine, backend_cls in BACKEND_ENTRYPOINTS.items():
try:
backend = backend_cls()
if backend.guess_can_open(store_spec):
compatible_engines.append(engine)
except Exception:
warnings.warn(f"{engine!r} fails while guessing", RuntimeWarning)

installed_engines = [k for k in engines if k != "store"]
if not compatible_engines:
if installed_engines:
error_msg = (
"did not find a match in any of xarray's currently installed IO "
f"backends {installed_engines}. Consider explicitly selecting one of the "
"installed engines via the ``engine`` parameter, or installing "
"additional IO dependencies, see:\n"
"http://xarray.pydata.org/en/stable/getting-started-guide/installing.html\n"
"http://xarray.pydata.org/en/stable/user-guide/io.html"
)
else:
error_msg = (
"xarray is unable to open this file because it has no currently "
"installed IO backends. Xarray's read/write support requires "
"installing optional IO dependencies, see:\n"
"http://xarray.pydata.org/en/stable/getting-started-guide/installing.html\n"
"http://xarray.pydata.org/en/stable/user-guide/io"
)
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"
error_msg = (
"found the following matches with the input file in xarray's IO "
f"backends: {compatible_engines}. But their dependencies may not be installed, see:\n"
"http://xarray.pydata.org/en/stable/user-guide/io.html \n"
"http://xarray.pydata.org/en/stable/getting-started-guide/installing.html"
)

raise ValueError(error_msg)


def get_backend(engine):
"""Select open_dataset method based on current engine."""
Expand Down
4 changes: 2 additions & 2 deletions xarray/backends/pseudonetcdf_.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def close(self):


class PseudoNetCDFBackendEntrypoint(BackendEntrypoint):
available = has_pseudonetcdf

# *args and **kwargs are not allowed in open_backend_dataset_ kwargs,
# unless the open_dataset_parameters are explicity defined like this:
Expand Down Expand Up @@ -153,5 +154,4 @@ def open_dataset(
return ds


if has_pseudonetcdf:
BACKEND_ENTRYPOINTS["pseudonetcdf"] = PseudoNetCDFBackendEntrypoint
BACKEND_ENTRYPOINTS["pseudonetcdf"] = PseudoNetCDFBackendEntrypoint
5 changes: 3 additions & 2 deletions xarray/backends/pydap_.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ def get_dimensions(self):


class PydapBackendEntrypoint(BackendEntrypoint):
available = has_pydap

def guess_can_open(self, filename_or_obj):
return isinstance(filename_or_obj, str) and is_remote_uri(filename_or_obj)

Expand Down Expand Up @@ -154,5 +156,4 @@ def open_dataset(
return ds


if has_pydap:
BACKEND_ENTRYPOINTS["pydap"] = PydapBackendEntrypoint
BACKEND_ENTRYPOINTS["pydap"] = PydapBackendEntrypoint
7 changes: 4 additions & 3 deletions xarray/backends/pynio_.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ def close(self):


class PynioBackendEntrypoint(BackendEntrypoint):
available = has_pynio

def open_dataset(
self,
filename_or_obj,
Expand All @@ -112,13 +114,13 @@ def open_dataset(
mode="r",
lock=None,
):
filename_or_obj = _normalize_path(filename_or_obj)
store = NioDataStore(
filename_or_obj,
mode=mode,
lock=lock,
)

filename_or_obj = _normalize_path(filename_or_obj)
store_entrypoint = StoreBackendEntrypoint()
with close_on_error(store):
ds = store_entrypoint.open_dataset(
Expand All @@ -134,5 +136,4 @@ def open_dataset(
return ds


if has_pynio:
BACKEND_ENTRYPOINTS["pynio"] = PynioBackendEntrypoint
BACKEND_ENTRYPOINTS["pynio"] = PynioBackendEntrypoint
5 changes: 3 additions & 2 deletions xarray/backends/scipy_.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ def close(self):


class ScipyBackendEntrypoint(BackendEntrypoint):
available = has_scipy

def guess_can_open(self, filename_or_obj):

magic_number = try_read_magic_number_from_file_or_path(filename_or_obj)
Expand Down Expand Up @@ -290,5 +292,4 @@ def open_dataset(
return ds


if has_scipy:
BACKEND_ENTRYPOINTS["scipy"] = ScipyBackendEntrypoint
BACKEND_ENTRYPOINTS["scipy"] = ScipyBackendEntrypoint
2 changes: 2 additions & 0 deletions xarray/backends/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@


class StoreBackendEntrypoint(BackendEntrypoint):
available = True

def guess_can_open(self, filename_or_obj):
return isinstance(filename_or_obj, AbstractDataStore)

Expand Down
12 changes: 10 additions & 2 deletions xarray/backends/zarr.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,15 @@ def open_zarr(


class ZarrBackendEntrypoint(BackendEntrypoint):
available = has_zarr

def guess_can_open(self, filename_or_obj):
try:
_, ext = os.path.splitext(filename_or_obj)
except TypeError:
return False
return ext in {".zarr"}

def open_dataset(
self,
filename_or_obj,
Expand Down Expand Up @@ -840,5 +849,4 @@ def open_dataset(
return ds


if has_zarr:
BACKEND_ENTRYPOINTS["zarr"] = ZarrBackendEntrypoint
BACKEND_ENTRYPOINTS["zarr"] = ZarrBackendEntrypoint
14 changes: 9 additions & 5 deletions xarray/tests/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,16 +164,20 @@ def test_build_engines_sorted():
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"
):
with pytest.raises(ValueError, match=r"did not find a match in any"):
plugins.guess_engine("not-valid")

with pytest.raises(ValueError, match=r"found the following matches with the input"):
plugins.guess_engine("foo.nc")


@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."):
def test_engines_not_installed():
with pytest.raises(ValueError, match=r"xarray is unable to open"):
plugins.guess_engine("not-valid")

with pytest.raises(ValueError, match=r"found the following matches with the input"):
plugins.guess_engine("foo.nc")

0 comments on commit eea7673

Please sign in to comment.