github.com/apache/beam/sdks/v2@v2.48.2/python/apache_beam/utils/annotations.py (about)

     1  #
     2  # Licensed to the Apache Software Foundation (ASF) under one or more
     3  # contributor license agreements.  See the NOTICE file distributed with
     4  # this work for additional information regarding copyright ownership.
     5  # The ASF licenses this file to You under the Apache License, Version 2.0
     6  # (the "License"); you may not use this file except in compliance with
     7  # the License.  You may obtain a copy of the License at
     8  #
     9  #    http://www.apache.org/licenses/LICENSE-2.0
    10  #
    11  # Unless required by applicable law or agreed to in writing, software
    12  # distributed under the License is distributed on an "AS IS" BASIS,
    13  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  # See the License for the specific language governing permissions and
    15  # limitations under the License.
    16  #
    17  
    18  """Deprecated annotations.
    19  
    20  Deprecated: Signifies that users are discouraged from using a public API
    21  typically because a better alternative exists, and the current form might be
    22  removed in a future version.
    23  
    24  Usage:
    25  For internal use only; no backwards-compatibility guarantees.
    26  
    27  Annotations come in two flavors: deprecated and experimental
    28  
    29  The 'deprecated' annotation requires a 'since" parameter to specify
    30  what version deprecated it.
    31  Both 'deprecated' and 'experimental' annotations can specify the
    32  current recommended version to use by means of a 'current' parameter.
    33  
    34  The following example illustrates how to annotate coexisting versions of the
    35  same function 'multiply'.::
    36  
    37    def multiply(arg1, arg2):
    38      print(arg1, '*', arg2, '=', end=' ')
    39      return arg1*arg2
    40  
    41  # This annotation marks 'old_multiply' as deprecated since 'v.1' and suggests
    42  # using 'multiply' instead.::
    43  
    44    @deprecated(since='v.1', current='multiply')
    45    def old_multiply(arg1, arg2):
    46      result = 0
    47      for i in xrange(arg1):
    48          result += arg2
    49      print(arg1, '*', arg2, '(the old way)=', end=' ')
    50      return result
    51  
    52  # Set a warning filter to control how often warnings are produced.::
    53  
    54    warnings.simplefilter("always")
    55    print(multiply(5, 6))
    56    print(old_multiply(5,6))
    57  """
    58  
    59  # pytype: skip-file
    60  
    61  import inspect
    62  import warnings
    63  from functools import partial
    64  from functools import wraps
    65  
    66  
    67  class BeamDeprecationWarning(DeprecationWarning):
    68    """Beam-specific deprecation warnings."""
    69  
    70  
    71  # Don't ignore BeamDeprecationWarnings.
    72  warnings.simplefilter('once', BeamDeprecationWarning)
    73  
    74  
    75  class _WarningMessage:
    76    """Utility class for assembling the warning message."""
    77    def __init__(self, label, since, current, extra_message, custom_message):
    78      """Initialize message, leave only name as placeholder."""
    79      if custom_message is None:
    80        message = '%name% is ' + label
    81        if label == 'deprecated':
    82          message += ' since %s' % since
    83        message += '. Use %s instead.' % current if current else '.'
    84        if extra_message:
    85          message += ' ' + extra_message
    86      else:
    87        if label == 'deprecated' and '%since%' not in custom_message:
    88          raise TypeError(
    89              "Replacement string %since% not found on \
    90          custom message")
    91        emptyArg = lambda x: '' if x is None else x
    92        message = custom_message\
    93        .replace('%since%', emptyArg(since))\
    94        .replace('%current%', emptyArg(current))\
    95        .replace('%extra%', emptyArg(extra_message))
    96      self.label = label
    97      self.message = message
    98  
    99    def emit_warning(self, fnc_name):
   100      if self.label == 'deprecated':
   101        warning_type = BeamDeprecationWarning
   102      else:
   103        warning_type = FutureWarning
   104      warnings.warn(
   105          self.message.replace('%name%', fnc_name), warning_type, stacklevel=3)
   106  
   107  
   108  def annotate(label, since, current, extra_message, custom_message=None):
   109    """Decorates an API with a deprecated or experimental annotation.
   110  
   111    Args:
   112      label: the kind of annotation ('deprecated' or 'experimental').
   113      since: the version that causes the annotation.
   114      current: the suggested replacement function.
   115      extra_message: an optional additional message.
   116      custom_message: if the default message does not suffice, the message
   117        can be changed using this argument. A string
   118        whit replacement tokens.
   119        A replecement string is were the previus args will
   120        be located on the custom message.
   121        The following replacement strings can be used:
   122        %name% -> API.__name__
   123        %since% -> since (Mandatory for the decapreted annotation)
   124        %current% -> current
   125        %extra% -> extra_message
   126  
   127    Returns:
   128      The decorator for the API.
   129    """
   130    warning_message = _WarningMessage(
   131        label=label,
   132        since=since,
   133        current=current,
   134        extra_message=extra_message,
   135        custom_message=custom_message)
   136  
   137    def _annotate(fnc):
   138      if inspect.isclass(fnc):
   139        # Wrapping class into function causes documentation rendering issue.
   140        # Patch class's __new__ method instead.
   141        old_new = fnc.__new__
   142  
   143        def wrapped_new(cls, *args, **kwargs):
   144          # Emit a warning when class instance is created.
   145          warning_message.emit_warning(fnc.__name__)
   146          if old_new is object.__new__:
   147            # object.__new__ takes no extra argument for python>=3
   148            return old_new(cls)
   149          return old_new(cls, *args, **kwargs)
   150  
   151        fnc.__new__ = staticmethod(wrapped_new)
   152        return fnc
   153      else:
   154  
   155        @wraps(fnc)
   156        def inner(*args, **kwargs):
   157          # Emit a warning when the function is called.
   158          warning_message.emit_warning(fnc.__name__)
   159          return fnc(*args, **kwargs)
   160  
   161        return inner
   162  
   163    return _annotate
   164  
   165  
   166  # Use partial application to customize each annotation.
   167  # 'current' will be optional in both deprecated and experimental
   168  # while 'since' will be mandatory for deprecated.
   169  deprecated = partial(
   170      annotate, label='deprecated', current=None, extra_message=None)