From 24c61522219604f8092cf6c4805875825b78e3fe Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Tue, 18 May 2021 14:22:59 -0700 Subject: [PATCH] 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 --- xarray/backends/plugins.py | 19 +++++++++++++++- xarray/tests/test_backends.py | 9 ++++++-- xarray/tests/test_plugins.py | 20 +++++++++++++++++ xarray/tutorial.py | 42 ++++++++++++++++++++++++++++++++++- 4 files changed, 86 insertions(+), 4 deletions(-) diff --git a/xarray/backends/plugins.py b/xarray/backends/plugins.py index d892e8761a7..633459239c2 100644 --- a/xarray/backends/plugins.py +++ b/xarray/backends/plugins.py @@ -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): diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index 60eb5b924ca..908d2f4940d 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -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"): @@ -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"), diff --git a/xarray/tests/test_plugins.py b/xarray/tests/test_plugins.py index 139de174442..b35971e185b 100644 --- a/xarray/tests/test_plugins.py +++ b/xarray/tests/test_plugins.py @@ -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") diff --git a/xarray/tutorial.py b/xarray/tutorial.py index f74155120ab..78471be7a0e 100644 --- a/xarray/tutorial.py +++ b/xarray/tutorial.py @@ -36,6 +36,39 @@ 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, +} + + +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 @@ -43,6 +76,8 @@ def open_dataset( name, cache=True, cache_dir=None, + *, + engine=None, **kws, ): """ @@ -94,13 +129,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()