Skip to content

Commit f11fee2

Browse files
Separate normal and wrapper hook implementations with distinct types
This change introduces type-safe separation between normal hook implementations and wrapper implementations by: - Creating WrapperImpl subclass for wrapper/hookwrapper implementations - Modifying _multicall to handle normal and wrapper implementations separately - Updating hook callers to maintain separate lists and enforce type constraints - Adding factory method in HookimplConfiguration for proper type instantiation - Ensuring historic hooks reject wrapper implementations at registration time The separation improves type safety, clarifies execution flow, and maintains backward compatibility while providing better error messages for misuse. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent b6af934 commit f11fee2

File tree

7 files changed

+282
-125
lines changed

7 files changed

+282
-125
lines changed

src/pluggy/_callers.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import warnings
1313

1414
from ._hook_callers import HookImpl
15+
from ._hook_callers import WrapperImpl
1516
from ._result import HookCallError
1617
from ._result import Result
1718
from ._warnings import PluggyTeardownRaisedWarning
@@ -23,7 +24,7 @@
2324

2425

2526
def run_old_style_hookwrapper(
26-
hook_impl: HookImpl, hook_name: str, args: Sequence[object]
27+
hook_impl: WrapperImpl, hook_name: str, args: Sequence[object]
2728
) -> Teardown:
2829
"""
2930
backward compatibility wrapper to run a old style hookwrapper as a wrapper
@@ -64,7 +65,7 @@ def _raise_wrapfail(
6465

6566

6667
def _warn_teardown_exception(
67-
hook_name: str, hook_impl: HookImpl, e: BaseException
68+
hook_name: str, hook_impl: WrapperImpl, e: BaseException
6869
) -> None:
6970
msg = "A plugin raised an exception during an old-style hookwrapper teardown.\n"
7071
msg += f"Plugin: {hook_impl.plugin_name}, Hook: {hook_name}\n"
@@ -75,7 +76,8 @@ def _warn_teardown_exception(
7576

7677
def _multicall(
7778
hook_name: str,
78-
hook_impls: Sequence[HookImpl],
79+
normal_impls: Sequence[HookImpl],
80+
wrapper_impls: Sequence[WrapperImpl],
7981
caller_kwargs: Mapping[str, object],
8082
firstresult: bool,
8183
) -> object | list[object]:
@@ -87,10 +89,11 @@ def _multicall(
8789
__tracebackhide__ = True
8890
results: list[object] = []
8991
exception = None
92+
9093
try: # run impl and wrapper setup functions in a loop
9194
teardowns: list[Teardown] = []
9295
try:
93-
for hook_impl in reversed(hook_impls):
96+
for hook_impl in reversed(wrapper_impls):
9497
try:
9598
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
9699
except KeyError as e:
@@ -103,10 +106,8 @@ def _multicall(
103106

104107
if hook_impl.hookwrapper:
105108
function_gen = run_old_style_hookwrapper(hook_impl, hook_name, args)
106-
107109
next(function_gen) # first yield
108110
teardowns.append(function_gen)
109-
110111
elif hook_impl.wrapper:
111112
try:
112113
# If this cast is not valid, a type error is raised below,
@@ -117,12 +118,25 @@ def _multicall(
117118
teardowns.append(function_gen)
118119
except StopIteration:
119120
_raise_wrapfail(function_gen, "did not yield")
120-
else:
121-
res = hook_impl.function(*args)
122-
if res is not None:
123-
results.append(res)
124-
if firstresult: # halt further impl calls
125-
break
121+
122+
# Process normal implementations (in reverse order for correct execution)
123+
# Caller ensures normal_impls contains only non-wrapper implementations
124+
for normal_impl in reversed(normal_impls):
125+
try:
126+
args = [caller_kwargs[argname] for argname in normal_impl.argnames]
127+
except KeyError as e:
128+
# coverage bug - this is tested
129+
for argname in normal_impl.argnames: # pragma: no cover
130+
if argname not in caller_kwargs:
131+
raise HookCallError(
132+
f"hook call must provide argument {argname!r}"
133+
) from e
134+
135+
res = normal_impl.function(*args)
136+
if res is not None:
137+
results.append(res)
138+
if firstresult: # halt further impl calls
139+
break
126140
except BaseException as exc:
127141
exception = exc
128142
finally:

0 commit comments

Comments
 (0)