Skip to content

[Help wanted / bug ?] Trying to create UserModelMixins and ModelMixins; trouble typing manager. #1414

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
PedroPerpetua opened this issue Mar 26, 2023 · 0 comments

Comments

@PedroPerpetua
Copy link

I've tried the Gitter help link, but it appears to not be an active place (I left a question there a couple months ago and it never got an answer - and there's only about 5 more messages since then), so I'm trying my luck here.

For boilerplate purposes, I'm trying to provide multiple model Mixins, as well as a base abstract user model to be used in projects. This abstract user model should implement one of these Mixins (SoftDeleteMixin); this mixin implements it's own custom manager. That appears to be the start of the mess, as I can't make the types coherent.

Here are some snippets of the models and what I'm trying to achieve:

# The mixin manager
SoftDeleteModel = TypeVar("SoftDeleteModel", bound=SoftDeleteMixin)
class SoftDeleteManager(models.Manager[SoftDeleteModel]):
    def get_queryset(self) -> models.QuerySet[SoftDeleteModel]: ...
    def include_deleted(self) -> models.QuerySet[SoftDeleteModel]: ...

# The mixin
class SoftDeleteMixin(models.Model):
     objects = SoftDeleteManager[Self]()

# The manager for the users - inherits from the other manager because the GenericUser type will subclass SoftDeleteMixin
GenericUser = TypeVar("GenericUser", bound=BaseAbstractUser)
class BaseUserManager(SoftDeleteManager[GenericUser], DjangoBaseUserManager[GenericUser]):
    def create_user(self, password: str, **fields: Any) -> GenericUser: ...
    def create_superuser(self, password: str, **fields: Any) -> GenericUser: ...

# The abstract user model
class BaseAbstractUser(PermissionsMixin, SoftDeleteMixin, DjangoAbstractBaseUser):
    objects = BaseUserManager[Self]()  # By default uses the base manager

Now this is generating loads of errors with mypy:

  • Incompatible types in assignment (expression has type "BaseUserManager[BaseAbstractUser]", base class "SoftDeleteMixin" defined the type as "SoftDeleteManager[SoftDeleteMixin]"
  • Subclasses have incompatible return type in the objects property; for example:
class UserModel(BaseAbstractUser): ...
instance = UserModel.objects.create(...)  # instance will evaluate to BaseAbstractUser instead of UserModel
# Notably, Pylance on VSCode appears to correctly identify that `create` returns a UserModel, which seems weird to me.

Is this some sort of bug, or am I doing some kind of "anti-pattern" here, typing things incorrectly, etc? I've tried multiple changes with Generics and all that and always end up with some kind of issue in the end. Help would be greatly appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant