Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/astroid/decorators.py: 26%
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
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
1# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
2# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
3# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
5"""A few useful function/method decorators."""
7from __future__ import annotations
9import functools
10import inspect
11import sys
12import warnings
13from collections.abc import Callable, Generator
14from typing import ParamSpec, TypeVar
16from astroid import util
17from astroid.context import InferenceContext
18from astroid.exceptions import InferenceError
19from astroid.typing import InferenceResult
21_R = TypeVar("_R")
22_P = ParamSpec("_P")
25def path_wrapper(func):
26 """Return the given infer function wrapped to handle the path.
28 Used to stop inference if the node has already been looked
29 at for a given `InferenceContext` to prevent infinite recursion
30 """
32 @functools.wraps(func)
33 def wrapped(
34 node, context: InferenceContext | None = None, _func=func, **kwargs
35 ) -> Generator:
36 """Wrapper function handling context."""
37 if context is None:
38 context = InferenceContext()
39 if context.push(node):
40 return
42 yielded = set()
44 for res in _func(node, context, **kwargs):
45 # unproxy only true instance, not const, tuple, dict...
46 if res.__class__.__name__ == "Instance":
47 ares = res._proxied
48 else:
49 ares = res
50 if ares not in yielded:
51 yield res
52 yielded.add(ares)
54 return wrapped
57def yes_if_nothing_inferred(
58 func: Callable[_P, Generator[InferenceResult]],
59) -> Callable[_P, Generator[InferenceResult]]:
60 def inner(*args: _P.args, **kwargs: _P.kwargs) -> Generator[InferenceResult]:
61 generator = func(*args, **kwargs)
63 try:
64 yield next(generator)
65 except StopIteration:
66 # generator is empty
67 yield util.Uninferable
68 return
70 yield from generator
72 return inner
75def raise_if_nothing_inferred(
76 func: Callable[_P, Generator[InferenceResult]],
77) -> Callable[_P, Generator[InferenceResult]]:
78 def inner(*args: _P.args, **kwargs: _P.kwargs) -> Generator[InferenceResult]:
79 generator = func(*args, **kwargs)
80 try:
81 yield next(generator)
82 except StopIteration as error:
83 # generator is empty
84 if error.args:
85 raise InferenceError(**error.args[0]) from error
86 raise InferenceError(
87 "StopIteration raised without any error information."
88 ) from error
89 except RecursionError as error:
90 raise InferenceError(
91 f"RecursionError raised with limit {sys.getrecursionlimit()}."
92 ) from error
94 yield from generator
96 return inner
99# Expensive decorators only used to emit Deprecation warnings.
100# If no other than the default DeprecationWarning are enabled,
101# fall back to passthrough implementations.
102if util.check_warnings_filter(): # noqa: C901
104 def deprecate_default_argument_values(
105 astroid_version: str = "3.0", **arguments: str
106 ) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]:
107 """Decorator which emits a DeprecationWarning if any arguments specified
108 are None or not passed at all.
110 Arguments should be a key-value mapping, with the key being the argument to check
111 and the value being a type annotation as string for the value of the argument.
113 To improve performance, only used when DeprecationWarnings other than
114 the default one are enabled.
115 """
116 # Helpful links
117 # Decorator for DeprecationWarning: https://stackoverflow.com/a/49802489
118 # Typing of stacked decorators: https://stackoverflow.com/a/68290080
120 def deco(func: Callable[_P, _R]) -> Callable[_P, _R]:
121 """Decorator function."""
123 @functools.wraps(func)
124 def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
125 """Emit DeprecationWarnings if conditions are met."""
127 keys = list(inspect.signature(func).parameters.keys())
128 for arg, type_annotation in arguments.items():
129 try:
130 index = keys.index(arg)
131 except ValueError:
132 raise ValueError(
133 f"Can't find argument '{arg}' for '{args[0].__class__.__qualname__}'"
134 ) from None
135 # pylint: disable = too-many-boolean-expressions
136 if (
137 # Check kwargs
138 # - if found, check it's not None
139 (arg in kwargs and kwargs[arg] is None)
140 # Check args
141 # - make sure not in kwargs
142 # - len(args) needs to be long enough, if too short
143 # arg can't be in args either
144 # - args[index] should not be None
145 or (
146 arg not in kwargs
147 and (
148 index == -1
149 or len(args) <= index
150 or (len(args) > index and args[index] is None)
151 )
152 )
153 ):
154 warnings.warn(
155 f"'{arg}' will be a required argument for "
156 f"'{args[0].__class__.__qualname__}.{func.__name__}'"
157 f" in astroid {astroid_version} "
158 f"('{arg}' should be of type: '{type_annotation}')",
159 DeprecationWarning,
160 stacklevel=2,
161 )
162 return func(*args, **kwargs)
164 return wrapper
166 return deco
168 def deprecate_arguments(
169 astroid_version: str = "3.0", **arguments: str
170 ) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]:
171 """Decorator which emits a DeprecationWarning if any arguments specified
172 are passed.
174 Arguments should be a key-value mapping, with the key being the argument to check
175 and the value being a string that explains what to do instead of passing the argument.
177 To improve performance, only used when DeprecationWarnings other than
178 the default one are enabled.
179 """
181 def deco(func: Callable[_P, _R]) -> Callable[_P, _R]:
182 @functools.wraps(func)
183 def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
184 keys = list(inspect.signature(func).parameters.keys())
185 for arg, note in arguments.items():
186 try:
187 index = keys.index(arg)
188 except ValueError:
189 raise ValueError(
190 f"Can't find argument '{arg}' for '{args[0].__class__.__qualname__}'"
191 ) from None
192 if arg in kwargs or len(args) > index:
193 warnings.warn(
194 f"The argument '{arg}' for "
195 f"'{args[0].__class__.__qualname__}.{func.__name__}' is deprecated "
196 f"and will be removed in astroid {astroid_version} ({note})",
197 DeprecationWarning,
198 stacklevel=2,
199 )
200 return func(*args, **kwargs)
202 return wrapper
204 return deco
206else:
208 def deprecate_default_argument_values(
209 astroid_version: str = "3.0", **arguments: str
210 ) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]:
211 """Passthrough decorator to improve performance if DeprecationWarnings are
212 disabled.
213 """
215 def deco(func: Callable[_P, _R]) -> Callable[_P, _R]:
216 """Decorator function."""
217 return func
219 return deco
221 def deprecate_arguments(
222 astroid_version: str = "3.0", **arguments: str
223 ) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]:
224 """Passthrough decorator to improve performance if DeprecationWarnings are
225 disabled.
226 """
228 def deco(func: Callable[_P, _R]) -> Callable[_P, _R]:
229 """Decorator function."""
230 return func
232 return deco