Skip to content

Resolve objects through the metaclass and ban manager access on instances - #3593

Open
UnknownPlatypus wants to merge 6 commits into
typeddjango:masterfrom
UnknownPlatypus:improve-objects-access
Open

Resolve objects through the metaclass and ban manager access on instances#3593
UnknownPlatypus wants to merge 6 commits into
typeddjango:masterfrom
UnknownPlatypus:improve-objects-access

Conversation

@UnknownPlatypus

@UnknownPlatypus UnknownPlatypus commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

I have made things!

This fixes two issues around Model.objects:

  • MyModel().objects was allowed while it's a runtime error -> I've used explicit __get__(self, instance: None) to solve that
  • explicit objects = BookManager() are not overriding our stub-defined type for pyright/pyrefly so objects was a Manager[Book] and incorrect. -> I think I found a way to have our objects annotation to act as a true fallback using __getattr__ on the ModelBase metaclass. By nature getattr is always checked last so now it's correct for every type checker!

I had to workaround two mypy issues that made this a bit more difficult but in the end it seem to work:

Related issues

From #3559

This also revealed that our typing of Model.objects as ClassVar[Manager[Self]] is already a bit problematic with pyright and pyrefly (see the type-ignore's). Given this exemple:

class Book(models.Model):
    objects = BookManager()
    foo = BookManager()

I get these revealed types

┌─────────────┬───────────────┬─────────────┐
│             │ Book.objects  │  Book.foo   │
├─────────────┼───────────────┼─────────────┤
│ mypy+plugin │ BookManager   │ BookManager │
├─────────────┼───────────────┼─────────────┤
│ ty          │ BookManager   │ BookManager │
├─────────────┼───────────────┼─────────────┤
│ pyright     │ Manager[Book] │ BookManager │
├─────────────┼───────────────┼─────────────┤
│ pyrefly     │ Manager[Book] │ BookManager │
└─────────────┴───────────────┴─────────────┘

To me our declaration in the stubs should not impact regular class level assignment inference and I would expect objects to have the same type as foo. The plugin should only apply the "does not exist on abstract model" bit, the rest should be regular inference.

Fixes #174
Fixes #1414
Also improves #579

AI Policy

  • I have read and agree to the AI Policy, removed any "Co-Authored-By" lines attributing coding agents, and manually reviewed the final result

…tances

Declaring `objects` on `Model` overrode class-level inference, so `objects = BookManager()`
resolved as `Manager[Book]` outside mypy. A `ModelBase.__getattr__` fallback leaves the
assignment alone; mypy ignores its `Literal` restriction, so the plugin drops it.

Reverse accessors annotated as `x_set: Manager[Book]` must become `RelatedManager[Book]`.

^ Conflicts:
^	django-stubs/db/models/base.pyi
^	django-stubs/db/models/manager.pyi
@github-actions

This comment has been minimized.

Comment thread pyproject.toml
@github-actions

Copy link
Copy Markdown
Contributor

Diff from mypy_primer, showing the effect of this PR on type check results on a corpus of open source code:

zulip (https://github.com/zulip/zulip)
+ zerver/models/users.py:696: error: Need type annotation for "objects"  [var-annotated]
- zerver/models/users.py:1165: error: Argument 1 to "list" has incompatible type "QuerySet[UserProfile, dict[str, Any]]"; expected "Iterable[RawUserDict]"  [arg-type]
+ zerver/models/users.py:1165: error: Argument 1 to "list" has incompatible type "QuerySet[Any, dict[str, Any]]"; expected "Iterable[RawUserDict]"  [arg-type]
- zerver/models/users.py:1184: error: Argument 1 to "list" has incompatible type "QuerySet[UserProfile, dict[str, Any]]"; expected "Iterable[RawUserDict]"  [arg-type]
+ zerver/models/users.py:1184: error: Argument 1 to "list" has incompatible type "QuerySet[Any, dict[str, Any]]"; expected "Iterable[RawUserDict]"  [arg-type]
- zerver/models/users.py:1194: error: Argument 1 to "list" has incompatible type "QuerySet[UserProfile, dict[str, Any]]"; expected "Iterable[RawUserDict]"  [arg-type]
+ zerver/models/users.py:1194: error: Argument 1 to "list" has incompatible type "QuerySet[Any, dict[str, Any]]"; expected "Iterable[RawUserDict]"  [arg-type]
- zerver/lib/display_recipient.py:67: error: Argument 1 to "list" has incompatible type "QuerySet[UserProfile, dict[str, Any]]"; expected "Iterable[UserDisplayRecipient]"  [arg-type]
+ zerver/lib/display_recipient.py:67: error: Argument 1 to "list" has incompatible type "QuerySet[Any, dict[str, Any]]"; expected "Iterable[UserDisplayRecipient]"  [arg-type]
- zerver/lib/display_recipient.py:83: error: Argument 1 to "list" has incompatible type "QuerySet[UserProfile, dict[str, Any]]"; expected "Iterable[UserDisplayRecipient]"  [arg-type]
+ zerver/lib/display_recipient.py:83: error: Argument 1 to "list" has incompatible type "QuerySet[Any, dict[str, Any]]"; expected "Iterable[UserDisplayRecipient]"  [arg-type]
- zerver/actions/users.py:918: error: Incompatible types in assignment (expression has type "QuerySet[UserProfile, dict[str, Any]]", variable has type "list[dict[str, Any]]")  [assignment]
+ zerver/actions/users.py:918: error: Incompatible types in assignment (expression has type "QuerySet[Any, dict[str, Any]]", variable has type "list[dict[str, Any]]")  [assignment]
- zerver/tests/test_users.py:1846: error: "UserProfile" has no attribute "realm_id"; maybe "realm"?  [attr-defined]
- zerver/tests/test_bots.py:639: error: "UserProfile" has no attribute "bot_owner_id"  [attr-defined]
- zerver/tests/test_bots.py:2212: error: "UserProfile" has no attribute "bot_owner_id"  [attr-defined]

Comment thread mypy_django_plugin/transformers/models.py

@sobolevn sobolevn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks solid! Thanks a lot, this is much better now!

@UnknownPlatypus

Copy link
Copy Markdown
Contributor Author

Should we apply a similar pattern to our defined manager ? For ex on the User model we still use classvar

@sobolevn

Copy link
Copy Markdown
Member

We should unify all type-checker results as much as possible :)

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

[Help wanted / bug ?] Trying to create UserModelMixins and ModelMixins; trouble typing manager. Incompatible types in assignment with User subclass

2 participants