@@ -45,6 +45,16 @@ def _warn_for_function(warning: Warning, function: Callable[..., object]) -> Non
4545 filename = func .__code__ .co_filename ,
4646 )
4747
48+ def _attr_is_property (obj : Any , name : str ) -> bool :
49+ """Check if a given attr is a @property on a module, class, or object"""
50+ if inspect .ismodule (obj ):
51+ return False # modules can never have @property methods
52+
53+ base_class = obj if inspect .isclass (obj ) else type (obj )
54+ if isinstance (getattr (base_class , name , None ), property ):
55+ return True
56+ return False
57+
4858
4959class PluginValidationError (Exception ):
5060 """Plugin failed validation.
@@ -182,23 +192,16 @@ def parse_hookimpl_opts(self, plugin: _Plugin, name: str) -> HookimplOpts | None
182192 options for items decorated with :class:`HookimplMarker`.
183193 """
184194
185- # IMPORTANT: @property methods can have side effects, and are never hookimpl
186- # if attr is a property, skip it in advance
187- plugin_class = plugin if inspect .isclass (plugin ) else type (plugin )
188- if isinstance (getattr (plugin_class , name , None ), property ):
189- return None
190-
191- # pydantic model fields are like attrs and also can never be hookimpls
192- plugin_is_pydantic_obj = hasattr (plugin , "__pydantic_core_schema__" )
193- if plugin_is_pydantic_obj and name in getattr (plugin , "model_fields" , {}):
195+ if _attr_is_property (plugin , name ):
196+ # @property methods can have side effects, and are never hookimpls
194197 return None
195198
196199 method : object
197200 try :
198201 method = getattr (plugin , name )
199202 except AttributeError :
200- # AttributeError: '__signature__' attribute of 'Plugin ' is class-only
201- # can happen for some special objects (e.g. proxies, pydantic, etc.)
203+ # AttributeError: '__signature__' attribute of 'plugin ' is class-only
204+ # can happen if plugin is a proxy object wrapping a class/module
202205 method = getattr (type (plugin ), name ) # use class sig instead
203206
204207 if not inspect .isroutine (method ):
@@ -305,7 +308,17 @@ def parse_hookspec_opts(
305308 customize how hook specifications are picked up. By default, returns the
306309 options for items decorated with :class:`HookspecMarker`.
307310 """
308- method = getattr (module_or_class , name )
311+ if _attr_is_property (module_or_class , name ):
312+ # @property methods can have side effects, and are never hookspecs
313+ return None
314+
315+ method : object
316+ try :
317+ method = getattr (module_or_class , name )
318+ except AttributeError :
319+ # AttributeError: '__signature__' attribute of <m_or_c> is class-only
320+ # can happen if module_or_class is a proxy obj wrapping a class/module
321+ method = getattr (type (module_or_class ), name ) # use class sig instead
309322 opts : HookspecOpts | None = getattr (method , self .project_name + "_spec" , None )
310323 return opts
311324
0 commit comments