Skip to content

Commit cc524a6

Browse files
author
Tyler Goodlet
committed
Encapsulate spec definitions with a class
Allows for easier introspection of spec definitions including function signatures and hook options. Originally introduced to address #15 and the accompanying PR (#43) which requires keeping track of spec default arguments values.
1 parent b289c9e commit cc524a6

File tree

4 files changed

+36
-23
lines changed

4 files changed

+36
-23
lines changed

pluggy/__init__.py

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,10 @@ def __init__(self, project_name, implprefix=None):
231231
self._implprefix = implprefix
232232
self._inner_hookexec = lambda hook, methods, kwargs: \
233233
hook.multicall(
234-
methods, kwargs, specopts=hook.spec_opts, hook=hook
234+
methods,
235+
kwargs,
236+
specopts=hook.spec.opts['firstresult'] if hook.spec else False,
237+
hook=hook
235238
).execute()
236239

237240
def _hookexec(self, hook, methods, kwargs):
@@ -379,7 +382,7 @@ def _verify_hook(self, hook, hookimpl):
379382
(hookimpl.plugin_name, hook.name))
380383

381384
# positional arg checking
382-
notinspec = set(hookimpl.argnames) - set(hook.argnames)
385+
notinspec = set(hookimpl.argnames) - set(hook.spec.argnames)
383386
if notinspec:
384387
raise PluginValidationError(
385388
"Plugin %r for hook %r\nhookimpl definition: %s\n"
@@ -472,8 +475,8 @@ def subset_hook_caller(self, name, remove_plugins):
472475
orig = getattr(self.hook, name)
473476
plugins_to_remove = [plug for plug in remove_plugins if hasattr(plug, name)]
474477
if plugins_to_remove:
475-
hc = _HookCaller(orig.name, orig._hookexec, orig._specmodule_or_class,
476-
orig.spec_opts)
478+
hc = _HookCaller(orig.name, orig._hookexec, orig.spec.namespace,
479+
orig.spec.opts)
477480
for hookimpl in (orig._wrappers + orig._nonwrappers):
478481
plugin = hookimpl.plugin
479482
if plugin not in plugins_to_remove:
@@ -494,17 +497,18 @@ class _LegacyMultiCall(object):
494497
# so we can remove it soon, allowing to avoid the below recursion
495498
# in execute() and simplify/speed up the execute loop.
496499

497-
def __init__(self, hook_impls, kwargs, specopts={}, hook=None):
498-
self.hook = hook
500+
def __init__(self, hook_impls, kwargs, firstresult=False, hook=None):
499501
self.hook_impls = hook_impls
500502
self.caller_kwargs = kwargs # come from _HookCaller.__call__()
501503
self.caller_kwargs["__multicall__"] = self
502-
self.specopts = hook.spec_opts if hook else specopts
504+
self.firstresult = firstresult
505+
self.hook = hook
506+
self.spec = hook.spec if hook else None
503507

504508
def execute(self):
505509
caller_kwargs = self.caller_kwargs
506510
self.results = results = []
507-
firstresult = self.specopts.get("firstresult")
511+
firstresult = self.firstresult
508512

509513
while self.hook_impls:
510514
hook_impl = self.hook_impls.pop()
@@ -515,8 +519,10 @@ def execute(self):
515519
if argname not in caller_kwargs:
516520
raise HookCallError(
517521
"hook call must provide argument %r" % (argname,))
522+
518523
if hook_impl.hookwrapper:
519524
return _wrapped_call(hook_impl.function(*args), self.execute)
525+
520526
res = hook_impl.function(*args)
521527
if res is not None:
522528
if firstresult:
@@ -603,26 +609,23 @@ def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None)
603609
self.argnames = None
604610
self.kwargnames = None
605611
self.multicall = _MultiCall
612+
self.spec = None
613+
self._call_history = None
606614
if specmodule_or_class is not None:
607615
assert spec_opts is not None
608616
self.set_specification(specmodule_or_class, spec_opts)
609617

610618
def has_spec(self):
611-
return hasattr(self, "_specmodule_or_class")
619+
return self.spec is not None
612620

613621
def set_specification(self, specmodule_or_class, spec_opts):
614622
assert not self.has_spec()
615-
self._specmodule_or_class = specmodule_or_class
616-
specfunc = getattr(specmodule_or_class, self.name)
617-
# get spec arg signature
618-
argnames, self.kwargnames = varnames(specfunc)
619-
self.argnames = ["__multicall__"] + list(argnames)
620-
self.spec_opts = spec_opts
623+
self.spec = HookSpec(specmodule_or_class, self.name, spec_opts)
621624
if spec_opts.get("historic"):
622625
self._call_history = []
623626

624627
def is_historic(self):
625-
return hasattr(self, "_call_history")
628+
return self._call_history is not None
626629

627630
def _remove_plugin(self, plugin):
628631
def remove(wrappers):
@@ -668,8 +671,8 @@ def __call__(self, *args, **kwargs):
668671
if args:
669672
raise TypeError("hook calling supports only keyword arguments")
670673
assert not self.is_historic()
671-
if self.argnames:
672-
notincall = set(self.argnames) - set(['__multicall__']) - set(
674+
if self.spec:
675+
notincall = set(self.spec.argnames) - set(['__multicall__']) - set(
673676
kwargs.keys())
674677
if notincall:
675678
warnings.warn(
@@ -715,6 +718,16 @@ def _maybe_apply_history(self, method):
715718
proc(res[0])
716719

717720

721+
class HookSpec(object):
722+
def __init__(self, namespace, name, opts):
723+
self.namespace = namespace
724+
self.function = function = getattr(namespace, name)
725+
self.name = name
726+
self.argnames, self.kwargnames = varnames(function)
727+
self.opts = opts
728+
self.argnames = ["__multicall__"] + list(self.argnames)
729+
730+
718731
class HookImpl(object):
719732
def __init__(self, plugin, plugin_name, function, hook_impl_opts):
720733
self.function = function

testing/benchmark.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def MC(methods, kwargs, callertype, firstresult=False):
1414
for method in methods:
1515
f = HookImpl(None, "<temp>", method, method.example_impl)
1616
hookfuncs.append(f)
17-
return callertype(hookfuncs, kwargs, {"firstresult": firstresult})
17+
return callertype(hookfuncs, kwargs, firstresult=firstresult)
1818

1919

2020
@hookimpl

testing/test_method_ordering.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,9 @@ def he_myhook3(arg1):
163163
pass
164164

165165
pm.add_hookspecs(HookSpec)
166-
assert not pm.hook.he_myhook1.spec_opts["firstresult"]
167-
assert pm.hook.he_myhook2.spec_opts["firstresult"]
168-
assert not pm.hook.he_myhook3.spec_opts["firstresult"]
166+
assert not pm.hook.he_myhook1.spec.opts["firstresult"]
167+
assert pm.hook.he_myhook2.spec.opts["firstresult"]
168+
assert not pm.hook.he_myhook3.spec.opts["firstresult"]
169169

170170

171171
@pytest.mark.parametrize('name', ["hookwrapper", "optionalhook", "tryfirst", "trylast"])

testing/test_multicall.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def MC(methods, kwargs, firstresult=False):
2525
hookfuncs.append(f)
2626
if '__multicall__' in f.argnames:
2727
caller = _LegacyMultiCall
28-
return caller(hookfuncs, kwargs, {"firstresult": firstresult})
28+
return caller(hookfuncs, kwargs, firstresult=firstresult)
2929

3030

3131
def test_call_passing():

0 commit comments

Comments
 (0)