• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ==============================================================================
15
16"""Tensor utility functions."""
17import collections
18import functools
19import inspect
20import re
21
22from tensorflow.python.platform import tf_logging as logging
23from tensorflow.python.util import decorator_utils
24from tensorflow.python.util import is_in_graph_mode
25from tensorflow.python.util import tf_contextlib
26from tensorflow.python.util import tf_decorator
27from tensorflow.python.util import tf_inspect
28from tensorflow.tools.docs import doc_controls
29
30
31# Allow deprecation warnings to be silenced temporarily with a context manager.
32_PRINT_DEPRECATION_WARNINGS = True
33
34# Remember which deprecation warnings have been printed already.
35_PRINTED_WARNING = {}
36
37
38class DeprecatedNamesAlreadySet(Exception):
39  """Raised when setting deprecated names multiple times for the same symbol."""
40  pass
41
42
43def _add_deprecated_function_notice_to_docstring(doc, date, instructions):
44  """Adds a deprecation notice to a docstring for deprecated functions."""
45  main_text = ['THIS FUNCTION IS DEPRECATED. It will be removed %s.' %
46               ('in a future version' if date is None else ('after %s' % date))]
47  if instructions:
48    main_text.append('Instructions for updating:')
49  return decorator_utils.add_notice_to_docstring(
50      doc,
51      instructions,
52      'DEPRECATED FUNCTION',
53      '(deprecated)',
54      main_text,
55      notice_type='Deprecated')
56
57
58def _add_deprecated_arg_notice_to_docstring(doc, date, instructions,
59                                            deprecated_names):
60  """Adds a deprecation notice to a docstring for deprecated arguments."""
61
62  deprecation_string = ', '.join(sorted(deprecated_names))
63
64  return decorator_utils.add_notice_to_docstring(
65      doc,
66      instructions,
67      'DEPRECATED FUNCTION ARGUMENTS',
68      '(deprecated arguments)', [
69          'SOME ARGUMENTS ARE DEPRECATED: `(%s)`. '
70          'They will be removed %s.' %
71          (deprecation_string, 'in a future version' if date is None else
72           ('after %s' % date)), 'Instructions for updating:'
73      ],
74      notice_type='Deprecated')
75
76
77def _add_deprecated_arg_value_notice_to_docstring(doc, date, instructions,
78                                                  deprecated_name_value_dict):
79  """Adds a deprecation notice to a docstring for deprecated arguments."""
80
81  deprecation_string = ', '.join(
82      '%s=%r' % (key, value)
83      for key, value in sorted(deprecated_name_value_dict.items()))
84
85  when = 'in a future version' if date is None else ('after %s' % date)
86
87  return decorator_utils.add_notice_to_docstring(
88      doc,
89      instructions,
90      'DEPRECATED FUNCTION ARGUMENT VALUES',
91      '(deprecated argument values)', [
92          'SOME ARGUMENT VALUES ARE DEPRECATED: `(%s)`. '
93          'They will be removed %s.' %
94          (deprecation_string, when), 'Instructions for updating:'
95      ],
96      notice_type='Deprecated')
97
98
99def _validate_deprecation_args(date, instructions):
100  if date is not None and not re.match(r'20\d\d-[01]\d-[0123]\d', date):
101    raise ValueError(f'Date must be in format YYYY-MM-DD. Received: {date}')
102  if not instructions:
103    raise ValueError(
104        'Don\'t deprecate things without conversion instructions! Specify '
105        'the `instructions` argument.')
106
107
108def _call_location(outer=False):
109  """Returns call location given level up from current call."""
110  # Two up: <_call_location>, <_call_location's caller>
111  # tf_inspect is not required here. Please ignore the lint warning by adding
112  # DISABLE_IMPORT_INSPECT_CHECK=TRUE to your cl description. Using it caused
113  # test timeouts (b/189384061).
114  f = inspect.currentframe().f_back.f_back
115  parent = f and f.f_back
116  if outer and parent is not None:
117    f = parent
118  return '{}:{}'.format(f.f_code.co_filename, f.f_lineno)
119
120
121def _safe_eq(a, b):
122  if a is None or b is None:
123    return a is None and b is None
124  return a == b
125
126
127def _wrap_decorator(wrapped_function):
128  """Indicate that one function wraps another.
129
130  This decorator wraps a function using `tf_decorator.make_decorator`
131  so that doc generation scripts can pick up original function
132  signature.
133  It would be better to use @functools.wrap decorator, but it would
134  not update function signature to match wrapped function in Python 2.
135
136  Args:
137    wrapped_function: The function that decorated function wraps.
138
139  Returns:
140    Function that accepts wrapper function as an argument and returns
141    `TFDecorator` instance.
142  """
143  def wrapper(wrapper_func):
144    return tf_decorator.make_decorator(wrapped_function, wrapper_func)
145  return wrapper
146
147
148def deprecated_alias(deprecated_name, name, func_or_class, warn_once=True):
149  """Deprecate a symbol in favor of a new name with identical semantics.
150
151  This function is meant to be used when defining a backwards-compatibility
152  alias for a symbol which has been moved. For example:
153
154  module1.py:
155  ```python
156  class NewNameForClass: pass
157  ```
158
159  module2.py:
160  ```python
161  import module1
162
163  DeprecatedNameForClass = deprecated_alias(
164    deprecated_name='module2.DeprecatedNameForClass',
165    name='module1.NewNameForClass',
166    func_or_class=module1.NewNameForClass)
167  ```
168
169  This function works for classes and functions.
170
171  For classes, it creates a new class which is functionally identical (it
172  inherits from the original, and overrides its constructor), but which prints
173  a deprecation warning when an instance is created. It also adds a deprecation
174  notice to the class' docstring.
175
176  For functions, it returns a function wrapped by `tf_decorator.make_decorator`.
177  That function prints a warning when used, and has a deprecation notice in its
178  docstring. This is more or less equivalent (the deprecation warning has
179  slightly different text) to writing:
180
181  ```python
182  @deprecated
183  def deprecated_alias(original_args):
184    real_function(original_args)
185  ```
186
187  Args:
188    deprecated_name: The name of the symbol that is being deprecated, to be used
189      in the warning message. This should be its fully qualified name to avoid
190      confusion.
191    name: The name of the symbol that is to be used instead of the deprecated
192      name. This should be a fully qualified name to avoid confusion.
193    func_or_class: The (non-deprecated) class or function for which a deprecated
194      alias should be created.
195    warn_once: If True (the default), only print a deprecation warning the first
196      time this function is used, or the class is instantiated.
197
198  Returns:
199    A wrapped version of `func_or_class` which prints a deprecation warning on
200    use and has a modified docstring.
201  """
202  if tf_inspect.isclass(func_or_class):
203
204    # Make a new class with __init__ wrapped in a warning.
205    class _NewClass(func_or_class):  # pylint: disable=missing-docstring
206      __doc__ = decorator_utils.add_notice_to_docstring(
207          func_or_class.__doc__,
208          'Please use %s instead.' % name,
209          'DEPRECATED CLASS',
210          '(deprecated)', [
211              ('THIS CLASS IS DEPRECATED. '
212               'It will be removed in a future version. ')
213          ],
214          notice_type='Deprecated')
215      __name__ = func_or_class.__name__
216      __module__ = _call_location(outer=True)
217
218      @_wrap_decorator(func_or_class.__init__)
219      def __init__(self, *args, **kwargs):
220        if hasattr(_NewClass.__init__, '__func__'):
221          # Python 2
222          _NewClass.__init__.__func__.__doc__ = func_or_class.__init__.__doc__
223        else:
224          # Python 3
225          _NewClass.__init__.__doc__ = func_or_class.__init__.__doc__
226
227        if _PRINT_DEPRECATION_WARNINGS:
228          # We're making the alias as we speak. The original may have other
229          # aliases, so we cannot use it to check for whether it's already been
230          # warned about.
231          if _NewClass.__init__ not in _PRINTED_WARNING:
232            if warn_once:
233              _PRINTED_WARNING[_NewClass.__init__] = True
234            logging.warning(
235                'From %s: The name %s is deprecated. Please use %s instead.\n',
236                _call_location(), deprecated_name, name)
237        super(_NewClass, self).__init__(*args, **kwargs)
238
239    return _NewClass
240  else:
241    decorator_utils.validate_callable(func_or_class, 'deprecated')
242
243    # Make a wrapper for the original
244    @functools.wraps(func_or_class)
245    def new_func(*args, **kwargs):  # pylint: disable=missing-docstring
246      if _PRINT_DEPRECATION_WARNINGS:
247        # We're making the alias as we speak. The original may have other
248        # aliases, so we cannot use it to check for whether it's already been
249        # warned about.
250        if new_func not in _PRINTED_WARNING:
251          if warn_once:
252            _PRINTED_WARNING[new_func] = True
253          logging.warning(
254              'From %s: The name %s is deprecated. Please use %s instead.\n',
255              _call_location(), deprecated_name, name)
256      return func_or_class(*args, **kwargs)
257    return tf_decorator.make_decorator(
258        func_or_class, new_func, 'deprecated',
259        _add_deprecated_function_notice_to_docstring(
260            func_or_class.__doc__, None, 'Please use %s instead.' % name))
261
262
263def deprecated_endpoints(*args):
264  """Decorator for marking endpoints deprecated.
265
266  This decorator does not print deprecation messages.
267  TODO(annarev): eventually start printing deprecation warnings when
268  @deprecation_endpoints decorator is added.
269
270  Args:
271    *args: Deprecated endpoint names.
272
273  Returns:
274    A function that takes symbol as an argument and adds
275    _tf_deprecated_api_names to that symbol.
276    _tf_deprecated_api_names would be set to a list of deprecated
277    endpoint names for the symbol.
278  """
279  def deprecated_wrapper(func):
280    # pylint: disable=protected-access
281    if '_tf_deprecated_api_names' in func.__dict__:
282      raise DeprecatedNamesAlreadySet(
283          f'Cannot set deprecated names for {func.__name__} to {args}. '
284          'Deprecated names are already set to '
285          f'{func._tf_deprecated_api_names}.')
286    func._tf_deprecated_api_names = args
287    # pylint: disable=protected-access
288    return func
289  return deprecated_wrapper
290
291
292def deprecated(date, instructions, warn_once=True):
293  """Decorator for marking functions or methods deprecated.
294
295  This decorator logs a deprecation warning whenever the decorated function is
296  called. It has the following format:
297
298    <function> (from <module>) is deprecated and will be removed after <date>.
299    Instructions for updating:
300    <instructions>
301
302  If `date` is None, 'after <date>' is replaced with 'in a future version'.
303  <function> will include the class name if it is a method.
304
305  It also edits the docstring of the function: ' (deprecated)' is appended
306  to the first line of the docstring and a deprecation notice is prepended
307  to the rest of the docstring.
308
309  Args:
310    date: String or None. The date the function is scheduled to be removed.
311      Must be ISO 8601 (YYYY-MM-DD), or None.
312    instructions: String. Instructions on how to update code using the
313      deprecated function.
314    warn_once: Boolean. Set to `True` to warn only the first time the decorated
315      function is called. Otherwise, every call will log a warning.
316
317  Returns:
318    Decorated function or method.
319
320  Raises:
321    ValueError: If date is not None or in ISO 8601 format, or instructions are
322      empty.
323  """
324  _validate_deprecation_args(date, instructions)
325
326  def deprecated_wrapper(func_or_class):
327    """Deprecation wrapper."""
328    if isinstance(func_or_class, type):
329      # If a class is deprecated, you actually want to wrap the constructor.
330      cls = func_or_class
331      if cls.__new__ is object.__new__:
332        func = cls.__init__
333        constructor_name = '__init__'
334      else:
335        func = cls.__new__
336        constructor_name = '__new__'
337
338    else:
339      cls = None
340      constructor_name = None
341      func = func_or_class
342
343    decorator_utils.validate_callable(func, 'deprecated')
344    @functools.wraps(func)
345    def new_func(*args, **kwargs):  # pylint: disable=missing-docstring
346      if _PRINT_DEPRECATION_WARNINGS:
347        if func not in _PRINTED_WARNING:
348          if warn_once:
349            _PRINTED_WARNING[func] = True
350          logging.warning(
351              'From %s: %s (from %s) is deprecated and will be removed %s.\n'
352              'Instructions for updating:\n%s',
353              _call_location(), decorator_utils.get_qualified_name(func),
354              func.__module__,
355              'in a future version' if date is None else ('after %s' % date),
356              instructions)
357      return func(*args, **kwargs)
358
359    doc_controls.set_deprecated(new_func)
360    new_func = tf_decorator.make_decorator(
361        func, new_func, 'deprecated',
362        _add_deprecated_function_notice_to_docstring(func.__doc__, date,
363                                                     instructions))
364
365    if cls is None:
366      return new_func
367    else:
368      # Insert the wrapped function as the constructor
369      setattr(cls, constructor_name, new_func)
370
371      # And update the docstring of the class.
372      cls.__doc__ = _add_deprecated_function_notice_to_docstring(
373          cls.__doc__, date, instructions)
374
375      return cls
376
377  return deprecated_wrapper
378
379
380DeprecatedArgSpec = collections.namedtuple(
381    'DeprecatedArgSpec', ['position', 'has_ok_value', 'ok_value'])
382
383
384def deprecated_args(date, instructions, *deprecated_arg_names_or_tuples,
385                    **kwargs):
386  """Decorator for marking specific function arguments as deprecated.
387
388  This decorator logs a deprecation warning whenever the decorated function is
389  called with the deprecated argument. It has the following format:
390
391    Calling <function> (from <module>) with <arg> is deprecated and will be
392    removed after <date>. Instructions for updating:
393      <instructions>
394
395  If `date` is None, 'after <date>' is replaced with 'in a future version'.
396  <function> includes the class name if it is a method.
397
398  It also edits the docstring of the function: ' (deprecated arguments)' is
399  appended to the first line of the docstring and a deprecation notice is
400  prepended to the rest of the docstring.
401
402  Args:
403    date: String or None. The date the function is scheduled to be removed.
404      Must be ISO 8601 (YYYY-MM-DD), or None.
405    instructions: String. Instructions on how to update code using the
406      deprecated function.
407    *deprecated_arg_names_or_tuples: String or 2-Tuple (String,
408      ok_val).  The string is the deprecated argument name.
409      Optionally, an ok-value may be provided.  If the user provided
410      argument equals this value, the warning is suppressed.
411    **kwargs: If `warn_once=False` is passed, every call with a deprecated
412      argument will log a warning. The default behavior is to only warn the
413      first time the function is called with any given deprecated argument.
414      All other kwargs raise `ValueError`.
415
416  Returns:
417    Decorated function or method.
418
419  Raises:
420    ValueError: If date is not None or in ISO 8601 format, instructions are
421      empty, the deprecated arguments are not present in the function
422      signature, the second element of a deprecated_tuple is not a
423      list, or if a kwarg other than `warn_once` is passed.
424  """
425  _validate_deprecation_args(date, instructions)
426  if not deprecated_arg_names_or_tuples:
427    raise ValueError('Specify which argument is deprecated.')
428  if kwargs and list(kwargs.keys()) != ['warn_once']:
429    kwargs.pop('warn_once', None)
430    raise ValueError(f'Illegal argument passed to deprecated_args: {kwargs}')
431  warn_once = kwargs.get('warn_once', True)
432
433  def _get_arg_names_to_ok_vals():
434    """Returns a dict mapping arg_name to DeprecatedArgSpec w/o position."""
435    d = {}
436    for name_or_tuple in deprecated_arg_names_or_tuples:
437      if isinstance(name_or_tuple, tuple):
438        d[name_or_tuple[0]] = DeprecatedArgSpec(-1, True, name_or_tuple[1])
439      else:
440        d[name_or_tuple] = DeprecatedArgSpec(-1, False, None)
441    return d
442
443  def _get_deprecated_positional_arguments(names_to_ok_vals, arg_spec):
444    """Builds a dictionary from deprecated arguments to their spec.
445
446    Returned dict is keyed by argument name.
447    Each value is a DeprecatedArgSpec with the following fields:
448       position: The zero-based argument position of the argument
449         within the signature.  None if the argument isn't found in
450         the signature.
451       ok_values:  Values of this argument for which warning will be
452         suppressed.
453
454    Args:
455      names_to_ok_vals: dict from string arg_name to a list of values,
456        possibly empty, which should not elicit a warning.
457      arg_spec: Output from tf_inspect.getfullargspec on the called function.
458
459    Returns:
460      Dictionary from arg_name to DeprecatedArgSpec.
461    """
462    # Extract argument list
463    arg_space = arg_spec.args + arg_spec.kwonlyargs
464    arg_name_to_pos = {
465        name: pos for pos, name in enumerate(arg_space)}
466    deprecated_positional_args = {}
467    for arg_name, spec in iter(names_to_ok_vals.items()):
468      if arg_name in arg_name_to_pos:
469        pos = arg_name_to_pos[arg_name]
470        deprecated_positional_args[arg_name] = DeprecatedArgSpec(
471            pos, spec.has_ok_value, spec.ok_value)
472    return deprecated_positional_args
473
474  deprecated_arg_names = _get_arg_names_to_ok_vals()
475
476  def deprecated_wrapper(func):
477    """Deprecation decorator."""
478    decorator_utils.validate_callable(func, 'deprecated_args')
479
480    arg_spec = tf_inspect.getfullargspec(func)
481    deprecated_positions = _get_deprecated_positional_arguments(
482        deprecated_arg_names, arg_spec)
483
484    is_varargs_deprecated = arg_spec.varargs in deprecated_arg_names
485    is_kwargs_deprecated = arg_spec.varkw in deprecated_arg_names
486
487    if (len(deprecated_positions) + is_varargs_deprecated
488        + is_kwargs_deprecated
489        != len(deprecated_arg_names_or_tuples)):
490      known_args = (arg_spec.args
491                    + arg_spec.kwonlyargs
492                    + [arg_spec.varargs, arg_spec.varkw])
493      missing_args = [arg_name for arg_name in deprecated_arg_names
494                      if arg_name not in known_args]
495      raise ValueError('The following deprecated arguments are not present '
496                       f'in the function signature: {missing_args}. '
497                       'Expected arguments from the following list: '
498                       f'{known_args}.')
499
500    def _same_value(a, b):
501      """A comparison operation that works for multiple object types.
502
503      Returns True for two empty lists, two numeric values with the
504      same value, etc.
505
506      Returns False for (pd.DataFrame, None), and other pairs which
507      should not be considered equivalent.
508
509      Args:
510        a: value one of the comparison.
511        b: value two of the comparison.
512
513      Returns:
514        A boolean indicating whether the two inputs are the same value
515        for the purposes of deprecation.
516      """
517      if a is b:
518        return True
519      try:
520        equality = a == b
521        if isinstance(equality, bool):
522          return equality
523      except TypeError:
524        return False
525      return False
526
527    @functools.wraps(func)
528    def new_func(*args, **kwargs):
529      """Deprecation wrapper."""
530      # TODO(apassos) figure out a way to have reasonable performance with
531      # deprecation warnings and eager mode.
532      if is_in_graph_mode.IS_IN_GRAPH_MODE() and _PRINT_DEPRECATION_WARNINGS:
533        invalid_args = []
534        named_args = tf_inspect.getcallargs(func, *args, **kwargs)
535        for arg_name, spec in iter(deprecated_positions.items()):
536          if (spec.position < len(args) and
537              not (spec.has_ok_value and
538                   _same_value(named_args[arg_name], spec.ok_value))):
539            invalid_args.append(arg_name)
540        if is_varargs_deprecated and len(args) > len(arg_spec.args):
541          invalid_args.append(arg_spec.varargs)
542        if is_kwargs_deprecated and kwargs:
543          invalid_args.append(arg_spec.varkw)
544        for arg_name in deprecated_arg_names:
545          if (arg_name in kwargs and
546              not (deprecated_positions[arg_name].has_ok_value and
547                   _same_value(named_args[arg_name],
548                               deprecated_positions[arg_name].ok_value))):
549            invalid_args.append(arg_name)
550        for arg_name in invalid_args:
551          if (func, arg_name) not in _PRINTED_WARNING:
552            if warn_once:
553              _PRINTED_WARNING[(func, arg_name)] = True
554            logging.warning(
555                'From %s: calling %s (from %s) with %s is deprecated and will '
556                'be removed %s.\nInstructions for updating:\n%s',
557                _call_location(), decorator_utils.get_qualified_name(func),
558                func.__module__, arg_name,
559                'in a future version' if date is None else ('after %s' % date),
560                instructions)
561      return func(*args, **kwargs)
562
563    doc = _add_deprecated_arg_notice_to_docstring(
564        func.__doc__, date, instructions, sorted(deprecated_arg_names.keys()))
565    return tf_decorator.make_decorator(func, new_func, 'deprecated', doc)
566
567  return deprecated_wrapper
568
569
570def deprecated_arg_values(date, instructions, warn_once=True,
571                          **deprecated_kwargs):
572  """Decorator for marking specific function argument values as deprecated.
573
574  This decorator logs a deprecation warning whenever the decorated function is
575  called with the deprecated argument values. It has the following format:
576
577    Calling <function> (from <module>) with <arg>=<value> is deprecated and
578    will be removed after <date>. Instructions for updating:
579      <instructions>
580
581  If `date` is None, 'after <date>' is replaced with 'in a future version'.
582  <function> will include the class name if it is a method.
583
584  It also edits the docstring of the function: ' (deprecated arguments)' is
585  appended to the first line of the docstring and a deprecation notice is
586  prepended to the rest of the docstring.
587
588  Args:
589    date: String or None. The date the function is scheduled to be removed.
590      Must be ISO 8601 (YYYY-MM-DD), or None
591    instructions: String. Instructions on how to update code using the
592      deprecated function.
593    warn_once: If `True`, warn only the first time this function is called with
594      deprecated argument values. Otherwise, every call (with a deprecated
595      argument value) will log a warning.
596    **deprecated_kwargs: The deprecated argument values.
597
598  Returns:
599    Decorated function or method.
600
601  Raises:
602    ValueError: If date is not None or in ISO 8601 format, or instructions are
603      empty.
604  """
605  _validate_deprecation_args(date, instructions)
606  if not deprecated_kwargs:
607    raise ValueError('Specify which argument values are deprecated.')
608
609  def deprecated_wrapper(func):
610    """Deprecation decorator."""
611    decorator_utils.validate_callable(func, 'deprecated_arg_values')
612    @functools.wraps(func)
613    def new_func(*args, **kwargs):
614      """Deprecation wrapper."""
615      if _PRINT_DEPRECATION_WARNINGS:
616        named_args = tf_inspect.getcallargs(func, *args, **kwargs)
617        for arg_name, arg_value in deprecated_kwargs.items():
618          if arg_name in named_args and _safe_eq(named_args[arg_name],
619                                                 arg_value):
620            if (func, arg_name) not in _PRINTED_WARNING:
621              if warn_once:
622                _PRINTED_WARNING[(func, arg_name)] = True
623              logging.warning(
624                  'From %s: calling %s (from %s) with %s=%s is deprecated and '
625                  'will be removed %s.\nInstructions for updating:\n%s',
626                  _call_location(), decorator_utils.get_qualified_name(func),
627                  func.__module__, arg_name, arg_value, 'in a future version'
628                  if date is None else ('after %s' % date), instructions)
629      return func(*args, **kwargs)
630
631    doc = _add_deprecated_arg_value_notice_to_docstring(
632        func.__doc__, date, instructions, deprecated_kwargs)
633    return tf_decorator.make_decorator(func, new_func, 'deprecated', doc)
634
635  return deprecated_wrapper
636
637
638def deprecated_argument_lookup(new_name, new_value, old_name, old_value):
639  """Looks up deprecated argument name and ensures both are not used.
640
641  Args:
642    new_name: new name of argument
643    new_value: value of new argument (or None if not used)
644    old_name: old name of argument
645    old_value: value of old argument (or None if not used)
646  Returns:
647    The effective argument that should be used.
648  Raises:
649    ValueError: if new_value and old_value are both non-null
650  """
651  if old_value is not None:
652    if new_value is not None:
653      raise ValueError(f"Cannot specify both '{old_name}' and '{new_name}'.")
654    return old_value
655  return new_value
656
657
658def rewrite_argument_docstring(old_doc, old_argument, new_argument):
659  return old_doc.replace('`%s`' % old_argument, '`%s`' % new_argument).replace(
660      '%s:' % old_argument, '%s:' % new_argument)
661
662
663@tf_contextlib.contextmanager
664def silence():
665  """Temporarily silence deprecation warnings."""
666  global _PRINT_DEPRECATION_WARNINGS
667  print_deprecation_warnings = _PRINT_DEPRECATION_WARNINGS
668  _PRINT_DEPRECATION_WARNINGS = False
669  yield
670  _PRINT_DEPRECATION_WARNINGS = print_deprecation_warnings
671
672
673def deprecate_moved_module(deprecated_name, new_module, deletion_version):
674  """Logs a warning when a module that has been moved to a new location is used.
675
676  Copy the following code into the old module:
677
678  ```
679  import deprecation
680  import new_module
681
682  __getattr__ = deprecation.deprecate_moved_module(
683      __name__, new_module, "2.9")  # adjust version number.
684  ```
685
686  Args:
687    deprecated_name: Name of old module.
688    new_module: Module to replace the old module.
689    deletion_version: Version of TensorFlow in which the old module will be
690      removed.
691
692  Returns:
693    A function that logs a warning and returns the symbol from the new module.
694    Set this function as the module's `__getattr__`.
695  """
696  def getter(name):
697    if getter not in _PRINTED_WARNING and _PRINT_DEPRECATION_WARNINGS:
698      _PRINTED_WARNING[getter] = True
699      logging.warning(
700          'Please fix your imports. Module %s has been moved to %s. The old '
701          'module will be deleted in version %s.',
702          deprecated_name, new_module.__name__, deletion_version)
703    return getattr(new_module, name)
704  return getter
705
706
707class HiddenTfApiAttribute(property):
708  """Hides a class attribute from the public API.
709
710  Attributes in public classes can be hidden from the API by having an '_' in
711  front of the name (e.g. ClassName._variables). This doesn't work when
712  attributes or methods are inherited from a parent class. To hide inherited
713  attributes, set their values to be `deprecation.hide_attribute_from_api`.
714  For example, this is used in V2 Estimator to hide the deprecated
715  export_savedmodel method:
716    class EstimatorV2(Estimator):
717       export_savedmodel = deprecation.hide_attribute_from_api('...')
718  """
719
720  def __init__(self, deprecation_message):
721
722    def raise_error(unused_self):
723      raise AttributeError(deprecation_message)
724
725    super(HiddenTfApiAttribute, self).__init__(raise_error)
726
727
728hide_attribute_from_api = HiddenTfApiAttribute  # pylint: disable=invalid-name
729
730# TODO(kathywu): Remove once cl/246395236 is submitted.
731HIDDEN_ATTRIBUTE = HiddenTfApiAttribute('This attribute has been deprecated.')
732