Add the ability to warn only once if deprecated functionality is used, and make that the default.

PiperOrigin-RevId: 168545655
This commit is contained in:
Martin Wicke 2017-09-13 08:53:54 -07:00 committed by TensorFlower Gardener
parent 99423416a4
commit 4982ef0fa4
2 changed files with 147 additions and 31 deletions

View File

@ -32,6 +32,9 @@ from tensorflow.python.util import tf_inspect
# Allow deprecation warnings to be silenced temporarily with a context manager. # Allow deprecation warnings to be silenced temporarily with a context manager.
_PRINT_DEPRECATION_WARNINGS = True _PRINT_DEPRECATION_WARNINGS = True
# Remember which deprecation warnings have been printed already.
_PRINTED_WARNING = {}
def _add_deprecated_function_notice_to_docstring(doc, date, instructions): def _add_deprecated_function_notice_to_docstring(doc, date, instructions):
"""Adds a deprecation notice to a docstring for deprecated functions.""" """Adds a deprecation notice to a docstring for deprecated functions."""
@ -80,7 +83,7 @@ def _call_location():
return '%s:%d' % (entry[1], entry[2]) 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. """Decorator for marking functions or methods deprecated.
This decorator logs a deprecation warning whenever the decorated function is 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. Must be ISO 8601 (YYYY-MM-DD), or None.
instructions: String. Instructions on how to update code using the instructions: String. Instructions on how to update code using the
deprecated function. 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: Returns:
Decorated function or method. Decorated function or method.
@ -118,13 +123,16 @@ def deprecated(date, instructions):
@functools.wraps(func) @functools.wraps(func)
def new_func(*args, **kwargs): # pylint: disable=missing-docstring def new_func(*args, **kwargs): # pylint: disable=missing-docstring
if _PRINT_DEPRECATION_WARNINGS: if _PRINT_DEPRECATION_WARNINGS:
logging.warning( if func not in _PRINTED_WARNING:
'From %s: %s (from %s) is deprecated and will be removed %s.\n' if warn_once:
'Instructions for updating:\n%s', _PRINTED_WARNING[func] = True
_call_location(), decorator_utils.get_qualified_name(func), logging.warning(
func.__module__, 'From %s: %s (from %s) is deprecated and will be removed %s.\n'
'in a future version' if date is None else ('after %s' % date), 'Instructions for updating:\n%s',
instructions) _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 func(*args, **kwargs)
return tf_decorator.make_decorator( return tf_decorator.make_decorator(
func, new_func, 'deprecated', func, new_func, 'deprecated',
@ -137,7 +145,8 @@ DeprecatedArgSpec = collections.namedtuple(
'DeprecatedArgSpec', ['position', 'has_ok_value', 'ok_value']) '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. """Decorator for marking specific function arguments as deprecated.
This decorator logs a deprecation warning whenever the decorated function is 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. Must be ISO 8601 (YYYY-MM-DD), or None.
instructions: String. Instructions on how to update code using the instructions: String. Instructions on how to update code using the
deprecated function. 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. [ok_vals]). The string is the deprecated argument name.
Optionally, an ok-value may be provided. If the user provided Optionally, an ok-value may be provided. If the user provided
argument equals this value, the warning is suppressed. 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: Returns:
Decorated function or method. Decorated function or method.
@ -170,12 +183,16 @@ def deprecated_args(date, instructions, *deprecated_arg_names_or_tuples):
Raises: Raises:
ValueError: If date is not None or in ISO 8601 format, instructions are ValueError: If date is not None or in ISO 8601 format, instructions are
empty, the deprecated arguments are not present in the function empty, the deprecated arguments are not present in the function
signature, or the second element of a deprecated_tuple is not a signature, the second element of a deprecated_tuple is not a
list. list, or if a kwarg other than `warn_once` is passed.
""" """
_validate_deprecation_args(date, instructions) _validate_deprecation_args(date, instructions)
if not deprecated_arg_names_or_tuples: if not deprecated_arg_names_or_tuples:
raise ValueError('Specify which argument is deprecated.') 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(): def _get_arg_names_to_ok_vals():
"""Returns a dict mapping arg_name to DeprecatedArgSpec w/o position.""" """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))): deprecated_positions[arg_name].ok_value))):
invalid_args.append(arg_name) invalid_args.append(arg_name)
for arg_name in invalid_args: for arg_name in invalid_args:
logging.warning( if (func, arg_name) not in _PRINTED_WARNING:
'From %s: calling %s (from %s) with %s is deprecated and will ' if warn_once:
'be removed %s.\nInstructions for updating:\n%s', _PRINTED_WARNING[(func, arg_name)] = True
_call_location(), decorator_utils.get_qualified_name(func), logging.warning(
func.__module__, arg_name, 'From %s: calling %s (from %s) with %s is deprecated and will '
'in a future version' if date is None else ('after %s' % date), 'be removed %s.\nInstructions for updating:\n%s',
instructions) _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 func(*args, **kwargs)
return tf_decorator.make_decorator(func, new_func, 'deprecated', return tf_decorator.make_decorator(func, new_func, 'deprecated',
_add_deprecated_arg_notice_to_docstring( _add_deprecated_arg_notice_to_docstring(
@ -300,7 +320,8 @@ def deprecated_args(date, instructions, *deprecated_arg_names_or_tuples):
return deprecated_wrapper 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. """Decorator for marking specific function argument values as deprecated.
This decorator logs a deprecation warning whenever the decorated function is 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 Must be ISO 8601 (YYYY-MM-DD), or None
instructions: String. Instructions on how to update code using the instructions: String. Instructions on how to update code using the
deprecated function. 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. **deprecated_kwargs: The deprecated argument values.
Returns: Returns:
@ -345,13 +369,15 @@ def deprecated_arg_values(date, instructions, **deprecated_kwargs):
named_args = tf_inspect.getcallargs(func, *args, **kwargs) named_args = tf_inspect.getcallargs(func, *args, **kwargs)
for arg_name, arg_value in deprecated_kwargs.items(): for arg_name, arg_value in deprecated_kwargs.items():
if arg_name in named_args and named_args[arg_name] == arg_value: if arg_name in named_args and named_args[arg_name] == arg_value:
logging.warning( if (func, arg_name) not in _PRINTED_WARNING:
'From %s: calling %s (from %s) with %s=%s is deprecated and ' if warn_once:
'will be removed %s.\nInstructions for updating:\n%s', _PRINTED_WARNING[(func, arg_name)] = True
_call_location(), decorator_utils.get_qualified_name(func), logging.warning(
func.__module__, arg_name, arg_value, 'From %s: calling %s (from %s) with %s=%s is deprecated and '
'in a future version' if date is None else ('after %s' % date), 'will be removed %s.\nInstructions for updating:\n%s',
instructions) _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 func(*args, **kwargs)
return tf_decorator.make_decorator(func, new_func, 'deprecated', return tf_decorator.make_decorator(func, new_func, 'deprecated',
_add_deprecated_arg_notice_to_docstring( _add_deprecated_arg_notice_to_docstring(

View File

@ -26,12 +26,26 @@ from tensorflow.python.util import deprecation
class DeprecationTest(test.TestCase): 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) @test.mock.patch.object(logging, "warning", autospec=True)
def test_silence(self, mock_warning): def test_silence(self, mock_warning):
date = "2016-07-04" date = "2016-07-04"
instructions = "This is how you update..." instructions = "This is how you update..."
@deprecation.deprecated(date, instructions) @deprecation.deprecated(date, instructions, warn_once=False)
def _fn(): def _fn():
pass pass
@ -614,6 +628,43 @@ class DeprecatedArgsTest(test.TestCase):
self.assertEqual(3, _fn(1, None, 2, d2="my_ok_val")) self.assertEqual(3, _fn(1, None, 2, d2="my_ok_val"))
self.assertEqual(0, mock_warning.call_count) 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): class DeprecatedArgValuesTest(test.TestCase):
@ -642,7 +693,8 @@ class DeprecatedArgValuesTest(test.TestCase):
date = "2016-07-04" date = "2016-07-04"
instructions = "This is how you update..." 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): def _fn(arg0, arg1, deprecated=True):
"""fn doc. """fn doc.
@ -692,7 +744,8 @@ class DeprecatedArgValuesTest(test.TestCase):
date = "2016-07-04" date = "2016-07-04"
instructions = "This is how you update..." 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): def _fn(arg0, arg1, deprecated=True):
"""fn doc.""" """fn doc."""
return arg0 + arg1 if deprecated else arg1 + arg0 return arg0 + arg1 if deprecated else arg1 + arg0
@ -725,7 +778,8 @@ class DeprecatedArgValuesTest(test.TestCase):
date = "2016-07-04" date = "2016-07-04"
instructions = "This is how you update..." 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): def _fn(arg0, arg1, deprecated=True):
return arg0 + arg1 if deprecated else arg1 + arg0 return arg0 + arg1 if deprecated else arg1 + arg0
@ -753,6 +807,42 @@ class DeprecatedArgValuesTest(test.TestCase):
self.assertEqual(3, _fn(1, 2)) self.assertEqual(3, _fn(1, 2))
self.assertEqual(2, mock_warning.call_count) 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): class DeprecationArgumentsTest(test.TestCase):