@@ -155,47 +155,77 @@ def register(self, plugin: _Plugin, name: str | None = None) -> str | None:
155155
156156 # register matching hook implementations of the plugin
157157 for name in dir (plugin ):
158- hookimpl_opts = self .parse_hookimpl_opts (plugin , name )
159- if hookimpl_opts is not None :
160- normalize_hookimpl_opts (hookimpl_opts )
158+ hookimpl_config = self ._parse_hookimpl (plugin , name )
159+ if hookimpl_config is not None :
161160 method : _HookImplFunction [object ] = getattr (plugin , name )
162- hookimpl_config = HookimplConfiguration .from_opts (hookimpl_opts )
163161 hookimpl = HookImpl (plugin , plugin_name , method , hookimpl_config )
164- name = hookimpl_opts . get ( " specname" ) or name
165- hook : HookCaller | None = getattr (self .hook , name , None )
162+ hook_name = hookimpl_config . specname or name
163+ hook : HookCaller | None = getattr (self .hook , hook_name , None )
166164 if hook is None :
167- hook = HookCaller (name , self ._hookexec )
168- setattr (self .hook , name , hook )
165+ hook = HookCaller (hook_name , self ._hookexec )
166+ setattr (self .hook , hook_name , hook )
169167 elif hook .has_spec ():
170168 self ._verify_hook (hook , hookimpl )
171169 hook ._maybe_apply_history (hookimpl )
172170 hook ._add_hookimpl (hookimpl )
173171 return plugin_name
174172
173+ def _parse_hookimpl (self , plugin : _Plugin , name : str ) -> HookimplConfiguration | None :
174+ """Internal method to parse hook implementation configuration.
175+
176+ This method uses the new HookimplConfiguration type internally.
177+ Falls back to the legacy parse_hookimpl_opts method for compatibility.
178+
179+ :param plugin: The plugin object to inspect
180+ :param name: The attribute name to check for hook implementation
181+ :returns: HookimplConfiguration if found, None otherwise
182+ """
183+ try :
184+ method : object = getattr (plugin , name )
185+ except Exception : # pragma: no cover
186+ return None
187+
188+ if not inspect .isroutine (method ):
189+ return None
190+
191+ try :
192+ # Try to get hook implementation configuration directly
193+ impl_attr = getattr (method , self .project_name + "_impl" , None )
194+ except Exception : # pragma: no cover
195+ impl_attr = None
196+
197+ if impl_attr is not None :
198+ # Check if it's already a HookimplConfiguration (new style)
199+ if isinstance (impl_attr , HookimplConfiguration ):
200+ return impl_attr
201+ # Handle legacy dict-based configuration
202+ elif isinstance (impl_attr , dict ):
203+ return HookimplConfiguration .from_opts (impl_attr )
204+
205+ # Fall back to legacy parse_hookimpl_opts for compatibility (e.g. pytest override)
206+ legacy_opts = self .parse_hookimpl_opts (plugin , name )
207+ if legacy_opts is not None :
208+ normalize_hookimpl_opts (legacy_opts )
209+ return HookimplConfiguration .from_opts (legacy_opts )
210+
211+ return None
212+
175213 def parse_hookimpl_opts (self , plugin : _Plugin , name : str ) -> HookimplOpts | None :
176214 """Try to obtain a hook implementation from an item with the given name
177215 in the given plugin which is being searched for hook impls.
178216
179217 :returns:
180218 The parsed hookimpl options, or None to skip the given item.
181219
182- This method can be overridden by ``PluginManager`` subclasses to
183- customize how hook implementation are picked up. By default, returns the
184- options for items decorated with :class:`HookimplMarker`.
220+ .. deprecated::
221+ Customizing hook implementation parsing by overriding this method is
222+ deprecated. This method is only kept as a compatibility shim for
223+ legacy projects like pytest. New code should use the standard
224+ :class:`HookimplMarker` decorators.
185225 """
186- method : object = getattr (plugin , name )
187- if not inspect .isroutine (method ):
188- return None
189- try :
190- res : HookimplOpts | None = getattr (
191- method , self .project_name + "_impl" , None
192- )
193- except Exception : # pragma: no cover
194- res = {} # type: ignore[assignment] #pragma: no cover
195- if res is not None and not isinstance (res , dict ):
196- # false positive
197- res = None # type:ignore[unreachable] #pragma: no cover
198- return res
226+ # Compatibility shim - only overridden by legacy projects like pytest
227+ # Modern hook implementations are handled by _parse_hookimpl
228+ return None
199229
200230 def unregister (
201231 self , plugin : _Plugin | None = None , name : str | None = None
0 commit comments