Commit 0c3b6d70 authored by Laurent Peuch's avatar Laurent Peuch
Browse files

[deprecation/fix] correctly automatically get the module in which deprecation utils are called

There was a missmatched combination of:

* the frame wasn't always correctly grabbed
* grabbing the frame in the situation of a decorator didn't make any sens, so
  switch to func.__module__
* the tests were bad and expected "[logilab.common]" while it should have been
  "[test_deprecation]" because it was there that the depreciation was declared
parent 47b6801e6d03
......@@ -45,7 +45,7 @@ class DeprecationWrapper(object):
setattr(self._proxied, attr, value)
def _get_package_name(number=2):
def _get_module_name(number=1):
"""
automagically try to determine the package name from which the warning has
been triggered by loop other calling frames.
......@@ -55,29 +55,27 @@ def _get_package_name(number=2):
frame = sys._getframe()
for i in range(number):
for i in range(number + 1):
if frame.f_back is None:
break
frame = frame.f_back
if frame.f_globals["__package__"] is not None:
if frame.f_globals["__package__"]:
return frame.f_globals["__package__"]
file_name = os.path.split(frame.f_globals["__file__"])[1]
if file_name.endswith(".py"):
file_name = file_name[:-len(".py")]
file_name = file_name[: -len(".py")]
return file_name
def send_warning(reason, version=None, stacklevel=2):
def send_warning(reason, version=None, stacklevel=2, module_name=None):
"""Display a deprecation message only if the version is older than the
compatible version.
"""
module_name = _get_package_name(stacklevel + 1)
if module_name and version:
reason = '[%s %s] %s' % (module_name, version, reason)
elif module_name:
......@@ -102,10 +100,15 @@ def callable_renamed(old_name, new_function, version=None):
"""
@wraps(new_function)
def wrapped(*args, **kwargs):
send_warning((
f"{old_name} has been renamed and is deprecated, uses {new_function.__name__} "
f"instead"
), stacklevel=3, version=version)
send_warning(
(
f"{old_name} has been renamed and is deprecated, uses {new_function.__name__} "
f"instead"
),
stacklevel=3,
version=version,
module_name=new_function.__module__,
)
return new_function(*args, **kwargs)
return wrapped
......@@ -129,8 +132,13 @@ def argument_removed(old_argument_name, version=None):
@wraps(func)
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"removed and is deprecated", stacklevel=3, version=version)
send_warning(
f"argument {old_argument_name} of callable {func.__name__} has been "
f"removed and is deprecated",
stacklevel=3,
version=version,
module_name=func.__module__,
)
del kwargs[old_argument_name]
return func(*args, **kwargs)
......@@ -153,7 +161,7 @@ def callable_deprecated(reason=None, version=None, stacklevel=2):
@wraps(func)
def wrapped(*args, **kwargs):
send_warning(message, version, stacklevel + 1)
send_warning(message, version, stacklevel + 1, module_name=func.__module__)
return func(*args, **kwargs)
return wrapped
......@@ -167,10 +175,15 @@ class class_deprecated(type):
"""metaclass to print a warning on instantiation of a deprecated class"""
def __call__(cls, *args, **kwargs):
msg = getattr(cls, "__deprecation_warning__",
"%(cls)s is deprecated") % {'cls': cls.__name__}
send_warning(msg, stacklevel=getattr(cls, "__deprecation_warning_stacklevel__", 4),
version=getattr(cls, "__deprecation_warning_version__", None))
msg = getattr(cls, "__deprecation_warning__", "%(cls)s is deprecated") % {
"cls": cls.__name__
}
send_warning(
msg,
stacklevel=getattr(cls, "__deprecation_warning_stacklevel__", 3),
version=getattr(cls, "__deprecation_warning_version__", None),
module_name=getattr(cls, "__deprecation_warning_module_name__", cls.__module__),
)
return type.__call__(cls, *args, **kwargs)
......@@ -201,15 +214,15 @@ def attribute_renamed(old_name, new_name, version=None):
)
def _get_old(self):
send_warning(reason, stacklevel=3, version=version)
send_warning(reason, stacklevel=3, version=version, module_name=klass.__module__)
return getattr(self, new_name)
def _set_old(self, value):
send_warning(reason, stacklevel=3, version=version)
send_warning(reason, stacklevel=3, version=version, module_name=klass.__module__)
setattr(self, new_name, value)
def _del_old(self):
send_warning(reason, stacklevel=3, version=version)
send_warning(reason, stacklevel=3, version=version, module_name=klass.__module__)
delattr(self, new_name)
setattr(klass, old_name, property(_get_old, _set_old, _del_old))
......@@ -240,9 +253,13 @@ def argument_renamed(old_name, new_name, version=None):
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"and is deprecated, use keyword argument {new_name} instead",
stacklevel=3, version=version)
send_warning(
f"argument {old_name} of callable {func.__name__} has been renamed "
f"and is deprecated, use keyword argument {new_name} instead",
stacklevel=3,
version=version,
module_name=func.__module__,
)
kwargs[new_name] = kwargs[old_name]
del kwargs[old_name]
......@@ -271,7 +288,9 @@ def callable_moved(module_name, object_name, version=None, stacklevel=2):
def callnew(*args, **kwargs):
from logilab.common.modutils import load_module_from_name
send_warning(message, version=version, stacklevel=stacklevel + 1)
send_warning(
message, version=version, stacklevel=stacklevel + 1, module_name=_get_module_name(1)
)
m = load_module_from_name(module_name)
return getattr(m, object_name)(*args, **kwargs)
......@@ -282,7 +301,7 @@ def callable_moved(module_name, object_name, version=None, stacklevel=2):
moved = callable_renamed(old_name="moved", new_function=callable_moved)
def class_renamed(old_name, new_class, message=None, version=None):
def class_renamed(old_name, new_class, message=None, version=None, module_name=None):
"""automatically creates a class which fires a DeprecationWarning
when instantiated.
......@@ -294,11 +313,16 @@ def class_renamed(old_name, new_class, message=None, version=None):
"""
class_dict = {}
if message is None:
message = '%s is deprecated, use %s instead' % (old_name, new_class.__name__)
message = "%s is deprecated, use %s instead" % (old_name, new_class.__name__)
class_dict['__deprecation_warning__'] = message
class_dict['__deprecation_warning_version__'] = version
class_dict['__deprecation_warning_stacklevel__'] = 5
class_dict["__deprecation_warning__"] = message
class_dict["__deprecation_warning_version__"] = version
class_dict["__deprecation_warning_stacklevel__"] = 3
if module_name:
class_dict["__deprecation_warning_module_name__"] = module_name
else:
class_dict["__deprecation_warning_module_name__"] = _get_module_name(1)
return class_deprecated(old_name, (new_class,), class_dict)
......@@ -311,7 +335,12 @@ def class_moved(new_class, old_name=None, message=None, version=None):
old_name = new_class.__name__
if message is None:
message = 'class %s is now available as %s.%s' % (
old_name, new_class.__module__, new_class.__name__)
message = "class %s is now available as %s.%s" % (
old_name,
new_class.__module__,
new_class.__name__,
)
module_name = _get_module_name(1)
return class_renamed(old_name, new_class, message=message)
return class_renamed(old_name, new_class, message=message, module_name=module_name)
......@@ -48,8 +48,7 @@ class RawInputTC(TestCase):
class AnyClass(object, metaclass=deprecation.class_deprecated):
pass
AnyClass()
self.assertEqual(self.messages,
['[logilab.common] AnyClass is deprecated'])
self.assertEqual(self.messages, ["[test_deprecation] AnyClass is deprecated"])
def test_class_renamed(self):
class AnyClass(object):
......@@ -58,8 +57,9 @@ class RawInputTC(TestCase):
OldClass = deprecation.class_renamed("OldClass", AnyClass)
OldClass()
self.assertEqual(self.messages,
['[logilab.common] OldClass is deprecated, use AnyClass instead'])
self.assertEqual(
self.messages, ["[test_deprecation] OldClass is deprecated, use AnyClass instead"]
)
def test_class_moved(self):
class AnyClass(object):
......@@ -67,36 +67,53 @@ class RawInputTC(TestCase):
OldClass = deprecation.class_moved(new_class=AnyClass, old_name="OldName")
OldClass()
self.assertEqual(self.messages,
['[logilab.common] class OldName is now available as unittest_deprecation.AnyClass'])
self.assertEqual(
self.messages,
["[test_deprecation] class OldName is now available as test_deprecation.AnyClass"],
)
self.messages = []
AnyClass = deprecation.class_moved(new_class=AnyClass)
AnyClass()
self.assertEqual(self.messages,
['[logilab.common] class AnyClass is now available as unittest_deprecation.AnyClass'])
self.assertEqual(
self.messages,
["[test_deprecation] class AnyClass is now available as test_deprecation.AnyClass"],
)
def test_deprecated_func(self):
any_func = deprecation.callable_deprecated()(self.mk_func())
any_func()
any_func = deprecation.callable_deprecated('message')(self.mk_func())
any_func = deprecation.callable_deprecated("message")(self.mk_func())
any_func()
self.assertEqual(self.messages,
['[logilab.common] The function "any_func" is deprecated', '[logilab.common] message'])
self.assertEqual(
self.messages,
[
'[test_deprecation] The function "any_func" is deprecated',
"[test_deprecation] message",
],
)
def test_deprecated_decorator(self):
@deprecation.callable_deprecated()
def any_func():
pass
any_func()
@deprecation.callable_deprecated('message')
@deprecation.callable_deprecated("message")
def any_func():
pass
any_func()
self.assertEqual(self.messages,
['[logilab.common] The function "any_func" is deprecated', '[logilab.common] message'])
self.assertEqual(
self.messages,
[
'[test_deprecation] The function "any_func" is deprecated',
"[test_deprecation] message",
],
)
def test_attribute_renamed(self):
@deprecation.attribute_renamed(old_name="old", new_name="new")
......@@ -106,9 +123,13 @@ class RawInputTC(TestCase):
some_class = SomeClass()
self.assertEqual(some_class.old, some_class.new)
self.assertEqual(self.messages,
['[logilab.common] SomeClass.old has been renamed and is deprecated, use SomeClass.new '
'instead'])
self.assertEqual(
self.messages,
[
"[test_deprecation] SomeClass.old has been renamed and is deprecated, "
"use SomeClass.new instead"
],
)
some_class.old = 43
self.assertEqual(some_class.old, 43)
......@@ -127,9 +148,13 @@ class RawInputTC(TestCase):
self.assertEqual(some_function(new=42), 42)
self.assertEqual(some_function(old=42), 42)
self.assertEqual(self.messages,
['[logilab.common] argument old of callable some_function has been renamed and is '
'deprecated, use keyword argument new instead'])
self.assertEqual(
self.messages,
[
"[test_deprecation] argument old of callable some_function has been renamed and is "
"deprecated, use keyword argument new instead"
],
)
with self.assertRaises(ValueError):
some_function(new=42, old=42)
......@@ -141,9 +166,13 @@ class RawInputTC(TestCase):
self.assertEqual(some_function(new=42), 42)
self.assertEqual(some_function(new=10, old=20), 10)
self.assertEqual(self.messages,
['[logilab.common] argument old of callable some_function has been removed and is '
'deprecated'])
self.assertEqual(
self.messages,
[
"[test_deprecation] argument old of callable some_function has been removed and is "
"deprecated"
],
)
def test_callable_renamed(self):
def any_func():
......@@ -152,16 +181,23 @@ class RawInputTC(TestCase):
old_func = deprecation.callable_renamed("old_func", any_func)
old_func()
self.assertEqual(self.messages,
['[logilab.common] old_func has been renamed and is deprecated, uses any_func instead'])
self.assertEqual(
self.messages,
[
"[test_deprecation] old_func has been renamed and is deprecated, "
"uses any_func instead"
],
)
def test_moved(self):
module = 'data.deprecation'
any_func = deprecation.callable_moved(module, 'moving_target')
module = "data.deprecation"
any_func = deprecation.callable_moved(module, "moving_target")
any_func()
self.assertEqual(self.messages,
['[logilab.common] object moving_target has been moved to module data.deprecation'])
self.assertEqual(
self.messages,
["[test_deprecation] object moving_target has been moved to module data.deprecation"],
)
if __name__ == '__main__':
if __name__ == "__main__":
unittest_main()
......@@ -90,12 +90,15 @@ class get_module_part_tc(ModutilsTestCase):
"""given a dotted name return the module part of the name"""
def test_knownValues_get_module_part_1(self):
self.assertEqual(modutils.get_module_part('logilab.common.modutils'),
'logilab.common.modutils')
self.assertEqual(
modutils.get_module_part("logilab.common.modutils"), "logilab.common.modutils"
)
def test_knownValues_get_module_part_2(self):
self.assertEqual(modutils.get_module_part('logilab.common.modutils.get_module_part'),
'logilab.common.modutils')
self.assertEqual(
modutils.get_module_part("logilab.common.modutils.get_module_part"),
"logilab.common.modutils",
)
def test_knownValues_get_module_part_3(self):
"""relative import from given file"""
......@@ -120,10 +123,13 @@ class modpath_from_file_tc(ModutilsTestCase):
def test_knownValues_modpath_from_file_1(self):
with warnings.catch_warnings(record=True) as warns:
self.assertEqual(modutils.modpath_from_file(modutils.__file__),
['logilab', 'common', 'modutils'])
self.assertIn('[logilab.common] you should avoid using modpath_from_file()',
[str(w.message) for w in warns])
self.assertEqual(
modutils.modpath_from_file(modutils.__file__), ["logilab", "common", "modutils"]
)
self.assertIn(
"[logilab.common.modutils] you should avoid using modpath_from_file()",
[str(w.message) for w in warns],
)
def test_knownValues_modpath_from_file_2(self):
self.assertEqual(modutils.modpath_from_file('unittest_modutils.py',
......
......@@ -189,11 +189,13 @@ class RegistryStoreTC(TestCase):
with warnings.catch_warnings(record=True) as warns:
store.register_objects([self.datapath('regobjects.py'),
self.datapath('regobjects2.py')])
self.assertIn('[logilab.common] use register_modnames() instead',
[str(w.message) for w in warns])
self.assertEqual(['zereg'], list(store.keys()))
self.assertEqual(set(('appobject1', 'appobject2', 'appobject3')),
set(store['zereg']))
self.assertIn(
"[logilab.common.registry] use register_modnames() instead",
[str(w.message) for w in warns],
)
def test_autoload_modnames(self):
store = RegistryStore()
......
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