mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 14:04:26 +08:00
Use py__get__ for Django Model.objects
This includes the fix in https://github.com/typeddjango/django-stubs/pull/394
This commit is contained in:
@@ -240,6 +240,9 @@ class Value(HelperValueMixin):
|
||||
debug.warning("No __get__ defined on %s", self)
|
||||
return ValueSet([self])
|
||||
|
||||
def py__get__on_class(self, calling_instance, instance, class_value):
|
||||
return NotImplemented
|
||||
|
||||
def get_qualified_names(self):
|
||||
# Returns Optional[Tuple[str, ...]]
|
||||
return None
|
||||
|
||||
@@ -200,6 +200,9 @@ class GenericClass(DefineGenericBaseClass, ClassMixin):
|
||||
return True
|
||||
return self._class_value.is_sub_class_of(class_value)
|
||||
|
||||
def with_generics(self, generics_tuple):
|
||||
return self._class_value.with_generics(generics_tuple)
|
||||
|
||||
def infer_type_vars(self, value_set):
|
||||
# Circular
|
||||
from jedi.inference.gradual.annotation import merge_pairwise_generics, merge_type_var_dicts
|
||||
@@ -287,6 +290,9 @@ class _LazyGenericBaseClass(object):
|
||||
new |= ValueSet([type_var])
|
||||
yield new
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._lazy_base_class)
|
||||
|
||||
|
||||
class _GenericInstanceWrapper(ValueWrapper):
|
||||
def py__stop_iteration_returns(self):
|
||||
|
||||
@@ -288,6 +288,11 @@ class _BaseTreeInstance(AbstractInstanceValue):
|
||||
"""
|
||||
# Arguments in __get__ descriptors are obj, class.
|
||||
# `method` is the new parent of the array, don't know if that's good.
|
||||
for cls in self.class_value.py__mro__():
|
||||
result = cls.py__get__on_class(self, instance, class_value)
|
||||
if result is not NotImplemented:
|
||||
return result
|
||||
|
||||
names = self.get_function_slot_names(u'__get__')
|
||||
if names:
|
||||
if instance is None:
|
||||
|
||||
@@ -114,8 +114,6 @@ class ClassFilter(ParserTreeFilter):
|
||||
if expr_stmt is not None and expr_stmt.type == 'expr_stmt':
|
||||
annassign = expr_stmt.children[1]
|
||||
if annassign.type == 'annassign':
|
||||
# TODO this is not proper matching
|
||||
|
||||
# If there is an =, the variable is obviously also
|
||||
# defined on the class.
|
||||
if 'ClassVar' not in annassign.children[1].get_code() \
|
||||
@@ -138,7 +136,7 @@ class ClassMixin(object):
|
||||
def is_class_mixin(self):
|
||||
return True
|
||||
|
||||
def py__call__(self, arguments=None):
|
||||
def py__call__(self, arguments):
|
||||
from jedi.inference.value import TreeInstance
|
||||
|
||||
from jedi.inference.gradual.typing import TypedDict
|
||||
@@ -195,7 +193,7 @@ class ClassMixin(object):
|
||||
metaclasses = self.get_metaclasses()
|
||||
if metaclasses:
|
||||
for f in self.get_metaclass_filters(metaclasses, is_instance):
|
||||
yield f
|
||||
yield f # Python 2..
|
||||
|
||||
for cls in self.py__mro__():
|
||||
if cls.is_compiled():
|
||||
@@ -203,7 +201,7 @@ class ClassMixin(object):
|
||||
yield filter
|
||||
else:
|
||||
yield ClassFilter(
|
||||
self, node_context=cls.as_context(),
|
||||
cls, node_context=self.as_context(),
|
||||
origin_scope=origin_scope,
|
||||
is_instance=is_instance
|
||||
)
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
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.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.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
|
||||
|
||||
|
||||
mapping = {
|
||||
@@ -124,36 +127,6 @@ def _create_manager_for(cls, manager_cls='BaseManager'):
|
||||
|
||||
|
||||
def _new_dict_filter(cls, is_instance):
|
||||
def get_manager_name(filters):
|
||||
for f in filters:
|
||||
names = f.get('objects')
|
||||
if not names:
|
||||
continue
|
||||
|
||||
# Found a match. Either the model has a custom manager, or we're
|
||||
# now in django.db.models.Model. If the latter we need to use
|
||||
# `_create_manager_for` because the manager we get from the
|
||||
# stubs doesn't work right.
|
||||
|
||||
name = names[0] # The first name should be good enough.
|
||||
|
||||
parent = name.get_defining_qualified_value()
|
||||
if parent.py__name__() == 'Model':
|
||||
|
||||
django_models_model, = cls.inference_state.import_module(
|
||||
('django', 'db', 'models', 'base'),
|
||||
).py__getattribute__('Model')
|
||||
if django_models_model == parent:
|
||||
# Don't want to use the value from the Django stubs, but
|
||||
# we have found the point where they'd take precedence.
|
||||
break
|
||||
|
||||
return name
|
||||
|
||||
manager = _create_manager_for(cls)
|
||||
if manager:
|
||||
return manager.name
|
||||
|
||||
filters = list(cls.get_filters(
|
||||
is_instance=is_instance,
|
||||
include_metaclasses=False,
|
||||
@@ -164,10 +137,14 @@ def _new_dict_filter(cls, is_instance):
|
||||
for filter_ in reversed(filters)
|
||||
for name in filter_.values()
|
||||
}
|
||||
|
||||
manager_name = get_manager_name(filters)
|
||||
if manager_name:
|
||||
dct['objects'] = manager_name
|
||||
if is_instance:
|
||||
# Replace the objects with a name that amounts to nothing when accessed
|
||||
# in an instance. This is not perfect and still completes "objects" in
|
||||
# that case, but it at least not inferes stuff like `.objects.filter`.
|
||||
# It would be nicer to do that in a better way, so that it also doesn't
|
||||
# show up in completions, but it's probably just not worth doing that
|
||||
# for the extra amount of work.
|
||||
dct['objects'] = EmptyCompiledName(cls.inference_state, 'objects')
|
||||
|
||||
return DictFilter(dct)
|
||||
|
||||
@@ -181,3 +158,32 @@ def get_metaclass_filters(func):
|
||||
|
||||
return func(cls, metaclasses, is_instance)
|
||||
return wrapper
|
||||
|
||||
|
||||
class ManagerWrapper(ValueWrapper):
|
||||
def py__getitem__(self, index_value_set, contextualized_node):
|
||||
return ValueSet(
|
||||
GenericManagerWrapper(generic)
|
||||
for generic in self._wrapped_value.py__getitem__(
|
||||
index_value_set, contextualized_node)
|
||||
)
|
||||
|
||||
|
||||
class GenericManagerWrapper(AttributeOverwrite, ClassMixin):
|
||||
def py__get__on_class(self, calling_instance, instance, class_value):
|
||||
return calling_instance.class_value.with_generics(
|
||||
(ValueSet({class_value}),)
|
||||
).py__call__(calling_instance._arguments)
|
||||
|
||||
def with_generics(self, generics_tuple):
|
||||
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
|
||||
|
||||
@@ -162,16 +162,22 @@ model_instance.method
|
||||
# Queries
|
||||
# -----------------
|
||||
|
||||
#? models.query.QuerySet.filter
|
||||
#? ['objects']
|
||||
model_instance.object
|
||||
#?
|
||||
model_instance.objects
|
||||
#?
|
||||
model_instance.objects.filter
|
||||
#? models.query.QuerySet.filter
|
||||
BusinessModel.objects.filter
|
||||
#? BusinessModel() None
|
||||
model_instance.objects.filter().first()
|
||||
BusinessModel.objects.filter().first()
|
||||
#? str()
|
||||
model_instance.objects.get().char_field
|
||||
BusinessModel.objects.get().char_field
|
||||
#? int()
|
||||
model_instance.objects.update(x='')
|
||||
BusinessModel.objects.update(x='')
|
||||
#? BusinessModel()
|
||||
model_instance.objects.create()
|
||||
BusinessModel.objects.create()
|
||||
|
||||
# -----------------
|
||||
# Custom object manager
|
||||
@@ -179,9 +185,13 @@ model_instance.objects.create()
|
||||
|
||||
#? TagManager()
|
||||
Tag.objects
|
||||
#? Tag() None
|
||||
Tag.objects.filter().first()
|
||||
|
||||
#? TagManager()
|
||||
Tag.custom_objects
|
||||
#? Tag() None
|
||||
Tag.custom_objects.filter().first()
|
||||
|
||||
# -----------------
|
||||
# Inheritance
|
||||
@@ -199,14 +209,27 @@ inherited.char_field
|
||||
#? float()
|
||||
inherited.new_field
|
||||
|
||||
#?
|
||||
Inherited.category_fk2.category_name
|
||||
#? str()
|
||||
inherited.category_fk2.category_name
|
||||
#? str()
|
||||
inherited.objects.get().char_field
|
||||
Inherited.objects.get().char_field
|
||||
#? int()
|
||||
inherited.objects.get().text_field
|
||||
Inherited.objects.get().text_field
|
||||
#? float()
|
||||
inherited.objects.get().new_field
|
||||
Inherited.objects.get().new_field
|
||||
|
||||
# -----------------
|
||||
# Model methods
|
||||
# -----------------
|
||||
|
||||
#? ['from_db']
|
||||
Inherited.from_db
|
||||
#? ['validate_unique']
|
||||
Inherited.validate_uniqu
|
||||
#? ['validate_unique']
|
||||
Inherited().validate_unique
|
||||
|
||||
# -----------------
|
||||
# Django Auth
|
||||
@@ -222,8 +245,8 @@ User.objects.get().email
|
||||
# -----------------
|
||||
|
||||
#?
|
||||
model_instance.objects.values_list('char_field')[0]
|
||||
BusinessModel.objects.values_list('char_field')[0]
|
||||
#? dict()
|
||||
model_instance.objects.values('char_field')[0]
|
||||
BusinessModel.objects.values('char_field')[0]
|
||||
#?
|
||||
model_instance.objects.values('char_field')[0]['char_field']
|
||||
BusinessModel.objects.values('char_field')[0]['char_field']
|
||||
|
||||
Reference in New Issue
Block a user