diff --git a/changelog/59.removal.rst b/changelog/59.removal.rst new file mode 100644 index 00000000..e5b12479 --- /dev/null +++ b/changelog/59.removal.rst @@ -0,0 +1,2 @@ +Remove legacy ``__multicall__`` recursive hook calling system. +The deprecation was announced in release ``0.5.0``. diff --git a/docs/examples/eggsample-spam/eggsample_spam.py b/docs/examples/eggsample-spam/eggsample_spam.py index 8d71b138..500d885d 100644 --- a/docs/examples/eggsample-spam/eggsample_spam.py +++ b/docs/examples/eggsample-spam/eggsample_spam.py @@ -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!" diff --git a/scripts/release.py b/scripts/release.py index a4a18a4c..1340480d 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -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") diff --git a/src/pluggy/callers.py b/src/pluggy/callers.py index e7ea464b..d2977a1b 100644 --- a/src/pluggy/callers.py +++ b/src/pluggy/callers.py @@ -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). diff --git a/src/pluggy/hooks.py b/src/pluggy/hooks.py index 79d246ac..a295b49f 100644 --- a/src/pluggy/hooks.py +++ b/src/pluggy/hooks.py @@ -4,7 +4,6 @@ import inspect import sys import warnings -from .callers import _legacymulticall, _multicall class HookspecMarker(object): @@ -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 @@ -264,14 +262,6 @@ 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,) @@ -279,10 +269,9 @@ 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 " @@ -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") diff --git a/src/pluggy/manager.py b/src/pluggy/manager.py index 22dd78d6..6677a440 100644 --- a/src/pluggy/manager.py +++ b/src/pluggy/manager.py @@ -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 @@ -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, diff --git a/testing/benchmark.py b/testing/benchmark.py index aa8de929..cca4a759 100644 --- a/testing/benchmark.py +++ b/testing/benchmark.py @@ -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, "", method, method.example_impl) hookfuncs.append(f) - return callertype(hookfuncs, kwargs, firstresult=firstresult) + return _multicall(hookfuncs, kwargs, firstresult=firstresult) @hookimpl @@ -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) diff --git a/testing/test_deprecations.py b/testing/test_deprecations.py index 7151921b..28bc930f 100644 --- a/testing/test_deprecations.py +++ b/testing/test_deprecations.py @@ -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()) diff --git a/testing/test_multicall.py b/testing/test_multicall.py index eaf534a0..d0ad0adc 100644 --- a/testing/test_multicall.py +++ b/testing/test_multicall.py @@ -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") @@ -14,34 +14,9 @@ def MC(methods, kwargs, firstresult=False): for method in methods: f = HookImpl(None, "", 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): @@ -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():