2 from typing
import List
3 from pathlib
import Path
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
12 _PYTEST_FIXTURE_MODULES
= [
13 ('_pytest', 'monkeypatch'),
14 ('_pytest', 'capture'),
15 ('_pytest', 'logging'),
16 ('_pytest', 'tmpdir'),
17 ('_pytest', 'pytester'),
21 def execute(callback
):
22 def wrapper(value
, arguments
):
23 # This might not be necessary anymore in pytest 4/5, definitely needed
25 if value
.py__name__() == 'fixture' \
26 and value
.parent_context
.py__name__() == '_pytest.fixtures':
29 return callback(value
, arguments
)
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')
40 return ValueSet
.from_sets(
41 v
.py__getattribute__('__next__').execute_annotation()
46 # In pytest we need to differentiate between generators and normal
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()
53 return function_context
.get_return_values()
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
)
62 module
= param_name
.get_root_context()
63 fixtures
= _goto_pytest_fixture(
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
,
71 return ValueSet
.from_sets(
73 for fixture
in fixtures
74 for value
in fixture
.infer()
76 return func(param_name
)
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
)
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
,
92 return func(param_name
)
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
):
101 for module_context
in _iter_pytest_modules(module_context
):
102 names
+= FixtureFilter(module_context
).values()
105 return func(context
, func_name
, decorator_nodes
)
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
)
116 def _is_a_pytest_param_and_inherited(param_name
):
118 Pytest params are either in a `test_*` function or have a pytest fixture
119 with the decorator @pytest.fixture.
121 This is a heuristic and will work in most cases.
123 funcdef
= search_ancestor(param_name
.tree_name
, 'funcdef')
124 if funcdef
is None: # A lambda
126 decorators
= funcdef
.get_decorators()
127 return _is_pytest_func(funcdef
.name
.value
, decorators
), \
128 funcdef
.name
.value
== param_name
.string_name
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
)
136 def _find_pytest_plugin_modules() -> List
[List
[str]]:
138 Finds pytest plugin modules hooked by setuptools entry points
140 See https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points
142 if sys
.version_info
>= (3, 8):
143 from importlib
.metadata
import entry_points
145 if sys
.version_info
>= (3, 10):
146 pytest_entry_points
= entry_points(group
="pytest11")
148 pytest_entry_points
= entry_points().get("pytest11", ())
150 if sys
.version_info
>= (3, 9):
151 return [ep
.module
.split(".") for ep
in pytest_entry_points
]
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`)
156 ep
.pattern
.match(ep
.value
)
157 for ep
in pytest_entry_points
159 return [x
.group('module').split(".") for x
in matches
if x
]
162 from pkg_resources
import iter_entry_points
163 return [ep
.module_name
.split(".") for ep
in iter_entry_points(group
="pytest11")]
166 @inference_state_method_cache()
167 def _iter_pytest_modules(module_context
, skip_own_module
=False):
168 if not skip_own_module
:
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()
176 # prevent an infinite loop when reaching the root of the current drive
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__():
183 m
= load_module_from_path(module_context
.inference_state
, file_io
)
185 except FileNotFoundError
:
187 folder
= folder
.get_parent_folder()
189 # prevent an infinite for loop if the same parent folder is return twice
190 if last_folder
is not None and folder
.path
== last_folder
.path
:
192 last_folder
= folder
# keep track of the last found parent name
194 for names
in _PYTEST_FIXTURE_MODULES
+ _find_pytest_plugin_modules():
195 for module_value
in module_context
.inference_state
.import_module(names
):
196 yield module_value
.as_context()
199 class FixtureFilter(ParserTreeFilter
):
200 def _filter(self
, names
):
201 for name
in super()._filter
(names
):
202 # look for fixture definitions of imported names
203 if name
.parent
.type == "import_from":
204 imported_names
= goto_import(self
.parent_context
, name
)
206 self
._is
_fixture
(iname
.parent_context
, iname
.tree_name
)
207 for iname
in imported_names
208 # discard imports of whole modules, that have no tree_name
213 elif self
._is
_fixture
(self
.parent_context
, name
):
216 def _is_fixture(self
, context
, name
):
217 funcdef
= name
.parent
218 # Class fixtures are not supported
219 if funcdef
.type != "funcdef":
221 decorated
= funcdef
.parent
222 if decorated
.type != "decorated":
224 decorators
= decorated
.children
[0]
225 if decorators
.type == 'decorators':
226 decorators
= decorators
.children
228 decorators
= [decorators
]
229 for decorator
in decorators
:
230 dotted_name
= decorator
.children
[1]
231 # A heuristic, this makes it faster.
232 if 'fixture' in dotted_name
.get_code():
233 if dotted_name
.type == 'atom_expr':
234 # Since Python3.9 a decorator does not have dotted names
236 last_trailer
= dotted_name
.children
[-1]
237 last_leaf
= last_trailer
.get_last_leaf()
239 values
= infer_call_of_leaf(
240 context
, last_leaf
, cut_own_trailer
=True
243 values
= context
.infer_node(dotted_name
)
245 values
= context
.infer_node(dotted_name
)
247 if value
.name
.get_qualified_names(include_module_names
=True) \
248 == ('_pytest', 'fixtures', 'fixture'):