From 0f2614d1ce5cf00200e1faf7c937f1d9c3346d2a Mon Sep 17 00:00:00 2001 From: Dylan Pulver Date: Wed, 9 Sep 2026 00:03:09 +0200 Subject: [PATCH 1/2] Point the missing-argument warning at the code making the hook call The warning for hookspec arguments missing from a hook call was moved out of HookCaller.__call__ and into _verify_all_args_are_provided in e77787b, so that call_historic could share it, but it kept stacklevel=2. Inside the helper that level is __call__ itself, so every such warning is attributed to pluggy's own frame in _hooks.py rather than to the caller. All three entry points reach the helper at the same depth, so stacklevel=3 restores the pre-e77787b attribution for __call__ and extends it to call_historic and call_extra. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011M5uTyCU4WcNTsPvGrErDo --- src/pluggy/_hooks.py | 5 ++++- testing/test_details.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) 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 From 0eaa5303d198a3337f70b2691bd9ff4d087ae67a Mon Sep 17 00:00:00 2001 From: Dylan Pulver Date: Wed, 9 Sep 2026 00:03:37 +0200 Subject: [PATCH 2/2] Add changelog fragment for #727 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011M5uTyCU4WcNTsPvGrErDo --- changelog/727.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/727.bugfix.rst 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``.