Skip to content

Commit

Permalink
Fix mypy cache of WithAnnotation types (#725)
Browse files Browse the repository at this point in the history
  • Loading branch information
kalekseev authored Mar 24, 2022
1 parent 1672b54 commit 3f340c9
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 26 deletions.
1 change: 1 addition & 0 deletions mypy_django_plugin/django/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()):
Expand Down
4 changes: 2 additions & 2 deletions mypy_django_plugin/transformers/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 24 additions & 24 deletions tests/typecheck/managers/querysets/test_annotate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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*"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -308,19 +308,19 @@
# 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
# arbitrary parameters such that we wouldn't know how many extra fields there might be.
# 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()
Expand Down

0 comments on commit 3f340c9

Please sign in to comment.