diff options
author | Laurent Peuch <cortex@worlddomination.be> | 2020-05-20 20:47:31 +0200 |
---|---|---|
committer | Laurent Peuch <cortex@worlddomination.be> | 2020-05-20 20:47:31 +0200 |
commit | be136de1bc2cbf3e9108c8b6f9d0e233339988ce (patch) | |
tree | 1ba40b681491acfdf9c92848c3858f812a9f3a4f /logilab/common | |
parent | cfc6d86916a4080d7ca805d62887977b80b86b3f (diff) | |
download | logilab-common-be136de1bc2cbf3e9108c8b6f9d0e233339988ce.tar.gz |
[deprecation/fix] implement lazy_wraps, a lazy version of functools.wraps for LazyObject
functools.wraps was breaking the behavior of LazyObject because it tried to
access attributes of the LazyObject that triggers an import which could
sometime fails in this situation where some modules are marked as deprecated
but aren't imported yet.
For example see https://forge.extranet.logilab.fr/cubicweb/cubicweb/blob/3.24.0/cubicweb/schemas/__init__.py#L51
Diffstat (limited to 'logilab/common')
-rw-r--r-- | logilab/common/deprecation.py | 50 |
1 files changed, 44 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) |