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

Improve error message for guess engine #5455

Merged
merged 15 commits into from
Jun 23, 2021
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 "
Comment on lines +134 to +135
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
"xarray is unable to open this file because it has no currently "
"installed IO backends. Xarray's read/write support requires "
"xarray is unable to open this file because no IO backend "
"is currently installed. 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 @@ -703,6 +703,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 @@ -757,5 +766,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")