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:
Joshua Harlow 2015-06-26 20:43:32 -07:00 committed by Joshua Harlow
parent f6d19baf86
commit f0f1fce2a2
4 changed files with 92 additions and 7 deletions

View File

@ -164,4 +164,10 @@ def get_callable_name(function):
parts = (im_class.__module__, im_class.__qualname__)
except AttributeError:
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)

View File

@ -22,7 +22,8 @@ from debtcollector import _utils
_KIND_MOVED_PREFIX_TPL = "%s '%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,
@ -56,23 +57,52 @@ def _moved_decorator(kind, new_attribute_name, message=None,
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,
version=None, removal_version=None, stacklevel=3,
category=None):
"""Decorates a *instance* method that was moved to another location."""
if not new_method_name.endswith(_MOVED_METHOD_POSTFIX):
new_method_name += _MOVED_METHOD_POSTFIX
"""Decorates an *instance* method that was moved to another location."""
if not new_method_name.endswith(_MOVED_CALLABLE_POSTFIX):
new_method_name += _MOVED_CALLABLE_POSTFIX
return _moved_decorator('Method', new_method_name, message=message,
version=version, removal_version=removal_version,
stacklevel=stacklevel,
attr_postfix=_MOVED_METHOD_POSTFIX,
attr_postfix=_MOVED_CALLABLE_POSTFIX,
category=category)
def moved_property(new_attribute_name, message=None,
version=None, removal_version=None, stacklevel=3,
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,
version=version, removal_version=removal_version,
stacklevel=stacklevel, category=category)

View File

@ -91,6 +91,14 @@ def blue_comet():
return True
def yellow_sun():
"""Yellow."""
return True
yellowish_sun = moves.moved_function(yellow_sun, 'yellowish_sun', __name__)
@removals.remove()
class EFSF(object):
pass
@ -217,6 +225,21 @@ class MovedPropertyTest(test_base.TestCase):
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):
def test_basics(self):
c = KittyKat()

View File

@ -113,6 +113,32 @@ A basic example to do just this (on a ``__init__`` method):
__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
---------------