From 2464e8df81943777b0d49ef3bc171b85989e6609 Mon Sep 17 00:00:00 2001 From: Jay Date: Tue, 9 Jun 2026 22:04:12 +0000 Subject: [PATCH 1/2] fix: guard against None version in module_available importlib.metadata.version() can return None in some environments (e.g. QGIS with custom import hooks, PEP 660 editable installs). When this happens, Version(None) crashes with TypeError. Guard the result so module_available returns False gracefully instead of crashing. Fixes #11344 Co-authored-by: Claude --- xarray/namedarray/utils.py | 3 +++ xarray/tests/test_namedarray.py | 25 ++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/xarray/namedarray/utils.py b/xarray/namedarray/utils.py index 3490a76aa8d..e4267637e17 100644 --- a/xarray/namedarray/utils.py +++ b/xarray/namedarray/utils.py @@ -60,6 +60,9 @@ def module_available(module: str, minversion: str | None = None) -> bool: if minversion is not None: version = importlib.metadata.version(module) + if version is None: + return False + return Version(version) >= Version(minversion) return True diff --git a/xarray/tests/test_namedarray.py b/xarray/tests/test_namedarray.py index 09ff1795ef4..08f14207897 100644 --- a/xarray/tests/test_namedarray.py +++ b/xarray/tests/test_namedarray.py @@ -18,7 +18,7 @@ _ShapeType_co, ) from xarray.namedarray.core import NamedArray, from_array -from xarray.namedarray.utils import fake_target_chunksize +from xarray.namedarray.utils import fake_target_chunksize, module_available from xarray.tests import requires_cftime if TYPE_CHECKING: @@ -666,3 +666,26 @@ def test_fake_target_chunksize_cftime() -> None: assert faked_chunksize == 73 assert dtype == np.float64 + + +def test_module_available_version_none() -> None: + """module_available should return False gracefully when metadata.version returns None.""" + import importlib.metadata as _metadata + + original_version = _metadata.version + + def mock_version(name: str) -> str | None: + if name == "pip": + return None + return original_version(name) + + _metadata.version = mock_version + try: + assert module_available("pip", minversion="1.0.0") is False + finally: + _metadata.version = original_version + + +def test_module_available_valid() -> None: + """module_available should return True for an installed module with version check.""" + assert module_available("pip", minversion="0.0.1") is True From 06ef651fefe29e0dc09047c4cb36ec9fcdc62c2b Mon Sep 17 00:00:00 2001 From: Jay Date: Wed, 10 Jun 2026 09:36:49 +0000 Subject: [PATCH 2/2] fix: use packaging instead of pip in module_available tests --- xarray/tests/test_namedarray.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xarray/tests/test_namedarray.py b/xarray/tests/test_namedarray.py index 08f14207897..93960735e13 100644 --- a/xarray/tests/test_namedarray.py +++ b/xarray/tests/test_namedarray.py @@ -675,17 +675,17 @@ def test_module_available_version_none() -> None: original_version = _metadata.version def mock_version(name: str) -> str | None: - if name == "pip": + if name == "packaging": return None return original_version(name) _metadata.version = mock_version try: - assert module_available("pip", minversion="1.0.0") is False + assert module_available("packaging", minversion="1.0.0") is False finally: _metadata.version = original_version def test_module_available_valid() -> None: """module_available should return True for an installed module with version check.""" - assert module_available("pip", minversion="0.0.1") is True + assert module_available("packaging", minversion="0.0.1") is True