@@ -74,6 +74,83 @@ class HookimplOpts(TypedDict):
74
74
specname : str | None
75
75
76
76
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
+ @classmethod
140
+ def from_opts (cls , opts : HookimplOpts ) -> HookimplConfiguration :
141
+ """Create from HookimplOpts for backward compatibility."""
142
+ return cls (** opts )
143
+
144
+ def __repr__ (self ) -> str :
145
+ attrs = []
146
+ for slot in self .__slots__ :
147
+ value = getattr (self , slot )
148
+ if value :
149
+ attrs .append (f"{ slot } ={ value !r} " )
150
+ attrs_str = ", " .join (attrs )
151
+ return f"HookimplConfiguration({ attrs_str } )"
152
+
153
+
77
154
@final
78
155
class HookspecMarker :
79
156
"""Decorator for marking functions as hook specifications.
@@ -261,22 +338,33 @@ def __call__( # noqa: F811
261
338
"""
262
339
263
340
def setattr_hookimpl_opts (func : _F ) -> _F :
264
- opts : HookimplOpts = {
265
- " wrapper" : wrapper ,
266
- " hookwrapper" : hookwrapper ,
267
- " optionalhook" : optionalhook ,
268
- " tryfirst" : tryfirst ,
269
- " trylast" : trylast ,
270
- " specname" : specname ,
271
- }
272
- setattr (func , self .project_name + "_impl" , opts )
341
+ config = HookimplConfiguration (
342
+ wrapper = wrapper ,
343
+ hookwrapper = hookwrapper ,
344
+ optionalhook = optionalhook ,
345
+ tryfirst = tryfirst ,
346
+ trylast = trylast ,
347
+ specname = specname ,
348
+ )
349
+ setattr (func , self .project_name + "_impl" , config )
273
350
return func
274
351
275
352
if function is None :
276
353
return setattr_hookimpl_opts
277
354
else :
278
355
return setattr_hookimpl_opts (function )
279
356
357
+ def get_hookconfig (self , func : Callable [..., object ]) -> HookimplConfiguration :
358
+ """Extract hook implementation configuration from a decorated function.
359
+
360
+ :param func: A function decorated with this HookimplMarker
361
+ :return: HookimplConfiguration object containing the hook implementation options
362
+ :raises AttributeError: If the function is not decorated with this marker
363
+ """
364
+ attr_name = self .project_name + "_impl"
365
+ config : HookimplConfiguration = getattr (func , attr_name )
366
+ return config
367
+
280
368
281
369
def normalize_hookimpl_opts (opts : HookimplOpts ) -> None :
282
370
opts .setdefault ("tryfirst" , False )
@@ -548,17 +636,10 @@ def call_extra(
548
636
"Cannot directly call a historic hook - use call_historic instead."
549
637
)
550
638
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 ()
559
640
hookimpls = self ._hookimpls .copy ()
560
641
for method in methods :
561
- hookimpl = HookImpl (None , "<temp>" , method , opts )
642
+ hookimpl = HookImpl (None , "<temp>" , method , config )
562
643
# Find last non-tryfirst nonwrapper method.
563
644
i = len (hookimpls ) - 1
564
645
while i >= 0 and (
@@ -642,21 +723,21 @@ class HookImpl:
642
723
"argnames" ,
643
724
"kwargnames" ,
644
725
"plugin" ,
645
- "opts" ,
646
726
"plugin_name" ,
647
727
"wrapper" ,
648
728
"hookwrapper" ,
649
729
"optionalhook" ,
650
730
"tryfirst" ,
651
731
"trylast" ,
732
+ "hookimpl_config" ,
652
733
)
653
734
654
735
def __init__ (
655
736
self ,
656
737
plugin : _Plugin ,
657
738
plugin_name : str ,
658
739
function : _HookImplFunction [object ],
659
- hook_impl_opts : HookimplOpts ,
740
+ hook_impl_config : HookimplConfiguration ,
660
741
) -> None :
661
742
""":meta private:"""
662
743
#: The hook implementation function.
@@ -668,24 +749,25 @@ def __init__(
668
749
self .kwargnames : Final = kwargnames
669
750
#: The plugin which defined this hook implementation.
670
751
self .plugin : Final = plugin
671
- #: The :class:`HookimplOpts` used to configure this hook implementation.
672
- self .opts : Final = hook_impl_opts
752
+ #: The :class:`HookimplConfiguration` used to configure this hook
753
+ #: implementation.
754
+ self .hookimpl_config : Final = hook_impl_config
673
755
#: The name of the plugin which defined this hook implementation.
674
756
self .plugin_name : Final = plugin_name
675
757
#: Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
676
- self .wrapper : Final = hook_impl_opts [ " wrapper" ]
758
+ self .wrapper : Final = hook_impl_config . wrapper
677
759
#: Whether the hook implementation is an :ref:`old-style wrapper
678
760
#: <old_style_hookwrappers>`.
679
- self .hookwrapper : Final = hook_impl_opts [ " hookwrapper" ]
761
+ self .hookwrapper : Final = hook_impl_config . hookwrapper
680
762
#: Whether validation against a hook specification is :ref:`optional
681
763
#: <optionalhook>`.
682
- self .optionalhook : Final = hook_impl_opts [ " optionalhook" ]
764
+ self .optionalhook : Final = hook_impl_config . optionalhook
683
765
#: Whether to try to order this hook implementation :ref:`first
684
766
#: <callorder>`.
685
- self .tryfirst : Final = hook_impl_opts [ " tryfirst" ]
767
+ self .tryfirst : Final = hook_impl_config . tryfirst
686
768
#: Whether to try to order this hook implementation :ref:`last
687
769
#: <callorder>`.
688
- self .trylast : Final = hook_impl_opts [ " trylast" ]
770
+ self .trylast : Final = hook_impl_config . trylast
689
771
690
772
def __repr__ (self ) -> str :
691
773
return f"<HookImpl plugin_name={ self .plugin_name !r} , plugin={ self .plugin !r} >"
0 commit comments