diff --git a/jedi/evaluate/builtin.py b/jedi/evaluate/builtin.py index 7cd503de..5696d4fb 100644 --- a/jedi/evaluate/builtin.py +++ b/jedi/evaluate/builtin.py @@ -35,10 +35,12 @@ import inspect from jedi import common from jedi import debug from jedi.parser import Parser +from jedi.parser import fast from jedi import modules +from jedi import cache -class BuiltinModule(modules.CachedModule): +class BuiltinModule(object): """ This module is a parser for all builtin modules, which are programmed in C/C++. It should also work on third party modules. @@ -69,14 +71,32 @@ class BuiltinModule(modules.CachedModule): def __init__(self, path=None, name=None, sys_path=None): if sys_path is None: sys_path = modules.get_sys_path() + self.sys_path = list(sys_path) + if not name: name = os.path.basename(path) name = name.rpartition('.')[0] # cut file type (normally .so) - super(BuiltinModule, self).__init__(path=path, name=name) + self.name = name - self.sys_path = list(sys_path) + self.path = path and os.path.abspath(path) + self._parser = None self._module = None + @property + def parser(self): + """ get the parser lazy """ + if self._parser is None: + self._parser = cache.load_parser(self.path, self.name) \ + or self._load_module() + return self._parser + + def _load_module(self): + source = _generate_code(self.module, self._load_mixins()) + p = self.path or self.name + p = fast.FastParser(source, p) + cache.save_parser(self.path, self.name, p) + return p + @property def module(self): def load_module(name, path): @@ -118,10 +138,6 @@ class BuiltinModule(modules.CachedModule): load_module(name, path) return self._module - def _get_source(self): - """ Override this abstract method """ - return _generate_code(self.module, self._load_mixins()) - def _load_mixins(self): """ Load functions that are mixed in to the standard library. @@ -158,14 +174,14 @@ class BuiltinModule(modules.CachedModule): raise NotImplementedError() return funcs - try: - name = self.name - # sometimes there are stupid endings like `_sqlite3.cpython-32mu` - name = re.sub(r'\..*', '', name) + name = self.name + # sometimes there are stupid endings like `_sqlite3.cpython-32mu` + name = re.sub(r'\..*', '', name) - if name == '__builtin__' and not is_py3k: - name = 'builtins' - path = os.path.dirname(os.path.abspath(__file__)) + if name == '__builtin__' and not is_py3k: + name = 'builtins' + path = os.path.dirname(os.path.abspath(__file__)) + try: with open(os.path.join(path, 'mixin', name) + '.pym') as f: s = f.read() except IOError: diff --git a/jedi/evaluate/dynamic.py b/jedi/evaluate/dynamic.py index 9c728901..2a97f747 100644 --- a/jedi/evaluate/dynamic.py +++ b/jedi/evaluate/dynamic.py @@ -83,7 +83,7 @@ def get_directory_modules_for_name(mods, name): with open(path) as f: source = modules.source_to_unicode(f.read()) if name in source: - return modules.Module(path, source).parser.module + return modules.load_module(path, source) # skip non python modules mods = set(m for m in mods if m.path is None or m.path.endswith('.py')) diff --git a/jedi/evaluate/imports.py b/jedi/evaluate/imports.py index 1801d825..8a4b8b23 100644 --- a/jedi/evaluate/imports.py +++ b/jedi/evaluate/imports.py @@ -110,9 +110,9 @@ class ImportPath(pr.Base): if self._is_relative_import(): rel_path = self._get_relative_path() + '/__init__.py' - with common.ignored(IOError): - m = modules.Module(rel_path) - names += m.parser.module.get_defined_names() + if os.path.exists(rel_path): + m = modules.load_module(rel_path) + names += m.get_defined_names() else: if on_import_stmt and isinstance(scope, pr.Module) \ and scope.path.endswith('__init__.py'): diff --git a/jedi/modules.py b/jedi/modules.py index f8353eaf..d2ea84b0 100644 --- a/jedi/modules.py +++ b/jedi/modules.py @@ -31,14 +31,14 @@ from jedi import common def load_module(path=None, source=None, name=None): def load(source): - if path.endswith('.py'): + if path is not None and path.endswith('.py'): if source is None: with open(path) as f: source = f.read() else: # TODO refactoring remove from jedi.evaluate import builtin - return builtin.BuiltinModule(name=path).parser.module + return builtin.BuiltinModule(path, name).parser.module p = path or name p = fast.FastParser(source_to_unicode(source), p) cache.save_parser(path, name, p) @@ -61,9 +61,13 @@ class ModuleWithCursor(object): """ def __init__(self, path, source, position): super(ModuleWithCursor, self).__init__() - self.position = position + self.path = path and os.path.abspath(path) + self.name = None self.source = source + self.position = position self._path_until_cursor = None + self._line_cache = None + self._parser = None # this two are only used, because there is no nonlocal in Python 2 self._line_temp = None diff --git a/test/test_cache.py b/test/test_cache.py index aa8a3725..6ffe9e46 100644 --- a/test/test_cache.py +++ b/test/test_cache.py @@ -8,15 +8,16 @@ import pytest import jedi from jedi import settings, cache -from jedi.cache import ParserCacheItem, _ModulePickling +from jedi.cache import ParserCacheItem, ParserPickling -ModulePickling = _ModulePickling() +ParserPicklingCls = type(ParserPickling) +ParserPickling = ParserPicklingCls() def test_modulepickling_change_cache_dir(monkeypatch, tmpdir): """ - ModulePickling should not save old cache when cache_directory is changed. + ParserPickling should not save old cache when cache_directory is changed. See: `#168 `_ """ @@ -29,19 +30,19 @@ def test_modulepickling_change_cache_dir(monkeypatch, tmpdir): path_2 = 'fake path 2' monkeypatch.setattr(settings, 'cache_directory', dir_1) - ModulePickling.save_module(path_1, item_1) - cached = load_stored_item(ModulePickling, path_1, item_1) + ParserPickling.save_parser(path_1, item_1) + cached = load_stored_item(ParserPickling, path_1, item_1) assert cached == item_1.parser monkeypatch.setattr(settings, 'cache_directory', dir_2) - ModulePickling.save_module(path_2, item_2) - cached = load_stored_item(ModulePickling, path_1, item_1) + ParserPickling.save_parser(path_2, item_2) + cached = load_stored_item(ParserPickling, path_1, item_1) assert cached is None def load_stored_item(cache, path, item): """Load `item` stored at `path` in `cache`.""" - return cache.load_module(path, item.change_time - 1) + return cache.load_parser(path, item.change_time - 1) @pytest.mark.usefixtures("isolated_jedi_cache") @@ -49,13 +50,13 @@ def test_modulepickling_delete_incompatible_cache(): item = ParserCacheItem('fake parser') path = 'fake path' - cache1 = _ModulePickling() + cache1 = ParserPicklingCls() cache1.version = 1 - cache1.save_module(path, item) + cache1.save_parser(path, item) cached1 = load_stored_item(cache1, path, item) assert cached1 == item.parser - cache2 = _ModulePickling() + cache2 = ParserPicklingCls() cache2.version = 2 cached2 = load_stored_item(cache2, path, item) assert cached2 is None diff --git a/test/test_regression.py b/test/test_regression.py index 3152a0ec..e4b80634 100644 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -11,6 +11,7 @@ from .helpers import TestCase, cwd_at import jedi from jedi import Script from jedi import api +from jedi import modules from jedi.parser import Parser #jedi.set_debug_function() @@ -81,8 +82,7 @@ class TestRegression(TestCase): src1 = "def r(a): return a" # Other fictional modules in another place in the fs. src2 = 'from .. import setup; setup.r(1)' - # .parser to load the module - api.modules.Module(os.path.abspath(fname), src2).parser + modules.load_module(os.path.abspath(fname), src2) result = Script(src1, path='../setup.py').goto_definitions() assert len(result) == 1 assert result[0].description == 'class int'