Skip to content

Commit f128f0f

Browse files
committed
- add basic support for recognizing hook impls by prefix
- add list_plugin_distinfo and list_name_plugin helpers to get at pluginmanager information without using underscores.
1 parent e02fe00 commit f128f0f

File tree

2 files changed

+84
-3
lines changed

2 files changed

+84
-3
lines changed

pluggy.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ def setattr_hookimpl_opts(func):
157157
return setattr_hookimpl_opts(function)
158158

159159

160+
def normalize_hookimpl_opts(opts):
161+
opts.setdefault("tryfirst", False)
162+
opts.setdefault("trylast", False)
163+
opts.setdefault("hookwrapper", False)
164+
opts.setdefault("optionalhook", False)
165+
166+
160167
class _TagTracer:
161168
def __init__(self):
162169
self._tag2proc = {}
@@ -310,13 +317,16 @@ class PluginManager(object):
310317
which will subsequently send debug information to the trace helper.
311318
"""
312319

313-
def __init__(self, project_name):
320+
def __init__(self, project_name, implprefix=None):
321+
""" if implprefix is given implementation functions
322+
will be recognized if their name matches the implprefix. """
314323
self.project_name = project_name
315324
self._name2plugin = {}
316325
self._plugin2hookcallers = {}
317326
self._plugin_distinfo = []
318327
self.trace = _TagTracer().get("pluginmanage")
319328
self.hook = _HookRelay(self.trace.root.get("hook"))
329+
self._implprefix = implprefix
320330
self._inner_hookexec = lambda hook, methods, kwargs: \
321331
_MultiCall(methods, kwargs, hook.spec_opts).execute()
322332

@@ -380,6 +390,7 @@ def register(self, plugin, name=None):
380390
for name in dir(plugin):
381391
hookimpl_opts = self.parse_hookimpl_opts(plugin, name)
382392
if hookimpl_opts is not None:
393+
normalize_hookimpl_opts(hookimpl_opts)
383394
method = getattr(plugin, name)
384395
hookimpl = _HookImpl(plugin, plugin_name, method, hookimpl_opts)
385396
hook = getattr(self.hook, name, None)
@@ -399,6 +410,8 @@ def parse_hookimpl_opts(self, plugin, name):
399410
if res is not None and not isinstance(res, dict):
400411
# false positive
401412
res = None
413+
elif res is None and self._implprefix and name.startswith(self._implprefix):
414+
res = {}
402415
return res
403416

404417
def parse_hookspec_opts(self, module_or_class, name):
@@ -524,9 +537,18 @@ def load_setuptools_entrypoints(self, entrypoint_name):
524537
except DistributionNotFound:
525538
continue
526539
self.register(plugin, name=ep.name)
527-
self._plugin_distinfo.append((ep.dist, plugin))
540+
self._plugin_distinfo.append((plugin, ep.dist))
528541
return len(self._plugin_distinfo)
529542

543+
def list_plugin_distinfo(self):
544+
""" return list of distinfo/plugin tuples for all setuptools registered
545+
plugins. """
546+
return list(self._plugin_distinfo)
547+
548+
def list_name_plugin(self):
549+
""" return list of name/plugin pairs. """
550+
return list(self._name2plugin.items())
551+
530552

531553
class _MultiCall:
532554
""" execute a call into multiple python functions/methods. """

test_pluggy.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ class A:
5252
assert pm.unregister(a1) == a1
5353
assert not pm.is_registered(a1)
5454

55+
l = pm.list_name_plugin()
56+
assert len(l) == 1
57+
assert l == [("hello", a2)]
58+
5559
def test_register_dynamic_attr(self, he_pm):
5660
class A:
5761
def __getattr__(self, name):
@@ -484,7 +488,7 @@ class PseudoPlugin:
484488
assert num == 1
485489
plugin = pm.get_plugin("myname")
486490
assert plugin.x == 42
487-
assert pm._plugin_distinfo == [(None, plugin)]
491+
assert pm.list_plugin_distinfo() == [(plugin, None)]
488492

489493
def test_load_setuptools_not_installed(self, monkeypatch, pm):
490494
monkeypatch.setitem(sys.modules, 'pkg_resources',
@@ -529,6 +533,61 @@ def he_method1(self):
529533
finally:
530534
undo()
531535

536+
def test_prefix_hookimpl(self):
537+
pm = PluginManager(hookspec.project_name, "hello_")
538+
class HookSpec:
539+
@hookspec
540+
def hello_myhook(self, arg1):
541+
""" add to arg1 """
542+
543+
pm.add_hookspecs(HookSpec)
544+
545+
class Plugin:
546+
def hello_myhook(self, arg1):
547+
return arg1 + 1
548+
549+
pm.register(Plugin())
550+
pm.register(Plugin())
551+
results = pm.hook.hello_myhook(arg1=17)
552+
assert results == [18, 18]
553+
554+
def test_parse_hookimpl_override():
555+
class MyPluginManager(PluginManager):
556+
def parse_hookimpl_opts(self, module_or_class, name):
557+
opts = PluginManager.parse_hookimpl_opts(self, module_or_class, name)
558+
if opts is None:
559+
if name.startswith("x1"):
560+
opts = {}
561+
return opts
562+
563+
class Plugin:
564+
def x1meth(self):
565+
pass
566+
567+
@hookimpl(hookwrapper=True, tryfirst=True)
568+
def x1meth2(self):
569+
pass
570+
571+
class Spec:
572+
@hookspec
573+
def x1meth(self):
574+
pass
575+
@hookspec
576+
def x1meth2(self):
577+
pass
578+
579+
580+
pm = MyPluginManager(hookspec.project_name)
581+
pm.register(Plugin())
582+
pm.add_hookspecs(Spec)
583+
assert not pm.hook.x1meth._nonwrappers[0].hookwrapper
584+
assert not pm.hook.x1meth._nonwrappers[0].tryfirst
585+
assert not pm.hook.x1meth._nonwrappers[0].trylast
586+
assert not pm.hook.x1meth._nonwrappers[0].optionalhook
587+
588+
assert pm.hook.x1meth2._wrappers[0].tryfirst
589+
assert pm.hook.x1meth2._wrappers[0].hookwrapper
590+
532591

533592
def test_varnames():
534593
def f(x):

0 commit comments

Comments
 (0)