mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 14:04:26 +08:00
Add support for Django signatures, fixes parts of #1587
This commit is contained in:
@@ -227,6 +227,11 @@ class ClassMixin(object):
|
||||
# Since calling staticmethod without a function is illegal, the Jedi
|
||||
# plugin doesn't return anything. Therefore call directly and get what
|
||||
# we want: An instance of staticmethod.
|
||||
metaclasses = self.get_metaclasses()
|
||||
if metaclasses:
|
||||
sigs = self.get_metaclass_signatures(metaclasses)
|
||||
if sigs:
|
||||
return sigs
|
||||
args = ValuesArguments([])
|
||||
init_funcs = self.py__call__(args).py__getattribute__('__init__')
|
||||
return [sig.bind(self) for sig in init_funcs.get_signatures()]
|
||||
@@ -360,8 +365,8 @@ class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase
|
||||
)]
|
||||
|
||||
@plugin_manager.decorate()
|
||||
def get_metaclass_filters(self, metaclass, is_instance):
|
||||
debug.warning('Unprocessed metaclass %s', metaclass)
|
||||
def get_metaclass_filters(self, metaclasses, is_instance):
|
||||
debug.warning('Unprocessed metaclass %s', metaclasses)
|
||||
return []
|
||||
|
||||
@inference_state_method_cache(default=NO_VALUES)
|
||||
@@ -381,3 +386,7 @@ class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase
|
||||
if values:
|
||||
return values
|
||||
return NO_VALUES
|
||||
|
||||
@plugin_manager.decorate()
|
||||
def get_metaclass_signatures(self, metaclasses):
|
||||
return []
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
"""
|
||||
Module is used to infer Django model fields.
|
||||
"""
|
||||
from jedi._compatibility import Parameter
|
||||
from jedi import debug
|
||||
from jedi.inference.cache import inference_state_function_cache
|
||||
from jedi.inference.base_value import ValueSet, iterator_to_value_set, ValueWrapper
|
||||
from jedi.inference.filters import DictFilter, AttributeOverwrite, publish_method
|
||||
from jedi.inference.names import NameWrapper
|
||||
from jedi.inference.filters import DictFilter, AttributeOverwrite
|
||||
from jedi.inference.names import NameWrapper, BaseTreeParamName
|
||||
from jedi.inference.compiled.value import EmptyCompiledName
|
||||
from jedi.inference.value.instance import TreeInstance
|
||||
from jedi.inference.value.klass import ClassMixin
|
||||
from jedi.inference.gradual.base import GenericClass
|
||||
from jedi.inference.gradual.generics import TupleGenericManager
|
||||
from jedi.inference.arguments import repack_with_argument_clinic
|
||||
from jedi.inference.signature import AbstractSignature
|
||||
|
||||
|
||||
mapping = {
|
||||
@@ -35,6 +37,7 @@ mapping = {
|
||||
}
|
||||
|
||||
|
||||
@inference_state_function_cache()
|
||||
def _get_deferred_attributes(inference_state):
|
||||
return inference_state.import_module(
|
||||
('django', 'db', 'models', 'query_utils')
|
||||
@@ -150,17 +153,56 @@ def _new_dict_filter(cls, is_instance):
|
||||
return DictFilter(dct)
|
||||
|
||||
|
||||
def is_django_model_base(value):
|
||||
return value.py__name__() == 'ModelBase' \
|
||||
and value.get_root_context().py__name__() == 'django.db.models.base'
|
||||
|
||||
|
||||
def get_metaclass_filters(func):
|
||||
def wrapper(cls, metaclasses, is_instance):
|
||||
for metaclass in metaclasses:
|
||||
if metaclass.py__name__() == 'ModelBase' \
|
||||
and metaclass.get_root_context().py__name__() == 'django.db.models.base':
|
||||
if is_django_model_base(metaclass):
|
||||
return [_new_dict_filter(cls, is_instance)]
|
||||
|
||||
return func(cls, metaclasses, is_instance)
|
||||
return wrapper
|
||||
|
||||
|
||||
def tree_name_to_values(func):
|
||||
def wrapper(inference_state, context, tree_name):
|
||||
result = func(inference_state, context, tree_name)
|
||||
if tree_name.value == 'BaseManager' and context.is_module() \
|
||||
and context.py__name__() == 'django.db.models.manager':
|
||||
return ValueSet(ManagerWrapper(r) for r in result)
|
||||
|
||||
if tree_name.value == 'Field' and context.is_module() \
|
||||
and context.py__name__() == 'django.db.models.fields':
|
||||
return ValueSet(FieldWrapper(r) for r in result)
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
|
||||
def _find_fields(cls):
|
||||
for name in _new_dict_filter(cls, is_instance=False).values():
|
||||
for value in name.infer():
|
||||
if value.name.get_qualified_names(include_module_names=True) \
|
||||
== ('django', 'db', 'models', 'query_utils', 'DeferredAttribute'):
|
||||
yield name
|
||||
|
||||
|
||||
def _get_signatures(cls):
|
||||
return [DjangoModelSignature(cls, field_names=list(_find_fields(cls)))]
|
||||
|
||||
|
||||
def get_metaclass_signatures(func):
|
||||
def wrapper(cls, metaclasses):
|
||||
for metaclass in metaclasses:
|
||||
if is_django_model_base(metaclass):
|
||||
return _get_signatures(cls)
|
||||
return func(cls, metaclass)
|
||||
return wrapper
|
||||
|
||||
|
||||
class ManagerWrapper(ValueWrapper):
|
||||
def py__getitem__(self, index_value_set, contextualized_node):
|
||||
return ValueSet(
|
||||
@@ -180,11 +222,38 @@ class GenericManagerWrapper(AttributeOverwrite, ClassMixin):
|
||||
return self._wrapped_value.with_generics(generics_tuple)
|
||||
|
||||
|
||||
def tree_name_to_values(func):
|
||||
def wrapper(inference_state, context, tree_name):
|
||||
result = func(inference_state, context, tree_name)
|
||||
if tree_name.value == 'BaseManager' and context.is_module() \
|
||||
and context.py__name__() == 'django.db.models.manager':
|
||||
return ValueSet(ManagerWrapper(r) for r in result)
|
||||
return result
|
||||
return wrapper
|
||||
class FieldWrapper(ValueWrapper):
|
||||
def py__getitem__(self, index_value_set, contextualized_node):
|
||||
return ValueSet(
|
||||
GenericFieldWrapper(generic)
|
||||
for generic in self._wrapped_value.py__getitem__(
|
||||
index_value_set, contextualized_node)
|
||||
)
|
||||
|
||||
|
||||
class GenericFieldWrapper(AttributeOverwrite, ClassMixin):
|
||||
def py__get__on_class(self, calling_instance, instance, class_value):
|
||||
# This is mostly an optimization to avoid Jedi aborting inference,
|
||||
# because of too many function executions of Field.__get__.
|
||||
return ValueSet({calling_instance})
|
||||
|
||||
|
||||
class DjangoModelSignature(AbstractSignature):
|
||||
def __init__(self, value, field_names):
|
||||
super(DjangoModelSignature, self).__init__(value)
|
||||
self._field_names = field_names
|
||||
|
||||
def get_param_names(self, resolve_stars=False):
|
||||
return [DjangoParamName(name) for name in self._field_names]
|
||||
|
||||
|
||||
class DjangoParamName(BaseTreeParamName):
|
||||
def __init__(self, field_name):
|
||||
super(DjangoParamName, self).__init__(field_name.parent_context, field_name.tree_name)
|
||||
self._field_name = field_name
|
||||
|
||||
def get_kind(self):
|
||||
return Parameter.KEYWORD_ONLY
|
||||
|
||||
def infer(self):
|
||||
return self._field_name.infer()
|
||||
|
||||
@@ -73,6 +73,8 @@ class BusinessModel(models.Model):
|
||||
BusinessModel.integer_field
|
||||
#? DeferredAttribute()
|
||||
BusinessModel.tags_m2m
|
||||
#? DeferredAttribute()
|
||||
BusinessModel.email_field
|
||||
|
||||
model_instance = BusinessModel()
|
||||
|
||||
@@ -250,3 +252,24 @@ BusinessModel.objects.values_list('char_field')[0]
|
||||
BusinessModel.objects.values('char_field')[0]
|
||||
#?
|
||||
BusinessModel.objects.values('char_field')[0]['char_field']
|
||||
|
||||
# -----------------
|
||||
# Completion
|
||||
# -----------------
|
||||
|
||||
#? 19 ['text_field=']
|
||||
Inherited(text_fiel)
|
||||
#? 18 ['new_field=']
|
||||
Inherited(new_fiel)
|
||||
#? 19 ['char_field=']
|
||||
Inherited(char_fiel)
|
||||
#? 19 ['email_field=']
|
||||
Inherited(email_fie)
|
||||
#? 19 []
|
||||
Inherited(unidentif)
|
||||
#? 21 ['category_fk=', 'category_fk2=', 'category_fk3=', 'category_fk4=', 'category_fk5=']
|
||||
Inherited(category_fk)
|
||||
#? 21 ['attached_o2o=']
|
||||
Inherited(attached_o2)
|
||||
#? 18 ['tags_m2m=']
|
||||
Inherited(tags_m2m)
|
||||
|
||||
Reference in New Issue
Block a user