aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--tensorflow/python/util/deprecation.py131
-rw-r--r--tensorflow/python/util/deprecation_test.py50
2 files changed, 172 insertions, 9 deletions
diff --git a/tensorflow/python/util/deprecation.py b/tensorflow/python/util/deprecation.py
index 2110fc64cf..fbec8fd2d8 100644
--- a/tensorflow/python/util/deprecation.py
+++ b/tensorflow/python/util/deprecation.py
@@ -39,13 +39,14 @@ _PRINTED_WARNING = {}
def _add_deprecated_function_notice_to_docstring(doc, date, instructions):
"""Adds a deprecation notice to a docstring for deprecated functions."""
+ main_text = ['THIS FUNCTION IS DEPRECATED. It will be removed %s.' %
+ ('in a future version' if date is None else ('after %s' % date))]
+ if instructions:
+ main_text.append('Instructions for updating:')
return decorator_utils.add_notice_to_docstring(
doc, instructions,
'DEPRECATED FUNCTION',
- '(deprecated)', [
- 'THIS FUNCTION IS DEPRECATED. It will be removed %s.' % (
- 'in a future version' if date is None else ('after %s' % date)),
- 'Instructions for updating:'])
+ '(deprecated)', main_text)
def _add_deprecated_arg_notice_to_docstring(doc, date, instructions):
@@ -67,23 +68,135 @@ def _validate_deprecation_args(date, instructions):
raise ValueError('Don\'t deprecate things without conversion instructions!')
-def _call_location():
+def _call_location(outer=False):
"""Returns call location given level up from current call."""
frame = tf_inspect.currentframe()
if frame:
# CPython internals are available, use them for performance.
# walk back two frames to get to deprecated function caller.
- first_frame = frame.f_back
- second_frame = first_frame.f_back
- frame = second_frame if second_frame else first_frame
+ frame = frame.f_back
+ if frame.f_back:
+ frame = frame.f_back
+ if outer and frame.f_back:
+ frame = frame.f_back
return '%s:%d' % (frame.f_code.co_filename, frame.f_lineno)
else:
# Slow fallback path
stack = tf_inspect.stack(0) # 0 avoids generating unused context
- entry = stack[2]
+ entry = stack[3 if outer else 2]
return '%s:%d' % (entry[1], entry[2])
+def deprecated_alias(deprecated_name, name, func_or_class, warn_once=True):
+ """Deprecate a symbol in favor of a new name with identical semantics.
+
+ This function is meant to be used when defining a backwards-compatibility
+ alias for a symbol which has been moved. For example:
+
+ module1.py:
+ ```python
+ class NewNameForClass: pass
+ ```
+
+ module2.py:
+ ```python
+ import module1
+
+ DeprecatedNameForClass = deprecated_alias(
+ deprecated_name='module2.DeprecatedNameForClass',
+ name='module1.NewNameForClass',
+ module1.NewNameForClass)
+ ```
+
+ This function works for classes and functions.
+
+ For classes, it creates a new class which is functionally identical (it
+ inherits from the original, and overrides its constructor), but which prints
+ a deprecation warning when an instance is created. It also adds a deprecation
+ notice to the class' docstring.
+
+ For functions, it returns a function wrapped by `tf_decorator.make_decorator`.
+ That function prints a warning when used, and has a deprecation notice in its
+ docstring. This is more or less equivalent (the deprecation warning has
+ slightly different text) to writing:
+
+ ```python
+ @deprecated
+ def deprecated_alias(original_args):
+ real_function(original_args)
+ ```
+
+ Args:
+ deprecated_name: The name of the symbol that is being deprecated, to be used
+ in the warning message. This should be its fully qualified name to avoid
+ confusion.
+ name: The name of the symbol that is to be used instead of the deprecated
+ name. This should be a fully qualified name to avoid confusion.
+ func_or_class: The (non-deprecated) class or function for which a deprecated
+ alias should be created.
+ warn_once: If True (the default), only print a deprecation warning the first
+ time this function is used, or the class is instantiated.
+
+ Returns:
+ A wrapped version of `func_or_class` which prints a deprecation warning on
+ use and has a modified docstring.
+ """
+ if tf_inspect.isclass(func_or_class):
+
+ # Make a new class with __init__ wrapped in a warning.
+ class NewClass(func_or_class): # pylint: disable=missing-docstring
+ __doc__ = decorator_utils.add_notice_to_docstring(
+ func_or_class.__doc__, 'Please use %s instead.' % name,
+ 'DEPRECATED CLASS',
+ '(deprecated)', ['THIS CLASS IS DEPRECATED. '
+ 'It will be removed in a future version. '])
+ __name__ = func_or_class.__name__
+ __module__ = _call_location(outer=True)
+
+ def __init__(self, *args, **kwargs):
+ if hasattr(NewClass.__init__, '__func__'):
+ # Python 2
+ NewClass.__init__.__func__.__doc__ = func_or_class.__init__.__doc__
+ else:
+ # Python 3
+ NewClass.__init__.__doc__ = func_or_class.__init__.__doc__
+
+ if _PRINT_DEPRECATION_WARNINGS:
+ # We're making the alias as we speak. The original may have other
+ # aliases, so we cannot use it to check for whether it's already been
+ # warned about.
+ if NewClass.__init__ not in _PRINTED_WARNING:
+ if warn_once:
+ _PRINTED_WARNING[NewClass.__init__] = True
+ logging.warning(
+ 'From %s: The name %s is deprecated. Please use %s instead.\n',
+ _call_location(), deprecated_name, name)
+ super(NewClass, self).__init__(*args, **kwargs)
+
+ return NewClass
+ else:
+ decorator_utils.validate_callable(func_or_class, 'deprecated')
+
+ # Make a wrapper for the original
+ @functools.wraps(func_or_class)
+ def new_func(*args, **kwargs): # pylint: disable=missing-docstring
+ if _PRINT_DEPRECATION_WARNINGS:
+ # We're making the alias as we speak. The original may have other
+ # aliases, so we cannot use it to check for whether it's already been
+ # warned about.
+ if new_func not in _PRINTED_WARNING:
+ if warn_once:
+ _PRINTED_WARNING[new_func] = True
+ logging.warning(
+ 'From %s: The name %s is deprecated. Please use %s instead.\n',
+ _call_location(), deprecated_name, name)
+ return func_or_class(*args, **kwargs)
+ return tf_decorator.make_decorator(
+ func_or_class, new_func, 'deprecated',
+ _add_deprecated_function_notice_to_docstring(
+ func_or_class.__doc__, None, 'Please use %s instead.' % name))
+
+
def deprecated(date, instructions, warn_once=True):
"""Decorator for marking functions or methods deprecated.
diff --git a/tensorflow/python/util/deprecation_test.py b/tensorflow/python/util/deprecation_test.py
index e61edb5cfa..bdd0bc48d2 100644
--- a/tensorflow/python/util/deprecation_test.py
+++ b/tensorflow/python/util/deprecation_test.py
@@ -24,6 +24,56 @@ from tensorflow.python.platform import tf_logging as logging
from tensorflow.python.util import deprecation
+class DeprecatedAliasTest(test.TestCase):
+
+ @test.mock.patch.object(logging, "warning", autospec=True)
+ def test_function_alias(self, mock_warning):
+ deprecated_func = deprecation.deprecated_alias("deprecated.func",
+ "real.func",
+ logging.error)
+
+ logging.error("fake error logged")
+ self.assertEqual(0, mock_warning.call_count)
+ deprecated_func("FAKE ERROR!")
+ self.assertEqual(1, mock_warning.call_count)
+ # Make sure the error points to the right file.
+ self.assertRegexpMatches(mock_warning.call_args[0][1],
+ r"deprecation_test\.py:")
+ deprecated_func("ANOTHER FAKE ERROR!")
+ self.assertEqual(1, mock_warning.call_count)
+
+ @test.mock.patch.object(logging, "warning", autospec=True)
+ def test_class_alias(self, mock_warning):
+ class MyClass(object):
+ """My docstring."""
+
+ init_args = []
+
+ def __init__(self, arg):
+ MyClass.init_args.append(arg)
+
+ deprecated_cls = deprecation.deprecated_alias("deprecated.cls",
+ "real.cls",
+ MyClass)
+
+ print(deprecated_cls.__name__)
+ print(deprecated_cls.__module__)
+ print(deprecated_cls.__doc__)
+
+ MyClass("test")
+ self.assertEqual(0, mock_warning.call_count)
+ deprecated_cls("deprecated")
+ self.assertEqual(1, mock_warning.call_count)
+ # Make sure the error points to the right file.
+ self.assertRegexpMatches(mock_warning.call_args[0][1],
+ r"deprecation_test\.py:")
+ deprecated_cls("deprecated again")
+ self.assertEqual(1, mock_warning.call_count)
+
+ self.assertEqual(["test", "deprecated", "deprecated again"],
+ MyClass.init_args)
+
+
class DeprecationTest(test.TestCase):
@test.mock.patch.object(logging, "warning", autospec=True)