aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Martin Wicke <wicke@google.com>2017-09-13 08:53:54 -0700
committerGravatar TensorFlower Gardener <gardener@tensorflow.org>2017-09-13 08:57:38 -0700
commit4982ef0fa4c2a2a2e9a438d42872425ec4ef5a0e (patch)
treeb5975c4272582fe3d3c3681caf1d15feb756b2e3
parent99423416a4d388730fc4538a6bbdba4d514a8c63 (diff)
Add the ability to warn only once if deprecated functionality is used, and make that the default.
PiperOrigin-RevId: 168545655
-rw-r--r--tensorflow/python/util/deprecation.py80
-rw-r--r--tensorflow/python/util/deprecation_test.py98
2 files changed, 147 insertions, 31 deletions
diff --git a/tensorflow/python/util/deprecation.py b/tensorflow/python/util/deprecation.py
index c2de723bfb..8a66f0435a 100644
--- a/tensorflow/python/util/deprecation.py
+++ b/tensorflow/python/util/deprecation.py
@@ -32,6 +32,9 @@ from tensorflow.python.util import tf_inspect
# Allow deprecation warnings to be silenced temporarily with a context manager.
_PRINT_DEPRECATION_WARNINGS = True
+# Remember which deprecation warnings have been printed already.
+_PRINTED_WARNING = {}
+
def _add_deprecated_function_notice_to_docstring(doc, date, instructions):
"""Adds a deprecation notice to a docstring for deprecated functions."""
@@ -80,7 +83,7 @@ def _call_location():
return '%s:%d' % (entry[1], entry[2])
-def deprecated(date, instructions):
+def deprecated(date, instructions, warn_once=True):
"""Decorator for marking functions or methods deprecated.
This decorator logs a deprecation warning whenever the decorated function is
@@ -102,6 +105,8 @@ def deprecated(date, instructions):
Must be ISO 8601 (YYYY-MM-DD), or None.
instructions: String. Instructions on how to update code using the
deprecated function.
+ warn_once: Boolean. Set to `True` to warn only the first time the decorated
+ function is called. Otherwise, every call will log a warning.
Returns:
Decorated function or method.
@@ -118,13 +123,16 @@ def deprecated(date, instructions):
@functools.wraps(func)
def new_func(*args, **kwargs): # pylint: disable=missing-docstring
if _PRINT_DEPRECATION_WARNINGS:
- logging.warning(
- 'From %s: %s (from %s) is deprecated and will be removed %s.\n'
- 'Instructions for updating:\n%s',
- _call_location(), decorator_utils.get_qualified_name(func),
- func.__module__,
- 'in a future version' if date is None else ('after %s' % date),
- instructions)
+ if func not in _PRINTED_WARNING:
+ if warn_once:
+ _PRINTED_WARNING[func] = True
+ logging.warning(
+ 'From %s: %s (from %s) is deprecated and will be removed %s.\n'
+ 'Instructions for updating:\n%s',
+ _call_location(), decorator_utils.get_qualified_name(func),
+ func.__module__,
+ 'in a future version' if date is None else ('after %s' % date),
+ instructions)
return func(*args, **kwargs)
return tf_decorator.make_decorator(
func, new_func, 'deprecated',
@@ -137,7 +145,8 @@ DeprecatedArgSpec = collections.namedtuple(
'DeprecatedArgSpec', ['position', 'has_ok_value', 'ok_value'])
-def deprecated_args(date, instructions, *deprecated_arg_names_or_tuples):
+def deprecated_args(date, instructions, *deprecated_arg_names_or_tuples,
+ **kwargs):
"""Decorator for marking specific function arguments as deprecated.
This decorator logs a deprecation warning whenever the decorated function is
@@ -159,10 +168,14 @@ def deprecated_args(date, instructions, *deprecated_arg_names_or_tuples):
Must be ISO 8601 (YYYY-MM-DD), or None.
instructions: String. Instructions on how to update code using the
deprecated function.
- *deprecated_arg_names_or_tuples: String. or 2-Tuple(String,
+ *deprecated_arg_names_or_tuples: String or 2-Tuple(String,
[ok_vals]). The string is the deprecated argument name.
Optionally, an ok-value may be provided. If the user provided
argument equals this value, the warning is suppressed.
+ **kwargs: If `warn_once=False` is passed, every call with a deprecated
+ argument will log a warning. The default behavior is to only warn the
+ first time the function is called with any given deprecated argument.
+ All other kwargs raise `ValueError`.
Returns:
Decorated function or method.
@@ -170,12 +183,16 @@ def deprecated_args(date, instructions, *deprecated_arg_names_or_tuples):
Raises:
ValueError: If date is not None or in ISO 8601 format, instructions are
empty, the deprecated arguments are not present in the function
- signature, or the second element of a deprecated_tuple is not a
- list.
+ signature, the second element of a deprecated_tuple is not a
+ list, or if a kwarg other than `warn_once` is passed.
"""
_validate_deprecation_args(date, instructions)
if not deprecated_arg_names_or_tuples:
raise ValueError('Specify which argument is deprecated.')
+ if kwargs and list(kwargs.keys()) != ['warn_once']:
+ kwargs.pop('warn_once', None)
+ raise ValueError('Illegal argument to deprecated_args: %s' % kwargs)
+ warn_once = kwargs.get('warn_once', True)
def _get_arg_names_to_ok_vals():
"""Returns a dict mapping arg_name to DeprecatedArgSpec w/o position."""
@@ -286,13 +303,16 @@ def deprecated_args(date, instructions, *deprecated_arg_names_or_tuples):
deprecated_positions[arg_name].ok_value))):
invalid_args.append(arg_name)
for arg_name in invalid_args:
- logging.warning(
- 'From %s: calling %s (from %s) with %s is deprecated and will '
- 'be removed %s.\nInstructions for updating:\n%s',
- _call_location(), decorator_utils.get_qualified_name(func),
- func.__module__, arg_name,
- 'in a future version' if date is None else ('after %s' % date),
- instructions)
+ if (func, arg_name) not in _PRINTED_WARNING:
+ if warn_once:
+ _PRINTED_WARNING[(func, arg_name)] = True
+ logging.warning(
+ 'From %s: calling %s (from %s) with %s is deprecated and will '
+ 'be removed %s.\nInstructions for updating:\n%s',
+ _call_location(), decorator_utils.get_qualified_name(func),
+ func.__module__, arg_name,
+ 'in a future version' if date is None else ('after %s' % date),
+ instructions)
return func(*args, **kwargs)
return tf_decorator.make_decorator(func, new_func, 'deprecated',
_add_deprecated_arg_notice_to_docstring(
@@ -300,7 +320,8 @@ def deprecated_args(date, instructions, *deprecated_arg_names_or_tuples):
return deprecated_wrapper
-def deprecated_arg_values(date, instructions, **deprecated_kwargs):
+def deprecated_arg_values(date, instructions, warn_once=True,
+ **deprecated_kwargs):
"""Decorator for marking specific function argument values as deprecated.
This decorator logs a deprecation warning whenever the decorated function is
@@ -322,6 +343,9 @@ def deprecated_arg_values(date, instructions, **deprecated_kwargs):
Must be ISO 8601 (YYYY-MM-DD), or None
instructions: String. Instructions on how to update code using the
deprecated function.
+ warn_once: If `True`, warn only the first time this function is called with
+ deprecated argument values. Otherwise, every call (with a deprecated
+ argument value) will log a warning.
**deprecated_kwargs: The deprecated argument values.
Returns:
@@ -345,13 +369,15 @@ def deprecated_arg_values(date, instructions, **deprecated_kwargs):
named_args = tf_inspect.getcallargs(func, *args, **kwargs)
for arg_name, arg_value in deprecated_kwargs.items():
if arg_name in named_args and named_args[arg_name] == arg_value:
- logging.warning(
- 'From %s: calling %s (from %s) with %s=%s is deprecated and '
- 'will be removed %s.\nInstructions for updating:\n%s',
- _call_location(), decorator_utils.get_qualified_name(func),
- func.__module__, arg_name, arg_value,
- 'in a future version' if date is None else ('after %s' % date),
- instructions)
+ if (func, arg_name) not in _PRINTED_WARNING:
+ if warn_once:
+ _PRINTED_WARNING[(func, arg_name)] = True
+ logging.warning(
+ 'From %s: calling %s (from %s) with %s=%s is deprecated and '
+ 'will be removed %s.\nInstructions for updating:\n%s',
+ _call_location(), decorator_utils.get_qualified_name(func),
+ func.__module__, arg_name, arg_value, 'in a future version'
+ if date is None else ('after %s' % date), instructions)
return func(*args, **kwargs)
return tf_decorator.make_decorator(func, new_func, 'deprecated',
_add_deprecated_arg_notice_to_docstring(
diff --git a/tensorflow/python/util/deprecation_test.py b/tensorflow/python/util/deprecation_test.py
index 16d246c4df..e61edb5cfa 100644
--- a/tensorflow/python/util/deprecation_test.py
+++ b/tensorflow/python/util/deprecation_test.py
@@ -27,11 +27,25 @@ from tensorflow.python.util import deprecation
class DeprecationTest(test.TestCase):
@test.mock.patch.object(logging, "warning", autospec=True)
+ def test_deprecated_once(self, mock_warning):
+ date = "2016-07-04"
+ instructions = "This is how you update..."
+
+ @deprecation.deprecated(date, instructions, warn_once=True)
+ def _fn():
+ pass
+
+ _fn()
+ self.assertEqual(1, mock_warning.call_count)
+ _fn()
+ self.assertEqual(1, mock_warning.call_count)
+
+ @test.mock.patch.object(logging, "warning", autospec=True)
def test_silence(self, mock_warning):
date = "2016-07-04"
instructions = "This is how you update..."
- @deprecation.deprecated(date, instructions)
+ @deprecation.deprecated(date, instructions, warn_once=False)
def _fn():
pass
@@ -614,6 +628,43 @@ class DeprecatedArgsTest(test.TestCase):
self.assertEqual(3, _fn(1, None, 2, d2="my_ok_val"))
self.assertEqual(0, mock_warning.call_count)
+ @test.mock.patch.object(logging, "warning", autospec=True)
+ def test_deprecated_args_once(self, mock_warning):
+ date = "2016-07-04"
+ instructions = "This is how you update..."
+
+ @deprecation.deprecated_args(date, instructions, "arg", warn_once=True)
+ def _fn(arg=0): # pylint: disable=unused-argument
+ pass
+
+ _fn()
+ self.assertEqual(0, mock_warning.call_count)
+ _fn(arg=0)
+ self.assertEqual(1, mock_warning.call_count)
+ _fn(arg=1)
+ self.assertEqual(1, mock_warning.call_count)
+
+ @test.mock.patch.object(logging, "warning", autospec=True)
+ def test_deprecated_multiple_args_once_each(self, mock_warning):
+ date = "2016-07-04"
+ instructions = "This is how you update..."
+
+ @deprecation.deprecated_args(date, instructions, "arg0", "arg1",
+ warn_once=True)
+ def _fn(arg0=0, arg1=0): # pylint: disable=unused-argument
+ pass
+
+ _fn(arg0=0)
+ self.assertEqual(1, mock_warning.call_count)
+ _fn(arg0=0)
+ self.assertEqual(1, mock_warning.call_count)
+ _fn(arg1=0)
+ self.assertEqual(2, mock_warning.call_count)
+ _fn(arg0=0)
+ self.assertEqual(2, mock_warning.call_count)
+ _fn(arg1=0)
+ self.assertEqual(2, mock_warning.call_count)
+
class DeprecatedArgValuesTest(test.TestCase):
@@ -642,7 +693,8 @@ class DeprecatedArgValuesTest(test.TestCase):
date = "2016-07-04"
instructions = "This is how you update..."
- @deprecation.deprecated_arg_values(date, instructions, deprecated=True)
+ @deprecation.deprecated_arg_values(date, instructions, warn_once=False,
+ deprecated=True)
def _fn(arg0, arg1, deprecated=True):
"""fn doc.
@@ -692,7 +744,8 @@ class DeprecatedArgValuesTest(test.TestCase):
date = "2016-07-04"
instructions = "This is how you update..."
- @deprecation.deprecated_arg_values(date, instructions, deprecated=True)
+ @deprecation.deprecated_arg_values(date, instructions, warn_once=False,
+ deprecated=True)
def _fn(arg0, arg1, deprecated=True):
"""fn doc."""
return arg0 + arg1 if deprecated else arg1 + arg0
@@ -725,7 +778,8 @@ class DeprecatedArgValuesTest(test.TestCase):
date = "2016-07-04"
instructions = "This is how you update..."
- @deprecation.deprecated_arg_values(date, instructions, deprecated=True)
+ @deprecation.deprecated_arg_values(date, instructions, warn_once=False,
+ deprecated=True)
def _fn(arg0, arg1, deprecated=True):
return arg0 + arg1 if deprecated else arg1 + arg0
@@ -753,6 +807,42 @@ class DeprecatedArgValuesTest(test.TestCase):
self.assertEqual(3, _fn(1, 2))
self.assertEqual(2, mock_warning.call_count)
+ @test.mock.patch.object(logging, "warning", autospec=True)
+ def test_deprecated_arg_values_once(self, mock_warning):
+ date = "2016-07-04"
+ instructions = "This is how you update..."
+
+ @deprecation.deprecated_arg_values(date, instructions, warn_once=True,
+ deprecated=True)
+ def _fn(deprecated): # pylint: disable=unused-argument
+ pass
+
+ _fn(deprecated=False)
+ self.assertEqual(0, mock_warning.call_count)
+ _fn(deprecated=True)
+ self.assertEqual(1, mock_warning.call_count)
+ _fn(deprecated=True)
+ self.assertEqual(1, mock_warning.call_count)
+
+ @test.mock.patch.object(logging, "warning", autospec=True)
+ def test_deprecated_multiple_arg_values_once_each(self, mock_warning):
+ date = "2016-07-04"
+ instructions = "This is how you update..."
+
+ @deprecation.deprecated_arg_values(date, instructions, warn_once=True,
+ arg0="forbidden", arg1="disallowed")
+ def _fn(arg0, arg1): # pylint: disable=unused-argument
+ pass
+
+ _fn(arg0="allowed", arg1="also allowed")
+ self.assertEqual(0, mock_warning.call_count)
+ _fn(arg0="forbidden", arg1="disallowed")
+ self.assertEqual(2, mock_warning.call_count)
+ _fn(arg0="forbidden", arg1="allowed")
+ self.assertEqual(2, mock_warning.call_count)
+ _fn(arg0="forbidden", arg1="disallowed")
+ self.assertEqual(2, mock_warning.call_count)
+
class DeprecationArgumentsTest(test.TestCase):