From f5ae7148dd772421af473aa74df2ad449c910ba3 Mon Sep 17 00:00:00 2001 From: ANtlord Date: Wed, 18 Sep 2019 09:27:39 +0300 Subject: [PATCH 01/15] Basic django model fields are infered as builtin types. --- jedi/plugins/stdlib.py | 90 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index fdf7bb25..4c53594e 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -787,6 +787,69 @@ _implemented = { } +def new_django_dict_filter(cls): + filter_ = ParserTreeFilter(parent_context=cls.as_context()) + field, = filter_.values() + field_tree_instance, = field.infer() + + if field_tree_instance.name.string_name in ('CharField', 'TextField', 'EmailField'): + builtin_str, = cls.inference_state.builtins_module.py__getattribute__('str') + return [DictFilter({ + field.string_name: DjangoModelField(builtin_str, field).name + })] + + integer_field_classes = ('IntegerField', 'BigIntegerField', 'PositiveIntegerField', 'SmallIntegerField') + if field_tree_instance.name.string_name in integer_field_classes: + builtin_str, = cls.inference_state.builtins_module.py__getattribute__('int') + return [DictFilter({ + field.string_name: DjangoModelField(builtin_str, field).name + })] + + if field_tree_instance.name.string_name == 'FloatField': + builtin_str, = cls.inference_state.builtins_module.py__getattribute__('float') + return [DictFilter({ + field.string_name: DjangoModelField(builtin_str, field).name + })] + + + if field_tree_instance.name.string_name == 'BinaryField': + builtin_str, = cls.inference_state.builtins_module.py__getattribute__('bytes') + return [DictFilter({ + field.string_name: DjangoModelField(builtin_str, field).name + })] + + if field_tree_instance.name.string_name == 'BooleanField': + builtin_str, = cls.inference_state.builtins_module.py__getattribute__('bool') + return [DictFilter({ + field.string_name: DjangoModelField(builtin_str, field).name + })] + + if field_tree_instance.name.string_name == 'DecimalField': + # TODO: make decimal.Decimal filter + return + + if field_tree_instance.name.string_name == 'ForeignKey': + # TODO: infer related object class and make a filter for that class + return + + if field_tree_instance.name.string_name == 'TimeField': + # TODO: make time.time filter + return + + if field_tree_instance.name.string_name == 'DurationField': + # TODO: make datetime.timedelta filter + return + + if field_tree_instance.name.string_name == 'DateField': + # TODO: make datetime.date filter + return + + if field_tree_instance.name.string_name == 'DatetimeField': + # TODO: make datetime.datetime filter + return + + + def get_metaclass_filters(func): def wrapper(cls, metaclasses): for metaclass in metaclasses: @@ -796,10 +859,37 @@ def get_metaclass_filters(func): return [DictFilter({ name.string_name: EnumInstance(cls, name).name for name in filter_.values() })] + + if metaclass.py__name__() == 'ModelBase' \ + and metaclass.get_root_context().py__name__() == 'django.db.models.base': + django_dict_filter = new_django_dict_filter(cls) + if django_dict_filter is not None: + return django_dict_filter + return func(cls, metaclasses) return wrapper +class DjangoModelField(LazyValueWrapper): + def __init__(self, cls, name): + self.inference_state = cls.inference_state + self._cls = cls # Corresponds to super().__self__ + self._name = name + self.tree_node = self._name.tree_name + + @safe_property + def name(self): + return ValueName(self, self._name.tree_name) + + def _get_wrapped_value(self): + obj, = self._cls.execute_with_values() + return obj + + def get_filters(self, origin_scope=None): + for f in self._get_wrapped_value().get_filters(): + yield f + + class EnumInstance(LazyValueWrapper): def __init__(self, cls, name): self.inference_state = cls.inference_state From 659aaf6861fb238b3bd2c06e70a4ce2574051dee Mon Sep 17 00:00:00 2001 From: ANtlord Date: Thu, 19 Sep 2019 08:42:39 +0300 Subject: [PATCH 02/15] Naming corrections. --- jedi/plugins/stdlib.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index 9e60cbab..98e8987c 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -787,35 +787,35 @@ def new_django_dict_filter(cls): field_tree_instance, = field.infer() if field_tree_instance.name.string_name in ('CharField', 'TextField', 'EmailField'): - builtin_str, = cls.inference_state.builtins_module.py__getattribute__('str') + model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('str') return [DictFilter({ - field.string_name: DjangoModelField(builtin_str, field).name + field.string_name: DjangoModelField(model_instance_field_type, field).name })] integer_field_classes = ('IntegerField', 'BigIntegerField', 'PositiveIntegerField', 'SmallIntegerField') if field_tree_instance.name.string_name in integer_field_classes: - builtin_str, = cls.inference_state.builtins_module.py__getattribute__('int') + model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('int') return [DictFilter({ - field.string_name: DjangoModelField(builtin_str, field).name + field.string_name: DjangoModelField(model_instance_field_type, field).name })] if field_tree_instance.name.string_name == 'FloatField': - builtin_str, = cls.inference_state.builtins_module.py__getattribute__('float') + model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('float') return [DictFilter({ - field.string_name: DjangoModelField(builtin_str, field).name + field.string_name: DjangoModelField(model_instance_field_type, field).name })] if field_tree_instance.name.string_name == 'BinaryField': - builtin_str, = cls.inference_state.builtins_module.py__getattribute__('bytes') + model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('bytes') return [DictFilter({ - field.string_name: DjangoModelField(builtin_str, field).name + field.string_name: DjangoModelField(model_instance_field_type, field).name })] if field_tree_instance.name.string_name == 'BooleanField': - builtin_str, = cls.inference_state.builtins_module.py__getattribute__('bool') + model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('bool') return [DictFilter({ - field.string_name: DjangoModelField(builtin_str, field).name + field.string_name: DjangoModelField(model_instance_field_type, field).name })] if field_tree_instance.name.string_name == 'DecimalField': From fbeff007612fcd584db1efae94df33383ab3111d Mon Sep 17 00:00:00 2001 From: ANtlord Date: Fri, 6 Dec 2019 23:47:19 +0200 Subject: [PATCH 03/15] Decimal, DurationField, DateField, DateTimeField, DecimalField django types are resolved. --- jedi/plugins/stdlib.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index 98e8987c..2c7cf98e 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -805,7 +805,6 @@ def new_django_dict_filter(cls): field.string_name: DjangoModelField(model_instance_field_type, field).name })] - if field_tree_instance.name.string_name == 'BinaryField': model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('bytes') return [DictFilter({ @@ -819,28 +818,38 @@ def new_django_dict_filter(cls): })] if field_tree_instance.name.string_name == 'DecimalField': - # TODO: make decimal.Decimal filter - return + model_instance_field_type, = cls.inference_state.import_module(('decimal',)).py__getattribute__('Decimal') + return [DictFilter({ + field.string_name: DjangoModelField(model_instance_field_type, field).name + })] if field_tree_instance.name.string_name == 'ForeignKey': # TODO: infer related object class and make a filter for that class return if field_tree_instance.name.string_name == 'TimeField': - # TODO: make time.time filter - return + model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('time') + return [DictFilter({ + field.string_name: DjangoModelField(model_instance_field_type, field).name + })] if field_tree_instance.name.string_name == 'DurationField': - # TODO: make datetime.timedelta filter - return + model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('timedelta') + return [DictFilter({ + field.string_name: DjangoModelField(model_instance_field_type, field).name + })] if field_tree_instance.name.string_name == 'DateField': - # TODO: make datetime.date filter - return + model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('date') + return [DictFilter({ + field.string_name: DjangoModelField(model_instance_field_type, field).name + })] - if field_tree_instance.name.string_name == 'DatetimeField': - # TODO: make datetime.datetime filter - return + if field_tree_instance.name.string_name == 'DateTimeField': + model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('datetime') + return [DictFilter({ + field.string_name: DjangoModelField(model_instance_field_type, field).name + })] From 654475b7d6198a083c1ad9087bd10acf76dd795c Mon Sep 17 00:00:00 2001 From: ANtlord Date: Fri, 6 Dec 2019 23:58:13 +0200 Subject: [PATCH 04/15] Infering multiple fields is fixed. --- jedi/plugins/stdlib.py | 51 +++++++++++++++--------------------------- 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index 2c7cf98e..5074e476 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -781,47 +781,33 @@ _implemented = { } -def new_django_dict_filter(cls): - filter_ = ParserTreeFilter(parent_context=cls.as_context()) - field, = filter_.values() +def infer_django_field(cls, field): field_tree_instance, = field.infer() if field_tree_instance.name.string_name in ('CharField', 'TextField', 'EmailField'): model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('str') - return [DictFilter({ - field.string_name: DjangoModelField(model_instance_field_type, field).name - })] + return DjangoModelField(model_instance_field_type, field).name integer_field_classes = ('IntegerField', 'BigIntegerField', 'PositiveIntegerField', 'SmallIntegerField') if field_tree_instance.name.string_name in integer_field_classes: model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('int') - return [DictFilter({ - field.string_name: DjangoModelField(model_instance_field_type, field).name - })] + return DjangoModelField(model_instance_field_type, field).name if field_tree_instance.name.string_name == 'FloatField': model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('float') - return [DictFilter({ - field.string_name: DjangoModelField(model_instance_field_type, field).name - })] + return DjangoModelField(model_instance_field_type, field).name if field_tree_instance.name.string_name == 'BinaryField': model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('bytes') - return [DictFilter({ - field.string_name: DjangoModelField(model_instance_field_type, field).name - })] + return DjangoModelField(model_instance_field_type, field).name if field_tree_instance.name.string_name == 'BooleanField': model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('bool') - return [DictFilter({ - field.string_name: DjangoModelField(model_instance_field_type, field).name - })] + return DjangoModelField(model_instance_field_type, field).name if field_tree_instance.name.string_name == 'DecimalField': model_instance_field_type, = cls.inference_state.import_module(('decimal',)).py__getattribute__('Decimal') - return [DictFilter({ - field.string_name: DjangoModelField(model_instance_field_type, field).name - })] + return DjangoModelField(model_instance_field_type, field).name if field_tree_instance.name.string_name == 'ForeignKey': # TODO: infer related object class and make a filter for that class @@ -829,30 +815,29 @@ def new_django_dict_filter(cls): if field_tree_instance.name.string_name == 'TimeField': model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('time') - return [DictFilter({ - field.string_name: DjangoModelField(model_instance_field_type, field).name - })] + return DjangoModelField(model_instance_field_type, field).name if field_tree_instance.name.string_name == 'DurationField': model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('timedelta') - return [DictFilter({ - field.string_name: DjangoModelField(model_instance_field_type, field).name - })] + return DjangoModelField(model_instance_field_type, field).name if field_tree_instance.name.string_name == 'DateField': model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('date') - return [DictFilter({ - field.string_name: DjangoModelField(model_instance_field_type, field).name - })] + return DjangoModelField(model_instance_field_type, field).name if field_tree_instance.name.string_name == 'DateTimeField': model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('datetime') - return [DictFilter({ - field.string_name: DjangoModelField(model_instance_field_type, field).name - })] + return DjangoModelField(model_instance_field_type, field).name +def new_django_dict_filter(cls): + filter_ = ParserTreeFilter(parent_context=cls.as_context()) + return [DictFilter({ + f.string_name: infer_django_field(cls, f) for f in filter_.values() + })] + + def get_metaclass_filters(func): def wrapper(cls, metaclasses): for metaclass in metaclasses: From a6dfc130c9149bb4711a8d96f6f4e46293103d4c Mon Sep 17 00:00:00 2001 From: ANtlord Date: Thu, 16 Jan 2020 15:40:45 +0200 Subject: [PATCH 05/15] Foreign key is handled. --- jedi/plugins/stdlib.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index 5074e476..3193d84f 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -21,7 +21,7 @@ from jedi.inference.arguments import \ from jedi.inference import analysis from jedi.inference import compiled from jedi.inference.value.instance import \ - AnonymousMethodExecutionContext, MethodExecutionContext + AnonymousMethodExecutionContext, MethodExecutionContext, TreeInstance from jedi.inference.base_value import ContextualizedNode, \ NO_VALUES, ValueSet, ValueWrapper, LazyValueWrapper from jedi.inference.value import ClassValue, ModuleValue @@ -810,8 +810,22 @@ def infer_django_field(cls, field): return DjangoModelField(model_instance_field_type, field).name if field_tree_instance.name.string_name == 'ForeignKey': - # TODO: infer related object class and make a filter for that class - return + if isinstance(field_tree_instance, TreeInstance): + 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: + # TODO: it has only one element in current state. Handle rest of elements. + for value in lazy_values.infer(): + string = value.get_safe_value(default=None) + if value.name.string_name == 'str': + foreign_key_class_name = value._compiled_obj.get_safe_value() + # TODO: it has only one element in current state. Handle rest of elements. + for v in cls.parent_context.py__getattribute__(foreign_key_class_name): + return DjangoModelField(v, field).name + else: + return DjangoModelField(value, field).name + + raise Exception('Should be handled') if field_tree_instance.name.string_name == 'TimeField': model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('time') From c61ca0d27be9dfc832911aa01234a2302e5e187c Mon Sep 17 00:00:00 2001 From: ANtlord Date: Sun, 19 Jan 2020 18:46:28 +0200 Subject: [PATCH 06/15] Infering of django model fields is moved to a dedicated module. --- jedi/plugins/django.py | 95 ++++++++++++++++++++++++++++++++++++++++++ jedi/plugins/stdlib.py | 95 ++---------------------------------------- 2 files changed, 98 insertions(+), 92 deletions(-) create mode 100644 jedi/plugins/django.py diff --git a/jedi/plugins/django.py b/jedi/plugins/django.py new file mode 100644 index 00000000..0090ef06 --- /dev/null +++ b/jedi/plugins/django.py @@ -0,0 +1,95 @@ +""" +Module provides infering of Django model fields. +""" +from jedi.inference.base_value import LazyValueWrapper +from jedi.inference.utils import safe_property +from jedi.inference.filters import ParserTreeFilter, DictFilter +from jedi.inference.names import ValueName +from jedi.inference.value.instance import TreeInstance + + +def new_dict_filter(cls): + filter_ = ParserTreeFilter(parent_context=cls.as_context()) + return [DictFilter({ + f.string_name: _infer_field(cls, f) for f in filter_.values() + })] + + +class DjangoModelField(LazyValueWrapper): + def __init__(self, cls, name): + self.inference_state = cls.inference_state + self._cls = cls # Corresponds to super().__self__ + self._name = name + self.tree_node = self._name.tree_name + + @safe_property + def name(self): + return ValueName(self, self._name.tree_name) + + def _get_wrapped_value(self): + obj, = self._cls.execute_with_values() + return obj + + +def _infer_field(cls, field): + field_tree_instance, = field.infer() + + if field_tree_instance.name.string_name in ('CharField', 'TextField', 'EmailField'): + model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('str') + return DjangoModelField(model_instance_field_type, field).name + + integer_field_classes = ('IntegerField', 'BigIntegerField', 'PositiveIntegerField', 'SmallIntegerField') + if field_tree_instance.name.string_name in integer_field_classes: + model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('int') + return DjangoModelField(model_instance_field_type, field).name + + if field_tree_instance.name.string_name == 'FloatField': + model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('float') + return DjangoModelField(model_instance_field_type, field).name + + if field_tree_instance.name.string_name == 'BinaryField': + model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('bytes') + return DjangoModelField(model_instance_field_type, field).name + + if field_tree_instance.name.string_name == 'BooleanField': + model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('bool') + return DjangoModelField(model_instance_field_type, field).name + + if field_tree_instance.name.string_name == 'DecimalField': + model_instance_field_type, = cls.inference_state.import_module(('decimal',)).py__getattribute__('Decimal') + return DjangoModelField(model_instance_field_type, field).name + + if field_tree_instance.name.string_name == 'ForeignKey': + if isinstance(field_tree_instance, TreeInstance): + 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: + # TODO: it has only one element in current state. Handle rest of elements. + for value in lazy_values.infer(): + string = value.get_safe_value(default=None) + if value.name.string_name == 'str': + foreign_key_class_name = value._compiled_obj.get_safe_value() + # TODO: it has only one element in current state. Handle rest of elements. + for v in cls.parent_context.py__getattribute__(foreign_key_class_name): + return DjangoModelField(v, field).name + else: + return DjangoModelField(value, field).name + + raise Exception('Should be handled') + + if field_tree_instance.name.string_name == 'TimeField': + model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('time') + return DjangoModelField(model_instance_field_type, field).name + + if field_tree_instance.name.string_name == 'DurationField': + model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('timedelta') + return DjangoModelField(model_instance_field_type, field).name + + if field_tree_instance.name.string_name == 'DateField': + model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('date') + return DjangoModelField(model_instance_field_type, field).name + + if field_tree_instance.name.string_name == 'DateTimeField': + model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('datetime') + return DjangoModelField(model_instance_field_type, field).name + diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index 3193d84f..a97cc4e9 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -35,6 +35,8 @@ from jedi.inference.filters import AttributeOverwrite, publish_method, \ ParserTreeFilter, DictFilter from jedi.inference.signature import AbstractSignature, SignatureWrapper +from . import django + # Copied from Python 3.6's stdlib. _NAMEDTUPLE_CLASS_TEMPLATE = """\ @@ -781,77 +783,6 @@ _implemented = { } -def infer_django_field(cls, field): - field_tree_instance, = field.infer() - - if field_tree_instance.name.string_name in ('CharField', 'TextField', 'EmailField'): - model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('str') - return DjangoModelField(model_instance_field_type, field).name - - integer_field_classes = ('IntegerField', 'BigIntegerField', 'PositiveIntegerField', 'SmallIntegerField') - if field_tree_instance.name.string_name in integer_field_classes: - model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('int') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'FloatField': - model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('float') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'BinaryField': - model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('bytes') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'BooleanField': - model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('bool') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'DecimalField': - model_instance_field_type, = cls.inference_state.import_module(('decimal',)).py__getattribute__('Decimal') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'ForeignKey': - if isinstance(field_tree_instance, TreeInstance): - 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: - # TODO: it has only one element in current state. Handle rest of elements. - for value in lazy_values.infer(): - string = value.get_safe_value(default=None) - if value.name.string_name == 'str': - foreign_key_class_name = value._compiled_obj.get_safe_value() - # TODO: it has only one element in current state. Handle rest of elements. - for v in cls.parent_context.py__getattribute__(foreign_key_class_name): - return DjangoModelField(v, field).name - else: - return DjangoModelField(value, field).name - - raise Exception('Should be handled') - - if field_tree_instance.name.string_name == 'TimeField': - model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('time') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'DurationField': - model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('timedelta') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'DateField': - model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('date') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'DateTimeField': - model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('datetime') - return DjangoModelField(model_instance_field_type, field).name - - - -def new_django_dict_filter(cls): - filter_ = ParserTreeFilter(parent_context=cls.as_context()) - return [DictFilter({ - f.string_name: infer_django_field(cls, f) for f in filter_.values() - })] - - def get_metaclass_filters(func): def wrapper(cls, metaclasses): for metaclass in metaclasses: @@ -864,7 +795,7 @@ def get_metaclass_filters(func): if metaclass.py__name__() == 'ModelBase' \ and metaclass.get_root_context().py__name__() == 'django.db.models.base': - django_dict_filter = new_django_dict_filter(cls) + django_dict_filter = django.new_dict_filter(cls) if django_dict_filter is not None: return django_dict_filter @@ -872,26 +803,6 @@ def get_metaclass_filters(func): return wrapper -class DjangoModelField(LazyValueWrapper): - def __init__(self, cls, name): - self.inference_state = cls.inference_state - self._cls = cls # Corresponds to super().__self__ - self._name = name - self.tree_node = self._name.tree_name - - @safe_property - def name(self): - return ValueName(self, self._name.tree_name) - - def _get_wrapped_value(self): - obj, = self._cls.execute_with_values() - return obj - - def get_filters(self, origin_scope=None): - for f in self._get_wrapped_value().get_filters(): - yield f - - class EnumInstance(LazyValueWrapper): def __init__(self, cls, name): self.inference_state = cls.inference_state From 7287d67e7a45bd1bb2eedd2bf72dc4107c20c478 Mon Sep 17 00:00:00 2001 From: ANtlord Date: Tue, 21 Jan 2020 21:12:38 +0200 Subject: [PATCH 07/15] Functions infers type of Django model field is refactored. --- jedi/plugins/django.py | 69 +++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/jedi/plugins/django.py b/jedi/plugins/django.py index 0090ef06..e5f839c6 100644 --- a/jedi/plugins/django.py +++ b/jedi/plugins/django.py @@ -30,34 +30,38 @@ class DjangoModelField(LazyValueWrapper): obj, = self._cls.execute_with_values() return obj +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_field(cls, field): field_tree_instance, = field.infer() - if field_tree_instance.name.string_name in ('CharField', 'TextField', 'EmailField'): - model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('str') - return DjangoModelField(model_instance_field_type, field).name - - integer_field_classes = ('IntegerField', 'BigIntegerField', 'PositiveIntegerField', 'SmallIntegerField') - if field_tree_instance.name.string_name in integer_field_classes: - model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('int') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'FloatField': - model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('float') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'BinaryField': - model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('bytes') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'BooleanField': - model_instance_field_type, = cls.inference_state.builtins_module.py__getattribute__('bool') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'DecimalField': - model_instance_field_type, = cls.inference_state.import_module(('decimal',)).py__getattribute__('Decimal') - return DjangoModelField(model_instance_field_type, field).name + try: + module_name, attribute_name = mapping[field_tree_instance.name.string_name] + except KeyError: + pass + else: + if module_name is None: + module = cls.inference_state.builtins_module + else: + module = cls.inference_state.import_module((module_name,)) + attribute, = module.py__getattribute__(attribute_name) + return DjangoModelField(attribute, field).name if field_tree_instance.name.string_name == 'ForeignKey': if isinstance(field_tree_instance, TreeInstance): @@ -76,20 +80,3 @@ def _infer_field(cls, field): return DjangoModelField(value, field).name raise Exception('Should be handled') - - if field_tree_instance.name.string_name == 'TimeField': - model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('time') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'DurationField': - model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('timedelta') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'DateField': - model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('date') - return DjangoModelField(model_instance_field_type, field).name - - if field_tree_instance.name.string_name == 'DateTimeField': - model_instance_field_type, = cls.inference_state.import_module(('datetime',)).py__getattribute__('datetime') - return DjangoModelField(model_instance_field_type, field).name - From 2a86f7d82fc9af349e28c2e6b51b5d831d832c41 Mon Sep 17 00:00:00 2001 From: ANtlord Date: Tue, 21 Jan 2020 21:21:43 +0200 Subject: [PATCH 08/15] Django-plugin related code is removed from stdlib-plugin. --- jedi/plugins/django.py | 13 +++++++++++++ jedi/plugins/registry.py | 3 ++- jedi/plugins/stdlib.py | 9 --------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/jedi/plugins/django.py b/jedi/plugins/django.py index e5f839c6..cacb9e75 100644 --- a/jedi/plugins/django.py +++ b/jedi/plugins/django.py @@ -80,3 +80,16 @@ def _infer_field(cls, field): return DjangoModelField(value, field).name raise Exception('Should be handled') + + +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': + django_dict_filter = new_dict_filter(cls) + if django_dict_filter is not None: + return django_dict_filter + + return func(cls, metaclasses) + return wrapper diff --git a/jedi/plugins/registry.py b/jedi/plugins/registry.py index 23913244..66d653ce 100644 --- a/jedi/plugins/registry.py +++ b/jedi/plugins/registry.py @@ -4,7 +4,8 @@ This is not a plugin, this is just the place were plugins are registered. from jedi.plugins import stdlib from jedi.plugins import flask +from jedi.plugins import django from jedi.plugins import plugin_manager -plugin_manager.register(stdlib, flask) +plugin_manager.register(stdlib, flask, django) diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index a97cc4e9..88684efd 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -35,8 +35,6 @@ from jedi.inference.filters import AttributeOverwrite, publish_method, \ ParserTreeFilter, DictFilter from jedi.inference.signature import AbstractSignature, SignatureWrapper -from . import django - # Copied from Python 3.6's stdlib. _NAMEDTUPLE_CLASS_TEMPLATE = """\ @@ -792,13 +790,6 @@ def get_metaclass_filters(func): return [DictFilter({ name.string_name: EnumInstance(cls, name).name for name in filter_.values() })] - - if metaclass.py__name__() == 'ModelBase' \ - and metaclass.get_root_context().py__name__() == 'django.db.models.base': - django_dict_filter = django.new_dict_filter(cls) - if django_dict_filter is not None: - return django_dict_filter - return func(cls, metaclasses) return wrapper From ddcd48edd8e2fa5dfd09969426c727a38b872ebd Mon Sep 17 00:00:00 2001 From: ANtlord Date: Wed, 22 Jan 2020 20:55:25 +0200 Subject: [PATCH 09/15] Typeshed submodule checked out to d386452 --- jedi/third_party/typeshed | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/third_party/typeshed b/jedi/third_party/typeshed index 3319cadf..d3864524 160000 --- a/jedi/third_party/typeshed +++ b/jedi/third_party/typeshed @@ -1 +1 @@ -Subproject commit 3319cadf85012328f8a12b15da4eecc8de0cf305 +Subproject commit d38645247816f862cafeed21a8f4466d306aacf3 From 8440e1719fe491e40a7a1940dceb6ef2b83754df Mon Sep 17 00:00:00 2001 From: ANtlord Date: Wed, 22 Jan 2020 20:57:17 +0200 Subject: [PATCH 10/15] Unuseful changes are rolled back. --- jedi/plugins/registry.py | 2 +- jedi/plugins/stdlib.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jedi/plugins/registry.py b/jedi/plugins/registry.py index 06e4aede..c1a0b749 100644 --- a/jedi/plugins/registry.py +++ b/jedi/plugins/registry.py @@ -4,9 +4,9 @@ This is not a plugin, this is just the place were plugins are registered. from jedi.plugins import stdlib from jedi.plugins import flask +from jedi.plugins import pytest from jedi.plugins import django from jedi.plugins import plugin_manager -from jedi.plugins import pytest plugin_manager.register(stdlib, flask, pytest, django) diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index c32535e1..448f2b50 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -21,7 +21,7 @@ from jedi.inference.arguments import \ from jedi.inference import analysis from jedi.inference import compiled from jedi.inference.value.instance import \ - AnonymousMethodExecutionContext, MethodExecutionContext, TreeInstance + AnonymousMethodExecutionContext, MethodExecutionContext from jedi.inference.base_value import ContextualizedNode, \ NO_VALUES, ValueSet, ValueWrapper, LazyValueWrapper from jedi.inference.value import ClassValue, ModuleValue From d48575c8c5b1e52d0e4703ad7838b43d236cffc5 Mon Sep 17 00:00:00 2001 From: ANtlord Date: Sat, 18 Apr 2020 16:13:48 +0300 Subject: [PATCH 11/15] Simple tests of Django plugin are added. --- jedi/plugins/django.py | 45 +++++++++++---------- test/test_inference/test_django.py | 64 ++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 21 deletions(-) create mode 100644 test/test_inference/test_django.py diff --git a/jedi/plugins/django.py b/jedi/plugins/django.py index cacb9e75..03a14c5d 100644 --- a/jedi/plugins/django.py +++ b/jedi/plugins/django.py @@ -1,5 +1,5 @@ """ -Module provides infering of Django model fields. +Module is used to infer Django model fields. """ from jedi.inference.base_value import LazyValueWrapper from jedi.inference.utils import safe_property @@ -10,9 +10,8 @@ from jedi.inference.value.instance import TreeInstance def new_dict_filter(cls): filter_ = ParserTreeFilter(parent_context=cls.as_context()) - return [DictFilter({ - f.string_name: _infer_field(cls, f) for f in filter_.values() - })] + res = {f.string_name: _infer_field(cls, f) for f in filter_.values()} + return [DictFilter({x: y for x, y in res.items() if y is not None})] class DjangoModelField(LazyValueWrapper): @@ -30,6 +29,7 @@ class DjangoModelField(LazyValueWrapper): obj, = self._cls.execute_with_values() return obj + mapping = { 'IntegerField': (None, 'int'), 'BigIntegerField': (None, 'int'), @@ -48,38 +48,41 @@ mapping = { 'DateTimeField': ('datetime', 'datetime'), } + +def _infer_scalar_field(cls, field, field_tree_instance): + if field_tree_instance.name.string_name not in mapping: + return None + + module_name, attribute_name = mapping[field_tree_instance.name.string_name] + if module_name is None: + module = cls.inference_state.builtins_module + else: + module = cls.inference_state.import_module((module_name,)) + + attribute, = module.py__getattribute__(attribute_name) + return DjangoModelField(attribute, field).name + + def _infer_field(cls, field): field_tree_instance, = field.infer() - - try: - module_name, attribute_name = mapping[field_tree_instance.name.string_name] - except KeyError: - pass - else: - if module_name is None: - module = cls.inference_state.builtins_module - else: - module = cls.inference_state.import_module((module_name,)) - attribute, = module.py__getattribute__(attribute_name) - return DjangoModelField(attribute, field).name + scalar_field = _infer_scalar_field(cls, field, field_tree_instance) + if scalar_field: + return scalar_field if field_tree_instance.name.string_name == 'ForeignKey': if isinstance(field_tree_instance, TreeInstance): 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: - # TODO: it has only one element in current state. Handle rest of elements. for value in lazy_values.infer(): - string = value.get_safe_value(default=None) if value.name.string_name == 'str': - foreign_key_class_name = value._compiled_obj.get_safe_value() - # TODO: it has only one element in current state. Handle rest of elements. + foreign_key_class_name = value.get_safe_value() for v in cls.parent_context.py__getattribute__(foreign_key_class_name): return DjangoModelField(v, field).name else: return DjangoModelField(value, field).name - raise Exception('Should be handled') + print('TODO: {}'.format(field)) def get_metaclass_filters(func): diff --git a/test/test_inference/test_django.py b/test/test_inference/test_django.py new file mode 100644 index 00000000..16c9ee52 --- /dev/null +++ b/test/test_inference/test_django.py @@ -0,0 +1,64 @@ +import pytest +import datetime +import decimal + +source_tpl_basic_types = ''' +from django.db import models + +class BusinessModel(models.Model): + {0} = {1} + +p1 = BusinessModel() +p1_field = p1.{0} +p1_field.''' + + +source_tpl_foreign_key = ''' +from django.db import models + +class Category(models.Model): + category_name = models.CharField() + +class BusinessModel(models.Model): + category = models.ForeignKey(Category) + +p1 = BusinessModel() +p1_field = p1.category +p1_field.''' + + +@pytest.mark.parametrize('field_name, field_model_type, expected_fields', [ + ('integer_field', 'models.IntegerField()', dir(int)), + ('big_integer_field', 'models.BigIntegerField()', dir(int)), + ('positive_integer_field', 'models.PositiveIntegerField()', dir(int)), + ('small_integer_field', 'models.SmallIntegerField()', dir(int)), + ('char_field', 'models.CharField()', dir(str)), + ('text_field', 'models.TextField()', dir(str)), + ('email_field', 'models.EmailField()', dir(str)), + ('float_field', 'models.FloatField()', dir(float)), + ('binary_field', 'models.BinaryField()', dir(bytes)), + ('boolean_field', 'models.BooleanField()', dir(bool)), + ('decimal_field', 'models.DecimalField()', dir(decimal.Decimal)), + ('time_field', 'models.TimeField()', dir(datetime.time)), + ('duration_field', 'models.DurationField()', dir(datetime.timedelta)), + ('date_field', 'models.DateField()', dir(datetime.date)), + ('date_time_field', 'models.DateTimeField()', dir(datetime.datetime)), +]) +def test_basic_types( + field_name, + field_model_type, + expected_fields, + Script, +): + source = source_tpl_basic_types.format(field_name, field_model_type) + result = Script(source).complete() + result = {x.name for x in result} + expected_fields_public = [x for x in expected_fields if x[0] != '_'] + for field in expected_fields_public: + assert field in result + + +def test_foreign_key(Script): + result = Script(source_tpl_foreign_key).complete() + result = {x.name for x in result} + assert 'category_name' in result From 09950233e77c2ec02757bab9997f63c52cdc7dce Mon Sep 17 00:00:00 2001 From: ANtlord Date: Sat, 18 Apr 2020 18:36:04 +0300 Subject: [PATCH 12/15] Django is designated in test dependencies. --- jedi/plugins/django.py | 3 +++ setup.py | 1 + 2 files changed, 4 insertions(+) diff --git a/jedi/plugins/django.py b/jedi/plugins/django.py index 03a14c5d..8ebd73b4 100644 --- a/jedi/plugins/django.py +++ b/jedi/plugins/django.py @@ -1,5 +1,8 @@ """ Module is used to infer Django model fields. +Bugs: + - Can't infer User model. + - Can't infer ManyToManyField. """ from jedi.inference.base_value import LazyValueWrapper from jedi.inference.utils import safe_property diff --git a/setup.py b/setup.py index b73fea79..54418153 100755 --- a/setup.py +++ b/setup.py @@ -43,6 +43,7 @@ setup(name='jedi', 'docopt', # coloroma for colored debug output 'colorama', + 'Django', ], 'qa': [ 'flake8==3.7.9', From 1d3082249f9f839ee0db43c962c5cdd000f72c9f Mon Sep 17 00:00:00 2001 From: ANtlord Date: Sat, 18 Apr 2020 18:51:12 +0300 Subject: [PATCH 13/15] Debug information corrections. --- jedi/plugins/django.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jedi/plugins/django.py b/jedi/plugins/django.py index 8ebd73b4..0b518c00 100644 --- a/jedi/plugins/django.py +++ b/jedi/plugins/django.py @@ -85,7 +85,9 @@ def _infer_field(cls, field): else: return DjangoModelField(value, field).name - print('TODO: {}'.format(field)) + print('django plugin: fail to infer `{}` from class `{}`'.format( + field.string_name, cls.name.string_name, + )) def get_metaclass_filters(func): From df76b2462ede414fdb6d9acfb79b4f46b2e3563d Mon Sep 17 00:00:00 2001 From: ANtlord Date: Mon, 20 Apr 2020 10:31:03 +0300 Subject: [PATCH 14/15] Review corrections. --- test/completion/django.py | 65 ++++++++++++++++++++++++++++++ test/test_inference/test_django.py | 64 ----------------------------- 2 files changed, 65 insertions(+), 64 deletions(-) create mode 100644 test/completion/django.py delete mode 100644 test/test_inference/test_django.py diff --git a/test/completion/django.py b/test/completion/django.py new file mode 100644 index 00000000..7840491c --- /dev/null +++ b/test/completion/django.py @@ -0,0 +1,65 @@ +import pytest +import datetime +import decimal + +from django.db import models + + +class Category(models.Model): + category_name = models.CharField() + + +class BusinessModel(models.Model): + category_fk = models.ForeignKey(Category) + 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() + + +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() +model_instance.category_fk +#? str() +model_instance.category_fk.category_name diff --git a/test/test_inference/test_django.py b/test/test_inference/test_django.py deleted file mode 100644 index 16c9ee52..00000000 --- a/test/test_inference/test_django.py +++ /dev/null @@ -1,64 +0,0 @@ -import pytest -import datetime -import decimal - -source_tpl_basic_types = ''' -from django.db import models - -class BusinessModel(models.Model): - {0} = {1} - -p1 = BusinessModel() -p1_field = p1.{0} -p1_field.''' - - -source_tpl_foreign_key = ''' -from django.db import models - -class Category(models.Model): - category_name = models.CharField() - -class BusinessModel(models.Model): - category = models.ForeignKey(Category) - -p1 = BusinessModel() -p1_field = p1.category -p1_field.''' - - -@pytest.mark.parametrize('field_name, field_model_type, expected_fields', [ - ('integer_field', 'models.IntegerField()', dir(int)), - ('big_integer_field', 'models.BigIntegerField()', dir(int)), - ('positive_integer_field', 'models.PositiveIntegerField()', dir(int)), - ('small_integer_field', 'models.SmallIntegerField()', dir(int)), - ('char_field', 'models.CharField()', dir(str)), - ('text_field', 'models.TextField()', dir(str)), - ('email_field', 'models.EmailField()', dir(str)), - ('float_field', 'models.FloatField()', dir(float)), - ('binary_field', 'models.BinaryField()', dir(bytes)), - ('boolean_field', 'models.BooleanField()', dir(bool)), - ('decimal_field', 'models.DecimalField()', dir(decimal.Decimal)), - ('time_field', 'models.TimeField()', dir(datetime.time)), - ('duration_field', 'models.DurationField()', dir(datetime.timedelta)), - ('date_field', 'models.DateField()', dir(datetime.date)), - ('date_time_field', 'models.DateTimeField()', dir(datetime.datetime)), -]) -def test_basic_types( - field_name, - field_model_type, - expected_fields, - Script, -): - source = source_tpl_basic_types.format(field_name, field_model_type) - result = Script(source).complete() - result = {x.name for x in result} - expected_fields_public = [x for x in expected_fields if x[0] != '_'] - for field in expected_fields_public: - assert field in result - - -def test_foreign_key(Script): - result = Script(source_tpl_foreign_key).complete() - result = {x.name for x in result} - assert 'category_name' in result From b5c1c6d414cc2746493e06928872907b0b22a7b4 Mon Sep 17 00:00:00 2001 From: ANtlord Date: Tue, 21 Apr 2020 10:56:22 +0300 Subject: [PATCH 15/15] Django plugin test of ManyToManyField is added and marked for future implementation. --- test/completion/django.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/completion/django.py b/test/completion/django.py index 7840491c..6a50626f 100644 --- a/test/completion/django.py +++ b/test/completion/django.py @@ -5,6 +5,10 @@ import decimal from django.db import models +class Tag(models.Model): + tag_name = models.CharField() + + class Category(models.Model): category_name = models.CharField() @@ -26,6 +30,7 @@ class BusinessModel(models.Model): duration_field = models.DurationField() date_field = models.DateField() date_time_field = models.DateTimeField() + tags_m2m = models.ManyToManyField(Tag) model_instance = BusinessModel() @@ -63,3 +68,6 @@ model_instance.date_time_field model_instance.category_fk #? str() model_instance.category_fk.category_name +# TODO: implement many to many field support +model_instance.tags_m2m +