mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 22:14:27 +08:00
Remove side effects when accessing jedi from the interpreter.
Note that there is http://bugs.python.org/issue31184. Fixes #925.
This commit is contained in:
@@ -8,6 +8,11 @@ from jedi.evaluate.compiled import mixed
|
|||||||
from jedi.evaluate.context import Context
|
from jedi.evaluate.context import Context
|
||||||
|
|
||||||
|
|
||||||
|
class NamespaceObject():
|
||||||
|
def __init__(self, dct):
|
||||||
|
self.__dict__ = dct
|
||||||
|
|
||||||
|
|
||||||
class MixedModuleContext(Context):
|
class MixedModuleContext(Context):
|
||||||
resets_positions = True
|
resets_positions = True
|
||||||
type = 'mixed_module'
|
type = 'mixed_module'
|
||||||
@@ -16,7 +21,7 @@ class MixedModuleContext(Context):
|
|||||||
self.evaluator = evaluator
|
self.evaluator = evaluator
|
||||||
self._namespaces = namespaces
|
self._namespaces = namespaces
|
||||||
|
|
||||||
self._namespace_objects = [type('jedi_namespace', (), n) for n in namespaces]
|
self._namespace_objects = [NamespaceObject(n) for n in namespaces]
|
||||||
self._module_context = ModuleContext(evaluator, tree_module, path=path)
|
self._module_context = ModuleContext(evaluator, tree_module, path=path)
|
||||||
self.tree_node = tree_module
|
self.tree_node = tree_module
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import inspect
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import types
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from jedi._compatibility import builtins as _builtins, unicode
|
from jedi._compatibility import builtins as _builtins, unicode
|
||||||
@@ -13,6 +14,7 @@ from jedi.cache import underscore_memoization, memoize_method
|
|||||||
from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \
|
from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \
|
||||||
ContextNameMixin
|
ContextNameMixin
|
||||||
from jedi.evaluate.context import Context, LazyKnownContext
|
from jedi.evaluate.context import Context, LazyKnownContext
|
||||||
|
from jedi.evaluate.compiled.getattr_static import getattr_static
|
||||||
from . import fake
|
from . import fake
|
||||||
|
|
||||||
|
|
||||||
@@ -22,6 +24,23 @@ if os.path.altsep is not None:
|
|||||||
_path_re = re.compile('(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep)))
|
_path_re = re.compile('(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep)))
|
||||||
del _sep
|
del _sep
|
||||||
|
|
||||||
|
# Those types don't exist in typing.
|
||||||
|
MethodDescriptorType = type(str.replace)
|
||||||
|
WrapperDescriptorType = type(set.__iter__)
|
||||||
|
# `object.__subclasshook__` is an already executed descriptor.
|
||||||
|
object_class_dict = type.__dict__["__dict__"].__get__(object)
|
||||||
|
ClassMethodDescriptorType = type(object_class_dict['__subclasshook__'])
|
||||||
|
|
||||||
|
ALLOWED_DESCRIPTOR_ACCESS = (
|
||||||
|
types.FunctionType,
|
||||||
|
types.GetSetDescriptorType,
|
||||||
|
types.MemberDescriptorType,
|
||||||
|
MethodDescriptorType,
|
||||||
|
WrapperDescriptorType,
|
||||||
|
ClassMethodDescriptorType,
|
||||||
|
staticmethod,
|
||||||
|
classmethod,
|
||||||
|
)
|
||||||
|
|
||||||
class CheckAttribute(object):
|
class CheckAttribute(object):
|
||||||
"""Raises an AttributeError if the attribute X isn't available."""
|
"""Raises an AttributeError if the attribute X isn't available."""
|
||||||
@@ -297,16 +316,17 @@ class CompiledObjectFilter(AbstractFilter):
|
|||||||
name = str(name)
|
name = str(name)
|
||||||
obj = self._compiled_object.obj
|
obj = self._compiled_object.obj
|
||||||
try:
|
try:
|
||||||
getattr(obj, name)
|
attr, is_get_descriptor = getattr_static(obj, name)
|
||||||
if self._is_instance and name not in dir(obj):
|
|
||||||
return []
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return []
|
return []
|
||||||
except Exception:
|
else:
|
||||||
# This is a bit ugly. We're basically returning this to make
|
if is_get_descriptor \
|
||||||
# lookups possible without having the actual attribute. However
|
and not type(attr) in ALLOWED_DESCRIPTOR_ACCESS:
|
||||||
# this makes proper completion possible.
|
# In case of descriptors that have get methods we cannot return
|
||||||
|
# it's value, because that would mean code execution.
|
||||||
return [EmptyCompiledName(self._evaluator, name)]
|
return [EmptyCompiledName(self._evaluator, name)]
|
||||||
|
if self._is_instance and name not in dir(obj):
|
||||||
|
return []
|
||||||
return [self._create_name(name)]
|
return [self._create_name(name)]
|
||||||
|
|
||||||
def values(self):
|
def values(self):
|
||||||
|
|||||||
110
jedi/evaluate/compiled/getattr_static.py
Normal file
110
jedi/evaluate/compiled/getattr_static.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
"""
|
||||||
|
A static version of getattr.
|
||||||
|
This is a backport of the Python 3 code with a little bit of additional
|
||||||
|
information returned to enable Jedi to make decisions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import types
|
||||||
|
|
||||||
|
_sentinel = object()
|
||||||
|
|
||||||
|
def _static_getmro(klass):
|
||||||
|
return type.__dict__['__mro__'].__get__(klass)
|
||||||
|
|
||||||
|
def _check_instance(obj, attr):
|
||||||
|
instance_dict = {}
|
||||||
|
try:
|
||||||
|
instance_dict = object.__getattribute__(obj, "__dict__")
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
return dict.get(instance_dict, attr, _sentinel)
|
||||||
|
|
||||||
|
|
||||||
|
def _check_class(klass, attr):
|
||||||
|
for entry in _static_getmro(klass):
|
||||||
|
if _shadowed_dict(type(entry)) is _sentinel:
|
||||||
|
try:
|
||||||
|
return entry.__dict__[attr]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return _sentinel
|
||||||
|
|
||||||
|
def _is_type(obj):
|
||||||
|
try:
|
||||||
|
_static_getmro(obj)
|
||||||
|
except TypeError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _shadowed_dict(klass):
|
||||||
|
dict_attr = type.__dict__["__dict__"]
|
||||||
|
for entry in _static_getmro(klass):
|
||||||
|
try:
|
||||||
|
class_dict = dict_attr.__get__(entry)["__dict__"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if not (type(class_dict) is types.GetSetDescriptorType and
|
||||||
|
class_dict.__name__ == "__dict__" and
|
||||||
|
class_dict.__objclass__ is entry):
|
||||||
|
return class_dict
|
||||||
|
return _sentinel
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_hasattr(obj, name):
|
||||||
|
return _check_class(type(obj), name) is not _sentinel
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_is_data_descriptor(obj):
|
||||||
|
return (_safe_hasattr(obj, '__set__') or _safe_hasattr(obj, '__delete__'))
|
||||||
|
|
||||||
|
|
||||||
|
def getattr_static(obj, attr, default=_sentinel):
|
||||||
|
"""Retrieve attributes without triggering dynamic lookup via the
|
||||||
|
descriptor protocol, __getattr__ or __getattribute__.
|
||||||
|
|
||||||
|
Note: this function may not be able to retrieve all attributes
|
||||||
|
that getattr can fetch (like dynamically created attributes)
|
||||||
|
and may find attributes that getattr can't (like descriptors
|
||||||
|
that raise AttributeError). It can also return descriptor objects
|
||||||
|
instead of instance members in some cases. See the
|
||||||
|
documentation for details.
|
||||||
|
|
||||||
|
Returns a tuple `(attr, is_get_descriptor)`. is_get_descripter means that
|
||||||
|
the attribute is a descriptor that has a `__get__` attribute.
|
||||||
|
"""
|
||||||
|
instance_result = _sentinel
|
||||||
|
if not _is_type(obj):
|
||||||
|
klass = type(obj)
|
||||||
|
dict_attr = _shadowed_dict(klass)
|
||||||
|
if (dict_attr is _sentinel or
|
||||||
|
type(dict_attr) is types.MemberDescriptorType):
|
||||||
|
instance_result = _check_instance(obj, attr)
|
||||||
|
else:
|
||||||
|
klass = obj
|
||||||
|
|
||||||
|
klass_result = _check_class(klass, attr)
|
||||||
|
|
||||||
|
if instance_result is not _sentinel and klass_result is not _sentinel:
|
||||||
|
if _safe_hasattr(klass_result, '__get__') \
|
||||||
|
and _safe_is_data_descriptor(klass_result):
|
||||||
|
# A get/set descriptor has priority over everything.
|
||||||
|
return klass_result, True
|
||||||
|
|
||||||
|
if instance_result is not _sentinel:
|
||||||
|
return instance_result, False
|
||||||
|
if klass_result is not _sentinel:
|
||||||
|
return klass_result, _safe_hasattr(klass_result, '__get__')
|
||||||
|
|
||||||
|
if obj is klass:
|
||||||
|
# for types we check the metaclass too
|
||||||
|
for entry in _static_getmro(type(klass)):
|
||||||
|
if _shadowed_dict(type(entry)) is _sentinel:
|
||||||
|
try:
|
||||||
|
return entry.__dict__[attr], False
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
if default is not _sentinel:
|
||||||
|
return default, False
|
||||||
|
raise AttributeError(attr)
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
Tests of ``jedi.api.Interpreter``.
|
Tests of ``jedi.api.Interpreter``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from ..helpers import TestCase
|
|
||||||
import jedi
|
import jedi
|
||||||
from jedi._compatibility import is_py33
|
from jedi._compatibility import is_py33
|
||||||
from jedi.evaluate.compiled import mixed
|
from jedi.evaluate.compiled import mixed
|
||||||
@@ -179,15 +178,20 @@ def test_getitem_side_effects():
|
|||||||
|
|
||||||
|
|
||||||
def test_property_error():
|
def test_property_error():
|
||||||
|
lst = []
|
||||||
class Foo3():
|
class Foo3():
|
||||||
@property
|
@property
|
||||||
def bar(self):
|
def bar(self):
|
||||||
|
lst.append(1)
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
foo = Foo3()
|
foo = Foo3()
|
||||||
_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
|
||||||
|
assert lst == []
|
||||||
|
|
||||||
|
|
||||||
def test_param_completion():
|
def test_param_completion():
|
||||||
def foo(bar):
|
def foo(bar):
|
||||||
|
|||||||
Reference in New Issue
Block a user