Skip to content

Commit

Permalink
Replace use of pkg_resources with importlib.metadata
Browse files Browse the repository at this point in the history
Use entry_points_wrap() workaround until Python 3.9 support is dropped
  • Loading branch information
tysmith committed Jun 26, 2024
1 parent 90584bc commit ae0886f
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 38 deletions.
16 changes: 8 additions & 8 deletions grizzly/common/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from logging import getLogger
from typing import Any, Dict, List, Tuple

from pkg_resources import iter_entry_points
from .utils import entry_points_wrap

__all__ = ("load", "scan", "PluginLoadError")

Expand All @@ -28,12 +28,12 @@ def load(name: str, group: str, base_type: type) -> Any:
Loaded plug-in object.
"""
assert isinstance(base_type, type)
for entry in iter_entry_points(group):
plugin = None
for entry in entry_points_wrap(group):
if entry.name == name:
LOG.debug("loading %r (%s)", name, base_type.__name__)
plugin = entry.load()
break
else:
if plugin is None:
raise PluginLoadError(f"{name!r} not found in {group!r}")
if not issubclass(plugin, base_type):
raise PluginLoadError(f"{name!r} doesn't inherit from {base_type.__name__}")
Expand All @@ -49,9 +49,9 @@ def scan(group: str) -> List[str]:
Returns:
Names of installed entry points.
"""
found = []
found: List[str] = []
LOG.debug("scanning %r", group)
for entry in iter_entry_points(group):
for entry in entry_points_wrap(group):
if entry.name in found:
# not sure if this can even happen
raise PluginLoadError(f"Duplicate entry {entry.name!r} in {group!r}")
Expand All @@ -68,7 +68,7 @@ def scan_target_assets() -> Dict[str, Tuple[str, ...]]:
Returns:
Name of target and list of supported assets.
"""
assets = {}
for entry in iter_entry_points("grizzly_targets"):
assets: Dict[str, Tuple[str, ...]] = {}
for entry in entry_points_wrap("grizzly_targets"):
assets[entry.name] = entry.load().SUPPORTED_ASSETS
return assets
44 changes: 24 additions & 20 deletions grizzly/common/test_plugins.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from pkg_resources import EntryPoint
from importlib.metadata import EntryPoint

from pytest import raises

from ..target import Target
Expand All @@ -19,31 +20,34 @@ class FakeType2:
def test_load_01(mocker):
"""test load() - nothing to load"""
mocker.patch(
"grizzly.common.plugins.iter_entry_points", autospec=True, return_value=[]
"grizzly.common.plugins.entry_points_wrap", autospec=True, return_value=()
)
with raises(PluginLoadError, match="'test-name' not found in 'test-group'"):
load("test-name", "test-group", FakeType1)


def test_load_02(mocker):
"""test load() - successful load"""
# Note: Mock.name cannot be set via the constructor so spec_set cannot be used
entry = mocker.Mock(spec=EntryPoint)
entry = mocker.Mock(spec_set=EntryPoint)
entry.name = "test-name"
entry.load.return_value = FakeType1
mocker.patch(
"grizzly.common.plugins.iter_entry_points", autospec=True, return_value=[entry]
"grizzly.common.plugins.entry_points_wrap",
autospec=True,
return_value=(entry,),
)
assert load("test-name", "test-group", FakeType1)


def test_load_03(mocker):
"""test load() - invalid type"""
entry = mocker.Mock(spec=EntryPoint)
entry = mocker.Mock(spec_set=EntryPoint)
entry.name = "test-name"
entry.load.return_value = FakeType1
mocker.patch(
"grizzly.common.plugins.iter_entry_points", autospec=True, return_value=[entry]
"grizzly.common.plugins.entry_points_wrap",
autospec=True,
return_value=(entry,),
)
with raises(PluginLoadError, match="'test-name' doesn't inherit from FakeType2"):
load("test-name", "test-group", FakeType2)
Expand All @@ -52,51 +56,51 @@ def test_load_03(mocker):
def test_scan_01(mocker):
"""test scan() - no entries found"""
mocker.patch(
"grizzly.common.plugins.iter_entry_points", autospec=True, return_value=[]
"grizzly.common.plugins.entry_points_wrap", autospec=True, return_value=()
)
assert not scan("test_group")


def test_scan_02(mocker):
"""test scan() - duplicate entry"""
entry = mocker.Mock(spec=EntryPoint)
entry = mocker.Mock(spec_set=EntryPoint)
entry.name = "test_entry"
mocker.patch(
"grizzly.common.plugins.iter_entry_points",
"grizzly.common.plugins.entry_points_wrap",
autospec=True,
return_value=[entry, entry],
return_value=(entry, entry),
)
with raises(PluginLoadError, match="Duplicate entry 'test_entry' in 'test_group'"):
scan("test_group")


def test_scan_03(mocker):
"""test scan() - success"""
entry = mocker.Mock(spec=EntryPoint)
entry = mocker.Mock(spec_set=EntryPoint)
entry.name = "test-name"
mocker.patch(
"grizzly.common.plugins.iter_entry_points",
"grizzly.common.plugins.entry_points_wrap",
autospec=True,
return_value=[entry],
return_value=(entry,),
)
assert "test-name" in scan("test_group")


def test_scan_target_assets_01(mocker):
"""test scan_target_assets() - success"""
targ1 = mocker.Mock(spec=EntryPoint)
targ1 = mocker.Mock(spec_set=EntryPoint)
targ1.name = "t1"
targ1.load.return_value = mocker.Mock(spec_set=Target, SUPPORTED_ASSETS=None)
targ2 = mocker.Mock(spec=EntryPoint)
targ1.load.return_value = mocker.Mock(spec_set=Target, SUPPORTED_ASSETS=())
targ2 = mocker.Mock(spec_set=EntryPoint)
targ2.name = "t2"
targ2.load.return_value = mocker.Mock(spec_set=Target, SUPPORTED_ASSETS=("a", "B"))
mocker.patch(
"grizzly.common.plugins.iter_entry_points",
"grizzly.common.plugins.entry_points_wrap",
autospec=True,
return_value=[targ1, targ2],
return_value=(targ1, targ2),
)
assets = scan_target_assets()
assert "t1" in assets
assert assets["t1"] is None
assert not assets["t1"]
assert "t2" in assets
assert "B" in assets["t2"]
24 changes: 23 additions & 1 deletion grizzly/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from enum import IntEnum, unique
from importlib.metadata import PackageNotFoundError, version
from importlib.metadata import EntryPoint, PackageNotFoundError, entry_points, version
from logging import DEBUG, basicConfig, getLogger
from os import getenv
from pathlib import Path
from sys import version_info
from tempfile import gettempdir
from typing import Any, Iterable, Optional, Tuple, Union

Expand Down Expand Up @@ -105,6 +106,27 @@ def display_time_limits(time_limit: int, timeout: int, no_harness: bool) -> None
LOG.warning("TIMEOUT DISABLED, not recommended for automation")


def entry_points_wrap(group: str) -> Tuple[EntryPoint, ...]:
"""Compatibility wrapper code for importlib.metadata.entry_points()
Args:
group: see entry_points().
Returns:
EntryPoints
"""
# TODO: remove this function when support for Python 3.9 is dropped
assert group
if version_info.minor >= 10:
eps = entry_points().select(group=group)
else:
try:
eps = dict(entry_points())[group]
except KeyError:
eps = () # type: ignore
return tuple(x for x in eps)


def package_version(name: str, default: str = "unknown") -> str:
"""Get version of an installed package.
Expand Down
6 changes: 2 additions & 4 deletions grizzly/reduce/strategies/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,8 @@
cast,
)

from pkg_resources import iter_entry_points

from ...common.storage import TestCase
from ...common.utils import grz_tmp
from ...common.utils import entry_points_wrap, grz_tmp

LOG = getLogger(__name__)

Expand Down Expand Up @@ -204,7 +202,7 @@ def _load_strategies() -> Dict[str, Type[Strategy]]:
A mapping of strategy names to strategy class.
"""
strategies: Dict[str, Type[Strategy]] = {}
for entry_point in iter_entry_points("grizzly_reduce_strategies"):
for entry_point in entry_points_wrap("grizzly_reduce_strategies"):
try:
strategy_cls = cast(Type[Strategy], entry_point.load())
assert (
Expand Down
9 changes: 4 additions & 5 deletions grizzly/reduce/test_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,10 @@ class _BadStrategy:
def load(cls):
raise RuntimeError("oops")

def entries(_):
yield _BadStrategy
yield _GoodStrategy

mocker.patch("grizzly.reduce.strategies.iter_entry_points", side_effect=entries)
mocker.patch(
"grizzly.reduce.strategies.entry_points_wrap",
return_value=(_BadStrategy, _GoodStrategy),
)
mocker.patch("grizzly.reduce.strategies.DEFAULT_STRATEGIES", new=("good",))
result = _load_strategies()
assert result == {"good": _GoodStrategy}
Expand Down

0 comments on commit ae0886f

Please sign in to comment.