mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 14:04:26 +08:00
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:
@@ -11,6 +11,8 @@ Changelog
|
|||||||
- ``*args``/``**kwargs`` resolving. If possible Jedi replaces the parameters
|
- ``*args``/``**kwargs`` resolving. If possible Jedi replaces the parameters
|
||||||
with the actual alternatives.
|
with the actual alternatives.
|
||||||
- Better support for enums/dataclasses
|
- 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:
|
New APIs:
|
||||||
|
|
||||||
|
|||||||
@@ -436,6 +436,7 @@ class Interpreter(Script):
|
|||||||
>>> print(script.completions()[0].name)
|
>>> print(script.completions()[0].name)
|
||||||
upper
|
upper
|
||||||
"""
|
"""
|
||||||
|
_allow_descriptor_getattr_default = True
|
||||||
|
|
||||||
def __init__(self, source, namespaces, **kwds):
|
def __init__(self, source, namespaces, **kwds):
|
||||||
"""
|
"""
|
||||||
@@ -466,6 +467,7 @@ class Interpreter(Script):
|
|||||||
super(Interpreter, self).__init__(source, environment=environment,
|
super(Interpreter, self).__init__(source, environment=environment,
|
||||||
_project=Project(os.getcwd()), **kwds)
|
_project=Project(os.getcwd()), **kwds)
|
||||||
self.namespaces = namespaces
|
self.namespaces = namespaces
|
||||||
|
self._evaluator.allow_descriptor_getattr = self._allow_descriptor_getattr_default
|
||||||
|
|
||||||
def _get_module(self):
|
def _get_module(self):
|
||||||
return interpreter.MixedModuleContext(
|
return interpreter.MixedModuleContext(
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ class Evaluator(object):
|
|||||||
self.is_analysis = False
|
self.is_analysis = False
|
||||||
self.project = project
|
self.project = project
|
||||||
self.access_cache = {}
|
self.access_cache = {}
|
||||||
|
self.allow_descriptor_getattr = False
|
||||||
|
|
||||||
self.reset_recursion_limitations()
|
self.reset_recursion_limitations()
|
||||||
self.allow_different_encoding = True
|
self.allow_different_encoding = True
|
||||||
|
|||||||
@@ -340,12 +340,16 @@ class DirectObjectAccess(object):
|
|||||||
def getattr_paths(self, name, default=_sentinel):
|
def getattr_paths(self, name, default=_sentinel):
|
||||||
try:
|
try:
|
||||||
return_obj = getattr(self._obj, name)
|
return_obj = getattr(self._obj, name)
|
||||||
except AttributeError:
|
except Exception as e:
|
||||||
# Happens e.g. in properties of
|
|
||||||
# PyQt4.QtGui.QStyleOptionComboBox.currentText
|
|
||||||
# -> just set it to None
|
|
||||||
if default is _sentinel:
|
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
|
return_obj = default
|
||||||
access = self._create_access(return_obj)
|
access = self._create_access(return_obj)
|
||||||
if inspect.ismodule(return_obj):
|
if inspect.ismodule(return_obj):
|
||||||
|
|||||||
@@ -399,7 +399,7 @@ class CompiledObjectFilter(AbstractFilter):
|
|||||||
# Always use unicode objects in Python 2 from here.
|
# Always use unicode objects in Python 2 from here.
|
||||||
name = force_unicode(name)
|
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)]
|
return [self._get_cached_name(name, is_empty=True)]
|
||||||
|
|
||||||
if self.is_instance and name not in dir_callback():
|
if self.is_instance and name not in dir_callback():
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import pytest
|
|||||||
|
|
||||||
import jedi
|
import jedi
|
||||||
from jedi._compatibility import is_py3, py_version
|
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
|
from importlib import import_module
|
||||||
|
|
||||||
if py_version > 30:
|
if py_version > 30:
|
||||||
@@ -197,7 +197,13 @@ def test_getitem_side_effects():
|
|||||||
_assert_interpreter_complete('foo["asdf"].upper', locals(), ['upper'])
|
_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 = []
|
lst = []
|
||||||
class Foo3:
|
class Foo3:
|
||||||
@property
|
@property
|
||||||
@@ -209,11 +215,14 @@ def test_property_error_oldstyle():
|
|||||||
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
|
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
|
||||||
_assert_interpreter_complete('foo.bar.baz', locals(), [])
|
_assert_interpreter_complete('foo.bar.baz', locals(), [])
|
||||||
|
|
||||||
# There should not be side effects
|
if allow_descriptor_access_or_not:
|
||||||
assert lst == []
|
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 = []
|
lst = []
|
||||||
class Foo3(object):
|
class Foo3(object):
|
||||||
@property
|
@property
|
||||||
@@ -225,8 +234,22 @@ def test_property_error_newstyle():
|
|||||||
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
|
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
|
||||||
_assert_interpreter_complete('foo.bar.baz', locals(), [])
|
_assert_interpreter_complete('foo.bar.baz', locals(), [])
|
||||||
|
|
||||||
# There should not be side effects
|
if allow_descriptor_access_or_not:
|
||||||
assert lst == []
|
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")
|
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
|
||||||
|
|||||||
Reference in New Issue
Block a user