6

I have an existing Django project which uses a custom User model class that extends AbstractUser. For various important reasons, we need to redefine the email field as follows:

class User(AbstractUser):
    ...
    email = models.EmailField(db_index=True, blank=True, null=True, unique=True)
    ...

Typing checks via mypy have been recently added. However, when I perform the mypy check, I get the following error:

error: Incompatible types in assignment (expression has type "EmailField[str | int | Combinable | None, str | None]", base class "AbstractUser" defined the type as "EmailField[str | int | Combinable, str]") [assignment]

How can I make it so that mypy allows this type reassignment? I don't wish to just use # type: ignore because I wish to use its type protections.

For context, if I do use # type: ignore, then I get dozens of instances of the following mypy error instead from all over my codebase:

error: Cannot determine type of "email" [has-type]

Here are details of my setup:

python version: 3.10.5
django version: 3.2.19
mypy version: 1.6.1
django-stubs[compatible-mypy] version: 4.2.6
django-stubs-ext version: 4.2.5
typing-extensions version: 4.8.0
4
  • 1
    Note that such a change breaks the Liskov Substitution Principle. Commented Nov 24, 2023 at 17:47
  • 3
    @PawełRubin Agreed, 100% —in this specific case though, we broke the Liskov principle very intentionally, as it saves a tremendous amount of engineering, and we’re using no other User-like models. Commented Nov 24, 2023 at 18:37
  • 1
    If your email field fulfills the contract, what about casting it to the type required in the abstract base class? (email = cast(EmailField[str | int | Combinable, str], models.EmailField(db_index=True, blank=True, null=True, unique=True))) Commented Nov 24, 2023 at 20:03
  • @erny But user.email needs to be allowed to be None for some objects under our custom User model Commented Nov 24, 2023 at 22:43

2 Answers 2

1

One option is to change the super class from AbstractUser to AbstractBaseUser. This would allow to to more easily over-write the specific properties you want to change and give you more flexibility in the long run, at the cost of some extra boilerplate.

class User(AbstractBaseUser, PermissionsMixin):
   ...
   email = models.EmailField(db_index=True, blank=True, null=True, unique=True)
   ...

Some extra info: https://testdriven.io/blog/django-custom-user-model/

Sign up to request clarification or add additional context in comments.

Comments

1
+25

One option is to overwrite the django stubs to be compatible with your user model.

Create a stub file (.pyi) for django and override the type for AbstractUser in there, see here.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.