#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Deprecated annotations.
Deprecated: Signifies that users are discouraged from using a public API
typically because a better alternative exists, and the current form might be
removed in a future version.
Usage:
For internal use only; no backwards-compatibility guarantees.
Annotations come in two flavors: deprecated and experimental
The 'deprecated' annotation requires a 'since" parameter to specify
what version deprecated it.
Both 'deprecated' and 'experimental' annotations can specify the
current recommended version to use by means of a 'current' parameter.
The following example illustrates how to annotate coexisting versions of the
same function 'multiply'.::
def multiply(arg1, arg2):
print(arg1, '*', arg2, '=', end=' ')
return arg1*arg2
# This annotation marks 'old_multiply' as deprecated since 'v.1' and suggests
# using 'multiply' instead.::
@deprecated(since='v.1', current='multiply')
def old_multiply(arg1, arg2):
result = 0
for i in xrange(arg1):
result += arg2
print(arg1, '*', arg2, '(the old way)=', end=' ')
return result
# Set a warning filter to control how often warnings are produced.::
warnings.simplefilter("always")
print(multiply(5, 6))
print(old_multiply(5,6))
"""
# pytype: skip-file
import inspect
import warnings
from functools import partial
from functools import wraps
[docs]
class BeamDeprecationWarning(DeprecationWarning):
"""Beam-specific deprecation warnings."""
# Don't ignore BeamDeprecationWarnings.
warnings.simplefilter('once', BeamDeprecationWarning)
class _WarningMessage:
"""Utility class for assembling the warning message."""
def __init__(self, label, since, current, extra_message, custom_message):
"""Initialize message, leave only name as placeholder."""
if custom_message is None:
message = '%name% is ' + label
if label == 'deprecated':
message += ' since %s' % since
message += '. Use %s instead.' % current if current else '.'
if extra_message:
message += ' ' + extra_message
else:
if label == 'deprecated' and '%since%' not in custom_message:
raise TypeError(
"Replacement string %since% not found on \
custom message")
emptyArg = lambda x: '' if x is None else x
message = custom_message\
.replace('%since%', emptyArg(since))\
.replace('%current%', emptyArg(current))\
.replace('%extra%', emptyArg(extra_message))
self.label = label
self.message = message
def emit_warning(self, fnc_name):
if self.label == 'deprecated':
warning_type = BeamDeprecationWarning
else:
warning_type = FutureWarning
warnings.warn(
self.message.replace('%name%', fnc_name), warning_type, stacklevel=3)
[docs]
def annotate(label, since, current, extra_message, custom_message=None):
"""Decorates an API with a deprecated or experimental annotation.
Args:
label: the kind of annotation ('deprecated' or 'experimental').
since: the version that causes the annotation.
current: the suggested replacement function.
extra_message: an optional additional message.
custom_message: if the default message does not suffice, the message
can be changed using this argument. A string
whit replacement tokens.
A replecement string is were the previus args will
be located on the custom message.
The following replacement strings can be used:
%name% -> API.__name__
%since% -> since (Mandatory for the decapreted annotation)
%current% -> current
%extra% -> extra_message
Returns:
The decorator for the API.
"""
warning_message = _WarningMessage(
label=label,
since=since,
current=current,
extra_message=extra_message,
custom_message=custom_message)
def _annotate(fnc):
if inspect.isclass(fnc):
# Wrapping class into function causes documentation rendering issue.
# Patch class's __new__ method instead.
old_new = fnc.__new__
def wrapped_new(cls, *args, **kwargs):
# Emit a warning when class instance is created.
warning_message.emit_warning(fnc.__name__)
if old_new is object.__new__:
# object.__new__ takes no extra argument for python>=3
return old_new(cls)
return old_new(cls, *args, **kwargs)
fnc.__new__ = staticmethod(wrapped_new)
return fnc
else:
@wraps(fnc)
def inner(*args, **kwargs):
# Emit a warning when the function is called.
warning_message.emit_warning(fnc.__name__)
return fnc(*args, **kwargs)
return inner
return _annotate
# Use partial application to customize each annotation.
# 'current' will be optional in both deprecated and experimental
# while 'since' will be mandatory for deprecated.
deprecated = partial(
annotate, label='deprecated', current=None, extra_message=None)