Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jedi-0.18.2-py3.8.egg/jedi/plugins/pytest.py: 23%
130 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 07:16 +0000
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 07:16 +0000
1from pathlib import Path
3from parso.tree import search_ancestor
4from jedi.inference.cache import inference_state_method_cache
5from jedi.inference.imports import goto_import, load_module_from_path
6from jedi.inference.filters import ParserTreeFilter
7from jedi.inference.base_value import NO_VALUES, ValueSet
8from jedi.inference.helpers import infer_call_of_leaf
10_PYTEST_FIXTURE_MODULES = [
11 ('_pytest', 'monkeypatch'),
12 ('_pytest', 'capture'),
13 ('_pytest', 'logging'),
14 ('_pytest', 'tmpdir'),
15 ('_pytest', 'pytester'),
16]
19def execute(callback):
20 def wrapper(value, arguments):
21 # This might not be necessary anymore in pytest 4/5, definitely needed
22 # for pytest 3.
23 if value.py__name__() == 'fixture' \
24 and value.parent_context.py__name__() == '_pytest.fixtures':
25 return NO_VALUES
27 return callback(value, arguments)
28 return wrapper
31def infer_anonymous_param(func):
32 def get_returns(value):
33 if value.tree_node.annotation is not None:
34 result = value.execute_with_values()
35 if any(v.name.get_qualified_names(include_module_names=True)
36 == ('typing', 'Generator')
37 for v in result):
38 return ValueSet.from_sets(
39 v.py__getattribute__('__next__').execute_annotation()
40 for v in result
41 )
42 return result
44 # In pytest we need to differentiate between generators and normal
45 # returns.
46 # Parameters still need to be anonymous, .as_context() ensures that.
47 function_context = value.as_context()
48 if function_context.is_generator():
49 return function_context.merge_yield_values()
50 else:
51 return function_context.get_return_values()
53 def wrapper(param_name):
54 # parameters with an annotation do not need special handling
55 if param_name.annotation_node:
56 return func(param_name)
57 is_pytest_param, param_name_is_function_name = \
58 _is_a_pytest_param_and_inherited(param_name)
59 if is_pytest_param:
60 module = param_name.get_root_context()
61 fixtures = _goto_pytest_fixture(
62 module,
63 param_name.string_name,
64 # This skips the current module, because we are basically
65 # inheriting a fixture from somewhere else.
66 skip_own_module=param_name_is_function_name,
67 )
68 if fixtures:
69 return ValueSet.from_sets(
70 get_returns(value)
71 for fixture in fixtures
72 for value in fixture.infer()
73 )
74 return func(param_name)
75 return wrapper
78def goto_anonymous_param(func):
79 def wrapper(param_name):
80 is_pytest_param, param_name_is_function_name = \
81 _is_a_pytest_param_and_inherited(param_name)
82 if is_pytest_param:
83 names = _goto_pytest_fixture(
84 param_name.get_root_context(),
85 param_name.string_name,
86 skip_own_module=param_name_is_function_name,
87 )
88 if names:
89 return names
90 return func(param_name)
91 return wrapper
94def complete_param_names(func):
95 def wrapper(context, func_name, decorator_nodes):
96 module_context = context.get_root_context()
97 if _is_pytest_func(func_name, decorator_nodes):
98 names = []
99 for module_context in _iter_pytest_modules(module_context):
100 names += FixtureFilter(module_context).values()
101 if names:
102 return names
103 return func(context, func_name, decorator_nodes)
104 return wrapper
107def _goto_pytest_fixture(module_context, name, skip_own_module):
108 for module_context in _iter_pytest_modules(module_context, skip_own_module=skip_own_module):
109 names = FixtureFilter(module_context).get(name)
110 if names:
111 return names
114def _is_a_pytest_param_and_inherited(param_name):
115 """
116 Pytest params are either in a `test_*` function or have a pytest fixture
117 with the decorator @pytest.fixture.
119 This is a heuristic and will work in most cases.
120 """
121 funcdef = search_ancestor(param_name.tree_name, 'funcdef')
122 if funcdef is None: # A lambda
123 return False, False
124 decorators = funcdef.get_decorators()
125 return _is_pytest_func(funcdef.name.value, decorators), \
126 funcdef.name.value == param_name.string_name
129def _is_pytest_func(func_name, decorator_nodes):
130 return func_name.startswith('test') \
131 or any('fixture' in n.get_code() for n in decorator_nodes)
134def _find_pytest_plugin_modules():
135 """
136 Finds pytest plugin modules hooked by setuptools entry points
138 See https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points
139 """
140 from pkg_resources import iter_entry_points
142 return [ep.module_name.split(".") for ep in iter_entry_points(group="pytest11")]
145@inference_state_method_cache()
146def _iter_pytest_modules(module_context, skip_own_module=False):
147 if not skip_own_module:
148 yield module_context
150 file_io = module_context.get_value().file_io
151 if file_io is not None:
152 folder = file_io.get_parent_folder()
153 sys_path = module_context.inference_state.get_sys_path()
155 # prevent an infinite loop when reaching the root of the current drive
156 last_folder = None
158 while any(folder.path.startswith(p) for p in sys_path):
159 file_io = folder.get_file_io('conftest.py')
160 if Path(file_io.path) != module_context.py__file__():
161 try:
162 m = load_module_from_path(module_context.inference_state, file_io)
163 yield m.as_context()
164 except FileNotFoundError:
165 pass
166 folder = folder.get_parent_folder()
168 # prevent an infinite for loop if the same parent folder is return twice
169 if last_folder is not None and folder.path == last_folder.path:
170 break
171 last_folder = folder # keep track of the last found parent name
173 for names in _PYTEST_FIXTURE_MODULES + _find_pytest_plugin_modules():
174 for module_value in module_context.inference_state.import_module(names):
175 yield module_value.as_context()
178class FixtureFilter(ParserTreeFilter):
179 def _filter(self, names):
180 for name in super()._filter(names):
181 # look for fixture definitions of imported names
182 if name.parent.type == "import_from":
183 imported_names = goto_import(self.parent_context, name)
184 if any(
185 self._is_fixture(iname.parent_context, iname.tree_name)
186 for iname in imported_names
187 # discard imports of whole modules, that have no tree_name
188 if iname.tree_name
189 ):
190 yield name
192 elif self._is_fixture(self.parent_context, name):
193 yield name
195 def _is_fixture(self, context, name):
196 funcdef = name.parent
197 # Class fixtures are not supported
198 if funcdef.type != "funcdef":
199 return False
200 decorated = funcdef.parent
201 if decorated.type != "decorated":
202 return False
203 decorators = decorated.children[0]
204 if decorators.type == 'decorators':
205 decorators = decorators.children
206 else:
207 decorators = [decorators]
208 for decorator in decorators:
209 dotted_name = decorator.children[1]
210 # A heuristic, this makes it faster.
211 if 'fixture' in dotted_name.get_code():
212 if dotted_name.type == 'atom_expr':
213 # Since Python3.9 a decorator does not have dotted names
214 # anymore.
215 last_trailer = dotted_name.children[-1]
216 last_leaf = last_trailer.get_last_leaf()
217 if last_leaf == ')':
218 values = infer_call_of_leaf(
219 context, last_leaf, cut_own_trailer=True
220 )
221 else:
222 values = context.infer_node(dotted_name)
223 else:
224 values = context.infer_node(dotted_name)
225 for value in values:
226 if value.name.get_qualified_names(include_module_names=True) \
227 == ('_pytest', 'fixtures', 'fixture'):
228 return True
229 return False