From c25ee531a259a79cf919f5c4553fab315728987d Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Fri, 15 Mar 2013 14:36:12 +0100 Subject: [PATCH 01/28] AttributeError: 'Generator' object has no attribute 'start_pos' Running py.test raises this error: ```tb test/test_api_classes.py:50: in > @pytest.mark.parametrize('definition', make_definitions()) test/test_api_classes.py:38: in make_definitions > definitions += script.definition() jedi/api_classes.py:44: in wrapper > result = func(*args, **kwds) jedi/api.py:274: in definition > if not isinstance(s, imports.ImportPath._GlobalNamespace)]) jedi/api_classes.py:418: in __init__ > super(Definition, self).__init__(definition, definition.start_pos) E AttributeError: 'Generator' object has no attribute 'start_pos' ``` --- test/test_api_classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_api_classes.py b/test/test_api_classes.py index bcb7a722..747eabdc 100644 --- a/test/test_api_classes.py +++ b/test/test_api_classes.py @@ -32,7 +32,7 @@ def make_definitions(): definitions += api.defined_names(source) source += textwrap.dedent(""" - variable = sys or C or x or f or g or h""") + variable = sys or C or x or f or g or g() or h""") lines = source.splitlines() script = api.Script(source, len(lines), len('variable'), None) definitions += script.definition() From 6a2e535bcbb20d8331ba7bab749c03725aea7f25 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 16 Mar 2013 21:49:46 +0100 Subject: [PATCH 02/28] Add er.Generator.__getattr__ --- jedi/evaluate_representation.py | 3 +++ test/test_api_classes.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/jedi/evaluate_representation.py b/jedi/evaluate_representation.py index 99fded99..019e038b 100644 --- a/jedi/evaluate_representation.py +++ b/jedi/evaluate_representation.py @@ -773,6 +773,9 @@ class Generator(use_metaclass(cache.CachedMetaClass, pr.Base)): def parent(self): return self.func.parent + def __getattr__(self, name): + return getattr(self.func, name) + def __repr__(self): return "<%s of %s>" % (type(self).__name__, self.func) diff --git a/test/test_api_classes.py b/test/test_api_classes.py index 747eabdc..2da3e13e 100644 --- a/test/test_api_classes.py +++ b/test/test_api_classes.py @@ -50,4 +50,4 @@ def make_definitions(): @pytest.mark.parametrize('definition', make_definitions()) def test_basedefinition_type(definition): assert definition.type in ('module', 'class', 'instance', 'function', - 'statement', 'import', 'param') + 'generator', 'statement', 'import', 'param') From d1150cfdae2fc7b5d302cad0fcc3738fe2df7c5d Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 17 Mar 2013 13:56:54 +0100 Subject: [PATCH 03/28] List allowed attributes in Generator.__getattr__ --- jedi/evaluate_representation.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/jedi/evaluate_representation.py b/jedi/evaluate_representation.py index 019e038b..f9bbd2c9 100644 --- a/jedi/evaluate_representation.py +++ b/jedi/evaluate_representation.py @@ -769,11 +769,12 @@ class Generator(use_metaclass(cache.CachedMetaClass, pr.Base)): debug.warning('Tried to get array access on a generator', self) return [] - @property - def parent(self): - return self.func.parent - def __getattr__(self, name): + if name not in ['start_pos', 'end_pos', 'parent', 'get_imports', + 'asserts', 'doc', 'docstr', 'get_parent_until', 'get_code', + 'subscopes']: + raise AttributeError("Accessing %s of %s is not allowed." + % (self, name)) return getattr(self.func, name) def __repr__(self): From 8734e0efe2d7b6eff1dea1d3ca28651fec861517 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 19 Mar 2013 20:33:15 +0100 Subject: [PATCH 04/28] Use cPickle if available --- jedi/cache.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jedi/cache.py b/jedi/cache.py index 84d9ede7..4a729dc5 100644 --- a/jedi/cache.py +++ b/jedi/cache.py @@ -21,7 +21,10 @@ from __future__ import with_statement import time import os import sys -import pickle +try: + import cPickle as pickle +except: + import pickle from _compatibility import json import settings From f8d3339f2fdf9259cf263c35c443e987523fdd31 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 19 Mar 2013 21:32:01 +0100 Subject: [PATCH 05/28] Remove old version of FS cache automatically --- jedi/cache.py | 30 ++++++++++++++++++++++++++++-- test/conftest.py | 12 ++++++++++++ test/test_cache.py | 28 ++++++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/jedi/cache.py b/jedi/cache.py index e04b40e3..67fc84c5 100644 --- a/jedi/cache.py +++ b/jedi/cache.py @@ -25,6 +25,7 @@ try: import cPickle as pickle except: import pickle +import shutil from jedi._compatibility import json from jedi import settings @@ -219,6 +220,20 @@ def save_module(path, name, parser, pickling=True): class _ModulePickling(object): + + version = 1 + """ + Version number (integer) for file system cache. + + Increment this number when there are any incompatible changes in + parser representation classes. For example, the following changes + are regarded as incompatible. + + - Class name is changed. + - Class is moved to another module. + - Defined slot of the class is changed. + """ + def __init__(self): self.__index = None self.py_version = '%s.%s' % sys.version_info[:2] @@ -259,9 +274,16 @@ class _ModulePickling(object): if self.__index is None: try: with open(self._get_path('index.json')) as f: - self.__index = json.load(f) + data = json.load(f) except IOError: self.__index = {} + else: + # 0 means version is not defined (= always delete cache): + if data.get('version', 0) < self.version: + self.delete_cache() + self.__index = {} + else: + self.__index = data['index'] return self.__index def _remove_old_modules(self): @@ -272,10 +294,14 @@ class _ModulePickling(object): self._index # reload index def _flush_index(self): + data = {'version': self.version, 'index': self._index} with open(self._get_path('index.json'), 'w') as f: - json.dump(self._index, f) + json.dump(data, f) self.__index = None + def delete_cache(self): + shutil.rmtree(settings.cache_directory) + def _get_hashed_path(self, path): return self._get_path('%s_%s.pkl' % (self.py_version, hash(path))) diff --git a/test/conftest.py b/test/conftest.py index c5a00879..12a01b13 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -65,6 +65,18 @@ def pytest_generate_tests(metafunc): refactor.collect_dir_tests(base_dir, test_files)) +@pytest.fixture() +def isolated_jedi_cache(monkeypatch, tmpdir): + """ + Set `jedi.settings.cache_directory` to a temporary directory during test. + + Same as `clean_jedi_cache`, but create the temporary directory for + each test case (scope='function'). + """ + settings = base.jedi.settings + monkeypatch.setattr(settings, 'cache_directory', str(tmpdir)) + + @pytest.fixture(scope='session') def clean_jedi_cache(request): """ diff --git a/test/test_cache.py b/test/test_cache.py index 9f845fc2..c27dc705 100644 --- a/test/test_cache.py +++ b/test/test_cache.py @@ -1,3 +1,5 @@ +import pytest + from jedi import settings from jedi.cache import ParserCacheItem, _ModulePickling @@ -21,10 +23,32 @@ def test_modulepickling_change_cache_dir(monkeypatch, tmpdir): monkeypatch.setattr(settings, 'cache_directory', dir_1) ModulePickling.save_module(path_1, item_1) - cached = ModulePickling.load_module(path_1, item_1.change_time - 1) + cached = load_stored_item(ModulePickling, path_1, item_1) assert cached == item_1.parser monkeypatch.setattr(settings, 'cache_directory', dir_2) ModulePickling.save_module(path_2, item_2) - cached = ModulePickling.load_module(path_1, item_1.change_time - 1) + cached = load_stored_item(ModulePickling, 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) + + +@pytest.mark.usefixtures("isolated_jedi_cache") +def test_modulepickling_delete_incompatible_cache(): + item = ParserCacheItem('fake parser') + path = 'fake path' + + cache1 = _ModulePickling() + cache1.version = 1 + cache1.save_module(path, item) + cached1 = load_stored_item(cache1, path, item) + assert cached1 == item.parser + + cache2 = _ModulePickling() + cache2.version = 2 + cached2 = load_stored_item(cache2, path, item) + assert cached2 is None From 942b66ba5bf0abaefb9a602a61e46c9e9f337ab9 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 19 Mar 2013 21:55:57 +0100 Subject: [PATCH 06/28] Separate directory for each Python version --- jedi/cache.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/jedi/cache.py b/jedi/cache.py index 67fc84c5..933e38af 100644 --- a/jedi/cache.py +++ b/jedi/cache.py @@ -240,7 +240,7 @@ class _ModulePickling(object): def load_module(self, path, original_changed_time): try: - pickle_changed_time = self._index[self.py_version][path] + pickle_changed_time = self._index[path] except KeyError: return None if original_changed_time is not None \ @@ -258,10 +258,10 @@ class _ModulePickling(object): def save_module(self, path, parser_cache_item): self.__index = None try: - files = self._index[self.py_version] + files = self._index except KeyError: files = {} - self._index[self.py_version] = files + self._index = files with open(self._get_hashed_path(path), 'wb') as f: pickle.dump(parser_cache_item, f, pickle.HIGHEST_PROTOCOL) @@ -300,16 +300,19 @@ class _ModulePickling(object): self.__index = None def delete_cache(self): - shutil.rmtree(settings.cache_directory) + shutil.rmtree(self._cache_directory()) def _get_hashed_path(self, path): - return self._get_path('%s_%s.pkl' % (self.py_version, hash(path))) + return self._get_path('%s.pkl' % hash(path)) def _get_path(self, file): - dir = settings.cache_directory + dir = self._cache_directory() if not os.path.exists(dir): os.makedirs(dir) - return dir + os.path.sep + file + return os.path.join(dir, file) + + def _cache_directory(self): + return os.path.join(settings.cache_directory, self.py_version) # is a singleton From 27d007adba2315b19364fdc38a05272cbaad8f0f Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 19 Mar 2013 21:56:40 +0100 Subject: [PATCH 07/28] Compare version using "!=" Cache is not forward compatible. --- jedi/cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/cache.py b/jedi/cache.py index 933e38af..2b744337 100644 --- a/jedi/cache.py +++ b/jedi/cache.py @@ -279,7 +279,7 @@ class _ModulePickling(object): self.__index = {} else: # 0 means version is not defined (= always delete cache): - if data.get('version', 0) < self.version: + if data.get('version', 0) != self.version: self.delete_cache() self.__index = {} else: From aca1cbfa4b9dc428370415138a8b4945c4a3f3a1 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Wed, 20 Mar 2013 16:44:17 +0100 Subject: [PATCH 08/28] Use "jedi/cpython-27" instead of "jedi/2.7" for FS cache --- jedi/cache.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/jedi/cache.py b/jedi/cache.py index 2b744337..20805845 100644 --- a/jedi/cache.py +++ b/jedi/cache.py @@ -236,7 +236,16 @@ class _ModulePickling(object): def __init__(self): self.__index = None - self.py_version = '%s.%s' % sys.version_info[:2] + self.py_tag = 'cpython-%s%s' % sys.version_info[:2] + """ + Short name for distinguish Python implementations and versions. + + It's like `sys.implementation.cache_tag` but for Python < 3.3 + we generate something similar. See: + http://docs.python.org/3/library/sys.html#sys.implementation + + .. todo:: Detect interpreter (e.g., PyPy). + """ def load_module(self, path, original_changed_time): try: @@ -312,7 +321,7 @@ class _ModulePickling(object): return os.path.join(dir, file) def _cache_directory(self): - return os.path.join(settings.cache_directory, self.py_version) + return os.path.join(settings.cache_directory, self.py_tag) # is a singleton From d481a7aae49649d37d00fb2c85f161810f5d0fbc Mon Sep 17 00:00:00 2001 From: Aldo Stracquadanio Date: Fri, 22 Mar 2013 13:39:07 +0000 Subject: [PATCH 09/28] Created find_module helper to handle compatibility with python 3.3 Moved package checking logic in follow_str function Created find_module compatibility helper method Conditional implementation of load_module for python 3.3 --- jedi/_compatibility.py | 43 +++++++++++++++++++++++++++++++++++++++++- jedi/imports.py | 20 ++++++++++++++------ 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/jedi/_compatibility.py b/jedi/_compatibility.py index 9a704304..454511dd 100644 --- a/jedi/_compatibility.py +++ b/jedi/_compatibility.py @@ -7,11 +7,52 @@ Most of the code here is necessary to support Python 2.5. Once this dependency will be dropped, we'll get rid of most code. """ import sys +import imp +import os +import io +try: + import importlib +except: + pass is_py3k = sys.hexversion >= 0x03000000 - +is_py33 = sys.hexversion >= 0x03030000 is_py25 = sys.hexversion < 0x02060000 +if is_py33: + def find_module(string, path=None): + importing = None + if path is not None: + importing = importlib.find_loader(string, path) + else: + importing = importlib.find_loader(string) + + returning = (None, None, None) + try: + filename = importing.get_filename(string) + if filename and os.path.exists(filename): + returning = (open(filename, 'U'), filename, False) + else: + returning = (None, filename, False) + except AttributeError: + if importing is None: + returning = (None, None, False) + else: + returning = (None, importing.load_module(string).__name__, False) + + return returning +else: + def find_module(string, path=None): + importing = None + if path is None: + importing = imp.find_module(string) + else: + importing = imp.find_module(string, path) + + returning = (importing[0], importing[1], importing[2][2] == imp.PKG_DIRECTORY) + + return returning + # next was defined in python 2.6, in python 3 obj.next won't be possible # anymore try: diff --git a/jedi/imports.py b/jedi/imports.py index 72a3d0f6..a006f3e7 100644 --- a/jedi/imports.py +++ b/jedi/imports.py @@ -21,6 +21,7 @@ import imp import sys import itertools +from jedi._compatibility import find_module from jedi import modules from jedi import debug from jedi import parsing_representation as pr @@ -238,20 +239,22 @@ class ImportPath(pr.Base): global imports_processed imports_processed += 1 + importing = None if path is not None: - return imp.find_module(string, [path]) + importing = find_module(string, [path]) else: debug.dbg('search_module', 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_mod, sys.path try: - i = imp.find_module(string) + importing = find_module(string) except ImportError: sys.path = temp raise sys.path = temp - return i + + return importing if self.file_path: sys_path_mod = list(self.sys_path_with_modifications()) @@ -259,6 +262,9 @@ class ImportPath(pr.Base): else: sys_path_mod = list(modules.get_sys_path()) + def module_not_found(): + raise ModuleNotFound('The module you searched has not been found') + current_namespace = (None, None, None) # now execute those paths rest = [] @@ -277,12 +283,14 @@ class ImportPath(pr.Base): if current_namespace[1]: rest = self.import_path[i:] else: - raise ModuleNotFound( - 'The module you searched has not been found') + module_not_found() + + if current_namespace == (None, None, False): + module_not_found() sys_path_mod.pop(0) # TODO why is this here? path = current_namespace[1] - is_package_directory = current_namespace[2][2] == imp.PKG_DIRECTORY + is_package_directory = current_namespace[2] f = None if is_package_directory or current_namespace[0]: From de849fbe8a1e08650f9556404a1959249375d8d6 Mon Sep 17 00:00:00 2001 From: Aldo Stracquadanio Date: Fri, 22 Mar 2013 19:11:44 +0000 Subject: [PATCH 10/28] Fixed follow_definition test --- jedi/_compatibility.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jedi/_compatibility.py b/jedi/_compatibility.py index 454511dd..dff2e42d 100644 --- a/jedi/_compatibility.py +++ b/jedi/_compatibility.py @@ -24,6 +24,8 @@ if is_py33: importing = None if path is not None: importing = importlib.find_loader(string, path) + if importing is None: + raise ImportError else: importing = importlib.find_loader(string) @@ -39,7 +41,7 @@ if is_py33: returning = (None, None, False) else: returning = (None, importing.load_module(string).__name__, False) - + return returning else: def find_module(string, path=None): From be8ef33b695ed3a37972d9caff9a8e1c0c00bb61 Mon Sep 17 00:00:00 2001 From: Aldo Stracquadanio Date: Sat, 23 Mar 2013 23:07:17 +0000 Subject: [PATCH 11/28] Fixed caching issues Fixed exception raised during cache reading Switched cache hashing to hashlib In python 3.3 the hash function is returning different hashes during different executions of the application. --- jedi/cache.py | 3 ++- jedi/parsing_representation.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/jedi/cache.py b/jedi/cache.py index 20805845..2b671700 100644 --- a/jedi/cache.py +++ b/jedi/cache.py @@ -21,6 +21,7 @@ from __future__ import with_statement import time import os import sys +import hashlib try: import cPickle as pickle except: @@ -312,7 +313,7 @@ class _ModulePickling(object): shutil.rmtree(self._cache_directory()) def _get_hashed_path(self, path): - return self._get_path('%s.pkl' % hash(path)) + return self._get_path('%s.pkl' % hashlib.md5(path.encode("utf-8")).hexdigest()) def _get_path(self, file): dir = self._cache_directory() diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index 65210124..d1ea83ff 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -560,7 +560,7 @@ class Flow(Scope): @parent.setter def parent(self, value): self._parent = value - if self.next: + if hasattr(self, "next") and self.next: self.next.parent = value def get_code(self, first_indent=False, indention=' '): From 3ef564847d460c782de2cf2cccd16e9a8eba5567 Mon Sep 17 00:00:00 2001 From: Aldo Stracquadanio Date: Sat, 23 Mar 2013 23:16:06 +0000 Subject: [PATCH 12/28] Simplified code for readability Splitted import compatibility function definition for better readability Simplified code for python 3.3 load_module implementation --- jedi/_compatibility.py | 73 ++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/jedi/_compatibility.py b/jedi/_compatibility.py index dff2e42d..a3489869 100644 --- a/jedi/_compatibility.py +++ b/jedi/_compatibility.py @@ -19,41 +19,52 @@ is_py3k = sys.hexversion >= 0x03000000 is_py33 = sys.hexversion >= 0x03030000 is_py25 = sys.hexversion < 0x02060000 -if is_py33: - def find_module(string, path=None): - importing = None - if path is not None: - importing = importlib.find_loader(string, path) - if importing is None: - raise ImportError +def find_module_py33(string, path=None): + returning = (None, None, None) + importing = None + if path is not None: + importing = importlib.find_loader(string, path) + else: + importing = importlib.find_loader(string) + + if importing is None: + raise ImportError + + try: + filename = importing.get_filename(string) + if filename and os.path.exists(filename): + returning = (open(filename, 'U'), filename, False) else: - importing = importlib.find_loader(string) + returning = (None, filename, False) + except AttributeError: + returning = (None, importing.load_module(string).__name__, False) - returning = (None, None, None) - try: - filename = importing.get_filename(string) - if filename and os.path.exists(filename): - returning = (open(filename, 'U'), filename, False) - else: - returning = (None, filename, False) - except AttributeError: - if importing is None: - returning = (None, None, False) - else: - returning = (None, importing.load_module(string).__name__, False) - - return returning -else: - def find_module(string, path=None): - importing = None - if path is None: - importing = imp.find_module(string) - else: - importing = imp.find_module(string, path) + return returning - returning = (importing[0], importing[1], importing[2][2] == imp.PKG_DIRECTORY) +def find_module_pre_py33(string, path=None): + importing = None + if path is None: + importing = imp.find_module(string) + else: + importing = imp.find_module(string, path) - return returning + return (importing[0], importing[1], importing[2][2] == imp.PKG_DIRECTORY) + +def find_module(string, path=None): + """Provides information about a module. + + This function isolates the differences in importing libraries introduced with + python 3.3 on; it gets a module name and optionally a path. It will return a + tuple containin an open file for the module (if not builtin), the filename + or the name of the module if it is a builtin one and a boolean indicating + ig the module is contained in a package.""" + returning = (None, None, None) + if is_py33: + returning = find_module_py33(string, path) + else: + returning = find_module_pre_py33(string, path) + + return returning # next was defined in python 2.6, in python 3 obj.next won't be possible # anymore From 07ec134bc92736aafdf8b1203297431ca54eebd2 Mon Sep 17 00:00:00 2001 From: Aldo Stracquadanio Date: Sun, 24 Mar 2013 10:14:14 +0000 Subject: [PATCH 13/28] Adding python 3.3 to test environment, mani fixes Added python 3.3 to test-suite Removed unused import Removed unused import Migrated to EAFP for attribute checking Bumped version of ModulePickling for migration to hashlib Added py33 environment to tox Fixed issue with package importing on python 3.3 --- .travis.yml | 1 + jedi/_compatibility.py | 12 +++++++----- jedi/cache.py | 2 +- jedi/imports.py | 6 ++---- jedi/parsing_representation.py | 4 +++- tox.ini | 2 +- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index e1529542..a85a4d06 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ env: - TOXENV=py26 - TOXENV=py27 - TOXENV=py32 + - TOXENV=py33 install: - pip install --quiet --use-mirrors tox script: diff --git a/jedi/_compatibility.py b/jedi/_compatibility.py index a3489869..331c1f11 100644 --- a/jedi/_compatibility.py +++ b/jedi/_compatibility.py @@ -9,7 +9,6 @@ will be dropped, we'll get rid of most code. import sys import imp import os -import io try: import importlib except: @@ -31,11 +30,14 @@ def find_module_py33(string, path=None): raise ImportError try: - filename = importing.get_filename(string) - if filename and os.path.exists(filename): - returning = (open(filename, 'U'), filename, False) + if (importing.is_package(string)): + returning = (None, os.path.dirname(importing.path), True) else: - returning = (None, filename, False) + filename = importing.get_filename(string) + if filename and os.path.exists(filename): + returning = (open(filename, 'U'), filename, False) + else: + returning = (None, filename, False) except AttributeError: returning = (None, importing.load_module(string).__name__, False) diff --git a/jedi/cache.py b/jedi/cache.py index 2b671700..5d76f24e 100644 --- a/jedi/cache.py +++ b/jedi/cache.py @@ -222,7 +222,7 @@ def save_module(path, name, parser, pickling=True): class _ModulePickling(object): - version = 1 + version = 2 """ Version number (integer) for file system cache. diff --git a/jedi/imports.py b/jedi/imports.py index a006f3e7..db72925f 100644 --- a/jedi/imports.py +++ b/jedi/imports.py @@ -5,9 +5,8 @@ any actual importing done. This module is about finding modules in the filesystem. This can be quite tricky sometimes, because Python imports are not always that simple. -Currently the import process uses ``imp`` to find modules. In the future, it's -a goal to use ``importlib`` for this purpose. There's a `pull request -`_ for that. +This module uses imp for python up to 3.2 and importlib for python 3.3 on; the +correct implementation is delegated to _compatibility. This module also supports import autocompletion, which means to complete statements like ``from datetim`` (curser at the end would return ``datetime``). @@ -17,7 +16,6 @@ from __future__ import with_statement import os import pkgutil -import imp import sys import itertools diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index d1ea83ff..311f38ab 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -560,8 +560,10 @@ class Flow(Scope): @parent.setter def parent(self, value): self._parent = value - if hasattr(self, "next") and self.next: + try: self.next.parent = value + except AttributeError: + return def get_code(self, first_indent=False, indention=' '): stmts = [] diff --git a/tox.ini b/tox.ini index 6dc16930..8fa50614 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py25, py26, py27, py32 +envlist = py25, py26, py27, py32, py33 [testenv] deps = pytest From 0b67a08e48ee4c1430a3557f9bbccc150547aacc Mon Sep 17 00:00:00 2001 From: Aldo Stracquadanio Date: Mon, 25 Mar 2013 19:30:58 +0000 Subject: [PATCH 14/28] Using PathFinder rather than find_loader to correctly handle paths Using PathFinder rather than find_loader to correctly handle from ... import ... Moved away from find_loader in favour of PathFinder also when using sys.path --- jedi/_compatibility.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jedi/_compatibility.py b/jedi/_compatibility.py index 331c1f11..e15ba463 100644 --- a/jedi/_compatibility.py +++ b/jedi/_compatibility.py @@ -22,9 +22,11 @@ def find_module_py33(string, path=None): returning = (None, None, None) importing = None if path is not None: - importing = importlib.find_loader(string, path) + importing = importlib.machinery.PathFinder.find_module(string, path) else: - importing = importlib.find_loader(string) + importing = importlib.machinery.PathFinder.find_module(string, sys.path) + if importing is None: + importing = importlib.find_loader(string) if importing is None: raise ImportError From 124595de6e503d5e840a43d893c1018190fd8413 Mon Sep 17 00:00:00 2001 From: Aldo Stracquadanio Date: Wed, 27 Mar 2013 00:19:39 +0000 Subject: [PATCH 15/28] Making it nicer Fixed typo in docstring and added some comments in find_module_py33 Removed a test that is not compatible with python 3.3 Better variable names in find_module implementation(s) Removed variable assignation in favor of direct return statement --- jedi/_compatibility.py | 48 +++++++++++++++++++++--------------------- test/completion/std.py | 9 -------- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/jedi/_compatibility.py b/jedi/_compatibility.py index e15ba463..29aef154 100644 --- a/jedi/_compatibility.py +++ b/jedi/_compatibility.py @@ -19,40 +19,43 @@ is_py33 = sys.hexversion >= 0x03030000 is_py25 = sys.hexversion < 0x02060000 def find_module_py33(string, path=None): - returning = (None, None, None) - importing = None + mod_info = (None, None, None) + loader = None if path is not None: - importing = importlib.machinery.PathFinder.find_module(string, path) + # Check for the module in the specidied path + loader = importlib.machinery.PathFinder.find_module(string, path) else: - importing = importlib.machinery.PathFinder.find_module(string, sys.path) - if importing is None: - importing = importlib.find_loader(string) + # Check for the module in sys.path + loader = importlib.machinery.PathFinder.find_module(string, sys.path) + if loader is None: + # Fallback to find builtins + loader = importlib.find_loader(string) - if importing is None: + if loader is None: raise ImportError try: - if (importing.is_package(string)): - returning = (None, os.path.dirname(importing.path), True) + if (loader.is_package(string)): + mod_info = (None, os.path.dirname(loader.path), True) else: - filename = importing.get_filename(string) + filename = loader.get_filename(string) if filename and os.path.exists(filename): - returning = (open(filename, 'U'), filename, False) + mod_info = (open(filename, 'U'), filename, False) else: - returning = (None, filename, False) + mod_info = (None, filename, False) except AttributeError: - returning = (None, importing.load_module(string).__name__, False) + mod_info = (None, loader.load_module(string).__name__, False) - return returning + return mod_info def find_module_pre_py33(string, path=None): - importing = None + mod_info = None if path is None: - importing = imp.find_module(string) + mod_info = imp.find_module(string) else: - importing = imp.find_module(string, path) + mod_info = imp.find_module(string, path) - return (importing[0], importing[1], importing[2][2] == imp.PKG_DIRECTORY) + return (mod_info[0], mod_info[1], mod_info[2][2] == imp.PKG_DIRECTORY) def find_module(string, path=None): """Provides information about a module. @@ -61,14 +64,11 @@ def find_module(string, path=None): python 3.3 on; it gets a module name and optionally a path. It will return a tuple containin an open file for the module (if not builtin), the filename or the name of the module if it is a builtin one and a boolean indicating - ig the module is contained in a package.""" - returning = (None, None, None) + if the module is contained in a package.""" if is_py33: - returning = find_module_py33(string, path) + return find_module_py33(string, path) else: - returning = find_module_pre_py33(string, path) - - return returning + return find_module_pre_py33(string, path) # next was defined in python 2.6, in python 3 obj.next won't be possible # anymore diff --git a/test/completion/std.py b/test/completion/std.py index 0eec6367..c4ed8c5a 100644 --- a/test/completion/std.py +++ b/test/completion/std.py @@ -89,12 +89,3 @@ def huhu(db): """ #? sqlite3.Connection() db - -# ----------------- -# various regression tests -# ----------------- - -#62 -import threading -#? ['_Verbose', '_VERBOSE'] -threading._Verbose From 492a6c5704cda72368af017ccf003ad12eb00428 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Fri, 5 Apr 2013 11:18:53 +0200 Subject: [PATCH 16/28] Added specific "Programming Language" trove classifiers (fixes #194) --- setup.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bc9c1fb1..bb0ee4e9 100755 --- a/setup.py +++ b/setup.py @@ -31,7 +31,13 @@ setup(name='jedi', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 'Operating System :: OS Independent', - 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.5', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Text Editors :: Integrated Development Environments (IDE)', 'Topic :: Utilities', From 327a1ded177650330e0a581e71cc1100bc3779a2 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Wed, 10 Apr 2013 15:47:44 +0200 Subject: [PATCH 17/28] Note about testing in README --- README.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.rst b/README.rst index dc32f115..b00ea40f 100644 --- a/README.rst +++ b/README.rst @@ -86,3 +86,15 @@ API for IDEs It's very easy to create an editor plugin that uses Jedi. See https://jedi.readthedocs.org/en/latest/docs/plugin-api.html for more information. + + +Testing +======= + +To run the tests:: + + pip install tox pytest + PIP_INSECURE=t tox + +Tests are also run automatically on `Travis CI +`_. From be7cecef009cb33adcf493c175d690dad45ead05 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Wed, 10 Apr 2013 16:04:56 +0200 Subject: [PATCH 18/28] Typos in docs --- docs/docs/history.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/docs/history.rst b/docs/docs/history.rst index df2ecc69..227cd47d 100644 --- a/docs/docs/history.rst +++ b/docs/docs/history.rst @@ -5,7 +5,7 @@ A little history The Star Wars Jedi are awesome. My Jedi software tries to imitate a little bit of the precognition the Jedi have. There's even an awesome `scene -`_ of Monty Python Jedi's :-). +`_ of Monty Python Jedis :-). But actually the name hasn't so much to do with Star Wars. It's part of my second name. @@ -13,13 +13,13 @@ second name. After I explained Guido van Rossum, how some parts of my auto-completion work, he said (we drank a beer or two): - *Oh, that worries me* + *"Oh, that worries me..."* When it's finished, I hope he'll like it :-) -I actually started Jedi, because there were no good solutions available for -VIM. Most auto-completions just didn't work well. The only good solution was -PyCharm. I just like my good old VIM. Rope was never really intended to be an +I actually started Jedi, because there were no good solutions available for VIM. +Most auto-completions just didn't work well. The only good solution was PyCharm. +But I like my good old VIM. Rope was never really intended to be an auto-completion (and also I really hate project folders for my Python scripts). It's more of a refactoring suite. So I decided to do my own version of a completion, which would execute non-dangerous code. But I soon realized, that From 798136787f57a39fdb1f78b299a5e15f743422bf Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Wed, 10 Apr 2013 16:30:32 +0200 Subject: [PATCH 19/28] Configuration to run coveralls on py33 --- .gitignore | 4 +++- .travis.yml | 3 ++- tox.ini | 7 ++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index ad9b77e4..4755f455 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,11 @@ *~ *.swp *.swo +*.pyc .ropeproject .tox -*.pyc +.coveralls.yml +.coverage /build/ /docs/_build/ /dist/ diff --git a/.travis.yml b/.travis.yml index a85a4d06..8c29ecb1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ env: - TOXENV=py32 - TOXENV=py33 install: - - pip install --quiet --use-mirrors tox + - pip install --quiet --use-mirrors tox coveralls script: - tox + - if [ $TOXENV == "py33" ]; then coveralls; fi diff --git a/tox.ini b/tox.ini index 8fa50614..c6bf7e98 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ envlist = py25, py26, py27, py32, py33 deps = pytest commands = - py.test [] + py.test jedi [testenv:py25] deps = simplejson @@ -14,3 +14,8 @@ deps = deps = unittest2 {[testenv]deps} +[testenv:py33] +deps = + pytest-cov +commands = + py.test --cov jedi From af22409059e0cb420628c3f97a0f9945d37ea087 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Wed, 10 Apr 2013 17:08:20 +0200 Subject: [PATCH 20/28] Separate tox env for coverage check --- .travis.yml | 3 ++- tox.ini | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8c29ecb1..333bcc1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,9 @@ env: - TOXENV=py27 - TOXENV=py32 - TOXENV=py33 + - TOXENV=cov install: - pip install --quiet --use-mirrors tox coveralls script: - tox - - if [ $TOXENV == "py33" ]; then coveralls; fi + - if [ $TOXENV == "cov" ]; then coveralls; fi diff --git a/tox.ini b/tox.ini index c6bf7e98..205c8594 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ envlist = py25, py26, py27, py32, py33 deps = pytest commands = - py.test jedi + py.test [] [testenv:py25] deps = simplejson @@ -14,8 +14,9 @@ deps = deps = unittest2 {[testenv]deps} -[testenv:py33] +[testenv:cov] deps = pytest-cov + {[testenv]deps} commands = - py.test --cov jedi + py.test --cov jedi [] From 8e4a76ca1b41a1fd4d87140882b83d178563b69c Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Wed, 10 Apr 2013 17:09:36 +0200 Subject: [PATCH 21/28] Allow TOXENV=cov to fail in travis-ci --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 333bcc1b..03af3394 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,10 @@ env: - TOXENV=py32 - TOXENV=py33 - TOXENV=cov +matrix: + allow_failures: + - env: + - TOXENV=cov install: - pip install --quiet --use-mirrors tox coveralls script: From 658bcb6421770ab0e6a622bce2da0e5962224eb9 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Wed, 10 Apr 2013 17:23:50 +0200 Subject: [PATCH 22/28] Install coveralls only when needed --- .travis.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 03af3394..57ea5c5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,11 @@ matrix: - env: - TOXENV=cov install: - - pip install --quiet --use-mirrors tox coveralls + - pip install --quiet --use-mirrors tox script: - tox - - if [ $TOXENV == "cov" ]; then coveralls; fi +after_script: + - if [ $TOXENV == "cov" ]; then + pip install --quiet --use-mirrors coveralls + coveralls; + fi From 944b3200a0ffca18d80159fc919756962f3ca990 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Wed, 10 Apr 2013 17:28:15 +0200 Subject: [PATCH 23/28] Fix allow_failures syntax allow_failures.env requires a string when matrix/global is not used? Also, indent is changed to fit with other entries. --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 57ea5c5e..51d30e2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,8 @@ env: - TOXENV=py33 - TOXENV=cov matrix: - allow_failures: - - env: - - TOXENV=cov + allow_failures: + - env: TOXENV=cov install: - pip install --quiet --use-mirrors tox script: From 26cabd581c06782c62ef4419bf7de966e946e780 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Wed, 10 Apr 2013 22:20:37 +0200 Subject: [PATCH 24/28] Added coverage report to README --- README.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.rst b/README.rst index b00ea40f..dcc654dd 100644 --- a/README.rst +++ b/README.rst @@ -6,6 +6,11 @@ Jedi - an awesome autocompletion library for Python :target: http://travis-ci.org/davidhalter/jedi :alt: Travis-CI build status +.. image:: https://coveralls.io/repos/davidhalter/jedi/badge.png?branch=master + :target: https://coveralls.io/r/davidhalter/jedi + :alt: Coverage Status + + Jedi is an autocompletion tool for Python that can be used in IDEs/editors. Jedi works. Jedi is fast. It understands all of the basic Python syntax elements including many builtin functions. From 1a41f92a0dde1b80b82f939982bedb15cc99b926 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Wed, 10 Apr 2013 23:41:25 +0200 Subject: [PATCH 25/28] Added .coveragerc (refs #196) --- .coveragerc | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..c48bafc2 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,18 @@ +[run] +omit = + jedi/_compatibility.py + +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: From fb96f9bf3d1c8029ae032ffcf8b532329d57895d Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Wed, 10 Apr 2013 23:52:57 +0200 Subject: [PATCH 26/28] Missing semicolon in .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 51d30e2d..16d38fab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,6 @@ script: - tox after_script: - if [ $TOXENV == "cov" ]; then - pip install --quiet --use-mirrors coveralls + pip install --quiet --use-mirrors coveralls; coveralls; fi From 24573c5ef2ee5f64af5a999b16ffc7db49ea993f Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Tue, 16 Apr 2013 22:51:41 +0200 Subject: [PATCH 27/28] Extended testing information in README --- README.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index dcc654dd..9ed17efe 100644 --- a/README.rst +++ b/README.rst @@ -96,10 +96,20 @@ information. Testing ======= -To run the tests:: +The test suite depends on ``tox`` and ``pytest``:: pip install tox pytest + +To run the tests for all supported Python versions:: + PIP_INSECURE=t tox +If you want to test only a specific Python version (e.g. Python 2.7), it's as +easy as :: + + tox -e py27 + +The ``PIP_INSECURE=t`` env variable is only needed for the ``py25`` target. + Tests are also run automatically on `Travis CI `_. From f98b0d7b6f36096b96d209498dfab657157c446f Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Wed, 17 Apr 2013 23:28:37 +0200 Subject: [PATCH 28/28] New "with ignored(*exceptions)" context manager --- jedi/api_classes.py | 10 ++++------ jedi/cache.py | 9 +++------ jedi/common.py | 10 ++++++++++ jedi/dynamic.py | 5 ++--- jedi/evaluate.py | 21 ++++++--------------- jedi/evaluate_representation.py | 10 ++++------ jedi/helpers.py | 11 +++++------ jedi/imports.py | 10 +++------- jedi/keywords.py | 7 ++++--- jedi/modules.py | 13 ++++--------- jedi/parsing.py | 5 ++--- jedi/parsing_representation.py | 6 ++---- jedi/refactoring.py | 7 ++----- 13 files changed, 51 insertions(+), 73 deletions(-) diff --git a/jedi/api_classes.py b/jedi/api_classes.py index 842fe195..77d4a489 100644 --- a/jedi/api_classes.py +++ b/jedi/api_classes.py @@ -3,6 +3,7 @@ The :mod:`api_classes` module contains the return classes of the API. These classes are the much bigger part of the whole API, because they contain the interesting information about completion and goto operations. """ +from __future__ import with_statement import re import os @@ -11,6 +12,7 @@ import functools from jedi._compatibility import unicode, next from jedi import settings +from jedi import common from jedi import parsing_representation as pr from jedi import cache import keywords @@ -144,10 +146,8 @@ class BaseDefinition(object): if not isinstance(self.definition, keywords.Keyword): par = self.definition while par is not None: - try: + with common.ignored(AttributeError): path.insert(0, par.name) - except AttributeError: - pass par = par.parent return path @@ -295,10 +295,8 @@ class BaseDefinition(object): if not path: return None # for keywords the path is empty - try: + with common.ignored(KeyError): path[0] = self._mapping[path[0]] - except KeyError: - pass for key, repl in self._tuple_mapping.items(): if tuple(path[:len(key)]) == key: path = [repl] + path[len(key):] diff --git a/jedi/cache.py b/jedi/cache.py index 5d76f24e..e2c0c236 100644 --- a/jedi/cache.py +++ b/jedi/cache.py @@ -30,6 +30,7 @@ import shutil from jedi._compatibility import json from jedi import settings +from jedi import common from jedi import debug # memoize caches will be deleted after every action @@ -148,12 +149,10 @@ def cache_function_definition(stmt): def cache_star_import(func): def wrapper(scope, *args, **kwargs): - try: + with common.ignored(KeyError): mods = star_import_cache[scope] if mods[0] + settings.star_import_cache_validity > time.time(): return mods[1] - except KeyError: - pass # cache is too old and therefore invalid or not available invalidate_star_import_cache(scope) mods = func(scope, *args, **kwargs) @@ -165,15 +164,13 @@ def cache_star_import(func): def invalidate_star_import_cache(module, only_main=False): """ Important if some new modules are being reparsed """ - try: + with common.ignored(KeyError): t, mods = star_import_cache[module] del star_import_cache[module] for m in mods: invalidate_star_import_cache(m, only_main=True) - except KeyError: - pass if not only_main: # We need a list here because otherwise the list is being changed diff --git a/jedi/common.py b/jedi/common.py index 08eadd6b..ebd1f25d 100644 --- a/jedi/common.py +++ b/jedi/common.py @@ -163,3 +163,13 @@ def indent_block(text, indention=' '): text = text[:-1] lines = text.split('\n') return '\n'.join(map(lambda s: indention + s, lines)) + temp + + +@contextlib.contextmanager +def ignored(*exceptions): + """Context manager that ignores all of the specified exceptions. This will + be in the standard library starting with Python 3.4.""" + try: + yield + except exceptions: + pass diff --git a/jedi/dynamic.py b/jedi/dynamic.py index 8cc0b3eb..bd87eeb2 100644 --- a/jedi/dynamic.py +++ b/jedi/dynamic.py @@ -59,6 +59,7 @@ from jedi import cache from jedi import parsing_representation as pr from jedi import modules from jedi import settings +from jedi import common from jedi import debug from jedi import fast_parser import api_classes @@ -487,10 +488,8 @@ def related_name_add_import_modules(definitions, search_name): for d in definitions: if isinstance(d.parent, pr.Import): s = imports.ImportPath(d.parent, direct_resolve=True) - try: + with common.ignored(IndexError): new.add(s.follow(is_goto=True)[0]) - except IndexError: - pass return set(definitions) | new diff --git a/jedi/evaluate.py b/jedi/evaluate.py index c76bbb27..87d59afa 100644 --- a/jedi/evaluate.py +++ b/jedi/evaluate.py @@ -68,6 +68,7 @@ backtracking algorithm. .. todo:: nonlocal statement, needed or can be ignored? (py3k) """ +from __future__ import with_statement import sys import itertools @@ -429,11 +430,9 @@ def find_name(scope, name_str, position=None, search_global=False, if isinstance(scope, (er.Instance, er.Class)) \ and hasattr(r, 'get_descriptor_return'): # handle descriptors - try: + with common.ignored(KeyError): res_new += r.get_descriptor_return(scope) continue - except KeyError: - pass res_new.append(r) return res_new @@ -462,19 +461,15 @@ def check_getattr(inst, name_str): # str is important to lose the NamePart! module = builtin.Builtin.scope name = pr.Call(module, str(name_str), pr.Call.STRING, (0, 0), inst) - try: + with common.ignored(KeyError): result = inst.execute_subscope_by_name('__getattr__', [name]) - except KeyError: - pass if not result: # this is a little bit special. `__getattribute__` is executed # before anything else. But: I know no use case, where this # could be practical and the jedi would return wrong types. If # you ever have something, let me know! - try: + with common.ignored(KeyError): result = inst.execute_subscope_by_name('__getattribute__', [name]) - except KeyError: - pass return result @@ -536,10 +531,8 @@ def assign_tuples(tup, results, seek_name): debug.warning("invalid tuple lookup %s of result %s in %s" % (tup, results, seek_name)) else: - try: + with common.ignored(IndexError): types += func(index) - except IndexError: - pass return types result = [] @@ -648,11 +641,9 @@ def follow_call_list(call_list, follow_array=False): call = next(calls_iterator) except StopIteration: break - try: + with common.ignored(AttributeError): if str(call.name) == 'else': break - except AttributeError: - pass continue result += follow_call(call) elif call == '*': diff --git a/jedi/evaluate_representation.py b/jedi/evaluate_representation.py index 3b5c972f..d5402e4b 100644 --- a/jedi/evaluate_representation.py +++ b/jedi/evaluate_representation.py @@ -9,6 +9,8 @@ instantiated. This class represents these cases. So, why is there also a ``Class`` class here? Well, there are decorators and they change classes in Python 3. """ +from __future__ import with_statement + import copy import itertools @@ -61,10 +63,8 @@ class Instance(use_metaclass(cache.CachedMetaClass, Executable)): else: # need to execute the __init__ function, because the dynamic param # searching needs it. - try: + with common.ignored(KeyError): self.execute_subscope_by_name('__init__', self.var_args) - except KeyError: - pass # Generated instances are classes that are just generated by self # (No var_args) used. self.is_generated = False @@ -804,10 +804,8 @@ class Array(use_metaclass(cache.CachedMetaClass, pr.Base)): if isinstance(index, Instance) \ and str(index.name) in ['int', 'str'] \ and len(index.var_args) == 1: - try: + with common.ignored(KeyError, IndexError): return self.get_exact_index_types(index.var_args[0]) - except (KeyError, IndexError): - pass result = list(self._follow_values(self._array.values)) result += dynamic.check_array_additions(self) diff --git a/jedi/helpers.py b/jedi/helpers.py index c4532961..a61027c5 100644 --- a/jedi/helpers.py +++ b/jedi/helpers.py @@ -1,5 +1,8 @@ +from __future__ import with_statement + import copy +from jedi import common from jedi import parsing_representation as pr @@ -21,13 +24,11 @@ def fast_parent_copy(obj): before = () for cls in new_obj.__class__.__mro__: - try: + with common.ignored(AttributeError): if before == cls.__slots__: continue before = cls.__slots__ items += [(n, getattr(new_obj, n)) for n in before] - except AttributeError: - pass for key, value in items: # replace parent (first try _parent and then parent) @@ -35,10 +36,8 @@ def fast_parent_copy(obj): if key == 'parent' and '_parent' in items: # parent can be a property continue - try: + with common.ignored(KeyError): setattr(new_obj, key, new_elements[value]) - except KeyError: - pass elif key in ['parent_function', 'use_as_parent', '_sub_module']: continue elif isinstance(value, list): diff --git a/jedi/imports.py b/jedi/imports.py index db72925f..d6081386 100644 --- a/jedi/imports.py +++ b/jedi/imports.py @@ -11,7 +11,6 @@ correct implementation is delegated to _compatibility. This module also supports import autocompletion, which means to complete statements like ``from datetim`` (curser at the end would return ``datetime``). """ - from __future__ import with_statement import os @@ -21,6 +20,7 @@ import itertools from jedi._compatibility import find_module from jedi import modules +from jedi import common from jedi import debug from jedi import parsing_representation as pr from jedi import cache @@ -122,11 +122,9 @@ class ImportPath(pr.Base): if self.import_stmt.relative_count: rel_path = self.get_relative_path() + '/__init__.py' - try: + with common.ignored(IOError): m = modules.Module(rel_path) names += m.parser.module.get_defined_names() - except IOError: - pass else: if on_import_stmt and isinstance(scope, pr.Module) \ and scope.path.endswith('__init__.py'): @@ -274,10 +272,8 @@ class ImportPath(pr.Base): and len(self.import_path) == 1: # follow `from . import some_variable` rel_path = self.get_relative_path() - try: + with common.ignored(ImportError): current_namespace = follow_str(rel_path, '__init__') - except ImportError: - pass if current_namespace[1]: rest = self.import_path[i:] else: diff --git a/jedi/keywords.py b/jedi/keywords.py index 5495ae32..cd591aa6 100644 --- a/jedi/keywords.py +++ b/jedi/keywords.py @@ -1,7 +1,10 @@ +from __future__ import with_statement + import pydoc import keyword from jedi._compatibility import is_py3k +from jedi import common import builtin try: @@ -63,12 +66,10 @@ def imitate_pydoc(string): # with unicode strings) string = str(string) h = pydoc.help - try: + with common.ignored(KeyError): # try to access symbols string = h.symbols[string] string, _, related = string.partition(' ') - except KeyError: - pass get_target = lambda s: h.topics.get(s, h.keywords.get(s)) while isinstance(string, str): diff --git a/jedi/modules.py b/jedi/modules.py index 9277e742..e67cc2c3 100644 --- a/jedi/modules.py +++ b/jedi/modules.py @@ -27,6 +27,7 @@ from jedi import parsing_representation as pr from jedi import fast_parser from jedi import debug from jedi import settings +from jedi import common class CachedModule(object): @@ -107,11 +108,9 @@ class ModuleWithCursor(Module): def parser(self): """ get the parser lazy """ if not self._parser: - try: + with common.ignored(KeyError): parser = cache.parser_cache[self.path].parser cache.invalidate_star_import_cache(parser.module) - except KeyError: - pass # Call the parser already here, because it will be used anyways. # Also, the position is here important (which will not be used by # default), therefore fill the cache here. @@ -348,10 +347,8 @@ def sys_path_with_modifications(module): return [] # support for modules without a path is intentionally bad. curdir = os.path.abspath(os.curdir) - try: + with common.ignored(OSError): os.chdir(os.path.dirname(module.path)) - except OSError: - pass result = check_module(module) result += detect_django_path(module.path) @@ -372,12 +369,10 @@ def detect_django_path(module_path): else: module_path = new - try: + with common.ignored(IOError): with open(module_path + os.path.sep + 'manage.py'): debug.dbg('Found django path: %s' % module_path) result.append(module_path) - except IOError: - pass return result diff --git a/jedi/parsing.py b/jedi/parsing.py index 69607fec..e5279f71 100644 --- a/jedi/parsing.py +++ b/jedi/parsing.py @@ -15,6 +15,7 @@ within the statement. This lowers memory usage and cpu time and reduces the complexity of the ``Parser`` (there's another parser sitting inside ``Statement``, which produces ``Array`` and ``Call``). """ +from __future__ import with_statement import tokenize import keyword @@ -396,7 +397,7 @@ class Parser(object): self._check_user_stmt(stmt) # Attribute docstring (PEP 257) support - try: + with common.ignored(IndexError, AttributeError): # If string literal is being parsed first_tok = stmt.token_list[0] if (not stmt.set_vars and @@ -405,8 +406,6 @@ class Parser(object): first_tok[0] == tokenize.STRING): # ... then set it as a docstring self.scope.statements[-1].add_docstr(first_tok[1]) - except (IndexError, AttributeError): - pass if tok in always_break + not_first_break: self._gen.push_last_back() diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index 311f38ab..d4b8380c 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -34,6 +34,7 @@ statements in this scope. Check this out: See also :attr:`Scope.subscopes` and :attr:`Scope.statements`. """ +from __future__ import with_statement import os import re @@ -1280,11 +1281,8 @@ class Array(Call): inner = [] for i, stmt in enumerate(self.values): s = '' - try: + with common.ignored(IndexError): key = self.keys[i] - except IndexError: - pass - else: s += key.get_code(new_line=False) + ': ' s += stmt.get_code(new_line=False) inner.append(s) diff --git a/jedi/refactoring.py b/jedi/refactoring.py index a724ec16..ced1a732 100644 --- a/jedi/refactoring.py +++ b/jedi/refactoring.py @@ -12,11 +12,11 @@ following functions (sometimes bug-prone): - extract variable - inline variable """ - from __future__ import with_statement import difflib +from jedi import common from jedi import modules from jedi import helpers from jedi import parsing_representation as pr @@ -168,7 +168,7 @@ def inline(script): dct = {} definitions = script.goto() - try: + with common.ignored(AssertionError): assert len(definitions) == 1 stmt = definitions[0].definition related_names = script.related_names() @@ -202,7 +202,4 @@ def inline(script): else: new_lines.pop(index) - except AssertionError: - pass - return Refactoring(dct)