facebook / facebook/pyrefly

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

Open
#4,596 0 comments 0 reactions 0 assignees View on GitHub
django typechecking
Dominant language
Rust
Stars
7k
Forks
516
PR merge metrics
No merged PRs in 30d

Description

## 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

```python
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

Contributor guide

Open the contributing guide

Research direction

Start with the Django reproduction in the issue and the documented custom-primary-key behavior. Trace the override-consistency check for the synthesized `id` on `AbstractBaseUser` and `PermissionsMixin`, then verify the fix against the shown output. Done means the UUID primary-key model reports no false-positive override errors while genuine mutable-attribute overrides remain checked.

Written by the indexing model from the issue text.

Assessment

Tech stack
django, python
Domain
authentication, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.