1
0
forked from VimPlug/jedi

Merge branch 'dev' of git://github.com/davidhalter/jedi

This commit is contained in:
Laurens Van Houtven
2013-06-23 22:50:00 +02:00
28 changed files with 1124 additions and 201 deletions

View File

@@ -5,9 +5,11 @@ env:
- TOXENV=py32
- TOXENV=py33
- TOXENV=cov
- TOXENV=sith
matrix:
allow_failures:
- env: TOXENV=cov
- env: TOXENV=sith
install:
- pip install --quiet --use-mirrors tox
script:

View File

@@ -10,6 +10,8 @@ Jedi - an awesome autocompletion library for Python
:target: https://coveralls.io/r/davidhalter/jedi
:alt: Coverage Status
.. image:: https://pypip.in/d/jedi/badge.png
:target: https://crate.io/packages/jedi/
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
@@ -24,12 +26,15 @@ which uses Jedi's autocompletion. I encourage you to use Jedi in your IDEs.
It's really easy. If there are any problems (also with licensing), just contact
me.
Jedi can be used with the following plugins/software:
Jedi can be used with the following editors:
- `VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_
- `Emacs-Plugin <https://github.com/tkf/emacs-jedi>`_
- `Sublime-Plugin <https://github.com/svaiter/SublimeJEDI>`_
- `wdb (web debugger) <https://github.com/Kozea/wdb>`_
- Vim (jedi-vim_, YouCompleteMe_)
- Emacs (Jedi.el_)
- Sublime Text (SublimeJEDI_)
And it powers the following projects:
- wdb_
Here are some pictures:
@@ -121,3 +126,10 @@ Tests are also run automatically on `Travis CI
For more detailed information visit the `testing documentation
<https://jedi.readthedocs.org/en/latest/docs/testing.html>`_
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
.. _youcompleteme: http://valloric.github.io/YouCompleteMe/
.. _Jedi.el: https://github.com/tkf/emacs-jedi
.. _sublimejedi: https://github.com/svaiter/SublimeJEDI
.. _wdb: https://github.com/Kozea/wdb

BIN
docs/_static/logo.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -28,7 +28,7 @@ sys.path.append(os.path.abspath('_themes'))
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.todo',
'sphinx.ext.inheritance_diagram']
'sphinx.ext.intersphinx', 'sphinx.ext.inheritance_diagram']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -141,7 +141,7 @@ html_static_path = ['_static']
# Custom sidebar templates, maps document names to template names.
html_sidebars = {
'**': [
# 'sidebarlogo.html',
'sidebarlogo.html',
'localtoc.html',
# 'relations.html',
'ghbuttons.html',
@@ -266,6 +266,14 @@ todo_include_todos = False
# -- Options for autodoc module ------------------------------------------------
autoclass_content = 'both'
autodoc_member_order = 'bysource'
autodoc_default_flags = []
#autodoc_default_flags = ['members', 'undoc-members']
# -- Options for intersphinx module --------------------------------------------
intersphinx_mapping = {
'http://docs.python.org/': None,
}

View File

@@ -64,6 +64,8 @@ Parser Representation (parser_representation.py)
.. automodule:: parsing_representation
Class inheritance diagram:
.. inheritance-diagram::
SubModule
Class

View File

@@ -30,8 +30,15 @@ System-wide installation via a package manager
Arch Linux
~~~~~~~~~~
You can install jedi directly from AUR: `python-jedi at AUR
<https://aur.archlinux.org/packages/python-jedi/>`__.
You can install |jedi| directly from official AUR packages:
- `python-jedi <https://aur.archlinux.org/packages/python-jedi/>`__ (Python 3)
- `python2-jedi <https://aur.archlinux.org/packages/python2-jedi/>`__ (Python 2)
The specified Python version just refers to the *runtime environment* for
|jedi|. Use the Python 2 version if you're running vim (or whatever editor you
use) under Python 2. Otherwise, use the Python 3 version. But whatever version
you choose, both are able to complete both Python 2 and 3 *code*.
(There is also a packaged version of the vim plugin available: `vim-jedi at AUR
<https://aur.archlinux.org/packages/vim-jedi/>`__.)
@@ -45,7 +52,7 @@ Debian packages are available as `experimental packages
Others
~~~~~~
We are in the discussion of adding Jedi to the Fedora repositories.
We are in the discussion of adding |jedi| to the Fedora repositories.
Manual installation from a downloaded package
@@ -53,7 +60,7 @@ Manual installation from a downloaded package
If you prefer not to use an automated package installer, you can `download
<https://github.com/davidhalter/jedi/archive/master.zip>`__ a current copy of
*Jedi* and install it manually.
|jedi| and install it manually.
To install it, navigate to the directory containing `setup.py` on your console
and type::

8
docs/docs/repl.rst Normal file
View File

@@ -0,0 +1,8 @@
.. include:: ../global.rst
How to use Jedi from Python interpreter
=======================================
.. automodule:: jedi.replstartup
.. autofunction:: jedi.utils.setup_readline

View File

@@ -22,6 +22,7 @@ Docs
docs/installation
docs/features
docs/repl
docs/recipes
docs/plugin-api
docs/history
@@ -44,9 +45,18 @@ Resources
Editor Plugins
--------------
- `Vim <http://github.com/davidhalter/jedi-vim>`_
- `Emacs <https://github.com/tkf/emacs-jedi>`_
- `Sublime Text 2 <https://github.com/svaiter/SublimeJEDI>`_
Vim:
- `jedi-vim <http://github.com/davidhalter/jedi-vim>`_
- `YouCompleteMe <http://valloric.github.io/YouCompleteMe/>`_
Emacs:
- `Jedi.el <https://github.com/tkf/emacs-jedi>`_
Sublime Text 2:
- `SublimeJEDI <https://github.com/svaiter/SublimeJEDI>`_
.. _other-software:

View File

@@ -42,8 +42,8 @@ import sys
# imports and circular imports... Just avoid it:
sys.path.insert(0, __path__[0])
from .api import Script, NotFoundError, set_debug_function, _quick_complete, \
preload_module
from .api import Script, Interpreter, NotFoundError, set_debug_function, \
preload_module, defined_names
from . import settings
sys.path.pop(0)

2
jedi/__main__.py Normal file
View File

@@ -0,0 +1,2 @@
from os import path
print(path.join(path.dirname(path.abspath(__file__)), 'replstartup.py'))

View File

@@ -20,6 +20,7 @@ from jedi import helpers
from jedi import common
from jedi import cache
from jedi import modules
from jedi import interpret
from jedi._compatibility import next, unicode
import evaluate
import keywords
@@ -53,14 +54,18 @@ class Script(object):
``unicode`` object (default ``'utf-8'``).
:type source_encoding: str
"""
def __init__(self, source, line, column, source_path,
def __init__(self, source, line=None, column=None, source_path=None,
source_encoding='utf-8'):
lines = source.splitlines()
line = len(lines) if line is None else line
column = len(lines[-1]) if column is None else column
api_classes._clear_caches()
debug.reset_time()
self.source = modules.source_to_unicode(source, source_encoding)
self.pos = line, column
self._module = modules.ModuleWithCursor(source_path,
source=self.source, position=self.pos)
self._module = modules.ModuleWithCursor(
source_path, source=self.source, position=self.pos)
self._source_path = source_path
self.source_path = None if source_path is None \
else os.path.abspath(source_path)
@@ -337,7 +342,8 @@ class Script(object):
:rtype: list of :class:`api_classes.Definition`
"""
d = [api_classes.Definition(d) for d in set(self._goto()[0])]
d = [api_classes.Definition(d) for d in set(self._goto()[0])
if not isinstance(d, imports.ImportPath._GlobalNamespace)]
return self._sorted_defs(d)
def _goto(self, add_import_name=False):
@@ -385,7 +391,9 @@ class Script(object):
defs, search_name = evaluate.goto(stmt)
definitions = follow_inexistent_imports(defs)
if isinstance(user_stmt, pr.Statement):
if user_stmt.get_commands()[0].start_pos > self.pos:
call = user_stmt.get_commands()[0]
if not isinstance(call, (str, unicode)) and \
call.start_pos > self.pos:
# The cursor must be after the start, otherwise the
# statement is just an assignee.
definitions = [user_stmt]
@@ -503,6 +511,45 @@ class Script(object):
return sorted(d, key=lambda x: (x.module_path or '', x.start_pos))
class Interpreter(Script):
"""
Jedi API for Python REPLs.
In addition to completion of simple attribute access, Jedi
supports code completion based on static code analysis.
Jedi can complete attributes of object which is not initialized
yet.
>>> from os.path import join
>>> namespace = locals()
>>> script = Interpreter('join().up', [namespace])
>>> print(script.complete()[0].word)
upper
"""
def __init__(self, source, namespaces=[], **kwds):
"""
Parse `source` and mixin interpreted Python objects from `namespaces`.
:type source: str
:arg source: Code to parse.
:type namespaces: list of dict
:arg namespaces: a list of namespace dictionaries such as the one
returned by :func:`locals`.
Other optional arguments are same as the ones for :class:`Script`.
If `line` and `column` are None, they are assumed be at the end of
`source`.
"""
super(Interpreter, self).__init__(source, **kwds)
importer = interpret.ObjectImporter(self._parser.user_scope)
for ns in namespaces:
importer.import_raw_namespace(ns)
def defined_names(source, source_path=None, source_encoding='utf-8'):
"""
Get all definitions in `source` sorted by its position.
@@ -519,7 +566,7 @@ def defined_names(source, source_path=None, source_encoding='utf-8'):
modules.source_to_unicode(source, source_encoding),
module_path=source_path,
)
return api_classes._defined_names(parser.scope)
return api_classes._defined_names(parser.module)
def preload_module(*modules):
@@ -545,25 +592,3 @@ def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,
debug.enable_warning = warnings
debug.enable_notice = notices
debug.enable_speed = speed
def _quick_complete(source):
"""
Convenience function to complete a source string at the end.
Example:
>>> _quick_complete('''
... import datetime
... datetime.da''') #doctest: +ELLIPSIS
[<Completion: date>, <Completion: datetime>, ...]
:param source: The source code to be completed.
:type source: string
:return: Completion objects as returned by :meth:`complete`.
:rtype: list of :class:`api_classes.Completion`
"""
lines = re.sub(r'[\n\r\s]*$', '', source).splitlines()
pos = len(lines), len(lines[-1])
script = Script(source, pos[0], pos[1], '')
return script.completions()

View File

@@ -142,9 +142,19 @@ class BaseDefinition(object):
def path(self):
"""The module path."""
path = []
def insert_nonnone(x):
if x:
path.insert(0, x)
if not isinstance(self._definition, keywords.Keyword):
par = self._definition
while par is not None:
if isinstance(par, pr.Import):
insert_nonnone(par.namespace)
insert_nonnone(par.from_ns)
if par.relative_count == 0:
break
with common.ignored(AttributeError):
path.insert(0, par.name)
par = par.parent

View File

@@ -318,7 +318,9 @@ def find_name(scope, name_str, position=None, search_global=False,
exc = pr.Class, pr.Function
until = lambda: par.parent.parent.get_parent_until(exc)
if par.isinstance(pr.Flow):
if par is None:
pass
elif par.isinstance(pr.Flow):
if par.command == 'for':
result += handle_for_loops(par)
else:
@@ -554,16 +556,33 @@ def assign_tuples(tup, results, seek_name):
else:
r = eval_results(i)
# are there still tuples or is it just a Call.
if isinstance(command, pr.Array):
# These are "sub"-tuples.
result += assign_tuples(command, r, seek_name)
else:
if command.name.names[-1] == seek_name:
result += r
# LHS of tuples can be nested, so resolve it recursively
result += find_assignments(command, r, seek_name)
return result
def find_assignments(lhs, results, seek_name):
"""
Check if `seek_name` is in the left hand side `lhs` of assignment.
`lhs` can simply be a variable (`pr.Call`) or a tuple/list (`pr.Array`)
representing the following cases::
a = 1 # lhs is pr.Call
(a, b) = 2 # lhs is pr.Array
:type lhs: pr.Call
:type results: list
:type seek_name: str
"""
if isinstance(lhs, pr.Array):
return assign_tuples(lhs, results, seek_name)
elif lhs.name.names[-1] == seek_name:
return results
else:
return []
@recursion.RecursionDecorator
@cache.memoize_default(default=())
def follow_statement(stmt, seek_name=None):
@@ -587,7 +606,7 @@ def follow_statement(stmt, seek_name=None):
if len(stmt.get_set_vars()) > 1 and seek_name and stmt.assignment_details:
new_result = []
for ass_commands, op in stmt.assignment_details:
new_result += assign_tuples(ass_commands[0], result, seek_name)
new_result += find_assignments(ass_commands[0], result, seek_name)
result = new_result
return set(result)
@@ -775,6 +794,9 @@ def goto(stmt, call_path=None):
commands = stmt.get_commands()
assert len(commands) == 1
call = commands[0]
if isinstance(call, (str, unicode)):
call_path = [call]
else:
call_path = list(call.generate_call_path())
scope = stmt.get_parent_until(pr.IsScope)

View File

@@ -162,9 +162,9 @@ class ImportPath(pr.Base):
# If you edit e.g. gunicorn, there will be imports like this:
# `from gunicorn import something`. But gunicorn is not in the
# sys.path. Therefore look if gunicorn is a parent directory, #56.
parts = self.file_path.split(os.path.sep)
in_path = []
if self.import_path:
parts = self.file_path.split(os.path.sep)
for i, p in enumerate(parts):
if p == self.import_path[0]:
new = os.path.sep.join(parts[:i])

171
jedi/interpret.py Normal file
View File

@@ -0,0 +1,171 @@
"""
Module to handle interpreted Python objects.
"""
import itertools
import tokenize
from jedi import parsing_representation as pr
class ObjectImporter(object):
"""
Import objects in "raw" namespace such as :func:`locals`.
"""
def __init__(self, scope):
self.scope = scope
count = itertools.count()
self._genname = lambda: '*jedi-%s*' % next(count)
"""
Generate unique variable names to avoid name collision.
To avoid name collision to already defined names, generated
names are invalid as Python identifier.
"""
def import_raw_namespace(self, raw_namespace):
"""
Import interpreted Python objects in a namespace.
Three kinds of objects are treated here.
1. Functions and classes. The objects imported like this::
from os.path import join
2. Modules. The objects imported like this::
import os
3. Instances. The objects created like this::
from datetime import datetime
dt = datetime(2013, 1, 1)
:type raw_namespace: dict
:arg raw_namespace: e.g., the dict given by `locals`
"""
scope = self.scope
for (variable, obj) in raw_namespace.items():
objname = getattr(obj, '__name__', None)
# Import functions and classes
module = getattr(obj, '__module__', None)
if module and objname:
fakeimport = self.make_fakeimport(module, objname, variable)
scope.add_import(fakeimport)
continue
# Import modules
if getattr(obj, '__file__', None) and objname:
fakeimport = self.make_fakeimport(objname)
scope.add_import(fakeimport)
continue
# Import instances
objclass = getattr(obj, '__class__', None)
module = getattr(objclass, '__module__', None)
if objclass and module:
alias = self._genname()
fakeimport = self.make_fakeimport(module, objclass.__name__,
alias)
fakestmt = self.make_fakestatement(variable, alias, call=True)
scope.add_import(fakeimport)
scope.add_statement(fakestmt)
continue
def make_fakeimport(self, module, variable=None, alias=None):
"""
Make a fake import object.
The following statements are created depending on what parameters
are given:
- only `module`: ``import <module>``
- `module` and `variable`: ``from <module> import <variable>``
- all: ``from <module> import <variable> as <alias>``
:type module: str
:arg module: ``<module>`` part in ``from <module> import ...``
:type variable: str
:arg variable: ``<variable>`` part in ``from ... import <variable>``
:type alias: str
:arg alias: ``<alias>`` part in ``... import ... as <alias>``.
:rtype: :class:`parsing_representation.Import`
"""
submodule = self.scope._sub_module
if variable:
varname = pr.Name(
module=submodule,
names=[(variable, (-1, 0))],
start_pos=(-1, 0),
end_pos=(None, None))
else:
varname = None
modname = pr.Name(
module=submodule,
names=[(module, (-1, 0))],
start_pos=(-1, 0),
end_pos=(None, None))
if alias:
aliasname = pr.Name(
module=submodule,
names=[(alias, (-1, 0))],
start_pos=(-1, 0),
end_pos=(None, None))
else:
aliasname = None
if varname:
fakeimport = pr.Import(
module=submodule,
namespace=varname,
from_ns=modname,
alias=aliasname,
start_pos=(-1, 0),
end_pos=(None, None))
else:
fakeimport = pr.Import(
module=submodule,
namespace=modname,
alias=aliasname,
start_pos=(-1, 0),
end_pos=(None, None))
return fakeimport
def make_fakestatement(self, lhs, rhs, call=False):
"""
Make a fake statement object that represents ``lhs = rhs``.
:type call: bool
:arg call: When `call` is true, make a fake statement that represents
``lhs = rhs()``.
:rtype: :class:`parsing_representation.Statement`
"""
submodule = self.scope._sub_module
lhsname = pr.Name(
module=submodule,
names=[(lhs, (0, 0))],
start_pos=(0, 0),
end_pos=(None, None))
rhsname = pr.Name(
module=submodule,
names=[(rhs, (0, 0))],
start_pos=(0, 0),
end_pos=(None, None))
token_list = [lhsname, (tokenize.OP, '=', (0, 0)), rhsname]
if call:
token_list.extend([
(tokenize.OP, '(', (0, 0)),
(tokenize.OP, ')', (0, 0)),
])
return pr.Statement(
module=submodule,
set_vars=[lhsname],
used_vars=[rhsname],
token_list=token_list,
start_pos=(0, 0),
end_pos=(None, None))

View File

@@ -141,6 +141,7 @@ class ModuleWithCursor(Module):
last_line = self.get_line(self._line_temp)
if last_line and last_line[-1] == '\\':
line = last_line[:-1] + ' ' + line
self._line_length = len(last_line)
else:
break
return line[::-1]
@@ -187,6 +188,7 @@ class ModuleWithCursor(Module):
elif token_type == tokenize.NUMBER:
pass
else:
self._column_temp = self._line_length - end[1]
break
self._column_temp = self._line_length - end[1]

View File

@@ -56,7 +56,7 @@ class Parser(object):
self.start_pos = self.end_pos = 1 + offset[0], offset[1]
# initialize global Scope
self.module = pr.SubModule(module_path, self.start_pos, top_module)
self.scope = self.module
self._scope = self.module
self.current = (None, None)
source = source + '\n' # end with \n, because the parser needs it
@@ -390,7 +390,7 @@ class Parser(object):
#print 'new_stat', set_vars, used_vars
if self.freshscope and not self.no_docstr and len(tok_list) == 1 \
and self.last_token[0] == tokenize.STRING:
self.scope.add_docstr(self.last_token[1])
self._scope.add_docstr(self.last_token[1])
return None, tok
else:
stmt = stmt_class(self.module, set_vars, used_vars, tok_list,
@@ -408,7 +408,7 @@ class Parser(object):
and len(stmt.token_list) == 1
and first_tok[0] == tokenize.STRING):
# ... then set it as a docstring
self.scope.statements[-1].add_docstr(first_tok[1])
self._scope.statements[-1].add_docstr(first_tok[1])
if tok in always_break + not_first_break:
self._gen.push_last_back()
@@ -429,7 +429,7 @@ class Parser(object):
self.start_pos, self.end_pos = start_pos, end_pos
except (StopIteration, common.MultiLevelStopIteration):
# on finish, set end_pos correctly
s = self.scope
s = self._scope
while s is not None:
if isinstance(s, pr.Module) \
and not isinstance(s, pr.SubModule):
@@ -443,8 +443,8 @@ class Parser(object):
or self.user_scope is None
and self.start_pos[0] >= self.user_position[0]):
debug.dbg('user scope found [%s] = %s' %
(self.parserline.replace('\n', ''), repr(self.scope)))
self.user_scope = self.scope
(self.parserline.replace('\n', ''), repr(self._scope)))
self.user_scope = self._scope
self.last_token = self.current
self.current = (typ, tok)
return self.current
@@ -472,29 +472,29 @@ class Parser(object):
#debug.dbg('main: tok=[%s] type=[%s] indent=[%s]'\
# % (tok, tokenize.tok_name[token_type], start_position[0]))
while token_type == tokenize.DEDENT and self.scope != self.module:
while token_type == tokenize.DEDENT and self._scope != self.module:
token_type, tok = self.next()
if self.start_pos[1] <= self.scope.start_pos[1]:
self.scope.end_pos = self.start_pos
self.scope = self.scope.parent
if isinstance(self.scope, pr.Module) \
and not isinstance(self.scope, pr.SubModule):
self.scope = self.module
if self.start_pos[1] <= self._scope.start_pos[1]:
self._scope.end_pos = self.start_pos
self._scope = self._scope.parent
if isinstance(self._scope, pr.Module) \
and not isinstance(self._scope, pr.SubModule):
self._scope = self.module
# check again for unindented stuff. this is true for syntax
# errors. only check for names, because thats relevant here. If
# some docstrings are not indented, I don't care.
while self.start_pos[1] <= self.scope.start_pos[1] \
while self.start_pos[1] <= self._scope.start_pos[1] \
and (token_type == tokenize.NAME or tok in ['(', '['])\
and self.scope != self.module:
self.scope.end_pos = self.start_pos
self.scope = self.scope.parent
if isinstance(self.scope, pr.Module) \
and not isinstance(self.scope, pr.SubModule):
self.scope = self.module
and self._scope != self.module:
self._scope.end_pos = self.start_pos
self._scope = self._scope.parent
if isinstance(self._scope, pr.Module) \
and not isinstance(self._scope, pr.SubModule):
self._scope = self.module
use_as_parent_scope = self.top_module if isinstance(self.scope,
pr.SubModule) else self.scope
use_as_parent_scope = self.top_module if isinstance(self._scope,
pr.SubModule) else self._scope
first_pos = self.start_pos
if tok == 'def':
func = self._parse_function()
@@ -503,7 +503,7 @@ class Parser(object):
self.start_pos[0])
continue
self.freshscope = True
self.scope = self.scope.add_scope(func, self._decorators)
self._scope = self._scope.add_scope(func, self._decorators)
self._decorators = []
elif tok == 'class':
cls = self._parse_class()
@@ -511,7 +511,7 @@ class Parser(object):
debug.warning("class: syntax error@%s" % self.start_pos[0])
continue
self.freshscope = True
self.scope = self.scope.add_scope(cls, self._decorators)
self._scope = self._scope.add_scope(cls, self._decorators)
self._decorators = []
# import stuff
elif tok == 'import':
@@ -522,7 +522,7 @@ class Parser(object):
i = pr.Import(self.module, first_pos, end_pos, m,
alias, defunct=defunct)
self._check_user_stmt(i)
self.scope.add_import(i)
self._scope.add_import(i)
if not imports:
i = pr.Import(self.module, first_pos, self.end_pos, None,
defunct=True)
@@ -559,7 +559,7 @@ class Parser(object):
alias, mod, star, relative_count,
defunct=defunct or defunct2)
self._check_user_stmt(i)
self.scope.add_import(i)
self._scope.add_import(i)
self.freshscope = False
#loops
elif tok == 'for':
@@ -569,7 +569,7 @@ class Parser(object):
if tok == ':':
s = [] if statement is None else [statement]
f = pr.ForFlow(self.module, s, first_pos, set_stmt)
self.scope = self.scope.add_statement(f)
self._scope = self._scope.add_statement(f)
else:
debug.warning('syntax err, for flow started @%s',
self.start_pos[0])
@@ -612,13 +612,13 @@ class Parser(object):
# the flow statement, because a dedent releases the
# main scope, so just take the last statement.
try:
s = self.scope.statements[-1].set_next(f)
s = self._scope.statements[-1].set_next(f)
except (AttributeError, IndexError):
# If set_next doesn't exist, just add it.
s = self.scope.add_statement(f)
s = self._scope.add_statement(f)
else:
s = self.scope.add_statement(f)
self.scope = s
s = self._scope.add_statement(f)
self._scope = s
else:
for i in inputs:
i.parent = use_as_parent_scope
@@ -629,7 +629,7 @@ class Parser(object):
s = self.start_pos
self.freshscope = False
# add returns to the scope
func = self.scope.get_parent_until(pr.Function)
func = self._scope.get_parent_until(pr.Function)
if tok == 'yield':
func.is_generator = True
@@ -646,7 +646,7 @@ class Parser(object):
elif tok == 'global':
stmt, tok = self._parse_statement(self.current)
if stmt:
self.scope.add_statement(stmt)
self._scope.add_statement(stmt)
for name in stmt.used_vars:
# add the global to the top, because there it is
# important.
@@ -659,8 +659,9 @@ class Parser(object):
continue
elif tok == 'assert':
stmt, tok = self._parse_statement()
if stmt is not None:
stmt.parent = use_as_parent_scope
self.scope.asserts.append(stmt)
self._scope.asserts.append(stmt)
# default
elif token_type in [tokenize.NAME, tokenize.STRING,
tokenize.NUMBER] \
@@ -670,7 +671,7 @@ class Parser(object):
# by the statement parser.
stmt, tok = self._parse_statement(self.current)
if stmt:
self.scope.add_statement(stmt)
self._scope.add_statement(stmt)
self.freshscope = False
else:
if token_type not in [tokenize.COMMENT, tokenize.INDENT,

View File

@@ -16,11 +16,11 @@ is the easiest way to write a parser. The same behaviour applies to ``Param``,
which is being used in a function definition.
The easiest way to play with this module is to use :class:`parsing.Parser`.
:attr:`parsing.Parser.scope` holds an instance of :class:`SubModule`:
:attr:`parsing.Parser.module` holds an instance of :class:`SubModule`:
>>> from jedi.parsing import Parser
>>> parser = Parser('import os', 'example.py')
>>> submodule = parser.scope
>>> submodule = parser.module
>>> submodule
<SubModule: example.py@1-1>
@@ -248,14 +248,14 @@ class Scope(Simple, IsScope):
... b = y
... b.c = z
... ''')
>>> parser.scope.get_defined_names()
>>> parser.module.get_defined_names()
[<Name: a@2,0>, <Name: b@3,0>]
Note that unlike :meth:`get_set_vars`, assignment to object
attribute does not change the result because it does not change
the defined names in this scope.
>>> parser.scope.get_set_vars()
>>> parser.module.get_set_vars()
[<Name: a@2,0>, <Name: b@3,0>, <Name: b.c@4,0>]
"""

25
jedi/replstartup.py Normal file
View File

@@ -0,0 +1,25 @@
"""
``PYTHONSTARTUP`` to use Jedi in your Python interpreter.
To use Jedi completion in Python interpreter, add the following in your shell
setup (e.g., ``.bashrc``)::
export PYTHONSTARTUP="$(python -m jedi)"
Then you will be able to use Jedi completer in your Python interpreter::
$ python
Python 2.7.2+ (default, Jul 20 2012, 22:15:08)
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.path.join().split().in<TAB> # doctest: +SKIP
os.path.join().split().index os.path.join().split().insert
"""
import jedi.utils
jedi.utils.setup_readline()
del jedi
# Note: try not to do many things here, as it will contaminate global
# namespace of the interpreter.

View File

@@ -127,8 +127,10 @@ cache_directory = os.path.expanduser(_cache_directory)
"""
The path where all the caches can be found.
On Linux, this defaults to ``~/.cache/jedi/``, on OS X to ``~/.jedi/`` and on
Windows to ``%APPDATA%\\Jedi\\Jedi\\``.
On Linux, this defaults to ``~/.cache/jedi/``, on OS X to
``~/Library/Caches/Jedi/`` and on Windows to ``%APPDATA%\\Jedi\\Jedi\\``.
On Linux, if environment variable ``$XDG_CACHE_HOME`` is set,
``$XDG_CACHE_HOME/jedi`` is used instead of the default one.
"""
# ----------------

110
jedi/utils.py Normal file
View File

@@ -0,0 +1,110 @@
"""
Utilities for end-users.
"""
from rlcompleter import Completer
from jedi import Interpreter
_NON_DELIMS = ' \t\n()'
"""
:class:`rcompleter.Completer` assumes these characters to be delimiter
(i.e., :meth:`rcompleter.Completer.complete` does not expect these
characters) but :class:`JediRLCompleter` can handle them.
"""
try:
import readline
except ImportError:
pass
else:
_READLINE_DEFAULT_DELIMS = readline.get_completer_delims()
_READLINE_JEDI_DELIMS = ''.join(
set(_READLINE_DEFAULT_DELIMS) - set(_NON_DELIMS))
class JediRLCompleter(Completer):
"""
:class:`rlcompleter.Completer` enhanced by Jedi.
This class tries matchers defined in :class:`.Completer` first.
If they fail, :class:`jedi.Interpreter` is used.
>>> import os
>>> completer = JediRLCompleter(locals())
>>> completer.complete('os.path.joi', 0) # completion w/o Jedi
'os.path.join('
>>> completer.complete('os.path.join().s', 0) # completion with Jedi
'os.path.join().split'
"""
def _jedi_matches(self, text):
completions = Interpreter(text, [self.namespace]).completions()
return [text + c.complete for c in completions]
@staticmethod
def _split_for_default_matcher(text, delims=_NON_DELIMS):
"""
Split `text` before passing it to :meth:`Completer.attr_matches` etc.
>>> JediRLCompleter._split_for_default_matcher('f(')
('f(', '')
>>> JediRLCompleter._split_for_default_matcher('f().g')
('f()', '.g')
"""
import re
m = re.match(r"(.*[{0}])([^{0}]*)".format(re.escape(delims)), text)
if not m:
return ('', text)
return m.groups()
def _find_matches(self, default_matcher, text):
"""
Common part for :meth:`attr_matches` and :meth:`global_matches`.
Try `default_matcher` first and return what it returns if
it is not empty. Otherwise, try :meth:`_jedi_matches`.
:arg default_matcher: :meth:`.Completer.attr_matches` or
:meth:`.Completer.global_matches`.
:arg str text: code to complete
"""
(pre, body) = self._split_for_default_matcher(text)
matches = default_matcher(self, body)
if matches:
return [pre + m for m in matches]
return self._jedi_matches(text)
def attr_matches(self, text):
# NOTE: Completer is old type class so `super` cannot be used here
return self._find_matches(Completer.attr_matches, text)
def global_matches(self, text):
# NOTE: Completer is old type class so `super` cannot be used here
return self._find_matches(Completer.global_matches, text)
def setup_readline():
"""
Install Jedi completer to :mod:`readline`.
This function setups :mod:`readline` to use Jedi in Python interactive
shell. If you want to use custom ``PYTHONSTARTUP`` file, you can call
this function like this:
>>> from jedi.utils import setup_readline
>>> setup_readline()
"""
try:
import readline
except ImportError:
print("Module readline not available.")
else:
readline.set_completer(JediRLCompleter().complete)
readline.parse_and_bind("tab: complete")
readline.set_completer_delims(_READLINE_JEDI_DELIMS)

311
sith.py Executable file
View File

@@ -0,0 +1,311 @@
#!/usr/bin/env python
"""
Sith attacks (and helps debugging) Jedi.
Randomly search Python files and run Jedi on it. Exception and used
arguments are recorded to ``./record.json`` (specified by --record)::
%(prog)s random /path/to/sourcecode
Redo recorded exception::
%(prog)s redo
Fallback to pdb when error is raised::
%(prog)s --pdb random
%(prog)s --pdb redo
"""
from __future__ import print_function, division, unicode_literals
import json
import os
import random
import sys
import traceback
import jedi
_unspecified = object()
class SourceCode(object):
def __init__(self, path):
self.path = path
with open(path) as f:
self.source = f.read()
self.lines = self.source.splitlines()
self.maxline = len(self.lines)
def choose_script_args(self):
line = random.randint(1, self.maxline)
column = random.randint(0, len(self.lines[line - 1]))
return (self.source, line, column, self.path)
class SourceFinder(object):
def __init__(self, rootpath):
self.rootpath = rootpath
self.files = list(self.search_files())
def search_files(self):
for root, dirnames, filenames in os.walk(self.rootpath):
for name in filenames:
if name.endswith('.py'):
yield os.path.join(root, name)
def choose_source(self):
# FIXME: try same file for several times
return SourceCode(random.choice(self.files))
class BaseAttacker(object):
def __init__(self):
self.record = {'data': []}
def attack(self, operation, *args):
script = jedi.Script(*args)
op = getattr(script, operation)
op()
def add_record(self, exc_info, operation, args):
(_type, value, tb) = exc_info
self.record['data'].append({
'traceback': traceback.format_tb(tb),
'error': repr(value),
'operation': operation,
'args': args,
})
def get_record(self, recid):
return self.record['data'][recid]
def save_record(self, path):
directory = os.path.dirname(os.path.abspath(path))
if not os.path.isdir(directory):
os.makedirs(directory)
with open(path, 'w') as f:
json.dump(self.record, f)
def load_record(self, path):
with open(path) as f:
self.record = json.load(f)
return self.record
def add_arguments(self, parser):
parser.set_defaults(func=self.do_run)
def get_help(self):
for line in self.__doc__.splitlines():
line = line.strip()
if line:
return line
class MixinPrinter(object):
def print_record(self, recid=-1):
data = self.get_record(recid)
print(*data['traceback'], end='')
print("""
{error} is raised by running Script(...).{operation}() with
line : {args[1]}
column: {args[2]}
path : {args[3]}
""".format(**data))
class MixinLoader(object):
def add_arguments(self, parser):
super(MixinLoader, self).add_arguments(parser)
parser = parser.add_argument(
'recid', default=0, nargs='?', type=int, help="""
This option currently has no effect as random attack record
only one error.
""")
def do_run(self, record, recid):
self.load_record(record)
class AttackReporter(object):
def __init__(self):
self.tries = 0
self.errors = 0
def __iter__(self):
return self
def __next__(self):
self.tries += 1
sys.stderr.write('.')
sys.stderr.flush()
return self.tries
next = __next__
def error(self):
self.errors += 1
sys.stderr.write('\n')
sys.stderr.flush()
print('{0}th error is encountered after {1} tries.'
.format(self.errors, self.tries))
class RandomAttacker(MixinPrinter, BaseAttacker):
"""
Randomly run Script().<method>() against files under <rootpath>.
"""
operations = [
'completions', 'goto_assignments', 'goto_definitions', 'usages',
'call_signatures']
def choose_operation(self):
return random.choice(self.operations)
def generate_attacks(self, maxtries, finder):
for _ in range(maxtries):
src = finder.choose_source()
operation = self.choose_operation()
yield (operation, src.choose_script_args())
def do_run(self, record, rootpath, maxtries):
finder = SourceFinder(rootpath)
reporter = AttackReporter()
for (operation, args) in self.generate_attacks(maxtries, finder):
reporter.next()
try:
self.attack(operation, *args)
except jedi.NotFoundError:
pass
except Exception:
self.add_record(sys.exc_info(), operation, args)
reporter.error()
self.print_record()
raise
finally:
self.save_record(record)
def add_arguments(self, parser):
super(RandomAttacker, self).add_arguments(parser)
parser.add_argument(
'--maxtries', '-l', default=10000, type=int)
parser.add_argument(
'rootpath', default='.', nargs='?',
help='root directory to look for Python files.')
class RedoAttacker(MixinLoader, BaseAttacker):
"""
Redo recorded attack.
"""
def do_run(self, record, recid):
super(RedoAttacker, self).do_run(record, recid)
data = self.get_record(recid)
try:
self.attack(data['operation'], *data['args'])
except:
traceback.print_exc()
raise
class ShowRecord(MixinLoader, MixinPrinter, BaseAttacker):
"""
Show recorded errors.
"""
def do_run(self, record, recid):
super(ShowRecord, self).do_run(record, recid)
self.print_record()
class AttackApp(object):
def __init__(self):
self.parsers = []
self.attackers = []
def run(self, args=None):
parser = self.get_parser()
self.do_run(**vars(parser.parse_args(args)))
def do_run(self, func, debugger, fs_cache, **kwds):
if fs_cache is _unspecified:
jedi.settings.use_filesystem_cache = False
else:
jedi.settings.cache_directory = fs_cache
try:
func(**kwds)
except:
if debugger:
einfo = sys.exc_info()
pdb = __import__(debugger)
pdb.post_mortem(einfo if debugger == 'pudb' else einfo[2])
sys.exit(1)
def add_parser(self, attacker_class, *args, **kwds):
attacker = attacker_class()
parser = self.subparsers.add_parser(
*args,
help=attacker.get_help(),
description=attacker.__doc__,
**kwds)
attacker.add_arguments(parser)
# Not required, just fore debugging:
self.parsers.append(parser)
self.attackers.append(attacker)
def get_parser(self):
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=__doc__)
parser.add_argument(
'--record', '-R', default='record.json',
help='Exceptions are recorded in here (default: %(default)s).')
parser.add_argument(
'--pdb', dest='debugger', const='pdb', action='store_const',
help='Launch pdb when error is raised.')
parser.add_argument(
'--ipdb', dest='debugger', const='ipdb', action='store_const',
help='Launch ipdb when error is raised.')
parser.add_argument(
'--pudb', dest='debugger', const='pudb', action='store_const',
help='Launch pudb when error is raised.')
parser.add_argument(
'--fs-cache', '-C', default=_unspecified,
help="""
By default, file system cache is off for reproducibility.
Pass a temporary directory to use file system cache.
It is set to ``jedi.settings.cache_directory``.
""")
self.subparsers = parser.add_subparsers()
self.add_parser(RandomAttacker, 'random')
self.add_parser(RedoAttacker, 'redo')
self.add_parser(ShowRecord, 'show')
return parser
if __name__ == '__main__':
try:
import argparse
except ImportError:
print('The argparse module (Python>=2.7) is needed to run sith.')
sys.exit(1)
app = AttackApp()
app.run()

View File

@@ -94,6 +94,28 @@ a4
b4
# -----------------
# multiple assignments
# -----------------
a = b = 1
#? int()
a
#? int()
b
(a, b) = (c, (e, f)) = ('2', (3, 4))
#? str()
a
#? tuple()
b
#? str()
c
#? int()
e
#? int()
f
# -----------------
# unnessecary braces
# -----------------

View File

@@ -3,7 +3,13 @@ import textwrap
import pytest
from jedi import api
import jedi
def test_is_keyword():
results = jedi.Script('import ', 1, 1, None).goto_definitions()
assert len(results) == 1 and results[0].is_keyword == True
results = jedi.Script('str', 1, 1, None).goto_definitions()
assert len(results) == 1 and results[0].is_keyword == False
def make_definitions():
"""

View File

@@ -0,0 +1,75 @@
"""
Tests for `api.defined_names`.
"""
import textwrap
from jedi import api
from .base import TestBase
class TestDefinedNames(TestBase):
def assert_definition_names(self, definitions, names):
self.assertEqual([d.name for d in definitions], names)
def check_defined_names(self, source, names):
definitions = api.defined_names(textwrap.dedent(source))
self.assert_definition_names(definitions, names)
return definitions
def test_get_definitions_flat(self):
self.check_defined_names("""
import module
class Class:
pass
def func():
pass
data = None
""", ['module', 'Class', 'func', 'data'])
def test_dotted_assignment(self):
self.check_defined_names("""
x = Class()
x.y.z = None
""", ['x'])
def test_multiple_assignment(self):
self.check_defined_names("""
x = y = None
""", ['x', 'y'])
def test_multiple_imports(self):
self.check_defined_names("""
from module import a, b
from another_module import *
""", ['a', 'b'])
def test_nested_definitions(self):
definitions = self.check_defined_names("""
class Class:
def f():
pass
def g():
pass
""", ['Class'])
subdefinitions = definitions[0].defined_names()
self.assert_definition_names(subdefinitions, ['f', 'g'])
self.assertEqual([d.full_name for d in subdefinitions],
['Class.f', 'Class.g'])
def test_nested_class(self):
definitions = self.check_defined_names("""
class L1:
class L2:
class L3:
def f(): pass
def f(): pass
def f(): pass
def f(): pass
""", ['L1', 'f'])
subdefs = definitions[0].defined_names()
subsubdefs = subdefs[0].defined_names()
self.assert_definition_names(subdefs, ['L2', 'f'])
self.assert_definition_names(subsubdefs, ['L3', 'f'])
self.assert_definition_names(subsubdefs[0].defined_names(), ['f'])

93
test/test_full_name.py Normal file
View File

@@ -0,0 +1,93 @@
"""
Tests for :attr:`.BaseDefinition.full_name`.
There are three kinds of test:
#. Test classes derived from :class:`MixinTestFullName`.
Child class defines :meth:`.get_definitions` to alter how
the api definition instance is created.
#. :class:`TestFullDefinedName` is to test combination of
:attr:`.full_name` and :func:`.defined_names`.
#. Misc single-function tests.
"""
import textwrap
import jedi
from jedi import api_classes
from .base import TestBase
class MixinTestFullName(object):
def get_definitions(self, source):
"""
Get definition objects of the variable at the end of `source`.
"""
raise NotImplementedError
def check(self, source, desired):
definitions = self.get_definitions(textwrap.dedent(source))
self.assertEqual(definitions[0].full_name, desired)
def test_os_path_join(self):
self.check('import os; os.path.join', 'os.path.join')
def test_builtin(self):
self.check('type', 'type')
def test_from_import(self):
self.check('from os import path', 'os.path')
class TestFullNameWithGotoDefinitions(MixinTestFullName, TestBase):
get_definitions = TestBase.goto_definitions
def test_tuple_mapping(self):
self.check("""
import re
any_re = re.compile('.*')
any_re""", 're.RegexObject')
class TestFullNameWithCompletions(MixinTestFullName, TestBase):
get_definitions = TestBase.completions
class TestFullDefinedName(TestBase):
"""
Test combination of :attr:`.full_name` and :func:`.defined_names`.
"""
def check(self, source, desired):
definitions = jedi.defined_names(textwrap.dedent(source))
full_names = [d.full_name for d in definitions]
self.assertEqual(full_names, desired)
def test_local_names(self):
self.check("""
def f(): pass
class C: pass
""", ['f', 'C'])
def test_imports(self):
self.check("""
import os
from os import path
from os.path import join
from os import path as opath
""", ['os', 'os.path', 'os.path.join', 'os.path'])
def test_keyword_full_name_should_be_none():
"""issue #94"""
# Using `from jedi.keywords import Keyword` here does NOT work
# in Python 3. This is due to the import hack jedi using.
Keyword = api_classes.keywords.Keyword
d = api_classes.Definition(Keyword('(', (0, 0)))
assert d.full_name is None

View File

@@ -14,9 +14,8 @@ import textwrap
from .base import TestBase, unittest, cwd_at
import jedi
from jedi._compatibility import utf8, unicode
from jedi._compatibility import utf8, unicode, is_py33
from jedi import api, parsing, common
api_classes = api.api_classes
#jedi.set_debug_function(jedi.debug.print_to_stdout)
@@ -314,14 +313,29 @@ class TestRegression(TestBase):
types = [o.type for o in objs]
assert 'import' not in types and 'class' in types
def test_keyword_definition_doc(self):
def test_keyword(self):
""" github jedi-vim issue #44 """
defs = self.goto_definitions("print")
assert [d.doc for d in defs]
defs = self.goto_definitions("import")
assert len(defs) == 1 and [1 for d in defs if d.doc]
# unrelated to #44
defs = self.goto_assignments("import")
assert len(defs) == 0
completions = self.completions("import", (1,1))
assert len(completions) == 0
with common.ignored(jedi.NotFoundError): # TODO shouldn't throw that.
defs = self.goto_definitions("assert")
assert len(defs) == 1
assert [d.doc for d in defs]
def test_goto_assignments_keyword(self):
"""
Bug: goto assignments on ``in`` used to raise AttributeError::
'unicode' object has no attribute 'generate_call_path'
"""
self.goto_assignments('in')
def test_goto_following_on_imports(self):
s = "import multiprocessing.dummy; multiprocessing.dummy"
@@ -377,10 +391,47 @@ class TestRegression(TestBase):
# jedi issue #150
s = "x()\nx( )\nx( )\nx ( )"
parser = parsing.Parser(s)
for i, s in enumerate(parser.scope.statements, 3):
for i, s in enumerate(parser.module.statements, 3):
for c in s.get_commands():
self.assertEqual(c.execution.end_pos[1], i)
def check_definition_by_marker(self, source, after_cursor, names):
r"""
Find definitions specified by `after_cursor` and check what found
For example, for the following configuration, you can pass
``after_cursor = 'y)'``.::
function(
x, y)
\
`- You want cursor to be here
"""
source = textwrap.dedent(source)
for (i, line) in enumerate(source.splitlines()):
if after_cursor in line:
break
column = len(line) - len(after_cursor)
defs = self.goto_definitions(source, (i + 1, column))
self.assertEqual([d.name for d in defs], names)
def test_backslash_continuation(self):
"""
Test that ModuleWithCursor.get_path_until_cursor handles continuation
"""
self.check_definition_by_marker(r"""
x = 0
a = \
[1, 2, 3, 4, 5, 6, 7, 8, 9, x] # <-- here
""", '] # <-- here', ['int'])
def test_backslash_continuation_and_bracket(self):
self.check_definition_by_marker(r"""
x = 0
a = \
[1, 2, 3, 4, 5, 6, 7, 8, 9, (x)] # <-- here
""", '(x)] # <-- here', [None])
class TestDocstring(TestBase):
@@ -410,28 +461,6 @@ class TestDocstring(TestBase):
class TestFeature(TestBase):
def test_full_name(self):
""" feature request #61"""
assert self.completions('import os; os.path.join')[0].full_name \
== 'os.path.join'
def test_keyword_full_name_should_be_none(self):
"""issue #94"""
# Using `from jedi.keywords import Keyword` here does NOT work
# in Python 3. This is due to the import hack jedi using.
Keyword = api_classes.keywords.Keyword
d = api_classes.Definition(Keyword('(', (0, 0)))
assert d.full_name is None
def test_full_name_builtin(self):
self.assertEqual(self.completions('type')[0].full_name, 'type')
def test_full_name_tuple_mapping(self):
s = """
import re
any_re = re.compile('.*')
any_re"""
self.assertEqual(self.goto_definitions(s)[0].full_name, 're.RegexObject')
def test_preload_modules(self):
def check_loaded(*modules):
@@ -453,81 +482,6 @@ class TestFeature(TestBase):
cache.parser_cache = temp_cache
def test_quick_completion(self):
sources = [
('import json; json.l', (1, 19)),
('import json; json.l ', (1, 19)),
('import json\njson.l', (2, 6)),
('import json\njson.l ', (2, 6)),
('import json\njson.l\n\n', (2, 6)),
('import json\njson.l \n\n', (2, 6)),
('import json\njson.l \n \n\n', (2, 6)),
]
for source, pos in sources:
# Run quick_complete
quick_completions = api._quick_complete(source)
# Run real completion
script = jedi.Script(source, pos[0], pos[1], '')
real_completions = script.completions()
# Compare results
quick_values = [(c.full_name, c.line, c.column) for c in quick_completions]
real_values = [(c.full_name, c.line, c.column) for c in real_completions]
self.assertEqual(quick_values, real_values)
class TestGetDefinitions(TestBase):
def test_get_definitions_flat(self):
definitions = api.defined_names("""
import module
class Class:
pass
def func():
pass
data = None
""")
self.assertEqual([d.name for d in definitions],
['module', 'Class', 'func', 'data'])
def test_dotted_assignment(self):
definitions = api.defined_names("""
x = Class()
x.y.z = None
""")
self.assertEqual([d.name for d in definitions],
['x'])
def test_multiple_assignment(self):
definitions = api.defined_names("""
x = y = None
""")
self.assertEqual([d.name for d in definitions],
['x', 'y'])
def test_multiple_imports(self):
definitions = api.defined_names("""
from module import a, b
from another_module import *
""")
self.assertEqual([d.name for d in definitions],
['a', 'b'])
def test_nested_definitions(self):
definitions = api.defined_names("""
class Class:
def f():
pass
def g():
pass
""")
self.assertEqual([d.name for d in definitions],
['Class'])
subdefinitions = definitions[0].defined_names()
self.assertEqual([d.name for d in subdefinitions],
['f', 'g'])
self.assertEqual([d.full_name for d in subdefinitions],
['Class.f', 'Class.g'])
class TestSpeed(TestBase):
def _check_speed(time_per_run, number=4, run_warm=True):
@@ -562,6 +516,44 @@ class TestSpeed(TestBase):
#print(jedi.imports.imports_processed)
class TestInterpreterAPI(unittest.TestCase):
def check_interpreter_complete(self, source, namespace, completions,
**kwds):
script = api.Interpreter(source, [namespace], **kwds)
cs = script.complete()
actual = [c.word for c in cs]
self.assertEqual(sorted(actual), sorted(completions))
def test_complete_raw_function(self):
from os.path import join
self.check_interpreter_complete('join().up',
locals(),
['upper'])
def test_complete_raw_function_different_name(self):
from os.path import join as pjoin
self.check_interpreter_complete('pjoin().up',
locals(),
['upper'])
def test_complete_raw_module(self):
import os
self.check_interpreter_complete('os.path.join().up',
locals(),
['upper'])
def test_complete_raw_instance(self):
import datetime
dt = datetime.datetime(2013, 1, 1)
completions = ['time', 'timetz', 'timetuple']
if is_py33:
completions += ['timestamp']
self.check_interpreter_complete('(dt - dt).ti',
locals(),
completions)
def test_settings_module():
"""
jedi.settings and jedi.cache.settings must be the same module.

View File

@@ -16,3 +16,8 @@ deps =
commands =
coverage run --source jedi -m py.test
coverage report
[testenv:sith]
deps =
argparse
commands =
{envpython} sith.py --record {envtmpdir}/record.json random {posargs:jedi}