Merge branch 'master' into typeshed

This commit is contained in:
Dave Halter
2019-02-27 13:13:17 +01:00
15 changed files with 112 additions and 18 deletions

View File

@@ -3,6 +3,11 @@
Changelog Changelog
--------- ---------
0.13.3 (2019-02-24)
+++++++++++++++++++
- Fixed an issue with embedded Pytho, see https://github.com/davidhalter/jedi-vim/issues/870
0.13.2 (2018-12-15) 0.13.2 (2018-12-15)
+++++++++++++++++++ +++++++++++++++++++

View File

@@ -36,7 +36,7 @@ As you see Jedi is pretty simple and allows you to concentrate on writing a
good text editor, while still having very good IDE features for Python. good text editor, while still having very good IDE features for Python.
""" """
__version__ = '0.13.2' __version__ = '0.13.3'
from jedi.api import Script, Interpreter, set_debug_function, \ from jedi.api import Script, Interpreter, set_debug_function, \
preload_module, names preload_module, names

View File

@@ -348,6 +348,11 @@ try:
except NameError: except NameError:
NotADirectoryError = IOError NotADirectoryError = IOError
try:
PermissionError = PermissionError
except NameError:
PermissionError = IOError
def no_unicode_pprint(dct): def no_unicode_pprint(dct):
""" """

View File

@@ -153,9 +153,9 @@ def _get_virtual_env_from_var():
variable is considered to be safe / controlled by the user solely. variable is considered to be safe / controlled by the user solely.
""" """
var = os.environ.get('VIRTUAL_ENV') var = os.environ.get('VIRTUAL_ENV')
if var is not None: if var:
if var == sys.prefix: if var == sys.prefix:
return SameEnvironment() return _try_get_same_env()
try: try:
return create_environment(var, safe=False) return create_environment(var, safe=False)
@@ -184,9 +184,47 @@ def get_default_environment():
if virtual_env is not None: if virtual_env is not None:
return virtual_env return virtual_env
# If no VirtualEnv is found, use the environment we're already return _try_get_same_env()
def _try_get_same_env():
env = SameEnvironment()
if not os.path.basename(env.executable).lower().startswith('python'):
# This tries to counter issues with embedding. In some cases (e.g.
# VIM's Python Mac/Windows, sys.executable is /foo/bar/vim. This
# happens, because for Mac a function called `_NSGetExecutablePath` is
# used and for Windows `GetModuleFileNameW`. These are both platform
# specific functions. For all other systems sys.executable should be
# alright. However here we try to generalize:
#
# 1. Check if the executable looks like python (heuristic)
# 2. In case it's not try to find the executable
# 3. In case we don't find it use an interpreter environment.
#
# The last option will always work, but leads to potential crashes of
# Jedi - which is ok, because it happens very rarely and even less,
# because the code below should work for most cases.
if os.name == 'nt':
# The first case would be a virtualenv and the second a normal
# Python installation.
checks = (r'Scripts\python.exe', 'python.exe')
else:
# For unix it looks like Python is always in a bin folder.
checks = (
'bin/python%s.%s' % (sys.version_info[0], sys.version[1]),
'bin/python%s' % (sys.version_info[0]),
'bin/python',
)
for check in checks:
guess = os.path.join(sys.exec_prefix, check)
if os.path.isfile(guess):
# Bingo - We think we have our Python.
return Environment(guess)
# It looks like there is no reasonable Python to be found.
return InterpreterEnvironment()
# If no virtualenv is found, use the environment we're already
# using. # using.
return SameEnvironment() return env
def get_cached_default_environment(): def get_cached_default_environment():

View File

@@ -130,7 +130,10 @@ def get_stack_at_position(grammar, code_lines, module_node, pos):
p.parse(tokens=tokenize_without_endmarker(code)) p.parse(tokens=tokenize_without_endmarker(code))
except EndMarkerReached: except EndMarkerReached:
return p.stack return p.stack
raise SystemError("This really shouldn't happen. There's a bug in Jedi.") raise SystemError(
"This really shouldn't happen. There's a bug in Jedi:\n%s"
% list(tokenize_without_endmarker(code))
)
def evaluate_goto_definition(evaluator, context, leaf): def evaluate_goto_definition(evaluator, context, leaf):

View File

@@ -1,7 +1,7 @@
import os import os
import json import json
from jedi._compatibility import FileNotFoundError, NotADirectoryError from jedi._compatibility import FileNotFoundError, NotADirectoryError, PermissionError
from jedi.api.environment import SameEnvironment, \ from jedi.api.environment import SameEnvironment, \
get_cached_default_environment get_cached_default_environment
from jedi.api.exceptions import WrongVersion from jedi.api.exceptions import WrongVersion
@@ -151,7 +151,7 @@ def _is_django_path(directory):
try: try:
with open(os.path.join(directory, 'manage.py'), 'rb') as f: with open(os.path.join(directory, 'manage.py'), 'rb') as f:
return b"DJANGO_SETTINGS_MODULE" in f.read() return b"DJANGO_SETTINGS_MODULE" in f.read()
except (FileNotFoundError, NotADirectoryError): except (FileNotFoundError, NotADirectoryError, PermissionError):
return False return False
return False return False
@@ -167,7 +167,7 @@ def get_default_project(path=None):
for dir in traverse_parents(check, include_current=True): for dir in traverse_parents(check, include_current=True):
try: try:
return Project.load(dir) return Project.load(dir)
except (FileNotFoundError, NotADirectoryError): except (FileNotFoundError, NotADirectoryError, PermissionError):
pass pass
if first_no_init_file is None: if first_no_init_file is None:

View File

@@ -97,7 +97,7 @@ class MixedObjectFilter(compiled.CompiledObjectFilter):
@evaluator_function_cache() @evaluator_function_cache()
def _load_module(evaluator, path): def _load_module(evaluator, path):
module_node = evaluator.grammar.parse( module_node = evaluator.parse(
path=path, path=path,
cache=True, cache=True,
diff_cache=settings.fast_parser, diff_cache=settings.fast_parser,

View File

@@ -514,9 +514,10 @@ class SelfAttributeFilter(ClassFilter):
for name in names: for name in names:
trailer = name.parent trailer = name.parent
if trailer.type == 'trailer' \ if trailer.type == 'trailer' \
and len(trailer.children) == 2 \ and len(trailer.parent.children) == 2 \
and trailer.children[0] == '.': and trailer.children[0] == '.':
if name.is_definition() and self._access_possible(name): if name.is_definition() and self._access_possible(name):
# TODO filter non-self assignments.
yield name yield name
def _convert_names(self, names): def _convert_names(self, names):

View File

@@ -172,7 +172,7 @@ def get_module_names(module, all_scopes):
# parent_scope. There's None as a parent, because nodes in the module # parent_scope. There's None as a parent, because nodes in the module
# node have the parent module and not suite as all the others. # node have the parent module and not suite as all the others.
# Therefore it's important to catch that case. # Therefore it's important to catch that case.
names = [n for n in names if get_parent_scope(n).parent in (module, None)] names = [n for n in names if get_parent_scope(n) == module]
return names return names

View File

@@ -572,7 +572,8 @@ def tree_name_to_contexts(evaluator, context, tree_name):
filters = [next(filters)] filters = [next(filters)]
return finder.find(filters, attribute_lookup=False) return finder.find(filters, attribute_lookup=False)
elif node.type not in ('import_from', 'import_name'): elif node.type not in ('import_from', 'import_name'):
raise ValueError("Should not happen. type: %s", node.type) context = evaluator.create_context(context, tree_name)
return eval_atom(context, tree_name)
typ = node.type typ = node.type
if typ == 'for_stmt': if typ == 'for_stmt':
@@ -612,6 +613,8 @@ def tree_name_to_contexts(evaluator, context, tree_name):
# the static analysis report. # the static analysis report.
exceptions = context.eval_node(tree_name.get_previous_sibling().get_previous_sibling()) exceptions = context.eval_node(tree_name.get_previous_sibling().get_previous_sibling())
types = exceptions.execute_evaluated() types = exceptions.execute_evaluated()
elif node.type == 'param':
types = NO_CONTEXTS
else: else:
raise ValueError("Should not happen. type: %s" % typ) raise ValueError("Should not happen. type: %s" % typ)
return types return types

View File

@@ -247,11 +247,16 @@ def get_parent_scope(node, include_flows=False):
Returns the underlying scope. Returns the underlying scope.
""" """
scope = node.parent scope = node.parent
while scope is not None: if scope is None:
if include_flows and isinstance(scope, tree.Flow): return None # It's a module already.
if scope.type in ('funcdef', 'classdef') and scope.name == node:
scope = scope.parent
if scope.parent is None: # The module scope.
return scope
while True:
if include_flows and isinstance(scope, tree.Flow) or is_scope(scope):
return scope return scope
if is_scope(scope):
break
scope = scope.parent scope = scope.parent
return scope return scope

View File

@@ -36,6 +36,7 @@ class TestClass(object):
self2.var_inst = first_param self2.var_inst = first_param
self2.second = second_param self2.second = second_param
self2.first = first_param self2.first = first_param
self2.first.var_on_argument = 5
a = 3 a = 3
def var_func(self): def var_func(self):

View File

@@ -102,7 +102,7 @@ def test_function_call_signature_in_doc(Script):
def test_param_docstring(): def test_param_docstring():
param = jedi.names("def test(parameter): pass")[1] param = jedi.names("def test(parameter): pass", all_scopes=True)[1]
assert param.name == 'parameter' assert param.name == 'parameter'
assert param.docstring() == '' assert param.docstring() == ''

View File

@@ -96,3 +96,25 @@ def test_names_twice(environment):
defs = names(source=source, environment=environment) defs = names(source=source, environment=environment)
assert defs[0].defined_names() == [] assert defs[0].defined_names() == []
def test_simple_name(environment):
defs = names('foo', references=True, environment=environment)
assert not defs[0]._name.infer()
def test_no_error(environment):
code = dedent("""
def foo(a, b):
if a == 10:
if b is None:
print("foo")
a = 20
""")
func_name, = names(code)
print(func_name.defined_names())
a, b, a20 = func_name.defined_names()
assert a.name == 'a'
assert b.name == 'b'
assert a20.name == 'a'
assert a20.goto_assignments() == [a20]

View File

@@ -131,6 +131,17 @@ def test_get_default_environment_from_env_does_not_use_safe(tmpdir, monkeypatch)
assert env.path == 'fake' assert env.path == 'fake'
@pytest.mark.parametrize('virtualenv', ['', 'fufuuuuu', sys.prefix])
def test_get_default_environment_when_embedded(monkeypatch, virtualenv):
# When using Python embedded, sometimes the executable is not a Python
# executable.
executable_name = 'RANDOM_EXE'
monkeypatch.setattr(sys, 'executable', executable_name)
monkeypatch.setenv('VIRTUAL_ENV', virtualenv)
env = get_default_environment()
assert env.executable != executable_name
def test_changing_venv(venv_path, monkeypatch): def test_changing_venv(venv_path, monkeypatch):
monkeypatch.setitem(os.environ, 'VIRTUAL_ENV', venv_path) monkeypatch.setitem(os.environ, 'VIRTUAL_ENV', venv_path)
get_cached_default_environment() get_cached_default_environment()