Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jedi-0.20.1-py3.11.egg/jedi/plugins/pytest.py: 22%

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

158 statements  

1import sys 

2from typing import List 

3from pathlib import Path 

4 

5from jedi.inference.arguments import ValuesArguments 

6from jedi.inference.cache import inference_state_method_cache 

7from jedi.inference.gradual.annotation import infer_return_types 

8from jedi.inference.imports import goto_import, load_module_from_path 

9from jedi.inference.filters import ParserTreeFilter 

10from jedi.inference.base_value import NO_VALUES, ValueSet 

11from jedi.inference.helpers import infer_call_of_leaf 

12 

13_PYTEST_FIXTURE_MODULES = [ 

14 ('_pytest', 'monkeypatch'), 

15 ('_pytest', 'capture'), 

16 ('_pytest', 'logging'), 

17 ('_pytest', 'tmpdir'), 

18 ('_pytest', 'pytester'), 

19] 

20 

21 

22def execute(callback): 

23 def wrapper(value, arguments): 

24 # This might not be necessary anymore in pytest 4/5, definitely needed 

25 # for pytest 3. 

26 if value.py__name__() == 'fixture' \ 

27 and value.parent_context.py__name__() == '_pytest.fixtures': 

28 return NO_VALUES 

29 

30 return callback(value, arguments) 

31 return wrapper 

32 

33 

34def infer_anonymous_param(func): 

35 def get_returns(value): 

36 if value.tree_node.annotation is not None: 

37 if value.tree_node.parent.type == 'async_funcdef': 

38 # Async fixtures provide the awaited value, rather than a 

39 # coroutine. The annotation of an async generator describes 

40 # the iterator, so the value is what that iterator yields. 

41 result = infer_return_types(value, ValuesArguments([])) 

42 if value.as_context().is_generator(): 

43 return ValueSet.from_sets( 

44 lazy_value.infer() 

45 for v in result 

46 for lazy_value in v.iterate(is_async=True) 

47 ) 

48 return result 

49 

50 result = value.execute_with_values() 

51 if any(v.name.get_qualified_names(include_module_names=True) 

52 == ('typing', 'Generator') 

53 for v in result): 

54 return ValueSet.from_sets( 

55 v.py__getattribute__('__next__').execute_annotation(None) 

56 for v in result 

57 ) 

58 return result 

59 

60 # In pytest we need to differentiate between generators and normal 

61 # returns. 

62 # Parameters still need to be anonymous, .as_context() ensures that. 

63 function_context = value.as_context() 

64 if function_context.is_generator(): 

65 return function_context.merge_yield_values() 

66 else: 

67 return function_context.get_return_values() 

68 

69 def wrapper(param_name): 

70 # parameters with an annotation do not need special handling 

71 if param_name.annotation_node: 

72 return func(param_name) 

73 is_pytest_param, param_name_is_function_name = \ 

74 _is_a_pytest_param_and_inherited(param_name) 

75 if is_pytest_param: 

76 module = param_name.get_root_context() 

77 fixtures = _goto_pytest_fixture( 

78 module, 

79 param_name.string_name, 

80 # This skips the current module, because we are basically 

81 # inheriting a fixture from somewhere else. 

82 skip_own_module=param_name_is_function_name, 

83 ) 

84 if fixtures: 

85 return ValueSet.from_sets( 

86 get_returns(value) 

87 for fixture in fixtures 

88 for value in fixture.infer() 

89 ) 

90 return func(param_name) 

91 return wrapper 

92 

93 

94def goto_anonymous_param(func): 

95 def wrapper(param_name): 

96 is_pytest_param, param_name_is_function_name = \ 

97 _is_a_pytest_param_and_inherited(param_name) 

98 if is_pytest_param: 

99 names = _goto_pytest_fixture( 

100 param_name.get_root_context(), 

101 param_name.string_name, 

102 skip_own_module=param_name_is_function_name, 

103 ) 

104 if names: 

105 return names 

106 return func(param_name) 

107 return wrapper 

108 

109 

110def complete_param_names(func): 

111 def wrapper(context, func_name, decorator_nodes): 

112 module_context = context.get_root_context() 

113 if _is_pytest_func(func_name, decorator_nodes): 

114 names = [] 

115 for module_context in _iter_pytest_modules(module_context): 

116 names += FixtureFilter(module_context).values() 

117 if names: 

118 return names 

119 return func(context, func_name, decorator_nodes) 

120 return wrapper 

121 

122 

123def _goto_pytest_fixture(module_context, name, skip_own_module): 

124 for module_context in _iter_pytest_modules(module_context, skip_own_module=skip_own_module): 

125 names = FixtureFilter(module_context).get(name) 

126 if names: 

127 return names 

128 

129 

130def _is_a_pytest_param_and_inherited(param_name): 

131 """ 

132 Pytest params are either in a `test_*` function or have a pytest fixture 

133 with the decorator @pytest.fixture. 

134 

135 This is a heuristic and will work in most cases. 

136 """ 

137 funcdef = param_name.tree_name.search_ancestor('funcdef') 

138 if funcdef is None: # A lambda 

139 return False, False 

140 decorators = funcdef.get_decorators() 

141 return _is_pytest_func(funcdef.name.value, decorators), \ 

142 funcdef.name.value == param_name.string_name 

143 

144 

145def _is_pytest_func(func_name, decorator_nodes): 

146 return func_name.startswith('test') \ 

147 or any('fixture' in n.get_code() for n in decorator_nodes) 

148 

149 

150def _find_pytest_plugin_modules() -> List[List[str]]: 

151 """ 

152 Finds pytest plugin modules hooked by setuptools entry points 

153 

154 See https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points 

155 """ 

156 from importlib.metadata import entry_points 

157 

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

159 pytest_entry_points = entry_points(group="pytest11") 

160 else: 

161 pytest_entry_points = entry_points().get("pytest11", ()) 

162 

163 return [ep.module.split(".") for ep in pytest_entry_points] 

164 

165 

166@inference_state_method_cache() 

167def _iter_pytest_modules(module_context, skip_own_module=False): 

168 if not skip_own_module: 

169 yield module_context 

170 

171 file_io = module_context.get_value().file_io 

172 if file_io is not None: 

173 folder = file_io.get_parent_folder() 

174 sys_path = module_context.inference_state.get_sys_path() 

175 

176 # prevent an infinite loop when reaching the root of the current drive 

177 last_folder = None 

178 

179 while any(folder.path.startswith(p) for p in sys_path): 

180 file_io = folder.get_file_io('conftest.py') 

181 if Path(file_io.path) != module_context.py__file__(): 

182 try: 

183 m = load_module_from_path(module_context.inference_state, file_io) 

184 conftest_module = m.as_context() 

185 yield conftest_module 

186 

187 plugins_list = m.tree_node.get_used_names().get("pytest_plugins") 

188 if plugins_list: 

189 name = conftest_module.create_name(plugins_list[0]) 

190 yield from _load_pytest_plugins(module_context, name) 

191 except FileNotFoundError: 

192 pass 

193 folder = folder.get_parent_folder() 

194 

195 # prevent an infinite for loop if the same parent folder is return twice 

196 if last_folder is not None and folder.path == last_folder.path: # type: ignore # TODO 

197 break 

198 last_folder = folder # keep track of the last found parent name 

199 

200 for names in _PYTEST_FIXTURE_MODULES + _find_pytest_plugin_modules(): 

201 for module_value in module_context.inference_state.import_module(names): 

202 yield module_value.as_context() 

203 

204 

205def _load_pytest_plugins(module_context, name): 

206 from jedi.inference.helpers import get_str_or_none 

207 

208 for inferred in name.infer(): 

209 for seq_value in inferred.py__iter__(): 

210 for value in seq_value.infer(): 

211 fq_name = get_str_or_none(value) 

212 if fq_name: 

213 names = fq_name.split(".") 

214 for module_value in module_context.inference_state.import_module(names): 

215 yield module_value.as_context() 

216 

217 

218class FixtureFilter(ParserTreeFilter): 

219 def _filter(self, names): 

220 for name in super()._filter(names): 

221 # look for fixture definitions of imported names 

222 if name.parent.type == "import_from": 

223 imported_names = goto_import(self.parent_context, name) 

224 if any( 

225 self._is_fixture(iname.parent_context, iname.tree_name) 

226 for iname in imported_names 

227 # discard imports of whole modules, that have no tree_name 

228 if iname.tree_name 

229 ): 

230 yield name 

231 

232 elif self._is_fixture(self.parent_context, name): 

233 yield name 

234 

235 def _is_fixture(self, context, name): 

236 funcdef = name.parent 

237 # Class fixtures are not supported 

238 if funcdef.type != "funcdef": 

239 return False 

240 decorated = funcdef.parent 

241 if decorated.type == "async_funcdef": 

242 decorated = decorated.parent 

243 if decorated.type != "decorated": 

244 return False 

245 decorators = decorated.children[0] 

246 if decorators.type == 'decorators': 

247 decorators = decorators.children 

248 else: 

249 decorators = [decorators] 

250 for decorator in decorators: 

251 dotted_name = decorator.children[1] 

252 # A heuristic, this makes it faster. 

253 if 'fixture' in dotted_name.get_code(): 

254 if dotted_name.type == 'atom_expr': 

255 # Since Python3.9 a decorator does not have dotted names 

256 # anymore. 

257 last_trailer = dotted_name.children[-1] 

258 last_leaf = last_trailer.get_last_leaf() 

259 if last_leaf == ')': 

260 values = infer_call_of_leaf( 

261 context, last_leaf, cut_own_trailer=True 

262 ) 

263 else: 

264 values = context.infer_node(dotted_name) 

265 else: 

266 values = context.infer_node(dotted_name) 

267 for value in values: 

268 if value.name.get_qualified_names(include_module_names=True) \ 

269 == ('_pytest', 'fixtures', 'fixture'): 

270 return True 

271 return False