Skip to content

Use PEP 696 for model fields#3317

Open
UnknownPlatypus wants to merge 20 commits intotypeddjango:masterfrom
UnknownPlatypus:pep696-field-typing-skip-null
Open

Use PEP 696 for model fields#3317
UnknownPlatypus wants to merge 20 commits intotypeddjango:masterfrom
UnknownPlatypus:pep696-field-typing-skip-null

Conversation

@UnknownPlatypus
Copy link
Copy Markdown
Contributor

@UnknownPlatypus UnknownPlatypus commented Apr 18, 2026

I have made things!

Replace _pyi_private_set_type / _pyi_private_get_type attributes on field stubs with PEP 696 TypeVar defaults. This makes field set/get types visible to all type checkers, not just mypy with the plugin.

This is a big PR, sry for that but the plugin code is so entangled I did not manage to make a smaller chunk.
I've rebased to make it a bit more digestible, hopefully that helps.

I'm pretty convinced we can improve the stubs of ForeignKey/ManyToMany and friends using trick similar to what is done to ArrayField in this PR and remove even more plugin code.
Most of the code in transform_into_proper_return_type should go I think.

Related issues

Fixes #766
Fixes #1264
Fixes #1900
Fixes #2590
Fixes #2724

Improves #579

Handling null=True

The important part is to make null working (and later primary_key). Two approches have emerged in the other issue discussion, I've investigated further with various type checker and was able to make the _NT typevar approach work with every typechecker I know off!

Using __new__ overloads (django-types is doing that)

Code snippet

from typing import TypeVar, Literal, Generic, overload, reveal_type


_ST = TypeVar("_ST")
_GT = TypeVar("_GT")

class Model:
    pass


class Field(Generic[_ST, _GT]):
    def __init__(self, *, null: bool = False) -> None:
        super().__init__()


_ST_1 = TypeVar("_ST_1", default=int)
_GT_1 = TypeVar("_GT_1", default=int)


class IntegerFieldSimple(Field[_ST_1, _GT_1]): ...

class IntegerFieldWithOverloads(Field[_ST_1, _GT_1]):
    @overload
    def __new__(
        cls, *, null: Literal[True]
    ) -> IntegerFieldWithOverloads[_ST_1 | None, _GT_1 | None]: ...

    @overload
    def __new__(
        cls, *, null: Literal[False] = False
    ) -> IntegerFieldWithOverloads[_ST_1, _GT_1]: ...

    def __new__(   # type: ignore[misc]
        cls, *, null: bool = False
    ) -> (
        IntegerFieldWithOverloads[_ST_1, _GT_1]
        | IntegerFieldWithOverloads[_ST_1 | None, _GT_1 | None]
    ):
        return super().__new__(cls)


class A(Model):
    field1 = IntegerFieldSimple(null=False)
    field2 = IntegerFieldSimple(null=True)
    field3 = IntegerFieldWithOverloads(null=False)
    field4 = IntegerFieldWithOverloads(null=True)

a = A()
reveal_type(a.field1) # Revealed type is "builtins.int"
reveal_type(a.field2) # Revealed type is "Union[builtins.int, None]"
reveal_type(a.field3) # Revealed type is "builtins.int"
reveal_type(a.field4) # Revealed type is "Union[builtins.int, None]"

Works with:

  • ty -> 🟠 Work with explicit __new__ overrides on every custom field (and need to be quoted)
  • pyright -> 🟠 Work with explicit __new__ overrides on every custom field
    Not with:
  • pyrefly -> 🔴 Not working
  • mypy -> 🟠 Work with explicit __new__ overrides on every custom field
  • zuban -> 🟠 Work with explicit __new__ overrides on every custom field, (but a false positive error on the __new__ definition)

Using third type variable _NT to Field encoding nullability as Literal[True] or Literal[False]

Code snippet

from typing import TypeVar, Literal, Generic, overload, Any, reveal_type, Self, Never

_ST = TypeVar("_ST", contravariant=True)
_GT = TypeVar("_GT", covariant=True)
_NT = TypeVar("_NT", Literal[True], Literal[False], default=Literal[False])

class Model:
    pass

class Field(Generic[_ST, _GT, _NT]):
    def __init__(self, null: _NT) -> None: ...
    @overload
    def __get__(self: Field[Any, Any, Literal[False]], instance: Any, owner: Any) -> _GT: ...
    @overload
    def __get__(self: Field[Any, Any, Literal[True]], instance: Any, owner: Any) -> _GT | None: ...
    def __get__(self, instance: Any, owner: Any) -> _GT | None:
        pass

_ST_1 = TypeVar("_ST_1", bound=int, default=int)
_GT_1 = TypeVar("_GT_1", bound=int, default=int)

class IntegerFieldSimple(Field[_ST_1, _GT_1, _NT]): ...

class IntegerFieldWithOverload(Field[_ST_1, _GT_1, _NT]):
    @overload
    def __get__(self: IntegerFieldWithOverload[Any, Any, Literal[False]], instance: Any, owner: Any) -> _GT_1: ...
    @overload
    def __get__(self: IntegerFieldWithOverload[Any, Any, Literal[True]], instance: Any, owner: Any) -> _GT_1 | None: ...
    def __get__(self, instance: Any, owner: Any) -> _GT_1 | None:
        pass

class A(Model):
    field1 = IntegerFieldSimple(null=False)
    field2 = IntegerFieldSimple(null=True)
    field3 = IntegerFieldWithOverload(null=False)
    field4 = IntegerFieldWithOverload(null=True)

a = A()
reveal_type(a.field1) # Revealed type is "int"
reveal_type(a.field2) # Revealed type is "int | None"
reveal_type(a.field3) # Revealed type is "int"
reveal_type(a.field4) # Revealed type is "int | None"

Works with all type-checkers:

  • pyright -> 🟢 Works (but false positive on the explicit override case)
    ---> todo
  • mypy 1.16+ -> 🟢 Works
  • pyrefly -> 🟢 Works
  • ty -> 🟢 Works (but need to be quoted)
  • zuban -> 🟢 Works

Custom field typing

Currently, bare custom field (class HTMLField(models.TextField): ...) are an issue because the TypeVar default machinery makes body_nullable = HTMLField(null=True) resolve to TextField[str, str, Literal[False]], causing this kind of errors

Argument "null" to "HTMLField" has incompatible type "Literal[True]"; expected "Literal[False]"

The mypy plugin currently makes this work by reparametrizing HTMLField to remain generic in _ST_Text, _GT_Text, _NT, so mypy's natural inference picks up _NT = Literal[True] from the call. But other typecheckers cannot do that so it means that for them, they have to explicitely fix their custom fields
following the recommendation in the README ie

from typing_extensions import TypeVar, Literal
from django.db import models
from django.db.models.expressions import Combinable

_ST_Text = TypeVar("_ST_Text", contravariant=True, default=str | int | Combinable)
_GT_Text = TypeVar("_GT_Text", covariant=True, default=str)
_NT = TypeVar("_NT", Literal[True], Literal[False], default=Literal[False])

class HtmlField(models.TextField[_ST_Text, _GT_Text, _NT]):
    pass

That is probably something we can live with because the workaround is documented (and I say workaround, it's more of a regular typing pattern, omitting generics is often an issue and flagged in strict modes)

Alternative I considered

Drop default=Literal[False] from _NT

his Violates PEP 696 ordering (no-default TypeVar can't follow defaulted ones) so we have to either:
- ❌ reorder typevars to Generic[_NT, _ST, _GT] which would completely change the meaning of existing Field[X, Y] annotations, it feels bad
- drop the _ST/_GT defaults we just added and inline the defaults. This would make custom field subclassing anything other than Field very difficult as the _GT/_ST are not reachable anymore. People would have to redeclare the whole __get__ / __set__ machinery which seems bad. It also requires that we do null: _NT = False to not regress Bare instantiation ( models.IntegerField() -> _NT=Literal[False]) but this only works with pyright and pyrefly.
Passing a default value to a TypeVar is not supported by mypy:

  • mypy, we can add a type ignore and the inferrence still works
  • ty, even with a atype ignore, the inference break and gives Unkown

_NT = TypeVar("_NT", bound=bool, default=Any)

But this breaks narrowing to _GT or _GT | None based on null=... because Literal[False] / Literal[True] become bool which is a blocker

Followups

  • Upstream pyrefly issue for this false positive
  • upstream ty issue for # TODO: ty should reject that too
  • Support Fk/OneToOne/m2m with minimal plugin work

@github-actions

This comment has been minimized.

Comment thread django-stubs/contrib/gis/db/models/fields.pyi Outdated
@github-actions

This comment has been minimized.

@UnknownPlatypus UnknownPlatypus force-pushed the pep696-field-typing-skip-null branch from 329fab2 to 5d6b276 Compare April 18, 2026 12:04
@github-actions

This comment has been minimized.

@ahmedasar00
Copy link
Copy Markdown
Contributor

Great work on this! It’s really impressive.!
Are there any specific tasks I can help with to move this forward?

@UnknownPlatypus

This comment was marked as outdated.

@UnknownPlatypus UnknownPlatypus force-pushed the pep696-field-typing-skip-null branch from 5d6b276 to b8c5edb Compare April 18, 2026 22:28
@github-actions

This comment has been minimized.

1 similar comment
@github-actions

This comment has been minimized.

@UnknownPlatypus
Copy link
Copy Markdown
Contributor Author

@sobolevn A bunch of issues in mypy_primer are because it does not load the plugin, hence the nullable part is bogus and we got false positives we did not have before when it was Any.

I'm gonna be expanding the scope of this pr to do the Using third type variable _NT to Field encoding nullability as Literal[True] or Literal[False] I think

@ahmedasar00

This comment was marked as outdated.

@ngnpope
Copy link
Copy Markdown
Member

ngnpope commented Apr 20, 2026

Still trying to remove unnecessary plugin code and need to test on my work repo (1.5M Loc) for regressions. I think it breaks some parts that are not tested well enough for now.

Not much to do to help I think but thanks for the proposal. When I'll make this ready for review, testing on external codebases would be valuable

We will be able to test out on a 16M LoC codebase at work when you're ready. (FYI @delfick)

@delfick
Copy link
Copy Markdown
Contributor

delfick commented Apr 20, 2026

well, we only run mypy on 5.9 million lines of that (excluding blank lines and comments), but yes, our codebase is often a rich source of edge cases!

@ngnpope
Copy link
Copy Markdown
Member

ngnpope commented Apr 20, 2026

Shh! You make it sound less impressive 😂

@ahmedasar00

This comment was marked as resolved.

@github-actions

This comment has been minimized.

@UnknownPlatypus UnknownPlatypus force-pushed the pep696-field-typing-skip-null branch from 8f638a5 to ebae746 Compare May 1, 2026 21:30
@github-actions

This comment has been minimized.

@UnknownPlatypus UnknownPlatypus force-pushed the pep696-field-typing-skip-null branch 2 times, most recently from 435477f to 91b7025 Compare May 2, 2026 10:36
@github-actions

This comment has been minimized.

1 similar comment
@github-actions

This comment has been minimized.

@UnknownPlatypus UnknownPlatypus force-pushed the pep696-field-typing-skip-null branch from 91b7025 to c0676f8 Compare May 2, 2026 19:10
@UnknownPlatypus
Copy link
Copy Markdown
Contributor Author

cc @delfick, this should be testable now

thanks @ahmedasar00 for the help on mypy_primer review!

Comment thread tests/assert_type/db/models/fields/test_nullable.py
Comment thread tests/assert_type/db/models/fields/test_related.py Outdated
Comment thread django-stubs/db/models/fields/related.pyi
@UnknownPlatypus UnknownPlatypus force-pushed the pep696-field-typing-skip-null branch from c0676f8 to b498992 Compare May 3, 2026 21:48
@github-actions

This comment has been minimized.

Comment on lines +274 to +288
def nullable_field_subclass_without_explicit_type_vars() -> None:
"""
We auto add typevars in mypy plugin which avoid issues here.

TODO: False positive pyrefly/ty/pyright
"""

class HTMLField(models.TextField): ...

class IntWrap(models.IntegerField): ...

class Article(models.Model):
body = HTMLField()
body_nullable = HTMLField(null=True) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] # ty: ignore[invalid-argument-type]
count_nullable = IntWrap(null=True) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] # ty: ignore[invalid-argument-type]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Currently, bare custom field are an issue because the TypeVar default machinery makes body_nullable = HTMLField(null=True) resolve to TextField[str, str, Literal[False]], causing this kind of errors

Argument "null" to "HTMLField" has incompatible type "Literal[True]"; expected "Literal[False]"

The mypy plugin currently makes this work by reparametrizing HTMLField to remain generic in _ST_Text, _GT_Text, _NT, so mypy's natural inference picks up _NT = Literal[True] from the call.

But other typecheckers cannot do that so it means that for them, they have to explicitely declare the typevar for their custom fields like in the test above (nullable_subclass_inherits_null_overload)

That is probably something we can live with because the workaround is documented (and I say workaround, it's more of a regular typing pattern, omitting generics is often an issue and flagged in strict modes)

I've updated the README to reflect that and guide users in this specifc case.

Alternative I considered:

Drop default=Literal[False] from _NT

This Violates PEP 696 ordering (no-default TypeVar can't follow defaulted ones) so we have to either:
- ❌ reorder typevars to Generic[_NT, _ST, _GT] which would completely change the meaning of existing Field[X, Y] annotations, it feels bad
- drop the _ST/_GT defaults we just added and inline the defaults. This would make custom field subclassing anything other than Field very difficult as the _GT/_ST are not reachable anymore. People would have to redeclare the whole __get__ / __set__ machinery which seems bad. It also requires that we do null: _NT = False to not regress Bare instantiation ( models.IntegerField() -> _NT=Literal[False]) but this only works with pyright and pyrefly.
Passing a default value to a TypeVar is not supported by:

However, mypy still infer the correct thing on usage so a type ignore on null: _NT = False does the trick, but it would still be an error for ty.
We can maybe ne consider it later if support is added in ty ?

_NT = TypeVar("_NT", bound=bool, default=Any)

But this breaks narrowing to _GT or _GT | None based on null=... because Literal[False] / Literal[True] become bool which is a blocker

Copy link
Copy Markdown
Member

@sobolevn sobolevn left a comment

Choose a reason for hiding this comment

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

Great work! I really want to be sure that old Field[_ST, _GT] annotations will continue to work as before, plus several small nitpicks :)

_NT = TypeVar("_NT", Literal[True], Literal[False], default=Literal[False])

class Field(RegisterLookupMixin, Generic[_ST, _GT]):
class Field(RegisterLookupMixin, Generic[_ST, _GT, _NT]):
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.

Do we have a test that Field[_ST, _GT] is still allowed? We must be sure that people who used it before this change will have the same experience.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've added a test for this and need to fix it for mypy in the plugin. However for the other type checkers it's the same story as above, it cannot work without writing all the TypeVar explicitely I think

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.

What about existing annotations for some custom fields: field: Field[A, B] = CustomField()?

Comment thread django-stubs/db/models/fields/__init__.pyi
Comment thread django-stubs/db/models/fields/__init__.pyi
Comment thread django-stubs/db/models/fields/json.pyi
Comment thread django-stubs/db/models/fields/related.pyi
@UnknownPlatypus
Copy link
Copy Markdown
Contributor Author

UnknownPlatypus commented May 4, 2026

mypy primer diff look mostly great, just noticed a pre-existing issue with db_default

class AbstractMessage(models.Model):
    class MessageType(models.IntegerChoices):
        NORMAL = 1
        RESOLVE_TOPIC_NOTIFICATION = 2

    type = models.PositiveSmallIntegerField(
        choices=MessageType.choices,
        default=MessageType.NORMAL,
        # Note: db_default is a new feature in Django 5.0, so we don't use
        # it across the codebase yet. It's useful here to simplify the
        # associated database migration, so we're making use of it.
        db_default=MessageType.NORMAL,
    )
- zerver/actions/message_send.py:1872: error: Incompatible types in assignment (expression has type "int", variable has type "MessageType")  [assignment]
+ zerver/actions/message_send.py:1872: error: No overload variant of "__set__" of "Field" matches argument types "Message", "int"  [call-overload]
+ zerver/actions/message_send.py:1872: note: Possible overload variants:
+ zerver/actions/message_send.py:1872: note:     def __set__(self, instance: Any, value: MessageType | Combinable) -> None

The problem is that because _ST is contravariant and part of the db_default type, the instantiation narrow _ST=MessageType which then reject int while it was a valid choice before

Not sure how to handle this but I don't think it should be a blocker for this, this is a preexisting issue I believe.
I can open a new issue for it, maybe just duplicating the type union we use in _ST default and using it in db_default would be ok ?

Similarly, this causes issue now:

  class M(models.Model):                                                                                                                                        
      a = models.JSONField(default=dict, db_default={})  # error: Need type annotation for "a" 

mypy tries to bind _ST_JSON from the empty-dict literal in the union, fails to commit to a type, and the field is left unparameterized. I've changed this one to Any since we don't loose type strictness versus before but doing so in other places would be a regression.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

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:62: error: Need type annotation for "enter_sends"  [var-annotated]
- zerver/models/users.py:68: error: Need type annotation for "left_side_userlist"  [var-annotated]
- zerver/models/users.py:69: error: Need type annotation for "default_language"  [var-annotated]
- zerver/models/users.py:72: error: Need type annotation for "web_home_view"  [var-annotated]
- zerver/models/users.py:73: error: Need type annotation for "web_escape_navigates_to_home_view"  [var-annotated]
- zerver/models/users.py:74: error: Need type annotation for "fluid_layout_width"  [var-annotated]
- zerver/models/users.py:75: error: Need type annotation for "high_contrast_mode"  [var-annotated]
- zerver/models/users.py:76: error: Need type annotation for "translate_emoticons"  [var-annotated]
- zerver/models/users.py:77: error: Need type annotation for "display_emoji_reaction_users"  [var-annotated]
- zerver/models/users.py:78: error: Need type annotation for "twenty_four_hour_time"  [var-annotated]
- zerver/models/users.py:79: error: Need type annotation for "starred_message_counts"  [var-annotated]
- zerver/models/users.py:80: error: Need type annotation for "web_suggest_update_timezone"  [var-annotated]
- zerver/models/users.py:85: error: Need type annotation for "color_scheme"  [var-annotated]
- zerver/models/users.py:93: error: Need type annotation for "web_font_size_px"  [var-annotated]
- zerver/models/users.py:94: error: Need type annotation for "web_line_height_percent"  [var-annotated]
- zerver/models/users.py:99: error: Need type annotation for "web_animate_image_previews"  [var-annotated]
- zerver/models/users.py:113: error: Need type annotation for "demote_inactive_streams"  [var-annotated]
- zerver/models/users.py:117: error: Need type annotation for "web_left_sidebar_show_channel_folders"  [var-annotated]
- zerver/models/users.py:132: error: Need type annotation for "web_mark_read_on_scroll_policy"  [var-annotated]
- zerver/models/users.py:150: error: Need type annotation for "web_channel_default_view"  [var-annotated]
- zerver/models/users.py:164: error: Need type annotation for "emojiset"  [var-annotated]
- zerver/models/users.py:175: error: Need type annotation for "user_list_style"  [var-annotated]
- zerver/models/users.py:186: error: Need type annotation for "web_stream_unreads_count_display_policy"  [var-annotated]
- zerver/models/users.py:190: error: Need type annotation for "web_left_sidebar_unreads_count_summary"  [var-annotated]
- zerver/models/users.py:194: error: Need type annotation for "web_navigate_to_sent_message"  [var-annotated]
- zerver/models/users.py:198: error: Need type annotation for "email_notifications_batching_period_seconds"  [var-annotated]
- zerver/models/users.py:201: error: Need type annotation for "enable_stream_desktop_notifications"  [var-annotated]
- zerver/models/users.py:202: error: Need type annotation for "enable_stream_email_notifications"  [var-annotated]
- zerver/models/users.py:203: error: Need type annotation for "enable_stream_push_notifications"  [var-annotated]
- zerver/models/users.py:204: error: Need type annotation for "enable_stream_audible_notifications"  [var-annotated]
- zerver/models/users.py:205: error: Need type annotation for "notification_sound"  [var-annotated]
- zerver/models/users.py:206: error: Need type annotation for "wildcard_mentions_notify"  [var-annotated]
- zerver/models/users.py:209: error: Need type annotation for "enable_followed_topic_desktop_notifications"  [var-annotated]
- zerver/models/users.py:210: error: Need type annotation for "enable_followed_topic_email_notifications"  [var-annotated]
- zerver/models/users.py:211: error: Need type annotation for "enable_followed_topic_push_notifications"  [var-annotated]
- zerver/models/users.py:212: error: Need type annotation for "enable_followed_topic_audible_notifications"  [var-annotated]
- zerver/models/users.py:213: error: Need type annotation for "enable_followed_topic_wildcard_mentions_notify"  [var-annotated]
- zerver/models/users.py:216: error: Need type annotation for "enable_desktop_notifications"  [var-annotated]
- zerver/models/users.py:217: error: Need type annotation for "pm_content_in_desktop_notifications"  [var-annotated]
- zerver/models/users.py:218: error: Need type annotation for "enable_sounds"  [var-annotated]
- zerver/models/users.py:219: error: Need type annotation for "enable_offline_email_notifications"  [var-annotated]
- zerver/models/users.py:220: error: Need type annotation for "message_content_in_email_notifications"  [var-annotated]
- zerver/models/users.py:221: error: Need type annotation for "enable_offline_push_notifications"  [var-annotated]
- zerver/models/users.py:222: error: Need type annotation for "enable_online_push_notifications"  [var-annotated]
- zerver/models/users.py:234: error: Need type annotation for "desktop_icon_count_display"  [var-annotated]
- zerver/models/users.py:238: error: Need type annotation for "enable_digest_emails"  [var-annotated]
- zerver/models/users.py:239: error: Need type annotation for "enable_login_emails"  [var-annotated]
- zerver/models/users.py:240: error: Need type annotation for "enable_marketing_emails"  [var-annotated]
- zerver/models/users.py:241: error: Need type annotation for "presence_enabled"  [var-annotated]
- zerver/models/users.py:251: error: Need type annotation for "realm_name_in_email_notifications_policy"  [var-annotated]
- zerver/models/users.py:272: error: Need type annotation for "automatically_follow_topics_policy"  [var-annotated]
- zerver/models/users.py:275: error: Need type annotation for "automatically_unmute_topics_in_muted_streams_policy"  [var-annotated]
- zerver/models/users.py:278: error: Need type annotation for "automatically_follow_topics_where_mentioned"  [var-annotated]
- zerver/models/users.py:280: error: Need type annotation for "resolved_topic_notice_auto_read_policy"  [var-annotated]
- zerver/models/users.py:287: error: Need type annotation for "enable_drafts_synchronization"  [var-annotated]
- zerver/models/users.py:290: error: Need type annotation for "send_stream_typing_notifications"  [var-annotated]
- zerver/models/users.py:291: error: Need type annotation for "send_private_typing_notifications"  [var-annotated]
- zerver/models/users.py:292: error: Need type annotation for "send_read_receipts"  [var-annotated]
- zerver/models/users.py:293: error: Need type annotation for "allow_private_data_export"  [var-annotated]
- zerver/models/users.py:296: error: Need type annotation for "receives_typing_notifications"  [var-annotated]
- zerver/models/users.py:300: error: Need type annotation for "web_inbox_show_channel_folders"  [var-annotated]
- zerver/models/users.py:311: error: Need type annotation for "email_address_visibility"  [var-annotated]
- zerver/models/users.py:326: error: Need type annotation for "hide_ai_features"  [var-annotated]
- zerver/models/users.py:443: error: Need type annotation for "realm"  [var-annotated]
- zerver/models/users.py:491: error: Need type annotation for "id"  [var-annotated]
- zerver/models/users.py:509: error: Need type annotation for "delivery_email"  [var-annotated]
- zerver/models/users.py:510: error: Need type annotation for "email"  [var-annotated]
- zerver/models/users.py:512: error: Need type annotation for "realm"  [var-annotated]
- zerver/models/users.py:521: error: Need type annotation for "full_name"  [var-annotated]
- zerver/models/users.py:523: error: Need type annotation for "date_joined"  [var-annotated]
- zerver/models/users.py:531: error: Need type annotation for "tos_version"  [var-annotated]
- zerver/models/users.py:532: error: Need type annotation for "api_key"  [var-annotated]
- zerver/models/users.py:538: error: Need type annotation for "uuid"  [var-annotated]
- zerver/models/users.py:541: error: Need type annotation for "is_staff"  [var-annotated]
- zerver/models/users.py:548: error: Need type annotation for "is_active"  [var-annotated]
- zerver/models/users.py:553: error: Need type annotation for "is_deleted"  [var-annotated]
- zerver/models/users.py:555: error: Need type annotation for "is_bot"  [var-annotated]
- zerver/models/users.py:556: error: Need type annotation for "bot_type"  [var-annotated]
- zerver/models/users.py:557: error: Need type annotation for "bot_owner"  [var-annotated]
- zerver/models/users.py:569: error: Need type annotation for "role"  [var-annotated]
- zerver/models/users.py:611: error: Need type annotation for "long_term_idle"  [var-annotated]
- zerver/models/users.py:614: error: Need type annotation for "last_active_message_id"  [var-annotated]
- zerver/models/users.py:621: error: Need type annotation for "is_mirror_dummy"  [var-annotated]
- zerver/models/users.py:624: error: Need type annotation for "is_imported_stub"  [var-annotated]
- zerver/models/users.py:628: error: Need type annotation for "can_forge_sender"  [var-annotated]
- zerver/models/users.py:630: error: Need type annotation for "can_create_users"  [var-annotated]
- zerver/models/users.py:632: error: Need type annotation for "can_change_user_emails"  [var-annotated]
- zerver/models/users.py:635: error: Need type annotation for "last_reminder"  [var-annotated]
- zerver/models/users.py:642: error: Need type annotation for "rate_limits"  [var-annotated]
- zerver/models/users.py:645: error: Need type annotation for "default_sending_stream"  [var-annotated]
- zerver/models/users.py:651: error: Need type annotation for "default_events_register_stream"  [var-annotated]
- zerver/models/users.py:657: error: Need type annotation for "default_all_public_streams"  [var-annotated]
- zerver/models/users.py:667: error: Need type annotation for "timezone"  [var-annotated]
- zerver/models/users.py:678: error: Need type annotation for "avatar_source"  [var-annotated]
- zerver/models/users.py:681: error: Need type annotation for "avatar_version"  [var-annotated]
- zerver/models/users.py:685: error: Need type annotation for "avatar_hash"  [var-annotated]
- zerver/models/users.py:1262: error: Need type annotation for "user"  [var-annotated]
- zerver/models/users.py:1263: error: Need type annotation for "realm"  [var-annotated]
- zerver/models/users.py:1264: error: Need type annotation for "date_created"  [var-annotated]
- zerver/models/users.py:1267: error: Need type annotation for "external_auth_method_name"  [var-annotated]
- zerver/models/users.py:1268: error: Need type annotation for "external_auth_id"  [var-annotated]
- zerver/models/clients.py:13: error: Need type annotation for "id"  [var-annotated]
- zerver/models/clients.py:14: error: Need type annotation for "name"  [var-annotated]
- zerver/models/user_activity.py:22: error: Need type annotation for "user_profile"  [var-annotated]
- zerver/models/user_activity.py:23: error: Need type annotation for "client"  [var-annotated]
- zerver/models/user_activity.py:24: error: Need type annotation for "query"  [var-annotated]
- zerver/models/user_activity.py:26: error: Need type annotation for "count"  [var-annotated]
- zerver/models/user_activity.py:27: error: Need type annotation for "last_visit"  [var-annotated]
- zerver/models/user_activity.py:36: error: Need type annotation for "user_profile"  [var-annotated]
- zerver/models/user_activity.py:37: error: Need type annotation for "start"  [var-annotated]
- zerver/models/user_activity.py:38: error: Need type annotation for "end"  [var-annotated]
- zerver/models/recipients.py:38: error: Need type annotation for "id"  [var-annotated]
- zerver/models/recipients.py:39: error: Need type annotation for "type_id"  [var-annotated]
- zerver/models/recipients.py:40: error: Need type annotation for "type"  [var-annotated]
- zerver/models/recipients.py:121: error: Need type annotation for "huddle_hash"  [var-annotated]
- zerver/models/recipients.py:123: error: Need type annotation for "recipient"  [var-annotated]
- zerver/models/recipients.py:125: error: Need type annotation for "group_size"  [var-annotated]
- zerver/models/push_notifications.py:19: error: Need type annotation for "kind"  [var-annotated]
- zerver/models/push_notifications.py:25: error: Need type annotation for "token"  [var-annotated]
- zerver/models/push_notifications.py:29: error: Need type annotation for "last_updated"  [var-annotated]
- zerver/models/push_notifications.py:32: error: Need type annotation for "ios_app_id"  [var-annotated]
- zerver/models/push_notifications.py:40: error: Need type annotation for "user"  [var-annotated]
- zerver/models/onboarding_steps.py:9: error: Need type annotation for "user"  [var-annotated]
- zerver/models/onboarding_steps.py:10: error: Need type annotation for "onboarding_step"  [var-annotated]
- zerver/models/onboarding_steps.py:11: error: Need type annotation for "timestamp"  [var-annotated]
- zerver/models/navigation_views.py:14: error: Need type annotation for "user"  [var-annotated]
- zerver/models/navigation_views.py:18: error: Need type annotation for "fragment"  [var-annotated]
- zerver/models/navigation_views.py:21: error: Need type annotation for "is_pinned"  [var-annotated]
- zerver/models/navigation_views.py:24: error: Need type annotation for "name"  [var-annotated]
- zerver/models/muted_users.py:12: error: Need type annotation for "user_profile"  [var-annotated]
- zerver/models/muted_users.py:13: error: Need type annotation for "muted_user"  [var-annotated]
- zerver/models/muted_users.py:14: error: Need type annotation for "date_muted"  [var-annotated]
- zerver/models/groups.py:44: error: Need type annotation for "realm"  [var-annotated]
- zerver/models/groups.py:53: error: Need type annotation for "usergroup_ptr"  [var-annotated]
- zerver/models/groups.py:65: error: Need type annotation for "name"  [var-annotated]
- zerver/models/groups.py:66: error: Need type annotation for "description"  [var-annotated]
- zerver/models/groups.py:67: error: Need type annotation for "date_created"  [var-annotated]
- zerver/models/groups.py:68: error: Need type annotation for "creator"  [var-annotated]
- zerver/models/groups.py:71: error: Need type annotation for "is_system_group"  [var-annotated]
- zerver/models/groups.py:73: error: Need type annotation for "can_add_members_group"  [var-annotated]
- zerver/models/groups.py:76: error: Need type annotation for "can_join_group"  [var-annotated]
- zerver/models/groups.py:77: error: Need type annotation for "can_leave_group"  [var-annotated]
- zerver/models/groups.py:78: error: Need type annotation for "can_manage_group"  [var-annotated]
- zerver/models/groups.py:79: error: Need type annotation for "can_mention_group"  [var-annotated]
- zerver/models/groups.py:82: error: Need type annotation for "can_remove_members_group"  [var-annotated]
- zerver/models/groups.py:86: error: Need type annotation for "realm_for_sharding"  [var-annotated]
- zerver/models/groups.py:87: error: Need type annotation for "deactivated"  [var-annotated]
- zerver/models/groups.py:167: error: Need type annotation for "user_group"  [var-annotated]
- zerver/models/groups.py:168: error: Need type annotation for "user_profile"  [var-annotated]
- zerver/models/groups.py:175: error: Need type annotation for "supergroup"  [var-annotated]
- zerver/models/groups.py:176: error: Need type annotation for "subgroup"  [var-annotated]
- zerver/models/devices.py:19: error: Need type annotation for "user"  [var-annotated]
- zerver/models/devices.py:26: error: Need type annotation for "push_key"  [var-annotated]
- zerver/models/devices.py:28: error: Need type annotation for "push_key_id"  [var-annotated]
- zerver/models/devices.py:31: error: Need type annotation for "push_token_id"  [var-annotated]
- zerver/models/devices.py:33: error: Need type annotation for "pending_push_token_id"  [var-annotated]
- zerver/models/devices.py:36: error: Need type annotation for "push_token_last_updated_timestamp"  [var-annotated]
- zerver/models/devices.py:42: error: Need type annotation for "push_token_kind"  [var-annotated]
- zerver/models/devices.py:50: error: Need type annotation for "push_registration_error_code"  [var-annotated]
- zerver/models/bots.py:28: error: Need type annotation for "name"  [var-annotated]
- zerver/models/bots.py:32: error: Need type annotation for "user_profile"  [var-annotated]
- zerver/models/bots.py:33: error: Need type annotation for "base_url"  [var-annotated]
- zerver/models/bots.py:34: error: Need type annotation for "token"  [var-annotated]
- zerver/models/bots.py:36: error: Need type annotation for "interface"  [var-annotated]
- zerver/models/bots.py:66: error: Need type annotation for "bot_profile"  [var-annotated]

... (truncated 554 lines) ...

@delfick
Copy link
Copy Markdown
Contributor

delfick commented May 6, 2026

I got around to giving this a shot on our codebase. I'm clearly gonna need to make it work against 6.0.3 first before I can be specific about this branch (might take me up to a few weeks), but that aside there's 84 unused type ignores with this branch (🥳 ) and seems to throw a bunch of new errors that indicate it actually understands what types things should be so that's nice. I think this will be great :)

@UnknownPlatypus
Copy link
Copy Markdown
Contributor Author

Awesome, I'll try to finish this later this week, would be awesome if you manage to check if some new issues are false positives or not 🙏

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

Labels

None yet

5 participants