Skip to content

Commit

Permalink
WIP: Draft iterable hooks implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyler Goodlet committed Nov 12, 2017
1 parent 4fb708b commit a7c2b02
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 3 deletions.
12 changes: 9 additions & 3 deletions pluggy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import inspect
import warnings
from .callers import _multicall, HookCallError, _Result, _legacymulticall
from .callers import (
_multicall, _itercall, HookCallError, _Result, _legacymulticall)

__version__ = '0.5.3.dev'

Expand Down Expand Up @@ -209,6 +210,8 @@ def __init__(self, project_name, implprefix=None):
self._plugin_distinfo = []
self.trace = _TagTracer().get("pluginmanage")
self.hook = _HookRelay(self.trace.root.get("hook"))
# alternative set of lazily executed hook calls
self.ihook = _HookRelay(self.trace.root.get("hook"))
self._implprefix = implprefix
self._inner_hookexec = lambda hook, methods, kwargs: \
hook.multicall(
Expand Down Expand Up @@ -247,7 +250,9 @@ def register(self, plugin, name=None):
hook = getattr(self.hook, name, None)
if hook is None:
hook = _HookCaller(name, self._hookexec)
ihook = _HookCaller(name, self._hookexec, iterate=True)
setattr(self.hook, name, hook)
setattr(self.ihook, name, ihook)
elif hook.has_spec():
self._verify_hook(hook, hookimpl)
hook._maybe_apply_history(hookimpl)
Expand Down Expand Up @@ -528,14 +533,15 @@ def __init__(self, trace):


class _HookCaller(object):
def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None):
def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None,
iterate=False):
self.name = name
self._wrappers = []
self._nonwrappers = []
self._hookexec = hook_execute
self.argnames = None
self.kwargnames = None
self.multicall = _multicall
self.multicall = _multicall if not iterate else _itercall
if specmodule_or_class is not None:
assert spec_opts is not None
self.set_specification(specmodule_or_class, spec_opts)
Expand Down
54 changes: 54 additions & 0 deletions pluggy/callers.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,57 @@ def _multicall(hook_impls, caller_kwargs, specopts={}, hook=None):
pass

return outcome.get_result()


def _itercall(hook_impls, caller_kwargs, specopts={}, hook=None):
"""Execute a calls into multiple python functions/methods and yield
the result(s) lazily.
``caller_kwargs`` comes from _HookCaller.__call__().
"""
__tracebackhide__ = True
specopts = hook.spec_opts if hook else specopts
results = []
firstresult = specopts.get("firstresult")
excinfo = None
try: # run impl and wrapper setup functions in a loop
teardowns = []
try:
for hook_impl in reversed(hook_impls):
try:
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
except KeyError:
for argname in hook_impl.argnames:
if argname not in caller_kwargs:
raise HookCallError(
"hook call must provide argument %r" % (argname,))

if hook_impl.hookwrapper:
try:
gen = hook_impl.function(*args)
next(gen) # first yield
teardowns.append(gen)
except StopIteration:
_raise_wrapfail(gen, "did not yield")
else:
res = hook_impl.function(*args)
if res is not None:
results.append(res)
yield res
if firstresult: # halt further impl calls
break
except BaseException:
excinfo = sys.exc_info()
finally:
if firstresult: # first result hooks return a single value
outcome = _Result(results[0] if results else None, excinfo)
else:
outcome = _Result(results, excinfo)

# run all wrapper post-yield blocks
for gen in reversed(teardowns):
try:
gen.send(outcome)
_raise_wrapfail(gen, "has second yield")
except StopIteration:
pass

0 comments on commit a7c2b02

Please sign in to comment.