Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/deprecated/classic.py: 53%

79 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +0000

1# -*- coding: utf-8 -*- 

2""" 

3Classic deprecation warning 

4=========================== 

5 

6Classic ``@deprecated`` decorator to deprecate old python classes, functions or methods. 

7 

8.. _The Warnings Filter: https://docs.python.org/3/library/warnings.html#the-warnings-filter 

9""" 

10import functools 

11import inspect 

12import platform 

13import warnings 

14 

15import wrapt 

16 

17try: 

18 # If the C extension for wrapt was compiled and wrapt/_wrappers.pyd exists, then the 

19 # stack level that should be passed to warnings.warn should be 2. However, if using 

20 # a pure python wrapt, a extra stacklevel is required. 

21 import wrapt._wrappers 

22 

23 _routine_stacklevel = 2 

24 _class_stacklevel = 2 

25except ImportError: 

26 _routine_stacklevel = 3 

27 if platform.python_implementation() == "PyPy": 

28 _class_stacklevel = 2 

29 else: 

30 _class_stacklevel = 3 

31 

32string_types = (type(b''), type(u'')) 

33 

34 

35class ClassicAdapter(wrapt.AdapterFactory): 

36 """ 

37 Classic adapter -- *for advanced usage only* 

38 

39 This adapter is used to get the deprecation message according to the wrapped object type: 

40 class, function, standard method, static method, or class method. 

41 

42 This is the base class of the :class:`~deprecated.sphinx.SphinxAdapter` class 

43 which is used to update the wrapped object docstring. 

44 

45 You can also inherit this class to change the deprecation message. 

46 

47 In the following example, we change the message into "The ... is deprecated.": 

48 

49 .. code-block:: python 

50 

51 import inspect 

52 

53 from deprecated.classic import ClassicAdapter 

54 from deprecated.classic import deprecated 

55 

56 

57 class MyClassicAdapter(ClassicAdapter): 

58 def get_deprecated_msg(self, wrapped, instance): 

59 if instance is None: 

60 if inspect.isclass(wrapped): 

61 fmt = "The class {name} is deprecated." 

62 else: 

63 fmt = "The function {name} is deprecated." 

64 else: 

65 if inspect.isclass(instance): 

66 fmt = "The class method {name} is deprecated." 

67 else: 

68 fmt = "The method {name} is deprecated." 

69 if self.reason: 

70 fmt += " ({reason})" 

71 if self.version: 

72 fmt += " -- Deprecated since version {version}." 

73 return fmt.format(name=wrapped.__name__, 

74 reason=self.reason or "", 

75 version=self.version or "") 

76 

77 Then, you can use your ``MyClassicAdapter`` class like this in your source code: 

78 

79 .. code-block:: python 

80 

81 @deprecated(reason="use another function", adapter_cls=MyClassicAdapter) 

82 def some_old_function(x, y): 

83 return x + y 

84 """ 

85 

86 def __init__(self, reason="", version="", action=None, category=DeprecationWarning): 

87 """ 

88 Construct a wrapper adapter. 

89 

90 :type reason: str 

91 :param reason: 

92 Reason message which documents the deprecation in your library (can be omitted). 

93 

94 :type version: str 

95 :param version: 

96 Version of your project which deprecates this feature. 

97 If you follow the `Semantic Versioning <https://semver.org/>`_, 

98 the version number has the format "MAJOR.MINOR.PATCH". 

99 

100 :type action: str 

101 :param action: 

102 A warning filter used to activate or not the deprecation warning. 

103 Can be one of "error", "ignore", "always", "default", "module", or "once". 

104 If ``None`` or empty, the the global filtering mechanism is used. 

105 See: `The Warnings Filter`_ in the Python documentation. 

106 

107 :type category: type 

108 :param category: 

109 The warning category to use for the deprecation warning. 

110 By default, the category class is :class:`~DeprecationWarning`, 

111 you can inherit this class to define your own deprecation warning category. 

112 """ 

113 self.reason = reason or "" 

114 self.version = version or "" 

115 self.action = action 

116 self.category = category 

117 super(ClassicAdapter, self).__init__() 

118 

119 def get_deprecated_msg(self, wrapped, instance): 

120 """ 

121 Get the deprecation warning message for the user. 

122 

123 :param wrapped: Wrapped class or function. 

124 

125 :param instance: The object to which the wrapped function was bound when it was called. 

126 

127 :return: The warning message. 

128 """ 

129 if instance is None: 

130 if inspect.isclass(wrapped): 

131 fmt = "Call to deprecated class {name}." 

132 else: 

133 fmt = "Call to deprecated function (or staticmethod) {name}." 

134 else: 

135 if inspect.isclass(instance): 

136 fmt = "Call to deprecated class method {name}." 

137 else: 

138 fmt = "Call to deprecated method {name}." 

139 if self.reason: 

140 fmt += " ({reason})" 

141 if self.version: 

142 fmt += " -- Deprecated since version {version}." 

143 return fmt.format(name=wrapped.__name__, reason=self.reason or "", version=self.version or "") 

144 

145 def __call__(self, wrapped): 

146 """ 

147 Decorate your class or function. 

148 

149 :param wrapped: Wrapped class or function. 

150 

151 :return: the decorated class or function. 

152 

153 .. versionchanged:: 1.2.4 

154 Don't pass arguments to :meth:`object.__new__` (other than *cls*). 

155 

156 .. versionchanged:: 1.2.8 

157 The warning filter is not set if the *action* parameter is ``None`` or empty. 

158 """ 

159 if inspect.isclass(wrapped): 

160 old_new1 = wrapped.__new__ 

161 

162 def wrapped_cls(cls, *args, **kwargs): 

163 msg = self.get_deprecated_msg(wrapped, None) 

164 if self.action: 

165 with warnings.catch_warnings(): 

166 warnings.simplefilter(self.action, self.category) 

167 warnings.warn(msg, category=self.category, stacklevel=_class_stacklevel) 

168 else: 

169 warnings.warn(msg, category=self.category, stacklevel=_class_stacklevel) 

170 if old_new1 is object.__new__: 

171 return old_new1(cls) 

172 # actually, we don't know the real signature of *old_new1* 

173 return old_new1(cls, *args, **kwargs) 

174 

175 wrapped.__new__ = staticmethod(wrapped_cls) 

176 

177 return wrapped 

178 

179 

180def deprecated(*args, **kwargs): 

181 """ 

182 This is a decorator which can be used to mark functions 

183 as deprecated. It will result in a warning being emitted 

184 when the function is used. 

185 

186 **Classic usage:** 

187 

188 To use this, decorate your deprecated function with **@deprecated** decorator: 

189 

190 .. code-block:: python 

191 

192 from deprecated import deprecated 

193 

194 

195 @deprecated 

196 def some_old_function(x, y): 

197 return x + y 

198 

199 You can also decorate a class or a method: 

200 

201 .. code-block:: python 

202 

203 from deprecated import deprecated 

204 

205 

206 class SomeClass(object): 

207 @deprecated 

208 def some_old_method(self, x, y): 

209 return x + y 

210 

211 

212 @deprecated 

213 class SomeOldClass(object): 

214 pass 

215 

216 You can give a *reason* message to help the developer to choose another function/class, 

217 and a *version* number to specify the starting version number of the deprecation. 

218 

219 .. code-block:: python 

220 

221 from deprecated import deprecated 

222 

223 

224 @deprecated(reason="use another function", version='1.2.0') 

225 def some_old_function(x, y): 

226 return x + y 

227 

228 The *category* keyword argument allow you to specify the deprecation warning class of your choice. 

229 By default, :exc:`DeprecationWarning` is used but you can choose :exc:`FutureWarning`, 

230 :exc:`PendingDeprecationWarning` or a custom subclass. 

231 

232 .. code-block:: python 

233 

234 from deprecated import deprecated 

235 

236 

237 @deprecated(category=PendingDeprecationWarning) 

238 def some_old_function(x, y): 

239 return x + y 

240 

241 The *action* keyword argument allow you to locally change the warning filtering. 

242 *action* can be one of "error", "ignore", "always", "default", "module", or "once". 

243 If ``None``, empty or missing, the the global filtering mechanism is used. 

244 See: `The Warnings Filter`_ in the Python documentation. 

245 

246 .. code-block:: python 

247 

248 from deprecated import deprecated 

249 

250 

251 @deprecated(action="error") 

252 def some_old_function(x, y): 

253 return x + y 

254 

255 """ 

256 if args and isinstance(args[0], string_types): 

257 kwargs['reason'] = args[0] 

258 args = args[1:] 

259 

260 if args and not callable(args[0]): 

261 raise TypeError(repr(type(args[0]))) 

262 

263 if args: 

264 action = kwargs.get('action') 

265 category = kwargs.get('category', DeprecationWarning) 

266 adapter_cls = kwargs.pop('adapter_cls', ClassicAdapter) 

267 adapter = adapter_cls(**kwargs) 

268 

269 wrapped = args[0] 

270 if inspect.isclass(wrapped): 

271 wrapped = adapter(wrapped) 

272 return wrapped 

273 

274 elif inspect.isroutine(wrapped): 

275 

276 @wrapt.decorator(adapter=adapter) 

277 def wrapper_function(wrapped_, instance_, args_, kwargs_): 

278 msg = adapter.get_deprecated_msg(wrapped_, instance_) 

279 if action: 

280 with warnings.catch_warnings(): 

281 warnings.simplefilter(action, category) 

282 warnings.warn(msg, category=category, stacklevel=_routine_stacklevel) 

283 else: 

284 warnings.warn(msg, category=category, stacklevel=_routine_stacklevel) 

285 return wrapped_(*args_, **kwargs_) 

286 

287 return wrapper_function(wrapped) 

288 

289 else: 

290 raise TypeError(repr(type(wrapped))) 

291 

292 return functools.partial(deprecated, **kwargs)