Skip to content

importlib-metadata again #215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 27, 2019
Merged
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/215.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Switch from ``pkg_resources`` to ``importlib-metadata`` for entrypoint detection for improved performance and import time. This time with ``.egg`` support.
9 changes: 4 additions & 5 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
import pkg_resources
import importlib_metadata


extensions = [
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def main():
author_email="[email protected]",
url="https://github.com/pytest-dev/pluggy",
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
install_requires=["importlib-metadata>=0.12"],
extras_require={"dev": ["pre-commit", "tox"]},
classifiers=classifiers,
packages=["pluggy"],
Expand Down
53 changes: 32 additions & 21 deletions src/pluggy/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -25,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.
Expand Down Expand Up @@ -259,29 +278,21 @@ 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:
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)
# already registered
or self.get_plugin(ep.name)
or self.is_blocked(ep.name)
):
continue
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
self.register(plugin, name=ep.name)
self._plugin_distinfo.append((plugin, DistFacade(dist)))
count += 1
return count

def list_plugin_distinfo(self):
Expand Down
66 changes: 21 additions & 45 deletions testing/test_pluginmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
import pytest
import types
import sys
import importlib_metadata
from pluggy import (
PluginManager,
PluginValidationError,
Expand Down Expand Up @@ -447,64 +447,40 @@ 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)]
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


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 = []

Expand Down