Skip to content

Commit 6689c91

Browse files
author
goodboy
authored
Merge pull request #58 from tgoodlet/deprecate__multicall__
Deprecate __multicall__
2 parents 45a5656 + 88f1b73 commit 6689c91

File tree

5 files changed

+158
-64
lines changed

5 files changed

+158
-64
lines changed

pluggy.py renamed to pluggy/__init__.py

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
1-
import sys
21
import inspect
32
import warnings
3+
from .callers import _MultiCall, HookCallError, _raise_wrapfail, _Result
44

55
__version__ = '0.5.0'
66

77
__all__ = ["PluginManager", "PluginValidationError", "HookCallError",
88
"HookspecMarker", "HookimplMarker"]
99

10-
_py3 = sys.version_info > (3, 0)
11-
1210

1311
class PluginValidationError(Exception):
1412
""" plugin failed validation. """
1513

1614

17-
class HookCallError(Exception):
18-
""" Hook was called wrongly. """
19-
20-
2115
class HookspecMarker(object):
2216
""" Decorator helper class for marking functions as hook specifications.
2317
@@ -86,7 +80,7 @@ def __call__(self, function=None, hookwrapper=False, optionalhook=False,
8680
If hookwrapper is True the hook implementations needs to execute exactly
8781
one "yield". The code before the yield is run early before any non-hookwrapper
8882
function is run. The code after the yield is run after all non-hookwrapper
89-
function have run. The yield receives an ``_CallOutcome`` object representing
83+
function have run. The yield receives a ``_Result`` object representing
9084
the exception or result outcome of the inner calls (including other hookwrapper
9185
calls).
9286
@@ -172,23 +166,17 @@ def get(self, name):
172166
return self.__class__(self.root, self.tags + (name,))
173167

174168

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-
181169
def _wrapped_call(wrap_controller, func):
182170
""" Wrap calling to a function with a generator which needs to yield
183171
exactly once. The yield point will trigger calling the wrapped function
184-
and return its _CallOutcome to the yield point. The generator then needs
172+
and return its ``_Result`` to the yield point. The generator then needs
185173
to finish (raise StopIteration) in order for the wrapped call to complete.
186174
"""
187175
try:
188176
next(wrap_controller) # first yield
189177
except StopIteration:
190178
_raise_wrapfail(wrap_controller, "did not yield")
191-
call_outcome = _CallOutcome(func)
179+
call_outcome = _Result.from_call(func)
192180
try:
193181
wrap_controller.send(call_outcome)
194182
_raise_wrapfail(wrap_controller, "has second yield")
@@ -197,39 +185,6 @@ def _wrapped_call(wrap_controller, func):
197185
return call_outcome.get_result()
198186

199187

200-
class _CallOutcome(object):
201-
""" Outcome of a function call, either an exception or a proper result.
202-
Calling the ``get_result`` method will return the result or reraise
203-
the exception raised when the function was called. """
204-
excinfo = None
205-
206-
def __init__(self, func):
207-
try:
208-
self.result = func()
209-
except BaseException:
210-
self.excinfo = sys.exc_info()
211-
212-
def force_result(self, result):
213-
self.result = result
214-
self.excinfo = None
215-
216-
def get_result(self):
217-
if self.excinfo is None:
218-
return self.result
219-
else:
220-
ex = self.excinfo
221-
if _py3:
222-
raise ex[1].with_traceback(ex[2])
223-
_reraise(*ex) # noqa
224-
225-
226-
if not _py3:
227-
exec("""
228-
def _reraise(cls, val, tb):
229-
raise cls, val, tb
230-
""")
231-
232-
233188
class _TracedHookExecution(object):
234189
def __init__(self, pluginmanager, before, after):
235190
self.pluginmanager = pluginmanager
@@ -241,7 +196,7 @@ def __init__(self, pluginmanager, before, after):
241196

242197
def __call__(self, hook, hook_impls, kwargs):
243198
self.before(hook.name, hook_impls, kwargs)
244-
outcome = _CallOutcome(lambda: self.oldcall(hook, hook_impls, kwargs))
199+
outcome = _Result.from_call(lambda: self.oldcall(hook, hook_impls, kwargs))
245200
self.after(outcome, hook.name, hook_impls, kwargs)
246201
return outcome.get_result()
247202

@@ -275,7 +230,7 @@ def __init__(self, project_name, implprefix=None):
275230
self.hook = _HookRelay(self.trace.root.get("hook"))
276231
self._implprefix = implprefix
277232
self._inner_hookexec = lambda hook, methods, kwargs: \
278-
_MultiCall(
233+
hook.multicall(
279234
methods, kwargs, specopts=hook.spec_opts, hook=hook
280235
).execute()
281236

@@ -490,7 +445,7 @@ def add_hookcall_monitoring(self, before, after):
490445
of HookImpl instances and the keyword arguments for the hook call.
491446
492447
``after(outcome, hook_name, hook_impls, kwargs)`` receives the
493-
same arguments as ``before`` but also a :py:class:`_CallOutcome`` object
448+
same arguments as ``before`` but also a :py:class:`_Result`` object
494449
which represents the result of the overall hook call.
495450
"""
496451
return _TracedHookExecution(self, before, after).undo
@@ -530,7 +485,7 @@ def subset_hook_caller(self, name, remove_plugins):
530485
return orig
531486

532487

533-
class _MultiCall(object):
488+
class _LegacyMultiCall(object):
534489
""" execute a call into multiple python functions/methods. """
535490

536491
# XXX note that the __multicall__ argument is supported only
@@ -647,6 +602,7 @@ def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None)
647602
self._hookexec = hook_execute
648603
self.argnames = None
649604
self.kwargnames = None
605+
self.multicall = _MultiCall
650606
if specmodule_or_class is not None:
651607
assert spec_opts is not None
652608
self.set_specification(specmodule_or_class, spec_opts)
@@ -697,6 +653,14 @@ def _add_hookimpl(self, hookimpl):
697653
i -= 1
698654
methods.insert(i + 1, hookimpl)
699655

656+
if '__multicall__' in hookimpl.argnames:
657+
warnings.warn(
658+
"Support for __multicall__ is now deprecated and will be"
659+
"removed in an upcoming release.",
660+
warnings.DeprecationWarning
661+
)
662+
self.multicall = _LegacyMultiCall
663+
700664
def __repr__(self):
701665
return "<_HookCaller %r>" % (self.name,)
702666

pluggy/callers.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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+
@classmethod
33+
def from_call(cls, func):
34+
result = excinfo = None
35+
try:
36+
result = func()
37+
except BaseException:
38+
excinfo = sys.exc_info()
39+
40+
return cls(result, excinfo)
41+
42+
def force_result(self, result):
43+
self.result = result
44+
self.excinfo = None
45+
46+
def get_result(self):
47+
if self.excinfo is None:
48+
return self.result
49+
else:
50+
ex = self.excinfo
51+
if _py3:
52+
raise ex[1].with_traceback(ex[2])
53+
_reraise(*ex) # noqa
54+
55+
56+
class _MultiCall(object):
57+
"""Execute a call into multiple python functions/methods.
58+
"""
59+
def __init__(self, hook_impls, kwargs, specopts={}, hook=None):
60+
self.hook = hook
61+
self.hook_impls = hook_impls
62+
self.caller_kwargs = kwargs # come from _HookCaller.__call__()
63+
self.specopts = hook.spec_opts if hook else specopts
64+
65+
def execute(self):
66+
caller_kwargs = self.caller_kwargs
67+
self.results = results = []
68+
firstresult = self.specopts.get("firstresult")
69+
excinfo = None
70+
try: # run impl and wrapper setup functions in a loop
71+
teardowns = []
72+
try:
73+
for hook_impl in reversed(self.hook_impls):
74+
try:
75+
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
76+
# args = operator.itemgetter(hookimpl.argnames)(caller_kwargs)
77+
except KeyError:
78+
for argname in hook_impl.argnames:
79+
if argname not in caller_kwargs:
80+
raise HookCallError(
81+
"hook call must provide argument %r" % (argname,))
82+
83+
if hook_impl.hookwrapper:
84+
try:
85+
gen = hook_impl.function(*args)
86+
next(gen) # first yield
87+
teardowns.append(gen)
88+
except StopIteration:
89+
_raise_wrapfail(gen, "did not yield")
90+
else:
91+
res = hook_impl.function(*args)
92+
if res is not None:
93+
results.append(res)
94+
if firstresult: # halt further impl calls
95+
break
96+
except BaseException:
97+
excinfo = sys.exc_info()
98+
finally:
99+
outcome = _Result(results, excinfo)
100+
101+
# run all wrapper post-yield blocks
102+
for gen in reversed(teardowns):
103+
try:
104+
gen.send(outcome)
105+
_raise_wrapfail(gen, "has second yield")
106+
except StopIteration:
107+
pass
108+
109+
if firstresult:
110+
return outcome.get_result()[0]
111+
112+
return outcome.get_result()
113+
114+
def __repr__(self):
115+
status = "%d meths" % (len(self.hook_impls),)
116+
if hasattr(self, "results"):
117+
status = ("%d results, " % len(self.results)) + status
118+
return "<_MultiCall %s, kwargs=%r>" % (status, self.caller_kwargs)

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
def get_version():
2424
p = os.path.join(os.path.dirname(
25-
os.path.abspath(__file__)), "pluggy.py")
25+
os.path.abspath(__file__)), "pluggy/__init__.py")
2626
with open(p) as f:
2727
for line in f.readlines():
2828
if "__version__" in line:
@@ -42,7 +42,7 @@ def main():
4242
author_email='holger at merlinux.eu',
4343
url='https://github.com/pytest-dev/pluggy',
4444
classifiers=classifiers,
45-
py_modules=['pluggy'],
45+
packages=['pluggy'],
4646
)
4747

4848

testing/benchmark.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22
Benchmarking and performance tests.
33
"""
44
import pytest
5-
from pluggy import _MultiCall, HookImpl, HookspecMarker, HookimplMarker
5+
from pluggy import (_MultiCall, _LegacyMultiCall, HookImpl, HookspecMarker,
6+
HookimplMarker)
67

78
hookspec = HookspecMarker("example")
89
hookimpl = HookimplMarker("example")
910

1011

11-
def MC(methods, kwargs, firstresult=False):
12+
def MC(methods, kwargs, callertype, firstresult=False):
1213
hookfuncs = []
1314
for method in methods:
1415
f = HookImpl(None, "<temp>", method, method.example_impl)
1516
hookfuncs.append(f)
16-
return _MultiCall(hookfuncs, kwargs, {"firstresult": firstresult})
17+
return callertype(hookfuncs, kwargs, {"firstresult": firstresult})
1718

1819

1920
@hookimpl
@@ -42,9 +43,17 @@ def wrappers(request):
4243
return [wrapper for i in range(request.param)]
4344

4445

45-
def inner_exec(methods):
46-
return MC(methods, {'arg1': 1, 'arg2': 2, 'arg3': 3}).execute()
46+
@pytest.fixture(
47+
params=[_MultiCall, _LegacyMultiCall],
48+
ids=lambda item: item.__name__
49+
)
50+
def callertype(request):
51+
return request.param
52+
53+
54+
def inner_exec(methods, callertype):
55+
return MC(methods, {'arg1': 1, 'arg2': 2, 'arg3': 3}, callertype).execute()
4756

4857

49-
def test_hook_and_wrappers_speed(benchmark, hooks, wrappers):
50-
benchmark(inner_exec, hooks + wrappers)
58+
def test_hook_and_wrappers_speed(benchmark, hooks, wrappers, callertype):
59+
benchmark(inner_exec, hooks + wrappers, callertype)

testing/test_multicall.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
from pluggy import _MultiCall, HookImpl, HookCallError
3+
from pluggy import _MultiCall, HookImpl, HookCallError, _LegacyMultiCall
44
from pluggy import HookspecMarker, HookimplMarker
55

66

@@ -18,11 +18,14 @@ def test_uses_copy_of_methods():
1818

1919

2020
def MC(methods, kwargs, firstresult=False):
21+
caller = _MultiCall
2122
hookfuncs = []
2223
for method in methods:
2324
f = HookImpl(None, "<temp>", method, method.example_impl)
2425
hookfuncs.append(f)
25-
return _MultiCall(hookfuncs, kwargs, {"firstresult": firstresult})
26+
if '__multicall__' in f.argnames:
27+
caller = _LegacyMultiCall
28+
return caller(hookfuncs, kwargs, {"firstresult": firstresult})
2629

2730

2831
def test_call_passing():

0 commit comments

Comments
 (0)