Skip to content

Commit 81ae5b9

Browse files
committed
Fix mypy cache of WithAnnotation types
1 parent fb12560 commit 81ae5b9

File tree

3 files changed

+40
-25
lines changed

3 files changed

+40
-25
lines changed

mypy_django_plugin/transformers/models.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import codecs
2+
import pickle
13
from typing import Dict, List, Optional, Type, Union, cast
24

35
from django.db.models.base import Model
@@ -436,9 +438,9 @@ def get_or_create_annotated_type(
436438
model_type = model_type.type.bases[0]
437439

438440
if fields_dict is not None:
439-
type_name = f"WithAnnotations[{model_type.type.fullname}, {fields_dict}]"
441+
type_name = f"WithAnnotations[{model_type.type.fullname.replace('.', '__')}, {fields_dict}]"
440442
else:
441-
type_name = f"WithAnnotations[{model_type.type.fullname}]"
443+
type_name = f"WithAnnotations[{model_type.type.fullname.replace('.', '__')}]"
442444

443445
annotated_typeinfo = helpers.lookup_fully_qualified_typeinfo(
444446
cast(TypeChecker, api), model_module_name + "." + type_name
@@ -462,6 +464,19 @@ def get_or_create_annotated_type(
462464
# To allow structural subtyping, make it a Protocol
463465
annotated_typeinfo.is_protocol = True
464466
# Save for later to easily find which field types were annotated
465-
annotated_typeinfo.metadata["annotated_field_types"] = fields_dict.items
467+
store_annotate_field_types(annotated_typeinfo, fields_dict)
466468
annotated_type = Instance(annotated_typeinfo, [])
467469
return annotated_type
470+
471+
472+
def store_annotate_field_types(annotated_typeinfo, fields_dict: TypedDictType) -> None:
473+
annotated_typeinfo.metadata["annotated_field_types"] = codecs.encode(
474+
pickle.dumps(fields_dict.items), "base64"
475+
).decode("utf-8")
476+
477+
478+
def retrieve_annotated_field_types(annotated_typeinfo) -> Optional[Dict[str, AnyType]]:
479+
field_dict = annotated_typeinfo.metadata.get("annotated_field_types")
480+
if field_dict is None:
481+
return field_dict
482+
return pickle.loads(codecs.decode(field_dict.encode("utf-8"), "base64"))

mypy_django_plugin/transformers/querysets.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from mypy_django_plugin.lib import fullnames, helpers
1616
from mypy_django_plugin.lib.fullnames import ANY_ATTR_ALLOWED_CLASS_FULLNAME
1717
from mypy_django_plugin.lib.helpers import is_annotated_model_fullname
18-
from mypy_django_plugin.transformers.models import get_or_create_annotated_type
18+
from mypy_django_plugin.transformers.models import get_or_create_annotated_type, retrieve_annotated_field_types
1919

2020

2121
def _extract_model_type_from_queryset(queryset_type: Instance) -> Optional[Instance]:
@@ -212,7 +212,7 @@ def extract_proper_type_queryset_annotate(ctx: MethodContext, django_context: Dj
212212

213213
api = ctx.api
214214

215-
field_types = model_type.type.metadata.get("annotated_field_types")
215+
field_types = retrieve_annotated_field_types(model_type.type)
216216
kwargs = gather_kwargs(ctx)
217217
if kwargs:
218218
# For now, we don't try to resolve the output_field of the field would be, but use Any.

tests/typecheck/managers/querysets/test_annotate.yml

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,20 @@
88
99
unannotated_user = User.objects.get(id=1)
1010
11-
print(annotated_user.asdf) # E: "WithAnnotations[myapp.models.User, TypedDict({'foo': Any})]" has no attribute "asdf"
11+
print(annotated_user.asdf) # E: "WithAnnotations[myapp__models__User, TypedDict({'foo': Any})]" has no attribute "asdf"
1212
print(unannotated_user.asdf) # E: "User" has no attribute "asdf"
1313
1414
def func(user: Annotated[User, Annotations]) -> str:
1515
return user.asdf
1616
17-
func(unannotated_user) # E: Argument 1 to "func" has incompatible type "User"; expected "WithAnnotations[myapp.models.User]"
18-
func(annotated_user) # E: Argument 1 to "func" has incompatible type "WithAnnotations[myapp.models.User, TypedDict({'foo': Any})]"; expected "WithAnnotations[myapp.models.User]"
17+
func(unannotated_user) # E: Argument 1 to "func" has incompatible type "User"; expected "WithAnnotations[myapp__models__User]"
18+
func(annotated_user) # E: Argument 1 to "func" has incompatible type "WithAnnotations[myapp__models__User, TypedDict({'foo': Any})]"; expected "WithAnnotations[myapp__models__User]"
1919
2020
def func2(user: WithAnnotations[User]) -> str:
2121
return user.asdf
2222
23-
func2(unannotated_user) # E: Argument 1 to "func2" has incompatible type "User"; expected "WithAnnotations[myapp.models.User]"
24-
func2(annotated_user) # E: Argument 1 to "func2" has incompatible type "WithAnnotations[myapp.models.User, TypedDict({'foo': Any})]"; expected "WithAnnotations[myapp.models.User]"
23+
func2(unannotated_user) # E: Argument 1 to "func2" has incompatible type "User"; expected "WithAnnotations[myapp__models__User]"
24+
func2(annotated_user) # E: Argument 1 to "func2" has incompatible type "WithAnnotations[myapp__models__User, TypedDict({'foo': Any})]"; expected "WithAnnotations[myapp__models__User]"
2525
installed_apps:
2626
- myapp
2727
files:
@@ -44,26 +44,26 @@
4444
foo: str
4545
4646
def func(user: Annotated[User, Annotations[MyDict]]) -> str:
47-
print(user.asdf) # E: "WithAnnotations[myapp.models.User, TypedDict('main.MyDict', {'foo': builtins.str})]" has no attribute "asdf"
47+
print(user.asdf) # E: "WithAnnotations[myapp__models__User, TypedDict('main.MyDict', {'foo': builtins.str})]" has no attribute "asdf"
4848
return user.foo
4949
5050
unannotated_user = User.objects.get(id=1)
5151
annotated_user = User.objects.annotate(foo=Value("")).get()
5252
other_annotated_user = User.objects.annotate(other=Value("")).get()
5353
54-
func(unannotated_user) # E: Argument 1 to "func" has incompatible type "User"; expected "WithAnnotations[myapp.models.User, TypedDict('main.MyDict', {'foo': builtins.str})]"
54+
func(unannotated_user) # E: Argument 1 to "func" has incompatible type "User"; expected "WithAnnotations[myapp__models__User, TypedDict('main.MyDict', {'foo': builtins.str})]"
5555
x: WithAnnotations[User]
5656
func(x)
5757
func(annotated_user)
58-
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})]"
58+
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})]"
5959
6060
def func2(user: WithAnnotations[User, MyDict]) -> str:
61-
print(user.asdf) # E: "WithAnnotations[myapp.models.User, TypedDict('main.MyDict', {'foo': builtins.str})]" has no attribute "asdf"
61+
print(user.asdf) # E: "WithAnnotations[myapp__models__User, TypedDict('main.MyDict', {'foo': builtins.str})]" has no attribute "asdf"
6262
return user.foo
6363
64-
func2(unannotated_user) # E: Argument 1 to "func2" has incompatible type "User"; expected "WithAnnotations[myapp.models.User, TypedDict('main.MyDict', {'foo': builtins.str})]"
64+
func2(unannotated_user) # E: Argument 1 to "func2" has incompatible type "User"; expected "WithAnnotations[myapp__models__User, TypedDict('main.MyDict', {'foo': builtins.str})]"
6565
func2(annotated_user)
66-
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})]"
66+
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})]"
6767
installed_apps:
6868
- myapp
6969
files:
@@ -100,7 +100,7 @@
100100
func(y)
101101
102102
z: WithAnnotations[User, OtherDict]
103-
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})]"
103+
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})]"
104104
105105
installed_apps:
106106
- myapp
@@ -119,12 +119,12 @@
119119
from django.db.models.expressions import F
120120
121121
qs = User.objects.annotate(foo=F('id'))
122-
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})]]"
122+
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})]]"
123123
124124
annotated = qs.get()
125-
reveal_type(annotated) # N: Revealed type is "django_stubs_ext.WithAnnotations[myapp.models.User, TypedDict({'foo': Any})]*"
125+
reveal_type(annotated) # N: Revealed type is "django_stubs_ext.WithAnnotations[myapp__models__User, TypedDict({'foo': Any})]*"
126126
reveal_type(annotated.foo) # N: Revealed type is "Any"
127-
print(annotated.bar) # E: "WithAnnotations[myapp.models.User, TypedDict({'foo': Any})]" has no attribute "bar"
127+
print(annotated.bar) # E: "WithAnnotations[myapp__models__User, TypedDict({'foo': Any})]" has no attribute "bar"
128128
reveal_type(annotated.username) # N: Revealed type is "builtins.str*"
129129
130130
installed_apps:
@@ -144,7 +144,7 @@
144144
from django.db.models import Count
145145
146146
qs = User.objects.annotate(Count('id'))
147-
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]]"
147+
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]]"
148148
149149
installed_apps:
150150
- myapp
@@ -167,7 +167,7 @@
167167
def animals_only(param: Animal):
168168
pass
169169
# Make sure that even though attr access falls back to Any, the type is still checked
170-
animals_only(annotated_user) # E: Argument 1 to "animals_only" has incompatible type "WithAnnotations[myapp.models.User]"; expected "Animal"
170+
animals_only(annotated_user) # E: Argument 1 to "animals_only" has incompatible type "WithAnnotations[myapp__models__User]"; expected "Animal"
171171
172172
def users_allowed(param: User):
173173
# But this function accepts only the original User type, so any attr access is not allowed within this function
@@ -196,7 +196,7 @@
196196
qs = User.objects.annotate(foo=F('id'))
197197
qs = qs.annotate(bar=F('id'))
198198
annotated = qs.get()
199-
reveal_type(annotated) # N: Revealed type is "django_stubs_ext.WithAnnotations[myapp.models.User, TypedDict({'foo': Any, 'bar': Any})]*"
199+
reveal_type(annotated) # N: Revealed type is "django_stubs_ext.WithAnnotations[myapp__models__User, TypedDict({'foo': Any, 'bar': Any})]*"
200200
reveal_type(annotated.foo) # N: Revealed type is "Any"
201201
reveal_type(annotated.bar) # N: Revealed type is "Any"
202202
reveal_type(annotated.username) # N: Revealed type is "builtins.str*"
@@ -227,11 +227,11 @@
227227
return qs.annotate(foo=F('id'))
228228
229229
def add_wrong_annotation(qs: QuerySet[User]) -> QuerySet[WithAnnotations[User, FooDict]]:
230-
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})]]")
230+
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})]]")
231231
232232
qs = add_annotation(qs)
233233
qs.get().foo
234-
qs.get().bar # E: "WithAnnotations[myapp.models.User, TypedDict('main.FooDict', {'foo': builtins.str})]" has no attribute "bar"
234+
qs.get().bar # E: "WithAnnotations[myapp__models__User, TypedDict('main.FooDict', {'foo': builtins.str})]" has no attribute "bar"
235235
236236
installed_apps:
237237
- myapp

0 commit comments

Comments
 (0)