typing: Populate POST and _files with `cast` and `setattr`.

`POST` is an immutable attribute and `_files` is an internal attribute
of `HttpRequest`. With type annotations provided by `django-stubs`, mypy
stops us from modifying these attributes. This uses `cast` and `setattr`
to avoid typing issues.

This is a part of django-stubs refactorings.

Signed-off-by: Zixuan James Li <p359101898@gmail.com>
This commit is contained in:
Zixuan James Li 2022-05-30 18:47:39 -04:00 committed by Tim Abbott
parent 27be27560b
commit a3a0545aac
1 changed files with 30 additions and 5 deletions

View File

@ -4,7 +4,18 @@ import logging
import urllib import urllib
from functools import wraps from functools import wraps
from io import BytesIO from io import BytesIO
from typing import Callable, Dict, Optional, Sequence, Set, TypeVar, Union, cast, overload from typing import (
TYPE_CHECKING,
Callable,
Dict,
Optional,
Sequence,
Set,
TypeVar,
Union,
cast,
overload,
)
import django_otp import django_otp
import orjson import orjson
@ -62,6 +73,9 @@ if settings.ZILENCER_ENABLED:
get_remote_server_by_uuid, get_remote_server_by_uuid,
) )
if TYPE_CHECKING:
from django.http.request import _ImmutableQueryDict
rate_limiter_logger = logging.getLogger("zerver.lib.rate_limiter") rate_limiter_logger = logging.getLogger("zerver.lib.rate_limiter")
webhook_logger = logging.getLogger("zulip.zerver.webhooks") webhook_logger = logging.getLogger("zulip.zerver.webhooks")
@ -719,15 +733,26 @@ def process_as_post(view_func: ViewFuncT) -> ViewFuncT:
if not request.POST: if not request.POST:
# Only take action if POST is empty. # Only take action if POST is empty.
if request.content_type == "multipart/form-data": if request.content_type == "multipart/form-data":
# Note that request._files is just the private attribute that backs the POST, _files = MultiPartParser(
# FILES property, so we are essentially setting request.FILES here. (In
# Django 1.5 FILES was still a read-only property.)
request.POST, request._files = MultiPartParser(
request.META, request.META,
BytesIO(request.body), BytesIO(request.body),
request.upload_handlers, request.upload_handlers,
request.encoding, request.encoding,
).parse() ).parse()
# request.POST is an immutable QueryDict in most cases, while
# MultiPartParser.parse() returns a mutable instance of QueryDict.
# This can be fix when https://code.djangoproject.com/ticket/17235
# is resolved.
# django-stubs makes QueryDict of different mutabilities incompatible
# types. There is no way to acknowledge the django-stubs mypy plugin
# the change of POST's mutability, so we bypass the check with cast.
# See also: https://github.com/typeddjango/django-stubs/pull/925#issue-1206399444
POST._mutable = False
request.POST = cast("_ImmutableQueryDict", POST)
# Note that request._files is just the private attribute that backs the
# FILES property, so we are essentially setting request.FILES here. (In
# Django 3.2 FILES was still a read-only property.)
setattr(request, "_files", _files)
elif request.content_type == "application/x-www-form-urlencoded": elif request.content_type == "application/x-www-form-urlencoded":
request.POST = QueryDict(request.body, encoding=request.encoding) request.POST = QueryDict(request.body, encoding=request.encoding)