-
-
Notifications
You must be signed in to change notification settings - Fork 368
Open
Description
Problem
When subclassing InheritanceQuerySetMixin to create a custom QuerySet, methods that return Self fail mypy type checking because annotate(), _chain(), and _clone() are typed to return InheritanceQuerySet[ModelT] instead of Self.
from typing import Self
from django.db.models import QuerySet
from model_utils.managers import InheritanceQuerySetMixin
class MyQuerySet(InheritanceQuerySetMixin[MyModel], QuerySet[MyModel]):
def my_filter(self) -> Self:
return self.annotate(foo=Value(1)) # mypy error heremypy error:
error: Incompatible return value type (got "InheritanceQuerySet[MyModel]", expected "Self") [return-value]
This happens because annotate() in InheritanceQuerySetMixin returns InheritanceQuerySet[ModelT]:
def annotate(self, *args: Any, **kwargs: Any) -> InheritanceQuerySet[ModelT]:
...The same issue affects _chain() and _clone().
Environment
- Django Model Utils version: 5.0.0
- Django version: 5.2.9
- Python version: 3.11
Code example / Failing test
# test_inheritance_manager.py
from typing import Self, Generic
from typing_extensions import TypeVar
from django.db import models
from django.db.models import Value
from django.db.models.query import QuerySet
from model_utils.managers import InheritanceQuerySetMixin, InheritanceManager
T_co = TypeVar("T_co", bound=models.Model, covariant=True)
class CustomQuerySet(InheritanceQuerySetMixin[T_co], QuerySet[T_co], Generic[T_co]):
def custom_annotate(self) -> Self:
# This should type-check correctly but currently fails mypy
return self.annotate(custom_field=Value(1))
# Running mypy on this file produces:
# error: Incompatible return value type (got "InheritanceQuerySet[T_co]", expected "Self")Proposed Solution
Change return types from InheritanceQuerySet[ModelT] to Self:
# managers.py
import sys
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self
class InheritanceQuerySetMixin(Generic[ModelT]):
...
def annotate(self, *args: Any, **kwargs: Any) -> Self:
...
def _chain(self, **kwargs: object) -> Self:
...
def _clone(self) -> Self:
...This requires adding typing_extensions as a dependency for Python 3.10 support.
Current Workaround
Users must use a two-class pattern:
# Base without mixin - type checks against QuerySet.annotate() -> Self
class MyBaseQuerySet(QuerySet[T_co], Generic[T_co]):
def my_filter(self) -> Self:
return self.annotate(...) # Works!
# Final class adds mixin for select_subclasses() support
class MyQuerySet(InheritanceQuerySetMixin[T_co], MyBaseQuerySet[T_co]):
passI'm happy to submit a PR with tests if this approach is acceptable.
Metadata
Metadata
Assignees
Labels
No labels