1 import sys 2 from typing import List 3 from pathlib import Path 4 5 from parso.tree import search_ancestor 6 from jedi.inference.cache import inference_state_method_cache 7 from jedi.inference.imports import goto_import, load_module_from_path 8 from jedi.inference.filters import ParserTreeFilter 9 from jedi.inference.base_value import NO_VALUES, ValueSet 10 from jedi.inference.helpers import infer_call_of_leaf 11 12 _PYTEST_FIXTURE_MODULES = [ 13 ('_pytest', 'monkeypatch'), 14 ('_pytest', 'capture'), 15 ('_pytest', 'logging'), 16 ('_pytest', 'tmpdir'), 17 ('_pytest', 'pytester'), 18 ] 19 20 21 def execute(callback): 22 def wrapper(value, arguments): 23 # This might not be necessary anymore in pytest 4/5, definitely needed 24 # for pytest 3. 25 if value.py__name__() == 'fixture' \ 26 and value.parent_context.py__name__() == '_pytest.fixtures': 27 return NO_VALUES 28 29 return callback(value, arguments) 30 return wrapper 31 32 33 def infer_anonymous_param(func): 34 def get_returns(value): 35 if value.tree_node.annotation is not None: 36 result = value.execute_with_values() 37 if any(v.name.get_qualified_names(include_module_names=True) 38 == ('typing', 'Generator') 39 for v in result): 40 return ValueSet.from_sets( 41 v.py__getattribute__('__next__').execute_annotation() 42 for v in result 43 ) 44 return result 45 46 # In pytest we need to differentiate between generators and normal 47 # returns. 48 # Parameters still need to be anonymous, .as_context() ensures that. 49 function_context = value.as_context() 50 if function_context.is_generator(): 51 return function_context.merge_yield_values() 52 else: 53 return function_context.get_return_values() 54 55 def wrapper(param_name): 56 # parameters with an annotation do not need special handling 57 if param_name.annotation_node: 58 return func(param_name) 59 is_pytest_param, param_name_is_function_name = \ 60 _is_a_pytest_param_and_inherited(param_name) 61 if is_pytest_param: 62 module = param_name.get_root_context() 63 fixtures = _goto_pytest_fixture( 64 module, 65 param_name.string_name, 66 # This skips the current module, because we are basically 67 # inheriting a fixture from somewhere else. 68 skip_own_module=param_name_is_function_name, 69 ) 70 if fixtures: 71 return ValueSet.from_sets( 72 get_returns(value) 73 for fixture in fixtures 74 for value in fixture.infer() 75 ) 76 return func(param_name) 77 return wrapper 78 79 80 def goto_anonymous_param(func): 81 def wrapper(param_name): 82 is_pytest_param, param_name_is_function_name = \ 83 _is_a_pytest_param_and_inherited(param_name) 84 if is_pytest_param: 85 names = _goto_pytest_fixture( 86 param_name.get_root_context(), 87 param_name.string_name, 88 skip_own_module=param_name_is_function_name, 89 ) 90 if names: 91 return names 92 return func(param_name) 93 return wrapper 94 95 96 def complete_param_names(func): 97 def wrapper(context, func_name, decorator_nodes): 98 module_context = context.get_root_context() 99 if _is_pytest_func(func_name, decorator_nodes): 100 names = [] 101 for module_context in _iter_pytest_modules(module_context): 102 names += FixtureFilter(module_context).values() 103 if names: 104 return names 105 return func(context, func_name, decorator_nodes) 106 return wrapper 107 108 109 def _goto_pytest_fixture(module_context, name, skip_own_module): 110 for module_context in _iter_pytest_modules(module_context, skip_own_module=skip_own_module): 111 names = FixtureFilter(module_context).get(name) 112 if names: 113 return names 114 115 116 def _is_a_pytest_param_and_inherited(param_name): 117 """ 118 Pytest params are either in a `test_*` function or have a pytest fixture 119 with the decorator @pytest.fixture. 120 121 This is a heuristic and will work in most cases. 122 """ 123 funcdef = search_ancestor(param_name.tree_name, 'funcdef') 124 if funcdef is None: # A lambda 125 return False, False 126 decorators = funcdef.get_decorators() 127 return _is_pytest_func(funcdef.name.value, decorators), \ 128 funcdef.name.value == param_name.string_name 129 130 131 def _is_pytest_func(func_name, decorator_nodes): 132 return func_name.startswith('test') \ 133 or any('fixture' in n.get_code() for n in decorator_nodes) 134 135 136 def _find_pytest_plugin_modules() -> List[List[str]]: 137 """ 138 Finds pytest plugin modules hooked by setuptools entry points 139 140 See https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points 141 """ 142 if sys.version_info >= (3, 8): 143 from importlib.metadata import entry_points 144 145 if sys.version_info >= (3, 10): 146 pytest_entry_points = entry_points(group="pytest11") 147 else: 148 pytest_entry_points = entry_points().get("pytest11", ()) 149 150 if sys.version_info >= (3, 9): 151 return [ep.module.split(".") for ep in pytest_entry_points] 152 else: 153 # Python 3.8 doesn't have `EntryPoint.module`. Implement equivalent 154 # to what Python 3.9 does (with additional None check to placate `mypy`) 155 matches = [ 156 ep.pattern.match(ep.value) 157 for ep in pytest_entry_points 158 ] 159 return [x.group('module').split(".") for x in matches if x] 160 161 else: 162 from pkg_resources import iter_entry_points 163 return [ep.module_name.split(".") for ep in iter_entry_points(group="pytest11")] 164 165 166 @inference_state_method_cache() 167 def _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: 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 205 def _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 218 class 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 != "decorated": 242 return False 243 decorators = decorated.children[0] 244 if decorators.type == 'decorators': 245 decorators = decorators.children 246 else: 247 decorators = [decorators] 248 for decorator in decorators: 249 dotted_name = decorator.children[1] 250 # A heuristic, this makes it faster. 251 if 'fixture' in dotted_name.get_code(): 252 if dotted_name.type == 'atom_expr': 253 # Since Python3.9 a decorator does not have dotted names 254 # anymore. 255 last_trailer = dotted_name.children[-1] 256 last_leaf = last_trailer.get_last_leaf() 257 if last_leaf == ')': 258 values = infer_call_of_leaf( 259 context, last_leaf, cut_own_trailer=True 260 ) 261 else: 262 values = context.infer_node(dotted_name) 263 else: 264 values = context.infer_node(dotted_name) 265 for value in values: 266 if value.name.get_qualified_names(include_module_names=True) \ 267 == ('_pytest', 'fixtures', 'fixture'): 268 return True 269 return False 270