Skip to content

Commit 23dc109

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 6587ed0 commit 23dc109

File tree

4 files changed

+30
-21
lines changed

4 files changed

+30
-21
lines changed

pluggy/hooks.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -202,28 +202,22 @@ def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None)
202202
self._wrappers = []
203203
self._nonwrappers = []
204204
self._hookexec = hook_execute
205-
self._specmodule_or_class = None
206205
self.argnames = None
207206
self.kwargnames = None
208207
self.multicall = _multicall
209-
self.spec_opts = spec_opts or {}
208+
self.spec = None
210209
if specmodule_or_class is not None:
210+
assert spec_opts is not None
211211
self.set_specification(specmodule_or_class, spec_opts)
212212

213213
def has_spec(self):
214-
return self._specmodule_or_class is not None
214+
return True if self.spec is not None else False
215215

216216
def set_specification(self, specmodule_or_class, spec_opts):
217217
assert not self.has_spec()
218-
self._specmodule_or_class = specmodule_or_class
219-
specfunc = getattr(specmodule_or_class, self.name)
220-
# get spec arg signature
221-
argnames, self.kwargnames = varnames(specfunc)
222-
self.argnames = ["__multicall__"] + list(argnames)
223-
self.spec_opts.update(spec_opts)
218+
self.spec = HookSpec(specmodule_or_class, self.name, spec_opts)
224219
if spec_opts.get("historic"):
225220
self._call_history = []
226-
self.warn_on_impl = spec_opts.get("warn_on_impl")
227221

228222
def is_historic(self):
229223
return hasattr(self, "_call_history")
@@ -273,8 +267,10 @@ def __call__(self, *args, **kwargs):
273267
if args:
274268
raise TypeError("hook calling supports only keyword arguments")
275269
assert not self.is_historic()
276-
if self.argnames:
277-
notincall = set(self.argnames) - set(["__multicall__"]) - set(kwargs.keys())
270+
if self.spec:
271+
notincall = (
272+
set(self.spec.argnames) - set(['__multicall__']) - set(kwargs.keys())
273+
)
278274
if notincall:
279275
warnings.warn(
280276
"Argument(s) {} which are declared in the hookspec "
@@ -344,3 +340,14 @@ def __init__(self, plugin, plugin_name, function, hook_impl_opts):
344340

345341
def __repr__(self):
346342
return "<HookImpl plugin_name=%r, plugin=%r>" % (self.plugin_name, self.plugin)
343+
344+
345+
class HookSpec(object):
346+
def __init__(self, namespace, name, opts):
347+
self.namespace = namespace
348+
self.function = function = getattr(namespace, name)
349+
self.name = name
350+
self.argnames, self.kwargnames = varnames(function)
351+
self.opts = opts
352+
self.argnames = ["__multicall__"] + list(self.argnames)
353+
self.warn_on_impl = opts.get("warn_on_impl")

pluggy/manager.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ def __init__(self, project_name, implprefix=None):
5656
)
5757
self._implprefix = implprefix
5858
self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
59-
methods, kwargs, firstresult=hook.spec_opts.get("firstresult")
59+
methods,
60+
kwargs,
61+
firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,
6062
)
6163

6264
def _hookexec(self, hook, methods, kwargs):
@@ -215,10 +217,10 @@ def _verify_hook(self, hook, hookimpl):
215217
"Plugin %r\nhook %r\nhistoric incompatible to hookwrapper"
216218
% (hookimpl.plugin_name, hook.name),
217219
)
218-
if hook.warn_on_impl:
219-
_warn_for_function(hook.warn_on_impl, hookimpl.function)
220+
if hook.spec.warn_on_impl:
221+
_warn_for_function(hook.spec.warn_on_impl, hookimpl.function)
220222
# positional arg checking
221-
notinspec = set(hookimpl.argnames) - set(hook.argnames)
223+
notinspec = set(hookimpl.argnames) - set(hook.spec.argnames)
222224
if notinspec:
223225
raise PluginValidationError(
224226
hookimpl.plugin,
@@ -325,7 +327,7 @@ def subset_hook_caller(self, name, remove_plugins):
325327
plugins_to_remove = [plug for plug in remove_plugins if hasattr(plug, name)]
326328
if plugins_to_remove:
327329
hc = _HookCaller(
328-
orig.name, orig._hookexec, orig._specmodule_or_class, orig.spec_opts
330+
orig.name, orig._hookexec, orig.spec.namespace, orig.spec.opts
329331
)
330332
for hookimpl in orig._wrappers + orig._nonwrappers:
331333
plugin = hookimpl.plugin

testing/benchmark.py

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

2020

2121
@hookimpl

testing/test_hookcaller.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,9 @@ def he_myhook3(arg1):
169169
pass
170170

171171
pm.add_hookspecs(HookSpec)
172-
assert not pm.hook.he_myhook1.spec_opts["firstresult"]
173-
assert pm.hook.he_myhook2.spec_opts["firstresult"]
174-
assert not pm.hook.he_myhook3.spec_opts["firstresult"]
172+
assert not pm.hook.he_myhook1.spec.opts["firstresult"]
173+
assert pm.hook.he_myhook2.spec.opts["firstresult"]
174+
assert not pm.hook.he_myhook3.spec.opts["firstresult"]
175175

176176

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

0 commit comments

Comments
 (0)