Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/pandas/util/_decorators.py: 66%

145 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1from __future__ import annotations 

2 

3from functools import wraps 

4import inspect 

5from textwrap import dedent 

6from typing import ( 

7 Any, 

8 Callable, 

9 Mapping, 

10 cast, 

11) 

12import warnings 

13 

14from pandas._libs.properties import cache_readonly 

15from pandas._typing import ( 

16 F, 

17 T, 

18) 

19from pandas.util._exceptions import find_stack_level 

20 

21 

22def deprecate( 

23 name: str, 

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

25 version: str, 

26 alt_name: str | None = None, 

27 klass: type[Warning] | None = None, 

28 stacklevel: int = 2, 

29 msg: str | None = None, 

30) -> Callable[[F], F]: 

31 """ 

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

33 

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

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

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

37 it will contain the deprecation directive with the provided version 

38 so it can be detected for future removal. 

39 

40 Parameters 

41 ---------- 

42 name : str 

43 Name of function to deprecate. 

44 alternative : func 

45 Function to use instead. 

46 version : str 

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

48 alt_name : str, optional 

49 Name to use in preference of alternative.__name__. 

50 klass : Warning, default FutureWarning 

51 stacklevel : int, default 2 

52 msg : str 

53 The message to display in the warning. 

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

55 """ 

56 alt_name = alt_name or alternative.__name__ 

57 klass = klass or FutureWarning 

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

59 

60 @wraps(alternative) 

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

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

63 return alternative(*args, **kwargs) 

64 

65 # adding deprecated directive to the docstring 

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

67 doc_error_msg = ( 

68 "deprecate needs a correctly formatted docstring in " 

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

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

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

72 ) 

73 

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

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

76 # but we allow empty docstrings 

77 if alternative.__doc__: 77 ↛ 94line 77 didn't jump to line 94, because the condition on line 77 was never false

78 if alternative.__doc__.count("\n") < 3: 78 ↛ 79line 78 didn't jump to line 79, because the condition on line 78 was never true

79 raise AssertionError(doc_error_msg) 

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

81 if empty1 or empty2 and not summary: 81 ↛ 82line 81 didn't jump to line 82, because the condition on line 81 was never true

82 raise AssertionError(doc_error_msg) 

83 wrapper.__doc__ = dedent( 

84 f""" 

85 {summary.strip()} 

86 

87 .. deprecated:: {version} 

88 {msg} 

89 

90 {dedent(doc)}""" 

91 ) 

92 # error: Incompatible return value type (got "Callable[[VarArg(Any), KwArg(Any)], 

93 # Callable[...,Any]]", expected "Callable[[F], F]") 

94 return wrapper # type: ignore[return-value] 

95 

96 

97def deprecate_kwarg( 

98 old_arg_name: str, 

99 new_arg_name: str | None, 

100 mapping: Mapping[Any, Any] | Callable[[Any], Any] | None = None, 

101 stacklevel: int = 2, 

102) -> Callable[[F], F]: 

103 """ 

104 Decorator to deprecate a keyword argument of a function. 

105 

106 Parameters 

107 ---------- 

108 old_arg_name : str 

109 Name of argument in function to deprecate 

110 new_arg_name : str or None 

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

112 ``old_arg_name`` keyword is deprecated. 

113 mapping : dict or callable 

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

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

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

117 

118 Examples 

119 -------- 

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

121 

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

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

124 ... print(columns) 

125 ... 

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

127 should work ok 

128 

129 >>> f(cols='should raise warning') # doctest: +SKIP 

130 FutureWarning: cols is deprecated, use columns instead 

131 warnings.warn(msg, FutureWarning) 

132 should raise warning 

133 

134 >>> f(cols='should error', columns="can\'t pass do both") # doctest: +SKIP 

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

136 

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

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

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

140 ... 

141 >>> f(old='yes') # doctest: +SKIP 

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

143 warnings.warn(msg, FutureWarning) 

144 yes! 

145 

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

147 

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

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

150 ... print(cols) 

151 ... 

152 >>> f(cols='should raise warning') # doctest: +SKIP 

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

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

155 should raise warning 

156 >>> f(another_param='should not raise warning') # doctest: +SKIP 

157 should not raise warning 

158 

159 >>> f(cols='should raise warning', another_param='') # doctest: +SKIP 

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

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

162 should raise warning 

163 """ 

164 if mapping is not None and not hasattr(mapping, "get") and not callable(mapping): 164 ↛ 165line 164 didn't jump to line 165, because the condition on line 164 was never true

165 raise TypeError( 

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

167 ) 

168 

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

170 @wraps(func) 

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

172 old_arg_value = kwargs.pop(old_arg_name, None) 

173 

174 if old_arg_value is not None: 

175 if new_arg_name is None: 

176 msg = ( 

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

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

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

180 ) 

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

182 kwargs[old_arg_name] = old_arg_value 

183 return func(*args, **kwargs) 

184 

185 elif mapping is not None: 

186 if callable(mapping): 

187 new_arg_value = mapping(old_arg_value) 

188 else: 

189 new_arg_value = mapping.get(old_arg_value, old_arg_value) 

190 msg = ( 

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

192 "deprecated, use " 

193 f"{new_arg_name}={repr(new_arg_value)} instead." 

194 ) 

195 else: 

196 new_arg_value = old_arg_value 

197 msg = ( 

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

199 f"use {repr(new_arg_name)} instead." 

200 ) 

201 

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

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

204 msg = ( 

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

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

207 ) 

208 raise TypeError(msg) 

209 else: 

210 kwargs[new_arg_name] = new_arg_value 

211 return func(*args, **kwargs) 

212 

213 return cast(F, wrapper) 

214 

215 return _deprecate_kwarg 

216 

217 

218def _format_argument_list(allow_args: list[str]): 

219 """ 

220 Convert the allow_args argument (either string or integer) of 

221 `deprecate_nonkeyword_arguments` function to a string describing 

222 it to be inserted into warning message. 

223 

224 Parameters 

225 ---------- 

226 allowed_args : list, tuple or int 

227 The `allowed_args` argument for `deprecate_nonkeyword_arguments`, 

228 but None value is not allowed. 

229 

230 Returns 

231 ------- 

232 s : str 

233 The substring describing the argument list in best way to be 

234 inserted to the warning message. 

235 

236 Examples 

237 -------- 

238 `format_argument_list([])` -> '' 

239 `format_argument_list(['a'])` -> "except for the arguments 'a'" 

240 `format_argument_list(['a', 'b'])` -> "except for the arguments 'a' and 'b'" 

241 `format_argument_list(['a', 'b', 'c'])` -> 

242 "except for the arguments 'a', 'b' and 'c'" 

243 """ 

244 if "self" in allow_args: 

245 allow_args.remove("self") 

246 if not allow_args: 

247 return "" 

248 elif len(allow_args) == 1: 

249 return f" except for the argument '{allow_args[0]}'" 

250 else: 

251 last = allow_args[-1] 

252 args = ", ".join(["'" + x + "'" for x in allow_args[:-1]]) 

253 return f" except for the arguments {args} and '{last}'" 

254 

255 

256def future_version_msg(version: str | None) -> str: 

257 """Specify which version of pandas the deprecation will take place in.""" 

258 if version is None: 

259 return "In a future version of pandas" 

260 else: 

261 return f"Starting with pandas version {version}" 

262 

263 

264def deprecate_nonkeyword_arguments( 

265 version: str | None, 

266 allowed_args: list[str] | None = None, 

267 name: str | None = None, 

268) -> Callable[[F], F]: 

269 """ 

270 Decorator to deprecate a use of non-keyword arguments of a function. 

271 

272 Parameters 

273 ---------- 

274 version : str, optional 

275 The version in which positional arguments will become 

276 keyword-only. If None, then the warning message won't 

277 specify any particular version. 

278 

279 allowed_args : list, optional 

280 In case of list, it must be the list of names of some 

281 first arguments of the decorated functions that are 

282 OK to be given as positional arguments. In case of None value, 

283 defaults to list of all arguments not having the 

284 default value. 

285 

286 name : str, optional 

287 The specific name of the function to show in the warning 

288 message. If None, then the Qualified name of the function 

289 is used. 

290 """ 

291 

292 def decorate(func): 

293 old_sig = inspect.signature(func) 

294 

295 if allowed_args is not None: 

296 allow_args = allowed_args 

297 else: 

298 allow_args = [ 

299 p.name 

300 for p in old_sig.parameters.values() 

301 if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD) 

302 and p.default is p.empty 

303 ] 

304 

305 new_params = [ 

306 p.replace(kind=p.KEYWORD_ONLY) 

307 if ( 

308 p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD) 

309 and p.name not in allow_args 

310 ) 

311 else p 

312 for p in old_sig.parameters.values() 

313 ] 

314 new_params.sort(key=lambda p: p.kind) 

315 new_sig = old_sig.replace(parameters=new_params) 

316 

317 num_allow_args = len(allow_args) 

318 msg = ( 

319 f"{future_version_msg(version)} all arguments of " 

320 f"{name or func.__qualname__}{{arguments}} will be keyword-only." 

321 ) 

322 

323 @wraps(func) 

324 def wrapper(*args, **kwargs): 

325 if len(args) > num_allow_args: 

326 warnings.warn( 

327 msg.format(arguments=_format_argument_list(allow_args)), 

328 FutureWarning, 

329 stacklevel=find_stack_level(), 

330 ) 

331 return func(*args, **kwargs) 

332 

333 # error: "Callable[[VarArg(Any), KwArg(Any)], Any]" has no 

334 # attribute "__signature__" 

335 wrapper.__signature__ = new_sig # type: ignore[attr-defined] 

336 return wrapper 

337 

338 return decorate 

339 

340 

341def rewrite_axis_style_signature( 

342 name: str, extra_params: list[tuple[str, Any]] 

343) -> Callable[[F], F]: 

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

345 @wraps(func) 

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

347 return func(*args, **kwargs) 

348 

349 kind = inspect.Parameter.POSITIONAL_OR_KEYWORD 

350 params = [ 

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

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

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

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

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

356 ] 

357 

358 for pname, default in extra_params: 

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

360 

361 sig = inspect.Signature(params) 

362 

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

364 # error: "F" has no attribute "__signature__" 

365 func.__signature__ = sig # type: ignore[attr-defined] 

366 return cast(F, wrapper) 

367 

368 return decorate 

369 

370 

371def doc(*docstrings: str | Callable, **params) -> Callable[[F], F]: 

372 """ 

373 A decorator take docstring templates, concatenate them and perform string 

374 substitution on it. 

375 

376 This decorator will add a variable "_docstring_components" to the wrapped 

377 callable to keep track the original docstring template for potential usage. 

378 If it should be consider as a template, it will be saved as a string. 

379 Otherwise, it will be saved as callable, and later user __doc__ and dedent 

380 to get docstring. 

381 

382 Parameters 

383 ---------- 

384 *docstrings : str or callable 

385 The string / docstring / docstring template to be appended in order 

386 after default docstring under callable. 

387 **params 

388 The string which would be used to format docstring template. 

389 """ 

390 

391 def decorator(decorated: F) -> F: 

392 # collecting docstring and docstring templates 

393 docstring_components: list[str | Callable] = [] 

394 if decorated.__doc__: 

395 docstring_components.append(dedent(decorated.__doc__)) 

396 

397 for docstring in docstrings: 

398 if hasattr(docstring, "_docstring_components"): 

399 # error: Item "str" of "Union[str, Callable[..., Any]]" has no attribute 

400 # "_docstring_components" 

401 # error: Item "function" of "Union[str, Callable[..., Any]]" has no 

402 # attribute "_docstring_components" 

403 docstring_components.extend( 

404 docstring._docstring_components # type: ignore[union-attr] 

405 ) 

406 elif isinstance(docstring, str) or docstring.__doc__: 

407 docstring_components.append(docstring) 

408 

409 # formatting templates and concatenating docstring 

410 decorated.__doc__ = "".join( 

411 [ 

412 component.format(**params) 

413 if isinstance(component, str) 

414 else dedent(component.__doc__ or "") 

415 for component in docstring_components 

416 ] 

417 ) 

418 

419 # error: "F" has no attribute "_docstring_components" 

420 decorated._docstring_components = ( # type: ignore[attr-defined] 

421 docstring_components 

422 ) 

423 return decorated 

424 

425 return decorator 

426 

427 

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

429# module https://matplotlib.org/users/license.html 

430 

431 

432class Substitution: 

433 """ 

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

435 substitution on it. 

436 

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

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

439 

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

441 dictionary suitable for performing substitution; then 

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

443 

444 sub_author_name = Substitution(author='Jason') 

445 

446 @sub_author_name 

447 def some_function(x): 

448 "%(author)s wrote this function" 

449 

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

451 

452 One can also use positional arguments. 

453 

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

455 

456 @sub_first_last_names 

457 def some_function(x): 

458 "%s %s wrote the Raven" 

459 """ 

460 

461 def __init__(self, *args, **kwargs) -> None: 

462 if args and kwargs: 462 ↛ 463line 462 didn't jump to line 463, because the condition on line 462 was never true

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

464 

465 self.params = args or kwargs 

466 

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

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

469 return func 

470 

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

472 """ 

473 Update self.params with supplied args. 

474 """ 

475 if isinstance(self.params, dict): 

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

477 

478 

479class Appender: 

480 """ 

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

482 of the target function. 

483 

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

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

486 

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

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

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

490 

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

492 

493 @add_copyright 

494 def my_dog(has='fleas'): 

495 "This docstring will have a copyright below" 

496 pass 

497 """ 

498 

499 addendum: str | None 

500 

501 def __init__(self, addendum: str | None, join: str = "", indents: int = 0) -> None: 

502 if indents > 0: 

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

504 else: 

505 self.addendum = addendum 

506 self.join = join 

507 

508 def __call__(self, func: T) -> T: 

509 func.__doc__ = func.__doc__ if func.__doc__ else "" 

510 self.addendum = self.addendum if self.addendum else "" 

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

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

513 return func 

514 

515 

516def indent(text: str | None, indents: int = 1) -> str: 

517 if not text or not isinstance(text, str): 517 ↛ 518line 517 didn't jump to line 518, because the condition on line 517 was never true

518 return "" 

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

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

521 

522 

523__all__ = [ 

524 "Appender", 

525 "cache_readonly", 

526 "deprecate", 

527 "deprecate_kwarg", 

528 "deprecate_nonkeyword_arguments", 

529 "doc", 

530 "future_version_msg", 

531 "rewrite_axis_style_signature", 

532 "Substitution", 

533]