Skip to content

Commit

Permalink
Merge pull request pytest-dev#147 from goodboy/drop_multicall
Browse files Browse the repository at this point in the history
Drop multicall
  • Loading branch information
goodboy committed Jun 4, 2020
2 parents 30a2f7c + bcbcdc0 commit 016e410
Show file tree
Hide file tree
Showing 9 changed files with 16 additions and 152 deletions.
2 changes: 2 additions & 0 deletions changelog/59.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Remove legacy ``__multicall__`` recursive hook calling system.
The deprecation was announced in release ``0.5.0``.
2 changes: 1 addition & 1 deletion docs/examples/eggsample-spam/eggsample_spam.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ def eggsample_prep_condiments(condiments):
except KeyError:
pass
condiments["spam sauce"] = 42
return f"Now this is what I call a condiments tray!"
return "Now this is what I call a condiments tray!"
2 changes: 1 addition & 1 deletion scripts/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def create_branch(version):
"""Create a fresh branch from upstream/master"""
repo = Repo.init(".")
if repo.is_dirty(untracked_files=True):
raise RuntimeError(f"Repository is dirty, please commit/stash your changes.")
raise RuntimeError("Repository is dirty, please commit/stash your changes.")

branch_name = f"release-{version}"
print(f"{Fore.CYAN}Create {branch_name} branch from upstream master")
Expand Down
73 changes: 0 additions & 73 deletions src/pluggy/callers.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,79 +81,6 @@ def get_result(self):
_reraise(*ex) # noqa


def _wrapped_call(wrap_controller, func):
""" Wrap calling to a function with a generator which needs to yield
exactly once. The yield point will trigger calling the wrapped function
and return its ``_Result`` to the yield point. The generator then needs
to finish (raise StopIteration) in order for the wrapped call to complete.
"""
try:
next(wrap_controller) # first yield
except StopIteration:
_raise_wrapfail(wrap_controller, "did not yield")
call_outcome = _Result.from_call(func)
try:
wrap_controller.send(call_outcome)
_raise_wrapfail(wrap_controller, "has second yield")
except StopIteration:
pass
return call_outcome.get_result()


class _LegacyMultiCall(object):
""" execute a call into multiple python functions/methods. """

# XXX note that the __multicall__ argument is supported only
# for pytest compatibility reasons. It was never officially
# supported there and is explicitely deprecated since 2.8
# 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, firstresult=False):
self.hook_impls = hook_impls
self.caller_kwargs = kwargs # come from _HookCaller.__call__()
self.caller_kwargs["__multicall__"] = self
self.firstresult = firstresult

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

while self.hook_impls:
hook_impl = self.hook_impls.pop()
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:
return _wrapped_call(hook_impl.function(*args), self.execute)
res = hook_impl.function(*args)
if res is not None:
if firstresult:
return res
results.append(res)

if not firstresult:
return results

def __repr__(self):
status = "%d meths" % (len(self.hook_impls),)
if hasattr(self, "results"):
status = ("%d results, " % len(self.results)) + status
return "<_MultiCall %s, kwargs=%r>" % (status, self.caller_kwargs)


def _legacymulticall(hook_impls, caller_kwargs, firstresult=False):
return _LegacyMultiCall(
hook_impls, caller_kwargs, firstresult=firstresult
).execute()


def _multicall(hook_impls, caller_kwargs, firstresult=False):
"""Execute a call into multiple python functions/methods and return the
result(s).
Expand Down
16 changes: 2 additions & 14 deletions src/pluggy/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import inspect
import sys
import warnings
from .callers import _legacymulticall, _multicall


class HookspecMarker(object):
Expand Down Expand Up @@ -212,7 +211,6 @@ def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None)
self._hookexec = hook_execute
self.argnames = None
self.kwargnames = None
self.multicall = _multicall
self.spec = None
if specmodule_or_class is not None:
assert spec_opts is not None
Expand Down Expand Up @@ -264,25 +262,16 @@ def _add_hookimpl(self, hookimpl):
i -= 1
methods.insert(i + 1, hookimpl)

if "__multicall__" in hookimpl.argnames:
warnings.warn(
"Support for __multicall__ is now deprecated and will be"
"removed in an upcoming release.",
DeprecationWarning,
)
self.multicall = _legacymulticall

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

def __call__(self, *args, **kwargs):
if args:
raise TypeError("hook calling supports only keyword arguments")
assert not self.is_historic()

if self.spec and self.spec.argnames:
notincall = (
set(self.spec.argnames) - set(["__multicall__"]) - set(kwargs.keys())
)
notincall = set(self.spec.argnames) - set(kwargs.keys())
if notincall:
warnings.warn(
"Argument(s) {} which are declared in the hookspec "
Expand Down Expand Up @@ -361,5 +350,4 @@ def __init__(self, namespace, name, opts):
self.name = name
self.argnames, self.kwargnames = varnames(function)
self.opts = opts
self.argnames = ["__multicall__"] + list(self.argnames)
self.warn_on_impl = opts.get("warn_on_impl")
4 changes: 2 additions & 2 deletions src/pluggy/manager.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import inspect
import sys
from . import _tracing
from .callers import _Result
from .callers import _Result, _multicall
from .hooks import HookImpl, _HookRelay, _HookCaller, normalize_hookimpl_opts
import warnings

Expand Down Expand Up @@ -81,7 +81,7 @@ def __init__(self, project_name, implprefix=None):
stacklevel=2,
)
self._implprefix = implprefix
self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
self._inner_hookexec = lambda hook, methods, kwargs: _multicall(
methods,
kwargs,
firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,
Expand Down
19 changes: 7 additions & 12 deletions testing/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
import pytest
from pluggy import HookspecMarker, HookimplMarker
from pluggy.hooks import HookImpl
from pluggy.callers import _multicall, _legacymulticall
from pluggy.callers import _multicall

hookspec = HookspecMarker("example")
hookimpl = HookimplMarker("example")


def MC(methods, kwargs, callertype, firstresult=False):
def MC(methods, kwargs, firstresult=False):
hookfuncs = []
for method in methods:
f = HookImpl(None, "<temp>", method, method.example_impl)
hookfuncs.append(f)
return callertype(hookfuncs, kwargs, firstresult=firstresult)
return _multicall(hookfuncs, kwargs, firstresult=firstresult)


@hookimpl
Expand All @@ -38,14 +38,9 @@ def wrappers(request):
return [wrapper for i in range(request.param)]


@pytest.fixture(params=[_multicall, _legacymulticall], ids=lambda item: item.__name__)
def callertype(request):
return request.param
def inner_exec(methods):
return MC(methods, {"arg1": 1, "arg2": 2, "arg3": 3})


def inner_exec(methods, callertype):
return MC(methods, {"arg1": 1, "arg2": 2, "arg3": 3}, callertype)


def test_hook_and_wrappers_speed(benchmark, hooks, wrappers, callertype):
benchmark(inner_exec, hooks + wrappers, callertype)
def test_hook_and_wrappers_speed(benchmark, hooks, wrappers):
benchmark(inner_exec, hooks + wrappers)
9 changes: 0 additions & 9 deletions testing/test_deprecations.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,3 @@ def m(self, x):
pm.register(p1)
with pytest.deprecated_call():
pm.hook.m.call_historic(kwargs=dict(x=10), proc=lambda res: res)


def test_multicall_deprecated(pm):
class P1(object):
@hookimpl
def m(self, __multicall__, x):
pass

pytest.deprecated_call(pm.register, P1())
41 changes: 1 addition & 40 deletions testing/test_multicall.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest
from pluggy import HookCallError, HookspecMarker, HookimplMarker
from pluggy.hooks import HookImpl
from pluggy.callers import _multicall, _legacymulticall
from pluggy.callers import _multicall


hookspec = HookspecMarker("example")
Expand All @@ -14,34 +14,9 @@ def MC(methods, kwargs, firstresult=False):
for method in methods:
f = HookImpl(None, "<temp>", method, method.example_impl)
hookfuncs.append(f)
if "__multicall__" in f.argnames:
caller = _legacymulticall
return caller(hookfuncs, kwargs, firstresult=firstresult)


def test_call_passing():
class P1(object):
@hookimpl
def m(self, __multicall__, x):
assert len(__multicall__.results) == 1
assert not __multicall__.hook_impls
return 17

class P2(object):
@hookimpl
def m(self, __multicall__, x):
assert __multicall__.results == []
assert __multicall__.hook_impls
return 23

p1 = P1()
p2 = P2()
reslist = MC([p1.m, p2.m], {"x": 23})
assert len(reslist) == 2
# ensure reversed order
assert reslist == [23, 17]


def test_keyword_args():
@hookimpl
def f(x):
Expand Down Expand Up @@ -74,20 +49,6 @@ def f(x):
MC([f], {})


def test_call_subexecute():
@hookimpl
def m(__multicall__):
subresult = __multicall__.execute()
return subresult + 1

@hookimpl
def n():
return 1

res = MC([n, m], {}, firstresult=True)
assert res == 2


def test_call_none_is_no_result():
@hookimpl
def m1():
Expand Down

0 comments on commit 016e410

Please sign in to comment.