Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from functools import wraps 

2import inspect 

3from textwrap import dedent 

4from typing import ( 

5 Any, 

6 Callable, 

7 List, 

8 Mapping, 

9 Optional, 

10 Tuple, 

11 Type, 

12 TypeVar, 

13 Union, 

14 cast, 

15) 

16import warnings 

17 

18from pandas._libs.properties import cache_readonly # noqa 

19 

20FuncType = Callable[..., Any] 

21F = TypeVar("F", bound=FuncType) 

22 

23 

24def deprecate( 

25 name: str, 

26 alternative: Callable[..., Any], 

27 version: str, 

28 alt_name: Optional[str] = None, 

29 klass: Optional[Type[Warning]] = None, 

30 stacklevel: int = 2, 

31 msg: Optional[str] = None, 

32) -> Callable[..., Any]: 

33 """ 

34 Return a new function that emits a deprecation warning on use. 

35 

36 To use this method for a deprecated function, another function 

37 `alternative` with the same signature must exist. The deprecated 

38 function will emit a deprecation warning, and in the docstring 

39 it will contain the deprecation directive with the provided version 

40 so it can be detected for future removal. 

41 

42 Parameters 

43 ---------- 

44 name : str 

45 Name of function to deprecate. 

46 alternative : func 

47 Function to use instead. 

48 version : str 

49 Version of pandas in which the method has been deprecated. 

50 alt_name : str, optional 

51 Name to use in preference of alternative.__name__. 

52 klass : Warning, default FutureWarning 

53 stacklevel : int, default 2 

54 msg : str 

55 The message to display in the warning. 

56 Default is '{name} is deprecated. Use {alt_name} instead.' 

57 """ 

58 

59 alt_name = alt_name or alternative.__name__ 

60 klass = klass or FutureWarning 

61 warning_msg = msg or f"{name} is deprecated, use {alt_name} instead" 

62 

63 @wraps(alternative) 

64 def wrapper(*args, **kwargs) -> Callable[..., Any]: 

65 warnings.warn(warning_msg, klass, stacklevel=stacklevel) 

66 return alternative(*args, **kwargs) 

67 

68 # adding deprecated directive to the docstring 

69 msg = msg or f"Use `{alt_name}` instead." 

70 doc_error_msg = ( 

71 "deprecate needs a correctly formatted docstring in " 

72 "the target function (should have a one liner short " 

73 "summary, and opening quotes should be in their own " 

74 f"line). Found:\n{alternative.__doc__}" 

75 ) 

76 

77 # when python is running in optimized mode (i.e. `-OO`), docstrings are 

78 # removed, so we check that a docstring with correct formatting is used 

79 # but we allow empty docstrings 

80 if alternative.__doc__: 

81 if alternative.__doc__.count("\n") < 3: 

82 raise AssertionError(doc_error_msg) 

83 empty1, summary, empty2, doc = alternative.__doc__.split("\n", 3) 

84 if empty1 or empty2 and not summary: 

85 raise AssertionError(doc_error_msg) 

86 wrapper.__doc__ = dedent( 

87 f""" 

88 {summary.strip()} 

89 

90 .. deprecated:: {version} 

91 {msg} 

92 

93 {dedent(doc)}""" 

94 ) 

95 

96 return wrapper 

97 

98 

99def deprecate_kwarg( 

100 old_arg_name: str, 

101 new_arg_name: Optional[str], 

102 mapping: Optional[Union[Mapping[Any, Any], Callable[[Any], Any]]] = None, 

103 stacklevel: int = 2, 

104) -> Callable[..., Any]: 

105 """ 

106 Decorator to deprecate a keyword argument of a function. 

107 

108 Parameters 

109 ---------- 

110 old_arg_name : str 

111 Name of argument in function to deprecate 

112 new_arg_name : str or None 

113 Name of preferred argument in function. Use None to raise warning that 

114 ``old_arg_name`` keyword is deprecated. 

115 mapping : dict or callable 

116 If mapping is present, use it to translate old arguments to 

117 new arguments. A callable must do its own value checking; 

118 values not found in a dict will be forwarded unchanged. 

119 

120 Examples 

121 -------- 

122 The following deprecates 'cols', using 'columns' instead 

123 

124 >>> @deprecate_kwarg(old_arg_name='cols', new_arg_name='columns') 

125 ... def f(columns=''): 

126 ... print(columns) 

127 ... 

128 >>> f(columns='should work ok') 

129 should work ok 

130 

131 >>> f(cols='should raise warning') 

132 FutureWarning: cols is deprecated, use columns instead 

133 warnings.warn(msg, FutureWarning) 

134 should raise warning 

135 

136 >>> f(cols='should error', columns="can\'t pass do both") 

137 TypeError: Can only specify 'cols' or 'columns', not both 

138 

139 >>> @deprecate_kwarg('old', 'new', {'yes': True, 'no': False}) 

140 ... def f(new=False): 

141 ... print('yes!' if new else 'no!') 

142 ... 

143 >>> f(old='yes') 

144 FutureWarning: old='yes' is deprecated, use new=True instead 

145 warnings.warn(msg, FutureWarning) 

146 yes! 

147 

148 To raise a warning that a keyword will be removed entirely in the future 

149 

150 >>> @deprecate_kwarg(old_arg_name='cols', new_arg_name=None) 

151 ... def f(cols='', another_param=''): 

152 ... print(cols) 

153 ... 

154 >>> f(cols='should raise warning') 

155 FutureWarning: the 'cols' keyword is deprecated and will be removed in a 

156 future version please takes steps to stop use of 'cols' 

157 should raise warning 

158 >>> f(another_param='should not raise warning') 

159 should not raise warning 

160 

161 >>> f(cols='should raise warning', another_param='') 

162 FutureWarning: the 'cols' keyword is deprecated and will be removed in a 

163 future version please takes steps to stop use of 'cols' 

164 should raise warning 

165 """ 

166 

167 if mapping is not None and not hasattr(mapping, "get") and not callable(mapping): 

168 raise TypeError( 

169 "mapping from old to new argument values must be dict or callable!" 

170 ) 

171 

172 def _deprecate_kwarg(func: F) -> F: 

173 @wraps(func) 

174 def wrapper(*args, **kwargs) -> Callable[..., Any]: 

175 old_arg_value = kwargs.pop(old_arg_name, None) 

176 

177 if old_arg_value is not None: 

178 if new_arg_name is None: 

179 msg = ( 

180 f"the {repr(old_arg_name)} keyword is deprecated and " 

181 "will be removed in a future version. Please take " 

182 f"steps to stop the use of {repr(old_arg_name)}" 

183 ) 

184 warnings.warn(msg, FutureWarning, stacklevel=stacklevel) 

185 kwargs[old_arg_name] = old_arg_value 

186 return func(*args, **kwargs) 

187 

188 elif mapping is not None: 

189 if callable(mapping): 

190 new_arg_value = mapping(old_arg_value) 

191 else: 

192 new_arg_value = mapping.get(old_arg_value, old_arg_value) 

193 msg = ( 

194 f"the {old_arg_name}={repr(old_arg_value)} keyword is " 

195 "deprecated, use " 

196 f"{new_arg_name}={repr(new_arg_value)} instead" 

197 ) 

198 else: 

199 new_arg_value = old_arg_value 

200 msg = ( 

201 f"the {repr(old_arg_name)}' keyword is deprecated, " 

202 f"use {repr(new_arg_name)} instead" 

203 ) 

204 

205 warnings.warn(msg, FutureWarning, stacklevel=stacklevel) 

206 if kwargs.get(new_arg_name) is not None: 

207 msg = ( 

208 f"Can only specify {repr(old_arg_name)} " 

209 f"or {repr(new_arg_name)}, not both" 

210 ) 

211 raise TypeError(msg) 

212 else: 

213 kwargs[new_arg_name] = new_arg_value 

214 return func(*args, **kwargs) 

215 

216 return cast(F, wrapper) 

217 

218 return _deprecate_kwarg 

219 

220 

221def rewrite_axis_style_signature( 

222 name: str, extra_params: List[Tuple[str, Any]] 

223) -> Callable[..., Any]: 

224 def decorate(func: F) -> F: 

225 @wraps(func) 

226 def wrapper(*args, **kwargs) -> Callable[..., Any]: 

227 return func(*args, **kwargs) 

228 

229 kind = inspect.Parameter.POSITIONAL_OR_KEYWORD 

230 params = [ 

231 inspect.Parameter("self", kind), 

232 inspect.Parameter(name, kind, default=None), 

233 inspect.Parameter("index", kind, default=None), 

234 inspect.Parameter("columns", kind, default=None), 

235 inspect.Parameter("axis", kind, default=None), 

236 ] 

237 

238 for pname, default in extra_params: 

239 params.append(inspect.Parameter(pname, kind, default=default)) 

240 

241 sig = inspect.Signature(params) 

242 

243 # https://github.com/python/typing/issues/598 

244 func.__signature__ = sig # type: ignore 

245 return cast(F, wrapper) 

246 

247 return decorate 

248 

249 

250# Substitution and Appender are derived from matplotlib.docstring (1.1.0) 

251# module http://matplotlib.org/users/license.html 

252 

253 

254class Substitution: 

255 """ 

256 A decorator to take a function's docstring and perform string 

257 substitution on it. 

258 

259 This decorator should be robust even if func.__doc__ is None 

260 (for example, if -OO was passed to the interpreter) 

261 

262 Usage: construct a docstring.Substitution with a sequence or 

263 dictionary suitable for performing substitution; then 

264 decorate a suitable function with the constructed object. e.g. 

265 

266 sub_author_name = Substitution(author='Jason') 

267 

268 @sub_author_name 

269 def some_function(x): 

270 "%(author)s wrote this function" 

271 

272 # note that some_function.__doc__ is now "Jason wrote this function" 

273 

274 One can also use positional arguments. 

275 

276 sub_first_last_names = Substitution('Edgar Allen', 'Poe') 

277 

278 @sub_first_last_names 

279 def some_function(x): 

280 "%s %s wrote the Raven" 

281 """ 

282 

283 def __init__(self, *args, **kwargs): 

284 if args and kwargs: 

285 raise AssertionError("Only positional or keyword args are allowed") 

286 

287 self.params = args or kwargs 

288 

289 def __call__(self, func: F) -> F: 

290 func.__doc__ = func.__doc__ and func.__doc__ % self.params 

291 return func 

292 

293 def update(self, *args, **kwargs) -> None: 

294 """ 

295 Update self.params with supplied args. 

296 """ 

297 if isinstance(self.params, dict): 

298 self.params.update(*args, **kwargs) 

299 

300 

301class Appender: 

302 """ 

303 A function decorator that will append an addendum to the docstring 

304 of the target function. 

305 

306 This decorator should be robust even if func.__doc__ is None 

307 (for example, if -OO was passed to the interpreter). 

308 

309 Usage: construct a docstring.Appender with a string to be joined to 

310 the original docstring. An optional 'join' parameter may be supplied 

311 which will be used to join the docstring and addendum. e.g. 

312 

313 add_copyright = Appender("Copyright (c) 2009", join='\n') 

314 

315 @add_copyright 

316 def my_dog(has='fleas'): 

317 "This docstring will have a copyright below" 

318 pass 

319 """ 

320 

321 addendum: Optional[str] 

322 

323 def __init__(self, addendum: Optional[str], join: str = "", indents: int = 0): 

324 if indents > 0: 

325 self.addendum = indent(addendum, indents=indents) 

326 else: 

327 self.addendum = addendum 

328 self.join = join 

329 

330 def __call__(self, func: F) -> F: 

331 func.__doc__ = func.__doc__ if func.__doc__ else "" 

332 self.addendum = self.addendum if self.addendum else "" 

333 docitems = [func.__doc__, self.addendum] 

334 func.__doc__ = dedent(self.join.join(docitems)) 

335 return func 

336 

337 

338def indent(text: Optional[str], indents: int = 1) -> str: 

339 if not text or not isinstance(text, str): 

340 return "" 

341 jointext = "".join(["\n"] + [" "] * indents) 

342 return jointext.join(text.split("\n"))