Merge branch 'master' into typeshed

There were quite a few conflicts, because there were two rewrites of the path
to dotted function.
This commit is contained in:
Dave Halter
2019-03-01 10:03:17 +01:00
12 changed files with 65 additions and 34 deletions

View File

@@ -32,7 +32,7 @@ from jedi.evaluate import imports
from jedi.evaluate import usages from jedi.evaluate import usages
from jedi.evaluate.arguments import try_iter_content from jedi.evaluate.arguments import try_iter_content
from jedi.evaluate.helpers import get_module_names, evaluate_call_of_leaf from jedi.evaluate.helpers import get_module_names, evaluate_call_of_leaf
from jedi.evaluate.sys_path import calculate_dotted_path_from_sys_path from jedi.evaluate.sys_path import transform_path_to_dotted
from jedi.evaluate.filters import TreeNameDefinition, ParamName from jedi.evaluate.filters import TreeNameDefinition, ParamName
from jedi.evaluate.syntax_tree import tree_name_to_contexts from jedi.evaluate.syntax_tree import tree_name_to_contexts
from jedi.evaluate.context import ModuleContext from jedi.evaluate.context import ModuleContext
@@ -108,7 +108,6 @@ class Script(object):
self._evaluator = Evaluator( self._evaluator = Evaluator(
project, environment=environment, script_path=self.path project, environment=environment, script_path=self.path
) )
self._project = project
debug.speed('init') debug.speed('init')
self._module_node, source = self._evaluator.parse_and_get_code( self._module_node, source = self._evaluator.parse_and_get_code(
code=source, code=source,
@@ -149,10 +148,7 @@ class Script(object):
def _get_module(self): def _get_module(self):
names = ('__main__',) names = ('__main__',)
if self.path is not None: if self.path is not None:
import_names = calculate_dotted_path_from_sys_path( import_names = transform_path_to_dotted(self._evaluator.get_sys_path(), self.path)
self._evaluator.get_sys_path(),
self.path
)
if import_names is not None: if import_names is not None:
names = import_names names = import_names

View File

@@ -522,7 +522,7 @@ def get_modules_containing_name(evaluator, modules, name):
if base_names: if base_names:
import_names = base_names + (module_name,) import_names = base_names + (module_name,)
else: else:
import_names = sys_path.calculate_dotted_path_from_sys_path(e_sys_path, path) import_names = sys_path.transform_path_to_dotted(e_sys_path, path)
module = _load_module( module = _load_module(
evaluator, path, code, evaluator, path, code,

View File

@@ -197,38 +197,40 @@ def _get_buildout_script_paths(search_path):
continue continue
def calculate_dotted_path_from_sys_path(sys_path, module_path): def transform_path_to_dotted(sys_path, module_path):
""" """
Returns the dotted path inside a sys.path as a list of names. Returns the dotted path inside a sys.path as a list of names. e.g.
This function is supposed to be a backup plan in case there's no idea where >>> transform_path_to_dotted(["/foo"], '/foo/bar/baz.py')
a file is lying. ('bar', 'baz')
Returns None if the path doesn't really resolve to anything.
""" """
# First remove the suffix. # First remove the suffix.
for suffix in all_suffixes(): for suffix in all_suffixes():
if module_path.endswith(suffix): if module_path.endswith(suffix):
module_path = module_path[:-len(suffix)] module_path = module_path[:-len(suffix)]
break break
else: # Once the suffix was removed we are using the files as we know them. This
# There should always be a suffix in a valid Python file on the path. # means that if someone uses an ending like .vim for a Python file, .vim
return None # will be part of the returned dotted part.
if module_path.endswith('__init__'): if module_path.endswith(os.path.sep + '__init__'):
# -1 to remove the separator
module_path = module_path[:-len('__init__') - 1] module_path = module_path[:-len('__init__') - 1]
if module_path.endswith(os.path.sep):
# The paths in sys.path may end with a slash.
module_path = module_path[:-1]
for p in sys_path: for p in sys_path:
if module_path.startswith(p): if module_path.startswith(p):
rest = module_path[len(p):] rest = module_path[len(p):]
# On Windows a path can also use a slash.
if rest.startswith(os.path.sep) or rest.startswith('/'): if rest.startswith(os.path.sep) or rest.startswith('/'):
# Remove a slash in cases it's still there.
rest = rest[1:] rest = rest[1:]
if rest: if rest:
split = rest.split(os.path.sep) split = rest.split(os.path.sep)
for string in split: for string in split:
if not string or '.' in string: if not string:
return None return None
return tuple(split) return tuple(split)
return None return None

View File

View File

View File

View File

@@ -12,4 +12,4 @@ def test_django_default_project(Script):
) )
c, = script.completions() c, = script.completions()
assert c.name == "SomeModel" assert c.name == "SomeModel"
assert script._project._django is True assert script._evaluator.project._django is True

View File

@@ -10,7 +10,8 @@ import pytest
from jedi._compatibility import find_module_py33, find_module from jedi._compatibility import find_module_py33, find_module
from jedi.evaluate import compiled from jedi.evaluate import compiled
from jedi.evaluate import imports from jedi.evaluate import imports
from ..helpers import cwd_at from jedi.api.project import Project
from ..helpers import cwd_at, get_example_dir
THIS_DIR = os.path.dirname(__file__) THIS_DIR = os.path.dirname(__file__)
@@ -272,3 +273,18 @@ def test_get_modules_containing_name(evaluator, path, goal):
) )
assert input_module is module assert input_module is module
assert found_module.string_names == goal assert found_module.string_names == goal
@pytest.mark.parametrize(
'path', ('api/whatever/test_this.py', 'api/whatever/file'))
def test_relative_imports_with_multiple_similar_directories(Script, path):
dir = get_example_dir('issue1209')
script = Script(
"from .",
path=os.path.join(dir, path)
)
# TODO pass this project to the script as a param once that's possible.
script._evaluator.project = Project(dir)
name, import_ = script.completions()
assert import_.name == 'import'
assert name.name == 'api_test1'

View File

@@ -63,17 +63,34 @@ def test_venv_and_pths(venv_path):
assert not set(sys.path).intersection(ETALON) assert not set(sys.path).intersection(ETALON)
@pytest.mark.parametrize(('sys_path_', 'path', 'expected'), [ _s = ['/a', '/b', '/c/d/']
(['/foo'], '/foo/bar.py', ('bar',)),
(['/foo'], '/foo/bar/baz.py', ('bar', 'baz')),
@pytest.mark.parametrize(
'sys_path_, module_path, result', [
(_s, '/a/b', ('b',)),
(_s, '/a/b/c', ('b', 'c')),
(_s, '/a/b.py', ('b',)),
(_s, '/a/b/c.py', ('b', 'c')),
(_s, '/x/b.py', None),
(_s, '/c/d/x.py', ('x',)),
(_s, '/c/d/x.py', ('x',)),
(_s, '/c/d/x/y.py', ('x', 'y')),
# If dots are in there they also resolve. These are obviously illegal
# in Python, but Jedi can handle them. Give the user a bit more freedom
# that he will have to correct eventually.
(_s, '/a/b.c.py', ('b.c',)),
(_s, '/a/b.d/foo.bar.py', ('b.d', 'foo.bar')),
(_s, '/a/.py', None),
(_s, '/a/c/.py', None),
(['/foo'], '/foo/bar/__init__.py', ('bar',)), (['/foo'], '/foo/bar/__init__.py', ('bar',)),
(['/foo'], '/foo/bar/baz/__init__.py', ('bar', 'baz')), (['/foo'], '/foo/bar/baz/__init__.py', ('bar', 'baz')),
(['/foo'], '/foo/bar.so', ('bar',)), (['/foo'], '/foo/bar.so', ('bar',)),
(['/foo'], '/foo/bar/__init__.so', ('bar',)), (['/foo'], '/foo/bar/__init__.so', ('bar',)),
(['/foo'], '/x/bar.py', None), (['/foo'], '/x/bar.py', None),
(['/foo'], '/foo/bar.xyz', None), (['/foo'], '/foo/bar.xyz', ('bar.xyz',)),
]) ])
def test_calculate_dotted_path_from_sys_path(path, sys_path_, expected): def test_calculate_dotted_from_path(sys_path_, module_path, result):
assert sys_path.calculate_dotted_path_from_sys_path(sys_path_, path) == expected assert sys_path.transform_path_to_dotted(sys_path_, module_path) == result