requirements: Add django-stubs and configure plugin.

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 #11560.
This commit is contained in:
PIG208 2021-07-17 10:25:08 +00:00 committed by Tim Abbott
parent 4c3c976174
commit df18bbbd48
10 changed files with 60 additions and 11 deletions

View File

@ -42,8 +42,10 @@ warn_unreachable = true
# with this behavior. # with this behavior.
local_partial_types = true local_partial_types = true
plugins = ["mypy_django_plugin.main"]
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
module = ["zproject.configured_settings", "zproject.settings"] module = ["zproject.configured_settings", "zproject.settings", "zproject.default_settings"]
no_implicit_reexport = false no_implicit_reexport = false
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
@ -59,7 +61,6 @@ module = [
"defusedxml.*", # https://github.com/tiran/defusedxml/issues/46 "defusedxml.*", # https://github.com/tiran/defusedxml/issues/46
"digitalocean.*", "digitalocean.*",
"disposable_email_domains.*", "disposable_email_domains.*",
"django.*", # https://github.com/zulip/zulip/issues/11560
"django_auth_ldap.*", "django_auth_ldap.*",
"django_bmemcached.*", "django_bmemcached.*",
"django_cte.*", "django_cte.*",
@ -93,3 +94,6 @@ module = [
"two_factor.*", "two_factor.*",
] ]
ignore_missing_imports = true ignore_missing_imports = true
[tool.django-stubs]
django_settings_module = "zproject.settings"

View File

@ -194,3 +194,6 @@ soupsieve
# Circuit-breaking for outgoing services # Circuit-breaking for outgoing services
circuitbreaker circuitbreaker
# Runtime monkeypatching of django-stubs generics
https://github.com/typeddjango/django-stubs/archive/9bd8aed1e19f9da2c7d3a2879367a40847b57dea.zip#egg=django-stubs-ext==0.5.0+git&subdirectory=django_stubs_ext

View File

@ -466,6 +466,8 @@ django[argon2]==4.0.7 \
# django-phonenumber-field # django-phonenumber-field
# django-scim2 # django-scim2
# django-sendfile2 # django-sendfile2
# django-stubs
# django-stubs-ext
# django-two-factor-auth # django-two-factor-auth
django-auth-ldap==4.1.0 \ django-auth-ldap==4.1.0 \
--hash=sha256:68870e7921e84b1a9867e268a9c8a3e573e8a0d95ea08bcf31be178f5826ff36 \ --hash=sha256:68870e7921e84b1a9867e268a9c8a3e573e8a0d95ea08bcf31be178f5826ff36 \
@ -504,6 +506,14 @@ django-statsd-mozilla==0.4.0 \
--hash=sha256:0d87cb63de8107279cbb748caad9aa74c6a44e7e96ccc5dbf07b89f77285a4b8 \ --hash=sha256:0d87cb63de8107279cbb748caad9aa74c6a44e7e96ccc5dbf07b89f77285a4b8 \
--hash=sha256:81084f3d426f5184f0a0f1dbfe035cc26b66f041d2184559d916a228d856f0d3 --hash=sha256:81084f3d426f5184f0a0f1dbfe035cc26b66f041d2184559d916a228d856f0d3
# via -r requirements/common.in # via -r requirements/common.in
https://github.com/typeddjango/django-stubs/archive/9bd8aed1e19f9da2c7d3a2879367a40847b57dea.zip#egg=django-stubs==1.12.0+git \
--hash=sha256:42340195b7e67a035f2399cf2718b0990ca40addf3ec2f0ac213887d7ef32c7b
# via -r requirements/mypy.in
https://github.com/typeddjango/django-stubs/archive/9bd8aed1e19f9da2c7d3a2879367a40847b57dea.zip#egg=django-stubs-ext==0.5.0+git&subdirectory=django_stubs_ext \
--hash=sha256:42340195b7e67a035f2399cf2718b0990ca40addf3ec2f0ac213887d7ef32c7b
# via
# -r requirements/common.in
# django-stubs
django-two-factor-auth[call,phonenumberslite,sms]==1.14.0 \ django-two-factor-auth[call,phonenumberslite,sms]==1.14.0 \
--hash=sha256:b414ef51cc84335e0049e3f98ef89ac15a09187efa381f3acd321651afae95b3 \ --hash=sha256:b414ef51cc84335e0049e3f98ef89ac15a09187efa381f3acd321651afae95b3 \
--hash=sha256:d18290f02766f400537b88937df06aa0a2d6d6d37937fbfb84896c4790a9e59b --hash=sha256:d18290f02766f400537b88937df06aa0a2d6d6d37937fbfb84896c4790a9e59b
@ -1049,6 +1059,7 @@ mypy==0.971 \
--hash=sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c --hash=sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c
# via # via
# -r requirements/mypy.in # -r requirements/mypy.in
# django-stubs
# sqlalchemy # sqlalchemy
mypy-boto3-s3==1.24.36.post1 \ mypy-boto3-s3==1.24.36.post1 \
--hash=sha256:30ae59b33c55f8b7b693170f9519ea5b91a2fbf31a73de79cdef57a27d784e5a \ --hash=sha256:30ae59b33c55f8b7b693170f9519ea5b91a2fbf31a73de79cdef57a27d784e5a \
@ -2043,6 +2054,7 @@ tomli==2.0.1 \
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
# via # via
# black # black
# django-stubs
# mypy # mypy
# pep517 # pep517
tornado==6.2 \ tornado==6.2 \
@ -2156,10 +2168,16 @@ types-python-dateutil==2.8.19 \
--hash=sha256:6284df1e4783d8fc6e587f0317a81333856b872a6669a282f8a325342bce7fa8 \ --hash=sha256:6284df1e4783d8fc6e587f0317a81333856b872a6669a282f8a325342bce7fa8 \
--hash=sha256:bfd3eb39c7253aea4ba23b10f69b017d30b013662bb4be4ab48b20bbd763f309 --hash=sha256:bfd3eb39c7253aea4ba23b10f69b017d30b013662bb4be4ab48b20bbd763f309
# via -r requirements/mypy.in # via -r requirements/mypy.in
types-pytz==2022.2.1.0 \
--hash=sha256:47cfb19c52b9f75896440541db392fd312a35b279c6307a531db71152ea63e2b \
--hash=sha256:50ead2254b524a3d4153bc65d00289b66898060d2938e586170dce918dbaf3b3
# via django-stubs
types-pyyaml==6.0.11 \ types-pyyaml==6.0.11 \
--hash=sha256:7f7da2fd11e9bc1e5e9eb3ea1be84f4849747017a59fc2eee0ea34ed1147c2e0 \ --hash=sha256:7f7da2fd11e9bc1e5e9eb3ea1be84f4849747017a59fc2eee0ea34ed1147c2e0 \
--hash=sha256:8f890028123607379c63550179ddaec4517dc751f4c527a52bb61934bf495989 --hash=sha256:8f890028123607379c63550179ddaec4517dc751f4c527a52bb61934bf495989
# via -r requirements/mypy.in # via
# -r requirements/mypy.in
# django-stubs
types-redis==4.3.19 \ types-redis==4.3.19 \
--hash=sha256:c74262197487a65e3f02db37d3f0e0f3bb2d5d370f7e6ba390814e865a45e56e \ --hash=sha256:c74262197487a65e3f02db37d3f0e0f3bb2d5d370f7e6ba390814e865a45e56e \
--hash=sha256:c881ffb94bd47dca21ed503328544a56bb315851575eebb27fb371bff735844a --hash=sha256:c881ffb94bd47dca21ed503328544a56bb315851575eebb27fb371bff735844a
@ -2201,6 +2219,8 @@ typing-extensions==4.3.0 \
# -r requirements/common.in # -r requirements/common.in
# black # black
# boto3-stubs # boto3-stubs
# django-stubs
# django-stubs-ext
# libcst # libcst
# mypy # mypy
# mypy-boto3-s3 # mypy-boto3-s3

View File

@ -30,3 +30,5 @@ types-stripe
types-zxcvbn types-zxcvbn
importlib-metadata ; python_version < "3.10" # for SQLAlchemy importlib-metadata ; python_version < "3.10" # for SQLAlchemy
https://github.com/typeddjango/django-stubs/archive/9bd8aed1e19f9da2c7d3a2879367a40847b57dea.zip#egg=django-stubs==1.12.0+git

View File

@ -304,6 +304,7 @@ django[argon2]==4.0.7 \
# django-phonenumber-field # django-phonenumber-field
# django-scim2 # django-scim2
# django-sendfile2 # django-sendfile2
# django-stubs-ext
# django-two-factor-auth # django-two-factor-auth
django-auth-ldap==4.1.0 \ django-auth-ldap==4.1.0 \
--hash=sha256:68870e7921e84b1a9867e268a9c8a3e573e8a0d95ea08bcf31be178f5826ff36 \ --hash=sha256:68870e7921e84b1a9867e268a9c8a3e573e8a0d95ea08bcf31be178f5826ff36 \
@ -342,6 +343,9 @@ django-statsd-mozilla==0.4.0 \
--hash=sha256:0d87cb63de8107279cbb748caad9aa74c6a44e7e96ccc5dbf07b89f77285a4b8 \ --hash=sha256:0d87cb63de8107279cbb748caad9aa74c6a44e7e96ccc5dbf07b89f77285a4b8 \
--hash=sha256:81084f3d426f5184f0a0f1dbfe035cc26b66f041d2184559d916a228d856f0d3 --hash=sha256:81084f3d426f5184f0a0f1dbfe035cc26b66f041d2184559d916a228d856f0d3
# via -r requirements/common.in # via -r requirements/common.in
https://github.com/typeddjango/django-stubs/archive/9bd8aed1e19f9da2c7d3a2879367a40847b57dea.zip#egg=django-stubs-ext==0.5.0+git&subdirectory=django_stubs_ext \
--hash=sha256:42340195b7e67a035f2399cf2718b0990ca40addf3ec2f0ac213887d7ef32c7b
# via -r requirements/common.in
django-two-factor-auth[call,phonenumberslite,sms]==1.14.0 \ django-two-factor-auth[call,phonenumberslite,sms]==1.14.0 \
--hash=sha256:b414ef51cc84335e0049e3f98ef89ac15a09187efa381f3acd321651afae95b3 \ --hash=sha256:b414ef51cc84335e0049e3f98ef89ac15a09187efa381f3acd321651afae95b3 \
--hash=sha256:d18290f02766f400537b88937df06aa0a2d6d6d37937fbfb84896c4790a9e59b --hash=sha256:d18290f02766f400537b88937df06aa0a2d6d6d37937fbfb84896c4790a9e59b
@ -1391,6 +1395,7 @@ typing-extensions==4.3.0 \
--hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6
# via # via
# -r requirements/common.in # -r requirements/common.in
# django-stubs-ext
# mypy-boto3-s3 # mypy-boto3-s3
# mypy-boto3-ses # mypy-boto3-ses
# mypy-boto3-sns # mypy-boto3-sns

View File

@ -48,4 +48,4 @@ API_FEATURE_LEVEL = 150
# historical commits sharing the same major version, in which case a # historical commits sharing the same major version, in which case a
# minor version bump suffices. # minor version bump suffices.
PROVISION_VERSION = (202, 0) PROVISION_VERSION = (203, 0)

View File

@ -295,6 +295,14 @@ class HostRequestMock(HttpRequest):
"""A mock request object where get_host() works. Useful for testing """A mock request object where get_host() works. Useful for testing
routes that use Zulip's subdomains feature""" routes that use Zulip's subdomains feature"""
# The base class HttpRequest declares GET and POST as immutable
# QueryDict objects. The implementation of HostRequestMock
# requires POST to be mutable, and we have some use cases that
# modify GET, so GET and POST are both redeclared as mutable.
GET: QueryDict # type: ignore[assignment] # See previous comment.
POST: QueryDict # type: ignore[assignment] # See previous comment.
def __init__( def __init__(
self, self,
post_data: Dict[str, Any] = {}, post_data: Dict[str, Any] = {},

View File

@ -132,7 +132,7 @@ class EmojiInfo(TypedDict):
@models.Field.register_lookup @models.Field.register_lookup
class AndZero(models.Lookup): class AndZero(models.Lookup[int]):
lookup_name = "andz" lookup_name = "andz"
def as_sql( def as_sql(
@ -144,7 +144,7 @@ class AndZero(models.Lookup):
@models.Field.register_lookup @models.Field.register_lookup
class AndNonZero(models.Lookup): class AndNonZero(models.Lookup[int]):
lookup_name = "andnz" lookup_name = "andnz"
def as_sql( def as_sql(
@ -247,7 +247,7 @@ def clear_supported_auth_backends_cache() -> None:
supported_backends = None supported_backends = None
class Realm(models.Model): class Realm(models.Model): # type: ignore[django-manager-missing] # django-stubs cannot resolve the custom CTEManager yet https://github.com/typeddjango/django-stubs/issues/1023
MAX_REALM_NAME_LENGTH = 40 MAX_REALM_NAME_LENGTH = 40
MAX_REALM_DESCRIPTION_LENGTH = 1000 MAX_REALM_DESCRIPTION_LENGTH = 1000
MAX_REALM_SUBDOMAIN_LENGTH = 40 MAX_REALM_SUBDOMAIN_LENGTH = 40
@ -1689,7 +1689,7 @@ class RealmUserDefault(UserBaseSettings):
realm = models.OneToOneField(Realm, on_delete=CASCADE) realm = models.OneToOneField(Realm, on_delete=CASCADE)
class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): # type: ignore[django-manager-missing] # django-stubs cannot resolve the custom CTEManager yet https://github.com/typeddjango/django-stubs/issues/1023
USERNAME_FIELD = "email" USERNAME_FIELD = "email"
MAX_NAME_LENGTH = 100 MAX_NAME_LENGTH = 100
MIN_NAME_LENGTH = 2 MIN_NAME_LENGTH = 2
@ -2160,7 +2160,7 @@ class PasswordTooWeakError(Exception):
pass pass
class UserGroup(models.Model): class UserGroup(models.Model): # type: ignore[django-manager-missing] # django-stubs cannot resolve the custom CTEManager yet https://github.com/typeddjango/django-stubs/issues/1023
objects: CTEManager = CTEManager() objects: CTEManager = CTEManager()
id = models.AutoField(auto_created=True, primary_key=True, verbose_name="ID") id = models.AutoField(auto_created=True, primary_key=True, verbose_name="ID")
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
@ -4450,7 +4450,7 @@ class RealmAuditLog(AbstractRealmAuditLog):
null=True, null=True,
on_delete=CASCADE, on_delete=CASCADE,
) )
event_last_message_id: Optional[int] = models.IntegerField(null=True) event_last_message_id = models.IntegerField(null=True)
def __str__(self) -> str: def __str__(self) -> str:
if self.modified_user is not None: if self.modified_user is not None:

View File

@ -1402,7 +1402,7 @@ class ZulipRemoteUserBackend(RemoteUserBackend, ExternalAuthMethod):
create_unknown_user = False create_unknown_user = False
def authenticate( def authenticate( # type: ignore[override] # authenticate has an incompatible signature with ModelBackend and BaseBackend
self, self,
request: Optional[HttpRequest] = None, request: Optional[HttpRequest] = None,
*, *,

View File

@ -15,6 +15,13 @@
# See https://zulip.readthedocs.io/en/latest/subsystems/settings.html for more information # See https://zulip.readthedocs.io/en/latest/subsystems/settings.html for more information
# #
######################################################################## ########################################################################
import django_stubs_ext
# Monkey-patch certain types that are declared as generic types
# generic in django-stubs, but not (yet) as generic types in Django
# itself. This is necessary to ensure type references like
# django.db.models.Lookup[int] work correctly at runtime.
django_stubs_ext.monkeypatch()
from .configured_settings import * # noqa: F401,F403 isort: skip from .configured_settings import * # noqa: F401,F403 isort: skip
from .computed_settings import * # noqa: F401,F403 isort: skip from .computed_settings import * # noqa: F401,F403 isort: skip