Skip to content

Commit 18413d5

Browse files
authored
Fix GenericPrefetch invariance with QuerySet (#3380)
1 parent d97f08b commit 18413d5

3 files changed

Lines changed: 26 additions & 3 deletions

File tree

django-stubs/contrib/contenttypes/prefetch.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from collections.abc import Sequence
12
from typing import Any, Generic
23

34
from django.db.models import Model, Prefetch
@@ -9,7 +10,7 @@ from typing_extensions import TypeVar, override
910
_LookupT = TypeVar("_LookupT", bound=str, covariant=True)
1011
# The type of the querysets passed to GenericPrefetch(...)
1112
_PrefetchedQuerySetsT = TypeVar(
12-
"_PrefetchedQuerySetsT", bound=list[QuerySet[Model]], covariant=True, default=list[QuerySet[Model]]
13+
"_PrefetchedQuerySetsT", bound=Sequence[QuerySet[Model]], covariant=True, default=list[QuerySet[Model]]
1314
)
1415
# The attribute name to store the prefetched list[_PrefetchedQuerySetT]
1516
# This will be specialized to a `LiteralString` in the plugin for further processing and validation

django-stubs/db/models/query.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ class Prefetch(Generic[_LookupT, _PrefetchedQuerySetT, _ToAttrT]):
303303
def add_prefix(self, prefix: str) -> None: ...
304304
def get_current_prefetch_to(self, level: int) -> str: ...
305305
def get_current_to_attr(self, level: int) -> tuple[str, str]: ...
306-
def get_current_querysets(self, level: int) -> list[_PrefetchedQuerySetT] | None: ...
306+
def get_current_querysets(self, level: int) -> Sequence[_PrefetchedQuerySetT] | None: ...
307307

308308
def normalize_prefetch_lookups(lookups: Sequence[str | Prefetch], prefix: str | None = None) -> list[Prefetch]: ...
309309
def prefetch_related_objects(model_instances: Sequence[_Model], *related_lookups: str | Prefetch) -> None: ...

tests/typecheck/managers/querysets/test_prefetch_related.yml

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@
428428
- myapp
429429
main: |
430430
from django.contrib.contenttypes.prefetch import GenericPrefetch
431-
from myapp.models import Bookmark, Animal, TaggedItem
431+
from myapp.models import Bookmark, Animal, TaggedItem, Dog, Cat
432432
from typing_extensions import reveal_type
433433
434434
# Basic GenericPrefetch usage
@@ -437,6 +437,17 @@
437437
)
438438
reveal_type(prefetch) # N: Revealed type is "django.contrib.contenttypes.prefetch.GenericPrefetch[Literal['content_object'], list[django.db.models.query.QuerySet[django.db.models.base.Model, django.db.models.base.Model]], str]"
439439
440+
# GenericPrefetch usage with custom querysets
441+
prefetch_custom = GenericPrefetch(
442+
"content_object", [Dog.objects.all(), Cat.objects.all()]
443+
)
444+
reveal_type(prefetch_custom) # N: Revealed type is "django.contrib.contenttypes.prefetch.GenericPrefetch[Literal['content_object'], list[myapp.models.CustomQuerySet[django.db.models.base.Model, django.db.models.base.Model]], str]"
445+
446+
prefetch_custom_mixed = GenericPrefetch(
447+
"content_object", [Bookmark.objects.all(), Animal.objects.only("name"), Dog.objects.all(), Cat.objects.all()]
448+
)
449+
reveal_type(prefetch_custom_mixed) # N: Revealed type is "django.contrib.contenttypes.prefetch.GenericPrefetch[Literal['content_object'], list[django.db.models.query.QuerySet[django.db.models.base.Model, django.db.models.base.Model]], str]"
450+
440451
# Using GenericPrefetch with prefetch_related
441452
qs = TaggedItem.objects.prefetch_related(prefetch).all()
442453
reveal_type(qs) # N: Revealed type is "django.db.models.query.QuerySet[myapp.models.TaggedItem, myapp.models.TaggedItem]"
@@ -477,6 +488,17 @@
477488
class Animal(models.Model):
478489
name = models.CharField(max_length=100)
479490
491+
class CustomQuerySet(models.QuerySet):
492+
pass
493+
494+
CustomManager = models.Manager.from_queryset(CustomQuerySet)
495+
496+
class Dog(models.Model):
497+
objects = CustomManager()
498+
499+
class Cat(models.Model):
500+
objects = CustomManager()
501+
480502
class TaggedItem(models.Model):
481503
tag = models.CharField(max_length=100)
482504
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)

0 commit comments

Comments
 (0)