django-stubs icon indicating copy to clipboard operation
django-stubs copied to clipboard

Related Manager lose typing

Open trecouvr opened this issue 1 year ago • 0 comments

Bug report

Hi, not sure if this is a bug or my having wrong declarations.

What's wrong

The typing of related_manager.all is off when I share a QuerySet between multiple models :

-   case: related
    main: |
        from myapp.models import MyModel
        reveal_type(MyModel.objects.all()) # N: Revealed type is "myapp.models.MyQuerySet[myapp.models.MyModel]"
        reveal_type(list(MyModel.objects.all())[0]) # N: Revealed type is "myapp.models.MyModel"
        for x in MyModel.objects.all():
            reveal_type(x) # N: Revealed type is "myapp.models.MyModel"
            reveal_type(x.relatedobject_set.all()) # N: Revealed type is "myapp.models.MyQuerySet[myapp.models.RelatedObject]"
            reveal_type(list(x.relatedobject_set.all())[0]) # N: Revealed type is "myapp.models.RelatedObject"
            for y in x.relatedobject_set.all():
                reveal_type(y) # N: Revealed type is "myapp.models.RelatedObject"

    installed_apps:
        - myapp
    files:
        -   path: myapp/__init__.py
        -   path: myapp/models.py
            content: |
                from django.db import models
                from django.db.models.manager import BaseManager
                from typing import List, Dict
                from typing_extensions import Self
                from typing import TypeVar, Generic

                T = TypeVar("T", bound=models.Model)

                class MyQuerySet(models.QuerySet[T], Generic[T]): ...

                class MyModel(models.Model):
                    objects = MyQuerySet.as_manager()

                class RelatedObject(models.Model):
                    objects = MyQuerySet.as_manager()
                    parent = models.ForeignKey(MyModel, models.PROTECT)

E   Actual:
E     ...
E     main:6: note: Revealed type is "myapp.models.MyQuerySet" (diff)
E     main:7: note: Revealed type is "T`1"          (diff)
E     main:9: note: Revealed type is "T`1"          (diff)
E     myapp/models:15: error: Need type annotation for "objects"  [var-annotated] (diff)
E     myapp/models:20: error: Need type annotation for "objects"  [var-annotated] (diff)
E   Expected:
E     ...
E     main:6: note: Revealed type is "myapp.models.MyQuerySet[myapp.models.RelatedObject]" (diff)
E     main:7: note: Revealed type is "myapp.models.RelatedObject" (diff)
E     main:9: note: Revealed type is "myapp.models.RelatedObject" (diff)

-   case: related_subclassing
    main: |
        from myapp.models import MyModel
        reveal_type(MyModel.objects.all()) # N: Revealed type is "myapp.models.BaseQuerySet[myapp.models.MyModel]"
        reveal_type(list(MyModel.objects.all())[0]) # N: Revealed type is "myapp.models.MyModel"
        for x in MyModel.objects.all():
            reveal_type(x) # N: Revealed type is "myapp.models.MyModel"
            reveal_type(x.relatedobject_set.all()) # N: Revealed type is "myapp.models.BaseQuerySet[myapp.models.RelatedObject]"
            reveal_type(list(x.relatedobject_set.all())[0]) # N: Revealed type is "myapp.models.RelatedObject"
            for y in x.relatedobject_set.all():
                reveal_type(y) # N: Revealed type is "myapp.models.RelatedObject"

    installed_apps:
        - myapp
    files:
        -   path: myapp/__init__.py
        -   path: myapp/models.py
            content: |
                from django.db import models
                from django.db.models.manager import BaseManager
                from typing import List, Dict
                from typing_extensions import Self
                from typing import TypeVar, Generic

                T = TypeVar("T", bound=models.Model)

                class BaseQuerySet(models.QuerySet[T], Generic[T]): ...

                class BaseModel(models.Model):
                    objects = BaseQuerySet.as_manager()
                    class Meta:
                        abstract = True

                class MyModel(BaseModel):
                    pass

                class RelatedObjectQuerySet(BaseQuerySet["RelatedObject"]):
                    pass

                class RelatedObject(BaseModel):
                    parent = models.ForeignKey(MyModel, models.PROTECT)

E   Actual:
E     ...
E     main:6: note: Revealed type is "myapp.models.BaseQuerySet" (diff)
E     main:7: note: Revealed type is "T`1"          (diff)
E     main:9: note: Revealed type is "T`1"          (diff)
E     myapp/models:13: error: Need type annotation for "objects"  [var-annotated] (diff)
E   Expected:
E     ...
E     main:6: note: Revealed type is "myapp.models.BaseQuerySet[myapp.models.RelatedObject]" (diff)
E     main:7: note: Revealed type is "myapp.models.RelatedObject" (diff)
E     main:9: note: Revealed type is "myapp.models.RelatedObject" (diff)

How is that should be

The types should be MyModel or RelatedObject instead of T.

Note: If I remove the Generic I get Any instead of T.

System information

  • OS: osx
  • python version: 3.11.3
  • django version: Django==4.2.9
  • mypy version: mypy==1.7.1
  • django-stubs version: django-stubs==4.2.7
  • django-stubs-ext version: django-stubs-ext==4.2.7

trecouvr avatar Jan 24 '24 15:01 trecouvr