@@ -46,6 +46,17 @@ def _warn_for_function(warning: Warning, function: Callable[..., object]) -> Non
4646 )
4747
4848
49+ def _attr_is_property (obj : Any , name : str ) -> bool :
50+ """Check if a given attr is a @property on a module, class, or object"""
51+ if inspect .ismodule (obj ):
52+ return False # modules can never have @property methods
53+
54+ base_class = obj if inspect .isclass (obj ) else type (obj )
55+ if isinstance (getattr (base_class , name , None ), property ):
56+ return True
57+ return False
58+
59+
4960class PluginValidationError (Exception ):
5061 """Plugin failed validation.
5162
@@ -182,23 +193,16 @@ def parse_hookimpl_opts(self, plugin: _Plugin, name: str) -> HookimplOpts | None
182193 options for items decorated with :class:`HookimplMarker`.
183194 """
184195
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" , {}):
196+ if _attr_is_property (plugin , name ):
197+ # @property methods can have side effects, and are never hookimpls
194198 return None
195199
196200 method : object
197201 try :
198202 method = getattr (plugin , name )
199203 except AttributeError :
200- # AttributeError: '__signature__' attribute of 'Plugin ' is class-only
201- # can happen for some special objects (e.g. proxies, pydantic, etc.)
204+ # AttributeError: '__signature__' attribute of 'plugin ' is class-only
205+ # can happen if plugin is a proxy object wrapping a class/module
202206 method = getattr (type (plugin ), name ) # use class sig instead
203207
204208 if not inspect .isroutine (method ):
@@ -305,7 +309,17 @@ def parse_hookspec_opts(
305309 customize how hook specifications are picked up. By default, returns the
306310 options for items decorated with :class:`HookspecMarker`.
307311 """
308- method = getattr (module_or_class , name )
312+ if _attr_is_property (module_or_class , name ):
313+ # @property methods can have side effects, and are never hookspecs
314+ return None
315+
316+ method : object
317+ try :
318+ method = getattr (module_or_class , name )
319+ except AttributeError :
320+ # AttributeError: '__signature__' attribute of <m_or_c> is class-only
321+ # can happen if module_or_class is a proxy obj wrapping a class/module
322+ method = getattr (type (module_or_class ), name ) # use class sig instead
309323 opts : HookspecOpts | None = getattr (method , self .project_name + "_spec" , None )
310324 return opts
311325
0 commit comments