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

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "jedi/third_party/typeshed"] [submodule "jedi/third_party/typeshed"]
path = jedi/third_party/typeshed path = jedi/third_party/typeshed
url = https://github.com/davidhalter/typeshed.git url = https://github.com/davidhalter/typeshed.git
[submodule "jedi/third_party/django-stubs"]
path = jedi/third_party/django-stubs
url = https://github.com/typeddjango/django-stubs

View File

@@ -3,6 +3,14 @@
Changelog Changelog
--------- ---------
Unreleased: 0.17.1 (2020-04-)
+++++++++++++++++++
- Django ``Model`` meta class support
- Added Django Stubs to Jedi, thanks to all contributors of the
`Django Stubs <https://github.com/typeddjango/django-stubs>`_ project
- A few bugfixes
0.17.0 (2020-04-14) 0.17.0 (2020-04-14)
+++++++++++++++++++ +++++++++++++++++++

View File

@@ -11,6 +11,7 @@ include requirements.txt
include jedi/parser/python/grammar*.txt include jedi/parser/python/grammar*.txt
recursive-include jedi/third_party *.pyi recursive-include jedi/third_party *.pyi
include jedi/third_party/typeshed/LICENSE include jedi/third_party/typeshed/LICENSE
include jedi/third_party/django-stubs/LICENSE.txt
include jedi/third_party/typeshed/README include jedi/third_party/typeshed/README
recursive-include test * recursive-include test *
recursive-include docs * recursive-include docs *

View File

@@ -13,7 +13,7 @@ from test.helpers import test_dir
collect_ignore = [ collect_ignore = [
'setup.py', 'setup.py',
'__main__.py', 'jedi/__main__.py',
'jedi/inference/compiled/subprocess/__main__.py', 'jedi/inference/compiled/subprocess/__main__.py',
'build/', 'build/',
'test/examples', 'test/examples',
@@ -147,6 +147,12 @@ def has_typing(environment):
return bool(script.infer()) return bool(script.infer())
@pytest.fixture(scope='session')
def has_django(environment):
script = jedi.Script('import django', environment=environment)
return bool(script.infer())
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def jedi_path(): def jedi_path():
return os.path.dirname(__file__) return os.path.dirname(__file__)

View File

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

View File

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

View File

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

View File

@@ -472,9 +472,20 @@ class Script(object):
if definitions: if definitions:
return definitions return definitions
leaf = self._module_node.get_leaf_for_position((line, column)) leaf = self._module_node.get_leaf_for_position((line, column))
if leaf.type in ('keyword', 'operator', 'error_leaf'): if leaf is not None and leaf.type in ('keyword', 'operator', 'error_leaf'):
reserved = self._inference_state.grammar._pgen_grammar.reserved_syntax_strings.keys() def need_pydoc():
if leaf.value in reserved: 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) name = KeywordName(self._inference_state, leaf.value)
return [classes.Name(self._inference_state, name)] return [classes.Name(self._inference_state, name)]
return [] return []

View File

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

View File

@@ -13,7 +13,7 @@ import json
import sys import sys
from jedi._compatibility import FileNotFoundError, PermissionError, \ from jedi._compatibility import FileNotFoundError, PermissionError, \
IsADirectoryError IsADirectoryError, NotADirectoryError
from jedi import debug from jedi import debug
from jedi.api.environment import get_cached_default_environment, create_environment from jedi.api.environment import get_cached_default_environment, create_environment
from jedi.api.exceptions import WrongVersion from jedi.api.exceptions import WrongVersion
@@ -383,6 +383,8 @@ def get_default_project(path=None):
return Project.load(dir) return Project.load(dir)
except (FileNotFoundError, IsADirectoryError, PermissionError): except (FileNotFoundError, IsADirectoryError, PermissionError):
pass pass
except NotADirectoryError:
continue
if first_no_init_file is None: if first_no_init_file is None:
if os.path.exists(os.path.join(dir, '__init__.py')): 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(): for generic_set in self.get_generics():
values = NO_VALUES values = NO_VALUES
for generic in generic_set: for generic in generic_set:
if isinstance(generic, (GenericClass, TypeVar)): if isinstance(generic, (DefineGenericBase, TypeVar)):
result = generic.define_generics(type_var_dict) result = generic.define_generics(type_var_dict)
values |= result values |= result
if result != ValueSet({generic}): 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__)))) _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') 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( _IMPORT_MAP = dict(
_collections='collections', _collections='collections',
@@ -173,6 +175,13 @@ def _try_to_load_stub(inference_state, import_names, python_value_set,
) )
if m is not None: if m is not None:
return m 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. # 2. Try to load pyi files next to py files.
for c in python_value_set: for c in python_value_set:

View File

@@ -226,9 +226,24 @@ class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex):
elif annotation_name == 'Callable': elif annotation_name == 'Callable':
if len(annotation_generics) == 2: if len(annotation_generics) == 2:
return annotation_generics[1].infer_type_vars( if is_class_value:
value_set.execute_annotation(), # 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': elif annotation_name == 'Tuple':
tuple_annotation = self.get_annotated_class_object() tuple_annotation = self.get_annotated_class_object()
@@ -421,6 +436,10 @@ class NewType(Value):
self._type_value_set = type_value_set self._type_value_set = type_value_set
self.tree_node = tree_node self.tree_node = tree_node
def py__class__(self):
c, = self._type_value_set.py__class__()
return c
def py__call__(self, arguments): def py__call__(self, arguments):
return self._type_value_set.execute_annotation() return self._type_value_set.execute_annotation()

View File

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

View File

@@ -186,11 +186,12 @@ class ClassMixin(object):
mro.append(cls_new) mro.append(cls_new)
yield cls_new yield cls_new
def get_filters(self, origin_scope=None, is_instance=False): def get_filters(self, origin_scope=None, is_instance=False, include_metaclasses=True):
metaclasses = self.get_metaclasses() if include_metaclasses:
if metaclasses: metaclasses = self.get_metaclasses()
for f in self.get_metaclass_filters(metaclasses): if metaclasses:
yield f for f in self.get_metaclass_filters(metaclasses):
yield f
for cls in self.py__mro__(): for cls in self.py__mro__():
if cls.is_compiled(): 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 stdlib
from jedi.plugins import flask from jedi.plugins import flask
from jedi.plugins import pytest from jedi.plugins import pytest
from jedi.plugins import django
from jedi.plugins import plugin_manager from jedi.plugins import plugin_manager
plugin_manager.register(stdlib, flask, pytest) plugin_manager.register(stdlib, flask, pytest, django)

View File

@@ -19,6 +19,8 @@ with open('requirements.txt') as f:
assert os.path.isfile("jedi/third_party/typeshed/LICENSE"), \ assert os.path.isfile("jedi/third_party/typeshed/LICENSE"), \
"Please download the typeshed submodule first (Hint: git submodule update --init)" "Please download the typeshed submodule first (Hint: git submodule update --init)"
assert os.path.isfile("jedi/third_party/django-stubs/LICENSE.txt"), \
"Please download the django-stubs submodule first (Hint: git submodule update --init)"
setup(name='jedi', setup(name='jedi',
version=version, version=version,
@@ -43,6 +45,7 @@ setup(name='jedi',
'docopt', 'docopt',
# coloroma for colored debug output # coloroma for colored debug output
'colorama', 'colorama',
'Django<3.1', # For now pin this.
], ],
'qa': [ 'qa': [
'flake8==3.7.9', 'flake8==3.7.9',

View File

@@ -123,7 +123,7 @@ class TestCase(object):
with open(self.path) as f: with open(self.path) as f:
self.script = jedi.Script(f.read(), path=self.path) self.script = jedi.Script(f.read(), path=self.path)
kwargs = {} kwargs = {}
if self.operation == 'goto_assignments': if self.operation == 'goto':
kwargs['follow_imports'] = random.choice([False, True]) kwargs['follow_imports'] = random.choice([False, True])
self.objects = getattr(self.script, self.operation)(self.line, self.column, **kwargs) self.objects = getattr(self.script, self.operation)(self.line, self.column, **kwargs)

168
test/completion/django.py Normal file
View File

@@ -0,0 +1,168 @@
import datetime
import decimal
from django.db import models
from django.contrib.auth.models import User
class Tag(models.Model):
tag_name = models.CharField()
class Category(models.Model):
category_name = models.CharField()
class BusinessModel(models.Model):
category_fk = models.ForeignKey(Category)
category_fk2 = models.ForeignKey('Category')
category_fk3 = models.ForeignKey(1)
category_fk4 = models.ForeignKey('models')
category_fk5 = models.ForeignKey()
integer_field = models.IntegerField()
big_integer_field = models.BigIntegerField()
positive_integer_field = models.PositiveIntegerField()
small_integer_field = models.SmallIntegerField()
char_field = models.CharField()
text_field = models.TextField()
email_field = models.EmailField()
float_field = models.FloatField()
binary_field = models.BinaryField()
boolean_field = models.BooleanField()
decimal_field = models.DecimalField()
time_field = models.TimeField()
duration_field = models.DurationField()
date_field = models.DateField()
date_time_field = models.DateTimeField()
tags_m2m = models.ManyToManyField(Tag)
unidentifiable = NOT_FOUND
# -----------------
# Model attribute inference
# -----------------
model_instance = BusinessModel()
#? int()
model_instance.integer_field
#? int()
model_instance.big_integer_field
#? int()
model_instance.positive_integer_field
#? int()
model_instance.small_integer_field
#? str()
model_instance.char_field
#? str()
model_instance.text_field
#? str()
model_instance.email_field
#? float()
model_instance.float_field
#? bytes()
model_instance.binary_field
#? bool()
model_instance.boolean_field
#? decimal.Decimal()
model_instance.decimal_field
#? datetime.time()
model_instance.time_field
#? datetime.timedelta()
model_instance.duration_field
#? datetime.date()
model_instance.date_field
#? datetime.datetime()
model_instance.date_time_field
#! ['category_fk = models.ForeignKey(Category)']
model_instance.category_fk
#! ['category_name = models.CharField()']
model_instance.category_fk.category_name
#? Category()
model_instance.category_fk
#? str()
model_instance.category_fk.category_name
#? Category()
model_instance.category_fk2
#? str()
model_instance.category_fk2.category_name
#?
model_instance.category_fk3
#?
model_instance.category_fk4
#?
model_instance.category_fk5
#? models.manager.RelatedManager()
model_instance.tags_m2m
#? Tag()
model_instance.tags_m2m.get()
#? ['add']
model_instance.tags_m2m.add
#?
model_instance.unidentifiable
#! ['unidentifiable = NOT_FOUND']
model_instance.unidentifiable
# -----------------
# Queries
# -----------------
#? models.query.QuerySet.filter
model_instance.objects.filter
#? BusinessModel() None
model_instance.objects.filter().first()
#? str()
model_instance.objects.get().char_field
#? int()
model_instance.objects.update(x='')
#? BusinessModel()
model_instance.objects.create()
# -----------------
# Inheritance
# -----------------
class Inherited(BusinessModel):
text_field = models.IntegerField()
new_field = models.FloatField()
inherited = Inherited()
#? int()
inherited.text_field
#? str()
inherited.char_field
#? float()
inherited.new_field
#? str()
inherited.category_fk2.category_name
#? str()
inherited.objects.get().char_field
#? int()
inherited.objects.get().text_field
#? float()
inherited.objects.get().new_field
# -----------------
# Django Auth
# -----------------
#? str()
User().email
#? str()
User.objects.get().email
# -----------------
# values & values_list (dave is too lazy to implement it)
# -----------------
#?
model_instance.objects.values_list('char_field')[0]
#? dict()
model_instance.objects.values('char_field')[0]
#?
model_instance.objects.values('char_field')[0]['char_field']

View File

@@ -207,40 +207,36 @@ for a in list_func_t_to_list_t(12):
a a
# The following are all actually wrong, however we're mainly testing here that
# we don't error when processing invalid values, rather than that we get the
# right output.
x0 = list_func_t_to_list_t(["abc"])[0] x0 = list_func_t_to_list_t(["abc"])[0]
#? str() #?
x0 x0
x2 = list_func_t_to_list_t([tpl])[0] x2 = list_func_t_to_list_t([tpl])[0]
#? tuple() #?
x2 x2
x3 = list_func_t_to_list_t([tpl_typed])[0] x3 = list_func_t_to_list_t([tpl_typed])[0]
#? tuple() #?
x3 x3
x4 = list_func_t_to_list_t([collection])[0] x4 = list_func_t_to_list_t([collection])[0]
#? dict() #?
x4 x4
x5 = list_func_t_to_list_t([collection_typed])[0] x5 = list_func_t_to_list_t([collection_typed])[0]
#? dict() #?
x5 x5
x6 = list_func_t_to_list_t([custom_generic])[0] x6 = list_func_t_to_list_t([custom_generic])[0]
#? CustomGeneric() #?
x6 x6
x7 = list_func_t_to_list_t([plain_instance])[0] x7 = list_func_t_to_list_t([plain_instance])[0]
#? PlainClass() #?
x7 x7
for a in list_func_t_to_list_t([12]): for a in list_func_t_to_list_t([12]):
#? int() #?
a a

View File

@@ -60,6 +60,27 @@ for b in list_type_t_to_list_t(list_of_int_type):
b b
# Test construction of nested generic tuple return parameters
def list_t_to_list_tuple_t(the_list: List[T]) -> List[Tuple[T]]:
return [(x,) for x in the_list]
x1t = list_t_to_list_tuple_t(list_of_ints)[0][0]
#? int()
x1t
for c1 in list_t_to_list_tuple_t(list_of_ints):
#? int()
c1[0]
for c2, in list_t_to_list_tuple_t(list_of_ints):
#? int()
c2
# Test handling of nested tuple input parameters
def list_tuple_t_to_tuple_list_t(the_list: List[Tuple[T]]) -> Tuple[List[T], ...]: def list_tuple_t_to_tuple_list_t(the_list: List[Tuple[T]]) -> Tuple[List[T], ...]:
return tuple(list(x) for x in the_list) return tuple(list(x) for x in the_list)
@@ -82,11 +103,12 @@ for b in list_tuple_t_elipsis_to_tuple_list_t(list_of_int_tuple_elipsis):
b[0] b[0]
def foo(x: T) -> T: # Test handling of nested callables
def foo(x: int) -> int:
return x return x
list_of_funcs = [foo] # type: List[Callable[[T], T]] list_of_funcs = [foo] # type: List[Callable[[int], int]]
def list_func_t_to_list_func_type_t(the_list: List[Callable[[T], T]]) -> List[Callable[[Type[T]], T]]: def list_func_t_to_list_func_type_t(the_list: List[Callable[[T], T]]) -> List[Callable[[Type[T]], T]]:
def adapt(func: Callable[[T], T]) -> Callable[[Type[T]], T]: def adapt(func: Callable[[T], T]) -> Callable[[Type[T]], T]:
@@ -101,6 +123,21 @@ for b in list_func_t_to_list_func_type_t(list_of_funcs):
b(int) b(int)
def bar(*a, **k) -> int:
return len(a) + len(k)
list_of_funcs_2 = [bar] # type: List[Callable[..., int]]
def list_func_t_passthrough(the_list: List[Callable[..., T]]) -> List[Callable[..., T]]:
return the_list
for b in list_func_t_passthrough(list_of_funcs_2):
#? int()
b(None, x="x")
mapping_int_str = {42: 'a'} # type: Dict[int, str] mapping_int_str = {42: 'a'} # type: Dict[int, str]
# Test that mappings (that have more than one parameter) are handled # Test that mappings (that have more than one parameter) are handled

View File

@@ -283,6 +283,18 @@ def testnewtype2(y):
y y
#? [] #? []
y. y.
# The type of a NewType is equivalent to the type of its underlying type.
MyInt = typing.NewType('MyInt', int)
x = type(MyInt)
#? type.mro
x.mro
PlainInt = int
y = type(PlainInt)
#? type.mro
y.mro
# python > 2.7 # python > 2.7
class TestDefaultDict(typing.DefaultDict[str, int]): class TestDefaultDict(typing.DefaultDict[str, int]):

View File

@@ -103,3 +103,15 @@ while True:
bar = bar # type: bar bar = bar # type: bar
#? int() #? int()
bar bar
class Comprehension:
def __init__(self, foo):
self.foo = foo
def update(self):
self.foo = (self.foo,)
#? int() tuple()
Comprehension(1).foo[0]

View File

@@ -1,11 +0,0 @@
#! ['class ObjectDoesNotExist']
from django.core.exceptions import ObjectDoesNotExist
import django
#? ['get_version']
django.get_version
from django.conf import settings
#? ['configured']
settings.configured

View File

@@ -115,3 +115,18 @@ def test_docstring_decorator(goto_or_help_or_infer, skip_python2):
doc = d.docstring() doc = d.docstring()
assert doc == 'FunctionType(*args: Any, **kwargs: Any) -> Any\n\nhello' assert doc == 'FunctionType(*args: Any, **kwargs: Any) -> Any\n\nhello'
@pytest.mark.parametrize('code', ['', '\n', ' '])
def test_empty(Script, code):
assert not Script(code).help(1, 0)
@pytest.mark.parametrize('code', ['f()', '(bar or baz)', 'f[3]'])
def test_no_help_for_operator(Script, code):
assert not Script(code).help()
@pytest.mark.parametrize('code', ['()', '(1,)', '[]', '[1]', 'f[]'])
def test_help_for_operator(Script, code):
assert Script(code).help()

View File

@@ -20,6 +20,12 @@ def test_django_default_project(Script):
assert script._inference_state.project._django is True assert script._inference_state.project._django is True
def test_django_default_project_of_file(Script):
project = get_default_project(__file__)
d = os.path.dirname
assert project._path == d(d(d(__file__)))
def test_interpreter_project_path(): def test_interpreter_project_path():
# Run from anywhere it should be the cwd. # Run from anywhere it should be the cwd.
dir = os.path.join(root_dir, 'test') dir = os.path.join(root_dir, 'test')

View File

@@ -36,7 +36,7 @@ unspecified = %s
""" % (case, sorted(d - a), sorted(a - d)) """ % (case, sorted(d - a), sorted(a - d))
def test_completion(case, monkeypatch, environment, has_typing): def test_completion(case, monkeypatch, environment, has_typing, has_django):
skip_reason = case.get_skip_reason(environment) skip_reason = case.get_skip_reason(environment)
if skip_reason is not None: if skip_reason is not None:
pytest.skip(skip_reason) pytest.skip(skip_reason)
@@ -47,6 +47,8 @@ def test_completion(case, monkeypatch, environment, has_typing):
_CONTAINS_TYPING = ('pep0484_typing', 'pep0484_comments', 'pep0526_variables') _CONTAINS_TYPING = ('pep0484_typing', 'pep0484_comments', 'pep0526_variables')
if not has_typing and any(x in case.path for x in _CONTAINS_TYPING): if not has_typing and any(x in case.path for x in _CONTAINS_TYPING):
pytest.skip('Needs the typing module installed to run this test.') pytest.skip('Needs the typing module installed to run this test.')
if (not has_django or environment.version_info.major == 2) and case.path.endswith('django.py'):
pytest.skip('Needs django to be installed to run this test.')
repo_root = helpers.root_dir repo_root = helpers.root_dir
monkeypatch.chdir(os.path.join(repo_root, 'jedi')) monkeypatch.chdir(os.path.join(repo_root, 'jedi'))
case.run(assert_case_equal, environment) case.run(assert_case_equal, environment)