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
Description
pyrefly reports
bad-override-mutable-attributewhen a custom Django user model overridesAbstractBaseUser's (andPermissionsMixin's) implicitidfield with aUUIDField. 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
Actual behavior
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/PermissionsMixinan independentintid 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/PermissionsMixinare abstract base classes (class Meta: abstract = Truein Django terms). Django's implicitid = 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 implicitintid specifically on the abstract base classes (since they don't declare a pk field individually), then flags the concrete subclass's real, intentionalUUIDField(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_MODELwith a non-integer (UUID, ULID, etc.) primary key while inheriting fromAbstractBaseUser/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