summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurent Peuch <cortex@worlddomination.be>2020-08-26 17:26:22 +0200
committerLaurent Peuch <cortex@worlddomination.be>2020-08-26 17:26:22 +0200
commit894ccade059de8cab667187e93ea27daf7c75658 (patch)
tree678d77dd8e65679cb71d41a2663bf0a4a8a6c585
parentae769891236f32de955497b8e80b714a4d684d92 (diff)
downloadlogilab-common-894ccade059de8cab667187e93ea27daf7c75658.tar.gz
fix(deprecation): stacked decorators breaks getting the real callable __name__ attribute
-rw-r--r--logilab/common/deprecation.py30
-rw-r--r--test/test_deprecation.py25
2 files changed, 49 insertions, 6 deletions
diff --git a/logilab/common/deprecation.py b/logilab/common/deprecation.py
index 0ddedf2..b9f989c 100644
--- a/logilab/common/deprecation.py
+++ b/logilab/common/deprecation.py
@@ -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):
send_warning(
(
- f"{old_name} has been renamed and is deprecated, uses {new_function.__name__} "
- f"instead"
+ f"{old_name} has been renamed and is deprecated, uses "
+ f"{get_real__name__(new_function)} instead"
),
stacklevel=3,
version=version,
@@ -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:
send_warning(
- 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",
stacklevel=3,
version=version,
@@ -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:
send_warning(
- 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",
stacklevel=3,
version=version,
diff --git a/test/test_deprecation.py b/test/test_deprecation.py
index 3cbce9c..2508208 100644
--- a/test/test_deprecation.py
+++ b/test/test_deprecation.py
@@ -143,6 +143,31 @@ class RawInputTC(TestCase):
# by default.
# See: https://forge.extranet.logilab.fr/cubicweb/cubicweb/blob/3.24.0/cubicweb/schemas/__init__.py#L51 # 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):
+ pass
+
+ old_function_name = deprecation.callable_renamed(
+ old_name="old_function_name", new_function=new_function_name
+ )
+ old_function_name(None)
+
+ 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: