From c090bc5ebeaaa33a336562277a4661b74a9dcae8 Mon Sep 17 00:00:00 2001 From: Tim Fuller Date: Thu, 7 Mar 2024 10:52:49 -0700 Subject: [PATCH] Drop optional dependencies of Spack (#43081) Remove dependency on `importlib_metadata` and `pkg_resources`, which can be problematic if the version in PYTHONPATH is incompatible with the interpreter Spack is running under. --- lib/spack/llnl/util/lang.py | 43 ++++++++-------------------- lib/spack/spack/test/entry_points.py | 36 ++++++++++++----------- 2 files changed, 31 insertions(+), 48 deletions(-) diff --git a/lib/spack/llnl/util/lang.py b/lib/spack/llnl/util/lang.py index ddca65c381..c59ecf2758 100644 --- a/lib/spack/llnl/util/lang.py +++ b/lib/spack/llnl/util/lang.py @@ -12,7 +12,6 @@ import re import sys import traceback -import warnings from datetime import datetime, timedelta from typing import Any, Callable, Iterable, List, Tuple @@ -847,43 +846,25 @@ def __repr__(self): def get_entry_points(*, group: str): """Wrapper for ``importlib.metadata.entry_points`` - Adapted from https://github.com/HypothesisWorks/hypothesis/blob/0a90ed6edf56319149956c7321d4110078a5c228/hypothesis-python/src/hypothesis/entry_points.py - Args: - group (str): the group of entry points to select + group: entry points to select Returns: - EntryPoints for ``group`` - + EntryPoints for ``group`` or empty list if unsupported """ try: - try: - from importlib import metadata as importlib_metadata # type: ignore # novermin - except ImportError: - import importlib_metadata # type: ignore # mypy thinks this is a redefinition - try: - entry_points = importlib_metadata.entry_points(group=group) - except TypeError: - # Prior to Python 3.10, entry_points accepted no parameters and always - # returned a dictionary of entry points, keyed by group. See - # https://docs.python.org/3/library/importlib.metadata.html#entry-points - entry_points = importlib_metadata.entry_points().get(group, []) - yield from entry_points + import importlib.metadata # type: ignore # novermin except ImportError: - # But if we're not on Python >= 3.8 and the importlib_metadata backport - # is not installed, we fall back to pkg_resources anyway. - try: - import pkg_resources # type: ignore - except ImportError: - warnings.warn( - "Under Python <= 3.7, Spack requires either the importlib_metadata " - "or setuptools package in order to load extensions via entrypoints.", - ImportWarning, - ) - yield from () - else: - yield from pkg_resources.iter_entry_points(group) + return [] + + try: + return importlib.metadata.entry_points(group=group) + except TypeError: + # Prior to Python 3.10, entry_points accepted no parameters and always + # returned a dictionary of entry points, keyed by group. See + # https://docs.python.org/3/library/importlib.metadata.html#entry-points + return importlib.metadata.entry_points().get(group, []) def load_module_from_file(module_name, module_path): diff --git a/lib/spack/spack/test/entry_points.py b/lib/spack/spack/test/entry_points.py index 6a7c543850..3909903c0c 100644 --- a/lib/spack/spack/test/entry_points.py +++ b/lib/spack/spack/test/entry_points.py @@ -8,6 +8,8 @@ import pytest +import llnl.util.lang + import spack.config import spack.extensions @@ -64,24 +66,12 @@ def entry_points(group=None): @pytest.fixture() -def mock_entry_points(tmp_path, monkeypatch): +def mock_get_entry_points(tmp_path, monkeypatch): entry_points = entry_points_factory(tmp_path) - try: - try: - import importlib.metadata as importlib_metadata # type: ignore # novermin - except ImportError: - import importlib_metadata - monkeypatch.setattr(importlib_metadata, "entry_points", entry_points) - except ImportError: - try: - import pkg_resources # type: ignore - except ImportError: - return - monkeypatch.setattr(pkg_resources, "iter_entry_points", entry_points) + monkeypatch.setattr(llnl.util.lang, "get_entry_points", entry_points) -@pytest.mark.skipif(sys.version_info[:2] < (3, 8), reason="Python>=3.8 required") -def test_spack_entry_point_config(tmp_path, mock_entry_points): +def test_spack_entry_point_config(tmp_path, mock_get_entry_points): """Test config scope entry point""" config_paths = dict(spack.config.config_paths_from_entry_points()) config_path = config_paths.get("plugin-mypackage_config") @@ -94,8 +84,7 @@ def test_spack_entry_point_config(tmp_path, mock_entry_points): assert config.get("config:install_tree:root", scope="plugin-mypackage_config") == "/spam/opt" -@pytest.mark.skipif(sys.version_info[:2] < (3, 8), reason="Python>=3.8 required") -def test_spack_entry_point_extension(tmp_path, mock_entry_points): +def test_spack_entry_point_extension(tmp_path, mock_get_entry_points): """Test config scope entry point""" my_ext = tmp_path / "spack/spack-myext" extensions = spack.extensions.get_extension_paths() @@ -110,3 +99,16 @@ def test_spack_entry_point_extension(tmp_path, mock_entry_points): assert os.path.samefile(root, my_ext) module = spack.extensions.get_module("spam") assert module is not None + + +@pytest.mark.skipif(sys.version_info[:2] < (3, 8), reason="Python>=3.8 required") +def test_llnl_util_lang_get_entry_points(tmp_path, monkeypatch): + import importlib.metadata # type: ignore # novermin + + monkeypatch.setattr(importlib.metadata, "entry_points", entry_points_factory(tmp_path)) + + entry_points = list(llnl.util.lang.get_entry_points(group="spack.config")) + assert isinstance(entry_points[0], MockConfigEntryPoint) + + entry_points = list(llnl.util.lang.get_entry_points(group="spack.extensions")) + assert isinstance(entry_points[0], MockExtensionsEntryPoint)