forked from VimPlug/jedi
Improve virtualenv support & egg-link resolution
- add sys_path= kwarg to Script & Evaluator constructors - store sys_path for each evaluator instance - replace get_sys_path with get_venv_path - get_venv_path: use addsitedir to load .pth extension files - get_venv_path: look for egg-link files in all directories in path
This commit is contained in:
@@ -61,6 +61,7 @@ that are not used are just being ignored.
|
||||
"""
|
||||
|
||||
import copy
|
||||
import sys
|
||||
from itertools import chain
|
||||
|
||||
from jedi.parser import tree
|
||||
@@ -79,7 +80,7 @@ from jedi.evaluate import helpers
|
||||
|
||||
|
||||
class Evaluator(object):
|
||||
def __init__(self, grammar):
|
||||
def __init__(self, grammar, sys_path=None):
|
||||
self.grammar = grammar
|
||||
self.memoize_cache = {} # for memoize decorators
|
||||
# To memorize modules -> equals `sys.modules`.
|
||||
@@ -88,6 +89,13 @@ class Evaluator(object):
|
||||
self.recursion_detector = recursion.RecursionDetector()
|
||||
self.execution_recursion_detector = recursion.ExecutionRecursionDetector()
|
||||
self.analysis = []
|
||||
if sys_path is None:
|
||||
sys_path = sys.path
|
||||
self.sys_path = copy.copy(sys_path)
|
||||
try:
|
||||
self.sys_path.remove('')
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def wrap(self, element):
|
||||
if isinstance(element, tree.Class):
|
||||
|
||||
@@ -10,7 +10,6 @@ from functools import partial
|
||||
from jedi._compatibility import builtins as _builtins, unicode
|
||||
from jedi import debug
|
||||
from jedi.cache import underscore_memoization, memoize_method
|
||||
from jedi.evaluate.sys_path import get_sys_path
|
||||
from jedi.parser.tree import Param, Base, Operator, zero_position_modifier
|
||||
from jedi.evaluate.helpers import FakeName
|
||||
from . import fake
|
||||
@@ -309,15 +308,12 @@ class CompiledName(FakeName):
|
||||
pass # Just ignore this, FakeName tries to overwrite the parent attribute.
|
||||
|
||||
|
||||
def dotted_from_fs_path(fs_path, sys_path=None):
|
||||
def dotted_from_fs_path(fs_path, sys_path):
|
||||
"""
|
||||
Changes `/usr/lib/python3.4/email/utils.py` to `email.utils`. I.e.
|
||||
compares the path with sys.path and then returns the dotted_path. If the
|
||||
path is not in the sys.path, just returns None.
|
||||
"""
|
||||
if sys_path is None:
|
||||
sys_path = get_sys_path()
|
||||
|
||||
if os.path.basename(fs_path).startswith('__init__.'):
|
||||
# We are calculating the path. __init__ files are not interesting.
|
||||
fs_path = os.path.dirname(fs_path)
|
||||
@@ -341,13 +337,13 @@ def dotted_from_fs_path(fs_path, sys_path=None):
|
||||
return _path_re.sub('', fs_path[len(path):].lstrip(os.path.sep)).replace(os.path.sep, '.')
|
||||
|
||||
|
||||
def load_module(path=None, name=None):
|
||||
def load_module(evaluator, path=None, name=None):
|
||||
sys_path = evaluator.sys_path
|
||||
if path is not None:
|
||||
dotted_path = dotted_from_fs_path(path)
|
||||
dotted_path = dotted_from_fs_path(path, sys_path=sys_path)
|
||||
else:
|
||||
dotted_path = name
|
||||
|
||||
sys_path = get_sys_path()
|
||||
if dotted_path is None:
|
||||
p, _, dotted_path = path.partition(os.path.sep)
|
||||
sys_path.insert(0, p)
|
||||
|
||||
@@ -342,7 +342,7 @@ class Importer(object):
|
||||
module_file.close()
|
||||
|
||||
if module_file is None and not module_path.endswith('.py'):
|
||||
module = compiled.load_module(module_path)
|
||||
module = compiled.load_module(self._evaluator, module_path)
|
||||
else:
|
||||
module = _load_module(self._evaluator, module_path, source, sys_path)
|
||||
|
||||
@@ -440,12 +440,15 @@ def _load_module(evaluator, path=None, source=None, sys_path=None):
|
||||
with open(path, 'rb') as f:
|
||||
source = f.read()
|
||||
else:
|
||||
return compiled.load_module(path)
|
||||
return compiled.load_module(evaluator, path)
|
||||
p = path
|
||||
p = fast.FastParser(evaluator.grammar, common.source_to_unicode(source), p)
|
||||
cache.save_parser(path, p)
|
||||
return p.module
|
||||
|
||||
if sys_path is None:
|
||||
sys_path = evaluator.sys_path
|
||||
|
||||
cached = cache.load_parser(path)
|
||||
module = load(source) if cached is None else cached.module
|
||||
module = evaluator.wrap(module)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
from site import addsitedir
|
||||
|
||||
from jedi._compatibility import exec_function, unicode
|
||||
from jedi.parser import tree
|
||||
@@ -11,24 +12,51 @@ from jedi import common
|
||||
from jedi import cache
|
||||
|
||||
|
||||
def get_sys_path():
|
||||
def check_virtual_env(sys_path):
|
||||
""" Add virtualenv's site-packages to the `sys.path`."""
|
||||
venv = os.getenv('VIRTUAL_ENV')
|
||||
if not venv:
|
||||
return
|
||||
venv = os.path.abspath(venv)
|
||||
p = _get_venv_sitepackages(venv)
|
||||
if p not in sys_path:
|
||||
sys_path.insert(0, p)
|
||||
def get_venv_path(venv):
|
||||
"""Get sys.path for specified virtual environment."""
|
||||
sys_path = _get_venv_path_dirs(venv)
|
||||
with common.ignored(ValueError):
|
||||
sys_path.remove('')
|
||||
sys_path = _get_sys_path_with_egglinks(sys_path)
|
||||
# As of now, get_venv_path_dirs does not scan built-in pythonpath and
|
||||
# user-local site-packages, let's approximate them using path from Jedi
|
||||
# interpreter.
|
||||
return sys_path + sys.path
|
||||
|
||||
# Add all egg-links from the virtualenv.
|
||||
|
||||
def _get_sys_path_with_egglinks(sys_path):
|
||||
"""Find all paths including those referenced by egg-links.
|
||||
|
||||
Egg-link-referenced directories are inserted into path immediately after
|
||||
the directory on which their links were found. Such directories are not
|
||||
taken into consideration by normal import mechanism, but they are traversed
|
||||
when doing pkg_resources.require.
|
||||
"""
|
||||
result = []
|
||||
for p in sys_path:
|
||||
result.append(p)
|
||||
for egg_link in glob.glob(os.path.join(p, '*.egg-link')):
|
||||
with open(egg_link) as fd:
|
||||
sys_path.insert(0, fd.readline().rstrip())
|
||||
for line in fd:
|
||||
line = line.strip()
|
||||
if line:
|
||||
result.append(os.path.join(p, line))
|
||||
# pkg_resources package only interprets the first
|
||||
# non-empty line in egg-link files.
|
||||
break
|
||||
return result
|
||||
|
||||
check_virtual_env(sys.path)
|
||||
return [p for p in sys.path if p != ""]
|
||||
|
||||
def _get_venv_path_dirs(venv):
|
||||
"""Get sys.path for venv without starting up the interpreter."""
|
||||
venv = os.path.abspath(venv)
|
||||
sitedir = _get_venv_sitepackages(venv)
|
||||
sys.path, old_sys_path = [], sys.path
|
||||
try:
|
||||
addsitedir(sitedir)
|
||||
return sys.path
|
||||
finally:
|
||||
sys.path = old_sys_path
|
||||
|
||||
|
||||
def _get_venv_sitepackages(venv):
|
||||
@@ -109,7 +137,6 @@ def _paths_from_list_modifications(module_path, trailer1, trailer2):
|
||||
name = trailer1.children[1].value
|
||||
if name not in ['insert', 'append']:
|
||||
return []
|
||||
|
||||
arg = trailer2.children[1]
|
||||
if name == 'insert' and len(arg.children) in (3, 4): # Possible trailing comma.
|
||||
arg = arg.children[2]
|
||||
@@ -117,6 +144,9 @@ def _paths_from_list_modifications(module_path, trailer1, trailer2):
|
||||
|
||||
|
||||
def _check_module(evaluator, module):
|
||||
"""
|
||||
Detect sys.path modifications within module.
|
||||
"""
|
||||
def get_sys_path_powers(names):
|
||||
for name in names:
|
||||
power = name.parent.parent
|
||||
@@ -128,10 +158,12 @@ def _check_module(evaluator, module):
|
||||
if isinstance(n, tree.Name) and n.value == 'path':
|
||||
yield name, power
|
||||
|
||||
sys_path = list(get_sys_path()) # copy
|
||||
sys_path = list(evaluator.sys_path) # copy
|
||||
try:
|
||||
possible_names = module.used_names['path']
|
||||
except KeyError:
|
||||
# module.used_names is MergedNamesDict whose getitem never throws
|
||||
# keyerror, this is superfluous.
|
||||
pass
|
||||
else:
|
||||
for name, power in get_sys_path_powers(possible_names):
|
||||
@@ -148,7 +180,7 @@ def sys_path_with_modifications(evaluator, module):
|
||||
if module.path is None:
|
||||
# Support for modules without a path is bad, therefore return the
|
||||
# normal path.
|
||||
return list(get_sys_path())
|
||||
return list(evaluator.sys_path)
|
||||
|
||||
curdir = os.path.abspath(os.curdir)
|
||||
with common.ignored(OSError):
|
||||
|
||||
Reference in New Issue
Block a user