Skip to content
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

Switch to importlib-metadata #199

Merged
merged 5 commits into from
May 5, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
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
50 changes: 29 additions & 21 deletions 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,18 @@ 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):
continue
# is the plugin registered or blocked?
if 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
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
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):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runtime checking of dependency versions is not possible without the old and slow pkg_resources? ;-(

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm I guess that's not the way I saw that. any time I've dealt with pkg_resources the side-effect of looking up package metadata triggering VersionConflict I always saw as a nuisance. It's not really protecting you against anything, you're already in the broken state.

It would be possible to do with importlib-metadata, but traversing the dependency graph is wasted cpu cycles :)

"""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