Skip to content

Commit

Permalink
Merge pull request #251 from tlambert-forks/hookimpl-specname
Browse files Browse the repository at this point in the history
add specname option to hookimpl
  • Loading branch information
goodboy committed Feb 5, 2020
2 parents 50ee362 + e37c8d0 commit 6e8b6d4
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 1 deletion.
6 changes: 6 additions & 0 deletions src/pluggy/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def __call__(
optionalhook=False,
tryfirst=False,
trylast=False,
specname=None,
):

""" if passed a function, directly sets attributes on the function
Expand All @@ -96,6 +97,9 @@ def __call__(
representing the exception or result outcome of the inner calls (including other
hookwrapper calls).
If ``specname`` is provided, it will be used instead of the function name when
matching this hook implementation to a hook specification during registration.
"""

def setattr_hookimpl_opts(func):
Expand All @@ -107,6 +111,7 @@ def setattr_hookimpl_opts(func):
optionalhook=optionalhook,
tryfirst=tryfirst,
trylast=trylast,
specname=specname,
),
)
return func
Expand All @@ -122,6 +127,7 @@ def normalize_hookimpl_opts(opts):
opts.setdefault("trylast", False)
opts.setdefault("hookwrapper", False)
opts.setdefault("optionalhook", False)
opts.setdefault("specname", None)


if hasattr(inspect, "getfullargspec"):
Expand Down
1 change: 1 addition & 0 deletions src/pluggy/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def register(self, plugin, name=None):
normalize_hookimpl_opts(hookimpl_opts)
method = getattr(plugin, name)
hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts)
name = hookimpl_opts.get("specname") or name
hook = getattr(self.hook, name, None)
if hook is None:
hook = _HookCaller(name, self._hookexec)
Expand Down
59 changes: 58 additions & 1 deletion testing/test_hookcaller.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from pluggy import HookimplMarker, HookspecMarker
from pluggy import HookimplMarker, HookspecMarker, PluginValidationError
from pluggy.hooks import HookImpl

hookspec = HookspecMarker("example")
Expand Down Expand Up @@ -213,3 +213,60 @@ def hello(self, arg):
assert not hasattr(hook, "world")
pm.unregister(plugin)
assert hook.hello(arg=3) == []


def test_hookrelay_registration_by_specname(pm):
"""Verify hook caller instances may also be registered by specifying a
specname option to the hookimpl"""

class Api(object):
@hookspec
def hello(self, arg):
"api hook 1"

pm.add_hookspecs(Api)
hook = pm.hook
assert hasattr(hook, "hello")
assert len(pm.hook.hello.get_hookimpls()) == 0

class Plugin(object):
@hookimpl(specname="hello")
def foo(self, arg):
return arg + 1

plugin = Plugin()
pm.register(plugin)
out = hook.hello(arg=3)
assert out == [4]


def test_hookrelay_registration_by_specname_raises(pm):
"""Verify using specname still raises the types of errors during registration as it
would have without using specname."""

class Api(object):
@hookspec
def hello(self, arg):
"api hook 1"

pm.add_hookspecs(Api)

# make sure a bad signature still raises an error when using specname
class Plugin(object):
@hookimpl(specname="hello")
def foo(self, arg, too, many, args):
return arg + 1

with pytest.raises(PluginValidationError):
pm.register(Plugin())

# make sure check_pending still fails if specname doesn't have a
# corresponding spec. EVEN if the function name matches one.
class Plugin2(object):
@hookimpl(specname="bar")
def hello(self, arg):
return arg + 1

pm.register(Plugin2())
with pytest.raises(PluginValidationError):
pm.check_pending()

0 comments on commit 6e8b6d4

Please sign in to comment.