Merge branch 'dev' into tokenizer

This commit is contained in:
David Halter
2013-04-20 08:55:02 +04:30
25 changed files with 305 additions and 119 deletions
+18
View File
@@ -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__.:
+3 -1
View File
@@ -1,9 +1,11 @@
*~ *~
*.swp *.swp
*.swo *.swo
*.pyc
.ropeproject .ropeproject
.tox .tox
*.pyc .coveralls.yml
.coverage
/build/ /build/
/docs/_build/ /docs/_build/
/dist/ /dist/
+10
View File
@@ -4,7 +4,17 @@ env:
- TOXENV=py26 - TOXENV=py26
- TOXENV=py27 - TOXENV=py27
- TOXENV=py32 - TOXENV=py32
- TOXENV=py33
- TOXENV=cov
matrix:
allow_failures:
- env: TOXENV=cov
install: install:
- pip install --quiet --use-mirrors tox - pip install --quiet --use-mirrors tox
script: script:
- tox - tox
after_script:
- if [ $TOXENV == "cov" ]; then
pip install --quiet --use-mirrors coveralls;
coveralls;
fi
+27
View File
@@ -6,6 +6,11 @@ Jedi - an awesome autocompletion library for Python
:target: http://travis-ci.org/davidhalter/jedi :target: http://travis-ci.org/davidhalter/jedi
:alt: Travis-CI build status :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 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 Jedi works. Jedi is fast. It understands all of the basic Python syntax
elements including many builtin functions. elements including many builtin functions.
@@ -86,3 +91,25 @@ API for IDEs
It's very easy to create an editor plugin that uses Jedi. See 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 https://jedi.readthedocs.org/en/latest/docs/plugin-api.html for more
information. information.
Testing
=======
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
<https://travis-ci.org/davidhalter/jedi/>`_.
+5 -5
View File
@@ -5,7 +5,7 @@ A little history
The Star Wars Jedi are awesome. My Jedi software tries to imitate a little bit 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 the precognition the Jedi have. There's even an awesome `scene
<http://www.youtube.com/watch?v=5BDO3pyavOY>`_ of Monty Python Jedi's :-). <http://www.youtube.com/watch?v=5BDO3pyavOY>`_ of Monty Python Jedis :-).
But actually the name hasn't so much to do with Star Wars. It's part of my But actually the name hasn't so much to do with Star Wars. It's part of my
second name. second name.
@@ -13,13 +13,13 @@ second name.
After I explained Guido van Rossum, how some parts of my auto-completion work, After I explained Guido van Rossum, how some parts of my auto-completion work,
he said (we drank a beer or two): 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 :-) When it's finished, I hope he'll like it :-)
I actually started Jedi, because there were no good solutions available for I actually started Jedi, because there were no good solutions available for VIM.
VIM. Most auto-completions just didn't work well. The only good solution was Most auto-completions just didn't work well. The only good solution was PyCharm.
PyCharm. I just like my good old VIM. Rope was never really intended to be an 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). 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 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 completion, which would execute non-dangerous code. But I soon realized, that
+59 -1
View File
@@ -7,11 +7,69 @@ 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. will be dropped, we'll get rid of most code.
""" """
import sys import sys
import imp
import os
try:
import importlib
except:
pass
is_py3k = sys.hexversion >= 0x03000000 is_py3k = sys.hexversion >= 0x03000000
is_py33 = sys.hexversion >= 0x03030000
is_py25 = sys.hexversion < 0x02060000 is_py25 = sys.hexversion < 0x02060000
def find_module_py33(string, path=None):
mod_info = (None, None, None)
loader = None
if path is not None:
# Check for the module in the specidied path
loader = importlib.machinery.PathFinder.find_module(string, path)
else:
# 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 loader is None:
raise ImportError
try:
if (loader.is_package(string)):
mod_info = (None, os.path.dirname(loader.path), True)
else:
filename = loader.get_filename(string)
if filename and os.path.exists(filename):
mod_info = (open(filename, 'U'), filename, False)
else:
mod_info = (None, filename, False)
except AttributeError:
mod_info = (None, loader.load_module(string).__name__, False)
return mod_info
def find_module_pre_py33(string, path=None):
mod_info = None
if path is None:
mod_info = imp.find_module(string)
else:
mod_info = imp.find_module(string, path)
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.
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
if the module is contained in a package."""
if is_py33:
return find_module_py33(string, path)
else:
return find_module_pre_py33(string, path)
# next was defined in python 2.6, in python 3 obj.next won't be possible # next was defined in python 2.6, in python 3 obj.next won't be possible
# anymore # anymore
try: try:
+4 -6
View File
@@ -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 classes are the much bigger part of the whole API, because they contain the
interesting information about completion and goto operations. interesting information about completion and goto operations.
""" """
from __future__ import with_statement
import re import re
import os import os
@@ -11,6 +12,7 @@ import functools
from jedi._compatibility import unicode, next from jedi._compatibility import unicode, next
from jedi import settings from jedi import settings
from jedi import common
from jedi import parsing_representation as pr from jedi import parsing_representation as pr
from jedi import cache from jedi import cache
import keywords import keywords
@@ -144,10 +146,8 @@ class BaseDefinition(object):
if not isinstance(self.definition, keywords.Keyword): if not isinstance(self.definition, keywords.Keyword):
par = self.definition par = self.definition
while par is not None: while par is not None:
try: with common.ignored(AttributeError):
path.insert(0, par.name) path.insert(0, par.name)
except AttributeError:
pass
par = par.parent par = par.parent
return path return path
@@ -295,10 +295,8 @@ class BaseDefinition(object):
if not path: if not path:
return None # for keywords the path is empty return None # for keywords the path is empty
try: with common.ignored(KeyError):
path[0] = self._mapping[path[0]] path[0] = self._mapping[path[0]]
except KeyError:
pass
for key, repl in self._tuple_mapping.items(): for key, repl in self._tuple_mapping.items():
if tuple(path[:len(key)]) == key: if tuple(path[:len(key)]) == key:
path = [repl] + path[len(key):] path = [repl] + path[len(key):]
+55 -16
View File
@@ -21,10 +21,16 @@ from __future__ import with_statement
import time import time
import os import os
import sys import sys
import pickle import hashlib
try:
import cPickle as pickle
except:
import pickle
import shutil
from jedi._compatibility import json from jedi._compatibility import json
from jedi import settings from jedi import settings
from jedi import common
from jedi import debug from jedi import debug
# memoize caches will be deleted after every action # memoize caches will be deleted after every action
@@ -143,12 +149,10 @@ def cache_function_definition(stmt):
def cache_star_import(func): def cache_star_import(func):
def wrapper(scope, *args, **kwargs): def wrapper(scope, *args, **kwargs):
try: with common.ignored(KeyError):
mods = star_import_cache[scope] mods = star_import_cache[scope]
if mods[0] + settings.star_import_cache_validity > time.time(): if mods[0] + settings.star_import_cache_validity > time.time():
return mods[1] return mods[1]
except KeyError:
pass
# cache is too old and therefore invalid or not available # cache is too old and therefore invalid or not available
invalidate_star_import_cache(scope) invalidate_star_import_cache(scope)
mods = func(scope, *args, **kwargs) mods = func(scope, *args, **kwargs)
@@ -160,15 +164,13 @@ def cache_star_import(func):
def invalidate_star_import_cache(module, only_main=False): def invalidate_star_import_cache(module, only_main=False):
""" Important if some new modules are being reparsed """ """ Important if some new modules are being reparsed """
try: with common.ignored(KeyError):
t, mods = star_import_cache[module] t, mods = star_import_cache[module]
del star_import_cache[module] del star_import_cache[module]
for m in mods: for m in mods:
invalidate_star_import_cache(m, only_main=True) invalidate_star_import_cache(m, only_main=True)
except KeyError:
pass
if not only_main: if not only_main:
# We need a list here because otherwise the list is being changed # We need a list here because otherwise the list is being changed
@@ -216,13 +218,36 @@ def save_module(path, name, parser, pickling=True):
class _ModulePickling(object): class _ModulePickling(object):
version = 2
"""
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): def __init__(self):
self.__index = None 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): def load_module(self, path, original_changed_time):
try: try:
pickle_changed_time = self._index[self.py_version][path] pickle_changed_time = self._index[path]
except KeyError: except KeyError:
return None return None
if original_changed_time is not None \ if original_changed_time is not None \
@@ -240,10 +265,10 @@ class _ModulePickling(object):
def save_module(self, path, parser_cache_item): def save_module(self, path, parser_cache_item):
self.__index = None self.__index = None
try: try:
files = self._index[self.py_version] files = self._index
except KeyError: except KeyError:
files = {} files = {}
self._index[self.py_version] = files self._index = files
with open(self._get_hashed_path(path), 'wb') as f: with open(self._get_hashed_path(path), 'wb') as f:
pickle.dump(parser_cache_item, f, pickle.HIGHEST_PROTOCOL) pickle.dump(parser_cache_item, f, pickle.HIGHEST_PROTOCOL)
@@ -256,9 +281,16 @@ class _ModulePickling(object):
if self.__index is None: if self.__index is None:
try: try:
with open(self._get_path('index.json')) as f: with open(self._get_path('index.json')) as f:
self.__index = json.load(f) data = json.load(f)
except IOError: except IOError:
self.__index = {} 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 return self.__index
def _remove_old_modules(self): def _remove_old_modules(self):
@@ -269,18 +301,25 @@ class _ModulePickling(object):
self._index # reload index self._index # reload index
def _flush_index(self): def _flush_index(self):
data = {'version': self.version, 'index': self._index}
with open(self._get_path('index.json'), 'w') as f: with open(self._get_path('index.json'), 'w') as f:
json.dump(self._index, f) json.dump(data, f)
self.__index = None self.__index = None
def delete_cache(self):
shutil.rmtree(self._cache_directory())
def _get_hashed_path(self, path): def _get_hashed_path(self, path):
return self._get_path('%s_%s.pkl' % (self.py_version, hash(path))) return self._get_path('%s.pkl' % hashlib.md5(path.encode("utf-8")).hexdigest())
def _get_path(self, file): def _get_path(self, file):
dir = settings.cache_directory dir = self._cache_directory()
if not os.path.exists(dir): if not os.path.exists(dir):
os.makedirs(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_tag)
# is a singleton # is a singleton
+10
View File
@@ -163,3 +163,13 @@ def indent_block(text, indention=' '):
text = text[:-1] text = text[:-1]
lines = text.split('\n') lines = text.split('\n')
return '\n'.join(map(lambda s: indention + s, lines)) + temp 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
+2 -3
View File
@@ -59,6 +59,7 @@ from jedi import cache
from jedi import parsing_representation as pr from jedi import parsing_representation as pr
from jedi import modules from jedi import modules
from jedi import settings from jedi import settings
from jedi import common
from jedi import debug from jedi import debug
from jedi import fast_parser from jedi import fast_parser
import api_classes import api_classes
@@ -487,10 +488,8 @@ def related_name_add_import_modules(definitions, search_name):
for d in definitions: for d in definitions:
if isinstance(d.parent, pr.Import): if isinstance(d.parent, pr.Import):
s = imports.ImportPath(d.parent, direct_resolve=True) s = imports.ImportPath(d.parent, direct_resolve=True)
try: with common.ignored(IndexError):
new.add(s.follow(is_goto=True)[0]) new.add(s.follow(is_goto=True)[0])
except IndexError:
pass
return set(definitions) | new return set(definitions) | new
+6 -15
View File
@@ -68,6 +68,7 @@ backtracking algorithm.
.. todo:: nonlocal statement, needed or can be ignored? (py3k) .. todo:: nonlocal statement, needed or can be ignored? (py3k)
""" """
from __future__ import with_statement
import sys import sys
import itertools import itertools
@@ -429,11 +430,9 @@ def find_name(scope, name_str, position=None, search_global=False,
if isinstance(scope, (er.Instance, er.Class)) \ if isinstance(scope, (er.Instance, er.Class)) \
and hasattr(r, 'get_descriptor_return'): and hasattr(r, 'get_descriptor_return'):
# handle descriptors # handle descriptors
try: with common.ignored(KeyError):
res_new += r.get_descriptor_return(scope) res_new += r.get_descriptor_return(scope)
continue continue
except KeyError:
pass
res_new.append(r) res_new.append(r)
return res_new return res_new
@@ -462,19 +461,15 @@ def check_getattr(inst, name_str):
# str is important to lose the NamePart! # str is important to lose the NamePart!
module = builtin.Builtin.scope module = builtin.Builtin.scope
name = pr.Call(module, str(name_str), pr.Call.STRING, (0, 0), inst) 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]) result = inst.execute_subscope_by_name('__getattr__', [name])
except KeyError:
pass
if not result: if not result:
# this is a little bit special. `__getattribute__` is executed # this is a little bit special. `__getattribute__` is executed
# before anything else. But: I know no use case, where this # before anything else. But: I know no use case, where this
# could be practical and the jedi would return wrong types. If # could be practical and the jedi would return wrong types. If
# you ever have something, let me know! # you ever have something, let me know!
try: with common.ignored(KeyError):
result = inst.execute_subscope_by_name('__getattribute__', [name]) result = inst.execute_subscope_by_name('__getattribute__', [name])
except KeyError:
pass
return result return result
@@ -536,10 +531,8 @@ def assign_tuples(tup, results, seek_name):
debug.warning("invalid tuple lookup %s of result %s in %s" debug.warning("invalid tuple lookup %s of result %s in %s"
% (tup, results, seek_name)) % (tup, results, seek_name))
else: else:
try: with common.ignored(IndexError):
types += func(index) types += func(index)
except IndexError:
pass
return types return types
result = [] result = []
@@ -648,11 +641,9 @@ def follow_call_list(call_list, follow_array=False):
call = next(calls_iterator) call = next(calls_iterator)
except StopIteration: except StopIteration:
break break
try: with common.ignored(AttributeError):
if str(call.name) == 'else': if str(call.name) == 'else':
break break
except AttributeError:
pass
continue continue
result += follow_call(call) result += follow_call(call)
elif call == '*': elif call == '*':
+11 -9
View File
@@ -9,6 +9,8 @@ instantiated. This class represents these cases.
So, why is there also a ``Class`` class here? Well, there are decorators and So, why is there also a ``Class`` class here? Well, there are decorators and
they change classes in Python 3. they change classes in Python 3.
""" """
from __future__ import with_statement
import copy import copy
import itertools import itertools
@@ -61,10 +63,8 @@ class Instance(use_metaclass(cache.CachedMetaClass, Executable)):
else: else:
# need to execute the __init__ function, because the dynamic param # need to execute the __init__ function, because the dynamic param
# searching needs it. # searching needs it.
try: with common.ignored(KeyError):
self.execute_subscope_by_name('__init__', self.var_args) self.execute_subscope_by_name('__init__', self.var_args)
except KeyError:
pass
# Generated instances are classes that are just generated by self # Generated instances are classes that are just generated by self
# (No var_args) used. # (No var_args) used.
self.is_generated = False self.is_generated = False
@@ -769,9 +769,13 @@ class Generator(use_metaclass(cache.CachedMetaClass, pr.Base)):
debug.warning('Tried to get array access on a generator', self) debug.warning('Tried to get array access on a generator', self)
return [] return []
@property def __getattr__(self, name):
def parent(self): if name not in ['start_pos', 'end_pos', 'parent', 'get_imports',
return self.func.parent '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): def __repr__(self):
return "<%s of %s>" % (type(self).__name__, self.func) return "<%s of %s>" % (type(self).__name__, self.func)
@@ -800,10 +804,8 @@ class Array(use_metaclass(cache.CachedMetaClass, pr.Base)):
if isinstance(index, Instance) \ if isinstance(index, Instance) \
and str(index.name) in ['int', 'str'] \ and str(index.name) in ['int', 'str'] \
and len(index.var_args) == 1: and len(index.var_args) == 1:
try: with common.ignored(KeyError, IndexError):
return self.get_exact_index_types(index.var_args[0]) return self.get_exact_index_types(index.var_args[0])
except (KeyError, IndexError):
pass
result = list(self._follow_values(self._array.values)) result = list(self._follow_values(self._array.values))
result += dynamic.check_array_additions(self) result += dynamic.check_array_additions(self)
+5 -6
View File
@@ -1,5 +1,8 @@
from __future__ import with_statement
import copy import copy
from jedi import common
from jedi import parsing_representation as pr from jedi import parsing_representation as pr
@@ -21,13 +24,11 @@ def fast_parent_copy(obj):
before = () before = ()
for cls in new_obj.__class__.__mro__: for cls in new_obj.__class__.__mro__:
try: with common.ignored(AttributeError):
if before == cls.__slots__: if before == cls.__slots__:
continue continue
before = cls.__slots__ before = cls.__slots__
items += [(n, getattr(new_obj, n)) for n in before] items += [(n, getattr(new_obj, n)) for n in before]
except AttributeError:
pass
for key, value in items: for key, value in items:
# replace parent (first try _parent and then parent) # replace parent (first try _parent and then parent)
@@ -35,10 +36,8 @@ def fast_parent_copy(obj):
if key == 'parent' and '_parent' in items: if key == 'parent' and '_parent' in items:
# parent can be a property # parent can be a property
continue continue
try: with common.ignored(KeyError):
setattr(new_obj, key, new_elements[value]) setattr(new_obj, key, new_elements[value])
except KeyError:
pass
elif key in ['parent_function', 'use_as_parent', '_sub_module']: elif key in ['parent_function', 'use_as_parent', '_sub_module']:
continue continue
elif isinstance(value, list): elif isinstance(value, list):
+19 -17
View File
@@ -5,23 +5,22 @@ any actual importing done. This module is about finding modules in the
filesystem. This can be quite tricky sometimes, because Python imports are not filesystem. This can be quite tricky sometimes, because Python imports are not
always that simple. always that simple.
Currently the import process uses ``imp`` to find modules. In the future, it's This module uses imp for python up to 3.2 and importlib for python 3.3 on; the
a goal to use ``importlib`` for this purpose. There's a `pull request correct implementation is delegated to _compatibility.
<https://github.com/davidhalter/jedi/pull/109>`_ for that.
This module also supports import autocompletion, which means to complete This module also supports import autocompletion, which means to complete
statements like ``from datetim`` (curser at the end would return ``datetime``). statements like ``from datetim`` (curser at the end would return ``datetime``).
""" """
from __future__ import with_statement from __future__ import with_statement
import os import os
import pkgutil import pkgutil
import imp
import sys import sys
import itertools import itertools
from jedi._compatibility import find_module
from jedi import modules from jedi import modules
from jedi import common
from jedi import debug from jedi import debug
from jedi import parsing_representation as pr from jedi import parsing_representation as pr
from jedi import cache from jedi import cache
@@ -123,11 +122,9 @@ class ImportPath(pr.Base):
if self.import_stmt.relative_count: if self.import_stmt.relative_count:
rel_path = self.get_relative_path() + '/__init__.py' rel_path = self.get_relative_path() + '/__init__.py'
try: with common.ignored(IOError):
m = modules.Module(rel_path) m = modules.Module(rel_path)
names += m.parser.module.get_defined_names() names += m.parser.module.get_defined_names()
except IOError:
pass
else: else:
if on_import_stmt and isinstance(scope, pr.Module) \ if on_import_stmt and isinstance(scope, pr.Module) \
and scope.path.endswith('__init__.py'): and scope.path.endswith('__init__.py'):
@@ -238,20 +235,22 @@ class ImportPath(pr.Base):
global imports_processed global imports_processed
imports_processed += 1 imports_processed += 1
importing = None
if path is not None: if path is not None:
return imp.find_module(string, [path]) importing = find_module(string, [path])
else: else:
debug.dbg('search_module', string, self.file_path) debug.dbg('search_module', string, self.file_path)
# Override the sys.path. It works only good that way. # Override the sys.path. It works only good that way.
# Injecting the path directly into `find_module` did not work. # Injecting the path directly into `find_module` did not work.
sys.path, temp = sys_path_mod, sys.path sys.path, temp = sys_path_mod, sys.path
try: try:
i = imp.find_module(string) importing = find_module(string)
except ImportError: except ImportError:
sys.path = temp sys.path = temp
raise raise
sys.path = temp sys.path = temp
return i
return importing
if self.file_path: if self.file_path:
sys_path_mod = list(self.sys_path_with_modifications()) sys_path_mod = list(self.sys_path_with_modifications())
@@ -259,6 +258,9 @@ class ImportPath(pr.Base):
else: else:
sys_path_mod = list(modules.get_sys_path()) 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) current_namespace = (None, None, None)
# now execute those paths # now execute those paths
rest = [] rest = []
@@ -270,19 +272,19 @@ class ImportPath(pr.Base):
and len(self.import_path) == 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()
try: with common.ignored(ImportError):
current_namespace = follow_str(rel_path, '__init__') current_namespace = follow_str(rel_path, '__init__')
except ImportError:
pass
if current_namespace[1]: if current_namespace[1]:
rest = self.import_path[i:] rest = self.import_path[i:]
else: else:
raise ModuleNotFound( module_not_found()
'The module you searched has not been found')
if current_namespace == (None, None, False):
module_not_found()
sys_path_mod.pop(0) # TODO why is this here? sys_path_mod.pop(0) # TODO why is this here?
path = current_namespace[1] path = current_namespace[1]
is_package_directory = current_namespace[2][2] == imp.PKG_DIRECTORY is_package_directory = current_namespace[2]
f = None f = None
if is_package_directory or current_namespace[0]: if is_package_directory or current_namespace[0]:
+4 -3
View File
@@ -1,7 +1,10 @@
from __future__ import with_statement
import pydoc import pydoc
import keyword import keyword
from jedi._compatibility import is_py3k from jedi._compatibility import is_py3k
from jedi import common
import builtin import builtin
try: try:
@@ -63,12 +66,10 @@ def imitate_pydoc(string):
# with unicode strings) # with unicode strings)
string = str(string) string = str(string)
h = pydoc.help h = pydoc.help
try: with common.ignored(KeyError):
# try to access symbols # try to access symbols
string = h.symbols[string] string = h.symbols[string]
string, _, related = string.partition(' ') string, _, related = string.partition(' ')
except KeyError:
pass
get_target = lambda s: h.topics.get(s, h.keywords.get(s)) get_target = lambda s: h.topics.get(s, h.keywords.get(s))
while isinstance(string, str): while isinstance(string, str):
+4 -9
View File
@@ -27,6 +27,7 @@ from jedi import parsing_representation as pr
from jedi import fast_parser from jedi import fast_parser
from jedi import debug from jedi import debug
from jedi import settings from jedi import settings
from jedi import common
class CachedModule(object): class CachedModule(object):
@@ -107,11 +108,9 @@ class ModuleWithCursor(Module):
def parser(self): def parser(self):
""" get the parser lazy """ """ get the parser lazy """
if not self._parser: if not self._parser:
try: with common.ignored(KeyError):
parser = cache.parser_cache[self.path].parser parser = cache.parser_cache[self.path].parser
cache.invalidate_star_import_cache(parser.module) cache.invalidate_star_import_cache(parser.module)
except KeyError:
pass
# Call the parser already here, because it will be used anyways. # Call the parser already here, because it will be used anyways.
# Also, the position is here important (which will not be used by # Also, the position is here important (which will not be used by
# default), therefore fill the cache here. # 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. return [] # support for modules without a path is intentionally bad.
curdir = os.path.abspath(os.curdir) curdir = os.path.abspath(os.curdir)
try: with common.ignored(OSError):
os.chdir(os.path.dirname(module.path)) os.chdir(os.path.dirname(module.path))
except OSError:
pass
result = check_module(module) result = check_module(module)
result += detect_django_path(module.path) result += detect_django_path(module.path)
@@ -372,12 +369,10 @@ def detect_django_path(module_path):
else: else:
module_path = new module_path = new
try: with common.ignored(IOError):
with open(module_path + os.path.sep + 'manage.py'): with open(module_path + os.path.sep + 'manage.py'):
debug.dbg('Found django path: %s' % module_path) debug.dbg('Found django path: %s' % module_path)
result.append(module_path) result.append(module_path)
except IOError:
pass
return result return result
+2 -3
View File
@@ -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 complexity of the ``Parser`` (there's another parser sitting inside
``Statement``, which produces ``Array`` and ``Call``). ``Statement``, which produces ``Array`` and ``Call``).
""" """
from __future__ import with_statement
import tokenize import tokenize
import keyword import keyword
@@ -396,7 +397,7 @@ class Parser(object):
self._check_user_stmt(stmt) self._check_user_stmt(stmt)
# Attribute docstring (PEP 257) support # Attribute docstring (PEP 257) support
try: with common.ignored(IndexError, AttributeError):
# If string literal is being parsed # If string literal is being parsed
first_tok = stmt.token_list[0] first_tok = stmt.token_list[0]
if (not stmt.set_vars and if (not stmt.set_vars and
@@ -405,8 +406,6 @@ class Parser(object):
first_tok[0] == tokenize.STRING): first_tok[0] == tokenize.STRING):
# ... then set it as a docstring # ... then set it as a docstring
self.scope.statements[-1].add_docstr(first_tok[1]) self.scope.statements[-1].add_docstr(first_tok[1])
except (IndexError, AttributeError):
pass
if tok in always_break + not_first_break: if tok in always_break + not_first_break:
self._gen.push_last_back() self._gen.push_last_back()
+5 -5
View File
@@ -34,6 +34,7 @@ statements in this scope. Check this out:
See also :attr:`Scope.subscopes` and :attr:`Scope.statements`. See also :attr:`Scope.subscopes` and :attr:`Scope.statements`.
""" """
from __future__ import with_statement
import os import os
import re import re
@@ -560,8 +561,10 @@ class Flow(Scope):
@parent.setter @parent.setter
def parent(self, value): def parent(self, value):
self._parent = value self._parent = value
if self.next: try:
self.next.parent = value self.next.parent = value
except AttributeError:
return
def get_code(self, first_indent=False, indention=' '): def get_code(self, first_indent=False, indention=' '):
stmts = [] stmts = []
@@ -1278,11 +1281,8 @@ class Array(Call):
inner = [] inner = []
for i, stmt in enumerate(self.values): for i, stmt in enumerate(self.values):
s = '' s = ''
try: with common.ignored(IndexError):
key = self.keys[i] key = self.keys[i]
except IndexError:
pass
else:
s += key.get_code(new_line=False) + ': ' s += key.get_code(new_line=False) + ': '
s += stmt.get_code(new_line=False) s += stmt.get_code(new_line=False)
inner.append(s) inner.append(s)
+2 -5
View File
@@ -12,11 +12,11 @@ following functions (sometimes bug-prone):
- extract variable - extract variable
- inline variable - inline variable
""" """
from __future__ import with_statement from __future__ import with_statement
import difflib import difflib
from jedi import common
from jedi import modules from jedi import modules
from jedi import helpers from jedi import helpers
from jedi import parsing_representation as pr from jedi import parsing_representation as pr
@@ -168,7 +168,7 @@ def inline(script):
dct = {} dct = {}
definitions = script.goto() definitions = script.goto()
try: with common.ignored(AssertionError):
assert len(definitions) == 1 assert len(definitions) == 1
stmt = definitions[0].definition stmt = definitions[0].definition
related_names = script.related_names() related_names = script.related_names()
@@ -202,7 +202,4 @@ def inline(script):
else: else:
new_lines.pop(index) new_lines.pop(index)
except AssertionError:
pass
return Refactoring(dct) return Refactoring(dct)
+7 -1
View File
@@ -31,7 +31,13 @@ setup(name='jedi',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
'Operating System :: OS Independent', '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 :: Software Development :: Libraries :: Python Modules',
'Topic :: Text Editors :: Integrated Development Environments (IDE)', 'Topic :: Text Editors :: Integrated Development Environments (IDE)',
'Topic :: Utilities', 'Topic :: Utilities',
-9
View File
@@ -89,12 +89,3 @@ def huhu(db):
""" """
#? sqlite3.Connection() #? sqlite3.Connection()
db db
# -----------------
# various regression tests
# -----------------
#62
import threading
#? ['_Verbose', '_VERBOSE']
threading._Verbose
+12
View File
@@ -65,6 +65,18 @@ def pytest_generate_tests(metafunc):
refactor.collect_dir_tests(base_dir, test_files)) 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') @pytest.fixture(scope='session')
def clean_jedi_cache(request): def clean_jedi_cache(request):
""" """
+2 -2
View File
@@ -32,7 +32,7 @@ def make_definitions():
definitions += api.defined_names(source) definitions += api.defined_names(source)
source += textwrap.dedent(""" 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() lines = source.splitlines()
script = api.Script(source, len(lines), len('variable'), None) script = api.Script(source, len(lines), len('variable'), None)
definitions += script.definition() definitions += script.definition()
@@ -50,4 +50,4 @@ def make_definitions():
@pytest.mark.parametrize('definition', make_definitions()) @pytest.mark.parametrize('definition', make_definitions())
def test_basedefinition_type(definition): def test_basedefinition_type(definition):
assert definition.type in ('module', 'class', 'instance', 'function', assert definition.type in ('module', 'class', 'instance', 'function',
'statement', 'import', 'param') 'generator', 'statement', 'import', 'param')
+26 -2
View File
@@ -1,3 +1,5 @@
import pytest
from jedi import settings from jedi import settings
from jedi.cache import ParserCacheItem, _ModulePickling 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) monkeypatch.setattr(settings, 'cache_directory', dir_1)
ModulePickling.save_module(path_1, item_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 assert cached == item_1.parser
monkeypatch.setattr(settings, 'cache_directory', dir_2) monkeypatch.setattr(settings, 'cache_directory', dir_2)
ModulePickling.save_module(path_2, item_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 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
+7 -1
View File
@@ -1,5 +1,5 @@
[tox] [tox]
envlist = py25, py26, py27, py32 envlist = py25, py26, py27, py32, py33
[testenv] [testenv]
deps = deps =
pytest pytest
@@ -14,3 +14,9 @@ deps =
deps = deps =
unittest2 unittest2
{[testenv]deps} {[testenv]deps}
[testenv:cov]
deps =
pytest-cov
{[testenv]deps}
commands =
py.test --cov jedi []