1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
|
# Copyright 2013 by Rackspace Hosting, Inc.
#
# Licensed 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.
"""Miscellaneous deprecation utilities.
This module provides decorators to mark functions and classes as deprecated.
"""
from __future__ import annotations
import functools
from typing import Any, Callable, Optional
import warnings
__all__ = (
'AttributeRemovedError',
'DeprecatedWarning',
'deprecated',
'deprecated_args',
)
class AttributeRemovedError(AttributeError):
"""A deprecated attribute, class, or function has been subsequently removed."""
# NOTE(kgriffs): We don't want our deprecations to be ignored by default,
# so create our own type.
#
# TODO(kgriffs): Revisit this decision if users complain.
class DeprecatedWarning(UserWarning):
pass
def deprecated(
instructions: str, is_property: bool = False, method_name: Optional[str] = None
) -> Callable[[Callable[..., Any]], Any]:
"""Flag a method as deprecated.
This function returns a decorator which can be used to mark deprecated
functions. Applying this decorator will result in a warning being
emitted when the function is used.
Args:
instructions (str): Specific guidance for the developer, e.g.:
'Please migrate to add_proxy(...)'.
is_property (bool): If the deprecated object is a property. It
will omit the ``(...)`` from the generated documentation.
method_name (str, optional): Set to override the name of the
deprecated function or property in the generated
documentation (default ``None``). This is useful when
decorating an alias that carries the target's ``__name__``.
"""
def decorator(func: Callable[..., Any]) -> Callable[[Callable[..., Any]], Any]:
object_name = 'property' if is_property else 'function'
post_name = '' if is_property else '(...)'
message = 'Call to deprecated {} {}{}. {}'.format(
object_name, method_name or func.__name__, post_name, instructions
)
@functools.wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Callable[..., Any]:
warnings.warn(message, category=DeprecatedWarning, stacklevel=2)
return func(*args, **kwargs)
return wrapper
return decorator
def deprecated_args(
*, allowed_positional: int, is_method: bool = True
) -> Callable[..., Callable[..., Any]]:
"""Flag a method call with positional args as deprecated.
Keyword Args:
allowed_positional (int): Number of allowed positional arguments
is_method (bool, optional): The decorated function is a method. Will
add one to the number of allowed positional args to account for
``self``. Defaults to True.
"""
template = (
'Calls to {{fn}}(...) with{arg_text} positional args are deprecated.'
' Please specify them as keyword arguments instead.'
)
text = ' more than {}'.format(allowed_positional) if allowed_positional else ''
warn_text = template.format(arg_text=text)
if is_method:
allowed_positional += 1
def deprecated_args(fn: Callable[..., Any]) -> Callable[..., Callable[..., Any]]:
@functools.wraps(fn)
def wraps(*args: Any, **kwargs: Any) -> Callable[..., Any]:
if len(args) > allowed_positional:
warnings.warn(
warn_text.format(fn=fn.__qualname__),
DeprecatedWarning,
stacklevel=2,
)
return fn(*args, **kwargs)
return wraps
return deprecated_args
|