Skip to content

Commit 845c502

Browse files
author
Tyler Goodlet
committed
Deprecate __multicall__ support
Add a new `pluggy.callers._MultiCall` implementation which removes support the implicit `__multicall__` special argument and drops the recursion required to handle hook wrappers. Rename the original implementation `_LegacyMultiCall` and load it only when the `__multicall__` argument is detected in a hookimpl function signature. Add a deprecation warning whenever the legacy fallback occurs. Resolves #23
1 parent fb00c4e commit 845c502

File tree

2 files changed

+120
-12
lines changed

2 files changed

+120
-12
lines changed

pluggy/__init__.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import sys
22
import inspect
33
import warnings
4+
from .callers import _MultiCall, HookCallError, _raise_wrapfail
45

56
__version__ = '0.5.0'
67

@@ -14,10 +15,6 @@ class PluginValidationError(Exception):
1415
""" plugin failed validation. """
1516

1617

17-
class HookCallError(Exception):
18-
""" Hook was called wrongly. """
19-
20-
2118
class HookspecMarker(object):
2219
""" Decorator helper class for marking functions as hook specifications.
2320
@@ -172,12 +169,6 @@ def get(self, name):
172169
return self.__class__(self.root, self.tags + (name,))
173170

174171

175-
def _raise_wrapfail(wrap_controller, msg):
176-
co = wrap_controller.gi_code
177-
raise RuntimeError("wrap_controller at %r %s:%d %s" %
178-
(co.co_name, co.co_filename, co.co_firstlineno, msg))
179-
180-
181172
def _wrapped_call(wrap_controller, func):
182173
""" Wrap calling to a function with a generator which needs to yield
183174
exactly once. The yield point will trigger calling the wrapped function
@@ -275,7 +266,7 @@ def __init__(self, project_name, implprefix=None):
275266
self.hook = _HookRelay(self.trace.root.get("hook"))
276267
self._implprefix = implprefix
277268
self._inner_hookexec = lambda hook, methods, kwargs: \
278-
_MultiCall(
269+
hook.multicall(
279270
methods, kwargs, specopts=hook.spec_opts, hook=hook
280271
).execute()
281272

@@ -530,7 +521,7 @@ def subset_hook_caller(self, name, remove_plugins):
530521
return orig
531522

532523

533-
class _MultiCall(object):
524+
class _LegacyMultiCall(object):
534525
""" execute a call into multiple python functions/methods. """
535526

536527
# XXX note that the __multicall__ argument is supported only
@@ -647,6 +638,7 @@ def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None)
647638
self._hookexec = hook_execute
648639
self.argnames = None
649640
self.kwargnames = None
641+
self.multicall = _MultiCall
650642
if specmodule_or_class is not None:
651643
assert spec_opts is not None
652644
self.set_specification(specmodule_or_class, spec_opts)
@@ -697,6 +689,14 @@ def _add_hookimpl(self, hookimpl):
697689
i -= 1
698690
methods.insert(i + 1, hookimpl)
699691

692+
if '__multicall__' in hookimpl.argnames:
693+
warnings.warn(
694+
"Support for __multicall__ is now deprecated and will be"
695+
"removed in an upcoming release.",
696+
warnings.DeprecationWarning
697+
)
698+
self.multicall = _LegacyMultiCall
699+
700700
def __repr__(self):
701701
return "<_HookCaller %r>" % (self.name,)
702702

pluggy/callers.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
'''
2+
Call loop machinery
3+
'''
4+
import sys
5+
6+
7+
_py3 = sys.version_info > (3, 0)
8+
9+
10+
if not _py3:
11+
exec("""
12+
def _reraise(cls, val, tb):
13+
raise cls, val, tb
14+
""")
15+
16+
17+
def _raise_wrapfail(wrap_controller, msg):
18+
co = wrap_controller.gi_code
19+
raise RuntimeError("wrap_controller at %r %s:%d %s" %
20+
(co.co_name, co.co_filename, co.co_firstlineno, msg))
21+
22+
23+
class HookCallError(Exception):
24+
""" Hook was called wrongly. """
25+
26+
27+
class Result(object):
28+
def __init__(self, result, excinfo):
29+
self.result = result
30+
self.excinfo = excinfo
31+
32+
def force_result(self, result):
33+
self.result = result
34+
self.excinfo = None
35+
36+
def get_result(self):
37+
if self.excinfo is None:
38+
return self.result
39+
else:
40+
ex = self.excinfo
41+
if _py3:
42+
raise ex[1].with_traceback(ex[2])
43+
_reraise(*ex) # noqa
44+
45+
46+
class _MultiCall(object):
47+
"""Execute a call into multiple python functions/methods.
48+
"""
49+
def __init__(self, hook_impls, kwargs, specopts={}, hook=None):
50+
self.hook = hook
51+
self.hook_impls = hook_impls
52+
self.caller_kwargs = kwargs # come from _HookCaller.__call__()
53+
self.specopts = hook.spec_opts if hook else specopts
54+
55+
def execute(self):
56+
caller_kwargs = self.caller_kwargs
57+
self.results = results = []
58+
firstresult = self.specopts.get("firstresult")
59+
excinfo = None
60+
try: # run impl and wrapper setup functions in a loop
61+
teardowns = []
62+
try:
63+
for hook_impl in reversed(self.hook_impls):
64+
try:
65+
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
66+
# args = operator.itemgetter(hookimpl.argnames)(caller_kwargs)
67+
except KeyError:
68+
for argname in hook_impl.argnames:
69+
if argname not in caller_kwargs:
70+
raise HookCallError(
71+
"hook call must provide argument %r" % (argname,))
72+
73+
if hook_impl.hookwrapper:
74+
try:
75+
gen = hook_impl.function(*args)
76+
next(gen) # first yield
77+
teardowns.append(gen)
78+
except StopIteration:
79+
_raise_wrapfail(gen, "did not yield")
80+
else:
81+
res = hook_impl.function(*args)
82+
if res is not None:
83+
results.append(res)
84+
if firstresult: # halt further impl calls
85+
break
86+
except BaseException:
87+
excinfo = sys.exc_info()
88+
finally:
89+
outcome = Result(results, excinfo)
90+
91+
# run all wrapper post-yield blocks
92+
for gen in reversed(teardowns):
93+
try:
94+
gen.send(outcome)
95+
_raise_wrapfail(gen, "has second yield")
96+
except StopIteration:
97+
pass
98+
99+
if firstresult:
100+
return outcome.get_result()[0]
101+
102+
return outcome.get_result()
103+
104+
def __repr__(self):
105+
status = "%d meths" % (len(self.hook_impls),)
106+
if hasattr(self, "results"):
107+
status = ("%d results, " % len(self.results)) + status
108+
return "<_MultiCall %s, kwargs=%r>" % (status, self.caller_kwargs)

0 commit comments

Comments
 (0)