Commit 804c40f2 authored by Laurent Peuch's avatar Laurent Peuch
Browse files

fix(deprecation): stacked decorators breaks getting the real callable __name__ attribute

parent de5ae7f7b977
Pipeline #12605 failed with stages
in 3 minutes and 21 seconds
......@@ -27,6 +27,24 @@ from typing import Any, Callable, Dict, Optional
from typing_extensions import Protocol
def get_real__name__(some_callable: Callable) -> str:
This is another super edge magic case which is needed because we uses
lazy_wraps because of logilab.common.modutils.LazyObject and because
__name__ has special behavior and doesn't work like a normal attribute and
that __getattribute__ of lazy_wraps is bypassed.
Therefor, to get the real callable name when several lazy_wrapped
decorator are used we need to travers the __wrapped__ attributes chain.
targeted_callable = some_callable
while hasattr(targeted_callable, "__wrapped__"):
targeted_callable = targeted_callable.__wrapped__ # type: ignore
return targeted_callable.__name__
def lazy_wraps(wrapped: Callable) -> Callable:
This is the equivalent of the @wraps decorator of functools except it won't
......@@ -151,8 +169,8 @@ def callable_renamed(
def wrapped(*args, **kwargs):
f"{old_name} has been renamed and is deprecated, uses {new_function.__name__} "
f"{old_name} has been renamed and is deprecated, uses "
f"{get_real__name__(new_function)} instead"
......@@ -186,7 +204,7 @@ def argument_removed(old_argument_name: str, version: Optional[str] = None) -> C
def check_kwargs(*args, **kwargs):
if old_argument_name in kwargs:
f"argument {old_argument_name} of callable {func.__name__} has been "
f"argument {old_argument_name} of callable {get_real__name__(func)} has been "
f"removed and is deprecated",
......@@ -215,7 +233,7 @@ def callable_deprecated(
def wrapped(*args, **kwargs) -> Callable:
message: str = reason or 'The function "%s" is deprecated'
if "%s" in message:
message %= func.__name__
message %= get_real__name__(func)
send_warning(message, version, stacklevel + 1, module_name=func.__module__)
return func(*args, **kwargs)
......@@ -328,14 +346,14 @@ def argument_renamed(old_name: str, new_name: str, version: Optional[str] = None
def check_kwargs(*args, **kwargs) -> Callable:
if old_name in kwargs and new_name in kwargs:
raise ValueError(
f"argument {old_name} of callable {func.__name__} has been "
f"argument {old_name} of callable {get_real__name__(func)} has been "
f"renamed to {new_name} but you are both using {old_name} and "
f"{new_name} has keyword arguments, only uses {new_name}"
if old_name in kwargs:
f"argument {old_name} of callable {func.__name__} has been renamed "
f"argument {old_name} of callable {get_real__name__(func)} has been renamed "
f"and is deprecated, use keyword argument {new_name} instead",
......@@ -143,6 +143,31 @@ class RawInputTC(TestCase):
# by default.
# See: # noqa
def test_lazy_wraps_function_name(self):
Avoid conflict from lazy_wraps where __name__ isn't correctly set on
the wrapper from the wrapped and we end up with the name of the wrapper
instead of the wrapped.
Like here it would fail if "check_kwargs" is the name of the new
function instead of new_function_name, this is because the wrapper in
argument_renamed is called check_kwargs and doesn't transmit the
__name__ of the wrapped (new_function_name) correctly.
@deprecation.argument_renamed(old_name="a", new_name="b")
def new_function_name(b):
old_function_name = deprecation.callable_renamed(
old_name="old_function_name", new_function=new_function_name
assert "old_function_name" in self.messages[0]
assert "new_function_name" in self.messages[0]
assert "check_kwargs" not in self.messages[0]
def test_attribute_renamed(self):
@deprecation.attribute_renamed(old_name="old", new_name="new")
class SomeClass:
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment