mirror of
https://github.com/davidhalter/jedi.git
synced 2026-02-27 13:22:18 +08:00
Merge branch 'master' into fix-nested-tuple-argument
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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 []
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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')):
|
||||
|
||||
@@ -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}):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
130
jedi/plugins/django.py
Normal 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
|
||||
@@ -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)
|
||||
|
||||
1
jedi/third_party/django-stubs
vendored
Submodule
1
jedi/third_party/django-stubs
vendored
Submodule
Submodule jedi/third_party/django-stubs added at 92c8dfc93f
Reference in New Issue
Block a user