forked from VimPlug/jedi
Trying to change the import logic completely. We now have a sys.modules like cache.
This commit is contained in:
@@ -114,7 +114,7 @@ class Script(object):
|
|||||||
|
|
||||||
def _parsed_callback(self, parser):
|
def _parsed_callback(self, parser):
|
||||||
module = er.wrap(self._evaluator, parser.module)
|
module = er.wrap(self._evaluator, parser.module)
|
||||||
self._evaluator.module_name_cache[module] = unicode(module.name)
|
self._evaluator.modules[unicode(module.name)] = module
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def source_path(self):
|
def source_path(self):
|
||||||
|
|||||||
+7
-12
@@ -191,17 +191,13 @@ def invalidate_star_import_cache(path):
|
|||||||
_invalidate_star_import_cache_module(parser_cache_item.parser.module)
|
_invalidate_star_import_cache_module(parser_cache_item.parser.module)
|
||||||
|
|
||||||
|
|
||||||
def load_parser(path, name):
|
def load_parser(path):
|
||||||
"""
|
"""
|
||||||
Returns the module or None, if it fails.
|
Returns the module or None, if it fails.
|
||||||
"""
|
"""
|
||||||
if path is None and name is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
p_time = os.path.getmtime(path) if path else None
|
p_time = os.path.getmtime(path) if path else None
|
||||||
n = name if path is None else path
|
|
||||||
try:
|
try:
|
||||||
parser_cache_item = parser_cache[n]
|
parser_cache_item = parser_cache[path]
|
||||||
if not path or p_time <= parser_cache_item.change_time:
|
if not path or p_time <= parser_cache_item.change_time:
|
||||||
return parser_cache_item.parser
|
return parser_cache_item.parser
|
||||||
else:
|
else:
|
||||||
@@ -211,21 +207,20 @@ def load_parser(path, name):
|
|||||||
_invalidate_star_import_cache_module(parser_cache_item.parser.module)
|
_invalidate_star_import_cache_module(parser_cache_item.parser.module)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if settings.use_filesystem_cache:
|
if settings.use_filesystem_cache:
|
||||||
return ParserPickling.load_parser(n, p_time)
|
return ParserPickling.load_parser(path, p_time)
|
||||||
|
|
||||||
|
|
||||||
def save_parser(path, name, parser, pickling=True):
|
def save_parser(path, parser, pickling=True):
|
||||||
try:
|
try:
|
||||||
p_time = None if not path else os.path.getmtime(path)
|
p_time = os.path.getmtime(path)
|
||||||
except OSError:
|
except OSError:
|
||||||
p_time = None
|
p_time = None
|
||||||
pickling = False
|
pickling = False
|
||||||
|
|
||||||
n = name if path is None else path
|
|
||||||
item = ParserCacheItem(parser, p_time)
|
item = ParserCacheItem(parser, p_time)
|
||||||
parser_cache[n] = item
|
parser_cache[path] = item
|
||||||
if settings.use_filesystem_cache and pickling:
|
if settings.use_filesystem_cache and pickling:
|
||||||
ParserPickling.save_parser(n, item)
|
ParserPickling.save_parser(path, item)
|
||||||
|
|
||||||
|
|
||||||
class ParserPickling(object):
|
class ParserPickling(object):
|
||||||
|
|||||||
@@ -82,9 +82,8 @@ class Evaluator(object):
|
|||||||
def __init__(self, grammar):
|
def __init__(self, grammar):
|
||||||
self.grammar = grammar
|
self.grammar = grammar
|
||||||
self.memoize_cache = {} # for memoize decorators
|
self.memoize_cache = {} # for memoize decorators
|
||||||
# To memorize module names (that are assigned to modules by the import
|
# To memorize modules -> equals `sys.modules`.
|
||||||
# logic) -> a ``__name__`` is given.
|
self.modules = {}
|
||||||
self.module_name_cache = {}
|
|
||||||
self.import_cache = {} # like `sys.modules`.
|
self.import_cache = {} # like `sys.modules`.
|
||||||
self.compiled_cache = {} # see `compiled.create()`
|
self.compiled_cache = {} # see `compiled.create()`
|
||||||
self.recursion_detector = recursion.RecursionDetector()
|
self.recursion_detector = recursion.RecursionDetector()
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ 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, '.')
|
return _path_re.sub('', fs_path[len(path):].lstrip(os.path.sep)).replace(os.path.sep, '.')
|
||||||
|
|
||||||
|
|
||||||
def load_module(path, name):
|
def load_module(path=None, name=None):
|
||||||
if path is not None:
|
if path is not None:
|
||||||
dotted_path = dotted_from_fs_path(path)
|
dotted_path = dotted_from_fs_path(path)
|
||||||
else:
|
else:
|
||||||
|
|||||||
+118
-44
@@ -163,13 +163,12 @@ def get_importer(evaluator, import_path, module, level=0):
|
|||||||
# TODO add import error.
|
# TODO add import error.
|
||||||
debug.warning('Attempted relative import beyond top-level package.')
|
debug.warning('Attempted relative import beyond top-level package.')
|
||||||
# TODO this is just in the wrong place.
|
# TODO this is just in the wrong place.
|
||||||
|
raise NotImplementedError
|
||||||
return _Importer(evaluator, import_path, module, level)
|
return _Importer(evaluator, import_path, module, level)
|
||||||
else:
|
else:
|
||||||
# Here we basically rewrite the level to 0.
|
# Here we basically rewrite the level to 0.
|
||||||
import_path = tuple(base) + import_path
|
import_path = tuple(base) + import_path
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
check_import_path = tuple(unicode(i) for i in import_path)
|
check_import_path = tuple(unicode(i) for i in import_path)
|
||||||
try:
|
try:
|
||||||
return evaluator.import_cache[check_import_path]
|
return evaluator.import_cache[check_import_path]
|
||||||
@@ -215,18 +214,34 @@ class _Importer(object):
|
|||||||
|
|
||||||
@memoize_default()
|
@memoize_default()
|
||||||
def sys_path_with_modifications(self):
|
def sys_path_with_modifications(self):
|
||||||
# If you edit e.g. gunicorn, there will be imports like this:
|
|
||||||
# `from gunicorn import something`. But gunicorn is not in the
|
|
||||||
# sys.path. Therefore look if gunicorn is a parent directory, #56.
|
|
||||||
in_path = []
|
in_path = []
|
||||||
if self.import_path and self.file_path is not None:
|
sys_path_mod = list(sys_path_with_modifications(self._evaluator, self.module))
|
||||||
parts = self.file_path.split(os.path.sep)
|
if self.file_path is not None:
|
||||||
for i, p in enumerate(parts):
|
# If you edit e.g. gunicorn, there will be imports like this:
|
||||||
if p == unicode(self.import_path[0]):
|
# `from gunicorn import something`. But gunicorn is not in the
|
||||||
new = os.path.sep.join(parts[:i])
|
# sys.path. Therefore look if gunicorn is a parent directory, #56.
|
||||||
in_path.append(new)
|
if self.import_path: # TODO is this check really needed?
|
||||||
|
parts = self.file_path.split(os.path.sep)
|
||||||
|
for i, p in enumerate(parts):
|
||||||
|
if p == unicode(self.import_path[0]):
|
||||||
|
new = os.path.sep.join(parts[:i])
|
||||||
|
in_path.append(new)
|
||||||
|
|
||||||
return in_path + sys_path_with_modifications(self._evaluator, self.module)
|
if not self.module.has_explicit_absolute_import:
|
||||||
|
# If the module explicitly asks for absolute imports,
|
||||||
|
# there's probably a bogus local one.
|
||||||
|
sys_path_mod.insert(0, self.file_path)
|
||||||
|
|
||||||
|
# First the sys path is searched normally and if that doesn't
|
||||||
|
# succeed, try to search the parent directories, because sometimes
|
||||||
|
# Jedi doesn't recognize sys.path modifications (like py.test
|
||||||
|
# stuff).
|
||||||
|
old_path, temp_path = self.file_path, os.path.dirname(self.file_path)
|
||||||
|
while old_path != temp_path:
|
||||||
|
sys_path_mod.append(temp_path)
|
||||||
|
old_path, temp_path = temp_path, os.path.dirname(temp_path)
|
||||||
|
|
||||||
|
return in_path + sys_path_mod
|
||||||
|
|
||||||
def follow(self, evaluator):
|
def follow(self, evaluator):
|
||||||
try:
|
try:
|
||||||
@@ -257,6 +272,11 @@ class _Importer(object):
|
|||||||
|
|
||||||
@memoize_default(NO_DEFAULT)
|
@memoize_default(NO_DEFAULT)
|
||||||
def follow_file_system(self):
|
def follow_file_system(self):
|
||||||
|
module = self._do_import(self.import_path, self.sys_path_with_modifications())
|
||||||
|
return module, []
|
||||||
|
|
||||||
|
|
||||||
|
# TODO delete - move!
|
||||||
# Handle "magic" Flask extension imports:
|
# Handle "magic" Flask extension imports:
|
||||||
# ``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
|
# ``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
|
||||||
if len(self.import_path) > 2 and self.str_import_path[:2] == ('flask', 'ext'):
|
if len(self.import_path) > 2 and self.str_import_path[:2] == ('flask', 'ext'):
|
||||||
@@ -325,50 +345,50 @@ class _Importer(object):
|
|||||||
return follow_path((str(i) for i in import_path), sys.path)
|
return follow_path((str(i) for i in import_path), sys.path)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def _follow_str(self, sys_path, ns_path, string):
|
||||||
|
debug.dbg('follow_module %s in %s', string, ns_path)
|
||||||
|
path = None
|
||||||
|
if ns_path:
|
||||||
|
path = ns_path
|
||||||
|
elif self.level > 0: # is a relative import
|
||||||
|
path = self.get_relative_path()
|
||||||
|
|
||||||
|
if path is not None:
|
||||||
|
importing = find_module(string, [path])
|
||||||
|
else:
|
||||||
|
debug.dbg('search_module %s in %s', string, self.file_path)
|
||||||
|
# Override the sys.path. It works only good that way.
|
||||||
|
# Injecting the path directly into `find_module` did not work.
|
||||||
|
sys.path, temp = sys_path, sys.path
|
||||||
|
try:
|
||||||
|
importing = find_module(string)
|
||||||
|
finally:
|
||||||
|
sys.path = temp
|
||||||
|
|
||||||
|
return importing
|
||||||
|
|
||||||
def _follow_sys_path(self, sys_path):
|
def _follow_sys_path(self, sys_path):
|
||||||
"""
|
"""
|
||||||
Find a module with a path (of the module, like usb.backend.libusb10).
|
Find a module with a path (of the module, like usb.backend.libusb10).
|
||||||
"""
|
"""
|
||||||
def follow_str(ns_path, string):
|
|
||||||
debug.dbg('follow_module %s in %s', string, ns_path)
|
|
||||||
path = None
|
|
||||||
if ns_path:
|
|
||||||
path = ns_path
|
|
||||||
elif self.level > 0: # is a relative import
|
|
||||||
path = self.get_relative_path()
|
|
||||||
|
|
||||||
if path is not None:
|
|
||||||
importing = find_module(string, [path])
|
|
||||||
else:
|
|
||||||
debug.dbg('search_module %s in %s', string, self.file_path)
|
|
||||||
# Override the sys.path. It works only good that way.
|
|
||||||
# Injecting the path directly into `find_module` did not work.
|
|
||||||
sys.path, temp = sys_path, sys.path
|
|
||||||
try:
|
|
||||||
importing = find_module(string)
|
|
||||||
finally:
|
|
||||||
sys.path = temp
|
|
||||||
|
|
||||||
return importing
|
|
||||||
|
|
||||||
current_namespace = (None, None, None)
|
current_namespace = (None, None, None)
|
||||||
# now execute those paths
|
# now execute those paths
|
||||||
rest = []
|
rest = []
|
||||||
for i, s in enumerate(self.import_path):
|
for i, s in enumerate(self.import_path):
|
||||||
try:
|
try:
|
||||||
current_namespace = follow_str(current_namespace[1], unicode(s))
|
current_namespace = self._follow_str(sys_path, current_namespace[1], unicode(s))
|
||||||
except ImportError:
|
except ImportError:
|
||||||
_continue = False
|
_continue = False
|
||||||
if self.level >= 1 and len(self.import_path) == 1:
|
if self.level >= 1 and len(self.import_path) == 1:
|
||||||
# follow `from . import some_variable`
|
# follow `from . import some_variable`
|
||||||
rel_path = self.get_relative_path()
|
rel_path = self.get_relative_path()
|
||||||
with common.ignored(ImportError):
|
with common.ignored(ImportError):
|
||||||
current_namespace = follow_str(rel_path, '__init__')
|
current_namespace = self._follow_str(sys_path, rel_path, '__init__')
|
||||||
elif current_namespace[2]: # is a package
|
elif current_namespace[2]: # is a package
|
||||||
path = self.str_import_path[:i]
|
path = self.str_import_path[:i]
|
||||||
for n in self.namespace_packages(current_namespace[1], path):
|
for n in self.namespace_packages(current_namespace[1], path):
|
||||||
try:
|
try:
|
||||||
current_namespace = follow_str(n, unicode(s))
|
current_namespace = self._follow_str(sys_path, n, unicode(s))
|
||||||
if current_namespace[1]:
|
if current_namespace[1]:
|
||||||
_continue = True
|
_continue = True
|
||||||
break
|
break
|
||||||
@@ -414,6 +434,62 @@ class _Importer(object):
|
|||||||
return _load_module(self._evaluator, name=path,
|
return _load_module(self._evaluator, name=path,
|
||||||
sys_path=sys_path, module_name=module_name), rest
|
sys_path=sys_path, module_name=module_name), rest
|
||||||
|
|
||||||
|
def _do_import(self, import_path, sys_path):
|
||||||
|
"""
|
||||||
|
This method is very similar to importlib's `_gcd_import`.
|
||||||
|
"""
|
||||||
|
import_parts = [str(i) for i in import_path]
|
||||||
|
module_name = '.'.join(import_parts)
|
||||||
|
try:
|
||||||
|
return self._evaluator.modules[module_name]
|
||||||
|
except KeyError:
|
||||||
|
try:
|
||||||
|
if len(import_path) > 1:
|
||||||
|
# This is a recursive way of importing that works great with
|
||||||
|
# the module cache.
|
||||||
|
base = self._do_import(import_path[:-1], sys_path)
|
||||||
|
path = base.py__file__()
|
||||||
|
|
||||||
|
debug.dbg('search_module %s in pkg %s', module_name, path)
|
||||||
|
module_file, module_path, is_pkg = \
|
||||||
|
find_module(import_parts[-1], [path])
|
||||||
|
raise NotImplementedError
|
||||||
|
else:
|
||||||
|
debug.dbg('search_module %s in %s', module_name, self.file_path)
|
||||||
|
# Override the sys.path. It works only good that way.
|
||||||
|
# Injecting the path directly into `find_module` did not work.
|
||||||
|
sys.path, temp = sys_path, sys.path
|
||||||
|
try:
|
||||||
|
module_file, module_path, is_pkg = \
|
||||||
|
find_module(import_parts[-1])
|
||||||
|
finally:
|
||||||
|
sys.path = temp
|
||||||
|
except ImportError:
|
||||||
|
raise NotImplementedError
|
||||||
|
else:
|
||||||
|
source = None
|
||||||
|
if is_pkg:
|
||||||
|
# In this case, we don't have a file yet. Search for the
|
||||||
|
# __init__ file.
|
||||||
|
for suffix, _, _ in imp.get_suffixes():
|
||||||
|
path = os.path.join(module_path, '__init__' + suffix)
|
||||||
|
if os.path.exists(path):
|
||||||
|
if suffix == '.py':
|
||||||
|
module_path = path
|
||||||
|
break
|
||||||
|
elif module_file:
|
||||||
|
source = module_file.read()
|
||||||
|
module_file.close()
|
||||||
|
|
||||||
|
if module_file is None and not module_path.endswith('.py'):
|
||||||
|
module = compiled.load_module(module_path)
|
||||||
|
else:
|
||||||
|
module = _load_module(self._evaluator, module_path, source,
|
||||||
|
sys_path, module_name)
|
||||||
|
|
||||||
|
self._evaluator.modules[module_name] = module
|
||||||
|
return module
|
||||||
|
|
||||||
def _generate_name(self, name):
|
def _generate_name(self, name):
|
||||||
return helpers.FakeName(name, parent=self.module)
|
return helpers.FakeName(name, parent=self.module)
|
||||||
|
|
||||||
@@ -505,7 +581,7 @@ class _Importer(object):
|
|||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
||||||
def _load_module(evaluator, path=None, source=None, name=None, sys_path=None, module_name=None):
|
def _load_module(evaluator, path=None, source=None, sys_path=None, module_name=None):
|
||||||
def load(source):
|
def load(source):
|
||||||
dotted_path = path and compiled.dotted_from_fs_path(path, sys_path)
|
dotted_path = path and compiled.dotted_from_fs_path(path, sys_path)
|
||||||
if path is not None and path.endswith('.py') \
|
if path is not None and path.endswith('.py') \
|
||||||
@@ -514,18 +590,16 @@ def _load_module(evaluator, path=None, source=None, name=None, sys_path=None, mo
|
|||||||
with open(path, 'rb') as f:
|
with open(path, 'rb') as f:
|
||||||
source = f.read()
|
source = f.read()
|
||||||
else:
|
else:
|
||||||
return compiled.load_module(path, name)
|
return compiled.load_module(path)
|
||||||
p = path or name
|
p = path
|
||||||
p = fast.FastParser(evaluator.grammar, common.source_to_unicode(source), p)
|
p = fast.FastParser(evaluator.grammar, common.source_to_unicode(source), p)
|
||||||
cache.save_parser(path, name, p)
|
cache.save_parser(path, p)
|
||||||
|
|
||||||
return p.module
|
return p.module
|
||||||
|
|
||||||
cached = cache.load_parser(path, name)
|
cached = cache.load_parser(path)
|
||||||
module = load(source) if cached is None else cached.module
|
module = load(source) if cached is None else cached.module
|
||||||
# TODO return mod instead of just something.
|
# TODO return mod instead of just something.
|
||||||
module = evaluator.wrap(module)
|
module = evaluator.wrap(module)
|
||||||
evaluator.module_name_cache[module] = module_name
|
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ def get_sys_path():
|
|||||||
check_virtual_env(sys.path)
|
check_virtual_env(sys.path)
|
||||||
return [p for p in sys.path if p != ""]
|
return [p for p in sys.path if p != ""]
|
||||||
|
|
||||||
|
|
||||||
def _get_venv_sitepackages(venv):
|
def _get_venv_sitepackages(venv):
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
p = os.path.join(venv, 'lib', 'site-packages')
|
p = os.path.join(venv, 'lib', 'site-packages')
|
||||||
@@ -175,7 +176,7 @@ def _get_paths_from_buildout_script(evaluator, buildout_script):
|
|||||||
return
|
return
|
||||||
|
|
||||||
p = Parser(evaluator.grammar, source, buildout_script)
|
p = Parser(evaluator.grammar, source, buildout_script)
|
||||||
cache.save_parser(buildout_script, None, p)
|
cache.save_parser(buildout_script, p)
|
||||||
return p.module
|
return p.module
|
||||||
|
|
||||||
cached = cache.load_parser(buildout_script, None)
|
cached = cache.load_parser(buildout_script, None)
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ class UserContextParser(object):
|
|||||||
if self._use_fast_parser:
|
if self._use_fast_parser:
|
||||||
parser = FastParser(self._grammar, self._source, self._path)
|
parser = FastParser(self._grammar, self._source, self._path)
|
||||||
# Don't pickle that module, because the main module is changing quickly
|
# Don't pickle that module, because the main module is changing quickly
|
||||||
cache.save_parser(self._path, None, parser, pickling=False)
|
cache.save_parser(self._path, parser, pickling=False)
|
||||||
else:
|
else:
|
||||||
parser = Parser(self._grammar, self._source, self._path)
|
parser = Parser(self._grammar, self._source, self._path)
|
||||||
self._parser_done_callback(parser)
|
self._parser_done_callback(parser)
|
||||||
|
|||||||
Reference in New Issue
Block a user