Skip to content

Commit 4fb708b

Browse files
Merge pull request #90 from tgoodlet/multicall_to_func
Multicall to func
2 parents a62cff6 + 7791b9c commit 4fb708b

File tree

4 files changed

+136
-150
lines changed

4 files changed

+136
-150
lines changed

pluggy/__init__.py

Lines changed: 4 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import inspect
22
import warnings
3-
from .callers import _MultiCall, HookCallError, _raise_wrapfail, _Result
3+
from .callers import _multicall, HookCallError, _Result, _legacymulticall
44

55
__version__ = '0.5.3.dev'
66

@@ -166,25 +166,6 @@ def get(self, name):
166166
return self.__class__(self.root, self.tags + (name,))
167167

168168

169-
def _wrapped_call(wrap_controller, func):
170-
""" Wrap calling to a function with a generator which needs to yield
171-
exactly once. The yield point will trigger calling the wrapped function
172-
and return its ``_Result`` to the yield point. The generator then needs
173-
to finish (raise StopIteration) in order for the wrapped call to complete.
174-
"""
175-
try:
176-
next(wrap_controller) # first yield
177-
except StopIteration:
178-
_raise_wrapfail(wrap_controller, "did not yield")
179-
call_outcome = _Result.from_call(func)
180-
try:
181-
wrap_controller.send(call_outcome)
182-
_raise_wrapfail(wrap_controller, "has second yield")
183-
except StopIteration:
184-
pass
185-
return call_outcome.get_result()
186-
187-
188169
class _TracedHookExecution(object):
189170
def __init__(self, pluginmanager, before, after):
190171
self.pluginmanager = pluginmanager
@@ -232,7 +213,7 @@ def __init__(self, project_name, implprefix=None):
232213
self._inner_hookexec = lambda hook, methods, kwargs: \
233214
hook.multicall(
234215
methods, kwargs, specopts=hook.spec_opts, hook=hook
235-
).execute()
216+
)
236217

237218
def _hookexec(self, hook, methods, kwargs):
238219
# called from all hookcaller instances.
@@ -485,54 +466,6 @@ def subset_hook_caller(self, name, remove_plugins):
485466
return orig
486467

487468

488-
class _LegacyMultiCall(object):
489-
""" execute a call into multiple python functions/methods. """
490-
491-
# XXX note that the __multicall__ argument is supported only
492-
# for pytest compatibility reasons. It was never officially
493-
# supported there and is explicitely deprecated since 2.8
494-
# so we can remove it soon, allowing to avoid the below recursion
495-
# in execute() and simplify/speed up the execute loop.
496-
497-
def __init__(self, hook_impls, kwargs, specopts={}, hook=None):
498-
self.hook = hook
499-
self.hook_impls = hook_impls
500-
self.caller_kwargs = kwargs # come from _HookCaller.__call__()
501-
self.caller_kwargs["__multicall__"] = self
502-
self.specopts = hook.spec_opts if hook else specopts
503-
504-
def execute(self):
505-
caller_kwargs = self.caller_kwargs
506-
self.results = results = []
507-
firstresult = self.specopts.get("firstresult")
508-
509-
while self.hook_impls:
510-
hook_impl = self.hook_impls.pop()
511-
try:
512-
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
513-
except KeyError:
514-
for argname in hook_impl.argnames:
515-
if argname not in caller_kwargs:
516-
raise HookCallError(
517-
"hook call must provide argument %r" % (argname,))
518-
if hook_impl.hookwrapper:
519-
return _wrapped_call(hook_impl.function(*args), self.execute)
520-
res = hook_impl.function(*args)
521-
if res is not None:
522-
if firstresult:
523-
return res
524-
results.append(res)
525-
526-
if not firstresult:
527-
return results
528-
529-
def __repr__(self):
530-
status = "%d meths" % (len(self.hook_impls),)
531-
if hasattr(self, "results"):
532-
status = ("%d results, " % len(self.results)) + status
533-
return "<_MultiCall %s, kwargs=%r>" % (status, self.caller_kwargs)
534-
535-
536469
def varnames(func):
537470
"""Return tuple of positional and keywrord argument names for a function,
538471
method, class or callable.
@@ -602,7 +535,7 @@ def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None)
602535
self._hookexec = hook_execute
603536
self.argnames = None
604537
self.kwargnames = None
605-
self.multicall = _MultiCall
538+
self.multicall = _multicall
606539
if specmodule_or_class is not None:
607540
assert spec_opts is not None
608541
self.set_specification(specmodule_or_class, spec_opts)
@@ -659,7 +592,7 @@ def _add_hookimpl(self, hookimpl):
659592
"removed in an upcoming release.",
660593
DeprecationWarning
661594
)
662-
self.multicall = _LegacyMultiCall
595+
self.multicall = _legacymulticall
663596

664597
def __repr__(self):
665598
return "<_HookCaller %r>" % (self.name,)

pluggy/callers.py

Lines changed: 107 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -77,67 +77,128 @@ def get_result(self):
7777
_reraise(*ex) # noqa
7878

7979

80-
class _MultiCall(object):
81-
"""Execute a call into multiple python functions/methods.
80+
def _wrapped_call(wrap_controller, func):
81+
""" Wrap calling to a function with a generator which needs to yield
82+
exactly once. The yield point will trigger calling the wrapped function
83+
and return its ``_Result`` to the yield point. The generator then needs
84+
to finish (raise StopIteration) in order for the wrapped call to complete.
8285
"""
86+
try:
87+
next(wrap_controller) # first yield
88+
except StopIteration:
89+
_raise_wrapfail(wrap_controller, "did not yield")
90+
call_outcome = _Result.from_call(func)
91+
try:
92+
wrap_controller.send(call_outcome)
93+
_raise_wrapfail(wrap_controller, "has second yield")
94+
except StopIteration:
95+
pass
96+
return call_outcome.get_result()
97+
98+
99+
class _LegacyMultiCall(object):
100+
""" execute a call into multiple python functions/methods. """
101+
102+
# XXX note that the __multicall__ argument is supported only
103+
# for pytest compatibility reasons. It was never officially
104+
# supported there and is explicitely deprecated since 2.8
105+
# so we can remove it soon, allowing to avoid the below recursion
106+
# in execute() and simplify/speed up the execute loop.
107+
83108
def __init__(self, hook_impls, kwargs, specopts={}, hook=None):
84109
self.hook = hook
85110
self.hook_impls = hook_impls
86111
self.caller_kwargs = kwargs # come from _HookCaller.__call__()
112+
self.caller_kwargs["__multicall__"] = self
87113
self.specopts = hook.spec_opts if hook else specopts
88114

89115
def execute(self):
90-
__tracebackhide__ = True
91116
caller_kwargs = self.caller_kwargs
92117
self.results = results = []
93118
firstresult = self.specopts.get("firstresult")
94-
excinfo = None
95-
try: # run impl and wrapper setup functions in a loop
96-
teardowns = []
97-
try:
98-
for hook_impl in reversed(self.hook_impls):
99-
try:
100-
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
101-
# args = operator.itemgetter(hookimpl.argnames)(caller_kwargs)
102-
except KeyError:
103-
for argname in hook_impl.argnames:
104-
if argname not in caller_kwargs:
105-
raise HookCallError(
106-
"hook call must provide argument %r" % (argname,))
107-
108-
if hook_impl.hookwrapper:
109-
try:
110-
gen = hook_impl.function(*args)
111-
next(gen) # first yield
112-
teardowns.append(gen)
113-
except StopIteration:
114-
_raise_wrapfail(gen, "did not yield")
115-
else:
116-
res = hook_impl.function(*args)
117-
if res is not None:
118-
results.append(res)
119-
if firstresult: # halt further impl calls
120-
break
121-
except BaseException:
122-
excinfo = sys.exc_info()
123-
finally:
124-
if firstresult: # first result hooks return a single value
125-
outcome = _Result(results[0] if results else None, excinfo)
126-
else:
127-
outcome = _Result(results, excinfo)
128-
129-
# run all wrapper post-yield blocks
130-
for gen in reversed(teardowns):
131-
try:
132-
gen.send(outcome)
133-
_raise_wrapfail(gen, "has second yield")
134-
except StopIteration:
135-
pass
136119

137-
return outcome.get_result()
120+
while self.hook_impls:
121+
hook_impl = self.hook_impls.pop()
122+
try:
123+
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
124+
except KeyError:
125+
for argname in hook_impl.argnames:
126+
if argname not in caller_kwargs:
127+
raise HookCallError(
128+
"hook call must provide argument %r" % (argname,))
129+
if hook_impl.hookwrapper:
130+
return _wrapped_call(hook_impl.function(*args), self.execute)
131+
res = hook_impl.function(*args)
132+
if res is not None:
133+
if firstresult:
134+
return res
135+
results.append(res)
136+
137+
if not firstresult:
138+
return results
138139

139140
def __repr__(self):
140141
status = "%d meths" % (len(self.hook_impls),)
141142
if hasattr(self, "results"):
142143
status = ("%d results, " % len(self.results)) + status
143144
return "<_MultiCall %s, kwargs=%r>" % (status, self.caller_kwargs)
145+
146+
147+
def _legacymulticall(hook_impls, caller_kwargs, specopts={}, hook=None):
148+
return _LegacyMultiCall(
149+
hook_impls, caller_kwargs, specopts=specopts, hook=hook).execute()
150+
151+
152+
def _multicall(hook_impls, caller_kwargs, specopts={}, hook=None):
153+
"""Execute a call into multiple python functions/methods and return the
154+
result(s).
155+
156+
``caller_kwargs`` comes from _HookCaller.__call__().
157+
"""
158+
__tracebackhide__ = True
159+
specopts = hook.spec_opts if hook else specopts
160+
results = []
161+
firstresult = specopts.get("firstresult")
162+
excinfo = None
163+
try: # run impl and wrapper setup functions in a loop
164+
teardowns = []
165+
try:
166+
for hook_impl in reversed(hook_impls):
167+
try:
168+
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
169+
except KeyError:
170+
for argname in hook_impl.argnames:
171+
if argname not in caller_kwargs:
172+
raise HookCallError(
173+
"hook call must provide argument %r" % (argname,))
174+
175+
if hook_impl.hookwrapper:
176+
try:
177+
gen = hook_impl.function(*args)
178+
next(gen) # first yield
179+
teardowns.append(gen)
180+
except StopIteration:
181+
_raise_wrapfail(gen, "did not yield")
182+
else:
183+
res = hook_impl.function(*args)
184+
if res is not None:
185+
results.append(res)
186+
if firstresult: # halt further impl calls
187+
break
188+
except BaseException:
189+
excinfo = sys.exc_info()
190+
finally:
191+
if firstresult: # first result hooks return a single value
192+
outcome = _Result(results[0] if results else None, excinfo)
193+
else:
194+
outcome = _Result(results, excinfo)
195+
196+
# run all wrapper post-yield blocks
197+
for gen in reversed(teardowns):
198+
try:
199+
gen.send(outcome)
200+
_raise_wrapfail(gen, "has second yield")
201+
except StopIteration:
202+
pass
203+
204+
return outcome.get_result()

testing/benchmark.py

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

88
hookspec = HookspecMarker("example")
@@ -28,31 +28,31 @@ def wrapper(arg1, arg2, arg3):
2828

2929

3030
@pytest.fixture(
31-
params=[0, 1, 10, 100],
31+
params=[10, 100],
3232
ids="hooks={}".format,
3333
)
3434
def hooks(request):
3535
return [hook for i in range(request.param)]
3636

3737

3838
@pytest.fixture(
39-
params=[0, 1, 10, 100],
39+
params=[10, 100],
4040
ids="wrappers={}".format,
4141
)
4242
def wrappers(request):
4343
return [wrapper for i in range(request.param)]
4444

4545

4646
@pytest.fixture(
47-
params=[_MultiCall, _LegacyMultiCall],
47+
params=[_multicall, _legacymulticall],
4848
ids=lambda item: item.__name__
4949
)
5050
def callertype(request):
5151
return request.param
5252

5353

5454
def inner_exec(methods, callertype):
55-
return MC(methods, {'arg1': 1, 'arg2': 2, 'arg3': 3}, callertype).execute()
55+
return MC(methods, {'arg1': 1, 'arg2': 2, 'arg3': 3}, callertype)
5656

5757

5858
def test_hook_and_wrappers_speed(benchmark, hooks, wrappers, callertype):

0 commit comments

Comments
 (0)