]>
Commit | Line | Data |
---|---|---|
1 | """ | |
2 | Module is used to infer Django model fields. | |
3 | """ | |
4 | from inspect import Parameter | |
5 | ||
6 | from jedi import debug | |
7 | from jedi.inference.cache import inference_state_function_cache | |
8 | from jedi.inference.base_value import ValueSet, iterator_to_value_set, ValueWrapper | |
9 | from jedi.inference.filters import DictFilter, AttributeOverwrite | |
10 | from jedi.inference.names import NameWrapper, BaseTreeParamName | |
11 | from jedi.inference.compiled.value import EmptyCompiledName | |
12 | from jedi.inference.value.instance import TreeInstance | |
13 | from jedi.inference.value.klass import ClassMixin | |
14 | from jedi.inference.gradual.base import GenericClass | |
15 | from jedi.inference.gradual.generics import TupleGenericManager | |
16 | from jedi.inference.signature import AbstractSignature | |
17 | ||
18 | ||
19 | mapping = { | |
20 | 'IntegerField': (None, 'int'), | |
21 | 'BigIntegerField': (None, 'int'), | |
22 | 'PositiveIntegerField': (None, 'int'), | |
23 | 'SmallIntegerField': (None, 'int'), | |
24 | 'CharField': (None, 'str'), | |
25 | 'TextField': (None, 'str'), | |
26 | 'EmailField': (None, 'str'), | |
27 | 'GenericIPAddressField': (None, 'str'), | |
28 | 'URLField': (None, 'str'), | |
29 | 'FloatField': (None, 'float'), | |
30 | 'BinaryField': (None, 'bytes'), | |
31 | 'BooleanField': (None, 'bool'), | |
32 | 'DecimalField': ('decimal', 'Decimal'), | |
33 | 'TimeField': ('datetime', 'time'), | |
34 | 'DurationField': ('datetime', 'timedelta'), | |
35 | 'DateField': ('datetime', 'date'), | |
36 | 'DateTimeField': ('datetime', 'datetime'), | |
37 | 'UUIDField': ('uuid', 'UUID'), | |
38 | } | |
39 | ||
40 | _FILTER_LIKE_METHODS = ('create', 'filter', 'exclude', 'update', 'get', | |
41 | 'get_or_create', 'update_or_create') | |
42 | ||
43 | ||
44 | @inference_state_function_cache() | |
45 | def _get_deferred_attributes(inference_state): | |
46 | return inference_state.import_module( | |
47 | ('django', 'db', 'models', 'query_utils') | |
48 | ).py__getattribute__('DeferredAttribute').execute_annotation() | |
49 | ||
50 | ||
51 | def _infer_scalar_field(inference_state, field_name, field_tree_instance, is_instance): | |
52 | try: | |
53 | module_name, attribute_name = mapping[field_tree_instance.py__name__()] | |
54 | except KeyError: | |
55 | return None | |
56 | ||
57 | if not is_instance: | |
58 | return _get_deferred_attributes(inference_state) | |
59 | ||
60 | if module_name is None: | |
61 | module = inference_state.builtins_module | |
62 | else: | |
63 | module = inference_state.import_module((module_name,)) | |
64 | ||
65 | for attribute in module.py__getattribute__(attribute_name): | |
66 | return attribute.execute_with_values() | |
67 | ||
68 | ||
69 | @iterator_to_value_set | |
70 | def _get_foreign_key_values(cls, field_tree_instance): | |
71 | if isinstance(field_tree_instance, TreeInstance): | |
72 | # TODO private access.. | |
73 | argument_iterator = field_tree_instance._arguments.unpack() | |
74 | key, lazy_values = next(argument_iterator, (None, None)) | |
75 | if key is None and lazy_values is not None: | |
76 | for value in lazy_values.infer(): | |
77 | if value.py__name__() == 'str': | |
78 | foreign_key_class_name = value.get_safe_value() | |
79 | module = cls.get_root_context() | |
80 | for v in module.py__getattribute__(foreign_key_class_name): | |
81 | if v.is_class(): | |
82 | yield v | |
83 | elif value.is_class(): | |
84 | yield value | |
85 | ||
86 | ||
87 | def _infer_field(cls, field_name, is_instance): | |
88 | inference_state = cls.inference_state | |
89 | result = field_name.infer() | |
90 | for field_tree_instance in result: | |
91 | scalar_field = _infer_scalar_field( | |
92 | inference_state, field_name, field_tree_instance, is_instance) | |
93 | if scalar_field is not None: | |
94 | return scalar_field | |
95 | ||
96 | name = field_tree_instance.py__name__() | |
97 | is_many_to_many = name == 'ManyToManyField' | |
98 | if name in ('ForeignKey', 'OneToOneField') or is_many_to_many: | |
99 | if not is_instance: | |
100 | return _get_deferred_attributes(inference_state) | |
101 | ||
102 | values = _get_foreign_key_values(cls, field_tree_instance) | |
103 | if is_many_to_many: | |
104 | return ValueSet(filter(None, [ | |
105 | _create_manager_for(v, 'RelatedManager') for v in values | |
106 | ])) | |
107 | else: | |
108 | return values.execute_with_values() | |
109 | ||
110 | debug.dbg('django plugin: fail to infer `%s` from class `%s`', | |
111 | field_name.string_name, cls.py__name__()) | |
112 | return result | |
113 | ||
114 | ||
115 | class DjangoModelName(NameWrapper): | |
116 | def __init__(self, cls, name, is_instance): | |
117 | super().__init__(name) | |
118 | self._cls = cls | |
119 | self._is_instance = is_instance | |
120 | ||
121 | def infer(self): | |
122 | return _infer_field(self._cls, self._wrapped_name, self._is_instance) | |
123 | ||
124 | ||
125 | def _create_manager_for(cls, manager_cls='BaseManager'): | |
126 | managers = cls.inference_state.import_module( | |
127 | ('django', 'db', 'models', 'manager') | |
128 | ).py__getattribute__(manager_cls) | |
129 | for m in managers: | |
130 | if m.is_class_mixin(): | |
131 | generics_manager = TupleGenericManager((ValueSet([cls]),)) | |
132 | for c in GenericClass(m, generics_manager).execute_annotation(): | |
133 | return c | |
134 | return None | |
135 | ||
136 | ||
137 | def _new_dict_filter(cls, is_instance): | |
138 | filters = list(cls.get_filters( | |
139 | is_instance=is_instance, | |
140 | include_metaclasses=False, | |
141 | include_type_when_class=False) | |
142 | ) | |
143 | dct = { | |
144 | name.string_name: DjangoModelName(cls, name, is_instance) | |
145 | for filter_ in reversed(filters) | |
146 | for name in filter_.values() | |
147 | } | |
148 | if is_instance: | |
149 | # Replace the objects with a name that amounts to nothing when accessed | |
150 | # in an instance. This is not perfect and still completes "objects" in | |
151 | # that case, but it at least not inferes stuff like `.objects.filter`. | |
152 | # It would be nicer to do that in a better way, so that it also doesn't | |
153 | # show up in completions, but it's probably just not worth doing that | |
154 | # for the extra amount of work. | |
155 | dct['objects'] = EmptyCompiledName(cls.inference_state, 'objects') | |
156 | ||
157 | return DictFilter(dct) | |
158 | ||
159 | ||
160 | def is_django_model_base(value): | |
161 | return value.py__name__() == 'ModelBase' \ | |
162 | and value.get_root_context().py__name__() == 'django.db.models.base' | |
163 | ||
164 | ||
165 | def get_metaclass_filters(func): | |
166 | def wrapper(cls, metaclasses, is_instance): | |
167 | for metaclass in metaclasses: | |
168 | if is_django_model_base(metaclass): | |
169 | return [_new_dict_filter(cls, is_instance)] | |
170 | ||
171 | return func(cls, metaclasses, is_instance) | |
172 | return wrapper | |
173 | ||
174 | ||
175 | def tree_name_to_values(func): | |
176 | def wrapper(inference_state, context, tree_name): | |
177 | result = func(inference_state, context, tree_name) | |
178 | if tree_name.value in _FILTER_LIKE_METHODS: | |
179 | # Here we try to overwrite stuff like User.objects.filter. We need | |
180 | # this to make sure that keyword param completion works on these | |
181 | # kind of methods. | |
182 | for v in result: | |
183 | if v.get_qualified_names() == ('_BaseQuerySet', tree_name.value) \ | |
184 | and v.parent_context.is_module() \ | |
185 | and v.parent_context.py__name__() == 'django.db.models.query': | |
186 | qs = context.get_value() | |
187 | generics = qs.get_generics() | |
188 | if len(generics) >= 1: | |
189 | return ValueSet(QuerySetMethodWrapper(v, model) | |
190 | for model in generics[0]) | |
191 | ||
192 | elif tree_name.value == 'BaseManager' and context.is_module() \ | |
193 | and context.py__name__() == 'django.db.models.manager': | |
194 | return ValueSet(ManagerWrapper(r) for r in result) | |
195 | ||
196 | elif tree_name.value == 'Field' and context.is_module() \ | |
197 | and context.py__name__() == 'django.db.models.fields': | |
198 | return ValueSet(FieldWrapper(r) for r in result) | |
199 | return result | |
200 | return wrapper | |
201 | ||
202 | ||
203 | def _find_fields(cls): | |
204 | for name in _new_dict_filter(cls, is_instance=False).values(): | |
205 | for value in name.infer(): | |
206 | if value.name.get_qualified_names(include_module_names=True) \ | |
207 | == ('django', 'db', 'models', 'query_utils', 'DeferredAttribute'): | |
208 | yield name | |
209 | ||
210 | ||
211 | def _get_signatures(cls): | |
212 | return [DjangoModelSignature(cls, field_names=list(_find_fields(cls)))] | |
213 | ||
214 | ||
215 | def get_metaclass_signatures(func): | |
216 | def wrapper(cls, metaclasses): | |
217 | for metaclass in metaclasses: | |
218 | if is_django_model_base(metaclass): | |
219 | return _get_signatures(cls) | |
220 | return func(cls, metaclass) | |
221 | return wrapper | |
222 | ||
223 | ||
224 | class ManagerWrapper(ValueWrapper): | |
225 | def py__getitem__(self, index_value_set, contextualized_node): | |
226 | return ValueSet( | |
227 | GenericManagerWrapper(generic) | |
228 | for generic in self._wrapped_value.py__getitem__( | |
229 | index_value_set, contextualized_node) | |
230 | ) | |
231 | ||
232 | ||
233 | class GenericManagerWrapper(AttributeOverwrite, ClassMixin): | |
234 | def py__get__on_class(self, calling_instance, instance, class_value): | |
235 | return calling_instance.class_value.with_generics( | |
236 | (ValueSet({class_value}),) | |
237 | ).py__call__(calling_instance._arguments) | |
238 | ||
239 | def with_generics(self, generics_tuple): | |
240 | return self._wrapped_value.with_generics(generics_tuple) | |
241 | ||
242 | ||
243 | class FieldWrapper(ValueWrapper): | |
244 | def py__getitem__(self, index_value_set, contextualized_node): | |
245 | return ValueSet( | |
246 | GenericFieldWrapper(generic) | |
247 | for generic in self._wrapped_value.py__getitem__( | |
248 | index_value_set, contextualized_node) | |
249 | ) | |
250 | ||
251 | ||
252 | class GenericFieldWrapper(AttributeOverwrite, ClassMixin): | |
253 | def py__get__on_class(self, calling_instance, instance, class_value): | |
254 | # This is mostly an optimization to avoid Jedi aborting inference, | |
255 | # because of too many function executions of Field.__get__. | |
256 | return ValueSet({calling_instance}) | |
257 | ||
258 | ||
259 | class DjangoModelSignature(AbstractSignature): | |
260 | def __init__(self, value, field_names): | |
261 | super().__init__(value) | |
262 | self._field_names = field_names | |
263 | ||
264 | def get_param_names(self, resolve_stars=False): | |
265 | return [DjangoParamName(name) for name in self._field_names] | |
266 | ||
267 | ||
268 | class DjangoParamName(BaseTreeParamName): | |
269 | def __init__(self, field_name): | |
270 | super().__init__(field_name.parent_context, field_name.tree_name) | |
271 | self._field_name = field_name | |
272 | ||
273 | def get_kind(self): | |
274 | return Parameter.KEYWORD_ONLY | |
275 | ||
276 | def infer(self): | |
277 | return self._field_name.infer() | |
278 | ||
279 | ||
280 | class QuerySetMethodWrapper(ValueWrapper): | |
281 | def __init__(self, method, model_cls): | |
282 | super().__init__(method) | |
283 | self._model_cls = model_cls | |
284 | ||
285 | def py__get__(self, instance, class_value): | |
286 | return ValueSet({QuerySetBoundMethodWrapper(v, self._model_cls) | |
287 | for v in self._wrapped_value.py__get__(instance, class_value)}) | |
288 | ||
289 | ||
290 | class QuerySetBoundMethodWrapper(ValueWrapper): | |
291 | def __init__(self, method, model_cls): | |
292 | super().__init__(method) | |
293 | self._model_cls = model_cls | |
294 | ||
295 | def get_signatures(self): | |
296 | return _get_signatures(self._model_cls) |