import sys import warnings import functools __all__ = ["deprecated", "DeprecationDict"] class deprecated(object): """Decorator to mark a function or class as deprecated. Issue a warning when the function is called/the class is instantiated and adds a warning to the docstring. The optional extra argument will be appended to the deprecation message and the docstring. Note: to use this with the default value for extra, put in an empty of parentheses: >>> from sklearn.utils import deprecated >>> deprecated() # doctest: +ELLIPSIS >>> @deprecated() ... def some_function(): pass Parameters ---------- extra : string to be added to the deprecation messages """ # Adapted from http://wiki.python.org/moin/PythonDecoratorLibrary, # but with many changes. def __init__(self, extra=''): self.extra = extra def __call__(self, obj): """Call method Parameters ---------- obj : object """ if isinstance(obj, type): return self._decorate_class(obj) else: return self._decorate_fun(obj) def _decorate_class(self, cls): msg = "Class %s is deprecated" % cls.__name__ if self.extra: msg += "; %s" % self.extra # FIXME: we should probably reset __new__ for full generality init = cls.__init__ def wrapped(*args, **kwargs): warnings.warn(msg, category=DeprecationWarning) return init(*args, **kwargs) cls.__init__ = wrapped wrapped.__name__ = '__init__' wrapped.__doc__ = self._update_doc(init.__doc__) wrapped.deprecated_original = init return cls def _decorate_fun(self, fun): """Decorate function fun""" msg = "Function %s is deprecated" % fun.__name__ if self.extra: msg += "; %s" % self.extra @functools.wraps(fun) def wrapped(*args, **kwargs): warnings.warn(msg, category=DeprecationWarning) return fun(*args, **kwargs) wrapped.__doc__ = self._update_doc(wrapped.__doc__) return wrapped def _update_doc(self, olddoc): newdoc = "DEPRECATED" if self.extra: newdoc = "%s: %s" % (newdoc, self.extra) if olddoc: newdoc = "%s\n\n%s" % (newdoc, olddoc) return newdoc def _is_deprecated(func): """Helper to check if func is wraped by our deprecated decorator""" if sys.version_info < (3, 5): raise NotImplementedError("This is only available for python3.5 " "or above") closures = getattr(func, '__closure__', []) if closures is None: closures = [] is_deprecated = ('deprecated' in ''.join([c.cell_contents for c in closures if isinstance(c.cell_contents, str)])) return is_deprecated class DeprecationDict(dict): """A dict which raises a warning when some keys are looked up Note, this does not raise a warning for __contains__ and iteration. It also will raise a warning even after the key has been manually set by the user. """ def __init__(self, *args, **kwargs): self._deprecations = {} super(DeprecationDict, self).__init__(*args, **kwargs) def __getitem__(self, key): if key in self._deprecations: warn_args, warn_kwargs = self._deprecations[key] warnings.warn(*warn_args, **warn_kwargs) return super(DeprecationDict, self).__getitem__(key) def get(self, key, default=None): # dict does not implement it like this, hence it needs to be overridden try: return self[key] except KeyError: return default def add_warning(self, key, *args, **kwargs): """Add a warning to be triggered when the specified key is read""" self._deprecations[key] = (args, kwargs)