diff --git a/doc/whats-new.rst b/doc/whats-new.rst index f499cbe3d21..b66c99d0bcb 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -36,6 +36,8 @@ Bug fixes ~~~~~~~~~ - Fix plot.line crash for data of shape ``(1, N)`` in _title_for_slice on format_item (:pull:`5948`). By `Sebastian Weigand `_. +- Fix a regression in the removal of duplicate backend entrypoints (:issue:`5944`, :pull:`5959`) + By `Kai Mühlbauer `_. Documentation ~~~~~~~~~~~~~ @@ -49,6 +51,10 @@ Documentation Internal Changes ~~~~~~~~~~~~~~~~ +- Use ``importlib`` to replace functionality of ``pkg_resources`` in + backend plugins tests. (:pull:`5959`). + By `Kai Mühlbauer `_. + .. _whats-new.0.20.1: diff --git a/xarray/backends/plugins.py b/xarray/backends/plugins.py index a0f46fad5b7..0a9ffcbda22 100644 --- a/xarray/backends/plugins.py +++ b/xarray/backends/plugins.py @@ -1,15 +1,16 @@ import functools import inspect import itertools +import sys import warnings from .common import BACKEND_ENTRYPOINTS, BackendEntrypoint -try: +if sys.version_info >= (3, 8): from importlib.metadata import entry_points -except ImportError: +else: # if the fallback library is missing, we are doomed. - from importlib_metadata import entry_points # type: ignore + from importlib_metadata import entry_points STANDARD_BACKENDS_ORDER = ["netcdf4", "h5netcdf", "scipy"] @@ -22,15 +23,17 @@ def remove_duplicates(entrypoints): # check if there are multiple entrypoints for the same name unique_entrypoints = [] for name, matches in entrypoints_grouped: - matches = list(matches) + # remove equal entrypoints + matches = list(set(matches)) unique_entrypoints.append(matches[0]) matches_len = len(matches) if matches_len > 1: - selected_module_name = matches[0].module_name - all_module_names = [e.module_name for e in matches] + all_module_names = [e.value.split(":")[0] for e in matches] + selected_module_name = all_module_names[0] warnings.warn( f"Found {matches_len} entrypoints for the engine name {name}:" - f"\n {all_module_names}.\n It will be used: {selected_module_name}.", + f"\n {all_module_names}.\n " + f"The entrypoint {selected_module_name} will be used.", RuntimeWarning, ) return unique_entrypoints diff --git a/xarray/tests/test_plugins.py b/xarray/tests/test_plugins.py index 7f77a677d6d..4d1eee6363d 100644 --- a/xarray/tests/test_plugins.py +++ b/xarray/tests/test_plugins.py @@ -1,10 +1,20 @@ +import sys from unittest import mock -import pkg_resources import pytest from xarray.backends import common, plugins +if sys.version_info >= (3, 8): + from importlib.metadata import EntryPoint + + importlib_metadata_mock = "importlib.metadata" +else: + # if the fallback library is missing, we are doomed. + from importlib_metadata import EntryPoint + + importlib_metadata_mock = "importlib_metadata" + class DummyBackendEntrypointArgs(common.BackendEntrypoint): def open_dataset(filename_or_obj, *args): @@ -29,12 +39,12 @@ def open_dataset(self, filename_or_obj, *, decoder): @pytest.fixture def dummy_duplicated_entrypoints(): specs = [ - "engine1 = xarray.tests.test_plugins:backend_1", - "engine1 = xarray.tests.test_plugins:backend_2", - "engine2 = xarray.tests.test_plugins:backend_1", - "engine2 = xarray.tests.test_plugins:backend_2", + ["engine1", "xarray.tests.test_plugins:backend_1", "xarray.backends"], + ["engine1", "xarray.tests.test_plugins:backend_2", "xarray.backends"], + ["engine2", "xarray.tests.test_plugins:backend_1", "xarray.backends"], + ["engine2", "xarray.tests.test_plugins:backend_2", "xarray.backends"], ] - eps = [pkg_resources.EntryPoint.parse(spec) for spec in specs] + eps = [EntryPoint(name, value, group) for name, value, group in specs] return eps @@ -46,8 +56,10 @@ def test_remove_duplicates(dummy_duplicated_entrypoints) -> None: def test_broken_plugin() -> None: - broken_backend = pkg_resources.EntryPoint.parse( - "broken_backend = xarray.tests.test_plugins:backend_1" + broken_backend = EntryPoint( + "broken_backend", + "xarray.tests.test_plugins:backend_1", + "xarray.backends", ) with pytest.warns(RuntimeWarning) as record: _ = plugins.build_engines([broken_backend]) @@ -68,13 +80,15 @@ def test_remove_duplicates_warnings(dummy_duplicated_entrypoints) -> None: assert "entrypoints" in message1 -@mock.patch("pkg_resources.EntryPoint.load", mock.MagicMock(return_value=None)) +@mock.patch( + f"{importlib_metadata_mock}.EntryPoint.load", mock.MagicMock(return_value=None) +) def test_backends_dict_from_pkg() -> None: specs = [ - "engine1 = xarray.tests.test_plugins:backend_1", - "engine2 = xarray.tests.test_plugins:backend_2", + ["engine1", "xarray.tests.test_plugins:backend_1", "xarray.backends"], + ["engine2", "xarray.tests.test_plugins:backend_2", "xarray.backends"], ] - entrypoints = [pkg_resources.EntryPoint.parse(spec) for spec in specs] + entrypoints = [EntryPoint(name, value, group) for name, value, group in specs] engines = plugins.backends_dict_from_pkg(entrypoints) assert len(engines) == 2 assert engines.keys() == set(("engine1", "engine2")) @@ -114,12 +128,12 @@ def test_set_missing_parameters_raise_error() -> None: @mock.patch( - "pkg_resources.EntryPoint.load", + f"{importlib_metadata_mock}.EntryPoint.load", mock.MagicMock(return_value=DummyBackendEntrypoint1), ) def test_build_engines() -> None: - dummy_pkg_entrypoint = pkg_resources.EntryPoint.parse( - "cfgrib = xarray.tests.test_plugins:backend_1" + dummy_pkg_entrypoint = EntryPoint( + "cfgrib", "xarray.tests.test_plugins:backend_1", "xarray_backends" ) backend_entrypoints = plugins.build_engines([dummy_pkg_entrypoint]) @@ -131,17 +145,13 @@ def test_build_engines() -> None: @mock.patch( - "pkg_resources.EntryPoint.load", + f"{importlib_metadata_mock}.EntryPoint.load", mock.MagicMock(return_value=DummyBackendEntrypoint1), ) def test_build_engines_sorted() -> None: dummy_pkg_entrypoints = [ - pkg_resources.EntryPoint.parse( - "dummy2 = xarray.tests.test_plugins:backend_1", - ), - pkg_resources.EntryPoint.parse( - "dummy1 = xarray.tests.test_plugins:backend_1", - ), + EntryPoint("dummy2", "xarray.tests.test_plugins:backend_1", "xarray.backends"), + EntryPoint("dummy1", "xarray.tests.test_plugins:backend_1", "xarray.backends"), ] backend_entrypoints = plugins.build_engines(dummy_pkg_entrypoints) backend_entrypoints = list(backend_entrypoints)