Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Monkey patch Jedi 0.10.0 for numpydoc #4121

Merged
merged 10 commits into from
Mar 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ a Python version greater than 2.7 (Python 3.2 is not supported anymore).
* **Python** 2.7 or 3.3+
* **PyQt5** 5.2+ or **PyQt4** 4.6+: PyQt5 is recommended.
* **qtconsole** 4.2.0+: Enhanced Python interpreter.
* **Rope** and **Jedi** 0.9.0: Editor code completion, calltips
* **Rope** and **Jedi**: Editor code completion, calltips
and go-to-definition.
* **Pyflakes**: Real-time code analysis.
* **Sphinx**: Rich text mode for the Help pane.
Expand Down
4 changes: 2 additions & 2 deletions continuous_integration/circle/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ if [ "$CIRCLE_NODE_INDEX" = "3" ]; then
export CONDA_DEPENDENCIES=""
else
export CONDA_DEPENDENCIES_FLAGS="--quiet"
export CONDA_DEPENDENCIES="rope jedi pyflakes sphinx pygments pylint pep8 psutil nbconvert \
export CONDA_DEPENDENCIES="rope pyflakes sphinx pygments pylint pep8 psutil nbconvert \
qtawesome pickleshare qtpy pyzmq chardet mock nomkl pandas \
pytest pytest-cov numpydoc scipy"
export PIP_DEPENDENCIES="coveralls pytest-qt pytest-xvfb flaky"
export PIP_DEPENDENCIES="coveralls pytest-qt pytest-xvfb flaky jedi"
fi


Expand Down
2 changes: 1 addition & 1 deletion continuous_integration/conda-recipes/spyder/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ requirements:
- rope 0.9.* # [py34 or py35]
- rope # [py27]
- pyflakes
- jedi 0.9.*
- jedi
- qtconsole
- nbconvert
- pygments
Expand Down
2 changes: 1 addition & 1 deletion doc/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ The requirements to run Spyder are:
enhanced Python interpreter.

* `Rope <http://rope.sourceforge.net/>`_ >=0.9.4 and
`Jedi <http://jedi.jedidjah.ch/en/latest/>` 0.9.0 -- for code completion,
`Jedi <http://jedi.jedidjah.ch/en/latest/>`_ >=0.9.0 -- for code completion,
go-to-definition and calltips on the Editor.

* `Pyflakes <http://pypi.python.org/pypi/pyflakes>`_ -- for real-time
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def run(self):

install_requires = [
'rope_py3k' if PY3 else 'rope>=0.9.4',
'jedi==0.9.0',
'jedi>=0.9.0',
'pyflakes',
'pygments>=2.0',
'qtconsole>=4.2.0',
Expand Down
187 changes: 105 additions & 82 deletions spyder/utils/introspection/jedi_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ def apply():
See [1] and [2] module docstring."""
from spyder.utils.programs import is_module_installed
if is_module_installed('jedi', '=0.9.0'):
if (is_module_installed('jedi', '=0.9.0') or
is_module_installed('jedi', '=0.10.0')):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change this to

if (is_module_installed('jedi', '>=0.9.0'):

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember that I wrote it that way so that when Jedi 0.11.0 comes out my patching won't thrash it(like it did when 0.10.0 came out). I am also hoping not to have to patch 0.11.0 :-)

I can still make the change if you think that's best.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, fair enough ;-) Please leave it as it is then.

import jedi
else:
raise ImportError("jedi %s can't be patched" % jedi.__version__)
raise ImportError("jedi not =0.9.0 or 0.10.0, can't be patched")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raise ImportError("jedi not >=0.9.0, can't be patched")


# [1] Adding numpydoc type returns to docstrings
from spyder.utils.introspection import numpy_docstr
Expand All @@ -36,11 +37,10 @@ def apply():

# [2] Adding type returns for compiled objects in jedi
# Patching jedi.evaluate.compiled.CompiledObject...
from jedi.evaluate.compiled import (
CompiledObject, builtin, _create_from_name, debug)
if is_module_installed('jedi', '=0.9.0'):
from jedi.evaluate.compiled import (builtin, _create_from_name,
debug, CompiledObject)

class CompiledObject(CompiledObject):
# ...adding docstrings int _execute_function...
def _execute_function(self, evaluator, params):
if self.type != 'funcdef':
return
Expand All @@ -58,87 +58,110 @@ def _execute_function(self, evaluator, params):
except AttributeError:
continue
else:
if isinstance(bltn_obj, CompiledObject) and bltn_obj.obj is None:
if (isinstance(bltn_obj, CompiledObject) and
bltn_obj.obj is None):
# We want everything except None.
continue
for result in evaluator.execute(bltn_obj, params):
yield result

# ...docstrings needs a raw_doc property
@property
def raw_doc(self):
try:
doc = unicode(self.doc)
except NameError: # python 3
doc = self.doc
return doc

jedi.evaluate.compiled.CompiledObject = CompiledObject

# [3] Fixing introspection for matplotlib Axes objects
# Patching jedi.evaluate.precedence...
from jedi.evaluate.precedence import tree, calculate

def calculate_children(evaluator, children):
"""
Calculate a list of children with operators.
"""
iterator = iter(children)
types = evaluator.eval_element(next(iterator))
for operator in iterator:
try:# PATCH: Catches StopIteration error
right = next(iterator)
if tree.is_node(operator, 'comp_op'): # not in / is not
operator = ' '.join(str(c.value) for c in operator.children)

# handle lazy evaluation of and/or here.
if operator in ('and', 'or'):
left_bools = set([left.py__bool__() for left in types])
if left_bools == set([True]):
if operator == 'and':
types = evaluator.eval_element(right)
elif left_bools == set([False]):
if operator != 'and':
types = evaluator.eval_element(right)
# Otherwise continue, because of uncertainty.
else: # Code for Jedi 0.10.0
from jedi.evaluate.compiled import debug, create
from jedi._compatibility import builtins as _builtins

def _execute_function(self, params):
from spyder.utils.introspection import numpy_docstr
if self.type != 'funcdef':
return
types = set([])
types |= set(numpy_docstr.find_return_types(self.parent_context,
self))
debug.dbg('docstrings type return: %s in %s', types, self)
for name in self._parse_function_doc()[1].split():
try:
bltn_obj = getattr(_builtins, name)
except AttributeError:
continue
else:
types = calculate(evaluator, types, operator,
evaluator.eval_element(right))
except StopIteration:
debug.warning('calculate_children StopIteration %s', types)
debug.dbg('calculate_children types %s', types)
return types

jedi.evaluate.precedence.calculate_children = calculate_children

# [4] Fixing introspection for matplotlib Axes objects
# Patching jedi.evaluate.precedence...
from jedi.evaluate.representation import (
InstanceName, Instance, compiled, FunctionExecution, InstanceElement)

def get_instance_el(evaluator, instance, var, is_class_var=False):
"""
Returns an InstanceElement if it makes sense, otherwise leaves the object
untouched.
Basically having an InstanceElement is context information. That is needed
in quite a lot of cases, which includes Nodes like ``power``, that need to
know where a self name comes from for example.
"""
if isinstance(var, tree.Name):
parent = get_instance_el(evaluator, instance, var.parent, is_class_var)
return InstanceName(var, parent)
# PATCH: compiled objects can be None
elif var is None:
return var
elif var.type != 'funcdef' \
and isinstance(var, (Instance, compiled.CompiledObject, tree.Leaf,
tree.Module, FunctionExecution)):
return var

var = evaluator.wrap(var)
return InstanceElement(evaluator, instance, var, is_class_var)

jedi.evaluate.representation.get_instance_el = get_instance_el
if bltn_obj is None:
# We want to evaluate everything except None.
continue
bltn_obj = create(self.evaluator, bltn_obj)
types |= set(self.evaluator.execute(bltn_obj, params))
for result in types:
yield result

jedi.evaluate.compiled.CompiledObject._execute_function = _execute_function

if is_module_installed('jedi', '=0.9.0'):
# [3] Fixing introspection for matplotlib Axes objects
# Patching jedi.evaluate.precedence...
from jedi.evaluate.precedence import tree, calculate

def calculate_children(evaluator, children):
"""
Calculate a list of children with operators.
"""
iterator = iter(children)
types = evaluator.eval_element(next(iterator))
for operator in iterator:
try: # PATCH: Catches StopIteration error
right = next(iterator)
if tree.is_node(operator, 'comp_op'): # not in / is not
operator = ' '.join(str(c.value) for c in
operator.children)

# handle lazy evaluation of and/or here.
if operator in ('and', 'or'):
left_bools = set([left.py__bool__() for left in types])
if left_bools == set([True]):
if operator == 'and':
types = evaluator.eval_element(right)
elif left_bools == set([False]):
if operator != 'and':
types = evaluator.eval_element(right)
# Otherwise continue, because of uncertainty.
else:
types = calculate(evaluator, types, operator,
evaluator.eval_element(right))
except StopIteration:
debug.warning('calculate_children StopIteration %s', types)
debug.dbg('calculate_children types %s', types)
return types

jedi.evaluate.precedence.calculate_children = calculate_children

# [4] Fixing introspection for matplotlib Axes objects
# Patching jedi.evaluate.precedence...
from jedi.evaluate.representation import (InstanceName, Instance,
compiled, FunctionExecution,
InstanceElement)

def get_instance_el(evaluator, instance, var, is_class_var=False):
"""
Returns an InstanceElement if it makes sense, otherwise leaves the
object untouched.
Basically having an InstanceElement is context information. That is
needed in quite a lot of cases, which includes Nodes like
``power``, that need to know where a self name comes from for
example.
"""
if isinstance(var, tree.Name):
parent = get_instance_el(evaluator, instance, var.parent,
is_class_var)
return InstanceName(var, parent)
# PATCH: compiled objects can be None
elif var is None:
return var
elif var.type != 'funcdef' \
and isinstance(var, (Instance, compiled.CompiledObject,
tree.Leaf, tree.Module, FunctionExecution)):
return var

var = evaluator.wrap(var)
return InstanceElement(evaluator, instance, var, is_class_var)

jedi.evaluate.representation.get_instance_el = get_instance_el

return jedi
1 change: 0 additions & 1 deletion spyder/utils/introspection/jedi_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ def get_completions(self, info):
completions = self.get_jedi_object('completions', info)
if DEBUG_EDITOR:
log_last_error(LOG_FILENAME, str("comp: " + str(completions)[:100]))
debug_print("comp: " + str(completions)[:100])
completions = [(c.name, c.type) for c in completions]
debug_print(str(completions)[:100])
return completions
Expand Down
2 changes: 1 addition & 1 deletion spyder/utils/introspection/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
_("Editor's code completion, go-to-definition and help"),
required_version=ROPE_REQVER)

JEDI_REQVER = '=0.9.0'
JEDI_REQVER = '>=0.9.0'
dependencies.add('jedi',
_("Editor's code completion, go-to-definition and help"),
required_version=JEDI_REQVER)
Expand Down
24 changes: 16 additions & 8 deletions spyder/utils/introspection/numpy_docstr.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
from ast import literal_eval
import re

from jedi._compatibility import is_py3
from spyder.utils.programs import is_module_installed

from jedi._compatibility import u, is_py3
from jedi.evaluate.cache import memoize_default
from jedi.evaluate.docstrings import (_evaluate_for_statement_string,
_strip_rst_role,
Expand Down Expand Up @@ -105,9 +107,9 @@ def _search_return_in_numpydocstr(docstr):
found.extend(_expand_typestr(p_type))
return found


@memoize_default(None, evaluator_is_first_arg=True)
def find_return_types(evaluator, func):
# Caching disabled because jedi_patch breaks it
# @memoize_default(None, evaluator_is_first_arg=True)
def find_return_types(module_context, func):
"""
Determines a set of potential return types for `func` using docstring hints
:type evaluator: jedi.evaluate.Evaluator
Expand Down Expand Up @@ -140,11 +142,17 @@ def search_return_in_docstr(docstr):
# Check for numpy style return hint
found = _search_return_in_numpydocstr(docstr)
return found

docstr = func.raw_doc
module = func.get_parent_until()
try:
docstr = u(func.raw_doc)
except AttributeError:
docstr = u(func.doc)
types = []
for type_str in search_return_in_docstr(docstr):
type_ = _evaluate_for_statement_string(evaluator, type_str, module)
if is_module_installed('jedi', '=0.10.0'):
type_ = _evaluate_for_statement_string(module_context, type_str)
else:
module = func.get_parent_until()
type_ = _evaluate_for_statement_string(module_context,
type_str, module)
types.extend(type_)
return types
4 changes: 2 additions & 2 deletions spyder/utils/introspection/test/test_jedi_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@


def test_get_info():
source_code = "import os; os.walk("
source_code = "import os; os.walk"
docs = p.get_info(CodeInfo('info', source_code, len(source_code)))
assert docs['calltip'].startswith('walk(') and docs['name'] == 'walk'

Expand Down Expand Up @@ -63,7 +63,7 @@ def test_get_docstring():
def test(a, b):
"""Test docstring"""
pass
test(1,''')
test''')
path, line = p.get_definition(CodeInfo('definition', source_code,
len(source_code), 'dummy.txt',
is_python_like=True))
Expand Down