Skip to content

django: custom UUID primary key on AbstractBaseUser subclass causes false positive bad-override-mutable-attribute #4596

Description

@dhitalkamal

Description

pyrefly reports bad-override-mutable-attribute when a custom Django user model overrides AbstractBaseUser's (and PermissionsMixin's) implicit id field with a UUIDField. This is a very common, standard Django pattern (every Django project wanting UUID primary keys on its user model does this) and pyrefly's own docs say custom primary keys are correctly type-inferred - the false positive is specifically in the override-consistency check against the abstract base classes, not in resolving the concrete field's own type.

Reproduction

import uuid
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db import models


class User(AbstractBaseUser, PermissionsMixin):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

Actual behavior

ERROR Class member `User.id` overrides parent class `AbstractBaseUser` in an inconsistent manner [bad-override-mutable-attribute]
    |
    |     id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    |          ^^
    |
  `User.id` has type `UUID`, which is not consistent with `int` in `AbstractBaseUser.id` (the type of read-write attributes cannot be changed)

ERROR Class member `User.id` overrides parent class `PermissionsMixin` in an inconsistent manner [bad-override-mutable-attribute]

Expected behavior

No error. Per your own docs (https://pyrefly.org/en/docs/django/): "if you define a field with primary_key=True, Django will not add the id field" - the framework's own actual runtime semantics never give AbstractBaseUser/PermissionsMixin an independent int id at all; only the final concrete model has a real pk, and it's the one this class declares.

Why this is a false positive

AbstractBaseUser/PermissionsMixin are abstract base classes (class Meta: abstract = True in Django terms). Django's implicit id = AutoField(primary_key=True) injection only ever applies to the final concrete model in an inheritance chain if nothing in that chain declares its own pk - it is not a real, independent field that exists on the abstract parent classes themselves. pyrefly appears to synthesize an implicit int id specifically on the abstract base classes (since they don't declare a pk field individually), then flags the concrete subclass's real, intentional UUIDField(primary_key=True) as an incompatible override of that synthesized-but-never-actually-instantiated attribute.

I tried the officially-suggested approach of explicitly parameterizing the field's generic type (id: models.UUIDField[uuid.UUID | str, uuid.UUID] = models.UUIDField(...)), which did not resolve it either (tested on pyrefly 1.2.0).

Real-world impact

Affects any Django project using a custom AUTH_USER_MODEL with a non-integer (UUID, ULID, etc.) primary key while inheriting from AbstractBaseUser/PermissionsMixin - a widely-recommended pattern for new Django projects, especially anything exposing IDs over an API.

Workaround

Can suppress with bad-override-mutable-attribute = "ignore" in [tool.pyrefly.errors], but that's a blanket suppression that would also hide genuine mutable-attribute override bugs elsewhere in the codebase.

Environment

  • pyrefly version: 1.2.0
  • django-stubs version: 6.1.0
  • Python: 3.14

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions