@@ -45,6 +45,16 @@ def _warn_for_function(warning: Warning, function: Callable[..., object]) -> Non
45
45
filename = func .__code__ .co_filename ,
46
46
)
47
47
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
+
48
58
49
59
class PluginValidationError (Exception ):
50
60
"""Plugin failed validation.
@@ -182,23 +192,16 @@ def parse_hookimpl_opts(self, plugin: _Plugin, name: str) -> HookimplOpts | None
182
192
options for items decorated with :class:`HookimplMarker`.
183
193
"""
184
194
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
194
197
return None
195
198
196
199
method : object
197
200
try :
198
201
method = getattr (plugin , name )
199
202
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
202
205
method = getattr (type (plugin ), name ) # use class sig instead
203
206
204
207
if not inspect .isroutine (method ):
@@ -305,7 +308,17 @@ def parse_hookspec_opts(
305
308
customize how hook specifications are picked up. By default, returns the
306
309
options for items decorated with :class:`HookspecMarker`.
307
310
"""
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
309
322
opts : HookspecOpts | None = getattr (method , self .project_name + "_spec" , None )
310
323
return opts
311
324
0 commit comments