From 786dd3bbc291c0d550fbb0c3d11a549f331dff5d Mon Sep 17 00:00:00 2001 From: Scott Staniewicz Date: Mon, 21 Jul 2025 10:55:17 -0400 Subject: [PATCH 1/4] Fix `_get_default_engine_netcdf` to check for `h5netcdf` The current implementation only checks for `scipy` and `netCDF4`. This adds the necessary check for `h5netcdf`. Switches to using `importlib.util.find_spec`, as recommended by https://docs.python.org/3/library/importlib.html#importlib.util.find_spec The unit test temporarily removes the other two backend options, then checks that `h5netcdf` is used. Closes #10401 --- xarray/backends/api.py | 29 ++++++++++++++--------------- xarray/tests/test_backends_api.py | 13 ++++++++++++- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/xarray/backends/api.py b/xarray/backends/api.py index cfd3ff7fc0f..9aa37115a37 100644 --- a/xarray/backends/api.py +++ b/xarray/backends/api.py @@ -1,5 +1,6 @@ from __future__ import annotations +import importlib.util import os from collections.abc import ( Callable, @@ -122,23 +123,21 @@ def _get_default_engine_gz() -> Literal["scipy"]: return engine -def _get_default_engine_netcdf() -> Literal["netcdf4", "scipy"]: - engine: Literal["netcdf4", "scipy"] - try: - import netCDF4 # noqa: F401 +def _get_default_engine_netcdf() -> Literal["netcdf4", "h5netcdf", "scipy"]: + candidates: list[tuple[str, str]] = [ + ("netcdf4", "netCDF4"), + ("h5netcdf", "h5netcdf"), + ("scipy", "scipy.io.netcdf"), + ] - engine = "netcdf4" - except ImportError: # pragma: no cover - try: - import scipy.io.netcdf # noqa: F401 + for engine, module_name in candidates: + if importlib.util.find_spec(module_name) is not None: + return cast(Literal["netcdf4", "h5netcdf", "scipy"], engine) - engine = "scipy" - except ImportError as err: - raise ValueError( - "cannot read or write netCDF files without " - "netCDF4-python or scipy installed" - ) from err - return engine + raise ValueError( + "cannot read or write NetCDF files because none of " + "'netCDF4-python', 'h5netcdf', or 'scipy' are installed" + ) def _get_default_engine(path: str, allow_remote: bool = False) -> T_NetcdfEngine: diff --git a/xarray/tests/test_backends_api.py b/xarray/tests/test_backends_api.py index 778e800ec67..51c0aea1288 100644 --- a/xarray/tests/test_backends_api.py +++ b/xarray/tests/test_backends_api.py @@ -1,12 +1,13 @@ from __future__ import annotations +import sys from numbers import Number import numpy as np import pytest import xarray as xr -from xarray.backends.api import _get_default_engine +from xarray.backends.api import _get_default_engine, _get_default_engine_netcdf from xarray.tests import ( assert_identical, assert_no_warnings, @@ -29,6 +30,16 @@ def test__get_default_engine() -> None: assert engine_default == "netcdf4" +def test_default_engine_h5netcdf(monkeypatch): + """Test the default netcdf engine when h5netcdf is the only importable module.""" + + monkeypatch.delitem(sys.modules, "netCDF4", raising=False) + monkeypatch.delitem(sys.modules, "scipy", raising=False) + monkeypatch.setattr(sys, "meta_path", []) + + assert _get_default_engine_netcdf() == "h5netcdf" + + def test_custom_engine() -> None: expected = xr.Dataset( dict(a=2 * np.arange(5)), coords=dict(x=("x", np.arange(5), dict(units="s"))) From 2a3be0cb2bebde14451406b5c0cab9b4417c1cdc Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 25 Jul 2025 16:54:24 -0600 Subject: [PATCH 2/4] Update xarray/tests/test_backends_api.py --- xarray/tests/test_backends_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xarray/tests/test_backends_api.py b/xarray/tests/test_backends_api.py index 51c0aea1288..bfe99eed51b 100644 --- a/xarray/tests/test_backends_api.py +++ b/xarray/tests/test_backends_api.py @@ -30,6 +30,7 @@ def test__get_default_engine() -> None: assert engine_default == "netcdf4" +@requires_h5netcdf def test_default_engine_h5netcdf(monkeypatch): """Test the default netcdf engine when h5netcdf is the only importable module.""" From 53dc8a487ff53e77916fc2f0bff49d946f2eeb1b Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Sat, 26 Jul 2025 22:37:32 -0600 Subject: [PATCH 3/4] Update test_backends_api.py --- xarray/tests/test_backends_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xarray/tests/test_backends_api.py b/xarray/tests/test_backends_api.py index bfe99eed51b..ed487b07450 100644 --- a/xarray/tests/test_backends_api.py +++ b/xarray/tests/test_backends_api.py @@ -12,6 +12,7 @@ assert_identical, assert_no_warnings, requires_dask, + requires_h5netcdf, requires_netCDF4, requires_scipy, ) From 74bc9f690c5a02fd8f2ec66d350d6ba412dbc5d6 Mon Sep 17 00:00:00 2001 From: Scott Staniewicz Date: Wed, 30 Jul 2025 11:28:37 -0400 Subject: [PATCH 4/4] Add whats-new for `h5netcdf` --- doc/whats-new.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index bace038bb17..f5469fa5227 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -32,7 +32,8 @@ Bug fixes - Fix transpose of boolean arrays read from disk. (:issue:`10536`) By `Deepak Cherian `_. - +- Fix detection of the ``h5netcdf`` backend. Xarray now selects ``h5netcdf`` if the default ``netCDF4`` engine is not available (:issue:`10401`, :pull:`10557`). + By `Scott Staniewicz `_. Documentation ~~~~~~~~~~~~~