1# -*- coding: utf-8 -*- 2 3import warnings 4 5 6class Deprecation(object): 7 """Decorator factory for deprecating functions or classes. 8 9 This class represent deprecations of functions or classes and is designed 10 to be used with the ``warnings`` library. 11 12 Parameters 13 ---------- 14 last_supported_version : str, optional 15 Version string, e.g. ``'0.2.1'``. 16 will_be_missing_in : str, optional 17 Version string, e.g. ``'0.3.0'``. 18 use_instead : object or str, optional 19 Function or class to use instead or descriptive string. 20 issue : str, optional 21 issues_url : callback, optional 22 Converts issue to url, e.g. ``lambda s: 'https://github.com/user/repo/issues/%s/' % s.lstrip('gh-')``. 23 warning: DeprecationWarning, optional 24 Any subclass of DeprecationWarning, tip: you may invoke: 25 ``warnings.simplefilter('once', MyWarning)`` at module init. 26 27 Examples 28 -------- 29 >>> import warnings 30 >>> warnings.simplefilter("error", DeprecationWarning) 31 >>> @Deprecation() 32 ... def f(): 33 ... return 1 34 ... 35 >>> f() # doctest: +IGNORE_EXCEPTION_DETAIL 36 Traceback (most recent call last): 37 ... 38 DeprecationWarning: f is deprecated. 39 >>> @Deprecation(last_supported_version='0.4.0') 40 ... def some_old_function(x): 41 ... return x*x - x 42 ... 43 >>> Deprecation.inspect(some_old_function).last_supported_version 44 '0.4.0' 45 >>> @Deprecation(will_be_missing_in='1.0') 46 ... class ClumsyClass(object): 47 ... pass 48 ... 49 >>> Deprecation.inspect(ClumsyClass).will_be_missing_in 50 '1.0' 51 >>> warnings.resetwarnings() 52 53 Notes 54 ----- 55 :class:`DeprecationWarning` is ignored by default. Use custom warning 56 and filter appropriately. Alternatively, run python with ``-W`` flag or set 57 the appropriate environment variable: 58 59 :: 60 61 $ python -c 'import warnings as w; w.warn("X", DeprecationWarning)' 62 $ python -Wd -c 'import warnings as w; w.warn("X", DeprecationWarning)' 63 -c:1: DeprecationWarning: X 64 $ export PYTHONWARNINGS=d 65 $ python -c 'import warnings as w; w.warn("X", DeprecationWarning)' 66 -c:1: DeprecationWarning: X 67 68 """ 69 70 _deprecations = {} 71 72 def __init__( 73 self, 74 last_supported_version=None, 75 will_be_missing_in=None, 76 use_instead=None, 77 issue=None, 78 issues_url=None, 79 warning=DeprecationWarning, 80 ): 81 if ( 82 last_supported_version is not None 83 and not isinstance(last_supported_version, (str, tuple, list)) 84 and callable(last_supported_version) 85 ): 86 raise ValueError("last_supported_version not str, tuple or list") 87 88 self.last_supported_version = last_supported_version 89 self.will_be_missing_in = will_be_missing_in 90 self.use_instead = use_instead 91 self.issue = issue 92 self.issues_url = issues_url 93 self.warning = warning 94 self.warning_message = self._warning_message_template() 95 96 @classmethod 97 def inspect(cls, obj): 98 """Get the :class:`Deprecation` instance of a deprecated function.""" 99 return cls._deprecations[obj] 100 101 def _warning_message_template(self): 102 msg = "%(func_name)s is deprecated" 103 if self.last_supported_version is not None: 104 msg += " since (not including) % s" % self.last_supported_version 105 if self.will_be_missing_in is not None: 106 msg += ", it will be missing in %s" % self.will_be_missing_in 107 if self.issue is not None: 108 if self.issues_url is not None: 109 msg += self.issues_url(self.issue) 110 else: 111 msg += " (see issue %s)" % self.issue 112 if self.use_instead is not None: 113 try: 114 msg += ". Use %s instead" % self.use_instead.__name__ 115 except AttributeError: 116 msg += ". Use %s instead" % self.use_instead 117 return msg + "." 118 119 def __call__(self, wrapped): 120 """Decorates function to be deprecated""" 121 msg = self.warning_message % {"func_name": wrapped.__name__} 122 wrapped_doc = wrapped.__doc__ or "" 123 if hasattr(wrapped, "__mro__"): # wrapped is a class 124 125 class _Wrapper(wrapped): 126 __doc__ = msg + "\n\n" + wrapped_doc 127 128 def __init__(_self, *args, **kwargs): 129 warnings.warn(msg, self.warning, stacklevel=2) 130 wrapped.__init__(_self, *args, **kwargs) 131 132 else: # wrapped is a function 133 134 def _Wrapper(*args, **kwargs): 135 warnings.warn(msg, self.warning, stacklevel=2) 136 return wrapped(*args, **kwargs) 137 138 _Wrapper.__doc__ = msg + "\n\n" + wrapped_doc 139 140 self._deprecations[_Wrapper] = self 141 _Wrapper.__name__ = wrapped.__name__ 142 _Wrapper.__module__ = wrapped.__module__ 143 return _Wrapper 144