diff --git a/src/pluggy/_manager.py b/src/pluggy/_manager.py index d778334b..82a5c54c 100644 --- a/src/pluggy/_manager.py +++ b/src/pluggy/_manager.py @@ -181,6 +181,19 @@ def parse_hookimpl_opts(self, plugin: _Plugin, name: str) -> HookimplOpts | None customize how hook implementation are picked up. By default, returns the options for items decorated with :class:`HookimplMarker`. """ + + # IMPORTANT: accessing an @property can have side effects that we dont want to trigger + # if attr is a property, skip it in advance (@property methods can never be hookimpls) + plugin_class = plugin if inspect.isclass(plugin) else type(plugin) + if isinstance(getattr(plugin_class, name, None), property): + return None + + # if attr is field on a pydantic model, skip it (pydantic fields are never hookimpls) + if hasattr(plugin, '__pydantic_core_schema__') and name in getattr(plugin, 'model_fields', {}): + # pydantic can present class attributes, instance attributes, or methods + # but none of them can be hookimpls so they throw off the logic below + return None + method: object = getattr(plugin, name) if not inspect.isroutine(method): return None