Skip to content

Commit

Permalink
refactor: Accept **kwargs** in extension hooks to allow forward-com…
Browse files Browse the repository at this point in the history
…patibility

Emit deprecation warnings when hooks in subclasses don't accept `**kwargs`.

Issue-312: #312
  • Loading branch information
pawamoy committed Aug 11, 2024
1 parent a20796a commit 2621d52
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 30 deletions.
45 changes: 29 additions & 16 deletions src/_griffe/extensions/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -135,106 +135,106 @@ 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:
node: The currently visited node.
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:
node: The currently visited node.
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:
node: The currently visited node.
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:
node: The currently visited node.
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:
node: The currently visited node.
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:
node: The currently visited node.
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:
node: The currently visited node.
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:
node: The currently visited node.
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:
Expand Down Expand Up @@ -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] = {
Expand Down
2 changes: 1 addition & 1 deletion src/_griffe/extensions/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
26 changes: 13 additions & 13 deletions tests/test_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")


Expand Down

0 comments on commit 2621d52

Please sign in to comment.