mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 14:04:26 +08:00
Get some pytest fixtures working with some side effects
This commit is contained in:
@@ -13,6 +13,12 @@ class AbstractFolderIO(object):
|
|||||||
def get_file_io(self, name):
|
def get_file_io(self, name):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_parent_folder(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s: %s>' % (self.__class__.__name__, self.path)
|
||||||
|
|
||||||
|
|
||||||
class FolderIO(AbstractFolderIO):
|
class FolderIO(AbstractFolderIO):
|
||||||
def list(self):
|
def list(self):
|
||||||
@@ -21,6 +27,9 @@ class FolderIO(AbstractFolderIO):
|
|||||||
def get_file_io(self, name):
|
def get_file_io(self, name):
|
||||||
return FileIO(os.path.join(self.path, name))
|
return FileIO(os.path.join(self.path, name))
|
||||||
|
|
||||||
|
def get_parent_folder(self):
|
||||||
|
return FolderIO(os.path.dirname(self.path))
|
||||||
|
|
||||||
|
|
||||||
class FileIOFolderMixin(object):
|
class FileIOFolderMixin(object):
|
||||||
def get_parent_folder(self):
|
def get_parent_folder(self):
|
||||||
|
|||||||
@@ -505,7 +505,7 @@ def _load_builtin_module(inference_state, import_names=None, sys_path=None):
|
|||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
||||||
def _load_module_from_path(inference_state, file_io, base_names):
|
def load_module_from_path(inference_state, file_io, base_names=None):
|
||||||
"""
|
"""
|
||||||
This should pretty much only be used for get_modules_containing_name. It's
|
This should pretty much only be used for get_modules_containing_name. It's
|
||||||
here to ensure that a random path is still properly loaded into the Jedi
|
here to ensure that a random path is still properly loaded into the Jedi
|
||||||
@@ -552,7 +552,7 @@ def get_module_contexts_containing_name(inference_state, module_contexts, name):
|
|||||||
if name not in code:
|
if name not in code:
|
||||||
return None
|
return None
|
||||||
new_file_io = KnownContentFileIO(file_io.path, code)
|
new_file_io = KnownContentFileIO(file_io.path, code)
|
||||||
m = _load_module_from_path(inference_state, new_file_io, base_names)
|
m = load_module_from_path(inference_state, new_file_io, base_names)
|
||||||
if isinstance(m, compiled.CompiledObject):
|
if isinstance(m, compiled.CompiledObject):
|
||||||
return None
|
return None
|
||||||
return m.as_context()
|
return m.as_context()
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from jedi.inference.base_value import ValueSet, NO_VALUES
|
|||||||
from jedi.inference import docstrings
|
from jedi.inference import docstrings
|
||||||
from jedi.cache import memoize_method
|
from jedi.cache import memoize_method
|
||||||
from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf
|
from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf
|
||||||
|
from jedi.plugins import plugin_manager
|
||||||
|
|
||||||
|
|
||||||
def _merge_name_docs(names):
|
def _merge_name_docs(names):
|
||||||
@@ -482,6 +483,7 @@ class _ActualTreeParamName(BaseTreeParamName):
|
|||||||
|
|
||||||
|
|
||||||
class AnonymousParamName(_ActualTreeParamName):
|
class AnonymousParamName(_ActualTreeParamName):
|
||||||
|
@plugin_manager.decorate(name='infer_anonymous_param')
|
||||||
def infer(self):
|
def infer(self):
|
||||||
values = super(AnonymousParamName, self).infer()
|
values = super(AnonymousParamName, self).infer()
|
||||||
if values:
|
if values:
|
||||||
|
|||||||
@@ -14,18 +14,18 @@ class _PluginManager(object):
|
|||||||
self._registered_plugins.extend(plugins)
|
self._registered_plugins.extend(plugins)
|
||||||
self._build_functions()
|
self._build_functions()
|
||||||
|
|
||||||
def decorate(self):
|
def decorate(self, name=None):
|
||||||
def decorator(callback):
|
def decorator(callback):
|
||||||
@wraps(callback)
|
@wraps(callback)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
return built_functions[name](*args, **kwargs)
|
return built_functions[public_name](*args, **kwargs)
|
||||||
|
|
||||||
name = callback.__name__
|
public_name = name or callback.__name__
|
||||||
|
|
||||||
assert name not in self._built_functions
|
assert public_name not in self._built_functions
|
||||||
built_functions = self._built_functions
|
built_functions = self._built_functions
|
||||||
built_functions[name] = callback
|
built_functions[public_name] = callback
|
||||||
self._cached_base_callbacks[name] = callback
|
self._cached_base_callbacks[public_name] = callback
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|||||||
62
jedi/plugins/pytest.py
Normal file
62
jedi/plugins/pytest.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
from jedi._compatibility import FileNotFoundError
|
||||||
|
from jedi.inference.cache import inference_state_method_cache
|
||||||
|
from jedi.inference.imports import load_module_from_path
|
||||||
|
from jedi.inference.filters import ParserTreeFilter
|
||||||
|
from jedi.inference.base_value import NO_VALUES, ValueSet
|
||||||
|
|
||||||
|
|
||||||
|
def execute(callback):
|
||||||
|
def wrapper(value, arguments):
|
||||||
|
if value.py__name__() == 'fixture' \
|
||||||
|
and value.parent_context.py__name__() == '_pytest.fixtures':
|
||||||
|
return NO_VALUES
|
||||||
|
|
||||||
|
return callback(value, arguments)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def infer_anonymous_param(func):
|
||||||
|
def get_returns(value):
|
||||||
|
function_context = value.as_context()
|
||||||
|
return function_context.get_return_values()
|
||||||
|
|
||||||
|
def wrapper(param):
|
||||||
|
module = param.get_root_context()
|
||||||
|
fixtures = _goto_pytest_fixture(module, param.string_name)
|
||||||
|
if fixtures:
|
||||||
|
return ValueSet.from_sets(
|
||||||
|
get_returns(value)
|
||||||
|
for fixture in fixtures
|
||||||
|
for value in fixture.infer()
|
||||||
|
)
|
||||||
|
return func(param)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def _goto_pytest_fixture(module_context, name):
|
||||||
|
for module_context in _iter_pytest_modules(module_context):
|
||||||
|
names = FixtureFilter(module_context).get(name)
|
||||||
|
if names:
|
||||||
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
@inference_state_method_cache()
|
||||||
|
def _iter_pytest_modules(module_context):
|
||||||
|
yield module_context
|
||||||
|
|
||||||
|
folder = module_context.get_value().file_io.get_parent_folder()
|
||||||
|
sys_path = module_context.inference_state.get_sys_path()
|
||||||
|
while any(folder.path.startswith(p) for p in sys_path):
|
||||||
|
file_io = folder.get_file_io('conftest.py')
|
||||||
|
try:
|
||||||
|
m = load_module_from_path(module_context.inference_state, file_io)
|
||||||
|
yield m.as_context()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
folder = folder.get_parent_folder()
|
||||||
|
|
||||||
|
|
||||||
|
class FixtureFilter(ParserTreeFilter):
|
||||||
|
def _filter(self, names):
|
||||||
|
for name in super(FixtureFilter, self)._filter(names):
|
||||||
|
yield name
|
||||||
@@ -4,7 +4,8 @@ This is not a plugin, this is just the place were plugins are registered.
|
|||||||
|
|
||||||
from jedi.plugins import stdlib
|
from jedi.plugins import stdlib
|
||||||
from jedi.plugins import flask
|
from jedi.plugins import flask
|
||||||
|
from jedi.plugins import pytest
|
||||||
from jedi.plugins import plugin_manager
|
from jedi.plugins import plugin_manager
|
||||||
|
|
||||||
|
|
||||||
plugin_manager.register(stdlib, flask)
|
plugin_manager.register(stdlib, flask, pytest)
|
||||||
|
|||||||
17
test/completion/conftest.py
Normal file
17
test/completion/conftest.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Exists only for completion/pytest.py
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def my_other_conftest_fixture():
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def my_conftest_fixture(my_other_conftest_fixture):
|
||||||
|
return my_other_conftest_fixture
|
||||||
|
|
||||||
|
|
||||||
|
def my_not_existing_fixture():
|
||||||
|
return 3 # Just a normal function
|
||||||
45
test/completion/pytest.py
Normal file
45
test/completion/pytest.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# python > 2
|
||||||
|
import pytest
|
||||||
|
from pytest import fixture
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='module')
|
||||||
|
def my_fixture() -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@fixture
|
||||||
|
def my_simple_fixture():
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# goto/infer
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
#! 18 'def my_conftest_fixture'
|
||||||
|
def test_x(my_conftest_fixture, my_fixture, my_not_existing_fixture):
|
||||||
|
#? str()
|
||||||
|
my_fixture
|
||||||
|
#?
|
||||||
|
my_not_existing_fixture
|
||||||
|
#? float()
|
||||||
|
return my_conftest_fixture
|
||||||
|
|
||||||
|
#? 18 float()
|
||||||
|
def test_x(my_conftest_fixture, my_fixture):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# completion
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
#? 34 ['my_fixture']
|
||||||
|
def test_x(my_simple_fixture, my_fixture):
|
||||||
|
return
|
||||||
|
#? 18 ['my_simple_fixture']
|
||||||
|
def test_x(my_simple_fixture):
|
||||||
|
return
|
||||||
|
#? 18 ['my_conftest_fixture']
|
||||||
|
def test_x(my_conftest_fixture):
|
||||||
|
return
|
||||||
Reference in New Issue
Block a user