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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

78 statements  

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, an 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, extra_stacklevel=0): 

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: Literal["default", "error", "ignore", "always", "module", "once"] 

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 global filtering mechanism is used. 

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

106 

107 :type category: Type[Warning] 

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 :type extra_stacklevel: int 

114 :param extra_stacklevel: 

115 Number of additional stack levels to consider instrumentation rather than user code. 

116 With the default value of 0, the warning refers to where the class was instantiated 

117 or the function was called. 

118 

119 .. versionchanged:: 1.2.15 

120 Add the *extra_stacklevel* parameter. 

121 """ 

122 self.reason = reason or "" 

123 self.version = version or "" 

124 self.action = action 

125 self.category = category 

126 self.extra_stacklevel = extra_stacklevel 

127 super(ClassicAdapter, self).__init__() 

128 

129 def get_deprecated_msg(self, wrapped, instance): 

130 """ 

131 Get the deprecation warning message for the user. 

132 

133 :param wrapped: Wrapped class or function. 

134 

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

136 

137 :return: The warning message. 

138 """ 

139 if instance is None: 

140 if inspect.isclass(wrapped): 

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

142 else: 

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

144 else: 

145 if inspect.isclass(instance): 

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

147 else: 

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

149 if self.reason: 

150 fmt += " ({reason})" 

151 if self.version: 

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

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

154 

155 def __call__(self, wrapped): 

156 """ 

157 Decorate your class or function. 

158 

159 :param wrapped: Wrapped class or function. 

160 

161 :return: the decorated class or function. 

162 

163 .. versionchanged:: 1.2.4 

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

165 

166 .. versionchanged:: 1.2.8 

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

168 """ 

169 if inspect.isclass(wrapped): 

170 old_new1 = wrapped.__new__ 

171 

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

173 msg = self.get_deprecated_msg(wrapped, None) 

174 stacklevel = _class_stacklevel + self.extra_stacklevel 

175 if self.action: 

176 with warnings.catch_warnings(): 

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

178 warnings.warn(msg, category=self.category, stacklevel=stacklevel) 

179 else: 

180 warnings.warn(msg, category=self.category, stacklevel=stacklevel) 

181 if old_new1 is object.__new__: 

182 return old_new1(cls) 

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

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

185 

186 wrapped.__new__ = staticmethod(wrapped_cls) 

187 

188 elif inspect.isroutine(wrapped): 

189 @wrapt.decorator 

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

191 msg = self.get_deprecated_msg(wrapped_, instance_) 

192 stacklevel = _routine_stacklevel + self.extra_stacklevel 

193 if self.action: 

194 with warnings.catch_warnings(): 

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

196 warnings.warn(msg, category=self.category, stacklevel=stacklevel) 

197 else: 

198 warnings.warn(msg, category=self.category, stacklevel=stacklevel) 

199 return wrapped_(*args_, **kwargs_) 

200 

201 return wrapper_function(wrapped) 

202 

203 else: 

204 raise TypeError(repr(type(wrapped))) 

205 

206 return wrapped 

207 

208 

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

210 """ 

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

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

213 when the function is used. 

214 

215 **Classic usage:** 

216 

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

218 

219 .. code-block:: python 

220 

221 from deprecated import deprecated 

222 

223 

224 @deprecated 

225 def some_old_function(x, y): 

226 return x + y 

227 

228 You can also decorate a class or a method: 

229 

230 .. code-block:: python 

231 

232 from deprecated import deprecated 

233 

234 

235 class SomeClass(object): 

236 @deprecated 

237 def some_old_method(self, x, y): 

238 return x + y 

239 

240 

241 @deprecated 

242 class SomeOldClass(object): 

243 pass 

244 

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

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

247 

248 .. code-block:: python 

249 

250 from deprecated import deprecated 

251 

252 

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

254 def some_old_function(x, y): 

255 return x + y 

256 

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

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

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

260 

261 .. code-block:: python 

262 

263 from deprecated import deprecated 

264 

265 

266 @deprecated(category=PendingDeprecationWarning) 

267 def some_old_function(x, y): 

268 return x + y 

269 

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

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

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

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

274 

275 .. code-block:: python 

276 

277 from deprecated import deprecated 

278 

279 

280 @deprecated(action="error") 

281 def some_old_function(x, y): 

282 return x + y 

283 

284 The *extra_stacklevel* keyword argument allows you to specify additional stack levels 

285 to consider instrumentation rather than user code. With the default value of 0, the 

286 warning refers to where the class was instantiated or the function was called. 

287 """ 

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

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

290 args = args[1:] 

291 

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

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

294 

295 if args: 

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

297 adapter = adapter_cls(**kwargs) 

298 wrapped = args[0] 

299 return adapter(wrapped) 

300 

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