Merge branch 'django'

This commit is contained in:
Dave Halter
2020-06-14 22:24:31 +02:00
11 changed files with 390 additions and 106 deletions

2
.gitmodules vendored
View File

@@ -3,4 +3,4 @@
url = https://github.com/davidhalter/typeshed.git
[submodule "jedi/third_party/django-stubs"]
path = jedi/third_party/django-stubs
url = https://github.com/typeddjango/django-stubs
url = https://github.com/davidhalter/django-stubs

View File

@@ -244,6 +244,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

View File

@@ -255,7 +255,7 @@ class _BuiltinMappedMethod(ValueWrapper):
def py__call__(self, arguments):
# TODO add TypeError if params are given/or not correct.
return self._method(self._value)
return self._method(self._value, arguments)
class SpecialMethodFilter(DictFilter):
@@ -330,7 +330,7 @@ class _AttributeOverwriteMixin(object):
def get_filters(self, *args, **kwargs):
yield SpecialMethodFilter(self, self.overwritten_methods, self._wrapped_value)
for filter in self._wrapped_value.get_filters():
for filter in self._wrapped_value.get_filters(*args, **kwargs):
yield filter

View File

@@ -156,7 +156,7 @@ class DefineGenericBaseClass(LazyValueWrapper):
)
class GenericClass(ClassMixin, DefineGenericBaseClass):
class GenericClass(DefineGenericBaseClass, ClassMixin):
"""
A class that is defined with generics, might be something simple like:
@@ -205,6 +205,9 @@ class GenericClass(ClassMixin, DefineGenericBaseClass):
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
@@ -292,6 +295,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):

View File

@@ -203,7 +203,7 @@ class _TypingClassMixin(ClassMixin):
return ValueName(self, self._tree_name)
class TypingClassWithGenerics(_TypingClassMixin, ProxyWithGenerics):
class TypingClassWithGenerics(ProxyWithGenerics, _TypingClassMixin):
def infer_type_vars(self, value_set):
type_var_dict = {}
annotation_generics = self.get_generics()
@@ -240,7 +240,7 @@ class TypingClassWithGenerics(_TypingClassMixin, ProxyWithGenerics):
)
class ProxyTypingClassValue(_TypingClassMixin, ProxyTypingValue):
class ProxyTypingClassValue(ProxyTypingValue, _TypingClassMixin):
index_class = TypingClassWithGenerics

View File

@@ -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:

View File

@@ -58,13 +58,13 @@ class GeneratorBase(LazyAttributeOverwrite, IterableMixin):
return True
@publish_method('__iter__')
def py__iter__(self, contextualized_node=None):
def _iter(self, arguments):
return ValueSet([self])
@publish_method('send')
@publish_method('next', python_version_match=2)
@publish_method('__next__', python_version_match=3)
def py__next__(self):
def py__next__(self, arguments):
return ValueSet.from_sets(lazy_value.infer() for lazy_value in self.py__iter__())
def py__stop_iteration_returns(self):
@@ -290,12 +290,12 @@ class DictComprehension(ComprehensionMixin, Sequence, _DictKeyMixin):
return ValueSet.from_sets(values for keys, values in self._iterate())
@publish_method('values')
def _imitate_values(self):
def _imitate_values(self, arguments):
lazy_value = LazyKnownValues(self._dict_values())
return ValueSet([FakeList(self.inference_state, [lazy_value])])
@publish_method('items')
def _imitate_items(self):
def _imitate_items(self, arguments):
lazy_values = [
LazyKnownValue(
FakeTuple(
@@ -457,12 +457,12 @@ class DictLiteralValue(_DictMixin, SequenceLiteralValue, _DictKeyMixin):
yield LazyKnownValues(types)
@publish_method('values')
def _imitate_values(self):
def _imitate_values(self, arguments):
lazy_value = LazyKnownValues(self._dict_values())
return ValueSet([FakeList(self.inference_state, [lazy_value])])
@publish_method('items')
def _imitate_items(self):
def _imitate_items(self, arguments):
lazy_values = [
LazyKnownValue(FakeTuple(
self.inference_state,
@@ -552,7 +552,7 @@ class FakeDict(_DictMixin, Sequence, _DictKeyMixin):
return lazy_value.infer()
@publish_method('values')
def _values(self):
def _values(self, arguments):
return ValueSet([FakeTuple(
self.inference_state,
[LazyKnownValues(self._dict_values())]

View File

@@ -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
@@ -189,12 +187,13 @@ class ClassMixin(object):
mro.append(cls_new)
yield cls_new
def get_filters(self, origin_scope=None, is_instance=False, include_metaclasses=True):
def get_filters(self, origin_scope=None, is_instance=False,
include_metaclasses=True, include_type_when_class=True):
if include_metaclasses:
metaclasses = self.get_metaclasses()
if metaclasses:
for f in self.get_metaclass_filters(metaclasses):
yield f
for f in self.get_metaclass_filters(metaclasses, is_instance):
yield f # Python 2..
for cls in self.py__mro__():
if cls.is_compiled():
@@ -206,7 +205,7 @@ class ClassMixin(object):
origin_scope=origin_scope,
is_instance=is_instance
)
if not is_instance:
if not is_instance and include_type_when_class:
from jedi.inference.compiled import builtin_from_name
type_ = builtin_from_name(self.inference_state, u'type')
assert isinstance(type_, ClassValue)
@@ -228,6 +227,11 @@ class ClassMixin(object):
# Since calling staticmethod without a function is illegal, the Jedi
# plugin doesn't return anything. Therefore call directly and get what
# we want: An instance of staticmethod.
metaclasses = self.get_metaclasses()
if metaclasses:
sigs = self.get_metaclass_signatures(metaclasses)
if sigs:
return sigs
args = ValuesArguments([])
init_funcs = self.py__call__(args).py__getattribute__('__init__')
return [sig.bind(self) for sig in init_funcs.get_signatures()]
@@ -270,50 +274,6 @@ class ClassMixin(object):
return True
return False
class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase)):
api_type = u'class'
@inference_state_method_cache()
def list_type_vars(self):
found = []
arglist = self.tree_node.get_super_arglist()
if arglist is None:
return []
for stars, node in unpack_arglist(arglist):
if stars:
continue # These are not relevant for this search.
from jedi.inference.gradual.annotation import find_unknown_type_vars
for type_var in find_unknown_type_vars(self.parent_context, node):
if type_var not in found:
# The order matters and it's therefore a list.
found.append(type_var)
return found
def _get_bases_arguments(self):
arglist = self.tree_node.get_super_arglist()
if arglist:
from jedi.inference import arguments
return arguments.TreeArguments(self.inference_state, self.parent_context, arglist)
return None
@inference_state_method_cache(default=())
def py__bases__(self):
args = self._get_bases_arguments()
if args is not None:
lst = [value for key, value in args.unpack() if key is None]
if lst:
return lst
if self.py__name__() == 'object' \
and self.parent_context.is_builtins_module():
return []
return [LazyKnownValues(
self.inference_state.builtins_module.py__getattribute__('object')
)]
def py__getitem__(self, index_value_set, contextualized_node):
from jedi.inference.gradual.base import GenericClass
if not index_value_set:
@@ -360,9 +320,53 @@ class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase
)])
return ValueSet({self})
class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase)):
api_type = u'class'
@inference_state_method_cache()
def list_type_vars(self):
found = []
arglist = self.tree_node.get_super_arglist()
if arglist is None:
return []
for stars, node in unpack_arglist(arglist):
if stars:
continue # These are not relevant for this search.
from jedi.inference.gradual.annotation import find_unknown_type_vars
for type_var in find_unknown_type_vars(self.parent_context, node):
if type_var not in found:
# The order matters and it's therefore a list.
found.append(type_var)
return found
def _get_bases_arguments(self):
arglist = self.tree_node.get_super_arglist()
if arglist:
from jedi.inference import arguments
return arguments.TreeArguments(self.inference_state, self.parent_context, arglist)
return None
@inference_state_method_cache(default=())
def py__bases__(self):
args = self._get_bases_arguments()
if args is not None:
lst = [value for key, value in args.unpack() if key is None]
if lst:
return lst
if self.py__name__() == 'object' \
and self.parent_context.is_builtins_module():
return []
return [LazyKnownValues(
self.inference_state.builtins_module.py__getattribute__('object')
)]
@plugin_manager.decorate()
def get_metaclass_filters(self, metaclass):
debug.dbg('Unprocessed metaclass %s', metaclass)
def get_metaclass_filters(self, metaclasses, is_instance):
debug.warning('Unprocessed metaclass %s', metaclasses)
return []
@inference_state_method_cache(default=NO_VALUES)
@@ -382,3 +386,7 @@ class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase
if values:
return values
return NO_VALUES
@plugin_manager.decorate()
def get_metaclass_signatures(self, metaclasses):
return []

View File

@@ -1,13 +1,18 @@
"""
Module is used to infer Django model fields.
"""
from jedi._compatibility import Parameter
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.names import NameWrapper
from jedi.inference.cache import inference_state_function_cache
from jedi.inference.base_value import ValueSet, iterator_to_value_set, ValueWrapper
from jedi.inference.filters import DictFilter, AttributeOverwrite
from jedi.inference.names import NameWrapper, BaseTreeParamName
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.signature import AbstractSignature
mapping = {
@@ -31,13 +36,26 @@ mapping = {
'UUIDField': ('uuid', 'UUID'),
}
_FILTER_LIKE_METHODS = ('create', 'filter', 'exclude', 'update', 'get',
'get_or_create', 'update_or_create')
def _infer_scalar_field(inference_state, field_name, field_tree_instance):
@inference_state_function_cache()
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 +83,21 @@ 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)
result = field_name.infer()
for field_tree_instance in result:
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, [
@@ -85,16 +108,17 @@ def _infer_field(cls, field_name):
debug.dbg('django plugin: fail to infer `%s` from class `%s`',
field_name.string_name, cls.py__name__())
return field_name.infer()
return result
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,25 +133,163 @@ def _create_manager_for(cls, manager_cls='BaseManager'):
return None
def _new_dict_filter(cls):
filters = cls.get_filters(is_instance=True, include_metaclasses=False)
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)
for filter_ in reversed(list(filters))
name.string_name: DjangoModelName(cls, name, is_instance)
for filter_ in reversed(filters)
for name in filter_.values()
}
manager = _create_manager_for(cls)
if manager:
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)
def is_django_model_base(value):
return value.py__name__() == 'ModelBase' \
and value.get_root_context().py__name__() == 'django.db.models.base'
def get_metaclass_filters(func):
def wrapper(cls, metaclasses, is_instance):
for metaclass in metaclasses:
if is_django_model_base(metaclass):
return [_new_dict_filter(cls, is_instance)]
return func(cls, metaclasses, is_instance)
return wrapper
def tree_name_to_values(func):
def wrapper(inference_state, context, tree_name):
result = func(inference_state, context, tree_name)
if tree_name.value in _FILTER_LIKE_METHODS:
# Here we try to overwrite stuff like User.objects.filter. We need
# this to make sure that keyword param completion works on these
# kind of methods.
for v in result:
if v.get_qualified_names() == ('_BaseQuerySet', tree_name.value) \
and v.parent_context.is_module() \
and v.parent_context.py__name__() == 'django.db.models.query':
qs = context.get_value()
generics = qs.get_generics()
if len(generics) >= 1:
return ValueSet(QuerySetMethodWrapper(v, model)
for model in generics[0])
elif tree_name.value == 'BaseManager' and context.is_module() \
and context.py__name__() == 'django.db.models.manager':
return ValueSet(ManagerWrapper(r) for r in result)
elif tree_name.value == 'Field' and context.is_module() \
and context.py__name__() == 'django.db.models.fields':
return ValueSet(FieldWrapper(r) for r in result)
return result
return wrapper
def _find_fields(cls):
for name in _new_dict_filter(cls, is_instance=False).values():
for value in name.infer():
if value.name.get_qualified_names(include_module_names=True) \
== ('django', 'db', 'models', 'query_utils', 'DeferredAttribute'):
yield name
def _get_signatures(cls):
return [DjangoModelSignature(cls, field_names=list(_find_fields(cls)))]
def get_metaclass_signatures(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':
return [_new_dict_filter(cls)]
return func(cls, metaclasses)
if is_django_model_base(metaclass):
return _get_signatures(cls)
return func(cls, metaclass)
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)
class FieldWrapper(ValueWrapper):
def py__getitem__(self, index_value_set, contextualized_node):
return ValueSet(
GenericFieldWrapper(generic)
for generic in self._wrapped_value.py__getitem__(
index_value_set, contextualized_node)
)
class GenericFieldWrapper(AttributeOverwrite, ClassMixin):
def py__get__on_class(self, calling_instance, instance, class_value):
# This is mostly an optimization to avoid Jedi aborting inference,
# because of too many function executions of Field.__get__.
return ValueSet({calling_instance})
class DjangoModelSignature(AbstractSignature):
def __init__(self, value, field_names):
super(DjangoModelSignature, self).__init__(value)
self._field_names = field_names
def get_param_names(self, resolve_stars=False):
return [DjangoParamName(name) for name in self._field_names]
class DjangoParamName(BaseTreeParamName):
def __init__(self, field_name):
super(DjangoParamName, self).__init__(field_name.parent_context, field_name.tree_name)
self._field_name = field_name
def get_kind(self):
return Parameter.KEYWORD_ONLY
def infer(self):
return self._field_name.infer()
class QuerySetMethodWrapper(ValueWrapper):
def __init__(self, method, model_cls):
super(QuerySetMethodWrapper, self).__init__(method)
self._model_cls = model_cls
def py__get__(self, instance, class_value):
return ValueSet({QuerySetBoundMethodWrapper(v, self._model_cls)
for v in self._wrapped_value.py__get__(instance, class_value)})
class QuerySetBoundMethodWrapper(ValueWrapper):
def __init__(self, method, model_cls):
super(QuerySetBoundMethodWrapper, self).__init__(method)
self._model_cls = model_cls
def get_signatures(self):
return _get_signatures(self._model_cls)

View File

@@ -260,13 +260,12 @@ class ReversedObject(AttributeOverwrite):
super(ReversedObject, self).__init__(reversed_obj)
self._iter_list = iter_list
@publish_method('__iter__')
def py__iter__(self, contextualized_node=None):
def py__iter__(self, contextualized_node):
return self._iter_list
@publish_method('next', python_version_match=2)
@publish_method('__next__', python_version_match=3)
def py__next__(self):
def py__next__(self, arguments):
return ValueSet.from_sets(
lazy_value.infer() for lazy_value in self._iter_list
)
@@ -395,13 +394,13 @@ class PropertyObject(AttributeOverwrite, ValueWrapper):
def py__get__(self, instance, class_value):
if instance is None:
return NO_VALUES
return ValueSet([self])
return self._function.execute_with_values(instance)
@publish_method('deleter')
@publish_method('getter')
@publish_method('setter')
def _return_self(self):
def _return_self(self, arguments):
return ValueSet({self})
@@ -518,6 +517,8 @@ class PartialObject(ValueWrapper):
class PartialMethodObject(PartialObject):
def py__get__(self, instance, class_value):
if instance is None:
return ValueSet([self])
return ValueSet([PartialObject(self._actual_value, self._arguments, instance)])
@@ -802,7 +803,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':
@@ -810,7 +811,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

View File

@@ -4,11 +4,21 @@ 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):
def specially_filtered_tags(self):
return self.all()
class Tag(models.Model):
tag_name = models.CharField()
objects = TagManager()
custom_objects = TagManager()
class Category(models.Model):
category_name = models.CharField()
@@ -49,10 +59,23 @@ class BusinessModel(models.Model):
unidentifiable = NOT_FOUND
#? models.IntegerField()
integer_field
def method(self):
return 42
# -----------------
# Model attribute inference
# -----------------
#? DeferredAttribute()
BusinessModel.integer_field
#? DeferredAttribute()
BusinessModel.tags_m2m
#? DeferredAttribute()
BusinessModel.email_field
model_instance = BusinessModel()
#? int()
@@ -132,20 +155,45 @@ model_instance.unidentifiable
#! ['unidentifiable = NOT_FOUND']
model_instance.unidentifiable
#? int()
model_instance.method()
#! ['def method']
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
# -----------------
#? TagManager()
Tag.objects
#? Tag() None
Tag.objects.filter().first()
#? TagManager()
Tag.custom_objects
#? Tag() None
Tag.custom_objects.filter().first()
# -----------------
# Inheritance
@@ -163,14 +211,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
@@ -186,8 +247,46 @@ 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']
# -----------------
# Completion
# -----------------
#? 19 ['text_field=']
Inherited(text_fiel)
#? 18 ['new_field=']
Inherited(new_fiel)
#? 19 ['char_field=']
Inherited(char_fiel)
#? 19 ['email_field=']
Inherited(email_fie)
#? 19 []
Inherited(unidentif)
#? 21 ['category_fk=', 'category_fk2=', 'category_fk3=', 'category_fk4=', 'category_fk5=']
Inherited(category_fk)
#? 21 ['attached_o2o=']
Inherited(attached_o2)
#? 18 ['tags_m2m=']
Inherited(tags_m2m)
#? 32 ['tags_m2m=']
Inherited.objects.create(tags_m2)
#? 32 ['tags_m2m=']
Inherited.objects.filter(tags_m2)
#? 35 ['char_field=']
Inherited.objects.exclude(char_fiel)
#? 34 ['char_field=']
Inherited.objects.update(char_fiel)
#? 32 ['email_field=']
Inherited.objects.get(email_fiel)
#? 44 ['category_fk2=']
Inherited.objects.get_or_create(category_fk2)
#? 44 ['uuid_field=']
Inherited.objects.update_or_create(uuid_fiel)
#? 48 ['char_field=']
Inherited.objects.exclude(pk=3).filter(char_fiel)