Skip to content

Commit 82ae01c

Browse files
Add HookimplConfiguration class to replace HookimplOpts dict
Introduces a class-based approach to replace the HookimplOpts TypedDict in future versions, with backward compatibility methods. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 38c0c82 commit 82ae01c

File tree

6 files changed

+126
-21
lines changed

6 files changed

+126
-21
lines changed

src/pluggy/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"HookCallError",
77
"HookspecOpts",
88
"HookimplOpts",
9+
"HookimplConfiguration",
910
"HookImpl",
1011
"HookRelay",
1112
"HookspecMarker",
@@ -16,6 +17,7 @@
1617
]
1718
from ._hooks import HookCaller
1819
from ._hooks import HookImpl
20+
from ._hooks import HookimplConfiguration
1921
from ._hooks import HookimplMarker
2022
from ._hooks import HookimplOpts
2123
from ._hooks import HookRelay

src/pluggy/_hooks.py

Lines changed: 103 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,94 @@ class HookimplOpts(TypedDict):
7474
specname: str | None
7575

7676

77+
@final
78+
class HookimplConfiguration:
79+
"""Configuration class for hook implementations.
80+
81+
This class is intended to replace HookimplOpts in future versions.
82+
It provides a more structured and extensible way to configure hook implementations.
83+
"""
84+
85+
__slots__ = (
86+
"wrapper",
87+
"hookwrapper",
88+
"optionalhook",
89+
"tryfirst",
90+
"trylast",
91+
"specname",
92+
)
93+
94+
def __init__(
95+
self,
96+
wrapper: bool = False,
97+
hookwrapper: bool = False,
98+
optionalhook: bool = False,
99+
tryfirst: bool = False,
100+
trylast: bool = False,
101+
specname: str | None = None,
102+
) -> None:
103+
"""Initialize hook implementation configuration.
104+
105+
:param wrapper:
106+
Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
107+
:param hookwrapper:
108+
Whether the hook implementation is an :ref:`old-style wrapper
109+
<old_style_hookwrappers>`.
110+
:param optionalhook:
111+
Whether validation against a hook specification is :ref:`optional
112+
<optionalhook>`.
113+
:param tryfirst:
114+
Whether to try to order this hook implementation :ref:`first
115+
<callorder>`.
116+
:param trylast:
117+
Whether to try to order this hook implementation :ref:`last
118+
<callorder>`.
119+
:param specname:
120+
The name of the hook specification to match, see :ref:`specname`.
121+
"""
122+
#: Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
123+
self.wrapper: Final = wrapper
124+
#: Whether the hook implementation is an :ref:`old-style wrapper
125+
#: <old_style_hookwrappers>`.
126+
self.hookwrapper: Final = hookwrapper
127+
#: Whether validation against a hook specification is :ref:`optional
128+
#: <optionalhook>`.
129+
self.optionalhook: Final = optionalhook
130+
#: Whether to try to order this hook implementation :ref:`first
131+
#: <callorder>`.
132+
self.tryfirst: Final = tryfirst
133+
#: Whether to try to order this hook implementation :ref:`last
134+
#: <callorder>`.
135+
self.trylast: Final = trylast
136+
#: The name of the hook specification to match, see :ref:`specname`.
137+
self.specname: Final = specname
138+
139+
def to_opts(self) -> HookimplOpts:
140+
"""Convert to HookimplOpts for backward compatibility."""
141+
return {
142+
"wrapper": self.wrapper,
143+
"hookwrapper": self.hookwrapper,
144+
"optionalhook": self.optionalhook,
145+
"tryfirst": self.tryfirst,
146+
"trylast": self.trylast,
147+
"specname": self.specname,
148+
}
149+
150+
@classmethod
151+
def from_opts(cls, opts: HookimplOpts) -> HookimplConfiguration:
152+
"""Create from HookimplOpts for backward compatibility."""
153+
return cls(**opts)
154+
155+
def __repr__(self) -> str:
156+
attrs = []
157+
for slot in self.__slots__:
158+
value = getattr(self, slot)
159+
if value:
160+
attrs.append(f"{slot}={value!r}")
161+
attrs_str = ", ".join(attrs)
162+
return f"HookimplConfiguration({attrs_str})"
163+
164+
77165
@final
78166
class HookspecMarker:
79167
"""Decorator for marking functions as hook specifications.
@@ -548,17 +636,10 @@ def call_extra(
548636
"Cannot directly call a historic hook - use call_historic instead."
549637
)
550638
self._verify_all_args_are_provided(kwargs)
551-
opts: HookimplOpts = {
552-
"wrapper": False,
553-
"hookwrapper": False,
554-
"optionalhook": False,
555-
"trylast": False,
556-
"tryfirst": False,
557-
"specname": None,
558-
}
639+
config = HookimplConfiguration()
559640
hookimpls = self._hookimpls.copy()
560641
for method in methods:
561-
hookimpl = HookImpl(None, "<temp>", method, opts)
642+
hookimpl = HookImpl(None, "<temp>", method, config)
562643
# Find last non-tryfirst nonwrapper method.
563644
i = len(hookimpls) - 1
564645
while i >= 0 and (
@@ -649,14 +730,15 @@ class HookImpl:
649730
"optionalhook",
650731
"tryfirst",
651732
"trylast",
733+
"hookimpl_config",
652734
)
653735

654736
def __init__(
655737
self,
656738
plugin: _Plugin,
657739
plugin_name: str,
658740
function: _HookImplFunction[object],
659-
hook_impl_opts: HookimplOpts,
741+
hook_impl_config: HookimplConfiguration,
660742
) -> None:
661743
""":meta private:"""
662744
#: The hook implementation function.
@@ -668,24 +750,28 @@ def __init__(
668750
self.kwargnames: Final = kwargnames
669751
#: The plugin which defined this hook implementation.
670752
self.plugin: Final = plugin
671-
#: The :class:`HookimplOpts` used to configure this hook implementation.
672-
self.opts: Final = hook_impl_opts
753+
#: The :class:`HookimplConfiguration` used to configure this hook
754+
#: implementation.
755+
self.hookimpl_config: Final = hook_impl_config
756+
#: The :class:`HookimplOpts` used to configure this hook implementation
757+
#: (deprecated).
758+
self.opts: Final = hook_impl_config.to_opts()
673759
#: The name of the plugin which defined this hook implementation.
674760
self.plugin_name: Final = plugin_name
675761
#: Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
676-
self.wrapper: Final = hook_impl_opts["wrapper"]
762+
self.wrapper: Final = hook_impl_config.wrapper
677763
#: Whether the hook implementation is an :ref:`old-style wrapper
678764
#: <old_style_hookwrappers>`.
679-
self.hookwrapper: Final = hook_impl_opts["hookwrapper"]
765+
self.hookwrapper: Final = hook_impl_config.hookwrapper
680766
#: Whether validation against a hook specification is :ref:`optional
681767
#: <optionalhook>`.
682-
self.optionalhook: Final = hook_impl_opts["optionalhook"]
768+
self.optionalhook: Final = hook_impl_config.optionalhook
683769
#: Whether to try to order this hook implementation :ref:`first
684770
#: <callorder>`.
685-
self.tryfirst: Final = hook_impl_opts["tryfirst"]
771+
self.tryfirst: Final = hook_impl_config.tryfirst
686772
#: Whether to try to order this hook implementation :ref:`last
687773
#: <callorder>`.
688-
self.trylast: Final = hook_impl_opts["trylast"]
774+
self.trylast: Final = hook_impl_config.trylast
689775

690776
def __repr__(self) -> str:
691777
return f"<HookImpl plugin_name={self.plugin_name!r}, plugin={self.plugin!r}>"

src/pluggy/_manager.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from ._hooks import _SubsetHookCaller
2121
from ._hooks import HookCaller
2222
from ._hooks import HookImpl
23+
from ._hooks import HookimplConfiguration
2324
from ._hooks import HookimplOpts
2425
from ._hooks import HookRelay
2526
from ._hooks import HookspecOpts
@@ -158,7 +159,8 @@ def register(self, plugin: _Plugin, name: str | None = None) -> str | None:
158159
if hookimpl_opts is not None:
159160
normalize_hookimpl_opts(hookimpl_opts)
160161
method: _HookImplFunction[object] = getattr(plugin, name)
161-
hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts)
162+
hookimpl_config = HookimplConfiguration.from_opts(hookimpl_opts)
163+
hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_config)
162164
name = hookimpl_opts.get("specname") or name
163165
hook: HookCaller | None = getattr(self.hook, name, None)
164166
if hook is None:

testing/benchmark.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pluggy import PluginManager
1212
from pluggy._callers import _multicall
1313
from pluggy._hooks import HookImpl
14+
from pluggy._hooks import HookimplConfiguration
1415

1516

1617
hookspec = HookspecMarker("example")
@@ -42,7 +43,12 @@ def setup():
4243
hook_name = "foo"
4344
hook_impls = []
4445
for method in hooks + wrappers:
45-
f = HookImpl(None, "<temp>", method, method.example_impl)
46+
f = HookImpl(
47+
None,
48+
"<temp>",
49+
method,
50+
HookimplConfiguration.from_opts(method.example_impl),
51+
)
4652
hook_impls.append(f)
4753
caller_kwargs = {"arg1": 1, "arg2": 2, "arg3": 3}
4854
firstresult = False

testing/test_hookcaller.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pluggy import PluginValidationError
1212
from pluggy._hooks import HookCaller
1313
from pluggy._hooks import HookImpl
14+
from pluggy._hooks import HookimplConfiguration
1415

1516

1617
hookspec = HookspecMarker("example")
@@ -50,7 +51,12 @@ def wrap(func: FuncT) -> FuncT:
5051
wrapper=wrapper,
5152
)(func)
5253
self.hc._add_hookimpl(
53-
HookImpl(None, "<temp>", func, func.example_impl), # type: ignore[attr-defined]
54+
HookImpl(
55+
None,
56+
"<temp>",
57+
func,
58+
HookimplConfiguration.from_opts(func.example_impl),
59+
),
5460
)
5561
return func
5662

testing/test_multicall.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from pluggy import HookspecMarker
1111
from pluggy._callers import _multicall
1212
from pluggy._hooks import HookImpl
13+
from pluggy._hooks import HookimplConfiguration
1314

1415

1516
hookspec = HookspecMarker("example")
@@ -24,7 +25,9 @@ def MC(
2425
caller = _multicall
2526
hookfuncs = []
2627
for method in methods:
27-
f = HookImpl(None, "<temp>", method, method.example_impl) # type: ignore[attr-defined]
28+
f = HookImpl(
29+
None, "<temp>", method, HookimplConfiguration.from_opts(method.example_impl)
30+
)
2831
hookfuncs.append(f)
2932
return caller("foo", hookfuncs, kwargs, firstresult)
3033

0 commit comments

Comments
 (0)