Skip to content

Commit

Permalink
Make it possible to access properties again
Browse files Browse the repository at this point in the history
This time we catch all exceptions and try to avoid issues for the user.

This is only happening when working with an Interpreter. I don't feel this is
necessary otherwise.

See #1299
  • Loading branch information
davidhalter committed Aug 11, 2019
1 parent a7accf4 commit c3d4094
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 13 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Changelog
- ``*args``/``**kwargs`` resolving. If possible Jedi replaces the parameters
with the actual alternatives.
- Better support for enums/dataclasses
- When using Interpreter, properties are now executed, since a lot of people
have complained about this. Discussion in #1299.

New APIs:

Expand Down
2 changes: 2 additions & 0 deletions jedi/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ class Interpreter(Script):
>>> print(script.completions()[0].name)
upper
"""
_allow_descriptor_getattr_default = True

def __init__(self, source, namespaces, **kwds):
"""
Expand Down Expand Up @@ -466,6 +467,7 @@ def __init__(self, source, namespaces, **kwds):
super(Interpreter, self).__init__(source, environment=environment,
_project=Project(os.getcwd()), **kwds)
self.namespaces = namespaces
self._evaluator.allow_descriptor_getattr = self._allow_descriptor_getattr_default

def _get_module(self):
return interpreter.MixedModuleContext(
Expand Down
1 change: 1 addition & 0 deletions jedi/evaluate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def __init__(self, project, environment=None, script_path=None):
self.is_analysis = False
self.project = project
self.access_cache = {}
self.allow_descriptor_getattr = False

self.reset_recursion_limitations()
self.allow_different_encoding = True
Expand Down
14 changes: 9 additions & 5 deletions jedi/evaluate/compiled/access.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,12 +340,16 @@ def is_allowed_getattr(self, name):
def getattr_paths(self, name, default=_sentinel):
try:
return_obj = getattr(self._obj, name)
except AttributeError:
# Happens e.g. in properties of
# PyQt4.QtGui.QStyleOptionComboBox.currentText
# -> just set it to None
except Exception as e:
if default is _sentinel:
raise
if isinstance(e, AttributeError):
# Happens e.g. in properties of
# PyQt4.QtGui.QStyleOptionComboBox.currentText
# -> just set it to None
raise
# Just in case anything happens, return an AttributeError. It
# should not crash.
raise AttributeError
return_obj = default
access = self._create_access(return_obj)
if inspect.ismodule(return_obj):
Expand Down
2 changes: 1 addition & 1 deletion jedi/evaluate/compiled/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ def _get(self, name, allowed_getattr_callback, dir_callback, check_has_attribute
# Always use unicode objects in Python 2 from here.
name = force_unicode(name)

if is_descriptor or not has_attribute:
if (is_descriptor and not self._evaluator.allow_descriptor_getattr) or not has_attribute:
return [self._get_cached_name(name, is_empty=True)]

if self.is_instance and name not in dir_callback():
Expand Down
37 changes: 30 additions & 7 deletions test/test_api/test_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import jedi
from jedi._compatibility import is_py3, py_version
from jedi.evaluate.compiled import mixed
from jedi.evaluate.compiled import mixed, context
from importlib import import_module

if py_version > 30:
Expand Down Expand Up @@ -197,7 +197,13 @@ def __getitem__(self, index):
_assert_interpreter_complete('foo["asdf"].upper', locals(), ['upper'])


def test_property_error_oldstyle():
@pytest.fixture(params=[False, True])
def allow_descriptor_access_or_not(request, monkeypatch):
monkeypatch.setattr(jedi.Interpreter, '_allow_descriptor_getattr_default', request.param)
return request.param


def test_property_error_oldstyle(allow_descriptor_access_or_not):
lst = []
class Foo3:
@property
Expand All @@ -209,11 +215,14 @@ def bar(self):
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
_assert_interpreter_complete('foo.bar.baz', locals(), [])

# There should not be side effects
assert lst == []
if allow_descriptor_access_or_not:
assert lst == [1, 1]
else:
# There should not be side effects
assert lst == []


def test_property_error_newstyle():
def test_property_error_newstyle(allow_descriptor_access_or_not):
lst = []
class Foo3(object):
@property
Expand All @@ -225,8 +234,22 @@ def bar(self):
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
_assert_interpreter_complete('foo.bar.baz', locals(), [])

# There should not be side effects
assert lst == []
if allow_descriptor_access_or_not:
assert lst == [1, 1]
else:
# There should not be side effects
assert lst == []


def test_property_content():
class Foo3(object):
@property
def bar(self):
return 1

foo = Foo3()
def_, = jedi.Interpreter('foo.bar', [locals()]).goto_definitions()
assert def_.name == 'int'


@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
Expand Down

0 comments on commit c3d4094

Please sign in to comment.