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
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1from __future__ import annotations
3from functools import wraps
4import inspect
5from textwrap import dedent
6from typing import (
7 Any,
8 Callable,
9 Mapping,
10 cast,
11)
12import warnings
14from pandas._libs.properties import cache_readonly
15from pandas._typing import (
16 F,
17 T,
18)
19from pandas.util._exceptions import find_stack_level
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.
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.
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."
60 @wraps(alternative)
61 def wrapper(*args, **kwargs) -> Callable[..., Any]:
62 warnings.warn(warning_msg, klass, stacklevel=stacklevel)
63 return alternative(*args, **kwargs)
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 )
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()}
87 .. deprecated:: {version}
88 {msg}
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]
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.
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.
118 Examples
119 --------
120 The following deprecates 'cols', using 'columns' instead
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
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
134 >>> f(cols='should error', columns="can\'t pass do both") # doctest: +SKIP
135 TypeError: Can only specify 'cols' or 'columns', not both
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!
146 To raise a warning that a keyword will be removed entirely in the future
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
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 )
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)
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)
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 )
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)
213 return cast(F, wrapper)
215 return _deprecate_kwarg
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.
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.
230 Returns
231 -------
232 s : str
233 The substring describing the argument list in best way to be
234 inserted to the warning message.
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}'"
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}"
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.
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.
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.
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 """
292 def decorate(func):
293 old_sig = inspect.signature(func)
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 ]
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)
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 )
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)
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
338 return decorate
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)
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 ]
358 for pname, default in extra_params:
359 params.append(inspect.Parameter(pname, kind, default=default))
361 sig = inspect.Signature(params)
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)
368 return decorate
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.
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.
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 """
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__))
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)
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 )
419 # error: "F" has no attribute "_docstring_components"
420 decorated._docstring_components = ( # type: ignore[attr-defined]
421 docstring_components
422 )
423 return decorated
425 return decorator
428# Substitution and Appender are derived from matplotlib.docstring (1.1.0)
429# module https://matplotlib.org/users/license.html
432class Substitution:
433 """
434 A decorator to take a function's docstring and perform string
435 substitution on it.
437 This decorator should be robust even if func.__doc__ is None
438 (for example, if -OO was passed to the interpreter)
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.
444 sub_author_name = Substitution(author='Jason')
446 @sub_author_name
447 def some_function(x):
448 "%(author)s wrote this function"
450 # note that some_function.__doc__ is now "Jason wrote this function"
452 One can also use positional arguments.
454 sub_first_last_names = Substitution('Edgar Allen', 'Poe')
456 @sub_first_last_names
457 def some_function(x):
458 "%s %s wrote the Raven"
459 """
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")
465 self.params = args or kwargs
467 def __call__(self, func: F) -> F:
468 func.__doc__ = func.__doc__ and func.__doc__ % self.params
469 return func
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)
479class Appender:
480 """
481 A function decorator that will append an addendum to the docstring
482 of the target function.
484 This decorator should be robust even if func.__doc__ is None
485 (for example, if -OO was passed to the interpreter).
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.
491 add_copyright = Appender("Copyright (c) 2009", join='\n')
493 @add_copyright
494 def my_dog(has='fleas'):
495 "This docstring will have a copyright below"
496 pass
497 """
499 addendum: str | None
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
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
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"))
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]