# Copyright (c) 2018 Hubert Kario
#
# See the LICENSE file for legal information regarding use of this file.
"""Methods for deprecating old names for arguments or attributes."""
import warnings
import inspect
from functools import wraps
[docs]
def deprecated_class_name(old_name,
warn="Class name '{old_name}' is deprecated, "
"please use '{new_name}'"):
"""
Class decorator to deprecate a use of class.
:param str old_name: the deprecated name that will be registered, but
will raise warnings if used.
:param str warn: DeprecationWarning format string for informing the
user what is the current class name, uses 'old_name' for the deprecated
keyword name and the 'new_name' for the current one.
Example: "Old name: {old_nam}, use '{new_name}' instead".
"""
def _wrap(obj):
assert callable(obj)
def _warn():
warnings.warn(warn.format(old_name=old_name,
new_name=obj.__name__),
DeprecationWarning,
stacklevel=3)
def _wrap_with_warn(func, is_inspect):
@wraps(func)
def _func(*args, **kwargs):
if is_inspect:
# XXX: If use another name to call,
# you will not get the warning.
# we do this instead of subclassing or metaclass as
# we want to isinstance(new_name(), old_name) and
# isinstance(old_name(), new_name) to work
frame = inspect.currentframe().f_back
code = inspect.getframeinfo(frame).code_context
if [line for line in code
if '{0}('.format(old_name) in line]:
_warn()
else:
_warn()
return func(*args, **kwargs)
return _func
# Make old name available.
frame = inspect.currentframe().f_back
if old_name in frame.f_globals:
raise NameError("Name '{0}' already in use.".format(old_name))
if inspect.isclass(obj):
obj.__init__ = _wrap_with_warn(obj.__init__, True)
placeholder = obj
else:
placeholder = _wrap_with_warn(obj, False)
frame.f_globals[old_name] = placeholder
return obj
return _wrap
[docs]
def deprecated_params(names, warn="Param name '{old_name}' is deprecated, "
"please use '{new_name}'"):
"""Decorator to translate obsolete names and warn about their use.
:param dict names: dictionary with pairs of new_name: old_name
that will be used for translating obsolete param names to new names
:param str warn: DeprecationWarning format string for informing the user
what is the current parameter name, uses 'old_name' for the
deprecated keyword name and 'new_name' for the current one.
Example: "Old name: {old_name}, use {new_name} instead".
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for new_name, old_name in names.items():
if old_name in kwargs:
if new_name in kwargs:
raise TypeError("got multiple values for keyword "
"argument '{0}'".format(new_name))
warnings.warn(warn.format(old_name=old_name,
new_name=new_name),
DeprecationWarning,
stacklevel=2)
kwargs[new_name] = kwargs.pop(old_name)
return func(*args, **kwargs)
return wrapper
return decorator
[docs]
def deprecated_instance_attrs(names,
warn="Attribute '{old_name}' is deprecated, "
"please use '{new_name}'"):
"""Decorator to deprecate class instance attributes.
Translates all names in `names` to use new names and emits warnings
if the translation was necessary. Does apply only to instance variables
and attributes (won't modify behaviour of class variables, static methods,
etc.
:param dict names: dictionary with paris of new_name: old_name that will
be used to translate the calls
:param str warn: DeprecationWarning format string for informing the user
what is the current parameter name, uses 'old_name' for the
deprecated keyword name and 'new_name' for the current one.
Example: "Old name: {old_name}, use {new_name} instead".
"""
# reverse the dict as we're looking for old attributes, not new ones
names = dict((j, i) for i, j in names.items())
def decorator(clazz):
def getx(self, name, __old_getx=getattr(clazz, "__getattr__", None)):
if name in names:
warnings.warn(warn.format(old_name=name,
new_name=names[name]),
DeprecationWarning,
stacklevel=2)
return getattr(self, names[name])
if __old_getx:
if hasattr(__old_getx, "__func__"):
return __old_getx.__func__(self, name)
return __old_getx(self, name)
raise AttributeError("'{0}' object has no attribute '{1}'"
.format(clazz.__name__, name))
getx.__name__ = "__getattr__"
clazz.__getattr__ = getx
def setx(self, name, value, __old_setx=getattr(clazz, "__setattr__")):
if name in names:
warnings.warn(warn.format(old_name=name,
new_name=names[name]),
DeprecationWarning,
stacklevel=2)
setattr(self, names[name], value)
else:
__old_setx(self, name, value)
setx.__name__ = "__setattr__"
clazz.__setattr__ = setx
def delx(self, name, __old_delx=getattr(clazz, "__delattr__")):
if name in names:
warnings.warn(warn.format(old_name=name,
new_name=names[name]),
DeprecationWarning,
stacklevel=2)
delattr(self, names[name])
else:
__old_delx(self, name)
delx.__name__ = "__delattr__"
clazz.__delattr__ = delx
return clazz
return decorator
[docs]
def deprecated_attrs(names, warn="Attribute '{old_name}' is deprecated, "
"please use '{new_name}'"):
"""Decorator to deprecate all specified attributes in class.
Translates all names in `names` to use new names and emits warnings
if the translation was necessary.
Note: uses metaclass magic so is incompatible with other metaclass uses
:param dict names: dictionary with paris of new_name: old_name that will
be used to translate the calls
:param str warn: DeprecationWarning format string for informing the user
what is the current parameter name, uses 'old_name' for the
deprecated keyword name and 'new_name' for the current one.
Example: "Old name: {old_name}, use {new_name} instead".
"""
# prepare metaclass for handling all the class methods, class variables
# and static methods (as they don't go through instance's __getattr__)
class DeprecatedProps(type):
pass
metaclass = deprecated_instance_attrs(names, warn)(DeprecatedProps)
def wrapper(cls):
cls = deprecated_instance_attrs(names, warn)(cls)
# apply metaclass
orig_vars = cls.__dict__.copy()
slots = orig_vars.get('__slots__')
if slots is not None:
if isinstance(slots, str):
slots = [slots]
for slots_var in slots:
orig_vars.pop(slots_var)
orig_vars.pop('__dict__', None)
orig_vars.pop('__weakref__', None)
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper
[docs]
def deprecated_method(message):
"""Decorator for deprecating methods.
:param ste message: The message you want to display.
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
warnings.warn("{0} is a deprecated method. {1}".format(func.__name__, message),
DeprecationWarning, stacklevel=2)
return func(*args, **kwargs)
return wrapper
return decorator