diff --git a/changelog/727.bugfix.rst b/changelog/727.bugfix.rst new file mode 100644 index 00000000..0e27bcc0 --- /dev/null +++ b/changelog/727.bugfix.rst @@ -0,0 +1 @@ +Fix the warning for hookspec arguments missing from a hook call being attributed to pluggy's own frame instead of to the code making the call. This affected ``HookCaller.__call__``, ``call_historic`` and ``call_extra``. diff --git a/src/pluggy/_hooks.py b/src/pluggy/_hooks.py index eaa006cc..1480308e 100644 --- a/src/pluggy/_hooks.py +++ b/src/pluggy/_hooks.py @@ -520,7 +520,10 @@ def _verify_all_args_are_provided(self, kwargs: Mapping[str, object]) -> None: warnings.warn( f"Argument(s) {notincall} which are declared in the hookspec " "cannot be found in this hook call", - stacklevel=2, + # 3, not 2: the warning is raised in this helper, which + # is called by __call__/call_historic/call_extra, which + # are called by the code making the hook call. + stacklevel=3, ) break diff --git a/testing/test_details.py b/testing/test_details.py index e42472d2..8df167f4 100644 --- a/testing/test_details.py +++ b/testing/test_details.py @@ -180,6 +180,37 @@ def herstory(self, arg1, arg2): pm.hook.herstory.call_historic(kwargs={}) +def test_not_all_arguments_warning_points_at_the_caller(pm: PluginManager) -> None: + """The warning must be attributed to the code making the hook call. + + It is raised inside a helper two frames below the caller, so a stacklevel + that is off by one blames pluggy's own frame instead. + """ + + class Spec: + @hookspec + def hello(self, arg1, arg2): + pass # pragma: no cover + + @hookspec(historic=True) + def herstory(self, arg1, arg2): + pass # pragma: no cover + + pm.add_hookspecs(Spec) + + with pytest.warns(UserWarning) as record: + pm.hook.hello(arg1=1) + assert record[0].filename == __file__ + + with pytest.warns(UserWarning) as record: + pm.hook.hello.call_extra([], kwargs={}) + assert record[0].filename == __file__ + + with pytest.warns(UserWarning) as record: + pm.hook.herstory.call_historic(kwargs={}) + assert record[0].filename == __file__ + + def test_repr() -> None: class Plugin: @hookimpl