python: Mark dict parameters with defaults as read-only.

Found by semgrep 0.115 more accurately applying the rule added in
commit 0d6c771baf (#15349).

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2022-10-06 02:56:48 -07:00 committed by Tim Abbott
parent 3f010bbcc4
commit 47c5deeccd
14 changed files with 50 additions and 44 deletions

View File

@ -1979,8 +1979,9 @@ class StripeTest(StripeTestCase):
invoice: bool,
licenses: Optional[int],
min_licenses_in_response: int,
upgrade_params: Dict[str, Any] = {},
upgrade_params: Mapping[str, Any] = {},
) -> None:
upgrade_params = dict(upgrade_params)
if licenses is None:
del_args = ["licenses"]
else:
@ -2006,8 +2007,9 @@ class StripeTest(StripeTestCase):
)
def check_success(
invoice: bool, licenses: Optional[int], upgrade_params: Dict[str, Any] = {}
invoice: bool, licenses: Optional[int], upgrade_params: Mapping[str, Any] = {}
) -> None:
upgrade_params = dict(upgrade_params)
if licenses is None:
del_args = ["licenses"]
else:

View File

@ -13,6 +13,7 @@ from typing import (
Iterable,
Iterator,
List,
Mapping,
Optional,
Protocol,
Set,
@ -173,7 +174,7 @@ def make_user_messages(
subscriber_map: Dict[int, Set[int]],
is_pm_data: bool,
mention_map: Dict[int, Set[int]],
wildcard_mention_map: Dict[int, bool] = {},
wildcard_mention_map: Mapping[int, bool] = {},
) -> List[ZerverFieldsT]:
zerver_usermessage = []

View File

@ -2,7 +2,7 @@
# high-level documentation on how this system works.
import copy
import time
from typing import Any, Callable, Collection, Dict, Iterable, Optional, Sequence, Set
from typing import Any, Callable, Collection, Dict, Iterable, Mapping, Optional, Sequence, Set
from django.conf import settings
from django.utils.translation import gettext as _
@ -1365,7 +1365,7 @@ def do_events_register(
all_public_streams: bool = False,
include_subscribers: bool = True,
include_streams: bool = True,
client_capabilities: Dict[str, bool] = {},
client_capabilities: Mapping[str, bool] = {},
narrow: Collection[Sequence[str]] = [],
fetch_event_types: Optional[Collection[str]] = None,
spectator_requested_language: Optional[str] = None,

View File

@ -6,7 +6,7 @@ import logging
import re
from dataclasses import dataclass
from functools import lru_cache
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple, Type, Union
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Sequence, Tuple, Type, Union
import gcm
import lxml.html
@ -150,7 +150,7 @@ def apns_enabled() -> bool:
return settings.APNS_CERT_FILE is not None
def modernize_apns_payload(data: Dict[str, Any]) -> Dict[str, Any]:
def modernize_apns_payload(data: Mapping[str, Any]) -> Mapping[str, Any]:
"""Take a payload in an unknown Zulip version's format, and return in current format."""
# TODO this isn't super robust as is -- if a buggy remote server
# sends a malformed payload, we are likely to raise an exception.
@ -183,7 +183,7 @@ APNS_MAX_RETRIES = 3
def send_apple_push_notification(
user_identity: UserPushIndentityCompat,
devices: Sequence[DeviceToken],
payload_data: Dict[str, Any],
payload_data: Mapping[str, Any],
remote: Optional["RemoteZulipServer"] = None,
) -> None:
if not devices:
@ -222,7 +222,7 @@ def send_apple_push_notification(
user_identity,
len(devices),
)
payload_data = modernize_apns_payload(payload_data).copy()
payload_data = dict(modernize_apns_payload(payload_data))
message = {**payload_data.pop("custom", {}), "aps": payload_data}
for device in devices:
# TODO obviously this should be made to actually use the async

View File

@ -234,7 +234,7 @@ def send_email(
from_address: Optional[str] = None,
reply_to_email: Optional[str] = None,
language: Optional[str] = None,
context: Dict[str, Any] = {},
context: Mapping[str, Any] = {},
realm: Optional[Realm] = None,
connection: Optional[BaseEmailBackend] = None,
dry_run: bool = False,
@ -338,7 +338,7 @@ def send_future_email(
from_name: Optional[str] = None,
from_address: Optional[str] = None,
language: Optional[str] = None,
context: Dict[str, Any] = {},
context: Mapping[str, Any] = {},
delay: datetime.timedelta = datetime.timedelta(0),
) -> None:
template_name = template_prefix.split("/")[-1]
@ -393,7 +393,7 @@ def send_email_to_admins(
from_name: Optional[str] = None,
from_address: Optional[str] = None,
language: Optional[str] = None,
context: Dict[str, Any] = {},
context: Mapping[str, Any] = {},
) -> None:
admins = realm.get_human_admin_users()
admin_user_ids = [admin.id for admin in admins]
@ -413,7 +413,7 @@ def send_email_to_billing_admins_and_realm_owners(
from_name: Optional[str] = None,
from_address: Optional[str] = None,
language: Optional[str] = None,
context: Dict[str, Any] = {},
context: Mapping[str, Any] = {},
) -> None:
send_email(
template_prefix,

View File

@ -253,7 +253,7 @@ Output:
url: str,
method: str,
result: "TestHttpResponse",
data: Union[str, bytes, Dict[str, Any]],
data: Union[str, bytes, Mapping[str, Any]],
extra: Dict[str, str],
intentionally_undocumented: bool = False,
) -> None:
@ -296,7 +296,7 @@ Output:
def client_patch(
self,
url: str,
info: Dict[str, Any] = {},
info: Mapping[str, Any] = {},
skip_user_agent: bool = False,
follow: bool = False,
secure: bool = False,
@ -325,7 +325,7 @@ Output:
def client_patch_multipart(
self,
url: str,
info: Dict[str, Any] = {},
info: Mapping[str, Any] = {},
skip_user_agent: bool = False,
follow: bool = False,
secure: bool = False,
@ -340,7 +340,7 @@ Output:
with the Django test client, it deals with MULTIPART_CONTENT
automatically, but not patch.)
"""
encoded = encode_multipart(BOUNDARY, info)
encoded = encode_multipart(BOUNDARY, dict(info))
django_client = self.client # see WRAPPER_COMMENT
self.set_http_headers(extra, skip_user_agent)
result = django_client.patch(
@ -359,7 +359,7 @@ Output:
def json_patch(
self,
url: str,
payload: Dict[str, Any] = {},
payload: Mapping[str, Any] = {},
skip_user_agent: bool = False,
follow: bool = False,
secure: bool = False,
@ -376,7 +376,7 @@ Output:
def client_put(
self,
url: str,
info: Dict[str, Any] = {},
info: Mapping[str, Any] = {},
skip_user_agent: bool = False,
follow: bool = False,
secure: bool = False,
@ -391,7 +391,7 @@ Output:
def json_put(
self,
url: str,
payload: Dict[str, Any] = {},
payload: Mapping[str, Any] = {},
skip_user_agent: bool = False,
follow: bool = False,
secure: bool = False,
@ -408,7 +408,7 @@ Output:
def client_delete(
self,
url: str,
info: Dict[str, Any] = {},
info: Mapping[str, Any] = {},
skip_user_agent: bool = False,
follow: bool = False,
secure: bool = False,
@ -434,7 +434,7 @@ Output:
def client_options(
self,
url: str,
info: Dict[str, Any] = {},
info: Mapping[str, Any] = {},
skip_user_agent: bool = False,
follow: bool = False,
secure: bool = False,
@ -442,13 +442,13 @@ Output:
) -> "TestHttpResponse":
django_client = self.client # see WRAPPER_COMMENT
self.set_http_headers(extra, skip_user_agent)
return django_client.options(url, info, follow=follow, secure=secure, **extra)
return django_client.options(url, dict(info), follow=follow, secure=secure, **extra)
@instrument_url
def client_head(
self,
url: str,
info: Dict[str, Any] = {},
info: Mapping[str, Any] = {},
skip_user_agent: bool = False,
follow: bool = False,
secure: bool = False,
@ -500,7 +500,7 @@ Output:
def client_get(
self,
url: str,
info: Dict[str, Any] = {},
info: Mapping[str, Any] = {},
skip_user_agent: bool = False,
follow: bool = False,
secure: bool = False,
@ -858,7 +858,7 @@ Output:
return "Basic " + base64.b64encode(credentials.encode()).decode()
def uuid_get(
self, identifier: str, url: str, info: Dict[str, Any] = {}, **extra: str
self, identifier: str, url: str, info: Mapping[str, Any] = {}, **extra: str
) -> "TestHttpResponse":
extra["HTTP_AUTHORIZATION"] = self.encode_uuid(identifier)
return self.client_get(
@ -890,7 +890,7 @@ Output:
)
def api_get(
self, user: UserProfile, url: str, info: Dict[str, Any] = {}, **extra: str
self, user: UserProfile, url: str, info: Mapping[str, Any] = {}, **extra: str
) -> "TestHttpResponse":
extra["HTTP_AUTHORIZATION"] = self.encode_user(user)
return self.client_get(
@ -923,7 +923,7 @@ Output:
)
def api_patch(
self, user: UserProfile, url: str, info: Dict[str, Any] = {}, **extra: str
self, user: UserProfile, url: str, info: Mapping[str, Any] = {}, **extra: str
) -> "TestHttpResponse":
extra["HTTP_AUTHORIZATION"] = self.encode_user(user)
return self.client_patch(
@ -937,7 +937,7 @@ Output:
)
def api_delete(
self, user: UserProfile, url: str, info: Dict[str, Any] = {}, **extra: str
self, user: UserProfile, url: str, info: Mapping[str, Any] = {}, **extra: str
) -> "TestHttpResponse":
extra["HTTP_AUTHORIZATION"] = self.encode_user(user)
return self.client_delete(
@ -1247,7 +1247,7 @@ Output:
self,
user: UserProfile,
streams: Iterable[str],
extra_post_data: Dict[str, Any] = {},
extra_post_data: Mapping[str, Any] = {},
invite_only: bool = False,
is_web_public: bool = False,
allow_fail: bool = False,

View File

@ -14,6 +14,7 @@ from typing import (
Iterable,
Iterator,
List,
Mapping,
Optional,
Tuple,
TypeVar,
@ -305,7 +306,7 @@ class HostRequestMock(HttpRequest):
def __init__(
self,
post_data: Dict[str, Any] = {},
post_data: Mapping[str, Any] = {},
user_profile: Union[UserProfile, None] = None,
remote_server: Optional[RemoteZulipServer] = None,
host: str = settings.EXTERNAL_HOST,

View File

@ -1,7 +1,7 @@
import re
import unicodedata
from collections import defaultdict
from typing import Any, Dict, Iterable, List, Optional, Sequence, TypedDict
from typing import Any, Dict, Iterable, List, Mapping, Optional, Sequence, TypedDict
import dateutil.parser as date_parser
from django.conf import settings
@ -79,7 +79,9 @@ def check_short_name(short_name_raw: str) -> str:
return short_name
def check_valid_bot_config(bot_type: int, service_name: str, config_data: Dict[str, str]) -> None:
def check_valid_bot_config(
bot_type: int, service_name: str, config_data: Mapping[str, str]
) -> None:
if bot_type == UserProfile.INCOMING_WEBHOOK_BOT:
from zerver.lib.integrations import WEBHOOK_INTEGRATIONS

View File

@ -539,7 +539,7 @@ SKIP_JSON = {
def validate_request(
url: str,
method: str,
data: Union[str, bytes, Dict[str, Any]],
data: Union[str, bytes, Mapping[str, Any]],
http_headers: Dict[str, str],
json_url: bool,
status_code: str,

View File

@ -3122,7 +3122,7 @@ class AppleAuthBackendNativeFlowTest(AppleAuthMixin, SocialAuthBase):
multiuse_object_key: str = "",
alternative_start_url: Optional[str] = None,
id_token: Optional[str] = None,
account_data_dict: Dict[str, str] = {},
account_data_dict: Mapping[str, str] = {},
*,
user_agent: Optional[str] = None,
) -> Tuple[str, Dict[str, Any]]:

View File

@ -4,7 +4,7 @@ import datetime
import re
import uuid
from contextlib import contextmanager
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
from typing import Any, Dict, Iterator, List, Mapping, Optional, Tuple, Union
from unittest import mock, skipUnless
from urllib import parse
@ -1540,7 +1540,7 @@ class TestAPNs(PushNotificationTest):
def send(
self,
devices: Optional[List[Union[PushDeviceToken, RemotePushDeviceToken]]] = None,
payload_data: Dict[str, Any] = {},
payload_data: Mapping[str, Any] = {},
) -> None:
send_apple_push_notification(
UserPushIndentityCompat(user_id=self.user_profile.id),

View File

@ -517,7 +517,7 @@ def oauth_redirect_to_root(
url: str,
sso_type: str,
is_signup: bool = False,
extra_url_params: Dict[str, str] = {},
extra_url_params: Mapping[str, str] = {},
next: Optional[str] = REQ(default=None),
multiuse_object_key: str = REQ(default=""),
mobile_flow_otp: Optional[str] = REQ(default=None),

View File

@ -1,5 +1,5 @@
from email.headerregistry import Address
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Mapping, Optional, Union
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
@ -467,7 +467,7 @@ def add_bot_backend(
bot_type: int = REQ(json_validator=check_int, default=UserProfile.DEFAULT_BOT),
payload_url: str = REQ(json_validator=check_url, default=""),
service_name: Optional[str] = REQ(default=None),
config_data: Dict[str, str] = REQ(
config_data: Mapping[str, str] = REQ(
default={}, json_validator=check_dict(value_validator=check_string)
),
interface_type: int = REQ(json_validator=check_int, default=Service.GENERIC),

View File

@ -1,5 +1,5 @@
from datetime import datetime, timezone
from typing import Any, Callable, Dict, List, Tuple
from typing import Any, Callable, Dict, List, Mapping, Tuple
import orjson
from django.http import HttpRequest, HttpResponse
@ -21,7 +21,7 @@ SNAPSHOT = "image_url"
class LibratoWebhookParser:
ALERT_URL_TEMPLATE = "https://metrics.librato.com/alerts#/{alert_id}"
def __init__(self, payload: Dict[str, Any], attachments: List[Dict[str, Any]]) -> None:
def __init__(self, payload: Mapping[str, Any], attachments: List[Dict[str, Any]]) -> None:
self.payload = payload
self.attachments = attachments
@ -65,7 +65,7 @@ class LibratoWebhookParser:
class LibratoWebhookHandler(LibratoWebhookParser):
def __init__(self, payload: Dict[str, Any], attachments: List[Dict[str, Any]]) -> None:
def __init__(self, payload: Mapping[str, Any], attachments: List[Dict[str, Any]]) -> None:
super().__init__(payload, attachments)
self.payload_available_types = {
ALERT_CLEAR: self.handle_alert_clear_message,
@ -163,7 +163,7 @@ class LibratoWebhookHandler(LibratoWebhookParser):
def api_librato_webhook(
request: HttpRequest,
user_profile: UserProfile,
payload: Dict[str, Any] = REQ(json_validator=check_dict(), default={}),
payload: Mapping[str, Any] = REQ(json_validator=check_dict(), default={}),
) -> HttpResponse:
try:
attachments = orjson.loads(request.body).get("attachments", [])