Make it possible to access properties again

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
This commit is contained in:
Dave Halter
2019-08-11 11:16:01 +02:00
parent a7accf4171
commit c3d40949b1
6 changed files with 45 additions and 13 deletions

View File

@@ -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:

View File

@@ -436,6 +436,7 @@ class Interpreter(Script):
>>> print(script.completions()[0].name)
upper
"""
_allow_descriptor_getattr_default = True
def __init__(self, source, namespaces, **kwds):
"""
@@ -466,6 +467,7 @@ class Interpreter(Script):
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(

View File

@@ -106,6 +106,7 @@ class Evaluator(object):
self.is_analysis = False
self.project = project
self.access_cache = {}
self.allow_descriptor_getattr = False
self.reset_recursion_limitations()
self.allow_different_encoding = True

View File

@@ -340,12 +340,16 @@ class DirectObjectAccess(object):
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):

View File

@@ -399,7 +399,7 @@ class CompiledObjectFilter(AbstractFilter):
# 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():

View File

@@ -7,7 +7,7 @@ import pytest
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:
@@ -197,7 +197,13 @@ def test_getitem_side_effects():
_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
@@ -209,11 +215,14 @@ def test_property_error_oldstyle():
_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
@@ -225,8 +234,22 @@ def test_property_error_newstyle():
_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")