summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--logilab/common/deprecation.py50
-rw-r--r--test/test_deprecation.py15
2 files changed, 59 insertions, 6 deletions
diff --git a/logilab/common/deprecation.py b/logilab/common/deprecation.py
index 5dc23f7..ff6dc7c 100644
--- a/logilab/common/deprecation.py
+++ b/logilab/common/deprecation.py
@@ -22,7 +22,45 @@ __docformat__ = "restructuredtext en"
import os
import sys
from warnings import warn
-from functools import wraps
+from functools import WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES, wraps
+
+
+def lazy_wraps(wrapped):
+ """
+ This is the equivalent of the @wraps decorator of functools except it won't
+ try to grabs attributes of the targeted function on decoration but on access.
+
+ This is needed because of logilab.common.modutils.LazyObject.
+
+ Indeed: if you try to decorate a LazyObject with @wraps, wraps will try to
+ access attributes of LazyObject and this will trigger the attempt to import
+ the module decorated by LazyObject which you don't want to do when you just
+ want to mark this LazyObject has been a deprecated objet that you only
+ wants to trigger if the user try to use it.
+
+ Usage: like @wraps()
+
+ >>> @lazy_wraps(function)
+ >>> def wrapper(*args, **kwargs): ...
+ """
+
+ def update_wrapper_attributes(wrapper):
+ def __getattribute__(self, attribute):
+ if attribute in WRAPPER_ASSIGNMENTS:
+ return getattr(wrapped, attribute)
+
+ return super(self.__class__, self).__getattribute__(attribute)
+
+ wrapper.__getattribute__ = __getattribute__
+
+ for attribute in WRAPPER_UPDATES:
+ getattr(wrapper, attribute).update(getattr(wrapped, attribute, {}))
+
+ wrapper.__wrapped__ = wrapped
+
+ return wrapper
+
+ return update_wrapper_attributes
class DeprecationWrapper(object):
@@ -160,12 +198,12 @@ def callable_deprecated(reason=None, version=None, stacklevel=2):
"""
def decorator(func):
- message = reason or 'The function "%s" is deprecated'
- if "%s" in message:
- message %= func.__name__
-
- @wraps(func)
+ @lazy_wraps(func)
def wrapped(*args, **kwargs):
+ message = reason or 'The function "%s" is deprecated'
+ if "%s" in message:
+ message %= func.__name__
+
send_warning(message, version, stacklevel + 1, module_name=func.__module__)
return func(*args, **kwargs)
diff --git a/test/test_deprecation.py b/test/test_deprecation.py
index 65ef8fd..3cbce9c 100644
--- a/test/test_deprecation.py
+++ b/test/test_deprecation.py
@@ -20,6 +20,7 @@
import warnings
from logilab.common.testlib import TestCase, unittest_main
+from logilab.common.modutils import LazyObject
from logilab.common import deprecation
@@ -128,6 +129,20 @@ class RawInputTC(TestCase):
],
)
+ def test_deprecated_decorator_bad_lazyobject(self):
+ # this should not raised an ImportationError
+ deprecation.deprecated("foobar")(LazyObject("cubes.localperms", "xperm"))
+
+ # with or without giving it a message (because it shouldn't access
+ # attributes of the wrapped object before the object is called)
+ deprecation.deprecated()(LazyObject("cubes.localperms", "xperm"))
+
+ # all of this is done because of the magical way LazyObject is working
+ # and that sometime CW used to use it to do fake import on deprecated
+ # modules to raise a warning if they were used but not importing them
+ # by default.
+ # See: https://forge.extranet.logilab.fr/cubicweb/cubicweb/blob/3.24.0/cubicweb/schemas/__init__.py#L51 # noqa
+
def test_attribute_renamed(self):
@deprecation.attribute_renamed(old_name="old", new_name="new")
class SomeClass: