diff options
author | 2017-09-13 08:53:54 -0700 | |
---|---|---|
committer | 2017-09-13 08:57:38 -0700 | |
commit | 4982ef0fa4c2a2a2e9a438d42872425ec4ef5a0e (patch) | |
tree | b5975c4272582fe3d3c3681caf1d15feb756b2e3 | |
parent | 99423416a4d388730fc4538a6bbdba4d514a8c63 (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.py | 80 | ||||
-rw-r--r-- | tensorflow/python/util/deprecation_test.py | 98 |
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): |