From 9adcf3d2337dff1b3e5fefff5f8d99bafdb36f3d Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 7 Jun 2020 14:54:26 +0200 Subject: [PATCH] Make sure meta class filters can distinguish between classes and instances --- jedi/inference/value/klass.py | 6 +++--- jedi/plugins/django.py | 34 ++++++++++++++++++++++++---------- jedi/plugins/stdlib.py | 4 ++-- test/completion/django.py | 9 +++++++++ 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/jedi/inference/value/klass.py b/jedi/inference/value/klass.py index 4a47da0e..6f60d4b8 100644 --- a/jedi/inference/value/klass.py +++ b/jedi/inference/value/klass.py @@ -193,7 +193,7 @@ class ClassMixin(object): if include_metaclasses: metaclasses = self.get_metaclasses() if metaclasses: - for f in self.get_metaclass_filters(metaclasses): + for f in self.get_metaclass_filters(metaclasses, is_instance): yield f for cls in self.py__mro__(): @@ -361,8 +361,8 @@ class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase return ValueSet({self}) @plugin_manager.decorate() - def get_metaclass_filters(self, metaclass): - debug.dbg('Unprocessed metaclass %s', metaclass) + def get_metaclass_filters(self, metaclass, is_instance): + debug.warning('Unprocessed metaclass %s', metaclass) return [] @inference_state_method_cache(default=NO_VALUES) diff --git a/jedi/plugins/django.py b/jedi/plugins/django.py index 12c1fdb7..b8aaa126 100644 --- a/jedi/plugins/django.py +++ b/jedi/plugins/django.py @@ -32,12 +32,21 @@ mapping = { } -def _infer_scalar_field(inference_state, field_name, field_tree_instance): +def _get_deferred_attributes(inference_state): + return inference_state.import_module( + ('django', 'db', 'models', 'query_utils') + ).py__getattribute__('DeferredAttribute').execute_annotation() + + +def _infer_scalar_field(inference_state, field_name, field_tree_instance, is_instance): try: module_name, attribute_name = mapping[field_tree_instance.py__name__()] except KeyError: return None + if not is_instance: + return _get_deferred_attributes(inference_state) + if module_name is None: module = inference_state.builtins_module else: @@ -65,16 +74,20 @@ def _get_foreign_key_values(cls, field_tree_instance): yield value -def _infer_field(cls, field_name): +def _infer_field(cls, field_name, is_instance): 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) + scalar_field = _infer_scalar_field( + inference_state, field_name, field_tree_instance, is_instance) if scalar_field is not None: return scalar_field name = field_tree_instance.py__name__() is_many_to_many = name == 'ManyToManyField' if name in ('ForeignKey', 'OneToOneField') or is_many_to_many: + if not is_instance: + return _get_deferred_attributes(inference_state) + values = _get_foreign_key_values(cls, field_tree_instance) if is_many_to_many: return ValueSet(filter(None, [ @@ -89,12 +102,13 @@ def _infer_field(cls, field_name): class DjangoModelName(NameWrapper): - def __init__(self, cls, name): + def __init__(self, cls, name, is_instance): super(DjangoModelName, self).__init__(name) self._cls = cls + self._is_instance = is_instance def infer(self): - return _infer_field(self._cls, self._wrapped_name) + return _infer_field(self._cls, self._wrapped_name, self._is_instance) def _create_manager_for(cls, manager_cls='BaseManager'): @@ -109,7 +123,7 @@ def _create_manager_for(cls, manager_cls='BaseManager'): return None -def _new_dict_filter(cls): +def _new_dict_filter(cls, is_instance): def get_manager_name(filters): for f in filters: names = f.get('objects') @@ -142,7 +156,7 @@ def _new_dict_filter(cls): filters = list(cls.get_filters(is_instance=True, include_metaclasses=False)) dct = { - name.string_name: DjangoModelName(cls, name) + name.string_name: DjangoModelName(cls, name, is_instance) for filter_ in reversed(filters) for name in filter_.values() } @@ -155,11 +169,11 @@ def _new_dict_filter(cls): def get_metaclass_filters(func): - def wrapper(cls, metaclasses): + 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': - return [_new_dict_filter(cls)] + return [_new_dict_filter(cls, is_instance)] - return func(cls, metaclasses) + return func(cls, metaclasses, is_instance) return wrapper diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index 2e113715..475cd6a8 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -801,7 +801,7 @@ _implemented = { def get_metaclass_filters(func): - def wrapper(cls, metaclasses): + def wrapper(cls, metaclasses, is_instance): for metaclass in metaclasses: if metaclass.py__name__() == 'EnumMeta' \ and metaclass.get_root_context().py__name__() == 'enum': @@ -809,7 +809,7 @@ def get_metaclass_filters(func): return [DictFilter({ name.string_name: EnumInstance(cls, name).name for name in filter_.values() })] - return func(cls, metaclasses) + return func(cls, metaclasses, is_instance) return wrapper diff --git a/test/completion/django.py b/test/completion/django.py index 6ed34ce1..6df0c3a3 100644 --- a/test/completion/django.py +++ b/test/completion/django.py @@ -4,6 +4,7 @@ import uuid from django.db import models from django.contrib.auth.models import User +from django.db.models.query_utils import DeferredAttribute class TagManager(models.Manager): @@ -58,6 +59,9 @@ class BusinessModel(models.Model): unidentifiable = NOT_FOUND + #? models.IntegerField() + integer_field + def method(self): return 42 @@ -65,6 +69,11 @@ class BusinessModel(models.Model): # Model attribute inference # ----------------- +#? DeferredAttribute() +BusinessModel.integer_field +#? DeferredAttribute() +BusinessModel.tags_m2m + model_instance = BusinessModel() #? int()