Skip to content

Commit eea7673

Browse files
authored
Improve error message for guess engine (#5455)
* 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
1 parent 1f5c633 commit eea7673

11 files changed

+82
-40
lines changed

xarray/backends/cfgrib_.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ def get_encoding(self):
9494

9595

9696
class CfgribfBackendEntrypoint(BackendEntrypoint):
97+
available = has_cfgrib
98+
9799
def guess_can_open(self, filename_or_obj):
98100
try:
99101
_, ext = os.path.splitext(filename_or_obj)
@@ -147,5 +149,4 @@ def open_dataset(
147149
return ds
148150

149151

150-
if has_cfgrib:
151-
BACKEND_ENTRYPOINTS["cfgrib"] = CfgribfBackendEntrypoint
152+
BACKEND_ENTRYPOINTS["cfgrib"] = CfgribfBackendEntrypoint

xarray/backends/h5netcdf_.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ def close(self, **kwargs):
337337

338338

339339
class H5netcdfBackendEntrypoint(BackendEntrypoint):
340+
available = has_h5netcdf
341+
340342
def guess_can_open(self, filename_or_obj):
341343
magic_number = try_read_magic_number_from_file_or_path(filename_or_obj)
342344
if magic_number is not None:
@@ -394,5 +396,4 @@ def open_dataset(
394396
return ds
395397

396398

397-
if has_h5netcdf:
398-
BACKEND_ENTRYPOINTS["h5netcdf"] = H5netcdfBackendEntrypoint
399+
BACKEND_ENTRYPOINTS["h5netcdf"] = H5netcdfBackendEntrypoint

xarray/backends/netCDF4_.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,8 @@ def close(self, **kwargs):
512512

513513

514514
class NetCDF4BackendEntrypoint(BackendEntrypoint):
515+
available = has_netcdf4
516+
515517
def guess_can_open(self, filename_or_obj):
516518
if isinstance(filename_or_obj, str) and is_remote_uri(filename_or_obj):
517519
return True
@@ -573,5 +575,4 @@ def open_dataset(
573575
return ds
574576

575577

576-
if has_netcdf4:
577-
BACKEND_ENTRYPOINTS["netcdf4"] = NetCDF4BackendEntrypoint
578+
BACKEND_ENTRYPOINTS["netcdf4"] = NetCDF4BackendEntrypoint

xarray/backends/plugins.py

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,10 @@ def sort_backends(backend_entrypoints):
8181

8282

8383
def build_engines(pkg_entrypoints):
84-
backend_entrypoints = BACKEND_ENTRYPOINTS.copy()
84+
backend_entrypoints = {}
85+
for backend_name, backend in BACKEND_ENTRYPOINTS.items():
86+
if backend.available:
87+
backend_entrypoints[backend_name] = backend
8588
pkg_entrypoints = remove_duplicates(pkg_entrypoints)
8689
external_backend_entrypoints = backends_dict_from_pkg(pkg_entrypoints)
8790
backend_entrypoints.update(external_backend_entrypoints)
@@ -101,30 +104,49 @@ def guess_engine(store_spec):
101104

102105
for engine, backend in engines.items():
103106
try:
104-
if backend.guess_can_open and backend.guess_can_open(store_spec):
107+
if backend.guess_can_open(store_spec):
105108
return engine
106109
except Exception:
107110
warnings.warn(f"{engine!r} fails while guessing", RuntimeWarning)
108111

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-
)
112+
compatible_engines = []
113+
for engine, backend_cls in BACKEND_ENTRYPOINTS.items():
114+
try:
115+
backend = backend_cls()
116+
if backend.guess_can_open(store_spec):
117+
compatible_engines.append(engine)
118+
except Exception:
119+
warnings.warn(f"{engine!r} fails while guessing", RuntimeWarning)
120+
121+
installed_engines = [k for k in engines if k != "store"]
122+
if not compatible_engines:
123+
if installed_engines:
124+
error_msg = (
125+
"did not find a match in any of xarray's currently installed IO "
126+
f"backends {installed_engines}. Consider explicitly selecting one of the "
127+
"installed engines via the ``engine`` parameter, or installing "
128+
"additional IO dependencies, see:\n"
129+
"http://xarray.pydata.org/en/stable/getting-started-guide/installing.html\n"
130+
"http://xarray.pydata.org/en/stable/user-guide/io.html"
131+
)
132+
else:
133+
error_msg = (
134+
"xarray is unable to open this file because it has no currently "
135+
"installed IO backends. Xarray's read/write support requires "
136+
"installing optional IO dependencies, see:\n"
137+
"http://xarray.pydata.org/en/stable/getting-started-guide/installing.html\n"
138+
"http://xarray.pydata.org/en/stable/user-guide/io"
139+
)
119140
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"
141+
error_msg = (
142+
"found the following matches with the input file in xarray's IO "
143+
f"backends: {compatible_engines}. But their dependencies may not be installed, see:\n"
144+
"http://xarray.pydata.org/en/stable/user-guide/io.html \n"
145+
"http://xarray.pydata.org/en/stable/getting-started-guide/installing.html"
126146
)
127147

148+
raise ValueError(error_msg)
149+
128150

129151
def get_backend(engine):
130152
"""Select open_dataset method based on current engine."""

xarray/backends/pseudonetcdf_.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def close(self):
102102

103103

104104
class PseudoNetCDFBackendEntrypoint(BackendEntrypoint):
105+
available = has_pseudonetcdf
105106

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

155156

156-
if has_pseudonetcdf:
157-
BACKEND_ENTRYPOINTS["pseudonetcdf"] = PseudoNetCDFBackendEntrypoint
157+
BACKEND_ENTRYPOINTS["pseudonetcdf"] = PseudoNetCDFBackendEntrypoint

xarray/backends/pydap_.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ def get_dimensions(self):
110110

111111

112112
class PydapBackendEntrypoint(BackendEntrypoint):
113+
available = has_pydap
114+
113115
def guess_can_open(self, filename_or_obj):
114116
return isinstance(filename_or_obj, str) and is_remote_uri(filename_or_obj)
115117

@@ -154,5 +156,4 @@ def open_dataset(
154156
return ds
155157

156158

157-
if has_pydap:
158-
BACKEND_ENTRYPOINTS["pydap"] = PydapBackendEntrypoint
159+
BACKEND_ENTRYPOINTS["pydap"] = PydapBackendEntrypoint

xarray/backends/pynio_.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ def close(self):
9999

100100

101101
class PynioBackendEntrypoint(BackendEntrypoint):
102+
available = has_pynio
103+
102104
def open_dataset(
103105
self,
104106
filename_or_obj,
@@ -112,13 +114,13 @@ def open_dataset(
112114
mode="r",
113115
lock=None,
114116
):
117+
filename_or_obj = _normalize_path(filename_or_obj)
115118
store = NioDataStore(
116119
filename_or_obj,
117120
mode=mode,
118121
lock=lock,
119122
)
120123

121-
filename_or_obj = _normalize_path(filename_or_obj)
122124
store_entrypoint = StoreBackendEntrypoint()
123125
with close_on_error(store):
124126
ds = store_entrypoint.open_dataset(
@@ -134,5 +136,4 @@ def open_dataset(
134136
return ds
135137

136138

137-
if has_pynio:
138-
BACKEND_ENTRYPOINTS["pynio"] = PynioBackendEntrypoint
139+
BACKEND_ENTRYPOINTS["pynio"] = PynioBackendEntrypoint

xarray/backends/scipy_.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ def close(self):
238238

239239

240240
class ScipyBackendEntrypoint(BackendEntrypoint):
241+
available = has_scipy
242+
241243
def guess_can_open(self, filename_or_obj):
242244

243245
magic_number = try_read_magic_number_from_file_or_path(filename_or_obj)
@@ -290,5 +292,4 @@ def open_dataset(
290292
return ds
291293

292294

293-
if has_scipy:
294-
BACKEND_ENTRYPOINTS["scipy"] = ScipyBackendEntrypoint
295+
BACKEND_ENTRYPOINTS["scipy"] = ScipyBackendEntrypoint

xarray/backends/store.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55

66
class StoreBackendEntrypoint(BackendEntrypoint):
7+
available = True
8+
79
def guess_can_open(self, filename_or_obj):
810
return isinstance(filename_or_obj, AbstractDataStore)
911

xarray/backends/zarr.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,15 @@ def open_zarr(
785785

786786

787787
class ZarrBackendEntrypoint(BackendEntrypoint):
788+
available = has_zarr
789+
790+
def guess_can_open(self, filename_or_obj):
791+
try:
792+
_, ext = os.path.splitext(filename_or_obj)
793+
except TypeError:
794+
return False
795+
return ext in {".zarr"}
796+
788797
def open_dataset(
789798
self,
790799
filename_or_obj,
@@ -840,5 +849,4 @@ def open_dataset(
840849
return ds
841850

842851

843-
if has_zarr:
844-
BACKEND_ENTRYPOINTS["zarr"] = ZarrBackendEntrypoint
852+
BACKEND_ENTRYPOINTS["zarr"] = ZarrBackendEntrypoint

xarray/tests/test_plugins.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,16 +164,20 @@ def test_build_engines_sorted():
164164
mock.MagicMock(return_value={"dummy": DummyBackendEntrypointArgs()}),
165165
)
166166
def test_no_matching_engine_found():
167-
with pytest.raises(
168-
ValueError, match="match in any of xarray's currently installed IO"
169-
):
167+
with pytest.raises(ValueError, match=r"did not find a match in any"):
170168
plugins.guess_engine("not-valid")
171169

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

173174
@mock.patch(
174175
"xarray.backends.plugins.list_engines",
175176
mock.MagicMock(return_value={}),
176177
)
177-
def test_no_engines_installed():
178-
with pytest.raises(ValueError, match="no currently installed IO backends."):
178+
def test_engines_not_installed():
179+
with pytest.raises(ValueError, match=r"xarray is unable to open"):
179180
plugins.guess_engine("not-valid")
181+
182+
with pytest.raises(ValueError, match=r"found the following matches with the input"):
183+
plugins.guess_engine("foo.nc")

0 commit comments

Comments
 (0)