Skip to content

Commit 3a8dc76

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 pytest-dev#15 and the accompanying PR (pytest-dev#43) which requires keeping track of spec default arguments values.
1 parent 01d3589 commit 3a8dc76

File tree

3 files changed

+28
-18
lines changed

3 files changed

+28
-18
lines changed

pluggy/__init__.py

+24-14
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,10 @@ def __init__(self, project_name, implprefix=None):
212212
self._implprefix = implprefix
213213
self._inner_hookexec = lambda hook, methods, kwargs: \
214214
hook.multicall(
215-
methods, kwargs, specopts=hook.spec_opts, hook=hook
215+
methods,
216+
kwargs,
217+
specopts=hook.spec.opts['firstresult'] if hook.spec else False,
218+
hook=hook
216219
)
217220

218221
def _hookexec(self, hook, methods, kwargs):
@@ -360,7 +363,7 @@ def _verify_hook(self, hook, hookimpl):
360363
(hookimpl.plugin_name, hook.name))
361364

362365
# positional arg checking
363-
notinspec = set(hookimpl.argnames) - set(hook.argnames)
366+
notinspec = set(hookimpl.argnames) - set(hook.spec.argnames)
364367
if notinspec:
365368
raise PluginValidationError(
366369
"Plugin %r for hook %r\nhookimpl definition: %s\n"
@@ -453,8 +456,8 @@ def subset_hook_caller(self, name, remove_plugins):
453456
orig = getattr(self.hook, name)
454457
plugins_to_remove = [plug for plug in remove_plugins if hasattr(plug, name)]
455458
if plugins_to_remove:
456-
hc = _HookCaller(orig.name, orig._hookexec, orig._specmodule_or_class,
457-
orig.spec_opts)
459+
hc = _HookCaller(orig.name, orig._hookexec, orig.spec.namespace,
460+
orig.spec.opts)
458461
for hookimpl in (orig._wrappers + orig._nonwrappers):
459462
plugin = hookimpl.plugin
460463
if plugin not in plugins_to_remove:
@@ -536,26 +539,23 @@ def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None)
536539
self.argnames = None
537540
self.kwargnames = None
538541
self.multicall = _multicall
542+
self.spec = None
543+
self._call_history = None
539544
if specmodule_or_class is not None:
540545
assert spec_opts is not None
541546
self.set_specification(specmodule_or_class, spec_opts)
542547

543548
def has_spec(self):
544-
return hasattr(self, "_specmodule_or_class")
549+
return self.spec is not None
545550

546551
def set_specification(self, specmodule_or_class, spec_opts):
547552
assert not self.has_spec()
548-
self._specmodule_or_class = specmodule_or_class
549-
specfunc = getattr(specmodule_or_class, self.name)
550-
# get spec arg signature
551-
argnames, self.kwargnames = varnames(specfunc)
552-
self.argnames = ["__multicall__"] + list(argnames)
553-
self.spec_opts = spec_opts
553+
self.spec = HookSpec(specmodule_or_class, self.name, spec_opts)
554554
if spec_opts.get("historic"):
555555
self._call_history = []
556556

557557
def is_historic(self):
558-
return hasattr(self, "_call_history")
558+
return self._call_history is not None
559559

560560
def _remove_plugin(self, plugin):
561561
def remove(wrappers):
@@ -601,8 +601,8 @@ def __call__(self, *args, **kwargs):
601601
if args:
602602
raise TypeError("hook calling supports only keyword arguments")
603603
assert not self.is_historic()
604-
if self.argnames:
605-
notincall = set(self.argnames) - set(['__multicall__']) - set(
604+
if self.spec:
605+
notincall = set(self.spec.argnames) - set(['__multicall__']) - set(
606606
kwargs.keys())
607607
if notincall:
608608
warnings.warn(
@@ -649,6 +649,16 @@ def _maybe_apply_history(self, method):
649649
proc(res[0])
650650

651651

652+
class HookSpec(object):
653+
def __init__(self, namespace, name, opts):
654+
self.namespace = namespace
655+
self.function = function = getattr(namespace, name)
656+
self.name = name
657+
self.argnames, self.kwargnames = varnames(function)
658+
self.opts = opts
659+
self.argnames = ["__multicall__"] + list(self.argnames)
660+
661+
652662
class HookImpl(object):
653663
def __init__(self, plugin, plugin_name, function, hook_impl_opts):
654664
self.function = function

testing/benchmark.py

+1-1
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

+3-3
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"])

0 commit comments

Comments
 (0)