Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/astroid/decorators.py: 58%

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

93 statements  

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 

4 

5"""A few useful function/method decorators.""" 

6 

7from __future__ import annotations 

8 

9import functools 

10import inspect 

11import sys 

12import warnings 

13from collections.abc import Callable, Generator 

14from typing import ParamSpec, TypeVar 

15 

16from astroid import util 

17from astroid.context import InferenceContext 

18from astroid.exceptions import InferenceError 

19from astroid.typing import InferenceResult 

20 

21_R = TypeVar("_R") 

22_P = ParamSpec("_P") 

23 

24 

25def path_wrapper(func): 

26 """Return the given infer function wrapped to handle the path. 

27 

28 Used to stop inference if the node has already been looked 

29 at for a given `InferenceContext` to prevent infinite recursion 

30 """ 

31 

32 @functools.wraps(func) 

33 def wrapped(node, context: InferenceContext | None = None, _func=func) -> Generator: 

34 """Wrapper function handling context.""" 

35 if context is None: 

36 context = InferenceContext() 

37 if context.push(node): 

38 return 

39 

40 yielded = set() 

41 

42 for res in _func(node, context): 

43 # unproxy only true instance, not const, tuple, dict... 

44 if res.__class__.__name__ == "Instance": 

45 ares = res._proxied 

46 else: 

47 ares = res 

48 if ares not in yielded: 

49 yield res 

50 yielded.add(ares) 

51 

52 return wrapped 

53 

54 

55def yes_if_nothing_inferred( 

56 func: Callable[_P, Generator[InferenceResult]], 

57) -> Callable[_P, Generator[InferenceResult]]: 

58 def inner(*args: _P.args, **kwargs: _P.kwargs) -> Generator[InferenceResult]: 

59 generator = func(*args, **kwargs) 

60 

61 try: 

62 yield next(generator) 

63 except StopIteration: 

64 # generator is empty 

65 yield util.Uninferable 

66 return 

67 

68 yield from generator 

69 

70 return inner 

71 

72 

73def raise_if_nothing_inferred( 

74 func: Callable[_P, Generator[InferenceResult]], 

75) -> Callable[_P, Generator[InferenceResult]]: 

76 def inner(*args: _P.args, **kwargs: _P.kwargs) -> Generator[InferenceResult]: 

77 generator = func(*args, **kwargs) 

78 try: 

79 yield next(generator) 

80 except StopIteration as error: 

81 # generator is empty 

82 if error.args: 

83 raise InferenceError(**error.args[0]) from error 

84 raise InferenceError( 

85 "StopIteration raised without any error information." 

86 ) from error 

87 except RecursionError as error: 

88 raise InferenceError( 

89 f"RecursionError raised with limit {sys.getrecursionlimit()}." 

90 ) from error 

91 

92 yield from generator 

93 

94 return inner 

95 

96 

97# Expensive decorators only used to emit Deprecation warnings. 

98# If no other than the default DeprecationWarning are enabled, 

99# fall back to passthrough implementations. 

100if util.check_warnings_filter(): # noqa: C901 

101 

102 def deprecate_default_argument_values( 

103 astroid_version: str = "3.0", **arguments: str 

104 ) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: 

105 """Decorator which emits a DeprecationWarning if any arguments specified 

106 are None or not passed at all. 

107 

108 Arguments should be a key-value mapping, with the key being the argument to check 

109 and the value being a type annotation as string for the value of the argument. 

110 

111 To improve performance, only used when DeprecationWarnings other than 

112 the default one are enabled. 

113 """ 

114 # Helpful links 

115 # Decorator for DeprecationWarning: https://stackoverflow.com/a/49802489 

116 # Typing of stacked decorators: https://stackoverflow.com/a/68290080 

117 

118 def deco(func: Callable[_P, _R]) -> Callable[_P, _R]: 

119 """Decorator function.""" 

120 

121 @functools.wraps(func) 

122 def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R: 

123 """Emit DeprecationWarnings if conditions are met.""" 

124 

125 keys = list(inspect.signature(func).parameters.keys()) 

126 for arg, type_annotation in arguments.items(): 

127 try: 

128 index = keys.index(arg) 

129 except ValueError: 

130 raise ValueError( 

131 f"Can't find argument '{arg}' for '{args[0].__class__.__qualname__}'" 

132 ) from None 

133 # pylint: disable = too-many-boolean-expressions 

134 if ( 

135 # Check kwargs 

136 # - if found, check it's not None 

137 (arg in kwargs and kwargs[arg] is None) 

138 # Check args 

139 # - make sure not in kwargs 

140 # - len(args) needs to be long enough, if too short 

141 # arg can't be in args either 

142 # - args[index] should not be None 

143 or ( 

144 arg not in kwargs 

145 and ( 

146 index == -1 

147 or len(args) <= index 

148 or (len(args) > index and args[index] is None) 

149 ) 

150 ) 

151 ): 

152 warnings.warn( 

153 f"'{arg}' will be a required argument for " 

154 f"'{args[0].__class__.__qualname__}.{func.__name__}'" 

155 f" in astroid {astroid_version} " 

156 f"('{arg}' should be of type: '{type_annotation}')", 

157 DeprecationWarning, 

158 stacklevel=2, 

159 ) 

160 return func(*args, **kwargs) 

161 

162 return wrapper 

163 

164 return deco 

165 

166 def deprecate_arguments( 

167 astroid_version: str = "3.0", **arguments: str 

168 ) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: 

169 """Decorator which emits a DeprecationWarning if any arguments specified 

170 are passed. 

171 

172 Arguments should be a key-value mapping, with the key being the argument to check 

173 and the value being a string that explains what to do instead of passing the argument. 

174 

175 To improve performance, only used when DeprecationWarnings other than 

176 the default one are enabled. 

177 """ 

178 

179 def deco(func: Callable[_P, _R]) -> Callable[_P, _R]: 

180 @functools.wraps(func) 

181 def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R: 

182 keys = list(inspect.signature(func).parameters.keys()) 

183 for arg, note in arguments.items(): 

184 try: 

185 index = keys.index(arg) 

186 except ValueError: 

187 raise ValueError( 

188 f"Can't find argument '{arg}' for '{args[0].__class__.__qualname__}'" 

189 ) from None 

190 if arg in kwargs or len(args) > index: 

191 warnings.warn( 

192 f"The argument '{arg}' for " 

193 f"'{args[0].__class__.__qualname__}.{func.__name__}' is deprecated " 

194 f"and will be removed in astroid {astroid_version} ({note})", 

195 DeprecationWarning, 

196 stacklevel=2, 

197 ) 

198 return func(*args, **kwargs) 

199 

200 return wrapper 

201 

202 return deco 

203 

204else: 

205 

206 def deprecate_default_argument_values( 

207 astroid_version: str = "3.0", **arguments: str 

208 ) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: 

209 """Passthrough decorator to improve performance if DeprecationWarnings are 

210 disabled. 

211 """ 

212 

213 def deco(func: Callable[_P, _R]) -> Callable[_P, _R]: 

214 """Decorator function.""" 

215 return func 

216 

217 return deco 

218 

219 def deprecate_arguments( 

220 astroid_version: str = "3.0", **arguments: str 

221 ) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: 

222 """Passthrough decorator to improve performance if DeprecationWarnings are 

223 disabled. 

224 """ 

225 

226 def deco(func: Callable[_P, _R]) -> Callable[_P, _R]: 

227 """Decorator function.""" 

228 return func 

229 

230 return deco