Merge branch 'master' into fix-nested-tuple-argument

This commit is contained in:
Peter Law
2020-04-26 13:56:14 +01:00
29 changed files with 512 additions and 57 deletions

View File

@@ -27,7 +27,7 @@ ad
load
"""
__version__ = '0.17.0'
__version__ = '0.17.1'
from jedi.api import Script, Interpreter, set_debug_function, \
preload_module, names

View File

@@ -44,20 +44,29 @@ def _complete():
import jedi
import pdb
if '-d' in sys.argv:
sys.argv.remove('-d')
jedi.set_debug_function()
try:
for c in jedi.Script(sys.argv[2]).complete():
completions = jedi.Script(sys.argv[2]).complete()
for c in completions:
c.docstring()
c.type
except Exception as e:
print(e)
print(repr(e))
pdb.post_mortem()
else:
print(completions)
if len(sys.argv) == 2 and sys.argv[1] == 'repl':
# don't want to use __main__ only for repl yet, maybe we want to use it for
# something else. So just use the keyword ``repl`` for now.
print(join(dirname(abspath(__file__)), 'api', 'replstartup.py'))
elif len(sys.argv) > 1 and sys.argv[1] == 'linter':
elif len(sys.argv) > 1 and sys.argv[1] == '_linter':
_start_linter()
elif len(sys.argv) > 1 and sys.argv[1] == '_complete':
_complete()
else:
print('Command not implemented: %s' % sys.argv[1])

View File

@@ -339,6 +339,13 @@ try:
except NameError:
PermissionError = IOError
try:
NotADirectoryError = NotADirectoryError
except NameError:
class NotADirectoryError(Exception):
# Don't implement this for Python 2 anymore.
pass
def no_unicode_pprint(dct):
"""

View File

@@ -472,9 +472,20 @@ class Script(object):
if definitions:
return definitions
leaf = self._module_node.get_leaf_for_position((line, column))
if leaf.type in ('keyword', 'operator', 'error_leaf'):
reserved = self._inference_state.grammar._pgen_grammar.reserved_syntax_strings.keys()
if leaf.value in reserved:
if leaf is not None and leaf.type in ('keyword', 'operator', 'error_leaf'):
def need_pydoc():
if leaf.value in ('(', ')', '[', ']'):
if leaf.parent.type == 'trailer':
return False
if leaf.parent.type == 'atom':
return False
grammar = self._inference_state.grammar
# This parso stuff is not public, but since I control it, this
# is fine :-) ~dave
reserved = grammar._pgen_grammar.reserved_syntax_strings.keys()
return leaf.value in reserved
if need_pydoc():
name = KeywordName(self._inference_state, leaf.value)
return [classes.Name(self._inference_state, name)]
return []

View File

@@ -94,7 +94,11 @@ class BaseName(object):
@property
def module_path(self):
"""Shows the file path of a module. e.g. ``/usr/lib/python2.7/os.py``"""
"""
Shows the file path of a module. e.g. ``/usr/lib/python2.7/os.py``
:rtype: str or None
"""
module = self._get_module_context()
if module.is_stub() or not module.is_compiled():
# Compiled modules should not return a module path even if they
@@ -168,7 +172,7 @@ class BaseName(object):
>>> defs[3]
'function'
Valid values for are ``module``, ``class``, ``instance``, ``function``,
Valid values for type are ``module``, ``class``, ``instance``, ``function``,
``param``, ``path``, ``keyword`` and ``statement``.
"""
@@ -245,8 +249,8 @@ class BaseName(object):
Document for function f.
Notice that useful extra information is added to the actual
docstring. For function, it is signature. If you need
actual docstring, use ``raw=True`` instead.
docstring, e.g. function signatures are prepended to their docstrings.
If you need the actual docstring, use ``raw=True`` instead.
>>> print(script.infer(1, len('def f'))[0].docstring(raw=True))
Document for function f.
@@ -665,7 +669,7 @@ class Completion(BaseName):
def docstring(self, raw=False, fast=True):
"""
Documentated under :meth:`BaseName.docstring`.
Documented under :meth:`BaseName.docstring`.
"""
if self._like_name_length >= 3:
# In this case we can just resolve the like name, because we
@@ -703,7 +707,7 @@ class Completion(BaseName):
@property
def type(self):
"""
Documentated under :meth:`BaseName.type`.
Documented under :meth:`BaseName.type`.
"""
# Purely a speed optimization.
if self._cached_name is not None:
@@ -734,8 +738,7 @@ class Name(BaseName):
DeprecationWarning,
stacklevel=2
)
position = '' if self.in_builtin_module else '@%s' % self.line
return "%s:%s%s" % (self.module_name, self.description, position)
return "%s:%s" % (self.module_name, self.description)
@memoize_method
def defined_names(self):
@@ -798,7 +801,7 @@ class BaseSignature(Name):
Returns a text representation of the signature. This could for example
look like ``foo(bar, baz: int, **kwargs)``.
:return str
:rtype: str
"""
return self._signature.to_string()
@@ -865,7 +868,7 @@ class ParamName(Name):
Returns a simple representation of a param, like
``f: Callable[..., Any]``.
:rtype: :class:`str`
:rtype: str
"""
return self._name.to_string()

View File

@@ -13,7 +13,7 @@ import json
import sys
from jedi._compatibility import FileNotFoundError, PermissionError, \
IsADirectoryError
IsADirectoryError, NotADirectoryError
from jedi import debug
from jedi.api.environment import get_cached_default_environment, create_environment
from jedi.api.exceptions import WrongVersion
@@ -383,6 +383,8 @@ def get_default_project(path=None):
return Project.load(dir)
except (FileNotFoundError, IsADirectoryError, PermissionError):
pass
except NotADirectoryError:
continue
if first_no_init_file is None:
if os.path.exists(os.path.join(dir, '__init__.py')):

View File

@@ -99,7 +99,7 @@ class DefineGenericBase(LazyValueWrapper):
for generic_set in self.get_generics():
values = NO_VALUES
for generic in generic_set:
if isinstance(generic, (GenericClass, TypeVar)):
if isinstance(generic, (DefineGenericBase, TypeVar)):
result = generic.define_generics(type_var_dict)
values |= result
if result != ValueSet({generic}):

View File

@@ -12,6 +12,8 @@ from jedi.inference.value import ModuleValue
_jedi_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
TYPESHED_PATH = os.path.join(_jedi_path, 'third_party', 'typeshed')
DJANGO_INIT_PATH = os.path.join(_jedi_path, 'third_party', 'django-stubs',
'django-stubs', '__init__.pyi')
_IMPORT_MAP = dict(
_collections='collections',
@@ -173,6 +175,13 @@ def _try_to_load_stub(inference_state, import_names, python_value_set,
)
if m is not None:
return m
if import_names[0] == 'django':
return _try_to_load_stub_from_file(
inference_state,
python_value_set,
file_io=FileIO(DJANGO_INIT_PATH),
import_names=import_names,
)
# 2. Try to load pyi files next to py files.
for c in python_value_set:

View File

@@ -226,9 +226,24 @@ class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex):
elif annotation_name == 'Callable':
if len(annotation_generics) == 2:
return annotation_generics[1].infer_type_vars(
value_set.execute_annotation(),
)
if is_class_value:
# This only applies if we are comparing something like
# List[Callable[..., T]] with Iterable[Callable[..., T]].
# First, Jedi tries to match List/Iterable. After that we
# will land here, because is_class_value will be True at
# that point. Obviously we also compare below that both
# sides are `Callable`.
for element in value_set:
element_name = element.py__name__()
if element_name == 'Callable':
merge_type_var_dicts(
type_var_dict,
merge_pairwise_generics(self, element),
)
else:
return annotation_generics[1].infer_type_vars(
value_set.execute_annotation(),
)
elif annotation_name == 'Tuple':
tuple_annotation = self.get_annotated_class_object()
@@ -421,6 +436,10 @@ class NewType(Value):
self._type_value_set = type_value_set
self.tree_node = tree_node
def py__class__(self):
c, = self._type_value_set.py__class__()
return c
def py__call__(self, arguments):
return self._type_value_set.execute_annotation()

View File

@@ -192,13 +192,17 @@ class Sequence(LazyAttributeOverwrite, IterableMixin):
def _get_generics(self):
return (self.merge_types_of_iterate().py__class__(),)
@inference_state_method_cache(default=())
def _cached_generics(self):
return self._get_generics()
def _get_wrapped_value(self):
from jedi.inference.gradual.base import GenericClass
from jedi.inference.gradual.generics import TupleGenericManager
klass = compiled.builtin_from_name(self.inference_state, self.array_type)
c, = GenericClass(
klass,
TupleGenericManager(self._get_generics())
TupleGenericManager(self._cached_generics())
).execute_annotation()
return c

View File

@@ -186,11 +186,12 @@ class ClassMixin(object):
mro.append(cls_new)
yield cls_new
def get_filters(self, origin_scope=None, is_instance=False):
metaclasses = self.get_metaclasses()
if metaclasses:
for f in self.get_metaclass_filters(metaclasses):
yield f
def get_filters(self, origin_scope=None, is_instance=False, include_metaclasses=True):
if include_metaclasses:
metaclasses = self.get_metaclasses()
if metaclasses:
for f in self.get_metaclass_filters(metaclasses):
yield f
for cls in self.py__mro__():
if cls.is_compiled():

130
jedi/plugins/django.py Normal file
View File

@@ -0,0 +1,130 @@
"""
Module is used to infer Django model fields.
"""
from jedi import debug
from jedi.inference.base_value import ValueSet, iterator_to_value_set
from jedi.inference.filters import ParserTreeFilter, DictFilter
from jedi.inference.names import NameWrapper
from jedi.inference.value.instance import TreeInstance
from jedi.inference.gradual.base import GenericClass
from jedi.inference.gradual.generics import TupleGenericManager
mapping = {
'IntegerField': (None, 'int'),
'BigIntegerField': (None, 'int'),
'PositiveIntegerField': (None, 'int'),
'SmallIntegerField': (None, 'int'),
'CharField': (None, 'str'),
'TextField': (None, 'str'),
'EmailField': (None, 'str'),
'FloatField': (None, 'float'),
'BinaryField': (None, 'bytes'),
'BooleanField': (None, 'bool'),
'DecimalField': ('decimal', 'Decimal'),
'TimeField': ('datetime', 'time'),
'DurationField': ('datetime', 'timedelta'),
'DateField': ('datetime', 'date'),
'DateTimeField': ('datetime', 'datetime'),
}
def _infer_scalar_field(inference_state, field_name, field_tree_instance):
try:
module_name, attribute_name = mapping[field_tree_instance.py__name__()]
except KeyError:
return None
if module_name is None:
module = inference_state.builtins_module
else:
module = inference_state.import_module((module_name,))
for attribute in module.py__getattribute__(attribute_name):
return attribute.execute_with_values()
@iterator_to_value_set
def _get_foreign_key_values(cls, field_tree_instance):
if isinstance(field_tree_instance, TreeInstance):
# TODO private access..
argument_iterator = field_tree_instance._arguments.unpack()
key, lazy_values = next(argument_iterator, (None, None))
if key is None and lazy_values is not None:
for value in lazy_values.infer():
if value.py__name__() == 'str':
foreign_key_class_name = value.get_safe_value()
module = cls.get_root_context()
for v in module.py__getattribute__(foreign_key_class_name):
if v.is_class():
yield v
elif value.is_class():
yield value
def _infer_field(cls, field_name):
inference_state = cls.inference_state
for field_tree_instance in field_name.infer():
scalar_field = _infer_scalar_field(inference_state, field_name, field_tree_instance)
if scalar_field is not None:
return scalar_field
name = field_tree_instance.py__name__()
is_many_to_many = name == 'ManyToManyField'
if name == 'ForeignKey' or is_many_to_many:
values = _get_foreign_key_values(cls, field_tree_instance)
if is_many_to_many:
return ValueSet(filter(None, [
_create_manager_for(v, 'RelatedManager') for v in values
]))
else:
return values.execute_with_values()
debug.dbg('django plugin: fail to infer `%s` from class `%s`',
field_name.string_name, cls.py__name__())
return field_name.infer()
class DjangoModelName(NameWrapper):
def __init__(self, cls, name):
super(DjangoModelName, self).__init__(name)
self._cls = cls
def infer(self):
return _infer_field(self._cls, self._wrapped_name)
def _create_manager_for(cls, manager_cls='BaseManager'):
managers = cls.inference_state.import_module(
('django', 'db', 'models', 'manager')
).py__getattribute__(manager_cls)
for m in managers:
if m.is_class() and not m.is_compiled():
generics_manager = TupleGenericManager((ValueSet([cls]),))
for c in GenericClass(m, generics_manager).execute_annotation():
return c
return None
def _new_dict_filter(cls):
filters = cls.get_filters(is_instance=True, include_metaclasses=False)
dct = {
name.string_name: DjangoModelName(cls, name)
for filter_ in reversed(list(filters))
for name in filter_.values()
}
manager = _create_manager_for(cls)
if manager:
dct['objects'] = manager.name
return DictFilter(dct)
def get_metaclass_filters(func):
def wrapper(cls, metaclasses):
for metaclass in metaclasses:
if metaclass.py__name__() == 'ModelBase' \
and metaclass.get_root_context().py__name__() == 'django.db.models.base':
return [_new_dict_filter(cls)]
return func(cls, metaclasses)
return wrapper

View File

@@ -5,7 +5,8 @@ This is not a plugin, this is just the place were plugins are registered.
from jedi.plugins import stdlib
from jedi.plugins import flask
from jedi.plugins import pytest
from jedi.plugins import django
from jedi.plugins import plugin_manager
plugin_manager.register(stdlib, flask, pytest)
plugin_manager.register(stdlib, flask, pytest, django)