Skip to content

Commit 6e8b6d4

Browse files
authored
Merge pull request #251 from tlambert-forks/hookimpl-specname
add specname option to hookimpl
2 parents 50ee362 + e37c8d0 commit 6e8b6d4

File tree

3 files changed

+65
-1
lines changed

3 files changed

+65
-1
lines changed

src/pluggy/hooks.py

+6
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def __call__(
7373
optionalhook=False,
7474
tryfirst=False,
7575
trylast=False,
76+
specname=None,
7677
):
7778

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

101105
def setattr_hookimpl_opts(func):
@@ -107,6 +111,7 @@ def setattr_hookimpl_opts(func):
107111
optionalhook=optionalhook,
108112
tryfirst=tryfirst,
109113
trylast=trylast,
114+
specname=specname,
110115
),
111116
)
112117
return func
@@ -122,6 +127,7 @@ def normalize_hookimpl_opts(opts):
122127
opts.setdefault("trylast", False)
123128
opts.setdefault("hookwrapper", False)
124129
opts.setdefault("optionalhook", False)
130+
opts.setdefault("specname", None)
125131

126132

127133
if hasattr(inspect, "getfullargspec"):

src/pluggy/manager.py

+1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def register(self, plugin, name=None):
118118
normalize_hookimpl_opts(hookimpl_opts)
119119
method = getattr(plugin, name)
120120
hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts)
121+
name = hookimpl_opts.get("specname") or name
121122
hook = getattr(self.hook, name, None)
122123
if hook is None:
123124
hook = _HookCaller(name, self._hookexec)

testing/test_hookcaller.py

+58-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
from pluggy import HookimplMarker, HookspecMarker
3+
from pluggy import HookimplMarker, HookspecMarker, PluginValidationError
44
from pluggy.hooks import HookImpl
55

66
hookspec = HookspecMarker("example")
@@ -213,3 +213,60 @@ def hello(self, arg):
213213
assert not hasattr(hook, "world")
214214
pm.unregister(plugin)
215215
assert hook.hello(arg=3) == []
216+
217+
218+
def test_hookrelay_registration_by_specname(pm):
219+
"""Verify hook caller instances may also be registered by specifying a
220+
specname option to the hookimpl"""
221+
222+
class Api(object):
223+
@hookspec
224+
def hello(self, arg):
225+
"api hook 1"
226+
227+
pm.add_hookspecs(Api)
228+
hook = pm.hook
229+
assert hasattr(hook, "hello")
230+
assert len(pm.hook.hello.get_hookimpls()) == 0
231+
232+
class Plugin(object):
233+
@hookimpl(specname="hello")
234+
def foo(self, arg):
235+
return arg + 1
236+
237+
plugin = Plugin()
238+
pm.register(plugin)
239+
out = hook.hello(arg=3)
240+
assert out == [4]
241+
242+
243+
def test_hookrelay_registration_by_specname_raises(pm):
244+
"""Verify using specname still raises the types of errors during registration as it
245+
would have without using specname."""
246+
247+
class Api(object):
248+
@hookspec
249+
def hello(self, arg):
250+
"api hook 1"
251+
252+
pm.add_hookspecs(Api)
253+
254+
# make sure a bad signature still raises an error when using specname
255+
class Plugin(object):
256+
@hookimpl(specname="hello")
257+
def foo(self, arg, too, many, args):
258+
return arg + 1
259+
260+
with pytest.raises(PluginValidationError):
261+
pm.register(Plugin())
262+
263+
# make sure check_pending still fails if specname doesn't have a
264+
# corresponding spec. EVEN if the function name matches one.
265+
class Plugin2(object):
266+
@hookimpl(specname="bar")
267+
def hello(self, arg):
268+
return arg + 1
269+
270+
pm.register(Plugin2())
271+
with pytest.raises(PluginValidationError):
272+
pm.check_pending()

0 commit comments

Comments
 (0)