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

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( 

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 

41 

42 yielded = set() 

43 

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) 

53 

54 return wrapped 

55 

56 

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) 

62 

63 try: 

64 yield next(generator) 

65 except StopIteration: 

66 # generator is empty 

67 yield util.Uninferable 

68 return 

69 

70 yield from generator 

71 

72 return inner 

73 

74 

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 

93 

94 yield from generator 

95 

96 return inner 

97 

98 

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 

103 

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. 

109 

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. 

112 

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 

119 

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

121 """Decorator function.""" 

122 

123 @functools.wraps(func) 

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

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

126 

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) 

163 

164 return wrapper 

165 

166 return deco 

167 

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. 

173 

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. 

176 

177 To improve performance, only used when DeprecationWarnings other than 

178 the default one are enabled. 

179 """ 

180 

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) 

201 

202 return wrapper 

203 

204 return deco 

205 

206else: 

207 

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 """ 

214 

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

216 """Decorator function.""" 

217 return func 

218 

219 return deco 

220 

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 """ 

227 

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

229 """Decorator function.""" 

230 return func 

231 

232 return deco