From 1419b144d963eeab5df135060398c5870ee0ae4f Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Sun, 15 Mar 2020 00:58:11 +0300 Subject: [PATCH] wip --- django-stubs/db/models/fields/__init__.pyi | 3 + mfile.py | 113 ++++++++++ mfile.py.gv | 13 ++ mfile.py.gv.pdf | Bin 0 -> 20039 bytes mfile.py.gv.png | Bin 0 -> 30176 bytes my.gv | 9 + my.gv.pdf | Bin 0 -> 14836 bytes mypy_django_plugin/django/context.py | 10 +- mypy_django_plugin/lib/helpers.py | 152 +++++++++++-- mypy_django_plugin/main.py | 22 +- mypy_django_plugin/transformers/managers.py | 99 --------- .../transformers2/dynamic_managers.py | 27 ++- mypy_django_plugin/transformers2/models.py | 207 ++++++++++++------ .../transformers2/new_helpers.py | 4 + .../transformers2/related_managers.py | 69 ++++++ test-data/typecheck/fields/test_related.yml | 2 +- .../managers/querysets/test_as_manager.yml | 95 -------- .../managers/querysets/test_from_queryset.yml | 6 +- test-output/round-table.gv | 3 + test-output/round-table.gv.pdf | Bin 0 -> 11474 bytes 20 files changed, 513 insertions(+), 321 deletions(-) create mode 100644 mfile.py create mode 100644 mfile.py.gv create mode 100644 mfile.py.gv.pdf create mode 100644 mfile.py.gv.png create mode 100644 my.gv create mode 100644 my.gv.pdf create mode 100644 mypy_django_plugin/transformers2/related_managers.py delete mode 100644 test-data/typecheck/managers/querysets/test_as_manager.yml create mode 100644 test-output/round-table.gv create mode 100644 test-output/round-table.gv.pdf diff --git a/django-stubs/db/models/fields/__init__.pyi b/django-stubs/db/models/fields/__init__.pyi index 9797769..3c8d46e 100644 --- a/django-stubs/db/models/fields/__init__.pyi +++ b/django-stubs/db/models/fields/__init__.pyi @@ -42,6 +42,9 @@ _ST = TypeVar("_ST") # __get__ return type _GT = TypeVar("_GT") +class CharField(Field[str, str]): + + class Field(RegisterLookupMixin, Generic[_ST, _GT]): _pyi_private_set_type: Any _pyi_private_get_type: Any diff --git a/mfile.py b/mfile.py new file mode 100644 index 0000000..29f6885 --- /dev/null +++ b/mfile.py @@ -0,0 +1,113 @@ +from graphviz import Digraph +from mypy.options import Options + +source = """ +from root.package import MyQuerySet + +MyQuerySet().mymethod() +""" + +from mypy import parse + +parsed = parse.parse(source, 'myfile.py', None, None, Options()) +print(parsed) + +graphattrs = { + "labelloc": "t", + "fontcolor": "blue", + # "bgcolor": "#333333", + "margin": "0", +} + +nodeattrs = { + # "color": "white", + "fontcolor": "#00008b", + # "style": "filled", + # "fillcolor": "#ffffff", + # "fillcolor": "#006699", +} + +edgeattrs = { + # "color": "white", + # "fontcolor": "white", +} + +graph = Digraph('mfile.py', graph_attr=graphattrs, node_attr=nodeattrs, edge_attr=edgeattrs) +graph.node('__builtins__') + +graph.node('django.db.models') +graph.node('django.db.models.fields') + +graph.edge('django.db.models', 'django.db.models.fields') +graph.edge('django.db.models', '__builtins__') +graph.edge('django.db.models.fields', '__builtins__') + +graph.node('mymodule') +graph.edge('mymodule', 'django.db.models') +graph.edge('mymodule', '__builtins__') +# +# graph.node('ImportFrom', label='ImportFrom(val=root.package, [MyQuerySet])') +# graph.edge('MypyFile', 'ImportFrom') + + + +# graph.node('ClassDef_MyQuerySet', label='ClassDef(name=MyQuerySet)') +# graph.edge('MypyFile', 'ClassDef_MyQuerySet') +# +# graph.node('FuncDef_mymethod', label='FuncDef(name=mymethod)') +# graph.edge('ClassDef_MyQuerySet', 'FuncDef_mymethod') +# +# graph.node('Args', label='Args') +# graph.edge('FuncDef_mymethod', 'Args') +# +# graph.node('Var_self', label='Var(name=self)') +# graph.edge('Args', 'Var_self') +# +# graph.node('Block', label='Block') +# graph.edge('FuncDef_mymethod', 'Block') +# +# graph.node('PassStmt') +# graph.edge('Block', 'PassStmt') + +# graph.node('ExpressionStmt') +# graph.edge('MypyFile', 'ExpressionStmt') +# +# graph.node('CallExpr', label='CallExpr(val="MyQuerySet()")') +# graph.edge('ExpressionStmt', 'CallExpr') +# +# graph.node('MemberExpr', label='MemberExpr(val=".mymethod()")') +# graph.edge('CallExpr', 'MemberExpr') +# +# graph.node('CallExpr_outer_Args', label='Args()') +# graph.edge('CallExpr', 'CallExpr_outer_Args') +# +# graph.node('CallExpr_inner', label='CallExpr(val="mymethod()")') +# graph.edge('MemberExpr', 'CallExpr_inner') +# +# graph.node('NameExpr', label='NameExpr(val="mymethod")') +# graph.edge('CallExpr_inner', 'NameExpr') +# +# graph.node('Expression_Args', label='Args()') +# graph.edge('CallExpr_inner', 'Expression_Args') + +graph.render(view=True, format='png') + + +# MypyFile( +# ClassDef( +# name=MyQuerySet, +# FuncDef( +# name=mymethod, +# Args( +# Var(self)) +# Block(PassStmt()) +# ) +# ) +# ExpressionStmt:6( +# CallExpr:6( +# MemberExpr:6( +# CallExpr:6( +# NameExpr(MyQuerySet) +# Args()) +# mymethod) +# Args()))) diff --git a/mfile.py.gv b/mfile.py.gv new file mode 100644 index 0000000..58ca391 --- /dev/null +++ b/mfile.py.gv @@ -0,0 +1,13 @@ +digraph "mfile.py" { + graph [fontcolor=blue labelloc=t margin=0] + node [fontcolor="#00008b"] + __builtins__ + "django.db.models" + "django.db.models.fields" + "django.db.models" -> "django.db.models.fields" + "django.db.models" -> __builtins__ + "django.db.models.fields" -> __builtins__ + mymodule + mymodule -> "django.db.models" + mymodule -> __builtins__ +} diff --git a/mfile.py.gv.pdf b/mfile.py.gv.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2be078e63a23c7b43fe09628b8766f30dd2a51f2 GIT binary patch literal 20039 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 literal 0 HcmV?d00001 diff --git a/my.gv b/my.gv new file mode 100644 index 0000000..3e79d53 --- /dev/null +++ b/my.gv @@ -0,0 +1,9 @@ +digraph AST { + File + ClassDef + ClassDef -> File + FuncDef + FuncDef -> ClassDef + ExpressionStmt + ExpressionStmt -> File +} diff --git a/my.gv.pdf b/my.gv.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5fc254d10937aba0556f860f8fa31f602d24a939 GIT binary patch literal 14836 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 J!e2$;{vX9cI Iterator[ForeignObjectRel]: - for field in model_cls._meta.get_fields(): - if isinstance(field, ForeignObjectRel): - yield field + def get_model_relations(self, model_cls: Type[Model]) -> Iterator[Tuple[Optional[str], ForeignObjectRel]]: + for relation in model_cls._meta.get_fields(): + if isinstance(relation, ForeignObjectRel): + yield relation.get_accessor_name(), relation def get_field_lookup_exact_type(self, api: TypeChecker, field: Union[Field, ForeignObjectRel]) -> MypyType: if isinstance(field, (RelatedField, ForeignObjectRel)): diff --git a/mypy_django_plugin/lib/helpers.py b/mypy_django_plugin/lib/helpers.py index cc68aa4..0e86466 100644 --- a/mypy_django_plugin/lib/helpers.py +++ b/mypy_django_plugin/lib/helpers.py @@ -10,11 +10,11 @@ from mypy.mro import calculate_mro from mypy.nodes import ( Block, ClassDef, Expression, MemberExpr, MypyFile, NameExpr, StrExpr, SymbolTable, SymbolTableNode, TypeInfo, Var, - CallExpr, Context, PlaceholderNode, FuncDef, FakeInfo) -from mypy.plugin import DynamicClassDefContext, ClassDefContext + CallExpr, Context, PlaceholderNode, FuncDef, FakeInfo, OverloadedFuncDef, Decorator) +from mypy.plugin import DynamicClassDefContext, ClassDefContext, AttributeContext, MethodContext from mypy.plugins.common import add_method -from mypy.semanal import SemanticAnalyzer -from mypy.types import AnyType, Instance, NoneTyp, TypeType +from mypy.semanal import SemanticAnalyzer, is_valid_replacement, is_same_symbol +from mypy.types import AnyType, Instance, NoneTyp, TypeType, ProperType, CallableType from mypy.types import Type as MypyType from mypy.types import TypeOfAny, UnionType from mypy.typetraverser import TypeTraverserVisitor @@ -38,8 +38,25 @@ class DjangoPluginCallback: self.plugin = plugin self.django_context = plugin.django_context - # def lookup_fully_qualified(self, fullname: str) -> Optional[SymbolTableNode]: - # return self.plugin.lookup_fully_qualified(fullname) + def new_typeinfo(self, name: str, bases: List[Instance]) -> TypeInfo: + class_def = ClassDef(name, Block([])) + class_def.fullname = self.qualified_name(name) + + info = TypeInfo(SymbolTable(), class_def, self.get_current_module().fullname) + info.bases = bases + calculate_mro(info) + info.metaclass_type = info.calculate_metaclass_type() + + class_def.info = info + return info + + @abstractmethod + def get_current_module(self) -> MypyFile: + raise NotImplementedError() + + @abstractmethod + def qualified_name(self, name: str) -> str: + raise NotImplementedError() class SemanalPluginCallback(DjangoPluginCallback): @@ -58,6 +75,12 @@ class SemanalPluginCallback(DjangoPluginCallback): print(f'LOG: defer: {self.build_defer_error_message(reason)}') return True + def get_current_module(self) -> MypyFile: + return self.semanal_api.cur_mod_node + + def qualified_name(self, name: str) -> str: + return self.semanal_api.qualified_name(name) + def lookup_typeinfo_or_defer(self, fullname: str, *, deferral_context: Optional[Context] = None, reason_for_defer: Optional[str] = None) -> Optional[TypeInfo]: @@ -74,11 +97,12 @@ class SemanalPluginCallback(DjangoPluginCallback): return sym.node - def new_typeinfo(self, name: str, bases: List[Instance]) -> TypeInfo: + def new_typeinfo(self, name: str, bases: List[Instance], module_fullname: Optional[str] = None) -> TypeInfo: class_def = ClassDef(name, Block([])) class_def.fullname = self.semanal_api.qualified_name(name) - info = TypeInfo(SymbolTable(), class_def, self.semanal_api.cur_mod_id) + info = TypeInfo(SymbolTable(), class_def, + module_fullname or self.get_current_module().fullname) info.bases = bases calculate_mro(info) info.metaclass_type = info.calculate_metaclass_type() @@ -86,6 +110,43 @@ class SemanalPluginCallback(DjangoPluginCallback): class_def.info = info return info + def add_symbol_table_node(self, + name: str, + symbol: SymbolTableNode, + symbol_table: Optional[SymbolTable] = None, + context: Optional[Context] = None, + can_defer: bool = True, + escape_comprehensions: bool = False) -> None: + """ Patched copy of SemanticAnalyzer.add_symbol_table_node(). """ + names = symbol_table or self.semanal_api.current_symbol_table(escape_comprehensions=escape_comprehensions) + existing = names.get(name) + if isinstance(symbol.node, PlaceholderNode) and can_defer: + self.semanal_api.defer(context) + return None + if (existing is not None + and context is not None + and not is_valid_replacement(existing, symbol)): + # There is an existing node, so this may be a redefinition. + # If the new node points to the same node as the old one, + # or if both old and new nodes are placeholders, we don't + # need to do anything. + old = existing.node + new = symbol.node + if isinstance(new, PlaceholderNode): + # We don't know whether this is okay. Let's wait until the next iteration. + return False + if not is_same_symbol(old, new): + if isinstance(new, (FuncDef, Decorator, OverloadedFuncDef, TypeInfo)): + self.semanal_api.add_redefinition(names, name, symbol) + if not (isinstance(new, (FuncDef, Decorator)) + and self.semanal_api.set_original_def(old, new)): + self.semanal_api.name_already_defined(name, context, existing) + elif name not in self.semanal_api.missing_names and '*' not in self.semanal_api.missing_names: + names[name] = symbol + self.progress = True + return None + raise new_helpers.SymbolAdditionNotPossible() + # def add_symbol_table_node_or_defer(self, name: str, sym: SymbolTableNode) -> bool: # return self.semanal_api.add_symbol_table_node(name, sym, # context=self.semanal_api.cur_mod_node) @@ -119,20 +180,6 @@ class SemanalPluginCallback(DjangoPluginCallback): self.semanal_api.add_imported_symbol(name, sym, context=self.semanal_api.cur_mod_node) class UnimportedTypesVisitor(TypeTraverserVisitor): - def visit_union_type(self, t: UnionType) -> None: - super().visit_union_type(t) - union_sym = currently_imported_symbols.get('Union') - if union_sym is None: - # TODO: check if it's exactly typing.Union - import_symbol_from_source('Union') - - def visit_type_type(self, t: TypeType) -> None: - super().visit_type_type(t) - type_sym = currently_imported_symbols.get('Union') - if type_sym is None: - # TODO: check if it's exactly typing.Type - import_symbol_from_source('Type') - def visit_instance(self, t: Instance) -> None: super().visit_instance(t) if isinstance(t.type, FakeInfo): @@ -140,7 +187,6 @@ class SemanalPluginCallback(DjangoPluginCallback): type_name = t.type.name sym = currently_imported_symbols.get(type_name) if sym is None: - # TODO: check if it's exactly typing.Type import_symbol_from_source(type_name) signature_node.type.accept(UnimportedTypesVisitor()) @@ -202,11 +248,13 @@ class DynamicClassPluginCallback(SemanalPluginCallback): class ClassDefPluginCallback(SemanalPluginCallback): reason: Expression class_defn: ClassDef + ctx: ClassDefContext def __call__(self, ctx: ClassDefContext) -> None: self.reason = ctx.reason self.class_defn = ctx.cls self.semanal_api = cast(SemanticAnalyzer, ctx.api) + self.ctx = ctx self.modify_class_defn() @abstractmethod @@ -214,6 +262,64 @@ class ClassDefPluginCallback(SemanalPluginCallback): raise NotImplementedError +class TypeCheckerPluginCallback(DjangoPluginCallback): + type_checker: TypeChecker + + def get_current_module(self) -> MypyFile: + current_module = None + for item in reversed(self.type_checker.scope.stack): + if isinstance(item, MypyFile): + current_module = item + break + assert current_module is not None + return current_module + + def qualified_name(self, name: str) -> str: + return self.type_checker.scope.stack[-1].fullname + '.' + name + + def lookup_typeinfo(self, fullname: str) -> Optional[TypeInfo]: + sym = self.plugin.lookup_fully_qualified(fullname) + if sym is None or sym.node is None: + return None + if not isinstance(sym.node, TypeInfo): + raise ValueError(f'{fullname!r} does not correspond to TypeInfo') + return sym.node + + +class GetMethodPluginCallback(TypeCheckerPluginCallback): + callee_type: Instance + ctx: MethodContext + + def __call__(self, ctx: MethodContext) -> MypyType: + self.type_checker = ctx.api + + assert isinstance(ctx.type, CallableType) + self.callee_type = ctx.type.ret_type + self.ctx = ctx + return self.get_method_return_type() + + @abstractmethod + def get_method_return_type(self) -> MypyType: + raise NotImplementedError + + +class GetAttributeCallback(TypeCheckerPluginCallback): + obj_type: ProperType + default_attr_type: MypyType + error_context: MemberExpr + name: str + + def __call__(self, ctx: AttributeContext) -> MypyType: + self.ctx = ctx + self.type_checker = ctx.api + self.obj_type = ctx.type + self.default_attr_type = ctx.default_attr_type + self.error_context = ctx.context + assert isinstance(self.error_context, MemberExpr) + self.name = self.error_context.name + return self.default_attr_type + + def get_django_metadata(model_info: TypeInfo) -> Dict[str, Any]: return model_info.metadata.setdefault('django', {}) diff --git a/mypy_django_plugin/main.py b/mypy_django_plugin/main.py index 5302ec2..70f8d8f 100644 --- a/mypy_django_plugin/main.py +++ b/mypy_django_plugin/main.py @@ -17,11 +17,10 @@ from mypy_django_plugin.lib import fullnames, helpers from mypy_django_plugin.transformers import ( fields, forms, init_create, meta, querysets, request, settings, ) -from mypy_django_plugin.transformers.managers import ( - create_manager_class_from_as_manager_method, instantiate_anonymous_queryset_from_as_manager) from mypy_django_plugin.transformers.models import process_model_class from mypy_django_plugin.transformers2.dynamic_managers import CreateNewManagerClassFrom_FromQuerySet from mypy_django_plugin.transformers2.models import ModelCallback +from mypy_django_plugin.transformers2.related_managers import GetRelatedManagerCallback def transform_model_class(ctx: ClassDefContext, @@ -176,10 +175,6 @@ class NewSemanalDjangoPlugin(Plugin): if fullname == 'django.contrib.auth.get_user_model': return partial(settings.get_user_model_hook, django_context=self.django_context) - # manager_bases = self._get_current_manager_bases() - # if fullname in manager_bases: - # return querysets.determine_proper_manager_type - info = self._get_typeinfo_or_none(fullname) if info: if info.has_base(fullnames.FIELD_FULLNAME): @@ -217,11 +212,6 @@ class NewSemanalDjangoPlugin(Plugin): if info and info.has_base(fullnames.OPTIONS_CLASS_FULLNAME): return partial(meta.return_proper_field_type_from_get_field, django_context=self.django_context) - if method_name == 'as_manager': - info = self._get_typeinfo_or_none(class_fullname) - if info and info.has_base(fullnames.QUERYSET_CLASS_FULLNAME): - return instantiate_anonymous_queryset_from_as_manager - manager_classes = self._get_current_manager_bases() if class_fullname in manager_classes and method_name == 'create': return partial(init_create.redefine_and_typecheck_model_create, django_context=self.django_context) @@ -253,6 +243,10 @@ class NewSemanalDjangoPlugin(Plugin): info = self._get_typeinfo_or_none(class_name) if info and info.has_base(fullnames.HTTPREQUEST_CLASS_FULLNAME) and attr_name == 'user': return partial(request.set_auth_user_model_as_type_for_request_user, django_context=self.django_context) + + if info and info.has_base(fullnames.MODEL_CLASS_FULLNAME): + return GetRelatedManagerCallback(self) + return None def get_dynamic_class_hook(self, fullname: str @@ -263,12 +257,6 @@ class NewSemanalDjangoPlugin(Plugin): if info and info.has_base(fullnames.BASE_MANAGER_CLASS_FULLNAME): return CreateNewManagerClassFrom_FromQuerySet(self) - if fullname.endswith('as_manager'): - class_name, _, _ = fullname.rpartition('.') - info = self._get_typeinfo_or_none(class_name) - if info and info.has_base(fullnames.QUERYSET_CLASS_FULLNAME): - return create_manager_class_from_as_manager_method - return None diff --git a/mypy_django_plugin/transformers/managers.py b/mypy_django_plugin/transformers/managers.py index 9bb7cea..9731b14 100644 --- a/mypy_django_plugin/transformers/managers.py +++ b/mypy_django_plugin/transformers/managers.py @@ -230,102 +230,3 @@ def add_symbol_table_node(api: SemanticAnalyzer, return True return False - -class CreateNewManagerClassFrom_AsManager(helpers.DynamicClassPluginCallback): - def create_new_dynamic_class(self) -> None: - pass - - -def create_manager_class_from_as_manager_method(ctx: DynamicClassDefContext) -> None: - semanal_api = sem_helpers.get_semanal_api(ctx) - try: - queryset_info = resolve_callee_info_or_exception(ctx) - django_manager_info = resolve_django_manager_info_or_exception(ctx) - except sem_helpers.IncompleteDefnError: - if not semanal_api.final_iteration: - semanal_api.defer() - return - else: - raise - - generic_param: MypyType = AnyType(TypeOfAny.explicit) - generic_param_name = 'Any' - if (semanal_api.scope.classes - and semanal_api.scope.classes[-1].has_base(fullnames.MODEL_CLASS_FULLNAME)): - info = semanal_api.scope.classes[-1] # type: TypeInfo - generic_param = Instance(info, []) - generic_param_name = info.name - - new_manager_class_name = queryset_info.name + '_AsManager_' + generic_param_name - new_manager_info = helpers.new_typeinfo(new_manager_class_name, - bases=[Instance(django_manager_info, [generic_param])], - module_name=semanal_api.cur_mod_id) - new_manager_info.set_line(ctx.call) - - record_new_manager_info_fullname_into_metadata(ctx, - new_manager_info.fullname, - django_manager_info, - queryset_info, - django_manager_info) - - class_def_context = ClassDefContext(cls=new_manager_info.defn, - reason=ctx.call, api=semanal_api) - self_type = Instance(new_manager_info, [AnyType(TypeOfAny.explicit)]) - - try: - for name, method_node in iter_all_custom_queryset_methods(queryset_info): - sem_helpers.copy_method_or_incomplete_defn_exception(class_def_context, - self_type, - new_method_name=name, - method_node=method_node) - except sem_helpers.IncompleteDefnError: - if not semanal_api.final_iteration: - semanal_api.defer() - return - else: - raise - - new_manager_sym = SymbolTableNode(GDEF, new_manager_info, plugin_generated=True) - - # context=None - forcibly replace old node - added = add_symbol_table_node(semanal_api, new_manager_class_name, new_manager_sym, - context=None, - symbol_table=semanal_api.globals) - if added: - # replace all references to the old manager Var everywhere - for _, module in semanal_api.modules.items(): - if module.fullname != semanal_api.cur_mod_id: - for sym_name, sym in module.names.items(): - if sym.fullname == new_manager_info.fullname: - module.names[sym_name] = new_manager_sym.copy() - - # we need another iteration to process methods - if (not added - and not semanal_api.final_iteration): - semanal_api.defer() - - -def instantiate_anonymous_queryset_from_as_manager(ctx: MethodContext) -> MypyType: - api = chk_helpers.get_typechecker_api(ctx) - django_manager_info = helpers.lookup_fully_qualified_typeinfo(api, fullnames.MANAGER_CLASS_FULLNAME) - assert django_manager_info is not None - - assert isinstance(ctx.type, CallableType) - assert isinstance(ctx.type.ret_type, Instance) - queryset_info = ctx.type.ret_type.type - - gen_name = django_manager_info.name + 'From' + queryset_info.name - gen_fullname = 'django.db.models.manager' + '.' + gen_name - - metadata = get_generated_managers_metadata(django_manager_info) - if gen_fullname not in metadata: - raise ValueError(f'{gen_fullname!r} is not present in generated managers list') - - module_name, _, class_name = metadata[gen_fullname].rpartition('.') - current_module = helpers.get_current_module(api) - assert module_name == current_module.fullname - - generated_manager_info = current_module.names[class_name].node - assert isinstance(generated_manager_info, TypeInfo) - - return Instance(generated_manager_info, []) diff --git a/mypy_django_plugin/transformers2/dynamic_managers.py b/mypy_django_plugin/transformers2/dynamic_managers.py index 9c927e5..275f780 100644 --- a/mypy_django_plugin/transformers2/dynamic_managers.py +++ b/mypy_django_plugin/transformers2/dynamic_managers.py @@ -4,11 +4,17 @@ from mypy.checker import gen_unique_name from mypy.nodes import NameExpr, TypeInfo, SymbolTableNode, StrExpr from mypy.types import Type as MypyType, TypeVarType, TypeVarDef, Instance -from mypy_django_plugin.lib import helpers, fullnames, chk_helpers, sem_helpers +from mypy_django_plugin.lib import helpers, fullnames from mypy_django_plugin.transformers.managers import iter_all_custom_queryset_methods class CreateNewManagerClassFrom_FromQuerySet(helpers.DynamicClassPluginCallback): + def set_manager_mapping(self, runtime_manager_fullname: str, generated_manager_fullname: str) -> None: + base_model_info = self.lookup_typeinfo_or_defer(fullnames.MODEL_CLASS_FULLNAME) + assert base_model_info is not None + managers_metadata = base_model_info.metadata.setdefault('managers', {}) + managers_metadata[runtime_manager_fullname] = generated_manager_fullname + def create_typevar_in_current_module(self, name: str, upper_bound: Optional[MypyType] = None) -> TypeVarDef: tvar_name = gen_unique_name(name, self.semanal_api.globals) @@ -48,19 +54,20 @@ class CreateNewManagerClassFrom_FromQuerySet(helpers.DynamicClassPluginCallback) parent_manager_type = Instance(callee_manager_info, [model_tvar_type]) # instantiate with a proper model, Manager[MyModel], filling all Manager type vars in process + queryset_type = Instance(passed_queryset_info, [Instance(base_model_info, [])]) new_manager_info = self.new_typeinfo(self.class_name, - bases=[parent_manager_type]) + bases=[queryset_type, parent_manager_type]) new_manager_info.defn.type_vars = [model_tvar_defn] new_manager_info.type_vars = [model_tvar_defn.name] new_manager_info.set_line(self.call_expr) # copy methods from passed_queryset_info with self type replaced - self_type = Instance(new_manager_info, [model_tvar_type]) - for name, method_node in iter_all_custom_queryset_methods(passed_queryset_info): - self.add_method_from_signature(method_node, - name, - self_type, - new_manager_info.defn) + # self_type = Instance(new_manager_info, [model_tvar_type]) + # for name, method_node in iter_all_custom_queryset_methods(passed_queryset_info): + # self.add_method_from_signature(method_node, + # name, + # self_type, + # new_manager_info.defn) new_manager_sym = SymbolTableNode(self.semanal_api.current_symbol_kind(), new_manager_info, @@ -75,5 +82,5 @@ class CreateNewManagerClassFrom_FromQuerySet(helpers.DynamicClassPluginCallback) runtime_manager_class_name = class_name_arg.value new_manager_name = runtime_manager_class_name or (callee_manager_info.name + 'From' + queryset_class_name) - django_generated_manager_name = 'django.db.models.manager.' + new_manager_name - base_model_info.metadata.setdefault('managers', {})[django_generated_manager_name] = new_manager_info.fullname + self.set_manager_mapping(f'django.db.models.manager.{new_manager_name}', + new_manager_info.fullname) diff --git a/mypy_django_plugin/transformers2/models.py b/mypy_django_plugin/transformers2/models.py index 12962e6..a8814d0 100644 --- a/mypy_django_plugin/transformers2/models.py +++ b/mypy_django_plugin/transformers2/models.py @@ -3,16 +3,16 @@ from typing import Type, Optional from django.db.models.base import Model from django.db.models.fields.related import OneToOneField, ForeignKey -from django.db.models.fields.reverse_related import OneToOneRel, ManyToManyRel, ManyToOneRel -from mypy.checker import gen_unique_name -from mypy.nodes import TypeInfo, Var, SymbolTableNode, MDEF +from mypy.nodes import TypeInfo, Var, SymbolTableNode, MDEF, Argument, ARG_STAR2 from mypy.plugin import ClassDefContext +from mypy.plugins import common from mypy.semanal import dummy_context from mypy.types import Instance, TypeOfAny, AnyType from mypy.types import Type as MypyType from django.db import models -from mypy_django_plugin.lib import helpers, fullnames +from django.db.models.fields import DateField, DateTimeField +from mypy_django_plugin.lib import helpers, fullnames, sem_helpers from mypy_django_plugin.transformers import fields from mypy_django_plugin.transformers.fields import get_field_type from mypy_django_plugin.transformers2 import new_helpers @@ -116,76 +116,77 @@ class AddPrimaryKeyIfDoesNotExist(TransformModelClassCallback): class AddRelatedManagersCallback(TransformModelClassCallback): def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None: - for relation in self.django_context.get_model_relations(runtime_model_cls): - reverse_manager_name = relation.get_accessor_name() + for reverse_manager_name, relation in self.django_context.get_model_relations(runtime_model_cls): if (reverse_manager_name is None or reverse_manager_name in self.class_defn.info.names): continue - related_model_cls = self.django_context.get_field_related_model_cls(relation) - if related_model_cls is None: - # could not find a referenced model (maybe invalid to= value, or GenericForeignKey) - continue - - related_model_info = self.lookup_typeinfo_for_class_or_defer(related_model_cls) - if related_model_info is None: - continue - - if isinstance(relation, OneToOneRel): - self.add_new_model_attribute(reverse_manager_name, - Instance(related_model_info, [])) - elif isinstance(relation, (ManyToOneRel, ManyToManyRel)): - related_manager_info = self.lookup_typeinfo_or_defer(fullnames.RELATED_MANAGER_CLASS) - if related_manager_info is None: - if not self.defer_till_next_iteration(self.class_defn, - reason=f'{fullnames.RELATED_MANAGER_CLASS!r} is not available for lookup'): - raise TypeInfoNotFound(fullnames.RELATED_MANAGER_CLASS) - continue - - # get type of default_manager for model - default_manager_fullname = helpers.get_class_fullname(related_model_cls._meta.default_manager.__class__) - reason_for_defer = (f'Trying to lookup default_manager {default_manager_fullname!r} ' - f'of model {helpers.get_class_fullname(related_model_cls)!r}') - default_manager_info = self.lookup_typeinfo_or_defer(default_manager_fullname, - reason_for_defer=reason_for_defer) - if default_manager_info is None: - continue - - default_manager_type = Instance(default_manager_info, [Instance(related_model_info, [])]) - - # related_model_cls._meta.default_manager.__class__ - # # we're making a subclass of 'objects', need to have it defined - # if 'objects' not in related_model_info.names: - # if not self.defer_till_next_iteration(self.class_defn, - # reason=f"'objects' manager is not yet defined on {related_model_info.fullname!r}"): - # raise AttributeNotFound(self.class_defn.info, 'objects') - # continue - - related_manager_type = Instance(related_manager_info, - [Instance(related_model_info, [])]) - # - # objects_sym = related_model_info.names['objects'] - # default_manager_type = objects_sym.type - # if default_manager_type is None: - # # dynamic base class, extract from django_context - # default_manager_cls = related_model_cls._meta.default_manager.__class__ - # default_manager_info = self.lookup_typeinfo_for_class_or_defer(default_manager_cls) - # if default_manager_info is None: - # continue - # default_manager_type = Instance(default_manager_info, [Instance(related_model_info, [])]) - - if (not isinstance(default_manager_type, Instance) - or default_manager_type.type.fullname == fullnames.MANAGER_CLASS_FULLNAME): - # if not defined or trivial -> just return RelatedManager[Model] - self.add_new_model_attribute(reverse_manager_name, related_manager_type) - continue - - # make anonymous class - name = gen_unique_name(related_model_cls.__name__ + '_' + 'RelatedManager', - self.semanal_api.current_symbol_table()) - bases = [related_manager_type, default_manager_type] - new_manager_info = self.new_typeinfo(name, bases) - self.add_new_model_attribute(reverse_manager_name, Instance(new_manager_info, [])) + self.add_new_model_attribute(reverse_manager_name, AnyType(TypeOfAny.implementation_artifact)) + # + # related_model_cls = self.django_context.get_field_related_model_cls(relation) + # if related_model_cls is None: + # # could not find a referenced model (maybe invalid to= value, or GenericForeignKey) + # continue + # + # related_model_info = self.lookup_typeinfo_for_class_or_defer(related_model_cls) + # if related_model_info is None: + # continue + # + # if isinstance(relation, OneToOneRel): + # self.add_new_model_attribute(reverse_manager_name, + # Instance(related_model_info, [])) + # elif isinstance(relation, (ManyToOneRel, ManyToManyRel)): + # related_manager_info = self.lookup_typeinfo_or_defer(fullnames.RELATED_MANAGER_CLASS) + # if related_manager_info is None: + # if not self.defer_till_next_iteration(self.class_defn, + # reason=f'{fullnames.RELATED_MANAGER_CLASS!r} is not available for lookup'): + # raise TypeInfoNotFound(fullnames.RELATED_MANAGER_CLASS) + # continue + # + # # get type of default_manager for model + # default_manager_fullname = helpers.get_class_fullname(related_model_cls._meta.default_manager.__class__) + # reason_for_defer = (f'Trying to lookup default_manager {default_manager_fullname!r} ' + # f'of model {helpers.get_class_fullname(related_model_cls)!r}') + # default_manager_info = self.lookup_typeinfo_or_defer(default_manager_fullname, + # reason_for_defer=reason_for_defer) + # if default_manager_info is None: + # continue + # + # default_manager_type = Instance(default_manager_info, [Instance(related_model_info, [])]) + # + # # related_model_cls._meta.default_manager.__class__ + # # # we're making a subclass of 'objects', need to have it defined + # # if 'objects' not in related_model_info.names: + # # if not self.defer_till_next_iteration(self.class_defn, + # # reason=f"'objects' manager is not yet defined on {related_model_info.fullname!r}"): + # # raise AttributeNotFound(self.class_defn.info, 'objects') + # # continue + # + # related_manager_type = Instance(related_manager_info, + # [Instance(related_model_info, [])]) + # # + # # objects_sym = related_model_info.names['objects'] + # # default_manager_type = objects_sym.type + # # if default_manager_type is None: + # # # dynamic base class, extract from django_context + # # default_manager_cls = related_model_cls._meta.default_manager.__class__ + # # default_manager_info = self.lookup_typeinfo_for_class_or_defer(default_manager_cls) + # # if default_manager_info is None: + # # continue + # # default_manager_type = Instance(default_manager_info, [Instance(related_model_info, [])]) + # + # if (not isinstance(default_manager_type, Instance) + # or default_manager_type.type.fullname == fullnames.MANAGER_CLASS_FULLNAME): + # # if not defined or trivial -> just return RelatedManager[Model] + # self.add_new_model_attribute(reverse_manager_name, related_manager_type) + # continue + # + # # make anonymous class + # name = gen_unique_name(related_model_cls.__name__ + '_' + 'RelatedManager', + # self.semanal_api.current_symbol_table()) + # bases = [related_manager_type, default_manager_type] + # new_manager_info = self.new_typeinfo(name, bases) + # self.add_new_model_attribute(reverse_manager_name, Instance(new_manager_info, [])) class AddForeignPrimaryKeys(TransformModelClassCallback): @@ -222,6 +223,69 @@ class AddForeignPrimaryKeys(TransformModelClassCallback): self.add_new_model_attribute(rel_pk_field_name, field_type) +class InjectAnyAsBaseForNestedMeta(TransformModelClassCallback): + """ + Replaces + class MyModel(models.Model): + class Meta: + pass + with + class MyModel(models.Model): + class Meta(Any): + pass + to get around incompatible Meta inner classes for different models. + """ + + def modify_class_defn(self) -> None: + meta_node = sem_helpers.get_nested_meta_node_for_current_class(self.class_defn.info) + if meta_node is None: + return None + meta_node.fallback_to_any = True + + +class AddMetaOptionsAttribute(TransformModelClassCallback): + def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None: + if '_meta' not in self.class_defn.info.names: + options_info = self.lookup_typeinfo_or_defer(fullnames.OPTIONS_CLASS_FULLNAME) + if options_info is not None: + self.add_new_model_attribute('_meta', + Instance(options_info, [ + Instance(self.class_defn.info, []) + ])) + + +class AddExtraFieldMethods(TransformModelClassCallback): + def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None: + # get_FOO_display for choices + for field in self.django_context.get_model_fields(runtime_model_cls): + if field.choices: + info = self.lookup_typeinfo_or_defer('builtins.str') + return_type = Instance(info, []) + common.add_method(self.ctx, + name='get_{}_display'.format(field.attname), + args=[], + return_type=return_type) + + # get_next_by, get_previous_by for Date, DateTime + for field in self.django_context.get_model_fields(runtime_model_cls): + if isinstance(field, (DateField, DateTimeField)) and not field.null: + return_type = Instance(self.class_defn.info, []) + common.add_method(self.ctx, + name='get_next_by_{}'.format(field.attname), + args=[Argument(Var('kwargs', AnyType(TypeOfAny.explicit)), + AnyType(TypeOfAny.explicit), + initializer=None, + kind=ARG_STAR2)], + return_type=return_type) + common.add_method(self.ctx, + name='get_previous_by_{}'.format(field.attname), + args=[Argument(Var('kwargs', AnyType(TypeOfAny.explicit)), + AnyType(TypeOfAny.explicit), + initializer=None, + kind=ARG_STAR2)], + return_type=return_type) + + class ModelCallback(helpers.ClassDefPluginCallback): def __call__(self, ctx: ClassDefContext) -> None: callback_classes = [ @@ -230,6 +294,9 @@ class ModelCallback(helpers.ClassDefPluginCallback): AddForeignPrimaryKeys, AddDefaultManagerCallback, AddRelatedManagersCallback, + InjectAnyAsBaseForNestedMeta, + AddMetaOptionsAttribute, + AddExtraFieldMethods, ] for callback_cls in callback_classes: callback = callback_cls(self.plugin) diff --git a/mypy_django_plugin/transformers2/new_helpers.py b/mypy_django_plugin/transformers2/new_helpers.py index 87f6958..ff1121c 100644 --- a/mypy_django_plugin/transformers2/new_helpers.py +++ b/mypy_django_plugin/transformers2/new_helpers.py @@ -22,5 +22,9 @@ class NameNotFound(IncompleteDefnError): super().__init__(f'Could not find {name!r} in the current activated namespaces') +class SymbolAdditionNotPossible(Exception): + pass + + def get_class_fullname(klass: type) -> str: return klass.__module__ + '.' + klass.__qualname__ diff --git a/mypy_django_plugin/transformers2/related_managers.py b/mypy_django_plugin/transformers2/related_managers.py new file mode 100644 index 0000000..b5c6cce --- /dev/null +++ b/mypy_django_plugin/transformers2/related_managers.py @@ -0,0 +1,69 @@ +from mypy.checker import gen_unique_name +from mypy.plugin import AttributeContext +from mypy.types import Instance +from mypy.types import Type as MypyType + +from django.db.models.fields.reverse_related import ForeignObjectRel, OneToOneRel, ManyToOneRel, ManyToManyRel + +from mypy_django_plugin.lib import helpers, fullnames +from mypy_django_plugin.lib.helpers import GetAttributeCallback + + +class GetRelatedManagerCallback(GetAttributeCallback): + obj_type: Instance + + def get_related_manager_type(self, relation: ForeignObjectRel) -> MypyType: + related_model_cls = self.django_context.get_field_related_model_cls(relation) + if related_model_cls is None: + # could not find a referenced model (maybe invalid to= value, or GenericForeignKey) + # TODO: show error + return self.default_attr_type + + related_model_info = self.lookup_typeinfo(helpers.get_class_fullname(related_model_cls)) + if related_model_info is None: + # TODO: show error + return self.default_attr_type + + if isinstance(relation, OneToOneRel): + return Instance(related_model_info, []) + + elif isinstance(relation, (ManyToOneRel, ManyToManyRel)): + related_manager_info = self.lookup_typeinfo(fullnames.RELATED_MANAGER_CLASS) + if related_manager_info is None: + return self.default_attr_type + + # get type of default_manager for model + default_manager_fullname = helpers.get_class_fullname(related_model_cls._meta.default_manager.__class__) + default_manager_info = self.lookup_typeinfo(default_manager_fullname) + if default_manager_info is None: + return self.default_attr_type + + default_manager_type = Instance(default_manager_info, [Instance(related_model_info, [])]) + related_manager_type = Instance(related_manager_info, + [Instance(related_model_info, [])]) + + if (not isinstance(default_manager_type, Instance) + or default_manager_type.type.fullname == fullnames.MANAGER_CLASS_FULLNAME): + # if not defined or trivial -> just return RelatedManager[Model] + return related_manager_type + + # make anonymous class + name = gen_unique_name(related_model_cls.__name__ + '_' + 'RelatedManager', + self.obj_type.type.names) + bases = [related_manager_type, default_manager_type] + new_manager_info = self.new_typeinfo(name, bases) + return Instance(new_manager_info, []) + + def __call__(self, ctx: AttributeContext): + super().__call__(ctx) + assert isinstance(self.obj_type, Instance) + + model_fullname = self.obj_type.type.fullname + model_cls = self.django_context.get_model_class_by_fullname(model_fullname) + if model_cls is None: + return self.default_attr_type + for reverse_manager_name, relation in self.django_context.get_model_relations(model_cls): + if reverse_manager_name == self.name: + return self.get_related_manager_type(relation) + + return self.default_attr_type diff --git a/test-data/typecheck/fields/test_related.yml b/test-data/typecheck/fields/test_related.yml index a757d37..518aba9 100644 --- a/test-data/typecheck/fields/test_related.yml +++ b/test-data/typecheck/fields/test_related.yml @@ -653,7 +653,7 @@ - case: related_manager_is_a_subclass_of_default_manager main: | from myapp.models import User - reveal_type(User().orders) # N: Revealed type is 'myapp.models.User.Order_RelatedManager' + reveal_type(User().orders) # N: Revealed type is 'main.Order_RelatedManager' reveal_type(User().orders.get()) # N: Revealed type is 'myapp.models.Order*' reveal_type(User().orders.manager_method()) # N: Revealed type is 'builtins.int' installed_apps: diff --git a/test-data/typecheck/managers/querysets/test_as_manager.yml b/test-data/typecheck/managers/querysets/test_as_manager.yml deleted file mode 100644 index fe2c20d..0000000 --- a/test-data/typecheck/managers/querysets/test_as_manager.yml +++ /dev/null @@ -1,95 +0,0 @@ -- case: anonymous_queryset_from_as_manager_inside_model - main: | - from myapp.models import MyModel - - reveal_type(MyModel.objects) # N: Revealed type is 'myapp.models.MyQuerySet_AsManager_MyModel' - reveal_type(MyModel.objects.get()) # N: Revealed type is 'myapp.models.MyModel*' - reveal_type(MyModel.objects.queryset_method) # N: Revealed type is 'def () -> builtins.int' - reveal_type(MyModel.objects.queryset_method()) # N: Revealed type is 'builtins.int' - installed_apps: - - myapp - files: - - path: myapp/__init__.py - - path: myapp/models.py - content: | - from django.db import models - class MyQuerySet(models.QuerySet): - def queryset_method(self) -> int: - pass - class MyModel(models.Model): - objects = MyQuerySet.as_manager() - - -- case: two_invocations_parametrized_with_different_models - main: | - from myapp.models import User, Blog - reveal_type(User.objects) # N: Revealed type is 'myapp.models.MyQuerySet_AsManager_User' - reveal_type(User.objects.get()) # N: Revealed type is 'myapp.models.User*' - reveal_type(User.objects.queryset_method) # N: Revealed type is 'def () -> builtins.int' - reveal_type(User.objects.queryset_method()) # N: Revealed type is 'builtins.int' - - reveal_type(Blog.objects) # N: Revealed type is 'myapp.models.MyQuerySet_AsManager_Blog' - reveal_type(Blog.objects.get()) # N: Revealed type is 'myapp.models.Blog*' - reveal_type(Blog.objects.queryset_method) # N: Revealed type is 'def () -> builtins.int' - reveal_type(Blog.objects.queryset_method()) # N: Revealed type is 'builtins.int' - installed_apps: - - myapp - files: - - path: myapp/__init__.py - - path: myapp/models.py - content: | - from django.db import models - class MyQuerySet(models.QuerySet): - def queryset_method(self) -> int: - pass - class User(models.Model): - objects = MyQuerySet.as_manager() - class Blog(models.Model): - objects = MyQuerySet.as_manager() - - -- case: as_manager_outside_model_parametrized_with_any - main: | - from myapp.models import NotModel, outside_objects - reveal_type(NotModel.objects) # N: Revealed type is 'myapp.models.MyQuerySet_AsManager_Any' - reveal_type(NotModel.objects.get()) # N: Revealed type is 'Any' - reveal_type(outside_objects) # N: Revealed type is 'myapp.models.MyQuerySet_AsManager_Any' - reveal_type(outside_objects.get()) # N: Revealed type is 'Any' - installed_apps: - - myapp - files: - - path: myapp/__init__.py - - path: myapp/models.py - content: | - from django.db import models - class MyQuerySet(models.QuerySet): - def queryset_method(self) -> int: - pass - outside_objects = MyQuerySet.as_manager() - class NotModel: - objects = MyQuerySet.as_manager() - -- case: test_as_manager_without_name_to_bind_in_different_files - main: | - from myapp.models import MyQuerySet - reveal_type(MyQuerySet.as_manager()) # N: Revealed type is 'Any' - reveal_type(MyQuerySet.as_manager().get()) # N: Revealed type is 'Any' - reveal_type(MyQuerySet.as_manager().mymethod()) # N: Revealed type is 'Any' - - from myapp import helpers - installed_apps: - - myapp - files: - - path: myapp/__init__.py - - path: myapp/models.py - content: | - from django.db import models - class MyQuerySet(models.QuerySet): - def mymethod(self) -> int: - pass - class MyModel(models.Model): - objects = MyQuerySet.as_manager() - - path: myapp/helpers.py - content: | - from myapp.models import MyQuerySet - MyQuerySet.as_manager() \ No newline at end of file diff --git a/test-data/typecheck/managers/querysets/test_from_queryset.yml b/test-data/typecheck/managers/querysets/test_from_queryset.yml index 83bd390..a839e3c 100644 --- a/test-data/typecheck/managers/querysets/test_from_queryset.yml +++ b/test-data/typecheck/managers/querysets/test_from_queryset.yml @@ -17,11 +17,15 @@ - path: myapp/__init__.py - path: myapp/models.py content: | + from typing import TypeVar from django.db import models from django.db.models.manager import BaseManager, Manager from mypy_django_plugin.lib import generics - class ModelQuerySet(models.QuerySet): + generics.make_classes_generic(models.QuerySet) + _M = TypeVar('_M', bound=models.Model) + + class ModelQuerySet(models.QuerySet[_M]): def queryset_method(self) -> str: return 'hello' diff --git a/test-output/round-table.gv b/test-output/round-table.gv new file mode 100644 index 0000000..9281e83 --- /dev/null +++ b/test-output/round-table.gv @@ -0,0 +1,3 @@ +digraph { + FuncDef [label="My FuncDef"] +} diff --git a/test-output/round-table.gv.pdf b/test-output/round-table.gv.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3478b08e24b84537463f78cefd812be5da0e2e89 GIT binary patch literal 11474 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 F{~wKS+%y0H literal 0 HcmV?d00001