]> crepu.dev Git - config.git/blame_incremental - djavu-asus/emacs/elpy/rpc-venv/lib/python3.11/site-packages/jedi/plugins/pytest.py
Reorganización de directorios
[config.git] / djavu-asus / emacs / elpy / rpc-venv / lib / python3.11 / site-packages / jedi / plugins / pytest.py
... / ...
CommitLineData
1import sys
2from typing import List
3from pathlib import Path
4
5from parso.tree import search_ancestor
6from jedi.inference.cache import inference_state_method_cache
7from jedi.inference.imports import goto_import, load_module_from_path
8from jedi.inference.filters import ParserTreeFilter
9from jedi.inference.base_value import NO_VALUES, ValueSet
10from 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
21def 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
33def 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
80def 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
96def 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
109def _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
116def _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
131def _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
136def _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()
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 yield m.as_context()
185 except FileNotFoundError:
186 pass
187 folder = folder.get_parent_folder()
188
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:
191 break
192 last_folder = folder # keep track of the last found parent name
193
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()
197
198
199class 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)
205 if any(
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
209 if iname.tree_name
210 ):
211 yield name
212
213 elif self._is_fixture(self.parent_context, name):
214 yield name
215
216 def _is_fixture(self, context, name):
217 funcdef = name.parent
218 # Class fixtures are not supported
219 if funcdef.type != "funcdef":
220 return False
221 decorated = funcdef.parent
222 if decorated.type != "decorated":
223 return False
224 decorators = decorated.children[0]
225 if decorators.type == 'decorators':
226 decorators = decorators.children
227 else:
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
235 # anymore.
236 last_trailer = dotted_name.children[-1]
237 last_leaf = last_trailer.get_last_leaf()
238 if last_leaf == ')':
239 values = infer_call_of_leaf(
240 context, last_leaf, cut_own_trailer=True
241 )
242 else:
243 values = context.infer_node(dotted_name)
244 else:
245 values = context.infer_node(dotted_name)
246 for value in values:
247 if value.name.get_qualified_names(include_module_names=True) \
248 == ('_pytest', 'fixtures', 'fixture'):
249 return True
250 return False