""" Module is used to infer Django model fields. """ from jedi import debug 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 = { 'IntegerField': (None, 'int'), 'BigIntegerField': (None, 'int'), 'PositiveIntegerField': (None, 'int'), 'SmallIntegerField': (None, 'int'), 'CharField': (None, 'str'), 'TextField': (None, 'str'), 'EmailField': (None, 'str'), 'GenericIPAddressField': (None, 'str'), 'URLField': (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'), 'UUIDField': ('uuid', 'UUID'), } 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: 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, 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, 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, [ _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, 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, self._is_instance) 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_mixin(): generics_manager = TupleGenericManager((ValueSet([cls]),)) for c in GenericClass(m, generics_manager).execute_annotation(): return c return None def _new_dict_filter(cls, is_instance): filters = list(cls.get_filters( is_instance=is_instance, include_metaclasses=False, include_type_when_class=False) ) dct = { name.string_name: DjangoModelName(cls, name, is_instance) for filter_ in reversed(filters) for name in filter_.values() } 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) 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': return [_new_dict_filter(cls, is_instance)] 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