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)