Skip to content

feat: Add Self return type and QuerySetT type parameter for inheritance classes#652

Draft
lee3jjang wants to merge 1 commit intojazzband:masterfrom
lee3jjang:fix/inheritance-manager-mixin-queryset-type-parameter
Draft

feat: Add Self return type and QuerySetT type parameter for inheritance classes#652
lee3jjang wants to merge 1 commit intojazzband:masterfrom
lee3jjang:fix/inheritance-manager-mixin-queryset-type-parameter

Conversation

@lee3jjang
Copy link

@lee3jjang lee3jjang commented Jan 20, 2026

Summary

This PR improves type safety for InheritanceQuerySetMixin and InheritanceManagerMixin by:

  1. InheritanceQuerySetMixin methods now return Self instead of InheritanceQuerySet[ModelT], enabling proper type inference for custom QuerySet subclasses.

  2. InheritanceManagerMixin now accepts an optional QuerySetT type parameter (defaults to InheritanceQuerySet[ModelT]) for specifying custom QuerySet types.

Problem

Currently, when subclassing InheritanceQuerySetMixin or InheritanceManagerMixin, methods return fixed types that don't match the actual subclass:

class MyQuerySet(InheritanceQuerySetMixin[MyModel], QuerySet[MyModel]):
    def my_filter(self) -> Self:
        return self.annotate(foo=Value(1))  # Error: returns InheritanceQuerySet[MyModel], not Self

class MyManager(InheritanceManager[MyModel]):
    def get_queryset(self) -> MyQuerySet:  # Error: incompatible return type
        return MyQuerySet(self.model)

Solution

1. InheritanceQuerySetMixin returns Self

class InheritanceQuerySetMixin(Generic[ModelT]):
    def select_subclasses(self, ...) -> Self: ...
    def _chain(self, ...) -> Self: ...
    def _clone(self) -> Self: ...
    def annotate(self, ...) -> Self: ...

2. InheritanceManagerMixin accepts QuerySetT

QuerySetT = TypeVar('QuerySetT', bound='InheritanceQuerySet[Any]', default='InheritanceQuerySet[ModelT]')

class InheritanceManagerMixin(Generic[ModelT, QuerySetT]):
    def get_queryset(self) -> QuerySetT: ...
    def select_subclasses(self, ...) -> QuerySetT: ...
    def instance_of(self, ...) -> QuerySetT: ...

Usage Examples

# Custom QuerySet with Self return type works correctly
class MyQuerySet(InheritanceQuerySetMixin[MyModel], QuerySet[MyModel]):
    def my_filter(self) -> Self:
        return self.annotate(foo=Value(1))  # OK!

# Existing code works unchanged (backwards compatible)
class MyManager(InheritanceManager[MyModel]):
    pass  # get_queryset() returns InheritanceQuerySet[MyModel]

# New: specify custom QuerySet type
class MyManager(InheritanceManager[MyModel, MyQuerySet]):
    _queryset_class = MyQuerySet

    def get_queryset(self) -> MyQuerySet:  # OK!
        return MyQuerySet(self.model, using=self._db)

Changes

  • Add Self import (from typing for Python 3.11+, typing_extensions otherwise)
  • Add QuerySetT TypeVar with default=InheritanceQuerySet[ModelT]
  • Update InheritanceQuerySetMixin methods to return Self:
    • select_subclasses()
    • _chain()
    • _clone()
    • annotate()
  • Update InheritanceQuerySet.instance_of() to return Self
  • Update InheritanceManagerMixin to be Generic[ModelT, QuerySetT]
  • Add typing_extensions>=4.0.0 dependency for Python < 3.13

Backwards Compatibility

✅ Fully backwards compatible:

  • Existing InheritanceManager[MyModel] code works unchanged
  • QuerySetT defaults to InheritanceQuerySet[ModelT] when not specified

Test Plan

  • Existing tests pass
  • mypy type checking passes
  • Backwards compatibility verified

Closes #651

…ce classes

This PR improves type safety for InheritanceQuerySetMixin and
InheritanceManagerMixin by:

1. InheritanceQuerySetMixin methods now return Self instead of
   InheritanceQuerySet[ModelT], enabling proper type inference
   for custom QuerySet subclasses.

2. InheritanceManagerMixin now accepts an optional QuerySetT type
   parameter (defaults to InheritanceQuerySet[ModelT]) for specifying
   custom QuerySet types.

Changes:
- Add Self import (typing for 3.11+, typing_extensions otherwise)
- Add QuerySetT TypeVar with default=InheritanceQuerySet[ModelT]
- Update InheritanceQuerySetMixin methods to return Self:
  - select_subclasses()
  - _chain()
  - _clone()
  - annotate()
- Update InheritanceQuerySet.instance_of() to return Self
- Update InheritanceManagerMixin to be Generic[ModelT, QuerySetT]
- Add typing_extensions dependency for Python < 3.13

Backwards compatibility:
- Existing InheritanceManager[MyModel] code works unchanged
- QuerySetT defaults to InheritanceQuerySet[ModelT] when not specified

Closes jazzband#651
@lee3jjang lee3jjang force-pushed the fix/inheritance-manager-mixin-queryset-type-parameter branch from e4287d5 to 8020a61 Compare January 20, 2026 05:20
@lee3jjang lee3jjang changed the title feat: Add QuerySetT type parameter to InheritanceManagerMixin feat: Add Self return type and QuerySetT type parameter for inheritance classes Jan 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

InheritanceQuerySetMixin methods should return Self instead of InheritanceQuerySet[ModelT]

1 participant