Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/804.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:meth:`PluginManager.load_setuptools_entrypoints() <pluggy.PluginManager.load_setuptools_entrypoints>` no longer counts, or records the distribution of, an entry point whose plugin was not registered: a plugin which loads to ``None``, or one which a :class:`~pluggy.PluginManager` subclass declines to register, as pytest's does for plugins whose names were merged into ``pytest``. With such a plugin installed, listing plugins through :meth:`~pluggy.PluginManager.list_plugin_distinfo` and :meth:`~pluggy.PluginManager.list_plugin_distributions` raised :exc:`KeyError`.
12 changes: 10 additions & 2 deletions src/pluggy/_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,8 +517,16 @@ def load_setuptools_entrypoints(self, group: str, name: str | None = None) -> in
):
continue
plugin = ep.load()
self.register(plugin, name=ep.name)
self._name2dist[ep.name] = dist
registered_name = self.register(plugin, name=ep.name)
if registered_name is None:
# register() declined to register this plugin: it returns
# None for a None plugin, and a PluginManager subclass may
# do the same (pytest's ignores deprecated plugin names).
# Nothing was registered, so nothing may be counted or
# remembered -- list_plugin_distributions() looks the names
# up in the plugin registry.
continue
self._name2dist[registered_name] = dist
count += 1
return count

Expand Down
43 changes: 43 additions & 0 deletions testing/test_pluginmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,49 @@ class Distribution:
assert pm.list_plugin_distributions() == [(a, dist)]


def test_load_setuptools_declined_plugin_is_not_recorded(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""An entry point whose plugin ``register()`` declines was not loaded.

``register()`` returns ``None`` when it registers nothing: for a ``None``
plugin, and for a ``PluginManager`` subclass which declines one, as
pytest's does for deprecated plugin names. Such an entry point must not be
counted, and must not be remembered for
:meth:`~pluggy.PluginManager.list_plugin_distinfo` and
:meth:`~pluggy.PluginManager.list_plugin_distributions`, which look the
recorded names up in the plugin registry.
"""

class EntryPoint:
group = "hello"

def __init__(self, name: str, target: object) -> None:
self.name = name
self._target = target

def load(self) -> object:
return self._target

class Distribution:
entry_points = (EntryPoint("null", None), EntryPoint("declined", object()))

dist = cast(importlib.metadata.Distribution, Distribution())
monkeypatch.setattr(importlib.metadata, "distributions", lambda: (dist,))

class DecliningPluginManager(PluginManager):
def register(self, plugin: object, name: str | None = None) -> str | None:
if name == "declined":
return None
return super().register(plugin, name=name)

pm = DecliningPluginManager("example")
assert pm.load_setuptools_entrypoints("hello") == 0
assert pm.list_name_plugin() == []
assert pm.list_plugin_distributions() == []
assert pm.list_plugin_distinfo() == []


def test_add_tracefuncs(he_pm: PluginManager) -> None:
out: list[Any] = []

Expand Down
Loading