diff --git a/mypy_django_plugin/django/context.py b/mypy_django_plugin/django/context.py index 7bbc8e0bd..1256ce83d 100644 --- a/mypy_django_plugin/django/context.py +++ b/mypy_django_plugin/django/context.py @@ -121,6 +121,7 @@ def get_model_class_by_fullname(self, fullname: str) -> Optional[Type[Model]]: if "," in fullname: # Remove second type arg, which might be present fullname = fullname[: fullname.index(",")] + fullname = fullname.replace("__", ".") module, _, model_cls_name = fullname.rpartition(".") for model_cls in self.model_modules.get(module, set()): diff --git a/mypy_django_plugin/transformers/models.py b/mypy_django_plugin/transformers/models.py index 06b252ddc..52ef3ca1d 100644 --- a/mypy_django_plugin/transformers/models.py +++ b/mypy_django_plugin/transformers/models.py @@ -512,9 +512,9 @@ def get_or_create_annotated_type( model_type = model_type.type.bases[0] if fields_dict is not None: - type_name = f"WithAnnotations[{model_type.type.fullname}, {fields_dict}]" + type_name = f"WithAnnotations[{model_type.type.fullname.replace('.', '__')}, {fields_dict}]" else: - type_name = f"WithAnnotations[{model_type.type.fullname}]" + type_name = f"WithAnnotations[{model_type.type.fullname.replace('.', '__')}]" annotated_typeinfo = helpers.lookup_fully_qualified_typeinfo( cast(TypeChecker, api), model_module_name + "." + type_name diff --git a/tests/typecheck/managers/querysets/test_annotate.yml b/tests/typecheck/managers/querysets/test_annotate.yml index dff775bf5..5f73b2150 100644 --- a/tests/typecheck/managers/querysets/test_annotate.yml +++ b/tests/typecheck/managers/querysets/test_annotate.yml @@ -8,20 +8,20 @@ unannotated_user = User.objects.get(id=1) - print(annotated_user.asdf) # E: "WithAnnotations[myapp.models.User, TypedDict({'foo': Any})]" has no attribute "asdf" + print(annotated_user.asdf) # E: "WithAnnotations[myapp__models__User, TypedDict({'foo': Any})]" has no attribute "asdf" print(unannotated_user.asdf) # E: "User" has no attribute "asdf" def func(user: Annotated[User, Annotations]) -> str: return user.asdf - func(unannotated_user) # E: Argument 1 to "func" has incompatible type "User"; expected "WithAnnotations[myapp.models.User]" - func(annotated_user) # E: Argument 1 to "func" has incompatible type "WithAnnotations[myapp.models.User, TypedDict({'foo': Any})]"; expected "WithAnnotations[myapp.models.User]" + func(unannotated_user) # E: Argument 1 to "func" has incompatible type "User"; expected "WithAnnotations[myapp__models__User]" + func(annotated_user) # E: Argument 1 to "func" has incompatible type "WithAnnotations[myapp__models__User, TypedDict({'foo': Any})]"; expected "WithAnnotations[myapp__models__User]" def func2(user: WithAnnotations[User]) -> str: return user.asdf - func2(unannotated_user) # E: Argument 1 to "func2" has incompatible type "User"; expected "WithAnnotations[myapp.models.User]" - func2(annotated_user) # E: Argument 1 to "func2" has incompatible type "WithAnnotations[myapp.models.User, TypedDict({'foo': Any})]"; expected "WithAnnotations[myapp.models.User]" + func2(unannotated_user) # E: Argument 1 to "func2" has incompatible type "User"; expected "WithAnnotations[myapp__models__User]" + func2(annotated_user) # E: Argument 1 to "func2" has incompatible type "WithAnnotations[myapp__models__User, TypedDict({'foo': Any})]"; expected "WithAnnotations[myapp__models__User]" installed_apps: - myapp files: @@ -44,26 +44,26 @@ foo: str def func(user: Annotated[User, Annotations[MyDict]]) -> str: - print(user.asdf) # E: "WithAnnotations[myapp.models.User, TypedDict('main.MyDict', {'foo': builtins.str})]" has no attribute "asdf" + print(user.asdf) # E: "WithAnnotations[myapp__models__User, TypedDict('main.MyDict', {'foo': builtins.str})]" has no attribute "asdf" return user.foo unannotated_user = User.objects.get(id=1) annotated_user = User.objects.annotate(foo=Value("")).get() other_annotated_user = User.objects.annotate(other=Value("")).get() - func(unannotated_user) # E: Argument 1 to "func" has incompatible type "User"; expected "WithAnnotations[myapp.models.User, TypedDict('main.MyDict', {'foo': builtins.str})]" + func(unannotated_user) # E: Argument 1 to "func" has incompatible type "User"; expected "WithAnnotations[myapp__models__User, TypedDict('main.MyDict', {'foo': builtins.str})]" x: WithAnnotations[User] func(x) func(annotated_user) - func(other_annotated_user) # E: Argument 1 to "func" has incompatible type "WithAnnotations[myapp.models.User, TypedDict({'other': Any})]"; expected "WithAnnotations[myapp.models.User, TypedDict('main.MyDict', {'foo': builtins.str})]" + func(other_annotated_user) # E: Argument 1 to "func" has incompatible type "WithAnnotations[myapp__models__User, TypedDict({'other': Any})]"; expected "WithAnnotations[myapp__models__User, TypedDict('main.MyDict', {'foo': builtins.str})]" def func2(user: WithAnnotations[User, MyDict]) -> str: - print(user.asdf) # E: "WithAnnotations[myapp.models.User, TypedDict('main.MyDict', {'foo': builtins.str})]" has no attribute "asdf" + print(user.asdf) # E: "WithAnnotations[myapp__models__User, TypedDict('main.MyDict', {'foo': builtins.str})]" has no attribute "asdf" return user.foo - func2(unannotated_user) # E: Argument 1 to "func2" has incompatible type "User"; expected "WithAnnotations[myapp.models.User, TypedDict('main.MyDict', {'foo': builtins.str})]" + func2(unannotated_user) # E: Argument 1 to "func2" has incompatible type "User"; expected "WithAnnotations[myapp__models__User, TypedDict('main.MyDict', {'foo': builtins.str})]" func2(annotated_user) - func2(other_annotated_user) # E: Argument 1 to "func2" has incompatible type "WithAnnotations[myapp.models.User, TypedDict({'other': Any})]"; expected "WithAnnotations[myapp.models.User, TypedDict('main.MyDict', {'foo': builtins.str})]" + func2(other_annotated_user) # E: Argument 1 to "func2" has incompatible type "WithAnnotations[myapp__models__User, TypedDict({'other': Any})]"; expected "WithAnnotations[myapp__models__User, TypedDict('main.MyDict', {'foo': builtins.str})]" installed_apps: - myapp files: @@ -100,7 +100,7 @@ func(y) z: WithAnnotations[User, OtherDict] - func(z) # E: Argument 1 to "func" has incompatible type "WithAnnotations[myapp.models.User, TypedDict('main.OtherDict', {'other': builtins.str})]"; expected "WithAnnotations[myapp.models.User, TypedDict('main.NarrowDict', {'foo': builtins.str})]" + func(z) # E: Argument 1 to "func" has incompatible type "WithAnnotations[myapp__models__User, TypedDict('main.OtherDict', {'other': builtins.str})]"; expected "WithAnnotations[myapp__models__User, TypedDict('main.NarrowDict', {'foo': builtins.str})]" installed_apps: - myapp @@ -119,12 +119,12 @@ from django.db.models.expressions import F qs = User.objects.annotate(foo=F('id')) - reveal_type(qs) # N: Revealed type is "django.db.models.query._QuerySet[django_stubs_ext.WithAnnotations[myapp.models.User, TypedDict({'foo': Any})], django_stubs_ext.WithAnnotations[myapp.models.User, TypedDict({'foo': Any})]]" + reveal_type(qs) # N: Revealed type is "django.db.models.query._QuerySet[django_stubs_ext.WithAnnotations[myapp__models__User, TypedDict({'foo': Any})], django_stubs_ext.WithAnnotations[myapp__models__User, TypedDict({'foo': Any})]]" annotated = qs.get() - reveal_type(annotated) # N: Revealed type is "django_stubs_ext.WithAnnotations[myapp.models.User, TypedDict({'foo': Any})]*" + reveal_type(annotated) # N: Revealed type is "django_stubs_ext.WithAnnotations[myapp__models__User, TypedDict({'foo': Any})]*" reveal_type(annotated.foo) # N: Revealed type is "Any" - print(annotated.bar) # E: "WithAnnotations[myapp.models.User, TypedDict({'foo': Any})]" has no attribute "bar" + print(annotated.bar) # E: "WithAnnotations[myapp__models__User, TypedDict({'foo': Any})]" has no attribute "bar" reveal_type(annotated.username) # N: Revealed type is "builtins.str*" installed_apps: @@ -144,7 +144,7 @@ from django.db.models import Count qs = User.objects.annotate(Count('id')) - reveal_type(qs) # N: Revealed type is "django.db.models.query._QuerySet[django_stubs_ext.WithAnnotations[myapp.models.User], django_stubs_ext.WithAnnotations[myapp.models.User]]" + reveal_type(qs) # N: Revealed type is "django.db.models.query._QuerySet[django_stubs_ext.WithAnnotations[myapp__models__User], django_stubs_ext.WithAnnotations[myapp__models__User]]" installed_apps: - myapp @@ -167,7 +167,7 @@ def animals_only(param: Animal): pass # Make sure that even though attr access falls back to Any, the type is still checked - animals_only(annotated_user) # E: Argument 1 to "animals_only" has incompatible type "WithAnnotations[myapp.models.User]"; expected "Animal" + animals_only(annotated_user) # E: Argument 1 to "animals_only" has incompatible type "WithAnnotations[myapp__models__User]"; expected "Animal" def users_allowed(param: User): # But this function accepts only the original User type, so any attr access is not allowed within this function @@ -196,7 +196,7 @@ qs = User.objects.annotate(foo=F('id')) qs = qs.annotate(bar=F('id')) annotated = qs.get() - reveal_type(annotated) # N: Revealed type is "django_stubs_ext.WithAnnotations[myapp.models.User, TypedDict({'foo': Any, 'bar': Any})]*" + reveal_type(annotated) # N: Revealed type is "django_stubs_ext.WithAnnotations[myapp__models__User, TypedDict({'foo': Any, 'bar': Any})]*" reveal_type(annotated.foo) # N: Revealed type is "Any" reveal_type(annotated.bar) # N: Revealed type is "Any" reveal_type(annotated.username) # N: Revealed type is "builtins.str*" @@ -227,11 +227,11 @@ return qs.annotate(foo=F('id')) def add_wrong_annotation(qs: QuerySet[User]) -> QuerySet[WithAnnotations[User, FooDict]]: - return qs.annotate(bar=F('id')) # E: Incompatible return value type (got "_QuerySet[WithAnnotations[myapp.models.User, TypedDict({'bar': Any})], WithAnnotations[myapp.models.User, TypedDict({'bar': Any})]]", expected "_QuerySet[WithAnnotations[myapp.models.User, TypedDict('main.FooDict', {'foo': builtins.str})], WithAnnotations[myapp.models.User, TypedDict('main.FooDict', {'foo': builtins.str})]]") + return qs.annotate(bar=F('id')) # E: Incompatible return value type (got "_QuerySet[WithAnnotations[myapp__models__User, TypedDict({'bar': Any})], WithAnnotations[myapp__models__User, TypedDict({'bar': Any})]]", expected "_QuerySet[WithAnnotations[myapp__models__User, TypedDict('main.FooDict', {'foo': builtins.str})], WithAnnotations[myapp__models__User, TypedDict('main.FooDict', {'foo': builtins.str})]]") qs = add_annotation(qs) qs.get().foo - qs.get().bar # E: "WithAnnotations[myapp.models.User, TypedDict('main.FooDict', {'foo': builtins.str})]" has no attribute "bar" + qs.get().bar # E: "WithAnnotations[myapp__models__User, TypedDict('main.FooDict', {'foo': builtins.str})]" has no attribute "bar" installed_apps: - myapp @@ -308,9 +308,9 @@ # It's possible to provide more precise types than than this, but without inspecting the # arguments to .annotate, these are the best types we can infer. qs1 = Blog.objects.values('text').annotate(foo=F('id')) - reveal_type(qs1) # N: Revealed type is "django.db.models.query._QuerySet[django_stubs_ext.WithAnnotations[myapp.models.Blog, TypedDict({'foo': Any})], builtins.dict[builtins.str, Any]]" + reveal_type(qs1) # N: Revealed type is "django.db.models.query._QuerySet[django_stubs_ext.WithAnnotations[myapp__models__Blog, TypedDict({'foo': Any})], builtins.dict[builtins.str, Any]]" qs2 = Blog.objects.values_list('text').annotate(foo=F('id')) - reveal_type(qs2) # N: Revealed type is "django.db.models.query._QuerySet[django_stubs_ext.WithAnnotations[myapp.models.Blog, TypedDict({'foo': Any})], builtins.tuple[Any]]" + reveal_type(qs2) # N: Revealed type is "django.db.models.query._QuerySet[django_stubs_ext.WithAnnotations[myapp__models__Blog, TypedDict({'foo': Any})], builtins.tuple[Any]]" qs3 = Blog.objects.values_list('text', named=True).annotate(foo=F('id')) # TODO: Would be nice to infer a NamedTuple which contains the field 'text' (str) + any number of other fields. # The reason it would have to appear to have any other fields is that annotate could potentially be called with @@ -318,9 +318,9 @@ # But it's not trivial to make such a NamedTuple, partly because since it is also an ordinary tuple, it would # have to have an arbitrary length, but still have certain fields at certain indices with specific types. # For now, Any :) - reveal_type(qs3) # N: Revealed type is "django.db.models.query._QuerySet[django_stubs_ext.WithAnnotations[myapp.models.Blog, TypedDict({'foo': Any})], Any]" + reveal_type(qs3) # N: Revealed type is "django.db.models.query._QuerySet[django_stubs_ext.WithAnnotations[myapp__models__Blog, TypedDict({'foo': Any})], Any]" qs4 = Blog.objects.values_list('text', flat=True).annotate(foo=F('id')) - reveal_type(qs4) # N: Revealed type is "django.db.models.query._QuerySet[django_stubs_ext.WithAnnotations[myapp.models.Blog, TypedDict({'foo': Any})], builtins.str]" + reveal_type(qs4) # N: Revealed type is "django.db.models.query._QuerySet[django_stubs_ext.WithAnnotations[myapp__models__Blog, TypedDict({'foo': Any})], builtins.str]" before_values_no_params = Blog.objects.values().annotate(foo=F('id')).get()