New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
requirements: Add django-stubs. #18777
Conversation
And this is clearly going to be a backwards-incompatible provision update, so |
Oops, this is accidentally closed by an empty push. |
There's 2K errors, but if you look at them, a huge portion are copies of a single confusion. E.g. we frequently access the My guess is the problem is you haven't installed the plugin: https://github.com/typeddjango/django-stubs#installation which I suspect exists to implicitly add those fields. |
I'm pushing back to this PR with two commits: the requirements change tweaked to also configure the mypy plugin, which has 1999 errors. And then there's a new commit that brings that down to 1946 (fixes 53 errors) by adding some missing type declarations in A good strategy for this project will be to debug with the full branch, generate commits that fix type annotations issues in a way that's correct (especially those that cause 50 errors with the Django stubs included!), and then incrementally merge those as we can. At the same time, we'll accumulate some commits that only work with the Django stubs package, e.g. because they use types that it adds or would be a regressions without the package. One needs to switch branches and provisioning to run the different check modes, but Zulip's provision caching makes that only take a few seconds. A few large clusters I notice when skimming:
I think this is actually really promising! |
update 1946 -> 1892 |
This is a preparation for adding django stubs. Related: zulip#18777
update 1907 (rebased to master) -> 1889 |
Because this PR is going to be huge and it is unlikely to be completed without running into merge conflicts, perhaps we can start reviewing & merging part of it on a commit-by-commit basis. |
Yeah, I completely agree. And really, we can keep this PR as mostly the commits that we'll merge at the end, and submit commits that work in master as their own PRs. I've merged 56e6bd7164131d198a528bc450364971be665c87 as a first step in that direction. |
This adds a new class called MessageRenderingResult to contain the additional properties we added to the Message object (like alert_words) as well as the rendered content to ensure typesafe reference. No behavioral change is made except changes in typing. This is a preparatory change for adding django-stubs to the backend. Related: zulip#18777
c9d7822
to
c3ca912
Compare
c3ca912
to
31208fc
Compare
I’ve reordered and squashed some of the commits to preserve bisectability. I think this is now ready. |
zerver/lib/test_helpers.py
Outdated
@@ -295,6 +295,13 @@ class HostRequestMock(HttpRequest): | |||
"""A mock request object where get_host() works. Useful for testing | |||
routes that use Zulip's subdomains feature""" | |||
|
|||
# The base class HttpRequest has GET and POST been immutable QueryDict, which makes | |||
# it incompatible for HostRequestMock to define them as being mutable. Since the test | |||
# cases rely on the mutability of these fields, we override it with type ignore. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine for now but I'd be curious how many tests change these vs. declaring a new HostRequestMock for new values; it seems possible we could either try to purge the modification pattern or make a MutableHostRequestMock
used by a small number of tests that plan to do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main issue here is that HostRequestMock
itself relies on this mutability of POST
to initialize it in the first place. POST
is mutated all the time via post_data
.
For GET
, HostRequestMock
does not modify it in __init__
, but we do have one or two test cases modifying its value, which is not as often.
@@ -15,6 +15,9 @@ | |||
# See https://zulip.readthedocs.io/en/latest/subsystems/settings.html for more information | |||
# | |||
######################################################################## | |||
import django_stubs_ext | |||
|
|||
django_stubs_ext.monkeypatch() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment documenting what this does?
https://pypi.org/project/django-stubs-ext/ explains it allows runtime monkey-patching... but it'd be helpful to have the context on why Zulip needs that behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I know, we only really need this for models.Lookup
. Added comments on this.
@@ -4447,7 +4447,7 @@ class RealmAuditLog(AbstractRealmAuditLog): | |||
null=True, | |||
on_delete=CASCADE, | |||
) | |||
event_last_message_id: Optional[int] = models.IntegerField(null=True) | |||
event_last_message_id = models.IntegerField(null=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this hunk belong in the previous commit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had actually moved that out of the previous commit intentionally, as the previous commit doesn’t type check with this hunk.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. event_last_message_id = assert_is_not_none(log_entry.event_last_message_id)
fails.
I didn't full read |
43c5171
to
4a7ee75
Compare
For reviewing purposes, this is the script that generates most of dc1aa60. #!/usr/bin/bash
# This removes all the type annnotations for model fields
set -ex
TARGETS=$(find . -name models.py)
sed -i "s|:.*\( = models.*\)|\1|g" $TARGETS |
I chose to make |
a1feb5b
to
9e2c021
Compare
We no longer need to annotate the type of objects returned from queries since django-stubs plugin infers that already. Signed-off-by: Zixuan James Li <p359101898@gmail.com>
Previously, we type the model fields with explicit type annotations manually with the approximate types. This was because the lack of types for Django. django-stubs provides more specific types for all these fields that incompatible with our previous approximate annotations. So now we can remove the inline type annotations and rely on the types defined in the stubs. This allows mypy to infer the types of the model fields for us. Signed-off-by: Zixuan James Li <p359101898@gmail.com>
This comment was marked as resolved.
This comment was marked as resolved.
Note that django_stubs_ext is required to be placed within common.in because we need the monkeypatched types in runtime; django-stubs itself is for type checking only. In the future, we would like to pin to a release instead of a git revision, but several patches we've contributed upstream have not appeared in a release yet. We also remove the type annotation for RealmAuditLog.event_last_message_id here instead of earlier because type checking fails otherwise. Fixes zulip#11560.
This saves us from using a conditional import. Signed-off-by: Zixuan James Li <p359101898@gmail.com>
This saves us from using a conditional import. Signed-off-by: Zixuan James Li <p359101898@gmail.com>
ab1d277
to
7c167be
Compare
This looks good; did some small edits to the comments and I've tagged this to merge once CI finishes. Huge thanks for doing this project! It's been a long slog and I think very meaningful for the codebase. |
Can you post in #backend an announcement about this having been integrated? And do an edit pass on our mypy documentation to reference that we now do this, perhaps with examples/links that may be helpful. |
#11560
_CodeCallable
usesBaseDatabaseSchemaEditor
instead ofDatabaseSchemaEditor
)Cursor.execute
expects astr
HttpRequest
#22153HttpResponse
with_MonkeyPatchedWSGIResponse.
#22209extra_data
field ofRealmAuditLog
toJSONField
#18391HttpResponse
with_MonkeyPatchedWSGIResponse.
#22209