Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Self contained call loops #102

Merged
merged 3 commits into from
Nov 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions pluggy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ def __init__(self, project_name, implprefix=None):
self._implprefix = implprefix
self._inner_hookexec = lambda hook, methods, kwargs: \
hook.multicall(
methods, kwargs, specopts=hook.spec_opts, hook=hook
methods, kwargs,
firstresult=hook.spec_opts.get('firstresult'),
)

def _hookexec(self, hook, methods, kwargs):
Expand Down Expand Up @@ -528,20 +529,22 @@ 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):
self.name = name
self._wrappers = []
self._nonwrappers = []
self._hookexec = hook_execute
self._specmodule_or_class = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good unification 👍

self.argnames = None
self.kwargnames = None
self.multicall = _multicall
self.spec_opts = spec_opts or {}
if specmodule_or_class is not None:
assert spec_opts is not None
self.set_specification(specmodule_or_class, spec_opts)

def has_spec(self):
return hasattr(self, "_specmodule_or_class")
return self._specmodule_or_class is not None

def set_specification(self, specmodule_or_class, spec_opts):
assert not self.has_spec()
Expand All @@ -550,7 +553,7 @@ def set_specification(self, specmodule_or_class, spec_opts):
# get spec arg signature
argnames, self.kwargnames = varnames(specfunc)
self.argnames = ["__multicall__"] + list(argnames)
self.spec_opts = spec_opts
self.spec_opts.update(spec_opts)
if spec_opts.get("historic"):
self._call_history = []

Expand Down
15 changes: 6 additions & 9 deletions pluggy/callers.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,16 @@ class _LegacyMultiCall(object):
# so we can remove it soon, allowing to avoid the below recursion
# in execute() and simplify/speed up the execute loop.

def __init__(self, hook_impls, kwargs, specopts={}, hook=None):
self.hook = hook
def __init__(self, hook_impls, kwargs, firstresult=False):
self.hook_impls = hook_impls
self.caller_kwargs = kwargs # come from _HookCaller.__call__()
self.caller_kwargs["__multicall__"] = self
self.specopts = hook.spec_opts if hook else specopts
self.firstresult = firstresult

def execute(self):
caller_kwargs = self.caller_kwargs
self.results = results = []
firstresult = self.specopts.get("firstresult")
firstresult = self.firstresult

while self.hook_impls:
hook_impl = self.hook_impls.pop()
Expand Down Expand Up @@ -144,21 +143,19 @@ def __repr__(self):
return "<_MultiCall %s, kwargs=%r>" % (status, self.caller_kwargs)


def _legacymulticall(hook_impls, caller_kwargs, specopts={}, hook=None):
def _legacymulticall(hook_impls, caller_kwargs, firstresult=False):
return _LegacyMultiCall(
hook_impls, caller_kwargs, specopts=specopts, hook=hook).execute()
hook_impls, caller_kwargs, firstresult=firstresult).execute()


def _multicall(hook_impls, caller_kwargs, specopts={}, hook=None):
def _multicall(hook_impls, caller_kwargs, firstresult=False):
"""Execute a call into multiple python functions/methods and return the
result(s).

``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 = []
Expand Down
14 changes: 8 additions & 6 deletions testing/test_method_ordering.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,15 +290,17 @@ def he_method1(self):
undo()


def test_prefix_hookimpl():
@pytest.mark.parametrize('include_hookspec', [True, False])
def test_prefix_hookimpl(include_hookspec):
pm = PluginManager(hookspec.project_name, "hello_")

class HookSpec(object):
@hookspec
def hello_myhook(self, arg1):
""" add to arg1 """
if include_hookspec:
class HookSpec(object):
@hookspec
def hello_myhook(self, arg1):
""" add to arg1 """

pm.add_hookspecs(HookSpec)
pm.add_hookspecs(HookSpec)

class Plugin(object):
def hello_myhook(self, arg1):
Expand Down
6 changes: 3 additions & 3 deletions testing/test_multicall.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def MC(methods, kwargs, firstresult=False):
hookfuncs.append(f)
if '__multicall__' in f.argnames:
caller = _legacymulticall
return caller(hookfuncs, kwargs, specopts={"firstresult": firstresult})
return caller(hookfuncs, kwargs, firstresult=firstresult)


def test_call_passing():
Expand Down Expand Up @@ -105,7 +105,7 @@ def m1():
def m2():
return None

res = MC([m1, m2], {}, {"firstresult": True})
res = MC([m1, m2], {}, firstresult=True)
assert res == 1
res = MC([m1, m2], {}, {})
assert res == [1]
Expand All @@ -129,7 +129,7 @@ def m2():
assert res == [2]
assert out == ["m1 init", "m2", "m1 finish"]
out[:] = []
res = MC([m2, m1], {}, {"firstresult": True})
res = MC([m2, m1], {}, firstresult=True)
assert res == 2
assert out == ["m1 init", "m2", "m1 finish"]

Expand Down