Add a moved function helper
This is useful for moving functions to newer names or newer modules/locations and retaining the old function so that the old function proxies to the new function and emits a deprecation warning when it is called. Also fixes how the 'get_callable_name' needs to filter for none, as it appears that when ran under sphinx the '__module__' attribute is set to none and this causes issues when joining into a string. Change-Id: I290f1b71c141c6647da1750aec348ea590a8d9bd
This commit is contained in:
parent
f6d19baf86
commit
f0f1fce2a2
@ -164,4 +164,10 @@ def get_callable_name(function):
|
|||||||
parts = (im_class.__module__, im_class.__qualname__)
|
parts = (im_class.__module__, im_class.__qualname__)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
parts = (im_class.__module__, im_class.__name__)
|
parts = (im_class.__module__, im_class.__name__)
|
||||||
return '.'.join(parts)
|
# When running under sphinx it appears this can be none? if so just
|
||||||
|
# don't include it...
|
||||||
|
mod, rest = (parts[0], parts[1:])
|
||||||
|
if not mod:
|
||||||
|
return '.'.join(rest)
|
||||||
|
else:
|
||||||
|
return '.'.join(parts)
|
||||||
|
@ -22,7 +22,8 @@ from debtcollector import _utils
|
|||||||
|
|
||||||
_KIND_MOVED_PREFIX_TPL = "%s '%s' has moved to '%s'"
|
_KIND_MOVED_PREFIX_TPL = "%s '%s' has moved to '%s'"
|
||||||
_CLASS_MOVED_PREFIX_TPL = "Class '%s' has moved to '%s'"
|
_CLASS_MOVED_PREFIX_TPL = "Class '%s' has moved to '%s'"
|
||||||
_MOVED_METHOD_POSTFIX = "()"
|
_MOVED_CALLABLE_POSTFIX = "()"
|
||||||
|
_FUNC_MOVED_PREFIX_TPL = "Function '%s' has moved to '%s'"
|
||||||
|
|
||||||
|
|
||||||
def _moved_decorator(kind, new_attribute_name, message=None,
|
def _moved_decorator(kind, new_attribute_name, message=None,
|
||||||
@ -56,23 +57,52 @@ def _moved_decorator(kind, new_attribute_name, message=None,
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def moved_function(new_func, old_func_name, old_module_name,
|
||||||
|
message=None, version=None, removal_version=None,
|
||||||
|
stacklevel=3, category=None):
|
||||||
|
"""Deprecates a function that was moved to another location.
|
||||||
|
|
||||||
|
This generates a wrapper around ``new_func`` that will emit a deprecation
|
||||||
|
warning when called. The warning message will include the new location
|
||||||
|
to obtain the function from.
|
||||||
|
"""
|
||||||
|
new_func_full_name = _utils.get_callable_name(new_func)
|
||||||
|
new_func_full_name += _MOVED_CALLABLE_POSTFIX
|
||||||
|
old_func_full_name = ".".join([old_module_name, old_func_name])
|
||||||
|
old_func_full_name += _MOVED_CALLABLE_POSTFIX
|
||||||
|
prefix = _FUNC_MOVED_PREFIX_TPL % (old_func_full_name, new_func_full_name)
|
||||||
|
out_message = _utils.generate_message(prefix,
|
||||||
|
message=message, version=version,
|
||||||
|
removal_version=removal_version)
|
||||||
|
|
||||||
|
@six.wraps(new_func)
|
||||||
|
def old_new_func(*args, **kwargs):
|
||||||
|
_utils.deprecation(out_message, stacklevel=stacklevel,
|
||||||
|
category=category)
|
||||||
|
return new_func(*args, **kwargs)
|
||||||
|
|
||||||
|
old_new_func.__name__ = old_func_name
|
||||||
|
old_new_func.__module__ = old_module_name
|
||||||
|
return old_new_func
|
||||||
|
|
||||||
|
|
||||||
def moved_method(new_method_name, message=None,
|
def moved_method(new_method_name, message=None,
|
||||||
version=None, removal_version=None, stacklevel=3,
|
version=None, removal_version=None, stacklevel=3,
|
||||||
category=None):
|
category=None):
|
||||||
"""Decorates a *instance* method that was moved to another location."""
|
"""Decorates an *instance* method that was moved to another location."""
|
||||||
if not new_method_name.endswith(_MOVED_METHOD_POSTFIX):
|
if not new_method_name.endswith(_MOVED_CALLABLE_POSTFIX):
|
||||||
new_method_name += _MOVED_METHOD_POSTFIX
|
new_method_name += _MOVED_CALLABLE_POSTFIX
|
||||||
return _moved_decorator('Method', new_method_name, message=message,
|
return _moved_decorator('Method', new_method_name, message=message,
|
||||||
version=version, removal_version=removal_version,
|
version=version, removal_version=removal_version,
|
||||||
stacklevel=stacklevel,
|
stacklevel=stacklevel,
|
||||||
attr_postfix=_MOVED_METHOD_POSTFIX,
|
attr_postfix=_MOVED_CALLABLE_POSTFIX,
|
||||||
category=category)
|
category=category)
|
||||||
|
|
||||||
|
|
||||||
def moved_property(new_attribute_name, message=None,
|
def moved_property(new_attribute_name, message=None,
|
||||||
version=None, removal_version=None, stacklevel=3,
|
version=None, removal_version=None, stacklevel=3,
|
||||||
category=None):
|
category=None):
|
||||||
"""Decorates a *instance* property that was moved to another location."""
|
"""Decorates an *instance* property that was moved to another location."""
|
||||||
return _moved_decorator('Property', new_attribute_name, message=message,
|
return _moved_decorator('Property', new_attribute_name, message=message,
|
||||||
version=version, removal_version=removal_version,
|
version=version, removal_version=removal_version,
|
||||||
stacklevel=stacklevel, category=category)
|
stacklevel=stacklevel, category=category)
|
||||||
|
@ -91,6 +91,14 @@ def blue_comet():
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def yellow_sun():
|
||||||
|
"""Yellow."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
yellowish_sun = moves.moved_function(yellow_sun, 'yellowish_sun', __name__)
|
||||||
|
|
||||||
|
|
||||||
@removals.remove()
|
@removals.remove()
|
||||||
class EFSF(object):
|
class EFSF(object):
|
||||||
pass
|
pass
|
||||||
@ -217,6 +225,21 @@ class MovedPropertyTest(test_base.TestCase):
|
|||||||
self.assertEqual(0, len(capture))
|
self.assertEqual(0, len(capture))
|
||||||
|
|
||||||
|
|
||||||
|
class MovedFunctionTest(test_base.TestCase):
|
||||||
|
def test_basics(self):
|
||||||
|
self.assertTrue(yellowish_sun())
|
||||||
|
self.assertTrue(yellow_sun())
|
||||||
|
self.assertEqual("Yellow.", yellowish_sun.__doc__)
|
||||||
|
|
||||||
|
def test_warnings_emitted(self):
|
||||||
|
with warnings.catch_warnings(record=True) as capture:
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
self.assertTrue(yellowish_sun())
|
||||||
|
self.assertEqual(1, len(capture))
|
||||||
|
w = capture[0]
|
||||||
|
self.assertEqual(DeprecationWarning, w.category)
|
||||||
|
|
||||||
|
|
||||||
class MovedMethodTest(test_base.TestCase):
|
class MovedMethodTest(test_base.TestCase):
|
||||||
def test_basics(self):
|
def test_basics(self):
|
||||||
c = KittyKat()
|
c = KittyKat()
|
||||||
|
@ -113,6 +113,32 @@ A basic example to do just this (on a ``__init__`` method):
|
|||||||
|
|
||||||
__main__:1: DeprecationWarning: Using the 'bleep' argument is deprecated
|
__main__:1: DeprecationWarning: Using the 'bleep' argument is deprecated
|
||||||
|
|
||||||
|
Moving a function
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
To change the name or location of a regular function use the
|
||||||
|
:py:func:`~debtcollector.moves.moved_function` function:
|
||||||
|
|
||||||
|
.. doctest::
|
||||||
|
|
||||||
|
>>> from debtcollector import moves
|
||||||
|
>>> import warnings
|
||||||
|
>>> warnings.simplefilter('always')
|
||||||
|
>>> def new_thing():
|
||||||
|
... return "new thing"
|
||||||
|
...
|
||||||
|
>>> old_thing = moves.moved_function(new_thing, 'old_thing', __name__)
|
||||||
|
>>> new_thing()
|
||||||
|
'new thing'
|
||||||
|
>>> old_thing()
|
||||||
|
'new thing'
|
||||||
|
|
||||||
|
**Expected output:**
|
||||||
|
|
||||||
|
.. testoutput::
|
||||||
|
|
||||||
|
__main__:1: DeprecationWarning: Function '__main__.old_thing()' has moved to '__main__.new_thing()'
|
||||||
|
|
||||||
Moving a method
|
Moving a method
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user