From 2621d52e4d1e89e043e022efb8eba087df5d321e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 11 Aug 2024 07:52:45 +0200 Subject: [PATCH] refactor: Accept `**kwargs**` in extension hooks to allow forward-compatibility Emit deprecation warnings when hooks in subclasses don't accept `**kwargs`. Issue-312: https://github.com/mkdocstrings/griffe/issues/312 --- src/_griffe/extensions/base.py | 45 +++++++++++++++++---------- src/_griffe/extensions/dataclasses.py | 2 +- tests/test_extensions.py | 26 ++++++++-------- 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/_griffe/extensions/base.py b/src/_griffe/extensions/base.py index 4e643eda..d68a4890 100644 --- a/src/_griffe/extensions/base.py +++ b/src/_griffe/extensions/base.py @@ -8,7 +8,7 @@ import warnings from collections import defaultdict from importlib.util import module_from_spec, spec_from_file_location -from inspect import isclass +from inspect import isclass, signature from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, Sequence, Type, Union @@ -135,14 +135,14 @@ def generic_inspect(self, node: ObjectNode) -> None: if not child.alias_target_path: self.inspect(child) - def on_node(self, *, node: ast.AST | ObjectNode) -> None: + def on_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: """Run when visiting a new node during static/dynamic analysis. Parameters: node: The currently visited node. """ - def on_instance(self, *, node: ast.AST | ObjectNode, obj: Object) -> None: + def on_instance(self, *, node: ast.AST | ObjectNode, obj: Object, **kwargs: Any) -> None: """Run when an Object has been created. Parameters: @@ -150,7 +150,7 @@ def on_instance(self, *, node: ast.AST | ObjectNode, obj: Object) -> None: obj: The object instance. """ - def on_members(self, *, node: ast.AST | ObjectNode, obj: Object) -> None: + def on_members(self, *, node: ast.AST | ObjectNode, obj: Object, **kwargs: Any) -> None: """Run when members of an Object have been loaded. Parameters: @@ -158,14 +158,14 @@ def on_members(self, *, node: ast.AST | ObjectNode, obj: Object) -> None: obj: The object instance. """ - def on_module_node(self, *, node: ast.AST | ObjectNode) -> None: + def on_module_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: """Run when visiting a new module node during static/dynamic analysis. Parameters: node: The currently visited node. """ - def on_module_instance(self, *, node: ast.AST | ObjectNode, mod: Module) -> None: + def on_module_instance(self, *, node: ast.AST | ObjectNode, mod: Module, **kwargs: Any) -> None: """Run when a Module has been created. Parameters: @@ -173,7 +173,7 @@ def on_module_instance(self, *, node: ast.AST | ObjectNode, mod: Module) -> None mod: The module instance. """ - def on_module_members(self, *, node: ast.AST | ObjectNode, mod: Module) -> None: + def on_module_members(self, *, node: ast.AST | ObjectNode, mod: Module, **kwargs: Any) -> None: """Run when members of a Module have been loaded. Parameters: @@ -181,14 +181,14 @@ def on_module_members(self, *, node: ast.AST | ObjectNode, mod: Module) -> None: mod: The module instance. """ - def on_class_node(self, *, node: ast.AST | ObjectNode) -> None: + def on_class_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: """Run when visiting a new class node during static/dynamic analysis. Parameters: node: The currently visited node. """ - def on_class_instance(self, *, node: ast.AST | ObjectNode, cls: Class) -> None: + def on_class_instance(self, *, node: ast.AST | ObjectNode, cls: Class, **kwargs: Any) -> None: """Run when a Class has been created. Parameters: @@ -196,7 +196,7 @@ def on_class_instance(self, *, node: ast.AST | ObjectNode, cls: Class) -> None: cls: The class instance. """ - def on_class_members(self, *, node: ast.AST | ObjectNode, cls: Class) -> None: + def on_class_members(self, *, node: ast.AST | ObjectNode, cls: Class, **kwargs: Any) -> None: """Run when members of a Class have been loaded. Parameters: @@ -204,14 +204,14 @@ def on_class_members(self, *, node: ast.AST | ObjectNode, cls: Class) -> None: cls: The class instance. """ - def on_function_node(self, *, node: ast.AST | ObjectNode) -> None: + def on_function_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: """Run when visiting a new function node during static/dynamic analysis. Parameters: node: The currently visited node. """ - def on_function_instance(self, *, node: ast.AST | ObjectNode, func: Function) -> None: + def on_function_instance(self, *, node: ast.AST | ObjectNode, func: Function, **kwargs: Any) -> None: """Run when a Function has been created. Parameters: @@ -219,14 +219,14 @@ def on_function_instance(self, *, node: ast.AST | ObjectNode, func: Function) -> func: The function instance. """ - def on_attribute_node(self, *, node: ast.AST | ObjectNode) -> None: + def on_attribute_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: """Run when visiting a new attribute node during static/dynamic analysis. Parameters: node: The currently visited node. """ - def on_attribute_instance(self, *, node: ast.AST | ObjectNode, attr: Attribute) -> None: + def on_attribute_instance(self, *, node: ast.AST | ObjectNode, attr: Attribute, **kwargs: Any) -> None: """Run when an Attribute has been created. Parameters: @@ -234,7 +234,7 @@ def on_attribute_instance(self, *, node: ast.AST | ObjectNode, attr: Attribute) attr: The attribute instance. """ - def on_package_loaded(self, *, pkg: Module) -> None: + def on_package_loaded(self, *, pkg: Module, **kwargs: Any) -> None: """Run when a package has been completely loaded. Parameters: @@ -370,7 +370,20 @@ def call(self, event: str, **kwargs: Any) -> None: **kwargs: Arguments passed to the hook. """ for extension in self._extensions: - getattr(extension, event)(**kwargs) + # YORE: Bump 1: Replace block with `getattr(extension, event)(**kwargs)`. + hook = getattr(extension, event) + params = signature(hook).parameters + has_kwargs = any(p.kind is p.VAR_KEYWORD for p in params.values()) + if not has_kwargs: + warnings.warn( + f"{hook.__qualname__} must accept variadic keyword parameters " + "(**kwargs) to allow forward-compatibility.", + DeprecationWarning, + stacklevel=1, + ) + hook(**{k: v for k, v in kwargs.items() if k in params}) + else: + hook(**kwargs) builtin_extensions: set[str] = { diff --git a/src/_griffe/extensions/dataclasses.py b/src/_griffe/extensions/dataclasses.py index 289b75ba..6cc9f8f8 100644 --- a/src/_griffe/extensions/dataclasses.py +++ b/src/_griffe/extensions/dataclasses.py @@ -221,7 +221,7 @@ class DataclassesExtension(Extension): if they don't already exist. """ - def on_package_loaded(self, *, pkg: Module) -> None: + def on_package_loaded(self, *, pkg: Module, **kwargs: Any) -> None: # noqa: ARG002 """Hook for loaded packages. Parameters: diff --git a/tests/test_extensions.py b/tests/test_extensions.py index 09364ace..de2ca91b 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -23,43 +23,43 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: D107 self.args = args self.kwargs = kwargs - def on_attribute_instance(self, *, node: ast.AST | ObjectNode, attr: Attribute) -> None: # noqa: D102,ARG002 + def on_attribute_instance(self, *, node: ast.AST | ObjectNode, attr: Attribute, **kwargs: Any) -> None: # noqa: D102,ARG002 self.records.append("on_attribute_instance") - def on_attribute_node(self, *, node: ast.AST | ObjectNode) -> None: # noqa: D102,ARG002 + def on_attribute_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002 self.records.append("on_attribute_node") - def on_class_instance(self, *, node: ast.AST | ObjectNode, cls: Class) -> None: # noqa: D102,ARG002 + def on_class_instance(self, *, node: ast.AST | ObjectNode, cls: Class, **kwargs: Any) -> None: # noqa: D102,ARG002 self.records.append("on_class_instance") - def on_class_members(self, *, node: ast.AST | ObjectNode, cls: Class) -> None: # noqa: D102,ARG002 + def on_class_members(self, *, node: ast.AST | ObjectNode, cls: Class, **kwargs: Any) -> None: # noqa: D102,ARG002 self.records.append("on_class_members") - def on_class_node(self, *, node: ast.AST | ObjectNode) -> None: # noqa: D102,ARG002 + def on_class_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002 self.records.append("on_class_node") - def on_function_instance(self, *, node: ast.AST | ObjectNode, func: Function) -> None: # noqa: D102,ARG002 + def on_function_instance(self, *, node: ast.AST | ObjectNode, func: Function, **kwargs: Any) -> None: # noqa: D102,ARG002 self.records.append("on_function_instance") - def on_function_node(self, *, node: ast.AST | ObjectNode) -> None: # noqa: D102,ARG002 + def on_function_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002 self.records.append("on_function_node") - def on_instance(self, *, node: ast.AST | ObjectNode, obj: Object) -> None: # noqa: D102,ARG002 + def on_instance(self, *, node: ast.AST | ObjectNode, obj: Object, **kwargs: Any) -> None: # noqa: D102,ARG002 self.records.append("on_instance") - def on_members(self, *, node: ast.AST | ObjectNode, obj: Object) -> None: # noqa: D102,ARG002 + def on_members(self, *, node: ast.AST | ObjectNode, obj: Object, **kwargs: Any) -> None: # noqa: D102,ARG002 self.records.append("on_members") - def on_module_instance(self, *, node: ast.AST | ObjectNode, mod: Module) -> None: # noqa: D102,ARG002 + def on_module_instance(self, *, node: ast.AST | ObjectNode, mod: Module, **kwargs: Any) -> None: # noqa: D102,ARG002 self.records.append("on_module_instance") - def on_module_members(self, *, node: ast.AST | ObjectNode, mod: Module) -> None: # noqa: D102,ARG002 + def on_module_members(self, *, node: ast.AST | ObjectNode, mod: Module, **kwargs: Any) -> None: # noqa: D102,ARG002 self.records.append("on_module_members") - def on_module_node(self, *, node: ast.AST | ObjectNode) -> None: # noqa: D102,ARG002 + def on_module_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002 self.records.append("on_module_node") - def on_node(self, *, node: ast.AST | ObjectNode) -> None: # noqa: D102,ARG002 + def on_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002 self.records.append("on_node")