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

95 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:53 +0000

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 TypeVar 

15 

16from astroid import util 

17from astroid.context import InferenceContext 

18from astroid.exceptions import InferenceError 

19from astroid.typing import InferenceResult 

20 

21if sys.version_info >= (3, 10): 

22 from typing import ParamSpec 

23else: 

24 from typing_extensions import ParamSpec 

25 

26_R = TypeVar("_R") 

27_P = ParamSpec("_P") 

28 

29 

30def path_wrapper(func): 

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

32 

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

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

35 """ 

36 

37 @functools.wraps(func) 

38 def wrapped( 

39 node, context: InferenceContext | None = None, _func=func, **kwargs 

40 ) -> Generator: 

41 """Wrapper function handling context.""" 

42 if context is None: 

43 context = InferenceContext() 

44 if context.push(node): 

45 return 

46 

47 yielded = set() 

48 

49 for res in _func(node, context, **kwargs): 

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

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

52 ares = res._proxied 

53 else: 

54 ares = res 

55 if ares not in yielded: 

56 yield res 

57 yielded.add(ares) 

58 

59 return wrapped 

60 

61 

62def yes_if_nothing_inferred( 

63 func: Callable[_P, Generator[InferenceResult, None, None]] 

64) -> Callable[_P, Generator[InferenceResult, None, None]]: 

65 def inner( 

66 *args: _P.args, **kwargs: _P.kwargs 

67 ) -> Generator[InferenceResult, None, None]: 

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

69 

70 try: 

71 yield next(generator) 

72 except StopIteration: 

73 # generator is empty 

74 yield util.Uninferable 

75 return 

76 

77 yield from generator 

78 

79 return inner 

80 

81 

82def raise_if_nothing_inferred( 

83 func: Callable[_P, Generator[InferenceResult, None, None]], 

84) -> Callable[_P, Generator[InferenceResult, None, None]]: 

85 def inner( 

86 *args: _P.args, **kwargs: _P.kwargs 

87 ) -> Generator[InferenceResult, None, None]: 

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

89 try: 

90 yield next(generator) 

91 except StopIteration as error: 

92 # generator is empty 

93 if error.args: 

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

95 raise InferenceError( 

96 "StopIteration raised without any error information." 

97 ) from error 

98 except RecursionError as error: 

99 raise InferenceError( 

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

101 ) from error 

102 

103 yield from generator 

104 

105 return inner 

106 

107 

108# Expensive decorators only used to emit Deprecation warnings. 

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

110# fall back to passthrough implementations. 

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

112 

113 def deprecate_default_argument_values( 

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

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

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

117 are None or not passed at all. 

118 

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

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

121 

122 To improve performance, only used when DeprecationWarnings other than 

123 the default one are enabled. 

124 """ 

125 # Helpful links 

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

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

128 

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

130 """Decorator function.""" 

131 

132 @functools.wraps(func) 

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

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

135 

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

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

138 try: 

139 index = keys.index(arg) 

140 except ValueError: 

141 raise ValueError( 

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

143 ) from None 

144 if ( 

145 # Check kwargs 

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

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

148 # Check args 

149 # - make sure not in kwargs 

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

151 # arg can't be in args either 

152 # - args[index] should not be None 

153 or arg not in kwargs 

154 and ( 

155 index == -1 

156 or len(args) <= index 

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

158 ) 

159 ): 

160 warnings.warn( 

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

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

163 f" in astroid {astroid_version} " 

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

165 DeprecationWarning, 

166 stacklevel=2, 

167 ) 

168 return func(*args, **kwargs) 

169 

170 return wrapper 

171 

172 return deco 

173 

174 def deprecate_arguments( 

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

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

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

178 are passed. 

179 

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

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

182 

183 To improve performance, only used when DeprecationWarnings other than 

184 the default one are enabled. 

185 """ 

186 

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

188 @functools.wraps(func) 

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

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

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

192 try: 

193 index = keys.index(arg) 

194 except ValueError: 

195 raise ValueError( 

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

197 ) from None 

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

199 warnings.warn( 

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

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

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

203 DeprecationWarning, 

204 stacklevel=2, 

205 ) 

206 return func(*args, **kwargs) 

207 

208 return wrapper 

209 

210 return deco 

211 

212else: 

213 

214 def deprecate_default_argument_values( 

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

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

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

218 disabled. 

219 """ 

220 

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

222 """Decorator function.""" 

223 return func 

224 

225 return deco 

226 

227 def deprecate_arguments( 

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

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

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

231 disabled. 

232 """ 

233 

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

235 """Decorator function.""" 

236 return func 

237 

238 return deco