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

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

155 statements  

1import sys 

2from typing import List 

3from pathlib import Path 

4 

5from jedi.inference.cache import inference_state_method_cache 

6from jedi.inference.imports import goto_import, load_module_from_path 

7from jedi.inference.filters import ParserTreeFilter 

8from jedi.inference.base_value import NO_VALUES, ValueSet 

9from jedi.inference.helpers import infer_call_of_leaf 

10 

11_PYTEST_FIXTURE_MODULES = [ 

12 ('_pytest', 'monkeypatch'), 

13 ('_pytest', 'capture'), 

14 ('_pytest', 'logging'), 

15 ('_pytest', 'tmpdir'), 

16 ('_pytest', 'pytester'), 

17] 

18 

19 

20def execute(callback): 

21 def wrapper(value, arguments): 

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

23 # for pytest 3. 

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

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

26 return NO_VALUES 

27 

28 return callback(value, arguments) 

29 return wrapper 

30 

31 

32def infer_anonymous_param(func): 

33 def get_returns(value): 

34 if value.tree_node.annotation is not None: 

35 result = value.execute_with_values() 

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

37 == ('typing', 'Generator') 

38 for v in result): 

39 return ValueSet.from_sets( 

40 v.py__getattribute__('__next__').execute_annotation() 

41 for v in result 

42 ) 

43 return result 

44 

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

46 # returns. 

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

48 function_context = value.as_context() 

49 if function_context.is_generator(): 

50 return function_context.merge_yield_values() 

51 else: 

52 return function_context.get_return_values() 

53 

54 def wrapper(param_name): 

55 # parameters with an annotation do not need special handling 

56 if param_name.annotation_node: 

57 return func(param_name) 

58 is_pytest_param, param_name_is_function_name = \ 

59 _is_a_pytest_param_and_inherited(param_name) 

60 if is_pytest_param: 

61 module = param_name.get_root_context() 

62 fixtures = _goto_pytest_fixture( 

63 module, 

64 param_name.string_name, 

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

66 # inheriting a fixture from somewhere else. 

67 skip_own_module=param_name_is_function_name, 

68 ) 

69 if fixtures: 

70 return ValueSet.from_sets( 

71 get_returns(value) 

72 for fixture in fixtures 

73 for value in fixture.infer() 

74 ) 

75 return func(param_name) 

76 return wrapper 

77 

78 

79def goto_anonymous_param(func): 

80 def wrapper(param_name): 

81 is_pytest_param, param_name_is_function_name = \ 

82 _is_a_pytest_param_and_inherited(param_name) 

83 if is_pytest_param: 

84 names = _goto_pytest_fixture( 

85 param_name.get_root_context(), 

86 param_name.string_name, 

87 skip_own_module=param_name_is_function_name, 

88 ) 

89 if names: 

90 return names 

91 return func(param_name) 

92 return wrapper 

93 

94 

95def complete_param_names(func): 

96 def wrapper(context, func_name, decorator_nodes): 

97 module_context = context.get_root_context() 

98 if _is_pytest_func(func_name, decorator_nodes): 

99 names = [] 

100 for module_context in _iter_pytest_modules(module_context): 

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

102 if names: 

103 return names 

104 return func(context, func_name, decorator_nodes) 

105 return wrapper 

106 

107 

108def _goto_pytest_fixture(module_context, name, skip_own_module): 

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

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

111 if names: 

112 return names 

113 

114 

115def _is_a_pytest_param_and_inherited(param_name): 

116 """ 

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

118 with the decorator @pytest.fixture. 

119 

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

121 """ 

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

123 if funcdef is None: # A lambda 

124 return False, False 

125 decorators = funcdef.get_decorators() 

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

127 funcdef.name.value == param_name.string_name 

128 

129 

130def _is_pytest_func(func_name, decorator_nodes): 

131 return func_name.startswith('test') \ 

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

133 

134 

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

136 """ 

137 Finds pytest plugin modules hooked by setuptools entry points 

138 

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

140 """ 

141 if sys.version_info >= (3, 8): 

142 from importlib.metadata import entry_points 

143 

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

145 pytest_entry_points = entry_points(group="pytest11") 

146 else: 

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

148 

149 if sys.version_info >= (3, 9): 

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

151 else: 

152 # Python 3.8 doesn't have `EntryPoint.module`. Implement equivalent 

153 # to what Python 3.9 does (with additional None check to placate `mypy`) 

154 matches = [ 

155 ep.pattern.match(ep.value) 

156 for ep in pytest_entry_points 

157 ] 

158 return [x.group('module').split(".") for x in matches if x] 

159 

160 else: 

161 from pkg_resources import iter_entry_points 

162 return [ep.module_name.split(".") for ep in iter_entry_points(group="pytest11")] 

163 

164 

165@inference_state_method_cache() 

166def _iter_pytest_modules(module_context, skip_own_module=False): 

167 if not skip_own_module: 

168 yield module_context 

169 

170 file_io = module_context.get_value().file_io 

171 if file_io is not None: 

172 folder = file_io.get_parent_folder() 

173 sys_path = module_context.inference_state.get_sys_path() 

174 

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

176 last_folder = None 

177 

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

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

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

181 try: 

182 m = load_module_from_path(module_context.inference_state, file_io) 

183 conftest_module = m.as_context() 

184 yield conftest_module 

185 

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

187 if plugins_list: 

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

189 yield from _load_pytest_plugins(module_context, name) 

190 except FileNotFoundError: 

191 pass 

192 folder = folder.get_parent_folder() 

193 

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

195 if last_folder is not None and folder.path == last_folder.path: 

196 break 

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

198 

199 for names in _PYTEST_FIXTURE_MODULES + _find_pytest_plugin_modules(): 

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

201 yield module_value.as_context() 

202 

203 

204def _load_pytest_plugins(module_context, name): 

205 from jedi.inference.helpers import get_str_or_none 

206 

207 for inferred in name.infer(): 

208 for seq_value in inferred.py__iter__(): 

209 for value in seq_value.infer(): 

210 fq_name = get_str_or_none(value) 

211 if fq_name: 

212 names = fq_name.split(".") 

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

214 yield module_value.as_context() 

215 

216 

217class FixtureFilter(ParserTreeFilter): 

218 def _filter(self, names): 

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

220 # look for fixture definitions of imported names 

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

222 imported_names = goto_import(self.parent_context, name) 

223 if any( 

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

225 for iname in imported_names 

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

227 if iname.tree_name 

228 ): 

229 yield name 

230 

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

232 yield name 

233 

234 def _is_fixture(self, context, name): 

235 funcdef = name.parent 

236 # Class fixtures are not supported 

237 if funcdef.type != "funcdef": 

238 return False 

239 decorated = funcdef.parent 

240 if decorated.type != "decorated": 

241 return False 

242 decorators = decorated.children[0] 

243 if decorators.type == 'decorators': 

244 decorators = decorators.children 

245 else: 

246 decorators = [decorators] 

247 for decorator in decorators: 

248 dotted_name = decorator.children[1] 

249 # A heuristic, this makes it faster. 

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

251 if dotted_name.type == 'atom_expr': 

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

253 # anymore. 

254 last_trailer = dotted_name.children[-1] 

255 last_leaf = last_trailer.get_last_leaf() 

256 if last_leaf == ')': 

257 values = infer_call_of_leaf( 

258 context, last_leaf, cut_own_trailer=True 

259 ) 

260 else: 

261 values = context.infer_node(dotted_name) 

262 else: 

263 values = context.infer_node(dotted_name) 

264 for value in values: 

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

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

267 return True 

268 return False