Skip to content

Commit a7c2b02

Browse files
author
Tyler Goodlet
committed
WIP: Draft iterable hooks implementation
1 parent 4fb708b commit a7c2b02

File tree

2 files changed

+63
-3
lines changed

2 files changed

+63
-3
lines changed

Diff for: pluggy/__init__.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import inspect
22
import warnings
3-
from .callers import _multicall, HookCallError, _Result, _legacymulticall
3+
from .callers import (
4+
_multicall, _itercall, HookCallError, _Result, _legacymulticall)
45

56
__version__ = '0.5.3.dev'
67

@@ -209,6 +210,8 @@ def __init__(self, project_name, implprefix=None):
209210
self._plugin_distinfo = []
210211
self.trace = _TagTracer().get("pluginmanage")
211212
self.hook = _HookRelay(self.trace.root.get("hook"))
213+
# alternative set of lazily executed hook calls
214+
self.ihook = _HookRelay(self.trace.root.get("hook"))
212215
self._implprefix = implprefix
213216
self._inner_hookexec = lambda hook, methods, kwargs: \
214217
hook.multicall(
@@ -247,7 +250,9 @@ def register(self, plugin, name=None):
247250
hook = getattr(self.hook, name, None)
248251
if hook is None:
249252
hook = _HookCaller(name, self._hookexec)
253+
ihook = _HookCaller(name, self._hookexec, iterate=True)
250254
setattr(self.hook, name, hook)
255+
setattr(self.ihook, name, ihook)
251256
elif hook.has_spec():
252257
self._verify_hook(hook, hookimpl)
253258
hook._maybe_apply_history(hookimpl)
@@ -528,14 +533,15 @@ def __init__(self, trace):
528533

529534

530535
class _HookCaller(object):
531-
def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None):
536+
def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None,
537+
iterate=False):
532538
self.name = name
533539
self._wrappers = []
534540
self._nonwrappers = []
535541
self._hookexec = hook_execute
536542
self.argnames = None
537543
self.kwargnames = None
538-
self.multicall = _multicall
544+
self.multicall = _multicall if not iterate else _itercall
539545
if specmodule_or_class is not None:
540546
assert spec_opts is not None
541547
self.set_specification(specmodule_or_class, spec_opts)

Diff for: pluggy/callers.py

+54
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,57 @@ def _multicall(hook_impls, caller_kwargs, specopts={}, hook=None):
202202
pass
203203

204204
return outcome.get_result()
205+
206+
207+
def _itercall(hook_impls, caller_kwargs, specopts={}, hook=None):
208+
"""Execute a calls into multiple python functions/methods and yield
209+
the result(s) lazily.
210+
211+
``caller_kwargs`` comes from _HookCaller.__call__().
212+
"""
213+
__tracebackhide__ = True
214+
specopts = hook.spec_opts if hook else specopts
215+
results = []
216+
firstresult = specopts.get("firstresult")
217+
excinfo = None
218+
try: # run impl and wrapper setup functions in a loop
219+
teardowns = []
220+
try:
221+
for hook_impl in reversed(hook_impls):
222+
try:
223+
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
224+
except KeyError:
225+
for argname in hook_impl.argnames:
226+
if argname not in caller_kwargs:
227+
raise HookCallError(
228+
"hook call must provide argument %r" % (argname,))
229+
230+
if hook_impl.hookwrapper:
231+
try:
232+
gen = hook_impl.function(*args)
233+
next(gen) # first yield
234+
teardowns.append(gen)
235+
except StopIteration:
236+
_raise_wrapfail(gen, "did not yield")
237+
else:
238+
res = hook_impl.function(*args)
239+
if res is not None:
240+
results.append(res)
241+
yield res
242+
if firstresult: # halt further impl calls
243+
break
244+
except BaseException:
245+
excinfo = sys.exc_info()
246+
finally:
247+
if firstresult: # first result hooks return a single value
248+
outcome = _Result(results[0] if results else None, excinfo)
249+
else:
250+
outcome = _Result(results, excinfo)
251+
252+
# run all wrapper post-yield blocks
253+
for gen in reversed(teardowns):
254+
try:
255+
gen.send(outcome)
256+
_raise_wrapfail(gen, "has second yield")
257+
except StopIteration:
258+
pass

0 commit comments

Comments
 (0)