From 9550fc30e06bf7037e7747356e58bc53d1d6e33b Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 6 Apr 2019 17:28:13 -0700 Subject: [PATCH 1/5] Switch to importlib-metadata --- docs/conf.py | 9 +++--- pluggy/manager.py | 39 ++++++++++------------ setup.py | 1 + testing/test_pluginmanager.py | 61 +++++++++-------------------------- 4 files changed, 38 insertions(+), 72 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index e752b101..550071d7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -import pkg_resources +import importlib_metadata extensions = [ @@ -20,14 +20,13 @@ # General information about the project. -dist = pkg_resources.get_distribution("pluggy") -project = dist.project_name +project = "pluggy" copyright = u"2016, Holger Krekel" author = "Holger Krekel" -release = dist.version +release = importlib_metadata.version(project) # The short X.Y version. -version = u".".join(dist.version.split(".")[:2]) +version = u".".join(release.split(".")[:2]) language = None diff --git a/pluggy/manager.py b/pluggy/manager.py index 351899a8..83c1d287 100644 --- a/pluggy/manager.py +++ b/pluggy/manager.py @@ -3,6 +3,8 @@ from .hooks import HookImpl, _HookRelay, _HookCaller, normalize_hookimpl_opts import warnings +import importlib_metadata + def _warn_for_function(warning, function): warnings.warn_explicit( @@ -259,29 +261,22 @@ def load_setuptools_entrypoints(self, group, name=None): :rtype: int :return: return the number of loaded plugins by this call. """ - from pkg_resources import ( - iter_entry_points, - DistributionNotFound, - VersionConflict, - ) - count = 0 - for ep in iter_entry_points(group, name=name): - # is the plugin registered or blocked? - if self.get_plugin(ep.name) or self.is_blocked(ep.name): - continue - try: - plugin = ep.load() - except DistributionNotFound: - continue - except VersionConflict as e: - raise PluginValidationError( - plugin=None, - message="Plugin %r could not be loaded: %s!" % (ep.name, e), - ) - self.register(plugin, name=ep.name) - self._plugin_distinfo.append((plugin, ep.dist)) - count += 1 + for dist in importlib_metadata.distributions(): + for ep in dist.entry_points: + if ep.group != group or (name is not None and ep.name != name): + continue + # is the plugin registered or blocked? + if self.get_plugin(ep.name) or self.is_blocked(ep.name): + continue + try: + plugin = ep.load() + except (ImportError, AttributeError): + continue + self.register(plugin, name=ep.name) + # TODO: `dist.project_name` is not a thing with importlib-metadata + self._plugin_distinfo.append((plugin, dist)) + count += 1 return count def list_plugin_distinfo(self): diff --git a/setup.py b/setup.py index d60e5fd9..866d194e 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ def main(): author_email="holger@merlinux.eu", url="https://github.com/pytest-dev/pluggy", python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + install_requires=["importlib-metadata>=0.9"], extras_require={"dev": ["pre-commit", "tox"]}, classifiers=classifiers, packages=["pluggy"], diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 20ede3c3..171357d3 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -3,7 +3,7 @@ """ import pytest import types -import sys +import importlib_metadata from pluggy import ( PluginManager, PluginValidationError, @@ -447,64 +447,35 @@ def example_hook(): def test_load_setuptools_instantiation(monkeypatch, pm): - pkg_resources = pytest.importorskip("pkg_resources") + class EntryPoint(object): + name = "myname" + group = "hello" + value = "myname:foo" - def my_iter(group, name=None): - assert group == "hello" + def load(self): + class PseudoPlugin(object): + x = 42 - class EntryPoint(object): - name = "myname" - dist = None + return PseudoPlugin() - def load(self): - class PseudoPlugin(object): - x = 42 + class Distribution(object): + entry_points = (EntryPoint(),) - return PseudoPlugin() + dist = Distribution() - return iter([EntryPoint()]) + def my_distributions(): + return (dist,) - monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter) + monkeypatch.setattr(importlib_metadata, "distributions", my_distributions) num = pm.load_setuptools_entrypoints("hello") assert num == 1 plugin = pm.get_plugin("myname") assert plugin.x == 42 - assert pm.list_plugin_distinfo() == [(plugin, None)] + assert pm.list_plugin_distinfo() == [(plugin, dist)] num = pm.load_setuptools_entrypoints("hello") assert num == 0 # no plugin loaded by this call -def test_load_setuptools_version_conflict(monkeypatch, pm): - """Check that we properly handle a VersionConflict problem when loading entry points""" - pkg_resources = pytest.importorskip("pkg_resources") - - def my_iter(group, name=None): - assert group == "hello" - - class EntryPoint(object): - name = "myname" - dist = None - - def load(self): - raise pkg_resources.VersionConflict("Some conflict") - - return iter([EntryPoint()]) - - monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter) - with pytest.raises( - PluginValidationError, - match="Plugin 'myname' could not be loaded: Some conflict!", - ): - pm.load_setuptools_entrypoints("hello") - - -def test_load_setuptools_not_installed(monkeypatch, pm): - monkeypatch.setitem(sys.modules, "pkg_resources", types.ModuleType("pkg_resources")) - - with pytest.raises(ImportError): - pm.load_setuptools_entrypoints("qwe") - - def test_add_tracefuncs(he_pm): out = [] From 491cefe68887eb190348b29556cfc0a42a692c08 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 7 Apr 2019 13:47:32 -0700 Subject: [PATCH 2/5] Add a Facade to pretend to be pkg_resources --- pluggy/manager.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/pluggy/manager.py b/pluggy/manager.py index 83c1d287..d7b730fc 100644 --- a/pluggy/manager.py +++ b/pluggy/manager.py @@ -27,6 +27,23 @@ def __init__(self, plugin, message): super(Exception, self).__init__(message) +class DistFacade(object): + """Emulate a pkg_resources Distribution""" + + def __init__(self, dist): + self._dist = dist + + @property + def project_name(self): + return self.metadata["name"] + + def __getattr__(self, attr, default=None): + return getattr(self._dist, attr, default) + + def __dir__(self): + return sorted(dir(self._dist) + ["_dist", "project_name"]) + + class PluginManager(object): """ Core Pluginmanager class which manages registration of plugin objects and 1:N hook calling. @@ -274,8 +291,7 @@ def load_setuptools_entrypoints(self, group, name=None): except (ImportError, AttributeError): continue self.register(plugin, name=ep.name) - # TODO: `dist.project_name` is not a thing with importlib-metadata - self._plugin_distinfo.append((plugin, dist)) + self._plugin_distinfo.append((plugin, DistFacade(dist))) count += 1 return count From e3875879865100e86cad88050e63f4f0df9671a7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 20 Apr 2019 16:08:55 -0700 Subject: [PATCH 3/5] Fix test for DistFacade --- testing/test_pluginmanager.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 171357d3..b226c413 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -471,7 +471,12 @@ def my_distributions(): assert num == 1 plugin = pm.get_plugin("myname") assert plugin.x == 42 - assert pm.list_plugin_distinfo() == [(plugin, dist)] + ret = pm.list_plugin_distinfo() + # poor man's `assert ret == [(plugin, mock.ANY)]` + assert len(ret) == 1 + assert len(ret[0]) == 2 + assert ret[0][0] == plugin + assert ret[0][1]._dist == dist num = pm.load_setuptools_entrypoints("hello") assert num == 0 # no plugin loaded by this call From f282a42a22aee250b09c3e6be91a2faae2253858 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 20 Apr 2019 17:37:10 -0700 Subject: [PATCH 4/5] Expose the ImportError / AttributeError to the caller --- pluggy/manager.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pluggy/manager.py b/pluggy/manager.py index d7b730fc..4de1497b 100644 --- a/pluggy/manager.py +++ b/pluggy/manager.py @@ -286,10 +286,7 @@ def load_setuptools_entrypoints(self, group, name=None): # is the plugin registered or blocked? if self.get_plugin(ep.name) or self.is_blocked(ep.name): continue - try: - plugin = ep.load() - except (ImportError, AttributeError): - continue + plugin = ep.load() self.register(plugin, name=ep.name) self._plugin_distinfo.append((plugin, DistFacade(dist))) count += 1 From 0706fbaa708a532d5ad1533b29cad5fe92d8e061 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 20 Apr 2019 18:02:13 -0700 Subject: [PATCH 5/5] Invoke tox directly (?) --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 61da86e1..176c56a2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,7 +31,7 @@ install: build: false # Not a C# project, build stuff at the test step instead. test_script: - - C:\Python35\python -m tox + - C:\Python35\Scripts\tox # We don't deploy anything on tags with AppVeyor, we use Travis instead, so we # might as well save resources