ruff: Fix UP006 Use `list` instead of `List` for type annotation.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2024-07-11 17:30:17 -07:00 committed by Tim Abbott
parent c2214b3904
commit e08a24e47f
457 changed files with 3588 additions and 3857 deletions

View File

@ -2,7 +2,7 @@ import logging
import time import time
from collections import OrderedDict, defaultdict from collections import OrderedDict, defaultdict
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Callable, Dict, List, Optional, Sequence, Tuple, Type, Union from typing import Callable, Optional, Sequence, Union
from django.conf import settings from django.conf import settings
from django.db import connection, models from django.db import connection, models
@ -82,7 +82,7 @@ class CountStat:
class LoggingCountStat(CountStat): class LoggingCountStat(CountStat):
def __init__(self, property: str, output_table: Type[BaseCount], frequency: str) -> None: def __init__(self, property: str, output_table: type[BaseCount], frequency: str) -> None:
CountStat.__init__(self, property, DataCollector(output_table, None), frequency) CountStat.__init__(self, property, DataCollector(output_table, None), frequency)
@ -102,7 +102,7 @@ class DependentCountStat(CountStat):
class DataCollector: class DataCollector:
def __init__( def __init__(
self, self,
output_table: Type[BaseCount], output_table: type[BaseCount],
pull_function: Optional[Callable[[str, datetime, datetime, Optional[Realm]], int]], pull_function: Optional[Callable[[str, datetime, datetime, Optional[Realm]], int]],
) -> None: ) -> None:
self.output_table = output_table self.output_table = output_table
@ -311,8 +311,8 @@ def do_increment_logging_stat(
return return
table = stat.data_collector.output_table table = stat.data_collector.output_table
id_args: Dict[str, Union[int, None]] = {} id_args: dict[str, Union[int, None]] = {}
conflict_args: List[str] = [] conflict_args: list[str] = []
if table == RealmCount: if table == RealmCount:
assert isinstance(model_object_for_bucket, Realm) assert isinstance(model_object_for_bucket, Realm)
id_args = {"realm_id": model_object_for_bucket.id} id_args = {"realm_id": model_object_for_bucket.id}
@ -425,7 +425,7 @@ def do_drop_single_stat(property: str) -> None:
## DataCollector-level operations ## ## DataCollector-level operations ##
QueryFn: TypeAlias = Callable[[Dict[str, Composable]], Composable] QueryFn: TypeAlias = Callable[[dict[str, Composable]], Composable]
def do_pull_by_sql_query( def do_pull_by_sql_query(
@ -433,7 +433,7 @@ def do_pull_by_sql_query(
start_time: datetime, start_time: datetime,
end_time: datetime, end_time: datetime,
query: QueryFn, query: QueryFn,
group_by: Optional[Tuple[Type[models.Model], str]], group_by: Optional[tuple[type[models.Model], str]],
) -> int: ) -> int:
if group_by is None: if group_by is None:
subgroup: Composable = SQL("NULL") subgroup: Composable = SQL("NULL")
@ -467,9 +467,9 @@ def do_pull_by_sql_query(
def sql_data_collector( def sql_data_collector(
output_table: Type[BaseCount], output_table: type[BaseCount],
query: QueryFn, query: QueryFn,
group_by: Optional[Tuple[Type[models.Model], str]], group_by: Optional[tuple[type[models.Model], str]],
) -> DataCollector: ) -> DataCollector:
def pull_function( def pull_function(
property: str, start_time: datetime, end_time: datetime, realm: Optional[Realm] = None property: str, start_time: datetime, end_time: datetime, realm: Optional[Realm] = None
@ -533,7 +533,7 @@ def do_pull_minutes_active(
.values_list("user_profile_id", "user_profile__realm_id", "start", "end") .values_list("user_profile_id", "user_profile__realm_id", "start", "end")
) )
seconds_active: Dict[Tuple[int, int], float] = defaultdict(float) seconds_active: dict[tuple[int, int], float] = defaultdict(float)
for user_id, realm_id, interval_start, interval_end in user_activity_intervals: for user_id, realm_id, interval_start, interval_end in user_activity_intervals:
if realm is None or realm.id == realm_id: if realm is None or realm.id == realm_id:
start = max(start_time, interval_start) start = max(start_time, interval_start)
@ -817,7 +817,7 @@ count_stream_by_realm_query = lambda kwargs: SQL(
).format(**kwargs) ).format(**kwargs)
def get_count_stats(realm: Optional[Realm] = None) -> Dict[str, CountStat]: def get_count_stats(realm: Optional[Realm] = None) -> dict[str, CountStat]:
## CountStat declarations ## ## CountStat declarations ##
count_stats_ = [ count_stats_ = [

View File

@ -1,6 +1,5 @@
from math import sqrt from math import sqrt
from random import Random from random import Random
from typing import List
from analytics.lib.counts import CountStat from analytics.lib.counts import CountStat
@ -16,7 +15,7 @@ def generate_time_series_data(
frequency: str = CountStat.DAY, frequency: str = CountStat.DAY,
partial_sum: bool = False, partial_sum: bool = False,
random_seed: int = 26, random_seed: int = 26,
) -> List[int]: ) -> list[int]:
""" """
Generate semi-realistic looking time series data for testing analytics graphs. Generate semi-realistic looking time series data for testing analytics graphs.

View File

@ -1,5 +1,5 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import List, Optional from typing import Optional
from analytics.lib.counts import CountStat from analytics.lib.counts import CountStat
from zerver.lib.timestamp import floor_to_day, floor_to_hour, verify_UTC from zerver.lib.timestamp import floor_to_day, floor_to_hour, verify_UTC
@ -11,7 +11,7 @@ from zerver.lib.timestamp import floor_to_day, floor_to_hour, verify_UTC
# and time_range(Sep 20, Sep 22, day, 5) returns [Sep 18, Sep 19, Sep 20, Sep 21, Sep 22] # and time_range(Sep 20, Sep 22, day, 5) returns [Sep 18, Sep 19, Sep 20, Sep 21, Sep 22]
def time_range( def time_range(
start: datetime, end: datetime, frequency: str, min_length: Optional[int] start: datetime, end: datetime, frequency: str, min_length: Optional[int]
) -> List[datetime]: ) -> list[datetime]:
verify_UTC(start) verify_UTC(start)
verify_UTC(end) verify_UTC(end)
if frequency == CountStat.HOUR: if frequency == CountStat.HOUR:

View File

@ -1,5 +1,5 @@
from datetime import timedelta from datetime import timedelta
from typing import Any, Dict, List, Mapping, Type, Union from typing import Any, Mapping, Union
from django.core.files.uploadedfile import UploadedFile from django.core.files.uploadedfile import UploadedFile
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
@ -53,7 +53,7 @@ class Command(ZulipBaseCommand):
spikiness: float, spikiness: float,
holiday_rate: float = 0, holiday_rate: float = 0,
partial_sum: bool = False, partial_sum: bool = False,
) -> List[int]: ) -> list[int]:
self.random_seed += 1 self.random_seed += 1
return generate_time_series_data( return generate_time_series_data(
days=self.DAYS_OF_DATA, days=self.DAYS_OF_DATA,
@ -147,18 +147,18 @@ class Command(ZulipBaseCommand):
with open(IMAGE_FILE_PATH, "rb") as fp: with open(IMAGE_FILE_PATH, "rb") as fp:
upload_message_attachment_from_request(UploadedFile(fp), shylock) upload_message_attachment_from_request(UploadedFile(fp), shylock)
FixtureData: TypeAlias = Mapping[Union[str, int, None], List[int]] FixtureData: TypeAlias = Mapping[Union[str, int, None], list[int]]
def insert_fixture_data( def insert_fixture_data(
stat: CountStat, stat: CountStat,
fixture_data: FixtureData, fixture_data: FixtureData,
table: Type[BaseCount], table: type[BaseCount],
) -> None: ) -> None:
end_times = time_range( end_times = time_range(
last_end_time, last_end_time, stat.frequency, len(next(iter(fixture_data.values()))) last_end_time, last_end_time, stat.frequency, len(next(iter(fixture_data.values())))
) )
if table == InstallationCount: if table == InstallationCount:
id_args: Dict[str, Any] = {} id_args: dict[str, Any] = {}
if table == RealmCount: if table == RealmCount:
id_args = {"realm": realm} id_args = {"realm": realm}
if table == UserCount: if table == UserCount:
@ -330,7 +330,7 @@ class Command(ZulipBaseCommand):
"true": self.generate_fixture_data(stat, 20, 2, 3, 0.2, 3), "true": self.generate_fixture_data(stat, 20, 2, 3, 0.2, 3),
} }
insert_fixture_data(stat, realm_data, RealmCount) insert_fixture_data(stat, realm_data, RealmCount)
stream_data: Mapping[Union[int, str, None], List[int]] = { stream_data: Mapping[Union[int, str, None], list[int]] = {
"false": self.generate_fixture_data(stat, 10, 7, 5, 0.6, 4), "false": self.generate_fixture_data(stat, 10, 7, 5, 0.6, 4),
"true": self.generate_fixture_data(stat, 5, 3, 2, 0.4, 2), "true": self.generate_fixture_data(stat, 5, 3, 2, 0.4, 2),
} }

View File

@ -2,7 +2,7 @@ import hashlib
import time import time
from argparse import ArgumentParser from argparse import ArgumentParser
from datetime import timezone from datetime import timezone
from typing import Any, Dict from typing import Any
from django.conf import settings from django.conf import settings
from django.utils.dateparse import parse_datetime from django.utils.dateparse import parse_datetime
@ -43,7 +43,7 @@ class Command(ZulipBaseCommand):
def handle(self, *args: Any, **options: Any) -> None: def handle(self, *args: Any, **options: Any) -> None:
self.run_update_analytics_counts(options) self.run_update_analytics_counts(options)
def run_update_analytics_counts(self, options: Dict[str, Any]) -> None: def run_update_analytics_counts(self, options: dict[str, Any]) -> None:
# installation_epoch relies on there being at least one realm; we # installation_epoch relies on there being at least one realm; we
# shouldn't run the analytics code if that condition isn't satisfied # shouldn't run the analytics code if that condition isn't satisfied
if not Realm.objects.exists(): if not Realm.objects.exists():

View File

@ -1,6 +1,6 @@
from contextlib import AbstractContextManager, ExitStack, contextmanager from contextlib import AbstractContextManager, ExitStack, contextmanager
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Any, Dict, Iterator, List, Optional, Tuple, Type from typing import Any, Iterator, Optional
from unittest import mock from unittest import mock
import time_machine import time_machine
@ -132,7 +132,7 @@ class AnalyticsTestCase(ZulipTestCase):
kwargs[key] = kwargs.get(key, value) kwargs[key] = kwargs.get(key, value)
kwargs["delivery_email"] = kwargs["email"] kwargs["delivery_email"] = kwargs["email"]
with time_machine.travel(kwargs["date_joined"], tick=False): with time_machine.travel(kwargs["date_joined"], tick=False):
pass_kwargs: Dict[str, Any] = {} pass_kwargs: dict[str, Any] = {}
if kwargs["is_bot"]: if kwargs["is_bot"]:
pass_kwargs["bot_type"] = UserProfile.DEFAULT_BOT pass_kwargs["bot_type"] = UserProfile.DEFAULT_BOT
pass_kwargs["bot_owner"] = None pass_kwargs["bot_owner"] = None
@ -158,7 +158,7 @@ class AnalyticsTestCase(ZulipTestCase):
) )
return user return user
def create_stream_with_recipient(self, **kwargs: Any) -> Tuple[Stream, Recipient]: def create_stream_with_recipient(self, **kwargs: Any) -> tuple[Stream, Recipient]:
self.name_counter += 1 self.name_counter += 1
defaults = { defaults = {
"name": f"stream name {self.name_counter}", "name": f"stream name {self.name_counter}",
@ -174,7 +174,7 @@ class AnalyticsTestCase(ZulipTestCase):
stream.save(update_fields=["recipient"]) stream.save(update_fields=["recipient"])
return stream, recipient return stream, recipient
def create_huddle_with_recipient(self, **kwargs: Any) -> Tuple[DirectMessageGroup, Recipient]: def create_huddle_with_recipient(self, **kwargs: Any) -> tuple[DirectMessageGroup, Recipient]:
self.name_counter += 1 self.name_counter += 1
defaults = {"huddle_hash": f"hash{self.name_counter}"} defaults = {"huddle_hash": f"hash{self.name_counter}"}
for key, value in defaults.items(): for key, value in defaults.items():
@ -224,7 +224,7 @@ class AnalyticsTestCase(ZulipTestCase):
# kwargs should only ever be a UserProfile or Stream. # kwargs should only ever be a UserProfile or Stream.
def assert_table_count( def assert_table_count(
self, self,
table: Type[BaseCount], table: type[BaseCount],
value: int, value: int,
property: Optional[str] = None, property: Optional[str] = None,
subgroup: Optional[str] = None, subgroup: Optional[str] = None,
@ -246,7 +246,7 @@ class AnalyticsTestCase(ZulipTestCase):
self.assertEqual(queryset.values_list("value", flat=True)[0], value) self.assertEqual(queryset.values_list("value", flat=True)[0], value)
def assertTableState( def assertTableState(
self, table: Type[BaseCount], arg_keys: List[str], arg_values: List[List[object]] self, table: type[BaseCount], arg_keys: list[str], arg_values: list[list[object]]
) -> None: ) -> None:
"""Assert that the state of a *Count table is what it should be. """Assert that the state of a *Count table is what it should be.
@ -276,7 +276,7 @@ class AnalyticsTestCase(ZulipTestCase):
"value": 1, "value": 1,
} }
for values in arg_values: for values in arg_values:
kwargs: Dict[str, Any] = {} kwargs: dict[str, Any] = {}
for i in range(len(values)): for i in range(len(values)):
kwargs[arg_keys[i]] = values[i] kwargs[arg_keys[i]] = values[i]
for key, value in defaults.items(): for key, value in defaults.items():
@ -1619,7 +1619,7 @@ class TestLoggingCountStats(AnalyticsTestCase):
def invite_context( def invite_context(
too_many_recent_realm_invites: bool = False, failure: bool = False too_many_recent_realm_invites: bool = False, failure: bool = False
) -> Iterator[None]: ) -> Iterator[None]:
managers: List[AbstractContextManager[Any]] = [ managers: list[AbstractContextManager[Any]] = [
mock.patch( mock.patch(
"zerver.actions.invites.too_many_recent_realm_invites", return_value=False "zerver.actions.invites.too_many_recent_realm_invites", return_value=False
), ),

View File

@ -1,5 +1,5 @@
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import List, Optional from typing import Optional
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from typing_extensions import override from typing_extensions import override
@ -84,11 +84,11 @@ class TestGetChartData(ZulipTestCase):
ceiling_to_day(self.realm.date_created) + timedelta(days=i) for i in range(4) ceiling_to_day(self.realm.date_created) + timedelta(days=i) for i in range(4)
] ]
def data(self, i: int) -> List[int]: def data(self, i: int) -> list[int]:
return [0, 0, i, 0] return [0, 0, i, 0]
def insert_data( def insert_data(
self, stat: CountStat, realm_subgroups: List[Optional[str]], user_subgroups: List[str] self, stat: CountStat, realm_subgroups: list[Optional[str]], user_subgroups: list[str]
) -> None: ) -> None:
if stat.frequency == CountStat.HOUR: if stat.frequency == CountStat.HOUR:
insert_time = self.end_times_hour[2] insert_time = self.end_times_hour[2]
@ -605,7 +605,7 @@ class TestGetChartData(ZulipTestCase):
class TestGetChartDataHelpers(ZulipTestCase): class TestGetChartDataHelpers(ZulipTestCase):
def test_sort_by_totals(self) -> None: def test_sort_by_totals(self) -> None:
empty: List[int] = [] empty: list[int] = []
value_arrays = {"c": [0, 1], "a": [9], "b": [1, 1, 1], "d": empty} value_arrays = {"c": [0, 1], "a": [9], "b": [1, 1, 1], "d": empty}
self.assertEqual(sort_by_totals(value_arrays), ["a", "b", "c", "d"]) self.assertEqual(sort_by_totals(value_arrays), ["a", "b", "c", "d"])

View File

@ -1,4 +1,4 @@
from typing import List, Union from typing import Union
from django.conf import settings from django.conf import settings
from django.conf.urls import include from django.conf.urls import include
@ -16,7 +16,7 @@ from analytics.views.stats import (
) )
from zerver.lib.rest import rest_path from zerver.lib.rest import rest_path
i18n_urlpatterns: List[Union[URLPattern, URLResolver]] = [ i18n_urlpatterns: list[Union[URLPattern, URLResolver]] = [
# Server admin (user_profile.is_staff) visible stats pages # Server admin (user_profile.is_staff) visible stats pages
path("stats/realm/<realm_str>/", stats_for_realm), path("stats/realm/<realm_str>/", stats_for_realm),
path("stats/installation", stats_for_installation), path("stats/installation", stats_for_installation),

View File

@ -1,7 +1,7 @@
import logging import logging
from collections import defaultdict from collections import defaultdict
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar, Union, cast from typing import Any, Optional, TypeVar, Union, cast
from django.conf import settings from django.conf import settings
from django.db.models import QuerySet from django.db.models import QuerySet
@ -260,10 +260,10 @@ def get_chart_data(
stream: Optional[Stream] = None, stream: Optional[Stream] = None,
) -> HttpResponse: ) -> HttpResponse:
TableType: TypeAlias = Union[ TableType: TypeAlias = Union[
Type["RemoteInstallationCount"], type["RemoteInstallationCount"],
Type[InstallationCount], type[InstallationCount],
Type["RemoteRealmCount"], type["RemoteRealmCount"],
Type[RealmCount], type[RealmCount],
] ]
if for_installation: if for_installation:
if remote: if remote:
@ -282,7 +282,7 @@ def get_chart_data(
aggregate_table = RealmCount aggregate_table = RealmCount
tables: Union[ tables: Union[
Tuple[TableType], Tuple[TableType, Type[UserCount]], Tuple[TableType, Type[StreamCount]] tuple[TableType], tuple[TableType, type[UserCount]], tuple[TableType, type[StreamCount]]
] ]
if chart_name == "number_of_humans": if chart_name == "number_of_humans":
@ -292,7 +292,7 @@ def get_chart_data(
COUNT_STATS["active_users_audit:is_bot:day"], COUNT_STATS["active_users_audit:is_bot:day"],
] ]
tables = (aggregate_table,) tables = (aggregate_table,)
subgroup_to_label: Dict[CountStat, Dict[Optional[str], str]] = { subgroup_to_label: dict[CountStat, dict[Optional[str], str]] = {
stats[0]: {None: "_1day"}, stats[0]: {None: "_1day"},
stats[1]: {None: "_15day"}, stats[1]: {None: "_15day"},
stats[2]: {"false": "all_time"}, stats[2]: {"false": "all_time"},
@ -372,7 +372,7 @@ def get_chart_data(
assert server is not None assert server is not None
assert aggregate_table is RemoteInstallationCount or aggregate_table is RemoteRealmCount assert aggregate_table is RemoteInstallationCount or aggregate_table is RemoteRealmCount
aggregate_table_remote = cast( aggregate_table_remote = cast(
Union[Type[RemoteInstallationCount], Type[RemoteRealmCount]], aggregate_table Union[type[RemoteInstallationCount], type[RemoteRealmCount]], aggregate_table
) # https://stackoverflow.com/questions/68540528/mypy-assertions-on-the-types-of-types ) # https://stackoverflow.com/questions/68540528/mypy-assertions-on-the-types-of-types
if not aggregate_table_remote.objects.filter(server=server).exists(): if not aggregate_table_remote.objects.filter(server=server).exists():
raise JsonableError( raise JsonableError(
@ -418,7 +418,7 @@ def get_chart_data(
assert len({stat.frequency for stat in stats}) == 1 assert len({stat.frequency for stat in stats}) == 1
end_times = time_range(start, end, stats[0].frequency, min_length) end_times = time_range(start, end, stats[0].frequency, min_length)
data: Dict[str, Any] = { data: dict[str, Any] = {
"end_times": [int(end_time.timestamp()) for end_time in end_times], "end_times": [int(end_time.timestamp()) for end_time in end_times],
"frequency": stats[0].frequency, "frequency": stats[0].frequency,
} }
@ -471,7 +471,7 @@ def get_chart_data(
return json_success(request, data=data) return json_success(request, data=data)
def sort_by_totals(value_arrays: Dict[str, List[int]]) -> List[str]: def sort_by_totals(value_arrays: dict[str, list[int]]) -> list[str]:
totals = sorted(((sum(values), label) for label, values in value_arrays.items()), reverse=True) totals = sorted(((sum(values), label) for label, values in value_arrays.items()), reverse=True)
return [label for total, label in totals] return [label for total, label in totals]
@ -482,10 +482,10 @@ def sort_by_totals(value_arrays: Dict[str, List[int]]) -> List[str]:
# understanding the realm's traffic and the user's traffic. This function # understanding the realm's traffic and the user's traffic. This function
# tries to rank the clients so that taking the first N elements of the # tries to rank the clients so that taking the first N elements of the
# sorted list has a reasonable chance of doing so. # sorted list has a reasonable chance of doing so.
def sort_client_labels(data: Dict[str, Dict[str, List[int]]]) -> List[str]: def sort_client_labels(data: dict[str, dict[str, list[int]]]) -> list[str]:
realm_order = sort_by_totals(data["everyone"]) realm_order = sort_by_totals(data["everyone"])
user_order = sort_by_totals(data["user"]) user_order = sort_by_totals(data["user"])
label_sort_values: Dict[str, float] = {label: i for i, label in enumerate(realm_order)} label_sort_values: dict[str, float] = {label: i for i, label in enumerate(realm_order)}
for i, label in enumerate(user_order): for i, label in enumerate(user_order):
label_sort_values[label] = min(i - 0.1, label_sort_values.get(label, i)) label_sort_values[label] = min(i - 0.1, label_sort_values.get(label, i))
return [label for label, sort_value in sorted(label_sort_values.items(), key=lambda x: x[1])] return [label for label, sort_value in sorted(label_sort_values.items(), key=lambda x: x[1])]
@ -494,7 +494,7 @@ def sort_client_labels(data: Dict[str, Dict[str, List[int]]]) -> List[str]:
CountT = TypeVar("CountT", bound=BaseCount) CountT = TypeVar("CountT", bound=BaseCount)
def table_filtered_to_id(table: Type[CountT], key_id: int) -> QuerySet[CountT]: def table_filtered_to_id(table: type[CountT], key_id: int) -> QuerySet[CountT]:
if table == RealmCount: if table == RealmCount:
return table._default_manager.filter(realm_id=key_id) return table._default_manager.filter(realm_id=key_id)
elif table == UserCount: elif table == UserCount:
@ -535,8 +535,8 @@ def client_label_map(name: str) -> str:
return name return name
def rewrite_client_arrays(value_arrays: Dict[str, List[int]]) -> Dict[str, List[int]]: def rewrite_client_arrays(value_arrays: dict[str, list[int]]) -> dict[str, list[int]]:
mapped_arrays: Dict[str, List[int]] = {} mapped_arrays: dict[str, list[int]] = {}
for label, array in value_arrays.items(): for label, array in value_arrays.items():
mapped_label = client_label_map(label) mapped_label = client_label_map(label)
if mapped_label in mapped_arrays: if mapped_label in mapped_arrays:
@ -549,18 +549,18 @@ def rewrite_client_arrays(value_arrays: Dict[str, List[int]]) -> Dict[str, List[
def get_time_series_by_subgroup( def get_time_series_by_subgroup(
stat: CountStat, stat: CountStat,
table: Type[BaseCount], table: type[BaseCount],
key_id: int, key_id: int,
end_times: List[datetime], end_times: list[datetime],
subgroup_to_label: Dict[Optional[str], str], subgroup_to_label: dict[Optional[str], str],
include_empty_subgroups: bool, include_empty_subgroups: bool,
) -> Dict[str, List[int]]: ) -> dict[str, list[int]]:
queryset = ( queryset = (
table_filtered_to_id(table, key_id) table_filtered_to_id(table, key_id)
.filter(property=stat.property) .filter(property=stat.property)
.values_list("subgroup", "end_time", "value") .values_list("subgroup", "end_time", "value")
) )
value_dicts: Dict[Optional[str], Dict[datetime, int]] = defaultdict(lambda: defaultdict(int)) value_dicts: dict[Optional[str], dict[datetime, int]] = defaultdict(lambda: defaultdict(int))
for subgroup, end_time, value in queryset: for subgroup, end_time, value in queryset:
value_dicts[subgroup][end_time] = value value_dicts[subgroup][end_time] = value
value_arrays = {} value_arrays = {}

View File

@ -4,7 +4,7 @@ __revision__ = "$Id: models.py 28 2009-10-22 15:03:02Z jarek.zgoda $"
import secrets import secrets
from base64 import b32encode from base64 import b32encode
from datetime import timedelta from datetime import timedelta
from typing import List, Mapping, Optional, Union, cast from typing import Mapping, Optional, Union, cast
from urllib.parse import urljoin from urllib.parse import urljoin
from django.conf import settings from django.conf import settings
@ -80,7 +80,7 @@ ConfirmationObjT: TypeAlias = Union[NoZilencerConfirmationObjT, ZilencerConfirma
def get_object_from_key( def get_object_from_key(
confirmation_key: str, confirmation_types: List[int], *, mark_object_used: bool confirmation_key: str, confirmation_types: list[int], *, mark_object_used: bool
) -> ConfirmationObjT: ) -> ConfirmationObjT:
"""Access a confirmation object from one of the provided confirmation """Access a confirmation object from one of the provided confirmation
types with the provided key. types with the provided key.

View File

@ -2,7 +2,7 @@ from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from decimal import Decimal from decimal import Decimal
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union from typing import Any, Callable, Optional, Sequence, Union
from urllib.parse import urlencode from urllib.parse import urlencode
from django.conf import settings from django.conf import settings
@ -52,7 +52,7 @@ def make_table(
) -> str: ) -> str:
if not has_row_class: if not has_row_class:
def fix_row(row: Any) -> Dict[str, Any]: def fix_row(row: Any) -> dict[str, Any]:
return dict(cells=row, row_class=None) return dict(cells=row, row_class=None)
rows = list(map(fix_row, rows)) rows = list(map(fix_row, rows))
@ -68,7 +68,7 @@ def make_table(
def fix_rows( def fix_rows(
rows: List[List[Any]], rows: list[list[Any]],
i: int, i: int,
fixup_func: Union[Callable[[str], Markup], Callable[[datetime], str], Callable[[int], int]], fixup_func: Union[Callable[[str], Markup], Callable[[datetime], str], Callable[[int], int]],
) -> None: ) -> None:
@ -76,7 +76,7 @@ def fix_rows(
row[i] = fixup_func(row[i]) row[i] = fixup_func(row[i])
def get_query_data(query: Composable) -> List[List[Any]]: def get_query_data(query: Composable) -> list[list[Any]]:
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute(query) cursor.execute(query)
rows = cursor.fetchall() rows = cursor.fetchall()
@ -85,7 +85,7 @@ def get_query_data(query: Composable) -> List[List[Any]]:
return rows return rows
def dictfetchall(cursor: CursorWrapper) -> List[Dict[str, Any]]: def dictfetchall(cursor: CursorWrapper) -> list[dict[str, Any]]:
"""Returns all rows from a cursor as a dict""" """Returns all rows from a cursor as a dict"""
desc = cursor.description desc = cursor.description
return [dict(zip((col[0] for col in desc), row)) for row in cursor.fetchall()] return [dict(zip((col[0] for col in desc), row)) for row in cursor.fetchall()]
@ -208,7 +208,7 @@ def get_remote_activity_plan_data(
) )
def get_estimated_arr_and_rate_by_realm() -> Tuple[Dict[str, int], Dict[str, str]]: # nocoverage def get_estimated_arr_and_rate_by_realm() -> tuple[dict[str, int], dict[str, str]]: # nocoverage
# NOTE: Customers without a plan might still have a discount attached to them which # NOTE: Customers without a plan might still have a discount attached to them which
# are not included in `plan_rate`. # are not included in `plan_rate`.
annual_revenue = {} annual_revenue = {}
@ -241,8 +241,8 @@ def get_estimated_arr_and_rate_by_realm() -> Tuple[Dict[str, int], Dict[str, str
return annual_revenue, plan_rate return annual_revenue, plan_rate
def get_plan_data_by_remote_server() -> Dict[int, RemoteActivityPlanData]: # nocoverage def get_plan_data_by_remote_server() -> dict[int, RemoteActivityPlanData]: # nocoverage
remote_server_plan_data: Dict[int, RemoteActivityPlanData] = {} remote_server_plan_data: dict[int, RemoteActivityPlanData] = {}
plans = ( plans = (
CustomerPlan.objects.filter( CustomerPlan.objects.filter(
status__lt=CustomerPlan.LIVE_STATUS_THRESHOLD, status__lt=CustomerPlan.LIVE_STATUS_THRESHOLD,
@ -290,8 +290,8 @@ def get_plan_data_by_remote_server() -> Dict[int, RemoteActivityPlanData]: # no
return remote_server_plan_data return remote_server_plan_data
def get_plan_data_by_remote_realm() -> Dict[int, Dict[int, RemoteActivityPlanData]]: # nocoverage def get_plan_data_by_remote_realm() -> dict[int, dict[int, RemoteActivityPlanData]]: # nocoverage
remote_server_plan_data_by_realm: Dict[int, Dict[int, RemoteActivityPlanData]] = {} remote_server_plan_data_by_realm: dict[int, dict[int, RemoteActivityPlanData]] = {}
plans = ( plans = (
CustomerPlan.objects.filter( CustomerPlan.objects.filter(
status__lt=CustomerPlan.LIVE_STATUS_THRESHOLD, status__lt=CustomerPlan.LIVE_STATUS_THRESHOLD,
@ -351,8 +351,8 @@ def get_plan_data_by_remote_realm() -> Dict[int, Dict[int, RemoteActivityPlanDat
def get_remote_realm_user_counts( def get_remote_realm_user_counts(
event_time: datetime = timezone_now(), event_time: datetime = timezone_now(),
) -> Dict[int, RemoteCustomerUserCount]: # nocoverage ) -> dict[int, RemoteCustomerUserCount]: # nocoverage
user_counts_by_realm: Dict[int, RemoteCustomerUserCount] = {} user_counts_by_realm: dict[int, RemoteCustomerUserCount] = {}
for log in ( for log in (
RemoteRealmAuditLog.objects.filter( RemoteRealmAuditLog.objects.filter(
event_type__in=RemoteRealmAuditLog.SYNCED_BILLING_EVENTS, event_type__in=RemoteRealmAuditLog.SYNCED_BILLING_EVENTS,
@ -378,8 +378,8 @@ def get_remote_realm_user_counts(
def get_remote_server_audit_logs( def get_remote_server_audit_logs(
event_time: datetime = timezone_now(), event_time: datetime = timezone_now(),
) -> Dict[int, List[RemoteRealmAuditLog]]: ) -> dict[int, list[RemoteRealmAuditLog]]:
logs_per_server: Dict[int, List[RemoteRealmAuditLog]] = defaultdict(list) logs_per_server: dict[int, list[RemoteRealmAuditLog]] = defaultdict(list)
for log in ( for log in (
RemoteRealmAuditLog.objects.filter( RemoteRealmAuditLog.objects.filter(
event_type__in=RemoteRealmAuditLog.SYNCED_BILLING_EVENTS, event_type__in=RemoteRealmAuditLog.SYNCED_BILLING_EVENTS,

View File

@ -1,5 +1,5 @@
import logging import logging
from typing import Literal, Optional, Tuple, TypedDict, Union, cast from typing import Literal, Optional, TypedDict, Union, cast
from django.http import HttpRequest from django.http import HttpRequest
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
@ -102,7 +102,7 @@ def get_identity_dict_from_session(
def get_remote_realm_and_user_from_session( def get_remote_realm_and_user_from_session(
request: HttpRequest, request: HttpRequest,
realm_uuid: Optional[str], realm_uuid: Optional[str],
) -> Tuple[RemoteRealm, RemoteRealmBillingUser]: ) -> tuple[RemoteRealm, RemoteRealmBillingUser]:
# Cannot use isinstance with TypeDicts, to make mypy know # Cannot use isinstance with TypeDicts, to make mypy know
# which of the TypedDicts in the Union this is - so just cast it. # which of the TypedDicts in the Union this is - so just cast it.
identity_dict = cast( identity_dict = cast(
@ -151,7 +151,7 @@ def get_remote_realm_and_user_from_session(
def get_remote_server_and_user_from_session( def get_remote_server_and_user_from_session(
request: HttpRequest, request: HttpRequest,
server_uuid: str, server_uuid: str,
) -> Tuple[RemoteZulipServer, Optional[RemoteServerBillingUser]]: ) -> tuple[RemoteZulipServer, Optional[RemoteServerBillingUser]]:
identity_dict: Optional[LegacyServerIdentityDict] = get_identity_dict_from_session( identity_dict: Optional[LegacyServerIdentityDict] = get_identity_dict_from_session(
request, realm_uuid=None, server_uuid=server_uuid request, realm_uuid=None, server_uuid=server_uuid
) )

View File

@ -8,18 +8,7 @@ from datetime import datetime, timedelta, timezone
from decimal import Decimal from decimal import Decimal
from enum import Enum from enum import Enum
from functools import wraps from functools import wraps
from typing import ( from typing import Any, Callable, Generator, Literal, Optional, TypedDict, TypeVar, Union
Any,
Callable,
Dict,
Generator,
Literal,
Optional,
Tuple,
TypedDict,
TypeVar,
Union,
)
from urllib.parse import urlencode, urljoin from urllib.parse import urlencode, urljoin
import stripe import stripe
@ -187,7 +176,7 @@ def get_seat_count(
return max(non_guests, math.ceil(guests / 5)) return max(non_guests, math.ceil(guests / 5))
def sign_string(string: str) -> Tuple[str, str]: def sign_string(string: str) -> tuple[str, str]:
salt = secrets.token_hex(32) salt = secrets.token_hex(32)
signer = Signer(salt=salt) signer = Signer(salt=salt)
return signer.sign(string), salt return signer.sign(string), salt
@ -541,7 +530,7 @@ class PriceArgs(TypedDict, total=False):
class StripeCustomerData: class StripeCustomerData:
description: str description: str
email: str email: str
metadata: Dict[str, Any] metadata: dict[str, Any]
@dataclass @dataclass
@ -754,7 +743,7 @@ class BillingSession(ABC):
event_time: datetime, event_time: datetime,
*, *,
background_update: bool = False, background_update: bool = False,
extra_data: Optional[Dict[str, Any]] = None, extra_data: Optional[dict[str, Any]] = None,
) -> None: ) -> None:
pass pass
@ -764,8 +753,8 @@ class BillingSession(ABC):
@abstractmethod @abstractmethod
def update_data_for_checkout_session_and_invoice_payment( def update_data_for_checkout_session_and_invoice_payment(
self, metadata: Dict[str, Any] self, metadata: dict[str, Any]
) -> Dict[str, Any]: ) -> dict[str, Any]:
pass pass
@abstractmethod @abstractmethod
@ -956,7 +945,7 @@ class BillingSession(ABC):
@abstractmethod @abstractmethod
def update_or_create_customer( def update_or_create_customer(
self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[Dict[str, Any]] = None self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[dict[str, Any]] = None
) -> Customer: ) -> Customer:
pass pass
@ -1013,11 +1002,11 @@ class BillingSession(ABC):
pass pass
@abstractmethod @abstractmethod
def add_sponsorship_info_to_context(self, context: Dict[str, Any]) -> None: def add_sponsorship_info_to_context(self, context: dict[str, Any]) -> None:
pass pass
@abstractmethod @abstractmethod
def get_metadata_for_stripe_update_card(self) -> Dict[str, str]: def get_metadata_for_stripe_update_card(self) -> dict[str, str]:
pass pass
@abstractmethod @abstractmethod
@ -1142,7 +1131,7 @@ class BillingSession(ABC):
def create_stripe_invoice_and_charge( def create_stripe_invoice_and_charge(
self, self,
metadata: Dict[str, Any], metadata: dict[str, Any],
) -> str: ) -> str:
""" """
Charge customer based on `billing_modality`. If `billing_modality` is `charge_automatically`, Charge customer based on `billing_modality`. If `billing_modality` is `charge_automatically`,
@ -1217,7 +1206,7 @@ class BillingSession(ABC):
self, self,
manual_license_management: bool, manual_license_management: bool,
tier: int, tier: int,
) -> Dict[str, Any]: ) -> dict[str, Any]:
metadata = self.get_metadata_for_stripe_update_card() metadata = self.get_metadata_for_stripe_update_card()
customer = self.update_or_create_stripe_customer() customer = self.update_or_create_stripe_customer()
assert customer.stripe_customer_id is not None assert customer.stripe_customer_id is not None
@ -1252,7 +1241,7 @@ class BillingSession(ABC):
"stripe_session_id": stripe_session.id, "stripe_session_id": stripe_session.id,
} }
def create_card_update_session(self) -> Dict[str, Any]: def create_card_update_session(self) -> dict[str, Any]:
metadata = self.get_metadata_for_stripe_update_card() metadata = self.get_metadata_for_stripe_update_card()
customer = self.get_customer() customer = self.get_customer()
assert customer is not None and customer.stripe_customer_id is not None assert customer is not None and customer.stripe_customer_id is not None
@ -1429,7 +1418,7 @@ class BillingSession(ABC):
required_plan_tier_name = CustomerPlan.name_from_tier(customer.required_plan_tier) required_plan_tier_name = CustomerPlan.name_from_tier(customer.required_plan_tier)
fixed_price_cents = fixed_price * 100 fixed_price_cents = fixed_price * 100
fixed_price_plan_params: Dict[str, Any] = { fixed_price_plan_params: dict[str, Any] = {
"fixed_price": fixed_price_cents, "fixed_price": fixed_price_cents,
"tier": customer.required_plan_tier, "tier": customer.required_plan_tier,
} }
@ -1591,7 +1580,7 @@ class BillingSession(ABC):
] ]
) )
def write_to_audit_log_plan_property_changed(extra_data: Dict[str, Any]) -> None: def write_to_audit_log_plan_property_changed(extra_data: dict[str, Any]) -> None:
extra_data["plan_id"] = plan.id extra_data["plan_id"] = plan.id
self.write_to_audit_log( self.write_to_audit_log(
event_type=AuditLogEventType.CUSTOMER_PLAN_PROPERTY_CHANGED, event_type=AuditLogEventType.CUSTOMER_PLAN_PROPERTY_CHANGED,
@ -1942,7 +1931,7 @@ class BillingSession(ABC):
current_plan_id=plan.id, current_plan_id=plan.id,
) )
def do_upgrade(self, upgrade_request: UpgradeRequest) -> Dict[str, Any]: def do_upgrade(self, upgrade_request: UpgradeRequest) -> dict[str, Any]:
customer = self.get_customer() customer = self.get_customer()
if customer is not None: if customer is not None:
self.ensure_current_plan_is_upgradable(customer, upgrade_request.tier) self.ensure_current_plan_is_upgradable(customer, upgrade_request.tier)
@ -1977,7 +1966,7 @@ class BillingSession(ABC):
"annual": CustomerPlan.BILLING_SCHEDULE_ANNUAL, "annual": CustomerPlan.BILLING_SCHEDULE_ANNUAL,
"monthly": CustomerPlan.BILLING_SCHEDULE_MONTHLY, "monthly": CustomerPlan.BILLING_SCHEDULE_MONTHLY,
}[schedule] }[schedule]
data: Dict[str, Any] = {} data: dict[str, Any] = {}
is_self_hosted_billing = not isinstance(self, RealmBillingSession) is_self_hosted_billing = not isinstance(self, RealmBillingSession)
free_trial = is_free_trial_offer_enabled(is_self_hosted_billing, upgrade_request.tier) free_trial = is_free_trial_offer_enabled(is_self_hosted_billing, upgrade_request.tier)
@ -2120,7 +2109,7 @@ class BillingSession(ABC):
@transaction.atomic @transaction.atomic
def make_end_of_cycle_updates_if_needed( def make_end_of_cycle_updates_if_needed(
self, plan: CustomerPlan, event_time: datetime self, plan: CustomerPlan, event_time: datetime
) -> Tuple[Optional[CustomerPlan], Optional[LicenseLedger]]: ) -> tuple[Optional[CustomerPlan], Optional[LicenseLedger]]:
last_ledger_entry = ( last_ledger_entry = (
LicenseLedger.objects.filter(plan=plan, event_time__lte=event_time) LicenseLedger.objects.filter(plan=plan, event_time__lte=event_time)
.order_by("-id") .order_by("-id")
@ -2338,7 +2327,7 @@ class BillingSession(ABC):
plan: CustomerPlan, plan: CustomerPlan,
last_ledger_entry: LicenseLedger, last_ledger_entry: LicenseLedger,
now: datetime, now: datetime,
) -> Dict[str, Any]: ) -> dict[str, Any]:
is_self_hosted_billing = not isinstance(self, RealmBillingSession) is_self_hosted_billing = not isinstance(self, RealmBillingSession)
downgrade_at_end_of_cycle = plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE downgrade_at_end_of_cycle = plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE
downgrade_at_end_of_free_trial = plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL downgrade_at_end_of_free_trial = plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL
@ -2483,7 +2472,7 @@ class BillingSession(ABC):
} }
return context return context
def get_billing_page_context(self) -> Dict[str, Any]: def get_billing_page_context(self) -> dict[str, Any]:
now = timezone_now() now = timezone_now()
customer = self.get_customer() customer = self.get_customer()
@ -2524,7 +2513,7 @@ class BillingSession(ABC):
context[key] = next_plan_context[key] context[key] = next_plan_context[key]
return context return context
def get_flat_discount_info(self, customer: Optional[Customer] = None) -> Tuple[int, int]: def get_flat_discount_info(self, customer: Optional[Customer] = None) -> tuple[int, int]:
is_self_hosted_billing = not isinstance(self, RealmBillingSession) is_self_hosted_billing = not isinstance(self, RealmBillingSession)
flat_discount = 0 flat_discount = 0
flat_discounted_months = 0 flat_discounted_months = 0
@ -2542,7 +2531,7 @@ class BillingSession(ABC):
def get_initial_upgrade_context( def get_initial_upgrade_context(
self, initial_upgrade_request: InitialUpgradeRequest self, initial_upgrade_request: InitialUpgradeRequest
) -> Tuple[Optional[str], Optional[UpgradePageContext]]: ) -> tuple[Optional[str], Optional[UpgradePageContext]]:
customer = self.get_customer() customer = self.get_customer()
# Allow users to upgrade to business regardless of current sponsorship status. # Allow users to upgrade to business regardless of current sponsorship status.
@ -3200,7 +3189,7 @@ class BillingSession(ABC):
assert type_of_tier_change == PlanTierChangeType.DOWNGRADE # nocoverage assert type_of_tier_change == PlanTierChangeType.DOWNGRADE # nocoverage
return "" # nocoverage return "" # nocoverage
def get_event_status(self, event_status_request: EventStatusRequest) -> Dict[str, Any]: def get_event_status(self, event_status_request: EventStatusRequest) -> dict[str, Any]:
customer = self.get_customer() customer = self.get_customer()
if customer is None: if customer is None:
@ -3261,7 +3250,7 @@ class BillingSession(ABC):
return sponsored_plan_name return sponsored_plan_name
def get_sponsorship_request_context(self) -> Optional[Dict[str, Any]]: def get_sponsorship_request_context(self) -> Optional[dict[str, Any]]:
customer = self.get_customer() customer = self.get_customer()
is_remotely_hosted = isinstance( is_remotely_hosted = isinstance(
self, (RemoteRealmBillingSession, RemoteServerBillingSession) self, (RemoteRealmBillingSession, RemoteServerBillingSession)
@ -3271,7 +3260,7 @@ class BillingSession(ABC):
if is_remotely_hosted: if is_remotely_hosted:
plan_name = "Free" plan_name = "Free"
context: Dict[str, Any] = { context: dict[str, Any] = {
"billing_base_url": self.billing_base_url, "billing_base_url": self.billing_base_url,
"is_remotely_hosted": is_remotely_hosted, "is_remotely_hosted": is_remotely_hosted,
"sponsorship_plan_name": self.get_sponsorship_plan_name(customer, is_remotely_hosted), "sponsorship_plan_name": self.get_sponsorship_plan_name(customer, is_remotely_hosted),
@ -3837,7 +3826,7 @@ class RealmBillingSession(BillingSession):
event_time: datetime, event_time: datetime,
*, *,
background_update: bool = False, background_update: bool = False,
extra_data: Optional[Dict[str, Any]] = None, extra_data: Optional[dict[str, Any]] = None,
) -> None: ) -> None:
audit_log_event = self.get_audit_log_event(event_type) audit_log_event = self.get_audit_log_event(event_type)
audit_log_data = { audit_log_data = {
@ -3859,7 +3848,7 @@ class RealmBillingSession(BillingSession):
# Support requests do not set any stripe billing information. # Support requests do not set any stripe billing information.
assert self.support_session is False assert self.support_session is False
assert self.user is not None assert self.user is not None
metadata: Dict[str, Any] = {} metadata: dict[str, Any] = {}
metadata["realm_id"] = self.realm.id metadata["realm_id"] = self.realm.id
metadata["realm_str"] = self.realm.string_id metadata["realm_str"] = self.realm.string_id
realm_stripe_customer_data = StripeCustomerData( realm_stripe_customer_data = StripeCustomerData(
@ -3871,8 +3860,8 @@ class RealmBillingSession(BillingSession):
@override @override
def update_data_for_checkout_session_and_invoice_payment( def update_data_for_checkout_session_and_invoice_payment(
self, metadata: Dict[str, Any] self, metadata: dict[str, Any]
) -> Dict[str, Any]: ) -> dict[str, Any]:
assert self.user is not None assert self.user is not None
updated_metadata = dict( updated_metadata = dict(
user_email=self.get_email(), user_email=self.get_email(),
@ -3885,7 +3874,7 @@ class RealmBillingSession(BillingSession):
@override @override
def update_or_create_customer( def update_or_create_customer(
self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[Dict[str, Any]] = None self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[dict[str, Any]] = None
) -> Customer: ) -> Customer:
if stripe_customer_id is not None: if stripe_customer_id is not None:
# Support requests do not set any stripe billing information. # Support requests do not set any stripe billing information.
@ -3986,7 +3975,7 @@ class RealmBillingSession(BillingSession):
return self.realm.plan_type == self.realm.PLAN_TYPE_STANDARD_FREE return self.realm.plan_type == self.realm.PLAN_TYPE_STANDARD_FREE
@override @override
def get_metadata_for_stripe_update_card(self) -> Dict[str, str]: def get_metadata_for_stripe_update_card(self) -> dict[str, str]:
assert self.user is not None assert self.user is not None
return { return {
"type": "card_update", "type": "card_update",
@ -4051,7 +4040,7 @@ class RealmBillingSession(BillingSession):
return self.realm.name return self.realm.name
@override @override
def add_sponsorship_info_to_context(self, context: Dict[str, Any]) -> None: def add_sponsorship_info_to_context(self, context: dict[str, Any]) -> None:
context.update( context.update(
realm_org_type=self.realm.org_type, realm_org_type=self.realm.org_type,
sorted_org_types=sorted( sorted_org_types=sorted(
@ -4166,7 +4155,7 @@ class RemoteRealmBillingSession(BillingSession):
# possible, in that the self-hosted server will have uploaded # possible, in that the self-hosted server will have uploaded
# current audit log data as needed as part of logging the user # current audit log data as needed as part of logging the user
# in. # in.
missing_data_context: Dict[str, Any] = { missing_data_context: dict[str, Any] = {
"remote_realm_session": True, "remote_realm_session": True,
"supports_remote_realms": self.remote_realm.server.last_api_feature_level is not None, "supports_remote_realms": self.remote_realm.server.last_api_feature_level is not None,
} }
@ -4216,7 +4205,7 @@ class RemoteRealmBillingSession(BillingSession):
event_time: datetime, event_time: datetime,
*, *,
background_update: bool = False, background_update: bool = False,
extra_data: Optional[Dict[str, Any]] = None, extra_data: Optional[dict[str, Any]] = None,
) -> None: ) -> None:
# These audit logs don't use all the fields of `RemoteRealmAuditLog`: # These audit logs don't use all the fields of `RemoteRealmAuditLog`:
# #
@ -4250,7 +4239,7 @@ class RemoteRealmBillingSession(BillingSession):
def get_data_for_stripe_customer(self) -> StripeCustomerData: def get_data_for_stripe_customer(self) -> StripeCustomerData:
# Support requests do not set any stripe billing information. # Support requests do not set any stripe billing information.
assert self.support_session is False assert self.support_session is False
metadata: Dict[str, Any] = {} metadata: dict[str, Any] = {}
metadata["remote_realm_uuid"] = self.remote_realm.uuid metadata["remote_realm_uuid"] = self.remote_realm.uuid
metadata["remote_realm_host"] = str(self.remote_realm.host) metadata["remote_realm_host"] = str(self.remote_realm.host)
realm_stripe_customer_data = StripeCustomerData( realm_stripe_customer_data = StripeCustomerData(
@ -4262,8 +4251,8 @@ class RemoteRealmBillingSession(BillingSession):
@override @override
def update_data_for_checkout_session_and_invoice_payment( def update_data_for_checkout_session_and_invoice_payment(
self, metadata: Dict[str, Any] self, metadata: dict[str, Any]
) -> Dict[str, Any]: ) -> dict[str, Any]:
assert self.remote_billing_user is not None assert self.remote_billing_user is not None
updated_metadata = dict( updated_metadata = dict(
remote_realm_user_id=self.remote_billing_user.id, remote_realm_user_id=self.remote_billing_user.id,
@ -4275,7 +4264,7 @@ class RemoteRealmBillingSession(BillingSession):
@override @override
def update_or_create_customer( def update_or_create_customer(
self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[Dict[str, Any]] = None self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[dict[str, Any]] = None
) -> Customer: ) -> Customer:
if stripe_customer_id is not None: if stripe_customer_id is not None:
# Support requests do not set any stripe billing information. # Support requests do not set any stripe billing information.
@ -4381,7 +4370,7 @@ class RemoteRealmBillingSession(BillingSession):
return self.remote_realm.plan_type == self.remote_realm.PLAN_TYPE_COMMUNITY return self.remote_realm.plan_type == self.remote_realm.PLAN_TYPE_COMMUNITY
@override @override
def get_metadata_for_stripe_update_card(self) -> Dict[str, str]: # nocoverage def get_metadata_for_stripe_update_card(self) -> dict[str, str]: # nocoverage
assert self.remote_billing_user is not None assert self.remote_billing_user is not None
return {"type": "card_update", "remote_realm_user_id": str(self.remote_billing_user.id)} return {"type": "card_update", "remote_realm_user_id": str(self.remote_billing_user.id)}
@ -4487,7 +4476,7 @@ class RemoteRealmBillingSession(BillingSession):
return self.remote_realm.host return self.remote_realm.host
@override @override
def add_sponsorship_info_to_context(self, context: Dict[str, Any]) -> None: def add_sponsorship_info_to_context(self, context: dict[str, Any]) -> None:
context.update( context.update(
realm_org_type=self.remote_realm.org_type, realm_org_type=self.remote_realm.org_type,
sorted_org_types=sorted( sorted_org_types=sorted(
@ -4659,7 +4648,7 @@ class RemoteServerBillingSession(BillingSession):
event_time: datetime, event_time: datetime,
*, *,
background_update: bool = False, background_update: bool = False,
extra_data: Optional[Dict[str, Any]] = None, extra_data: Optional[dict[str, Any]] = None,
) -> None: ) -> None:
audit_log_event = self.get_audit_log_event(event_type) audit_log_event = self.get_audit_log_event(event_type)
log_data = { log_data = {
@ -4687,7 +4676,7 @@ class RemoteServerBillingSession(BillingSession):
def get_data_for_stripe_customer(self) -> StripeCustomerData: def get_data_for_stripe_customer(self) -> StripeCustomerData:
# Support requests do not set any stripe billing information. # Support requests do not set any stripe billing information.
assert self.support_session is False assert self.support_session is False
metadata: Dict[str, Any] = {} metadata: dict[str, Any] = {}
metadata["remote_server_uuid"] = self.remote_server.uuid metadata["remote_server_uuid"] = self.remote_server.uuid
metadata["remote_server_str"] = str(self.remote_server) metadata["remote_server_str"] = str(self.remote_server)
realm_stripe_customer_data = StripeCustomerData( realm_stripe_customer_data = StripeCustomerData(
@ -4699,8 +4688,8 @@ class RemoteServerBillingSession(BillingSession):
@override @override
def update_data_for_checkout_session_and_invoice_payment( def update_data_for_checkout_session_and_invoice_payment(
self, metadata: Dict[str, Any] self, metadata: dict[str, Any]
) -> Dict[str, Any]: ) -> dict[str, Any]:
assert self.remote_billing_user is not None assert self.remote_billing_user is not None
updated_metadata = dict( updated_metadata = dict(
remote_server_user_id=self.remote_billing_user.id, remote_server_user_id=self.remote_billing_user.id,
@ -4712,7 +4701,7 @@ class RemoteServerBillingSession(BillingSession):
@override @override
def update_or_create_customer( def update_or_create_customer(
self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[Dict[str, Any]] = None self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[dict[str, Any]] = None
) -> Customer: ) -> Customer:
if stripe_customer_id is not None: if stripe_customer_id is not None:
# Support requests do not set any stripe billing information. # Support requests do not set any stripe billing information.
@ -4848,7 +4837,7 @@ class RemoteServerBillingSession(BillingSession):
return self.remote_server.plan_type == self.remote_server.PLAN_TYPE_COMMUNITY return self.remote_server.plan_type == self.remote_server.PLAN_TYPE_COMMUNITY
@override @override
def get_metadata_for_stripe_update_card(self) -> Dict[str, str]: # nocoverage def get_metadata_for_stripe_update_card(self) -> dict[str, str]: # nocoverage
assert self.remote_billing_user is not None assert self.remote_billing_user is not None
return {"type": "card_update", "remote_server_user_id": str(self.remote_billing_user.id)} return {"type": "card_update", "remote_server_user_id": str(self.remote_billing_user.id)}
@ -4936,7 +4925,7 @@ class RemoteServerBillingSession(BillingSession):
return self.remote_server.hostname return self.remote_server.hostname
@override @override
def add_sponsorship_info_to_context(self, context: Dict[str, Any]) -> None: # nocoverage def add_sponsorship_info_to_context(self, context: dict[str, Any]) -> None: # nocoverage
context.update( context.update(
realm_org_type=self.remote_server.org_type, realm_org_type=self.remote_server.org_type,
sorted_org_types=sorted( sorted_org_types=sorted(
@ -5025,7 +5014,7 @@ def get_price_per_license(
# We already have a set discounted price for the current tier. # We already have a set discounted price for the current tier.
return price_per_license return price_per_license
price_map: Dict[int, Dict[str, int]] = { price_map: dict[int, dict[str, int]] = {
CustomerPlan.TIER_CLOUD_STANDARD: {"Annual": 8000, "Monthly": 800}, CustomerPlan.TIER_CLOUD_STANDARD: {"Annual": 8000, "Monthly": 800},
CustomerPlan.TIER_CLOUD_PLUS: {"Annual": 12000, "Monthly": 1200}, CustomerPlan.TIER_CLOUD_PLUS: {"Annual": 12000, "Monthly": 1200},
CustomerPlan.TIER_SELF_HOSTED_BASIC: {"Annual": 4200, "Monthly": 350}, CustomerPlan.TIER_SELF_HOSTED_BASIC: {"Annual": 4200, "Monthly": 350},
@ -5047,7 +5036,7 @@ def get_price_per_license(
def get_price_per_license_and_discount( def get_price_per_license_and_discount(
tier: int, billing_schedule: int, customer: Optional[Customer] tier: int, billing_schedule: int, customer: Optional[Customer]
) -> Tuple[int, Union[str, None]]: ) -> tuple[int, Union[str, None]]:
original_price_per_license = get_price_per_license(tier, billing_schedule) original_price_per_license = get_price_per_license(tier, billing_schedule)
if customer is None: if customer is None:
return original_price_per_license, None return original_price_per_license, None
@ -5070,7 +5059,7 @@ def compute_plan_parameters(
billing_cycle_anchor: Optional[datetime] = None, billing_cycle_anchor: Optional[datetime] = None,
is_self_hosted_billing: bool = False, is_self_hosted_billing: bool = False,
should_schedule_upgrade_for_legacy_remote_server: bool = False, should_schedule_upgrade_for_legacy_remote_server: bool = False,
) -> Tuple[datetime, datetime, datetime, int]: ) -> tuple[datetime, datetime, datetime, int]:
# Everything in Stripe is stored as timestamps with 1 second resolution, # Everything in Stripe is stored as timestamps with 1 second resolution,
# so standardize on 1 second resolution. # so standardize on 1 second resolution.
# TODO talk about leap seconds? # TODO talk about leap seconds?
@ -5354,7 +5343,7 @@ def downgrade_small_realms_behind_on_payments_as_needed() -> None:
billing_session = RealmBillingSession(user=None, realm=realm) billing_session = RealmBillingSession(user=None, realm=realm)
billing_session.downgrade_now_without_creating_additional_invoices() billing_session.downgrade_now_without_creating_additional_invoices()
billing_session.void_all_open_invoices() billing_session.void_all_open_invoices()
context: Dict[str, Union[str, Realm]] = { context: dict[str, Union[str, Realm]] = {
"upgrade_url": f"{realm.url}{reverse('upgrade_page')}", "upgrade_url": f"{realm.url}{reverse('upgrade_page')}",
"realm": realm, "realm": realm,
} }

View File

@ -1,5 +1,5 @@
from enum import Enum from enum import Enum
from typing import Any, Dict, Optional, Union from typing import Any, Optional, Union
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -109,7 +109,7 @@ class Event(models.Model):
handler_error = models.JSONField(default=None, null=True) handler_error = models.JSONField(default=None, null=True)
def get_event_handler_details_as_dict(self) -> Dict[str, Any]: def get_event_handler_details_as_dict(self) -> dict[str, Any]:
details_dict = {} details_dict = {}
details_dict["status"] = { details_dict["status"] = {
Event.RECEIVED: "not_started", Event.RECEIVED: "not_started",
@ -158,8 +158,8 @@ class Session(models.Model):
Session.CARD_UPDATE_FROM_UPGRADE_PAGE: "card_update_from_upgrade_page", Session.CARD_UPDATE_FROM_UPGRADE_PAGE: "card_update_from_upgrade_page",
}[self.type] }[self.type]
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> dict[str, Any]:
session_dict: Dict[str, Any] = {} session_dict: dict[str, Any] = {}
session_dict["status"] = self.get_status_as_string() session_dict["status"] = self.get_status_as_string()
session_dict["type"] = self.get_type_as_string() session_dict["type"] = self.get_type_as_string()
@ -216,8 +216,8 @@ class PaymentIntent(models.Model): # nocoverage
return None # nocoverage return None # nocoverage
return get_last_associated_event_by_type(self, event_type) return get_last_associated_event_by_type(self, event_type)
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> dict[str, Any]:
payment_intent_dict: Dict[str, Any] = {} payment_intent_dict: dict[str, Any] = {}
payment_intent_dict["status"] = self.get_status_as_string() payment_intent_dict["status"] = self.get_status_as_string()
event = self.get_last_associated_event() event = self.get_last_associated_event()
if event is not None: if event is not None:
@ -251,8 +251,8 @@ class Invoice(models.Model):
return None # nocoverage return None # nocoverage
return get_last_associated_event_by_type(self, event_type) return get_last_associated_event_by_type(self, event_type)
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> dict[str, Any]:
stripe_invoice_dict: Dict[str, Any] = {} stripe_invoice_dict: dict[str, Any] = {}
stripe_invoice_dict["status"] = self.get_status_as_string() stripe_invoice_dict["status"] = self.get_status_as_string()
event = self.get_last_associated_event() event = self.get_last_associated_event()
if event is not None: if event is not None:

View File

@ -14,14 +14,10 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Any, Any,
Callable, Callable,
Dict,
List,
Literal, Literal,
Mapping, Mapping,
Optional, Optional,
Sequence, Sequence,
Tuple,
Type,
TypeVar, TypeVar,
Union, Union,
cast, cast,
@ -139,7 +135,7 @@ def stripe_fixture_path(
return f"{STRIPE_FIXTURES_DIR}/{decorated_function_name}--{mocked_function_name[7:]}.{call_count}.json" return f"{STRIPE_FIXTURES_DIR}/{decorated_function_name}--{mocked_function_name[7:]}.{call_count}.json"
def fixture_files_for_function(decorated_function: CallableT) -> List[str]: # nocoverage def fixture_files_for_function(decorated_function: CallableT) -> list[str]: # nocoverage
decorated_function_name = decorated_function.__name__ decorated_function_name = decorated_function.__name__
if decorated_function_name[:5] == "test_": if decorated_function_name[:5] == "test_":
decorated_function_name = decorated_function_name[5:] decorated_function_name = decorated_function_name[5:]
@ -269,7 +265,7 @@ def normalize_fixture_data(
f'"{timestamp_field}": 1{i + 1:02}%07d' f'"{timestamp_field}": 1{i + 1:02}%07d'
) )
normalized_values: Dict[str, Dict[str, str]] = {pattern: {} for pattern in pattern_translations} normalized_values: dict[str, dict[str, str]] = {pattern: {} for pattern in pattern_translations}
for fixture_file in fixture_files_for_function(decorated_function): for fixture_file in fixture_files_for_function(decorated_function):
with open(fixture_file) as f: with open(fixture_file) as f:
file_content = f.read() file_content = f.read()
@ -470,7 +466,7 @@ class StripeTestCase(ZulipTestCase):
return "tok_visa_chargeDeclined" return "tok_visa_chargeDeclined"
def assert_details_of_valid_session_from_event_status_endpoint( def assert_details_of_valid_session_from_event_status_endpoint(
self, stripe_session_id: str, expected_details: Dict[str, Any] self, stripe_session_id: str, expected_details: dict[str, Any]
) -> None: ) -> None:
json_response = self.client_billing_get( json_response = self.client_billing_get(
"/billing/event/status", "/billing/event/status",
@ -484,7 +480,7 @@ class StripeTestCase(ZulipTestCase):
def assert_details_of_valid_invoice_payment_from_event_status_endpoint( def assert_details_of_valid_invoice_payment_from_event_status_endpoint(
self, self,
stripe_invoice_id: str, stripe_invoice_id: str,
expected_details: Dict[str, Any], expected_details: dict[str, Any],
) -> None: ) -> None:
json_response = self.client_billing_get( json_response = self.client_billing_get(
"/billing/event/status", "/billing/event/status",
@ -626,7 +622,7 @@ class StripeTestCase(ZulipTestCase):
upgrade_page_response = self.client_get(upgrade_url, {}, subdomain="selfhosting") upgrade_page_response = self.client_get(upgrade_url, {}, subdomain="selfhosting")
else: else:
upgrade_page_response = self.client_get(upgrade_url, {}) upgrade_page_response = self.client_get(upgrade_url, {})
params: Dict[str, Any] = { params: dict[str, Any] = {
"schedule": "annual", "schedule": "annual",
"signed_seat_count": self.get_signed_seat_count_from_response(upgrade_page_response), "signed_seat_count": self.get_signed_seat_count_from_response(upgrade_page_response),
"salt": self.get_salt_from_response(upgrade_page_response), "salt": self.get_salt_from_response(upgrade_page_response),
@ -4468,7 +4464,7 @@ class StripeTest(StripeTestCase):
for invoice in invoices: for invoice in invoices:
self.assertEqual(invoice.status, "void") self.assertEqual(invoice.status, "void")
def create_invoices(self, customer: Customer, num_invoices: int) -> List[stripe.Invoice]: def create_invoices(self, customer: Customer, num_invoices: int) -> list[stripe.Invoice]:
invoices = [] invoices = []
assert customer.stripe_customer_id is not None assert customer.stripe_customer_id is not None
for _ in range(num_invoices): for _ in range(num_invoices):
@ -4499,7 +4495,7 @@ class StripeTest(StripeTestCase):
create_stripe_customer: bool, create_stripe_customer: bool,
create_plan: bool, create_plan: bool,
num_invoices: Optional[int] = None, num_invoices: Optional[int] = None,
) -> Tuple[Realm, Optional[CustomerPlan], List[stripe.Invoice]]: ) -> tuple[Realm, Optional[CustomerPlan], list[stripe.Invoice]]:
nonlocal test_realm_count nonlocal test_realm_count
test_realm_count += 1 test_realm_count += 1
realm_string_id = "test-realm-" + str(test_realm_count) realm_string_id = "test-realm-" + str(test_realm_count)
@ -4541,7 +4537,7 @@ class StripeTest(StripeTestCase):
expected_invoice_count: int expected_invoice_count: int
email_expected_to_be_sent: bool email_expected_to_be_sent: bool
rows: List[Row] = [] rows: list[Row] = []
# no stripe customer ID (excluded from query) # no stripe customer ID (excluded from query)
realm, _, _ = create_realm( realm, _, _ = create_realm(
@ -4970,11 +4966,11 @@ class RequiresBillingAccessTest(StripeTestCase):
tested_endpoints = set() tested_endpoints = set()
def check_users_cant_access( def check_users_cant_access(
users: List[UserProfile], users: list[UserProfile],
error_message: str, error_message: str,
url: str, url: str,
method: str, method: str,
data: Dict[str, Any], data: dict[str, Any],
) -> None: ) -> None:
tested_endpoints.add(url) tested_endpoints.add(url)
for user in users: for user in users:
@ -6507,7 +6503,7 @@ class TestRemoteBillingWriteAuditLog(StripeTestCase):
# Necessary cast or mypy doesn't understand that we can use Django's # Necessary cast or mypy doesn't understand that we can use Django's
# model .objects. style queries on this. # model .objects. style queries on this.
audit_log_model = cast( audit_log_model = cast(
Union[Type[RemoteRealmAuditLog], Type[RemoteZulipServerAuditLog]], audit_log_class Union[type[RemoteRealmAuditLog], type[RemoteZulipServerAuditLog]], audit_log_class
) )
assert isinstance(remote_user, (RemoteRealmBillingUser, RemoteServerBillingUser)) assert isinstance(remote_user, (RemoteRealmBillingUser, RemoteServerBillingUser))
# No acting user: # No acting user:

View File

@ -1,5 +1,5 @@
import logging import logging
from typing import Any, Dict, Literal, Optional from typing import Any, Literal, Optional
from django.http import HttpRequest, HttpResponse, HttpResponseNotAllowed, HttpResponseRedirect from django.http import HttpRequest, HttpResponse, HttpResponseNotAllowed, HttpResponseRedirect
from django.shortcuts import render from django.shortcuts import render
@ -55,7 +55,7 @@ def billing_page(
billing_session = RealmBillingSession(user=user, realm=user.realm) billing_session = RealmBillingSession(user=user, realm=user.realm)
context: Dict[str, Any] = { context: dict[str, Any] = {
"admin_access": user.has_billing_access, "admin_access": user.has_billing_access,
"has_active_plan": False, "has_active_plan": False,
"org_name": billing_session.org_name(), "org_name": billing_session.org_name(),
@ -101,7 +101,7 @@ def remote_realm_billing_page(
success_message: str = "", success_message: str = "",
) -> HttpResponse: ) -> HttpResponse:
realm_uuid = billing_session.remote_realm.uuid realm_uuid = billing_session.remote_realm.uuid
context: Dict[str, Any] = { context: dict[str, Any] = {
# We wouldn't be here if user didn't have access. # We wouldn't be here if user didn't have access.
"admin_access": billing_session.has_billing_access(), "admin_access": billing_session.has_billing_access(),
"has_active_plan": False, "has_active_plan": False,
@ -161,7 +161,7 @@ def remote_server_billing_page(
*, *,
success_message: str = "", success_message: str = "",
) -> HttpResponse: ) -> HttpResponse:
context: Dict[str, Any] = { context: dict[str, Any] = {
# We wouldn't be here if user didn't have access. # We wouldn't be here if user didn't have access.
"admin_access": billing_session.has_billing_access(), "admin_access": billing_session.has_billing_access(),
"has_active_plan": False, "has_active_plan": False,

View File

@ -1,5 +1,5 @@
from collections import defaultdict from collections import defaultdict
from typing import Dict, Optional from typing import Optional
from django.conf import settings from django.conf import settings
from django.db import connection from django.db import connection
@ -33,7 +33,7 @@ from zerver.models.realm_audit_logs import RealmAuditLog
from zerver.models.realms import get_org_type_display_name from zerver.models.realms import get_org_type_display_name
def get_realm_day_counts() -> Dict[str, Dict[str, Markup]]: def get_realm_day_counts() -> dict[str, dict[str, Markup]]:
# To align with UTC days, we subtract an hour from end_time to # To align with UTC days, we subtract an hour from end_time to
# get the start_time, since the hour that starts at midnight was # get the start_time, since the hour that starts at midnight was
# on the previous day. # on the previous day.
@ -61,7 +61,7 @@ def get_realm_day_counts() -> Dict[str, Dict[str, Markup]]:
rows = dictfetchall(cursor) rows = dictfetchall(cursor)
cursor.close() cursor.close()
counts: Dict[str, Dict[int, int]] = defaultdict(dict) counts: dict[str, dict[int, int]] = defaultdict(dict)
for row in rows: for row in rows:
counts[row["string_id"]][row["age"]] = row["cnt"] counts[row["string_id"]][row["age"]] = row["cnt"]

View File

@ -2,7 +2,7 @@ import itertools
import re import re
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from typing import Any, Collection, Dict, Optional, Set from typing import Any, Collection, Optional
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http import HttpRequest, HttpResponse, HttpResponseNotFound from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
@ -97,9 +97,9 @@ def get_user_activity_summary(records: Collection[UserActivity]) -> UserActivity
def realm_user_summary_table( def realm_user_summary_table(
all_records: QuerySet[UserActivity], admin_emails: Set[str], title: str, stats_link: Markup all_records: QuerySet[UserActivity], admin_emails: set[str], title: str, stats_link: Markup
) -> str: ) -> str:
user_records: Dict[str, UserActivitySummary] = {} user_records: dict[str, UserActivitySummary] = {}
def by_email(record: UserActivity) -> str: def by_email(record: UserActivity) -> str:
return record.user_profile.delivery_email return record.user_profile.delivery_email
@ -141,7 +141,7 @@ def realm_user_summary_table(
row = dict(cells=cells, row_class=row_class) row = dict(cells=cells, row_class=row_class)
rows.append(row) rows.append(row)
def by_last_heard_from(row: Dict[str, Any]) -> str: def by_last_heard_from(row: dict[str, Any]) -> str:
return row["cells"][4] return row["cells"][4]
rows = sorted(rows, key=by_last_heard_from, reverse=True) rows = sorted(rows, key=by_last_heard_from, reverse=True)

View File

@ -1,5 +1,5 @@
import logging import logging
from typing import Any, Dict, Literal, Optional, Union, cast from typing import Any, Literal, Optional, Union, cast
from urllib.parse import urlsplit, urlunsplit from urllib.parse import urlsplit, urlunsplit
from django.conf import settings from django.conf import settings
@ -526,7 +526,7 @@ def remote_billing_legacy_server_login(
zulip_org_key: Optional[str] = None, zulip_org_key: Optional[str] = None,
next_page: VALID_NEXT_PAGES_TYPE = None, next_page: VALID_NEXT_PAGES_TYPE = None,
) -> HttpResponse: ) -> HttpResponse:
context: Dict[str, Any] = {"next_page": next_page} context: dict[str, Any] = {"next_page": next_page}
if zulip_org_id is None or zulip_org_key is None: if zulip_org_id is None or zulip_org_key is None:
context.update({"error_message": False}) context.update({"error_message": False})
return render(request, "corporate/billing/legacy_server_login.html", context) return render(request, "corporate/billing/legacy_server_login.html", context)

View File

@ -3,7 +3,7 @@ from contextlib import suppress
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from operator import attrgetter from operator import attrgetter
from typing import Any, Dict, Iterable, List, Optional, Union from typing import Any, Iterable, Optional, Union
from urllib.parse import urlencode, urlsplit from urllib.parse import urlencode, urlsplit
from django import forms from django import forms
@ -215,8 +215,8 @@ def get_plan_type_string(plan_type: int) -> str:
def get_confirmations( def get_confirmations(
types: List[int], object_ids: Iterable[int], hostname: Optional[str] = None types: list[int], object_ids: Iterable[int], hostname: Optional[str] = None
) -> List[Dict[str, Any]]: ) -> list[dict[str, Any]]:
lowest_datetime = timezone_now() - timedelta(days=30) lowest_datetime = timezone_now() - timedelta(days=30)
confirmations = Confirmation.objects.filter( confirmations = Confirmation.objects.filter(
type__in=types, object_id__in=object_ids, date_sent__gte=lowest_datetime type__in=types, object_id__in=object_ids, date_sent__gte=lowest_datetime
@ -265,7 +265,7 @@ class SupportSelectOption:
value: int value: int
def get_remote_plan_tier_options() -> List[SupportSelectOption]: def get_remote_plan_tier_options() -> list[SupportSelectOption]:
remote_plan_tiers = [ remote_plan_tiers = [
SupportSelectOption("None", 0), SupportSelectOption("None", 0),
SupportSelectOption( SupportSelectOption(
@ -280,7 +280,7 @@ def get_remote_plan_tier_options() -> List[SupportSelectOption]:
return remote_plan_tiers return remote_plan_tiers
def get_realm_plan_type_options() -> List[SupportSelectOption]: def get_realm_plan_type_options() -> list[SupportSelectOption]:
plan_types = [ plan_types = [
SupportSelectOption( SupportSelectOption(
get_plan_type_string(Realm.PLAN_TYPE_SELF_HOSTED), Realm.PLAN_TYPE_SELF_HOSTED get_plan_type_string(Realm.PLAN_TYPE_SELF_HOSTED), Realm.PLAN_TYPE_SELF_HOSTED
@ -297,7 +297,7 @@ def get_realm_plan_type_options() -> List[SupportSelectOption]:
return plan_types return plan_types
def get_realm_plan_type_options_for_discount() -> List[SupportSelectOption]: def get_realm_plan_type_options_for_discount() -> list[SupportSelectOption]:
plan_types = [ plan_types = [
SupportSelectOption("None", 0), SupportSelectOption("None", 0),
SupportSelectOption( SupportSelectOption(
@ -349,7 +349,7 @@ def support(
query: Annotated[Optional[str], ApiParamConfig("q")] = None, query: Annotated[Optional[str], ApiParamConfig("q")] = None,
org_type: Optional[Json[NonNegativeInt]] = None, org_type: Optional[Json[NonNegativeInt]] = None,
) -> HttpResponse: ) -> HttpResponse:
context: Dict[str, Any] = {} context: dict[str, Any] = {}
if "success_message" in request.session: if "success_message" in request.session:
context["success_message"] = request.session["success_message"] context["success_message"] = request.session["success_message"]
@ -499,7 +499,7 @@ def support(
context["users"] = users context["users"] = users
context["realms"] = realms context["realms"] = realms
confirmations: List[Dict[str, Any]] = [] confirmations: list[dict[str, Any]] = []
preregistration_user_ids = [ preregistration_user_ids = [
user.id for user in PreregistrationUser.objects.filter(email__in=key_words) user.id for user in PreregistrationUser.objects.filter(email__in=key_words)
@ -544,7 +544,7 @@ def support(
] ]
+ [user.realm for user in users] + [user.realm for user in users]
) )
realm_support_data: Dict[int, CloudSupportData] = {} realm_support_data: dict[int, CloudSupportData] = {}
for realm in all_realms: for realm in all_realms:
billing_session = RealmBillingSession(user=None, realm=realm) billing_session = RealmBillingSession(user=None, realm=realm)
realm_data = get_data_for_cloud_support_view(billing_session) realm_data = get_data_for_cloud_support_view(billing_session)
@ -581,7 +581,7 @@ def support(
def get_remote_servers_for_support( def get_remote_servers_for_support(
email_to_search: Optional[str], uuid_to_search: Optional[str], hostname_to_search: Optional[str] email_to_search: Optional[str], uuid_to_search: Optional[str], hostname_to_search: Optional[str]
) -> List["RemoteZulipServer"]: ) -> list["RemoteZulipServer"]:
remote_servers_query = RemoteZulipServer.objects.order_by("id") remote_servers_query = RemoteZulipServer.objects.order_by("id")
if email_to_search: if email_to_search:
@ -645,7 +645,7 @@ def remote_servers_support(
delete_fixed_price_next_plan: Json[bool] = False, delete_fixed_price_next_plan: Json[bool] = False,
remote_server_status: Optional[VALID_STATUS_VALUES] = None, remote_server_status: Optional[VALID_STATUS_VALUES] = None,
) -> HttpResponse: ) -> HttpResponse:
context: Dict[str, Any] = {} context: dict[str, Any] = {}
if "success_message" in request.session: if "success_message" in request.session:
context["success_message"] = request.session["success_message"] context["success_message"] = request.session["success_message"]
@ -778,10 +778,10 @@ def remote_servers_support(
uuid_to_search=uuid_to_search, uuid_to_search=uuid_to_search,
hostname_to_search=hostname_to_search, hostname_to_search=hostname_to_search,
) )
remote_server_to_max_monthly_messages: Dict[int, Union[int, str]] = dict() remote_server_to_max_monthly_messages: dict[int, Union[int, str]] = dict()
server_support_data: Dict[int, RemoteSupportData] = {} server_support_data: dict[int, RemoteSupportData] = {}
realm_support_data: Dict[int, RemoteSupportData] = {} realm_support_data: dict[int, RemoteSupportData] = {}
remote_realms: Dict[int, List[RemoteRealm]] = {} remote_realms: dict[int, list[RemoteRealm]] = {}
for remote_server in remote_servers: for remote_server in remote_servers:
# Get remote realms attached to remote server # Get remote realms attached to remote server
remote_realms_for_server = list( remote_realms_for_server = list(

View File

@ -1,4 +1,4 @@
from typing import Any, List from typing import Any
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
@ -43,7 +43,7 @@ def get_user_activity(request: HttpRequest, user_profile_id: int) -> HttpRespons
"Last visit (UTC)", "Last visit (UTC)",
] ]
def row(record: UserActivity) -> List[Any]: def row(record: UserActivity) -> list[Any]:
return [ return [
record.query, record.query,
record.client.name, record.client.name,

View File

@ -3,7 +3,7 @@ import configparser
import os import os
import sys import sys
from collections import defaultdict from collections import defaultdict
from typing import Dict, List, Optional from typing import Optional
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR) sys.path.append(BASE_DIR)
@ -18,7 +18,7 @@ from typing_extensions import override
from scripts.lib.zulip_tools import assert_not_running_as_root from scripts.lib.zulip_tools import assert_not_running_as_root
def get_filtered_commands() -> Dict[str, str]: def get_filtered_commands() -> dict[str, str]:
"""Because Zulip uses management commands in production, `manage.py """Because Zulip uses management commands in production, `manage.py
help` is a form of documentation for users. Here we exclude from help` is a form of documentation for users. Here we exclude from
that documentation built-in commands that are not constructive for that documentation built-in commands that are not constructive for
@ -110,7 +110,7 @@ class FilteredManagementUtility(ManagementUtility):
return "\n".join(usage) return "\n".join(usage)
def execute_from_command_line(argv: Optional[List[str]] = None) -> None: def execute_from_command_line(argv: Optional[list[str]] = None) -> None:
"""Run a FilteredManagementUtility.""" """Run a FilteredManagementUtility."""
utility = FilteredManagementUtility(argv) utility = FilteredManagementUtility(argv)
utility.execute() utility.execute()

View File

@ -3,7 +3,7 @@
import contextlib import contextlib
import sys import sys
import time import time
from typing import Any, Dict, Iterable, Optional, Sequence, Union from typing import Any, Iterable, Optional, Sequence, Union
sys.path.append("/home/zulip/deployments/current") sys.path.append("/home/zulip/deployments/current")
from scripts.lib.setup_path import setup_path from scripts.lib.setup_path import setup_path
@ -47,7 +47,7 @@ class MemcachedCollector(Collector):
name: str, name: str,
doc: str, doc: str,
value: Union[bytes, float], value: Union[bytes, float],
labels: Optional[Dict[str, str]] = None, labels: Optional[dict[str, str]] = None,
) -> CounterMetricFamily: ) -> CounterMetricFamily:
if labels is None: if labels is None:
labels = {} labels = {}
@ -63,7 +63,7 @@ class MemcachedCollector(Collector):
) )
return metric return metric
cache: Dict[str, Any] = settings.CACHES["default"] cache: dict[str, Any] = settings.CACHES["default"]
client = None client = None
with contextlib.suppress(Exception): with contextlib.suppress(Exception):
client = bmemcached.Client((cache["LOCATION"],), **cache["OPTIONS"]) client = bmemcached.Client((cache["LOCATION"],), **cache["OPTIONS"])
@ -73,7 +73,7 @@ class MemcachedCollector(Collector):
return return
raw_stats = client.stats() raw_stats = client.stats()
stats: Dict[str, bytes] = next(iter(raw_stats.values())) stats: dict[str, bytes] = next(iter(raw_stats.values()))
version_gauge = gauge( version_gauge = gauge(
"version", "The version of this memcached server.", labels=["version"] "version", "The version of this memcached server.", labels=["version"]

View File

@ -12,11 +12,11 @@ mirrors when they receive the messages sent every minute by
import os import os
import sys import sys
import time import time
from typing import Dict, NoReturn from typing import NoReturn
RESULTS_DIR: str = "/home/zulip/mirror_status" RESULTS_DIR: str = "/home/zulip/mirror_status"
states: Dict[str, int] = { states: dict[str, int] = {
"OK": 0, "OK": 0,
"WARNING": 1, "WARNING": 1,
"CRITICAL": 2, "CRITICAL": 2,

View File

@ -13,11 +13,11 @@ See puppet/kandra/files/cron.d/zephyr-mirror for the crontab details.
import os import os
import sys import sys
import time import time
from typing import Dict, NoReturn from typing import NoReturn
RESULTS_FILE = "/var/lib/nagios_state/check-mirroring-results" RESULTS_FILE = "/var/lib/nagios_state/check-mirroring-results"
states: Dict[str, int] = { states: dict[str, int] = {
"OK": 0, "OK": 0,
"WARNING": 1, "WARNING": 1,
"CRITICAL": 2, "CRITICAL": 2,

View File

@ -6,10 +6,9 @@ file output by the cron job is correct.
import sys import sys
import time import time
from typing import Tuple
def nagios_from_file(results_file: str, max_time_diff: int = 60 * 2) -> Tuple[int, str]: def nagios_from_file(results_file: str, max_time_diff: int = 60 * 2) -> tuple[int, str]:
"""Returns a nagios-appropriate string and return code obtained by """Returns a nagios-appropriate string and return code obtained by
parsing the desired file on disk. The file on disk should be of format parsing the desired file on disk. The file on disk should be of format

View File

@ -13,7 +13,7 @@ import random
import sys import sys
import time import time
import traceback import traceback
from typing import Any, Dict, List, Literal, NoReturn, Optional from typing import Any, Literal, NoReturn, Optional
sys.path.append(".") sys.path.append(".")
sys.path.append("/home/zulip/deployments/current") sys.path.append("/home/zulip/deployments/current")
@ -58,13 +58,13 @@ def report(
sys.exit(atomic_nagios_write("check_send_receive_state", state, msg)) sys.exit(atomic_nagios_write("check_send_receive_state", state, msg))
def send_zulip(sender: zulip.Client, message: Dict[str, Any]) -> None: def send_zulip(sender: zulip.Client, message: dict[str, Any]) -> None:
result = sender.send_message(message) result = sender.send_message(message)
if result["result"] != "success": if result["result"] != "success":
report("critical", msg=f"Error sending Zulip, args were: {message}, {result}") report("critical", msg=f"Error sending Zulip, args were: {message}, {result}")
def get_zulips() -> List[Dict[str, Any]]: def get_zulips() -> list[dict[str, Any]]:
global last_event_id global last_event_id
res = zulip_recipient.get_events(queue_id=queue_id, last_event_id=last_event_id) res = zulip_recipient.get_events(queue_id=queue_id, last_event_id=last_event_id)
if "error" in res.get("result", {}): if "error" in res.get("result", {}):
@ -128,7 +128,7 @@ send_zulip(
}, },
) )
msg_content: List[str] = [] msg_content: list[str] = []
while msg_to_send not in msg_content: while msg_to_send not in msg_content:
messages = get_zulips() messages = get_zulips()

View File

@ -11,7 +11,6 @@ import configparser
import re import re
import subprocess import subprocess
import sys import sys
from typing import Dict, List
def get_config( def get_config(
@ -44,7 +43,7 @@ def report(state: str, msg: str) -> None:
MAXSTATE = max(MAXSTATE, states[state]) MAXSTATE = max(MAXSTATE, states[state])
def run_sql_query(query: str) -> List[List[str]]: def run_sql_query(query: str) -> list[list[str]]:
command = [ command = [
"psql", "psql",
"-t", # Omit header line "-t", # Omit header line
@ -130,7 +129,7 @@ else:
report("CRITICAL", f"replica {client_addr} is in state {state}, not streaming") report("CRITICAL", f"replica {client_addr} is in state {state}, not streaming")
sent_offset = loc_to_abs_offset(sent_lsn) sent_offset = loc_to_abs_offset(sent_lsn)
lag: Dict[str, int] = {} lag: dict[str, int] = {}
lag["write"] = sent_offset - loc_to_abs_offset(write_lsn) lag["write"] = sent_offset - loc_to_abs_offset(write_lsn)
lag["flush"] = sent_offset - loc_to_abs_offset(flush_lsn) lag["flush"] = sent_offset - loc_to_abs_offset(flush_lsn)
lag["replay"] = sent_offset - loc_to_abs_offset(replay_lsn) lag["replay"] = sent_offset - loc_to_abs_offset(replay_lsn)

View File

@ -9,7 +9,7 @@ import sys
from collections import defaultdict from collections import defaultdict
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from http.server import BaseHTTPRequestHandler, HTTPServer from http.server import BaseHTTPRequestHandler, HTTPServer
from typing import Dict, List, Mapping, Optional, Protocol from typing import Mapping, Optional, Protocol
from urllib.parse import parse_qs, urlsplit from urllib.parse import parse_qs, urlsplit
@ -21,8 +21,8 @@ class GaugeMetric(Protocol):
class WalGPrometheusServer(BaseHTTPRequestHandler): class WalGPrometheusServer(BaseHTTPRequestHandler):
METRIC_PREFIX = "wal_g_backup_" METRIC_PREFIX = "wal_g_backup_"
metrics: Dict[str, List[str]] = {} metrics: dict[str, list[str]] = {}
metric_values: Dict[str, Dict[str, str]] = defaultdict(dict) metric_values: dict[str, dict[str, str]] = defaultdict(dict)
server_version = "wal-g-prometheus-server/1.0" server_version = "wal-g-prometheus-server/1.0"
@ -124,7 +124,7 @@ class WalGPrometheusServer(BaseHTTPRequestHandler):
backup_latest_compressed_size_bytes(latest["compressed_size"], labels) backup_latest_compressed_size_bytes(latest["compressed_size"], labels)
backup_latest_uncompressed_size_bytes(latest["uncompressed_size"], labels) backup_latest_uncompressed_size_bytes(latest["uncompressed_size"], labels)
def t(key: str, e: Dict[str, str] = latest) -> datetime: def t(key: str, e: dict[str, str] = latest) -> datetime:
return datetime.strptime(e[key], e["date_fmt"]).replace(tzinfo=timezone.utc) return datetime.strptime(e[key], e["date_fmt"]).replace(tzinfo=timezone.utc)
backup_earliest_age_seconds( backup_earliest_age_seconds(

View File

@ -5,7 +5,7 @@ import subprocess
import sys import sys
import time import time
from collections import defaultdict from collections import defaultdict
from typing import Any, DefaultDict, Dict, List from typing import Any
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@ -43,14 +43,14 @@ states = {
3: "UNKNOWN", 3: "UNKNOWN",
} }
MAX_SECONDS_TO_CLEAR: DefaultDict[str, int] = defaultdict( MAX_SECONDS_TO_CLEAR: defaultdict[str, int] = defaultdict(
lambda: 30, lambda: 30,
deferred_work=600, deferred_work=600,
digest_emails=1200, digest_emails=1200,
missedmessage_mobile_notifications=120, missedmessage_mobile_notifications=120,
embed_links=60, embed_links=60,
) )
CRITICAL_SECONDS_TO_CLEAR: DefaultDict[str, int] = defaultdict( CRITICAL_SECONDS_TO_CLEAR: defaultdict[str, int] = defaultdict(
lambda: 60, lambda: 60,
deferred_work=900, deferred_work=900,
missedmessage_mobile_notifications=180, missedmessage_mobile_notifications=180,
@ -60,8 +60,8 @@ CRITICAL_SECONDS_TO_CLEAR: DefaultDict[str, int] = defaultdict(
def analyze_queue_stats( def analyze_queue_stats(
queue_name: str, stats: Dict[str, Any], queue_count_rabbitmqctl: int queue_name: str, stats: dict[str, Any], queue_count_rabbitmqctl: int
) -> Dict[str, Any]: ) -> dict[str, Any]:
now = int(time.time()) now = int(time.time())
if stats == {}: if stats == {}:
return dict(status=UNKNOWN, name=queue_name, message="invalid or no stats data") return dict(status=UNKNOWN, name=queue_name, message="invalid or no stats data")
@ -117,7 +117,7 @@ WARN_COUNT_THRESHOLD_DEFAULT = 10
CRITICAL_COUNT_THRESHOLD_DEFAULT = 50 CRITICAL_COUNT_THRESHOLD_DEFAULT = 50
def check_other_queues(queue_counts_dict: Dict[str, int]) -> List[Dict[str, Any]]: def check_other_queues(queue_counts_dict: dict[str, int]) -> list[dict[str, Any]]:
"""Do a simple queue size check for queues whose workers don't publish stats files.""" """Do a simple queue size check for queues whose workers don't publish stats files."""
results = [] results = []
@ -161,7 +161,7 @@ def check_rabbitmq_queues() -> None:
[os.path.join(ZULIP_PATH, "scripts/get-django-setting"), "QUEUE_STATS_DIR"], [os.path.join(ZULIP_PATH, "scripts/get-django-setting"), "QUEUE_STATS_DIR"],
text=True, text=True,
).strip() ).strip()
queue_stats: Dict[str, Dict[str, Any]] = {} queue_stats: dict[str, dict[str, Any]] = {}
check_queues = normal_queues check_queues = normal_queues
if mobile_notification_shards > 1: if mobile_notification_shards > 1:
check_queues += [ check_queues += [

View File

@ -2,7 +2,6 @@
import argparse import argparse
import os import os
import sys import sys
from typing import Set
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(ZULIP_PATH) sys.path.append(ZULIP_PATH)
@ -17,7 +16,7 @@ ENV = get_environment()
EMOJI_CACHE_PATH = "/srv/zulip-emoji-cache" EMOJI_CACHE_PATH = "/srv/zulip-emoji-cache"
def get_caches_in_use(threshold_days: int) -> Set[str]: def get_caches_in_use(threshold_days: int) -> set[str]:
setups_to_check = {ZULIP_PATH} setups_to_check = {ZULIP_PATH}
caches_in_use = set() caches_in_use = set()

View File

@ -7,7 +7,6 @@
import argparse import argparse
import os import os
import sys import sys
from typing import Set
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(ZULIP_PATH) sys.path.append(ZULIP_PATH)
@ -22,7 +21,7 @@ ENV = get_environment()
NODE_MODULES_CACHE_PATH = "/srv/zulip-npm-cache" NODE_MODULES_CACHE_PATH = "/srv/zulip-npm-cache"
def get_caches_in_use(threshold_days: int) -> Set[str]: def get_caches_in_use(threshold_days: int) -> set[str]:
setups_to_check = {ZULIP_PATH} setups_to_check = {ZULIP_PATH}
caches_in_use = set() caches_in_use = set()

View File

@ -3,7 +3,6 @@ import argparse
import glob import glob
import os import os
import sys import sys
from typing import Set
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(ZULIP_PATH) sys.path.append(ZULIP_PATH)
@ -19,7 +18,7 @@ ENV = get_environment()
VENV_CACHE_DIR = "/srv/zulip-venv-cache" VENV_CACHE_DIR = "/srv/zulip-venv-cache"
def get_caches_in_use(threshold_days: int) -> Set[str]: def get_caches_in_use(threshold_days: int) -> set[str]:
setups_to_check = {ZULIP_PATH} setups_to_check = {ZULIP_PATH}
caches_in_use = set() caches_in_use = set()

View File

@ -4,11 +4,11 @@ import hashlib
import os import os
import subprocess import subprocess
import sys import sys
from typing import Iterable, List from typing import Iterable
def expand_reqs_helper(fpath: str) -> List[str]: def expand_reqs_helper(fpath: str) -> list[str]:
result: List[str] = [] result: list[str] = []
with open(fpath) as f: with open(fpath) as f:
for line in f: for line in f:
@ -20,7 +20,7 @@ def expand_reqs_helper(fpath: str) -> List[str]:
return result return result
def expand_reqs(fpath: str) -> List[str]: def expand_reqs(fpath: str) -> list[str]:
""" """
Returns a sorted list of unique dependencies specified by the requirements file `fpath`. Returns a sorted list of unique dependencies specified by the requirements file `fpath`.
Removes comments from the output and recursively visits files specified inside `fpath`. Removes comments from the output and recursively visits files specified inside `fpath`.

View File

@ -2,7 +2,7 @@ import logging
import os import os
import shutil import shutil
import subprocess import subprocess
from typing import List, Optional, Set, Tuple from typing import Optional
from scripts.lib.hash_reqs import expand_reqs, python_version from scripts.lib.hash_reqs import expand_reqs, python_version
from scripts.lib.zulip_tools import ENDC, WARNING, os_families, run, run_as_root from scripts.lib.zulip_tools import ENDC, WARNING, os_families, run, run_as_root
@ -62,7 +62,7 @@ FEDORA_VENV_DEPENDENCIES = [
] ]
def get_venv_dependencies(vendor: str, os_version: str) -> List[str]: def get_venv_dependencies(vendor: str, os_version: str) -> list[str]:
if "debian" in os_families(): if "debian" in os_families():
return VENV_DEPENDENCIES return VENV_DEPENDENCIES
elif "rhel" in os_families(): elif "rhel" in os_families():
@ -93,7 +93,7 @@ def get_index_filename(venv_path: str) -> str:
return os.path.join(venv_path, "package_index") return os.path.join(venv_path, "package_index")
def get_package_names(requirements_file: str) -> List[str]: def get_package_names(requirements_file: str) -> list[str]:
packages = expand_reqs(requirements_file) packages = expand_reqs(requirements_file)
cleaned = [] cleaned = []
operators = ["~=", "==", "!=", "<", ">"] operators = ["~=", "==", "!=", "<", ">"]
@ -132,7 +132,7 @@ def create_requirements_index_file(venv_path: str, requirements_file: str) -> st
return index_filename return index_filename
def get_venv_packages(venv_path: str) -> Set[str]: def get_venv_packages(venv_path: str) -> set[str]:
""" """
Returns the packages installed in the virtual environment using the Returns the packages installed in the virtual environment using the
package index file. package index file.
@ -141,7 +141,7 @@ def get_venv_packages(venv_path: str) -> Set[str]:
return {p.strip() for p in reader.read().split("\n") if p.strip()} return {p.strip() for p in reader.read().split("\n") if p.strip()}
def try_to_copy_venv(venv_path: str, new_packages: Set[str]) -> bool: def try_to_copy_venv(venv_path: str, new_packages: set[str]) -> bool:
""" """
Tries to copy packages from an old virtual environment in the cache Tries to copy packages from an old virtual environment in the cache
to the new virtual environment. The algorithm works as follows: to the new virtual environment. The algorithm works as follows:
@ -159,8 +159,8 @@ def try_to_copy_venv(venv_path: str, new_packages: Set[str]) -> bool:
desired_python_version = python_version() desired_python_version = python_version()
venv_name = os.path.basename(venv_path) venv_name = os.path.basename(venv_path)
overlaps: List[Tuple[int, str, Set[str]]] = [] overlaps: list[tuple[int, str, set[str]]] = []
old_packages: Set[str] = set() old_packages: set[str] = set()
for sha1sum in os.listdir(VENV_CACHE_PATH): for sha1sum in os.listdir(VENV_CACHE_PATH):
curr_venv_path = os.path.join(VENV_CACHE_PATH, sha1sum, venv_name) curr_venv_path = os.path.join(VENV_CACHE_PATH, sha1sum, venv_name)
if curr_venv_path == venv_path or not os.path.exists(get_index_filename(curr_venv_path)): if curr_venv_path == venv_path or not os.path.exists(get_index_filename(curr_venv_path)):
@ -230,8 +230,8 @@ def get_logfile_name(venv_path: str) -> str:
def create_log_entry( def create_log_entry(
target_log: str, target_log: str,
parent: str, parent: str,
copied_packages: Set[str], copied_packages: set[str],
new_packages: Set[str], new_packages: set[str],
) -> None: ) -> None:
venv_path = os.path.dirname(target_log) venv_path = os.path.dirname(target_log)
with open(target_log, "a") as writer: with open(target_log, "a") as writer:

View File

@ -5,7 +5,7 @@ import json
import os import os
import subprocess import subprocess
import sys import sys
from typing import Dict, List, Tuple, Union from typing import Union
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(BASE_DIR) sys.path.append(BASE_DIR)
@ -46,8 +46,8 @@ def write_updated_configs() -> None:
nginx_sharding_conf_f.write("map $host $tornado_server {\n") nginx_sharding_conf_f.write("map $host $tornado_server {\n")
nginx_sharding_conf_f.write(" default http://tornado9800;\n") nginx_sharding_conf_f.write(" default http://tornado9800;\n")
shard_map: Dict[str, Union[int, List[int]]] = {} shard_map: dict[str, Union[int, list[int]]] = {}
shard_regexes: List[Tuple[str, Union[int, List[int]]]] = [] shard_regexes: list[tuple[str, Union[int, list[int]]]] = []
external_host = subprocess.check_output( external_host = subprocess.check_output(
[os.path.join(BASE_DIR, "scripts/get-django-setting"), "EXTERNAL_HOST"], [os.path.join(BASE_DIR, "scripts/get-django-setting"), "EXTERNAL_HOST"],
text=True, text=True,

View File

@ -1,7 +1,7 @@
import socket import socket
import time import time
from http.client import HTTPConnection from http.client import HTTPConnection
from typing import Dict, List, Optional, Tuple, Union from typing import Optional, Union
from xmlrpc import client from xmlrpc import client
from typing_extensions import override from typing_extensions import override
@ -33,7 +33,7 @@ class UnixStreamTransport(client.Transport):
@override @override
def make_connection( def make_connection(
self, host: Union[Tuple[str, Dict[str, str]], str] self, host: Union[tuple[str, dict[str, str]], str]
) -> UnixStreamHTTPConnection: ) -> UnixStreamHTTPConnection:
return UnixStreamHTTPConnection(self.socket_path) return UnixStreamHTTPConnection(self.socket_path)
@ -45,8 +45,8 @@ def rpc() -> client.ServerProxy:
def list_supervisor_processes( def list_supervisor_processes(
filter_names: Optional[List[str]] = None, *, only_running: Optional[bool] = None filter_names: Optional[list[str]] = None, *, only_running: Optional[bool] = None
) -> List[str]: ) -> list[str]:
results = [] results = []
processes = rpc().supervisor.getAllProcessInfo() processes = rpc().supervisor.getAllProcessInfo()
assert isinstance(processes, list) assert isinstance(processes, list)

View File

@ -16,7 +16,7 @@ import sys
import time import time
import uuid import uuid
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import IO, Any, Dict, List, Literal, Optional, Sequence, Set, Union, overload from typing import IO, Any, Literal, Optional, Sequence, Union, overload
from urllib.parse import SplitResult from urllib.parse import SplitResult
import zoneinfo import zoneinfo
@ -300,7 +300,7 @@ def get_environment() -> str:
return "dev" return "dev"
def get_recent_deployments(threshold_days: int) -> Set[str]: def get_recent_deployments(threshold_days: int) -> set[str]:
# Returns a list of deployments not older than threshold days # Returns a list of deployments not older than threshold days
# including `/root/zulip` directory if it exists. # including `/root/zulip` directory if it exists.
recent = set() recent = set()
@ -337,8 +337,8 @@ def get_threshold_timestamp(threshold_days: int) -> int:
def get_caches_to_be_purged( def get_caches_to_be_purged(
caches_dir: str, caches_in_use: Set[str], threshold_days: int caches_dir: str, caches_in_use: set[str], threshold_days: int
) -> Set[str]: ) -> set[str]:
# Given a directory containing caches, a list of caches in use # Given a directory containing caches, a list of caches in use
# and threshold days, this function return a list of caches # and threshold days, this function return a list of caches
# which can be purged. Remove the cache only if it is: # which can be purged. Remove the cache only if it is:
@ -360,7 +360,7 @@ def get_caches_to_be_purged(
def purge_unused_caches( def purge_unused_caches(
caches_dir: str, caches_dir: str,
caches_in_use: Set[str], caches_in_use: set[str],
cache_type: str, cache_type: str,
args: argparse.Namespace, args: argparse.Namespace,
) -> None: ) -> None:
@ -405,8 +405,8 @@ def generate_sha1sum_emoji(zulip_path: str) -> str:
def maybe_perform_purging( def maybe_perform_purging(
dirs_to_purge: Set[str], dirs_to_purge: set[str],
dirs_to_keep: Set[str], dirs_to_keep: set[str],
dir_type: str, dir_type: str,
dry_run: bool, dry_run: bool,
verbose: bool, verbose: bool,
@ -429,7 +429,7 @@ def maybe_perform_purging(
@functools.lru_cache(None) @functools.lru_cache(None)
def parse_os_release() -> Dict[str, str]: def parse_os_release() -> dict[str, str]:
""" """
Example of the useful subset of the data: Example of the useful subset of the data:
{ {
@ -444,7 +444,7 @@ def parse_os_release() -> Dict[str, str]:
developers, but we avoid using it, as it is not available on developers, but we avoid using it, as it is not available on
RHEL-based platforms. RHEL-based platforms.
""" """
distro_info: Dict[str, str] = {} distro_info: dict[str, str] = {}
with open("/etc/os-release") as fp: with open("/etc/os-release") as fp:
for line in fp: for line in fp:
line = line.strip() line = line.strip()
@ -458,7 +458,7 @@ def parse_os_release() -> Dict[str, str]:
@functools.lru_cache(None) @functools.lru_cache(None)
def os_families() -> Set[str]: def os_families() -> set[str]:
""" """
Known families: Known families:
debian (includes: debian, ubuntu) debian (includes: debian, ubuntu)
@ -548,7 +548,7 @@ def is_root() -> bool:
return False return False
def run_as_root(args: List[str], **kwargs: Any) -> None: def run_as_root(args: list[str], **kwargs: Any) -> None:
sudo_args = kwargs.pop("sudo_args", []) sudo_args = kwargs.pop("sudo_args", [])
if not is_root(): if not is_root():
args = ["sudo", *sudo_args, "--", *args] args = ["sudo", *sudo_args, "--", *args]
@ -619,7 +619,7 @@ def get_config_file() -> configparser.RawConfigParser:
return config_file return config_file
def get_deploy_options(config_file: configparser.RawConfigParser) -> List[str]: def get_deploy_options(config_file: configparser.RawConfigParser) -> list[str]:
return shlex.split(get_config(config_file, "deployment", "deploy_options", "")) return shlex.split(get_config(config_file, "deployment", "deploy_options", ""))
@ -632,7 +632,7 @@ def run_psql_as_postgres(
subprocess.check_call(["su", "postgres", "-c", subcmd]) subprocess.check_call(["su", "postgres", "-c", subcmd])
def get_tornado_ports(config_file: configparser.RawConfigParser) -> List[int]: def get_tornado_ports(config_file: configparser.RawConfigParser) -> list[int]:
ports = [] ports = []
if config_file.has_section("tornado_sharding"): if config_file.has_section("tornado_sharding"):
ports = sorted( ports = sorted(
@ -705,7 +705,7 @@ def start_arg_parser(action: str, add_help: bool = False) -> argparse.ArgumentPa
return parser return parser
def listening_publicly(port: int) -> List[str]: def listening_publicly(port: int) -> list[str]:
filter = f"sport = :{port} and not src 127.0.0.1:{port} and not src [::1]:{port}" filter = f"sport = :{port} and not src 127.0.0.1:{port} and not src [::1]:{port}"
# Parse lines that look like this: # Parse lines that look like this:
# tcp LISTEN 0 128 0.0.0.0:25672 0.0.0.0:* # tcp LISTEN 0 128 0.0.0.0:25672 0.0.0.0:*

View File

@ -10,7 +10,7 @@ import signal
import sys import sys
from datetime import date, datetime, timedelta, timezone from datetime import date, datetime, timedelta, timezone
from enum import Enum, auto from enum import Enum, auto
from typing import List, Match, Optional, Set, TextIO, Tuple from typing import Match, Optional, TextIO
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(ZULIP_PATH) sys.path.append(ZULIP_PATH)
@ -242,7 +242,7 @@ def main() -> None:
sys.exit(signal.SIGINT + 128) sys.exit(signal.SIGINT + 128)
def parse_logfile_names(args: argparse.Namespace) -> List[str]: def parse_logfile_names(args: argparse.Namespace) -> list[str]:
if args.nginx: if args.nginx:
base_path = "/var/log/nginx/access.log" base_path = "/var/log/nginx/access.log"
else: else:
@ -311,7 +311,7 @@ def convert_to_nginx_date(date: str) -> str:
def parse_filters( def parse_filters(
args: argparse.Namespace, args: argparse.Namespace,
) -> Tuple[Set[FilterType], List[FilterFunc], List[str]]: ) -> tuple[set[FilterType], list[FilterFunc], list[str]]:
# The heuristics below are not intended to be precise -- they # The heuristics below are not intended to be precise -- they
# certainly count things as "IPv4" or "IPv6" addresses that are # certainly count things as "IPv4" or "IPv6" addresses that are
# invalid. However, we expect the input here to already be # invalid. However, we expect the input here to already be
@ -397,7 +397,7 @@ def parse_filters(
def passes_filters( def passes_filters(
string_filters: List[FilterFunc], string_filters: list[FilterFunc],
match: Match[str], match: Match[str],
args: argparse.Namespace, args: argparse.Namespace,
) -> bool: ) -> bool:
@ -434,7 +434,7 @@ last_match_end: Optional[datetime] = None
def print_line( def print_line(
match: Match[str], match: Match[str],
args: argparse.Namespace, args: argparse.Namespace,
filter_types: Set[FilterType], filter_types: set[FilterType],
use_color: bool, use_color: bool,
) -> None: ) -> None:
global last_match_end global last_match_end

View File

@ -6,7 +6,6 @@ import subprocess
import sys import sys
import time import time
from collections import defaultdict from collections import defaultdict
from typing import Dict
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(ZULIP_PATH) sys.path.append(ZULIP_PATH)
@ -30,7 +29,7 @@ TORNADO_PROCESSES = len(get_tornado_ports(config_file))
output = subprocess.check_output(["/usr/sbin/rabbitmqctl", "list_consumers"], text=True) output = subprocess.check_output(["/usr/sbin/rabbitmqctl", "list_consumers"], text=True)
consumers: Dict[str, int] = defaultdict(int) consumers: dict[str, int] = defaultdict(int)
queues = { queues = {
*normal_queues, *normal_queues,

View File

@ -3,7 +3,6 @@ import argparse
import os import os
import subprocess import subprocess
import sys import sys
from typing import Set
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(ZULIP_PATH) sys.path.append(ZULIP_PATH)
@ -56,7 +55,7 @@ def parse_args() -> argparse.Namespace:
return args return args
def get_deployments_to_be_purged(recent_deployments: Set[str]) -> Set[str]: def get_deployments_to_be_purged(recent_deployments: set[str]) -> set[str]:
all_deployments = { all_deployments = {
os.path.join(DEPLOYMENTS_DIR, deployment) for deployment in os.listdir(DEPLOYMENTS_DIR) os.path.join(DEPLOYMENTS_DIR, deployment) for deployment in os.listdir(DEPLOYMENTS_DIR)
} }

View File

@ -3,7 +3,6 @@
import os import os
import sys import sys
from contextlib import suppress from contextlib import suppress
from typing import Dict, List
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(BASE_DIR) sys.path.append(BASE_DIR)
@ -62,7 +61,7 @@ def generate_django_secretkey() -> str:
return get_random_string(50, chars) return get_random_string(50, chars)
def get_old_conf(output_filename: str) -> Dict[str, str]: def get_old_conf(output_filename: str) -> dict[str, str]:
if not os.path.exists(output_filename) or os.path.getsize(output_filename) == 0: if not os.path.exists(output_filename) or os.path.getsize(output_filename) == 0:
return {} return {}
@ -79,7 +78,7 @@ def generate_secrets(development: bool = False) -> None:
OUTPUT_SETTINGS_FILENAME = "/etc/zulip/zulip-secrets.conf" OUTPUT_SETTINGS_FILENAME = "/etc/zulip/zulip-secrets.conf"
current_conf = get_old_conf(OUTPUT_SETTINGS_FILENAME) current_conf = get_old_conf(OUTPUT_SETTINGS_FILENAME)
lines: List[str] = [] lines: list[str] = []
if len(current_conf) == 0: if len(current_conf) == 0:
lines = ["[secrets]\n"] lines = ["[secrets]\n"]

View File

@ -6,7 +6,6 @@ import re
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
from typing import List
import yaml import yaml
@ -64,7 +63,7 @@ puppet_env["FACTER_zulip_conf_path"] = args.config
puppet_env["FACTER_zulip_scripts_path"] = scripts_path puppet_env["FACTER_zulip_scripts_path"] = scripts_path
def noop_would_change(puppet_cmd: List[str]) -> bool: def noop_would_change(puppet_cmd: list[str]) -> bool:
# --noop does not work with --detailed-exitcodes; see # --noop does not work with --detailed-exitcodes; see
# https://tickets.puppetlabs.com/browse/PUP-686 # https://tickets.puppetlabs.com/browse/PUP-686
try: try:

View File

@ -4,7 +4,6 @@ import json
import os import os
import subprocess import subprocess
import sys import sys
from typing import List
sys.path.append(os.path.join(os.path.dirname(__file__), "..")) sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
@ -16,7 +15,7 @@ sanity_check.check_venv(__file__)
from scripts.lib.zulip_tools import ENDC, FAIL, WARNING from scripts.lib.zulip_tools import ENDC, FAIL, WARNING
def find_handlebars(translatable_strings: List[str]) -> List[str]: def find_handlebars(translatable_strings: list[str]) -> list[str]:
return [string for string in translatable_strings if "{{" in string] return [string for string in translatable_strings if "{{" in string]

View File

@ -4,7 +4,7 @@ import configparser
import os import os
import re import re
import sys import sys
from typing import Any, Dict, List, Optional from typing import Any, Optional
import requests import requests
@ -27,7 +27,7 @@ def get_config() -> configparser.ConfigParser:
return config return config
def area_labeled(issue: Dict[str, Any]) -> bool: def area_labeled(issue: dict[str, Any]) -> bool:
for label in issue["labels"]: for label in issue["labels"]:
label_name = str(label["name"]) label_name = str(label["name"])
if "area:" in label_name: if "area:" in label_name:
@ -35,7 +35,7 @@ def area_labeled(issue: Dict[str, Any]) -> bool:
return False return False
def is_issue(item: Dict[str, Any]) -> bool: def is_issue(item: dict[str, Any]) -> bool:
return "issues" in item["html_url"] return "issues" in item["html_url"]
@ -64,7 +64,7 @@ def check_issue_labels() -> None:
sys.exit(1) sys.exit(1)
next_page_url: Optional[str] = "https://api.github.com/repos/zulip/zulip/issues" next_page_url: Optional[str] = "https://api.github.com/repos/zulip/zulip/issues"
unlabeled_issue_urls: List[str] = [] unlabeled_issue_urls: list[str] = []
while next_page_url: while next_page_url:
try: try:
if args.force: if args.force:

View File

@ -14,7 +14,7 @@ import difflib
import os import os
import subprocess import subprocess
import sys import sys
from typing import Any, Callable, Dict, List, Optional from typing import Any, Callable, Optional
import orjson import orjson
@ -87,7 +87,7 @@ DEPRECATED_EVENTS = [
] ]
def get_event_checker(event: Dict[str, Any]) -> Optional[Callable[[str, Dict[str, Any]], None]]: def get_event_checker(event: dict[str, Any]) -> Optional[Callable[[str, dict[str, Any]], None]]:
name = event["type"] name = event["type"]
if "op" in event: if "op" in event:
name += "_" + event["op"] name += "_" + event["op"]
@ -99,7 +99,7 @@ def get_event_checker(event: Dict[str, Any]) -> Optional[Callable[[str, Dict[str
return None return None
def check_event(name: str, event: Dict[str, Any]) -> None: def check_event(name: str, event: dict[str, Any]) -> None:
event["id"] = 1 event["id"] = 1
checker = get_event_checker(event) checker = get_event_checker(event)
if checker is not None: if checker is not None:
@ -112,7 +112,7 @@ def check_event(name: str, event: Dict[str, Any]) -> None:
print(f"WARNING - NEED SCHEMA: {name}") print(f"WARNING - NEED SCHEMA: {name}")
def read_fixtures() -> Dict[str, Any]: def read_fixtures() -> dict[str, Any]:
cmd = [ cmd = [
"node", "node",
os.path.join(TOOLS_DIR, "node_lib/dump_fixtures.js"), os.path.join(TOOLS_DIR, "node_lib/dump_fixtures.js"),
@ -121,7 +121,7 @@ def read_fixtures() -> Dict[str, Any]:
return orjson.loads(schema) return orjson.loads(schema)
def verify_fixtures_are_sorted(names: List[str]) -> None: def verify_fixtures_are_sorted(names: list[str]) -> None:
for i in range(1, len(names)): for i in range(1, len(names)):
if names[i] < names[i - 1]: if names[i] < names[i - 1]:
raise Exception( raise Exception(
@ -135,7 +135,7 @@ def verify_fixtures_are_sorted(names: List[str]) -> None:
) )
def from_openapi(node: Dict[str, Any]) -> Any: def from_openapi(node: dict[str, Any]) -> Any:
"""Converts the OpenAPI data into event_schema.py style type """Converts the OpenAPI data into event_schema.py style type
definitions for convenient comparison with the types used for backend definitions for convenient comparison with the types used for backend
tests declared there.""" tests declared there."""

View File

@ -11,7 +11,7 @@ from tools.lib import sanity_check
sanity_check.check_venv(__file__) sanity_check.check_venv(__file__)
from typing import Dict, Iterable, List from typing import Iterable
from zulint import lister from zulint import lister
@ -31,7 +31,7 @@ EXCLUDED_FILES = [
] ]
def check_our_files(modified_only: bool, all_dups: bool, fix: bool, targets: List[str]) -> None: def check_our_files(modified_only: bool, all_dups: bool, fix: bool, targets: list[str]) -> None:
by_lang = lister.list_files( by_lang = lister.list_files(
targets=targets, targets=targets,
modified_only=modified_only, modified_only=modified_only,
@ -53,7 +53,7 @@ def check_html_templates(templates: Iterable[str], all_dups: bool, fix: bool) ->
if "templates/corporate/team.html" in templates: if "templates/corporate/team.html" in templates:
templates.remove("templates/corporate/team.html") templates.remove("templates/corporate/team.html")
def check_for_duplicate_ids(templates: List[str]) -> Dict[str, List[str]]: def check_for_duplicate_ids(templates: list[str]) -> dict[str, list[str]]:
template_id_dict = build_id_dict(templates) template_id_dict = build_id_dict(templates)
# TODO: Clean up these cases of duplicate ids in the code # TODO: Clean up these cases of duplicate ids in the code
IGNORE_IDS = [ IGNORE_IDS = [

View File

@ -4,7 +4,7 @@ import platform
import shlex import shlex
import subprocess import subprocess
import sys import sys
from typing import Callable, List from typing import Callable
TOOLS_DIR = os.path.dirname(__file__) TOOLS_DIR = os.path.dirname(__file__)
ROOT_DIR = os.path.dirname(TOOLS_DIR) ROOT_DIR = os.path.dirname(TOOLS_DIR)
@ -24,7 +24,7 @@ def run(check_func: Callable[[], bool]) -> None:
sys.exit(1) sys.exit(1)
def run_command(args: List[str]) -> None: def run_command(args: list[str]) -> None:
print(shlex.join(args)) print(shlex.join(args))
subprocess.check_call(args) subprocess.check_call(args)

View File

@ -1,12 +1,12 @@
import optparse import optparse
from typing import List, Union from typing import Union
from scrapy.commands import crawl from scrapy.commands import crawl
from scrapy.crawler import Crawler from scrapy.crawler import Crawler
class Command(crawl.Command): class Command(crawl.Command):
def run(self, args: List[str], opts: optparse.Values) -> None: def run(self, args: list[str], opts: optparse.Values) -> None:
crawlers = [] crawlers = []
real_create_crawler = self.crawler_process.create_crawler real_create_crawler = self.crawler_process.create_crawler

View File

@ -1,11 +1,10 @@
import os import os
import pathlib import pathlib
from typing import List
from .common.spiders import BaseDocumentationSpider from .common.spiders import BaseDocumentationSpider
def get_start_url() -> List[str]: def get_start_url() -> list[str]:
# Get index.html file as start URL and convert it to file URI # Get index.html file as start URL and convert it to file URI
dir_path = os.path.dirname(os.path.realpath(__file__)) dir_path = os.path.dirname(os.path.realpath(__file__))
start_file = os.path.join( start_file = os.path.join(

View File

@ -1,6 +1,6 @@
import os import os
from posixpath import basename from posixpath import basename
from typing import Any, List, Set from typing import Any
from urllib.parse import urlsplit from urllib.parse import urlsplit
from typing_extensions import override from typing_extensions import override
@ -20,7 +20,7 @@ class UnusedImagesLinterSpider(BaseDocumentationSpider):
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.static_images: Set[str] = set() self.static_images: set[str] = set()
self.images_static_dir: str = get_images_dir(self.images_path) self.images_static_dir: str = get_images_dir(self.images_path)
@override @override
@ -45,7 +45,7 @@ class UnusedImagesLinterSpider(BaseDocumentationSpider):
class HelpDocumentationSpider(UnusedImagesLinterSpider): class HelpDocumentationSpider(UnusedImagesLinterSpider):
name = "help_documentation_crawler" name = "help_documentation_crawler"
start_urls = ["http://localhost:9981/help/"] start_urls = ["http://localhost:9981/help/"]
deny_domains: List[str] = [] deny_domains: list[str] = []
deny = ["/policies/privacy"] deny = ["/policies/privacy"]
images_path = "static/images/help" images_path = "static/images/help"
@ -53,7 +53,7 @@ class HelpDocumentationSpider(UnusedImagesLinterSpider):
class APIDocumentationSpider(UnusedImagesLinterSpider): class APIDocumentationSpider(UnusedImagesLinterSpider):
name = "api_documentation_crawler" name = "api_documentation_crawler"
start_urls = ["http://localhost:9981/api"] start_urls = ["http://localhost:9981/api"]
deny_domains: List[str] = [] deny_domains: list[str] = []
images_path = "static/images/api" images_path = "static/images/api"
@ -84,4 +84,4 @@ class PorticoDocumentationSpider(BaseDocumentationSpider):
"http://localhost:9981/for/research/", "http://localhost:9981/for/research/",
"http://localhost:9981/security/", "http://localhost:9981/security/",
] ]
deny_domains: List[str] = [] deny_domains: list[str] = []

View File

@ -1,7 +1,7 @@
import json import json
import os import os
import re import re
from typing import Callable, Iterator, List, Optional, Union from typing import Callable, Iterator, Optional, Union
from urllib.parse import urlsplit from urllib.parse import urlsplit
import scrapy import scrapy
@ -60,10 +60,10 @@ ZULIP_SERVER_GITHUB_DIRECTORY_PATH_PREFIX = "/zulip/zulip/tree/main"
class BaseDocumentationSpider(scrapy.Spider): class BaseDocumentationSpider(scrapy.Spider):
name: Optional[str] = None name: Optional[str] = None
# Exclude domain address. # Exclude domain address.
deny_domains: List[str] = [] deny_domains: list[str] = []
start_urls: List[str] = [] start_urls: list[str] = []
deny: List[str] = [] deny: list[str] = []
file_extensions: List[str] = ["." + ext for ext in IGNORED_EXTENSIONS] file_extensions: list[str] = ["." + ext for ext in IGNORED_EXTENSIONS]
tags = ("a", "area", "img") tags = ("a", "area", "img")
attrs = ("href", "src") attrs = ("href", "src")

View File

@ -14,7 +14,6 @@ import re
import socket import socket
import sys import sys
from argparse import ArgumentParser from argparse import ArgumentParser
from typing import List
import requests import requests
@ -30,7 +29,7 @@ append_key = """\
""" """
def get_mentor_keys(username: str) -> List[str]: def get_mentor_keys(username: str) -> list[str]:
url = f"https://api.github.com/users/{username}/keys" url = f"https://api.github.com/users/{username}/keys"
r = requests.get(url) r = requests.get(url)

View File

@ -20,7 +20,7 @@ import sys
import time import time
import urllib.error import urllib.error
import urllib.request import urllib.request
from typing import Any, Dict, List, Tuple from typing import Any
import digitalocean import digitalocean
import requests import requests
@ -56,7 +56,7 @@ def assert_github_user_exists(github_username: str) -> bool:
sys.exit(1) sys.exit(1)
def get_ssh_public_keys_from_github(github_username: str) -> List[Dict[str, Any]]: def get_ssh_public_keys_from_github(github_username: str) -> list[dict[str, Any]]:
print("Checking to see that GitHub user has available public keys...") print("Checking to see that GitHub user has available public keys...")
apiurl_keys = f"https://api.github.com/users/{github_username}/keys" apiurl_keys = f"https://api.github.com/users/{github_username}/keys"
try: try:
@ -108,12 +108,12 @@ def assert_droplet_does_not_exist(my_token: str, droplet_name: str, recreate: bo
print("...No droplet found...proceeding.") print("...No droplet found...proceeding.")
def get_ssh_keys_string_from_github_ssh_key_dicts(userkey_dicts: List[Dict[str, Any]]) -> str: def get_ssh_keys_string_from_github_ssh_key_dicts(userkey_dicts: list[dict[str, Any]]) -> str:
return "\n".join(userkey_dict["key"] for userkey_dict in userkey_dicts) return "\n".join(userkey_dict["key"] for userkey_dict in userkey_dicts)
def generate_dev_droplet_user_data( def generate_dev_droplet_user_data(
username: str, subdomain: str, userkey_dicts: List[Dict[str, Any]] username: str, subdomain: str, userkey_dicts: list[dict[str, Any]]
) -> str: ) -> str:
ssh_keys_string = get_ssh_keys_string_from_github_ssh_key_dicts(userkey_dicts) ssh_keys_string = get_ssh_keys_string_from_github_ssh_key_dicts(userkey_dicts)
setup_root_ssh_keys = f"printf '{ssh_keys_string}' > /root/.ssh/authorized_keys" setup_root_ssh_keys = f"printf '{ssh_keys_string}' > /root/.ssh/authorized_keys"
@ -159,7 +159,7 @@ su -c 'git config --global pull.rebase true' zulipdev
return cloudconf return cloudconf
def generate_prod_droplet_user_data(username: str, userkey_dicts: List[Dict[str, Any]]) -> str: def generate_prod_droplet_user_data(username: str, userkey_dicts: list[dict[str, Any]]) -> str:
ssh_keys_string = get_ssh_keys_string_from_github_ssh_key_dicts(userkey_dicts) ssh_keys_string = get_ssh_keys_string_from_github_ssh_key_dicts(userkey_dicts)
setup_root_ssh_keys = f"printf '{ssh_keys_string}' > /root/.ssh/authorized_keys" setup_root_ssh_keys = f"printf '{ssh_keys_string}' > /root/.ssh/authorized_keys"
@ -179,10 +179,10 @@ def create_droplet(
my_token: str, my_token: str,
template_id: str, template_id: str,
name: str, name: str,
tags: List[str], tags: list[str],
user_data: str, user_data: str,
region: str = "nyc3", region: str = "nyc3",
) -> Tuple[str, str]: ) -> tuple[str, str]:
droplet = digitalocean.Droplet( droplet = digitalocean.Droplet(
token=my_token, token=my_token,
name=name, name=name,
@ -215,7 +215,7 @@ def create_droplet(
return (droplet.ip_address, droplet.ip_v6_address) return (droplet.ip_address, droplet.ip_v6_address)
def delete_existing_records(records: List[digitalocean.Record], record_name: str) -> None: def delete_existing_records(records: list[digitalocean.Record], record_name: str) -> None:
count = 0 count = 0
for record in records: for record in records:
if ( if (

View File

@ -11,7 +11,7 @@ import os
import sys import sys
import unicodedata import unicodedata
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Dict, List, Optional, Union from typing import Optional, Union
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from scripts.lib.setup_path import setup_path from scripts.lib.setup_path import setup_path
@ -42,7 +42,7 @@ args = parser.parse_args()
class ContributorsJSON(TypedDict): class ContributorsJSON(TypedDict):
date: str date: str
contributors: List[Dict[str, Union[int, str]]] contributors: list[dict[str, Union[int, str]]]
class Contributor(TypedDict): class Contributor(TypedDict):
@ -56,15 +56,15 @@ class Contributor(TypedDict):
logger = logging.getLogger("zulip.fetch_contributors_json") logger = logging.getLogger("zulip.fetch_contributors_json")
def fetch_contributors(repo_name: str, max_retries: int) -> List[Contributor]: def fetch_contributors(repo_name: str, max_retries: int) -> list[Contributor]:
contributors: List[Contributor] = [] contributors: list[Contributor] = []
page_index = 1 page_index = 1
api_link = f"https://api.github.com/repos/zulip/{repo_name}/contributors" api_link = f"https://api.github.com/repos/zulip/{repo_name}/contributors"
api_data = {"anon": "1"} api_data = {"anon": "1"}
certificates = os.environ.get("CUSTOM_CA_CERTIFICATES") certificates = os.environ.get("CUSTOM_CA_CERTIFICATES")
headers: Dict[str, str] = {} headers: dict[str, str] = {}
personal_access_token = get_secret("github_personal_access_token") personal_access_token = get_secret("github_personal_access_token")
if personal_access_token is not None: if personal_access_token is not None:
headers = {"Authorization": f"token {personal_access_token}"} headers = {"Authorization": f"token {personal_access_token}"}
@ -134,7 +134,7 @@ def update_contributor_data_file() -> None:
] ]
data: ContributorsJSON = dict(date=str(datetime.now(tz=timezone.utc).date()), contributors=[]) data: ContributorsJSON = dict(date=str(datetime.now(tz=timezone.utc).date()), contributors=[])
contributor_username_to_data: Dict[str, Dict[str, Union[str, int]]] = {} contributor_username_to_data: dict[str, dict[str, Union[str, int]]] = {}
for repo_name in repo_names: for repo_name in repo_names:
contributors = fetch_contributors(repo_name, args.max_retries) contributors = fetch_contributors(repo_name, args.max_retries)

View File

@ -3,14 +3,13 @@ import json
import os import os
import re import re
from subprocess import check_output from subprocess import check_output
from typing import Dict, List
def get_json_filename(locale: str) -> str: def get_json_filename(locale: str) -> str:
return f"locale/{locale}/mobile.json" return f"locale/{locale}/mobile.json"
def get_locales() -> List[str]: def get_locales() -> list[str]:
output = check_output(["git", "ls-files", "locale"], text=True) output = check_output(["git", "ls-files", "locale"], text=True)
tracked_files = output.split() tracked_files = output.split()
regex = re.compile(r"locale/(\w+)/LC_MESSAGES/django.po") regex = re.compile(r"locale/(\w+)/LC_MESSAGES/django.po")
@ -23,7 +22,7 @@ def get_locales() -> List[str]:
return locales return locales
def get_translation_stats(resource_path: str) -> Dict[str, int]: def get_translation_stats(resource_path: str) -> dict[str, int]:
with open(resource_path) as raw_resource_file: with open(resource_path) as raw_resource_file:
raw_info = json.load(raw_resource_file) raw_info = json.load(raw_resource_file)
@ -32,7 +31,7 @@ def get_translation_stats(resource_path: str) -> Dict[str, int]:
return {"total": total, "not_translated": not_translated} return {"total": total, "not_translated": not_translated}
translation_stats: Dict[str, Dict[str, int]] = {} translation_stats: dict[str, dict[str, int]] = {}
locale_paths = [] # List[str] locale_paths = [] # List[str]
for locale in get_locales(): for locale in get_locales():
path = get_json_filename(locale) path = get_json_filename(locale)

View File

@ -4,7 +4,7 @@ import argparse
import html import html
import json import json
import sys import sys
from typing import Dict, NamedTuple from typing import NamedTuple
class CLIArgs(NamedTuple): class CLIArgs(NamedTuple):
@ -36,7 +36,7 @@ if __name__ == "__main__":
args = parse_args() args = parse_args()
print(f"unescaping file {args.filename}", file=sys.stderr) print(f"unescaping file {args.filename}", file=sys.stderr)
json_data: Dict[str, str] = {} json_data: dict[str, str] = {}
with open(args.filename) as source: with open(args.filename) as source:
json_data = json.load(source) json_data = json.load(source)

View File

@ -3,7 +3,6 @@ import json
import os import os
import re import re
from subprocess import check_output from subprocess import check_output
from typing import Dict, List
LEGACY_STRINGS_MAP = { LEGACY_STRINGS_MAP = {
"<p>You are searching for messages that belong to more than one channel, which is not possible.</p>": "<p>You are searching for messages that belong to more than one stream, which is not possible.</p>", "<p>You are searching for messages that belong to more than one channel, which is not possible.</p>": "<p>You are searching for messages that belong to more than one stream, which is not possible.</p>",
@ -187,7 +186,7 @@ def get_legacy_filename(locale: str) -> str:
return f"locale/{locale}/legacy_stream_translations.json" return f"locale/{locale}/legacy_stream_translations.json"
def get_locales() -> List[str]: def get_locales() -> list[str]:
output = check_output(["git", "ls-files", "locale"], text=True) output = check_output(["git", "ls-files", "locale"], text=True)
tracked_files = output.split() tracked_files = output.split()
regex = re.compile(r"locale/(\w+)/LC_MESSAGES/django.po") regex = re.compile(r"locale/(\w+)/LC_MESSAGES/django.po")
@ -200,7 +199,7 @@ def get_locales() -> List[str]:
return locales return locales
def get_translations(path: str) -> Dict[str, str]: def get_translations(path: str) -> dict[str, str]:
with open(path) as raw_resource_file: with open(path) as raw_resource_file:
translations = json.load(raw_resource_file) translations = json.load(raw_resource_file)
@ -208,10 +207,10 @@ def get_translations(path: str) -> Dict[str, str]:
def update_for_legacy_stream_translations( def update_for_legacy_stream_translations(
current: Dict[str, str], legacy: Dict[str, str], path: str current: dict[str, str], legacy: dict[str, str], path: str
) -> None: ) -> None:
number_of_updates = 0 number_of_updates = 0
updated_translations: Dict[str, str] = {} updated_translations: dict[str, str] = {}
for line in current: for line in current:
# If the string has a legacy string mapped and see if it's # If the string has a legacy string mapped and see if it's
# not currently translated (e.g. an empty string), then use # not currently translated (e.g. an empty string), then use

View File

@ -1,5 +1,5 @@
import re import re
from typing import List, Match, Tuple from typing import Match
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
@ -245,7 +245,7 @@ def is_capitalized(safe_text: str) -> bool:
return not any(DISALLOWED_REGEX.search(sentence.strip()) for sentence in sentences) return not any(DISALLOWED_REGEX.search(sentence.strip()) for sentence in sentences)
def check_banned_words(text: str) -> List[str]: def check_banned_words(text: str) -> list[str]:
lower_cased_text = text.lower() lower_cased_text = text.lower()
errors = [] errors = []
for word, reason in BANNED_WORDS.items(): for word, reason in BANNED_WORDS.items():
@ -266,7 +266,7 @@ def check_banned_words(text: str) -> List[str]:
return errors return errors
def check_capitalization(strings: List[str]) -> Tuple[List[str], List[str], List[str]]: def check_capitalization(strings: list[str]) -> tuple[list[str], list[str], list[str]]:
errors = [] errors = []
ignored = [] ignored = []
banned_word_errors = [] banned_word_errors = []

View File

@ -1,5 +1,3 @@
from typing import List
from gitlint.git import GitCommit from gitlint.git import GitCommit
from gitlint.rules import CommitMessageTitle, LineRule, RuleViolation from gitlint.rules import CommitMessageTitle, LineRule, RuleViolation
@ -87,7 +85,7 @@ class ImperativeMood(LineRule):
'("{word}" -> "{imperative}"): "{title}"' '("{word}" -> "{imperative}"): "{title}"'
) )
def validate(self, line: str, commit: GitCommit) -> List[RuleViolation]: def validate(self, line: str, commit: GitCommit) -> list[RuleViolation]:
violations = [] violations = []
# Ignore the section tag (ie `<section tag>: <message body>.`) # Ignore the section tag (ie `<section tag>: <message body>.`)

View File

@ -1,12 +1,11 @@
import re import re
from collections import defaultdict from collections import defaultdict
from typing import Dict, List
from .template_parser import FormattedError, Token, tokenize from .template_parser import FormattedError, Token, tokenize
class TagInfo: class TagInfo:
def __init__(self, tag: str, classes: List[str], ids: List[str], token: Token) -> None: def __init__(self, tag: str, classes: list[str], ids: list[str], token: Token) -> None:
self.tag = tag self.tag = tag
self.classes = classes self.classes = classes
self.ids = ids self.ids = ids
@ -29,8 +28,8 @@ class TagInfo:
def get_tag_info(token: Token) -> TagInfo: def get_tag_info(token: Token) -> TagInfo:
s = token.s s = token.s
tag = token.tag tag = token.tag
classes: List[str] = [] classes: list[str] = []
ids: List[str] = [] ids: list[str] = []
searches = [ searches = [
(classes, ' class="(.*?)"'), (classes, ' class="(.*?)"'),
@ -48,7 +47,7 @@ def get_tag_info(token: Token) -> TagInfo:
return TagInfo(tag=tag, classes=classes, ids=ids, token=token) return TagInfo(tag=tag, classes=classes, ids=ids, token=token)
def split_for_id_and_class(element: str) -> List[str]: def split_for_id_and_class(element: str) -> list[str]:
# Here we split a given string which is expected to contain id or class # Here we split a given string which is expected to contain id or class
# attributes from HTML tags. This also takes care of template variables # attributes from HTML tags. This also takes care of template variables
# in string during splitting process. For eg. 'red black {{ a|b|c }}' # in string during splitting process. For eg. 'red black {{ a|b|c }}'
@ -74,8 +73,8 @@ def split_for_id_and_class(element: str) -> List[str]:
return lst return lst
def build_id_dict(templates: List[str]) -> Dict[str, List[str]]: def build_id_dict(templates: list[str]) -> dict[str, list[str]]:
template_id_dict: Dict[str, List[str]] = defaultdict(list) template_id_dict: dict[str, list[str]] = defaultdict(list)
for fn in templates: for fn in templates:
with open(fn) as f: with open(fn) as f:

View File

@ -1,12 +1,12 @@
import subprocess import subprocess
from typing import List, Optional from typing import Optional
from zulint.printer import BOLDRED, CYAN, ENDC, GREEN from zulint.printer import BOLDRED, CYAN, ENDC, GREEN
from .template_parser import Token from .template_parser import Token
def shift_indents_to_the_next_tokens(tokens: List[Token]) -> None: def shift_indents_to_the_next_tokens(tokens: list[Token]) -> None:
""" """
During the parsing/validation phase, it's useful to have separate During the parsing/validation phase, it's useful to have separate
tokens for "indent" chunks, but during pretty printing, we like tokens for "indent" chunks, but during pretty printing, we like
@ -39,7 +39,7 @@ def token_allows_children_to_skip_indents(token: Token) -> bool:
return token.kind in ("django_start", "handlebars_start") or token.tag == "a" return token.kind in ("django_start", "handlebars_start") or token.tag == "a"
def adjust_block_indentation(tokens: List[Token], fn: str) -> None: def adjust_block_indentation(tokens: list[Token], fn: str) -> None:
start_token: Optional[Token] = None start_token: Optional[Token] = None
for token in tokens: for token in tokens:
@ -106,7 +106,7 @@ def adjust_block_indentation(tokens: List[Token], fn: str) -> None:
token.indent = start_token.child_indent token.indent = start_token.child_indent
def fix_indents_for_multi_line_tags(tokens: List[Token]) -> None: def fix_indents_for_multi_line_tags(tokens: list[Token]) -> None:
def fix(frag: str) -> str: def fix(frag: str) -> str:
frag = frag.strip() frag = frag.strip()
return continue_indent + frag if frag else "" return continue_indent + frag if frag else ""
@ -128,13 +128,13 @@ def fix_indents_for_multi_line_tags(tokens: List[Token]) -> None:
token.new_s = frags[0] + "\n" + "\n".join(fix(frag) for frag in frags[1:]) token.new_s = frags[0] + "\n" + "\n".join(fix(frag) for frag in frags[1:])
def apply_token_indents(tokens: List[Token]) -> None: def apply_token_indents(tokens: list[Token]) -> None:
for token in tokens: for token in tokens:
if token.indent: if token.indent:
token.new_s = token.indent + token.new_s token.new_s = token.indent + token.new_s
def pretty_print_html(tokens: List[Token], fn: str) -> str: def pretty_print_html(tokens: list[Token], fn: str) -> str:
for token in tokens: for token in tokens:
token.new_s = token.s token.new_s = token.s
@ -150,7 +150,7 @@ def numbered_lines(s: str) -> str:
return "".join(f"{i + 1: >5} {line}\n" for i, line in enumerate(s.split("\n"))) return "".join(f"{i + 1: >5} {line}\n" for i, line in enumerate(s.split("\n")))
def validate_indent_html(fn: str, tokens: List[Token], fix: bool) -> bool: def validate_indent_html(fn: str, tokens: list[Token], fix: bool) -> bool:
with open(fn) as f: with open(fn) as f:
html = f.read() html = f.read()
phtml = pretty_print_html(tokens, fn) phtml = pretty_print_html(tokens, fn)

View File

@ -6,7 +6,7 @@ import os
import platform import platform
import subprocess import subprocess
import sys import sys
from typing import List, NoReturn from typing import NoReturn
os.environ["PYTHONUNBUFFERED"] = "y" os.environ["PYTHONUNBUFFERED"] = "y"
@ -232,7 +232,7 @@ def install_system_deps() -> None:
run_as_root(["./scripts/lib/build-pgroonga"]) run_as_root(["./scripts/lib/build-pgroonga"])
def install_apt_deps(deps_to_install: List[str]) -> None: def install_apt_deps(deps_to_install: list[str]) -> None:
# setup-apt-repo does an `apt-get update` if the sources.list files changed. # setup-apt-repo does an `apt-get update` if the sources.list files changed.
run_as_root(["./scripts/lib/setup-apt-repo"]) run_as_root(["./scripts/lib/setup-apt-repo"])
@ -255,7 +255,7 @@ def install_apt_deps(deps_to_install: List[str]) -> None:
) )
def install_yum_deps(deps_to_install: List[str]) -> None: def install_yum_deps(deps_to_install: list[str]) -> None:
print(WARNING + "RedHat support is still experimental." + ENDC) print(WARNING + "RedHat support is still experimental." + ENDC)
run_as_root(["./scripts/lib/setup-yum-repo"]) run_as_root(["./scripts/lib/setup-yum-repo"])
@ -265,7 +265,7 @@ def install_yum_deps(deps_to_install: List[str]) -> None:
# #
# Error: Package: moreutils-0.49-2.el7.x86_64 (epel) # Error: Package: moreutils-0.49-2.el7.x86_64 (epel)
# Requires: perl(IPC::Run) # Requires: perl(IPC::Run)
yum_extra_flags: List[str] = [] yum_extra_flags: list[str] = []
if vendor == "rhel": if vendor == "rhel":
proc = subprocess.run( proc = subprocess.run(
["sudo", "subscription-manager", "status"], ["sudo", "subscription-manager", "status"],

View File

@ -11,7 +11,6 @@ import glob
import os import os
import shutil import shutil
import sys import sys
from typing import List
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@ -57,7 +56,7 @@ def create_var_directories() -> None:
os.makedirs(path, exist_ok=True) os.makedirs(path, exist_ok=True)
def build_pygments_data_paths() -> List[str]: def build_pygments_data_paths() -> list[str]:
paths = [ paths = [
"tools/setup/build_pygments_data", "tools/setup/build_pygments_data",
"tools/setup/lang.json", "tools/setup/lang.json",
@ -65,27 +64,27 @@ def build_pygments_data_paths() -> List[str]:
return paths return paths
def build_timezones_data_paths() -> List[str]: def build_timezones_data_paths() -> list[str]:
paths = [ paths = [
"tools/setup/build_timezone_values", "tools/setup/build_timezone_values",
] ]
return paths return paths
def build_landing_page_images_paths() -> List[str]: def build_landing_page_images_paths() -> list[str]:
paths = ["tools/setup/generate_landing_page_images.py"] paths = ["tools/setup/generate_landing_page_images.py"]
paths += glob.glob("static/images/landing-page/hello/original/*") paths += glob.glob("static/images/landing-page/hello/original/*")
return paths return paths
def compilemessages_paths() -> List[str]: def compilemessages_paths() -> list[str]:
paths = ["zerver/management/commands/compilemessages.py"] paths = ["zerver/management/commands/compilemessages.py"]
paths += glob.glob("locale/*/LC_MESSAGES/*.po") paths += glob.glob("locale/*/LC_MESSAGES/*.po")
paths += glob.glob("locale/*/translations.json") paths += glob.glob("locale/*/translations.json")
return paths return paths
def configure_rabbitmq_paths() -> List[str]: def configure_rabbitmq_paths() -> list[str]:
paths = [ paths = [
"scripts/setup/configure-rabbitmq", "scripts/setup/configure-rabbitmq",
] ]
@ -194,7 +193,7 @@ def need_to_run_compilemessages() -> bool:
) )
def need_to_run_configure_rabbitmq(settings_list: List[str]) -> bool: def need_to_run_configure_rabbitmq(settings_list: list[str]) -> bool:
obsolete = is_digest_obsolete( obsolete = is_digest_obsolete(
"last_configure_rabbitmq_hash", "last_configure_rabbitmq_hash",
configure_rabbitmq_paths(), configure_rabbitmq_paths(),

View File

@ -1,4 +1,4 @@
from typing import Callable, List, Optional from typing import Callable, Optional
from typing_extensions import override from typing_extensions import override
@ -51,7 +51,7 @@ class Token:
self.parent_token: Optional[Token] = None self.parent_token: Optional[Token] = None
def tokenize(text: str, template_format: Optional[str] = None) -> List[Token]: def tokenize(text: str, template_format: Optional[str] = None) -> list[Token]:
in_code_block = False in_code_block = False
def advance(n: int) -> None: def advance(n: int) -> None:
@ -124,7 +124,7 @@ def tokenize(text: str, template_format: Optional[str] = None) -> List[Token]:
return looking_at("\n") or looking_at(" ") return looking_at("\n") or looking_at(" ")
state = TokenizerState() state = TokenizerState()
tokens: List[Token] = [] tokens: list[Token] = []
while state.i < len(text): while state.i < len(text):
try: try:
@ -355,7 +355,7 @@ def validate(
fn: Optional[str] = None, fn: Optional[str] = None,
text: Optional[str] = None, text: Optional[str] = None,
template_format: Optional[str] = None, template_format: Optional[str] = None,
) -> List[Token]: ) -> list[Token]:
assert fn or text assert fn or text
if fn is None: if fn is None:
@ -500,7 +500,7 @@ def validate(
return tokens return tokens
def ensure_matching_indentation(fn: str, tokens: List[Token], lines: List[str]) -> None: def ensure_matching_indentation(fn: str, tokens: list[Token], lines: list[str]) -> None:
def has_bad_indentation() -> bool: def has_bad_indentation() -> bool:
is_inline_tag = start_tag in HTML_INLINE_TAGS and start_token.kind == "html_start" is_inline_tag = start_tag in HTML_INLINE_TAGS and start_token.kind == "html_start"
@ -545,7 +545,7 @@ def ensure_matching_indentation(fn: str, tokens: List[Token], lines: List[str])
) )
def prevent_extra_newlines(fn: str, tokens: List[Token]) -> None: def prevent_extra_newlines(fn: str, tokens: list[Token]) -> None:
count = 0 count = 0
for token in tokens: for token in tokens:
@ -560,7 +560,7 @@ def prevent_extra_newlines(fn: str, tokens: List[Token]) -> None:
) )
def prevent_whitespace_violations(fn: str, tokens: List[Token]) -> None: def prevent_whitespace_violations(fn: str, tokens: list[Token]) -> None:
if tokens[0].kind in ("indent", "whitespace"): if tokens[0].kind in ("indent", "whitespace"):
raise TemplateParserError(f" Please remove the whitespace at the beginning of {fn}.") raise TemplateParserError(f" Please remove the whitespace at the beginning of {fn}.")

View File

@ -3,7 +3,7 @@ import os
import subprocess import subprocess
import sys import sys
from argparse import ArgumentParser from argparse import ArgumentParser
from typing import Iterable, List, Optional, Tuple from typing import Iterable, Optional
from scripts.lib.zulip_tools import get_dev_uuid_var_path from scripts.lib.zulip_tools import get_dev_uuid_var_path
from version import PROVISION_VERSION from version import PROVISION_VERSION
@ -25,7 +25,7 @@ properly.
""" """
def preamble(version: Tuple[int, ...]) -> str: def preamble(version: tuple[int, ...]) -> str:
text = PREAMBLE.format(version, PROVISION_VERSION) text = PREAMBLE.format(version, PROVISION_VERSION)
text += "\n" text += "\n"
return text return text
@ -49,7 +49,7 @@ Do this: `./tools/provision`
""" """
def get_provisioning_status() -> Tuple[bool, Optional[str]]: def get_provisioning_status() -> tuple[bool, Optional[str]]:
version_file = get_version_file() version_file = get_version_file()
if not os.path.exists(version_file): if not os.path.exists(version_file):
# If the developer doesn't have a version_file written by # If the developer doesn't have a version_file written by
@ -93,7 +93,7 @@ def add_provision_check_override_param(parser: ArgumentParser) -> None:
) )
def find_js_test_files(test_dir: str, files: Iterable[str]) -> List[str]: def find_js_test_files(test_dir: str, files: Iterable[str]) -> list[str]:
test_files = [] test_files = []
for file in files: for file in files:
file = min( file = min(

View File

@ -1,5 +1,3 @@
from typing import List
from zulint.custom_rules import Rule, RuleList from zulint.custom_rules import Rule, RuleList
# Rule help: # Rule help:
@ -39,7 +37,7 @@ FILES_WITH_LEGACY_SUBJECT = {
"zerver/tests/test_message_fetch.py", "zerver/tests/test_message_fetch.py",
} }
shebang_rules: List["Rule"] = [ shebang_rules: list["Rule"] = [
{ {
"pattern": r"\A#!", "pattern": r"\A#!",
"description": "zerver library code shouldn't have a shebang line.", "description": "zerver library code shouldn't have a shebang line.",
@ -59,7 +57,7 @@ shebang_rules: List["Rule"] = [
}, },
] ]
base_whitespace_rules: List["Rule"] = [ base_whitespace_rules: list["Rule"] = [
{ {
"pattern": r"[\t ]+$", "pattern": r"[\t ]+$",
"exclude": {"tools/ci/success-http-headers.template.txt"}, "exclude": {"tools/ci/success-http-headers.template.txt"},
@ -70,7 +68,7 @@ base_whitespace_rules: List["Rule"] = [
"description": "Missing newline at end of file", "description": "Missing newline at end of file",
}, },
] ]
whitespace_rules: List["Rule"] = [ whitespace_rules: list["Rule"] = [
*base_whitespace_rules, *base_whitespace_rules,
{ {
"pattern": "http://zulip.readthedocs.io", "pattern": "http://zulip.readthedocs.io",
@ -85,7 +83,7 @@ whitespace_rules: List["Rule"] = [
"description": "Web app should be two words", "description": "Web app should be two words",
}, },
] ]
comma_whitespace_rule: List["Rule"] = [ comma_whitespace_rule: list["Rule"] = [
{ {
"pattern": ", {2,}[^#/ ]", "pattern": ", {2,}[^#/ ]",
"exclude": {"zerver/tests", "web/tests", "corporate/tests"}, "exclude": {"zerver/tests", "web/tests", "corporate/tests"},
@ -94,7 +92,7 @@ comma_whitespace_rule: List["Rule"] = [
"bad_lines": ["foo(1, 2, 3)", "foo(1, 2, 3)"], "bad_lines": ["foo(1, 2, 3)", "foo(1, 2, 3)"],
}, },
] ]
markdown_whitespace_rules: List["Rule"] = [ markdown_whitespace_rules: list["Rule"] = [
*(rule for rule in whitespace_rules if rule["pattern"] != r"[\t ]+$"), *(rule for rule in whitespace_rules if rule["pattern"] != r"[\t ]+$"),
# Two spaces trailing a line with other content is okay--it's a Markdown line break. # Two spaces trailing a line with other content is okay--it's a Markdown line break.
# This rule finds one space trailing a non-space, three or more trailing spaces, and # This rule finds one space trailing a non-space, three or more trailing spaces, and
@ -508,7 +506,7 @@ css_rules = RuleList(
], ],
) )
prose_style_rules: List["Rule"] = [ prose_style_rules: list["Rule"] = [
{ {
"pattern": r'^[\t ]*[^\n{].*?[^\n\/\#\-"]([jJ]avascript)', # exclude usage in hrefs/divs/custom-markdown "pattern": r'^[\t ]*[^\n{].*?[^\n\/\#\-"]([jJ]avascript)', # exclude usage in hrefs/divs/custom-markdown
"exclude": {"docs/documentation/api.md", "templates/corporate/policies/privacy.md"}, "exclude": {"docs/documentation/api.md", "templates/corporate/policies/privacy.md"},
@ -531,7 +529,7 @@ prose_style_rules: List["Rule"] = [
}, },
*comma_whitespace_rule, *comma_whitespace_rule,
] ]
html_rules: List["Rule"] = [ html_rules: list["Rule"] = [
*whitespace_rules, *whitespace_rules,
*prose_style_rules, *prose_style_rules,
{ {

View File

@ -2,7 +2,6 @@ import os
import subprocess import subprocess
import time import time
from pathlib import Path from pathlib import Path
from typing import List
import digitalocean import digitalocean
import zulip import zulip
@ -63,7 +62,7 @@ def set_api_request_retry_limits(api_object: digitalocean.baseapi.BaseAPI) -> No
def create_droplet( def create_droplet(
name: str, ssh_keys: List[str], image: str = "ubuntu-22-04-x64" name: str, ssh_keys: list[str], image: str = "ubuntu-22-04-x64"
) -> digitalocean.Droplet: ) -> digitalocean.Droplet:
droplet = digitalocean.Droplet( droplet = digitalocean.Droplet(
token=manager.token, token=manager.token,

View File

@ -4,10 +4,9 @@ import glob
import os import os
import re import re
import sys import sys
from typing import List
def validate_order(order: List[int], length: int) -> None: def validate_order(order: list[int], length: int) -> None:
if len(order) != length: if len(order) != length:
print("Please enter the sequence of all the conflicting files at once") print("Please enter the sequence of all the conflicting files at once")
sys.exit(1) sys.exit(1)
@ -18,8 +17,8 @@ def validate_order(order: List[int], length: int) -> None:
sys.exit(1) sys.exit(1)
def renumber_migration(conflicts: List[str], order: List[int], last_correct_migration: str) -> None: def renumber_migration(conflicts: list[str], order: list[int], last_correct_migration: str) -> None:
stack: List[str] = [] stack: list[str] = []
for i in order: for i in order:
if conflicts[i - 1][0:4] not in stack: if conflicts[i - 1][0:4] not in stack:
stack.append(conflicts[i - 1][0:4]) stack.append(conflicts[i - 1][0:4])
@ -38,7 +37,7 @@ def renumber_migration(conflicts: List[str], order: List[int], last_correct_migr
last_correct_migration = new_name.replace(".py", "") last_correct_migration = new_name.replace(".py", "")
def resolve_conflicts(conflicts: List[str], files_list: List[str]) -> None: def resolve_conflicts(conflicts: list[str], files_list: list[str]) -> None:
print("Conflicting migrations:") print("Conflicting migrations:")
for i in range(len(conflicts)): for i in range(len(conflicts)):
print(str(i + 1) + ". " + conflicts[i]) print(str(i + 1) + ". " + conflicts[i])
@ -56,8 +55,8 @@ def resolve_conflicts(conflicts: List[str], files_list: List[str]) -> None:
if __name__ == "__main__": if __name__ == "__main__":
MIGRATIONS_TO_SKIP = {"0209", "0261", "0501"} MIGRATIONS_TO_SKIP = {"0209", "0261", "0501"}
while True: while True:
conflicts: List[str] = [] conflicts: list[str] = []
stack: List[str] = [] stack: list[str] = []
files_list = [os.path.basename(path) for path in glob.glob("zerver/migrations/????_*.py")] files_list = [os.path.basename(path) for path in glob.glob("zerver/migrations/????_*.py")]
file_index = [file[0:4] for file in files_list] file_index = [file[0:4] for file in files_list]

View File

@ -2,7 +2,6 @@
import shlex import shlex
import subprocess import subprocess
import sys import sys
from typing import List
def exit(message: str) -> None: def exit(message: str) -> None:
@ -11,12 +10,12 @@ def exit(message: str) -> None:
sys.exit(1) sys.exit(1)
def run(command: List[str]) -> None: def run(command: list[str]) -> None:
print(f"\n>>> {shlex.join(command)}") print(f"\n>>> {shlex.join(command)}")
subprocess.check_call(command) subprocess.check_call(command)
def check_output(command: List[str]) -> str: def check_output(command: list[str]) -> str:
return subprocess.check_output(command, text=True) return subprocess.check_output(command, text=True)

View File

@ -8,7 +8,6 @@ import pwd
import signal import signal
import subprocess import subprocess
import sys import sys
from typing import List
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__)) TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.dirname(TOOLS_DIR)) sys.path.insert(0, os.path.dirname(TOOLS_DIR))
@ -81,7 +80,7 @@ if options.interface is None:
elif options.interface == "": elif options.interface == "":
options.interface = None options.interface = None
runserver_args: List[str] = [] runserver_args: list[str] = []
base_port = 9991 base_port = 9991
if options.test: if options.test:
base_port = 9981 base_port = 9981
@ -139,7 +138,7 @@ with open(pid_file_path, "w+") as f:
f.write(str(os.getpgrp()) + "\n") f.write(str(os.getpgrp()) + "\n")
def server_processes() -> List[List[str]]: def server_processes() -> list[list[str]]:
main_cmds = [ main_cmds = [
[ [
"./manage.py", "./manage.py",
@ -330,7 +329,7 @@ def https_log_filter(record: logging.LogRecord) -> bool:
logging.getLogger("aiohttp.server").addFilter(https_log_filter) logging.getLogger("aiohttp.server").addFilter(https_log_filter)
runner: web.AppRunner runner: web.AppRunner
children: List["subprocess.Popen[bytes]"] = [] children: list["subprocess.Popen[bytes]"] = []
async def serve() -> None: async def serve() -> None:

View File

@ -3,7 +3,6 @@ import argparse
import os import os
import subprocess import subprocess
import sys import sys
from typing import List
from zulint import lister from zulint import lister
@ -72,7 +71,7 @@ if not python_files and not pyi_files:
print("There are no files to run mypy on.") print("There are no files to run mypy on.")
sys.exit(0) sys.exit(0)
mypy_args: List[str] = [] mypy_args: list[str] = []
# --no-error-summary is a mypy flag that comes after all dmypy options # --no-error-summary is a mypy flag that comes after all dmypy options
if args.use_daemon: if args.use_daemon:
mypy_args += ["run", "--"] mypy_args += ["run", "--"]

View File

@ -6,7 +6,7 @@ import base64
import os import os
import subprocess import subprocess
import sys import sys
from typing import Any, Dict, Optional, Tuple from typing import Any, Optional
from urllib.parse import parse_qsl, urlencode from urllib.parse import parse_qsl, urlencode
SCREENSHOTS_DIR = os.path.abspath(os.path.dirname(__file__)) SCREENSHOTS_DIR = os.path.abspath(os.path.dirname(__file__))
@ -94,7 +94,7 @@ def create_integration_stream(integration: Integration, bot: UserProfile) -> Non
bulk_add_subscriptions(realm, [stream], [bot, bot.bot_owner], acting_user=bot) bulk_add_subscriptions(realm, [stream], [bot, bot.bot_owner], acting_user=bot)
def get_fixture_info(fixture_path: str) -> Tuple[Any, bool, str]: def get_fixture_info(fixture_path: str) -> tuple[Any, bool, str]:
json_fixture = fixture_path.endswith(".json") json_fixture = fixture_path.endswith(".json")
_, fixture_name = split_fixture_path(fixture_path) _, fixture_name = split_fixture_path(fixture_path)
@ -116,7 +116,7 @@ def get_integration(integration_name: str) -> Integration:
return integration return integration
def get_requests_headers(integration_name: str, fixture_name: str) -> Dict[str, Any]: def get_requests_headers(integration_name: str, fixture_name: str) -> dict[str, Any]:
headers = get_fixture_http_headers(integration_name, fixture_name) headers = get_fixture_http_headers(integration_name, fixture_name)
def fix_name(header: str) -> str: def fix_name(header: str) -> str:
@ -126,7 +126,7 @@ def get_requests_headers(integration_name: str, fixture_name: str) -> Dict[str,
return {fix_name(k): v for k, v in headers.items()} return {fix_name(k): v for k, v in headers.items()}
def custom_headers(headers_json: str) -> Dict[str, str]: def custom_headers(headers_json: str) -> dict[str, str]:
if not headers_json: if not headers_json:
return {} return {}
try: try:

View File

@ -6,7 +6,7 @@ import os
import subprocess import subprocess
import sys import sys
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Dict, List, Optional from typing import Optional
from django.conf import settings from django.conf import settings
from pydantic import BaseModel, ConfigDict from pydantic import BaseModel, ConfigDict
@ -59,7 +59,7 @@ realm.save()
DEFAULT_USER = get_user_by_delivery_email("iago@zulip.com", realm) DEFAULT_USER = get_user_by_delivery_email("iago@zulip.com", realm)
NOTIFICATION_BOT = get_system_bot(settings.NOTIFICATION_BOT, realm.id) NOTIFICATION_BOT = get_system_bot(settings.NOTIFICATION_BOT, realm.id)
message_thread_ids: List[int] = [] message_thread_ids: list[int] = []
USER_AVATARS_MAP = { USER_AVATARS_MAP = {
"Ariella Drake": "tools/screenshots/user_avatars/AriellaDrake.png", "Ariella Drake": "tools/screenshots/user_avatars/AriellaDrake.png",
@ -82,8 +82,8 @@ class MessageThread(BaseModel):
content: str content: str
starred: bool starred: bool
edited: bool edited: bool
reactions: Dict[str, List[str]] reactions: dict[str, list[str]]
date: Dict[str, int] date: dict[str, int]
def create_user(full_name: str, avatar_filename: Optional[str]) -> None: def create_user(full_name: str, avatar_filename: Optional[str]) -> None:
@ -104,7 +104,7 @@ def set_avatar(user: UserProfile, filename: str) -> None:
def create_and_subscribe_stream( def create_and_subscribe_stream(
stream_name: str, users: List[str], color: Optional[str] = None, invite_only: bool = False stream_name: str, users: list[str], color: Optional[str] = None, invite_only: bool = False
) -> None: ) -> None:
stream = ensure_stream(realm, stream_name, invite_only=invite_only, acting_user=DEFAULT_USER) stream = ensure_stream(realm, stream_name, invite_only=invite_only, acting_user=DEFAULT_USER)
bulk_add_subscriptions( bulk_add_subscriptions(
@ -123,22 +123,22 @@ def create_and_subscribe_stream(
def send_stream_messages( def send_stream_messages(
stream_name: str, topic: str, staged_messages_data: List[MessageThread] stream_name: str, topic: str, staged_messages_data: list[MessageThread]
) -> List[int]: ) -> list[int]:
staged_messages = [dict(staged_message) for staged_message in staged_messages_data] staged_messages = [dict(staged_message) for staged_message in staged_messages_data]
stream = ensure_stream(realm, stream_name, acting_user=DEFAULT_USER) stream = ensure_stream(realm, stream_name, acting_user=DEFAULT_USER)
subscribers_query = get_active_subscriptions_for_stream_id( subscribers_query = get_active_subscriptions_for_stream_id(
stream.id, include_deactivated_users=False stream.id, include_deactivated_users=False
).values_list("user_profile", flat=True) ).values_list("user_profile", flat=True)
subscribers: Dict[str, UserProfile] = {} subscribers: dict[str, UserProfile] = {}
for subscriber_id in subscribers_query: for subscriber_id in subscribers_query:
subscriber = UserProfile.objects.get(realm=realm, id=subscriber_id) subscriber = UserProfile.objects.get(realm=realm, id=subscriber_id)
subscribers[subscriber.full_name] = subscriber subscribers[subscriber.full_name] = subscriber
subscribers["Notification Bot"] = NOTIFICATION_BOT subscribers["Notification Bot"] = NOTIFICATION_BOT
messages: List[Optional[SendMessageRequest]] = [] messages: list[Optional[SendMessageRequest]] = []
for message in staged_messages: for message in staged_messages:
date_sent = message["date"] date_sent = message["date"]
@ -186,7 +186,7 @@ def send_stream_messages(
return message_ids return message_ids
def add_message_reactions(message_id: int, emoji: str, users: List[UserProfile]) -> None: def add_message_reactions(message_id: int, emoji: str, users: list[UserProfile]) -> None:
preview_message = access_message(user_profile=DEFAULT_USER, message_id=message_id) preview_message = access_message(user_profile=DEFAULT_USER, message_id=message_id)
emoji_data = get_emoji_data(realm.id, emoji) emoji_data = get_emoji_data(realm.id, emoji)
for user in users: for user in users:
@ -195,7 +195,7 @@ def add_message_reactions(message_id: int, emoji: str, users: List[UserProfile])
) )
def create_user_group(group_name: str, members: List[str]) -> None: def create_user_group(group_name: str, members: list[str]) -> None:
member_profiles = [ member_profiles = [
UserProfile.objects.get(realm=realm, full_name=member_name) for member_name in members UserProfile.objects.get(realm=realm, full_name=member_name) for member_name in members
] ]

View File

@ -5,7 +5,7 @@
import os import os
import shutil import shutil
import sys import sys
from typing import Any, Dict, Iterator, List, Optional, Sequence from typing import Any, Iterator, Optional, Sequence
import orjson import orjson
@ -127,13 +127,13 @@ def percent(f: float) -> str:
return f"{f * 100:0.3f}%" return f"{f * 100:0.3f}%"
def get_square_size(emoji_data: Sequence[Dict[str, Any]]) -> int: def get_square_size(emoji_data: Sequence[dict[str, Any]]) -> int:
""" """
Spritesheets are usually NxN squares, and we have to Spritesheets are usually NxN squares, and we have to
infer N from the sheet_x/sheet_y values of emojis. infer N from the sheet_x/sheet_y values of emojis.
""" """
def get_offsets(emoji_data: Sequence[Dict[str, Any]]) -> Iterator[int]: def get_offsets(emoji_data: Sequence[dict[str, Any]]) -> Iterator[int]:
for emoji_dict in emoji_data: for emoji_dict in emoji_data:
yield emoji_dict["sheet_x"] yield emoji_dict["sheet_x"]
yield emoji_dict["sheet_y"] yield emoji_dict["sheet_y"]
@ -148,10 +148,10 @@ def get_square_size(emoji_data: Sequence[Dict[str, Any]]) -> int:
def generate_sprite_css_files( def generate_sprite_css_files(
cache_path: str, cache_path: str,
emoji_data: List[Dict[str, Any]], emoji_data: list[dict[str, Any]],
emojiset: str, emojiset: str,
alt_name: str, alt_name: str,
fallback_emoji_data: Sequence[Dict[str, Any]], fallback_emoji_data: Sequence[dict[str, Any]],
) -> None: ) -> None:
""" """
Spritesheets are usually NxN squares. Spritesheets are usually NxN squares.
@ -272,9 +272,9 @@ def generate_sprite_css_files(
f.write(extra_emoji_positions) f.write(extra_emoji_positions)
def setup_emoji_farms(cache_path: str, emoji_data: List[Dict[str, Any]]) -> None: def setup_emoji_farms(cache_path: str, emoji_data: list[dict[str, Any]]) -> None:
def ensure_emoji_image( def ensure_emoji_image(
emoji_dict: Dict[str, Any], src_emoji_farm: str, target_emoji_farm: str emoji_dict: dict[str, Any], src_emoji_farm: str, target_emoji_farm: str
) -> None: ) -> None:
# We use individual images from emoji farm for rendering emojis # We use individual images from emoji farm for rendering emojis
# in notification messages. We have a custom emoji formatter in # in notification messages. We have a custom emoji formatter in
@ -289,9 +289,9 @@ def setup_emoji_farms(cache_path: str, emoji_data: List[Dict[str, Any]]) -> None
def setup_emoji_farm( def setup_emoji_farm(
emojiset: str, emojiset: str,
emoji_data: List[Dict[str, Any]], emoji_data: list[dict[str, Any]],
alt_name: Optional[str] = None, alt_name: Optional[str] = None,
fallback_emoji_data: Sequence[Dict[str, Any]] = [], fallback_emoji_data: Sequence[dict[str, Any]] = [],
) -> None: ) -> None:
# `alt_name` is an optional parameter that we use to avoid duplicating below # `alt_name` is an optional parameter that we use to avoid duplicating below
# code. It is only used while setting up google-blob emoji set as it is just # code. It is only used while setting up google-blob emoji set as it is just
@ -342,7 +342,7 @@ def setup_emoji_farms(cache_path: str, emoji_data: List[Dict[str, Any]]) -> None
def setup_old_emoji_farm( def setup_old_emoji_farm(
cache_path: str, emoji_map: Dict[str, str], emoji_data: List[Dict[str, Any]] cache_path: str, emoji_map: dict[str, str], emoji_data: list[dict[str, Any]]
) -> None: ) -> None:
# Code for setting up old emoji farm. # Code for setting up old emoji farm.
os.chdir(cache_path) os.chdir(cache_path)
@ -374,7 +374,7 @@ def setup_old_emoji_farm(
pass pass
def generate_map_files(cache_path: str, emoji_catalog: Dict[str, List[str]]) -> None: def generate_map_files(cache_path: str, emoji_catalog: dict[str, list[str]]) -> None:
# This function generates the main data files about emoji that are # This function generates the main data files about emoji that are
# consumed by the web app, mobile apps, Markdown processor, etc. # consumed by the web app, mobile apps, Markdown processor, etc.
names = emoji_names_for_picker(EMOJI_NAME_MAPS) names = emoji_names_for_picker(EMOJI_NAME_MAPS)

View File

@ -1,6 +1,6 @@
from typing import Any, Dict from typing import Any
CUSTOM_EMOJI_NAME_MAPS: Dict[str, Dict[str, Any]] = { CUSTOM_EMOJI_NAME_MAPS: dict[str, dict[str, Any]] = {
# seems like best emoji for happy # seems like best emoji for happy
"1f600": {"canonical_name": "grinning", "aliases": ["happy"]}, "1f600": {"canonical_name": "grinning", "aliases": ["happy"]},
"1f603": {"canonical_name": "smiley", "aliases": []}, "1f603": {"canonical_name": "smiley", "aliases": []},

View File

@ -1,8 +1,8 @@
from typing import Any, Dict from typing import Any
# Generated with `generate_emoji_names`. # Generated with `generate_emoji_names`.
EMOJI_NAME_MAPS: Dict[str, Dict[str, Any]] = { EMOJI_NAME_MAPS: dict[str, dict[str, Any]] = {
"0023-20e3": {"canonical_name": "hash", "aliases": []}, "0023-20e3": {"canonical_name": "hash", "aliases": []},
"002a-20e3": {"canonical_name": "asterisk", "aliases": []}, "002a-20e3": {"canonical_name": "asterisk", "aliases": []},
"0030-20e3": {"canonical_name": "zero", "aliases": []}, "0030-20e3": {"canonical_name": "zero", "aliases": []},

View File

@ -1,7 +1,7 @@
# This file contains various helper functions used by `build_emoji` tool. # This file contains various helper functions used by `build_emoji` tool.
# See docs/subsystems/emoji.md for details on how this system works. # See docs/subsystems/emoji.md for details on how this system works.
from collections import defaultdict from collections import defaultdict
from typing import Any, Dict, List from typing import Any
from zerver.lib.emoji_utils import emoji_to_hex_codepoint, hex_codepoint_to_emoji, unqualify_emoji from zerver.lib.emoji_utils import emoji_to_hex_codepoint, hex_codepoint_to_emoji, unqualify_emoji
@ -50,8 +50,8 @@ EMOTICON_CONVERSIONS = {
} }
def emoji_names_for_picker(emoji_name_maps: Dict[str, Dict[str, Any]]) -> List[str]: def emoji_names_for_picker(emoji_name_maps: dict[str, dict[str, Any]]) -> list[str]:
emoji_names: List[str] = [] emoji_names: list[str] = []
for name_info in emoji_name_maps.values(): for name_info in emoji_name_maps.values():
emoji_names.append(name_info["canonical_name"]) emoji_names.append(name_info["canonical_name"])
emoji_names.extend(name_info["aliases"]) emoji_names.extend(name_info["aliases"])
@ -59,7 +59,7 @@ def emoji_names_for_picker(emoji_name_maps: Dict[str, Dict[str, Any]]) -> List[s
return sorted(emoji_names) return sorted(emoji_names)
def get_emoji_code(emoji_dict: Dict[str, Any]) -> str: def get_emoji_code(emoji_dict: dict[str, Any]) -> str:
# There is a `non_qualified` field on `emoji_dict` but it's # There is a `non_qualified` field on `emoji_dict` but it's
# inconsistently present, so we'll always use the unqualified # inconsistently present, so we'll always use the unqualified
# emoji by unqualifying it ourselves. This gives us more consistent # emoji by unqualifying it ourselves. This gives us more consistent
@ -72,10 +72,10 @@ def get_emoji_code(emoji_dict: Dict[str, Any]) -> str:
# codepoints are sorted according to the `sort_order` as defined in # codepoints are sorted according to the `sort_order` as defined in
# `emoji_data`. # `emoji_data`.
def generate_emoji_catalog( def generate_emoji_catalog(
emoji_data: List[Dict[str, Any]], emoji_name_maps: Dict[str, Dict[str, Any]] emoji_data: list[dict[str, Any]], emoji_name_maps: dict[str, dict[str, Any]]
) -> Dict[str, List[str]]: ) -> dict[str, list[str]]:
sort_order: Dict[str, int] = {} sort_order: dict[str, int] = {}
emoji_catalog: Dict[str, List[str]] = defaultdict(list) emoji_catalog: dict[str, list[str]] = defaultdict(list)
for emoji_dict in emoji_data: for emoji_dict in emoji_data:
emoji_code = get_emoji_code(emoji_dict) emoji_code = get_emoji_code(emoji_dict)
@ -93,8 +93,8 @@ def generate_emoji_catalog(
return dict(emoji_catalog) return dict(emoji_catalog)
def generate_codepoint_to_name_map(emoji_name_maps: Dict[str, Dict[str, Any]]) -> Dict[str, str]: def generate_codepoint_to_name_map(emoji_name_maps: dict[str, dict[str, Any]]) -> dict[str, str]:
codepoint_to_name: Dict[str, str] = {} codepoint_to_name: dict[str, str] = {}
for emoji_code, name_info in emoji_name_maps.items(): for emoji_code, name_info in emoji_name_maps.items():
codepoint_to_name[emoji_code] = name_info["canonical_name"] codepoint_to_name[emoji_code] = name_info["canonical_name"]
return codepoint_to_name return codepoint_to_name
@ -102,13 +102,13 @@ def generate_codepoint_to_name_map(emoji_name_maps: Dict[str, Dict[str, Any]]) -
# We support Google Modern, and fall back to Google Modern when emoji # We support Google Modern, and fall back to Google Modern when emoji
# aren't supported by the other styles we use. # aren't supported by the other styles we use.
def emoji_is_supported(emoji_dict: Dict[str, Any]) -> bool: def emoji_is_supported(emoji_dict: dict[str, Any]) -> bool:
return emoji_dict["has_img_google"] return emoji_dict["has_img_google"]
def generate_codepoint_to_names_map( def generate_codepoint_to_names_map(
emoji_name_maps: Dict[str, Dict[str, Any]], emoji_name_maps: dict[str, dict[str, Any]],
) -> Dict[str, List[str]]: ) -> dict[str, list[str]]:
# The first element of the names list is always the canonical name. # The first element of the names list is always the canonical name.
return { return {
emoji_code: [name_info["canonical_name"], *name_info["aliases"]] emoji_code: [name_info["canonical_name"], *name_info["aliases"]]
@ -116,7 +116,7 @@ def generate_codepoint_to_names_map(
} }
def generate_name_to_codepoint_map(emoji_name_maps: Dict[str, Dict[str, Any]]) -> Dict[str, str]: def generate_name_to_codepoint_map(emoji_name_maps: dict[str, dict[str, Any]]) -> dict[str, str]:
name_to_codepoint = {} name_to_codepoint = {}
for emoji_code, name_info in emoji_name_maps.items(): for emoji_code, name_info in emoji_name_maps.items():
canonical_name = name_info["canonical_name"] canonical_name = name_info["canonical_name"]

View File

@ -7,7 +7,7 @@
# codepoint. # codepoint.
import os import os
import sys import sys
from typing import Any, Dict, List from typing import Any
import orjson import orjson
@ -124,15 +124,15 @@ SORTED_CATEGORIES = [
"Skin Tones", "Skin Tones",
] ]
emoji_code_to_zulip_names: Dict[str, str] = {} emoji_code_to_zulip_names: dict[str, str] = {}
emoji_code_to_iamcal_names: Dict[str, str] = {} emoji_code_to_iamcal_names: dict[str, str] = {}
emoji_code_to_gemoji_names: Dict[str, str] = {} emoji_code_to_gemoji_names: dict[str, str] = {}
emoji_collection: Dict[str, List[Dict[str, Any]]] = {category: [] for category in SORTED_CATEGORIES} emoji_collection: dict[str, list[dict[str, Any]]] = {category: [] for category in SORTED_CATEGORIES}
def generate_emoji_code_to_emoji_names_maps() -> None: def generate_emoji_code_to_emoji_names_maps() -> None:
# Prepare gemoji names map. # Prepare gemoji names map.
reverse_unified_reactions_map: Dict[str, List[str]] = {} reverse_unified_reactions_map: dict[str, list[str]] = {}
for name in UNIFIED_REACTIONS_MAP: for name in UNIFIED_REACTIONS_MAP:
emoji_code = UNIFIED_REACTIONS_MAP[name] emoji_code = UNIFIED_REACTIONS_MAP[name]
if emoji_code in reverse_unified_reactions_map: if emoji_code in reverse_unified_reactions_map:

View File

@ -3,7 +3,6 @@ import glob
import os import os
import shutil import shutil
import sys import sys
from typing import List
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
if ZULIP_PATH not in sys.path: if ZULIP_PATH not in sys.path:
@ -25,7 +24,7 @@ def generate_zulip_bots_static_files() -> None:
package_bots_dir = get_bots_directory_path() package_bots_dir = get_bots_directory_path()
def copy_bots_data(bot_names: List[str]) -> None: def copy_bots_data(bot_names: list[str]) -> None:
for name in bot_names: for name in bot_names:
src_dir = os.path.join(package_bots_dir, name) src_dir = os.path.join(package_bots_dir, name)
dst_dir = os.path.join(bots_dir, name) dst_dir = os.path.join(bots_dir, name)

View File

@ -13,7 +13,7 @@ os.environ["DJANGO_SETTINGS_MODULE"] = "zproject.settings"
import argparse import argparse
import secrets import secrets
from contextlib import contextmanager from contextlib import contextmanager
from typing import Iterator, List, Tuple, TypedDict from typing import Iterator, TypedDict
import boto3.session import boto3.session
import orjson import orjson
@ -86,7 +86,7 @@ def get_ses_arn(session: boto3.session.Session, args: argparse.Namespace) -> str
@contextmanager @contextmanager
def our_sqs_queue(session: boto3.session.Session, ses_topic_arn: str) -> Iterator[Tuple[str, str]]: def our_sqs_queue(session: boto3.session.Session, ses_topic_arn: str) -> Iterator[tuple[str, str]]:
(_, _, _, region, account_id, topic_name) = ses_topic_arn.split(":") (_, _, _, region, account_id, topic_name) = ses_topic_arn.split(":")
sqs: SQSClient = session.client("sqs") sqs: SQSClient = session.client("sqs")
@ -153,7 +153,7 @@ def print_messages(session: boto3.session.Session, queue_url: str) -> None:
MessageAttributeNames=["All"], MessageAttributeNames=["All"],
) )
messages = resp.get("Messages", []) messages = resp.get("Messages", [])
delete_list: List[DeleteMessageBatchRequestEntryTypeDef] = [] delete_list: list[DeleteMessageBatchRequestEntryTypeDef] = []
for m in messages: for m in messages:
body = orjson.loads(m["Body"]) body = orjson.loads(m["Body"])
body_message = orjson.loads(body["Message"]) body_message = orjson.loads(body["Message"])

View File

@ -8,7 +8,7 @@ import shlex
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
from typing import TYPE_CHECKING, Iterator, List, Type, cast from typing import TYPE_CHECKING, Iterator, cast
from unittest import mock from unittest import mock
if TYPE_CHECKING: if TYPE_CHECKING:
@ -154,7 +154,7 @@ enforce_fully_covered = sorted(
FAILED_TEST_PATH = "var/last_test_failure.json" FAILED_TEST_PATH = "var/last_test_failure.json"
def get_failed_tests() -> List[str]: def get_failed_tests() -> list[str]:
try: try:
with open(FAILED_TEST_PATH, "rb") as f: with open(FAILED_TEST_PATH, "rb") as f:
return orjson.loads(f.read()) return orjson.loads(f.read())
@ -418,7 +418,7 @@ def main() -> None:
# isinstance check cannot be used with types. This can potentially improved by supporting # isinstance check cannot be used with types. This can potentially improved by supporting
# dynamic resolution of the test runner type with the django-stubs mypy plugin. # dynamic resolution of the test runner type with the django-stubs mypy plugin.
TestRunner = cast("Type[Runner]", get_runner(settings)) TestRunner = cast("type[Runner]", get_runner(settings))
if options.processes: if options.processes:
parallel = options.processes parallel = options.processes

View File

@ -5,7 +5,7 @@ import os
import pwd import pwd
import subprocess import subprocess
import sys import sys
from typing import Any, Dict, List, Set from typing import Any
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__)) TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.dirname(TOOLS_DIR)) sys.path.insert(0, os.path.dirname(TOOLS_DIR))
@ -34,7 +34,7 @@ USAGE = """
""" """
def make_set(files: List[str]) -> Set[str]: def make_set(files: list[str]) -> set[str]:
for i in range(1, len(files)): for i in range(1, len(files)):
if files[i - 1] > files[i]: if files[i - 1] > files[i]:
raise Exception(f"Please move {files[i]} so that names are sorted.") raise Exception(f"Please move {files[i]} so that names are sorted.")
@ -376,7 +376,7 @@ def clean_file(orig_fn: str) -> str:
return fn return fn
def clean_files(fns: List[str]) -> List[str]: def clean_files(fns: list[str]) -> list[str]:
cleaned_files = [clean_file(fn) for fn in fns] cleaned_files = [clean_file(fn) for fn in fns]
return cleaned_files return cleaned_files
@ -442,7 +442,7 @@ def run_tests_via_node_js() -> int:
def check_line_coverage( def check_line_coverage(
fn: str, line_coverage: Dict[Any, Any], line_mapping: Dict[Any, Any], log: bool = True fn: str, line_coverage: dict[Any, Any], line_mapping: dict[Any, Any], log: bool = True
) -> bool: ) -> bool:
missing_lines = [] missing_lines = []
for line in line_coverage: for line in line_coverage:

View File

@ -39,7 +39,7 @@ from tools.lib import sanity_check
sanity_check.check_venv(__file__) sanity_check.check_venv(__file__)
from typing import Iterable, Tuple from typing import Iterable
from tools.lib.test_script import ( from tools.lib.test_script import (
add_provision_check_override_param, add_provision_check_override_param,
@ -94,7 +94,7 @@ def run_tests(files: Iterable[str], external_host: str, loop: int = 1) -> None:
test_files = find_js_test_files(test_dir, files) test_files = find_js_test_files(test_dir, files)
total_tests = len(test_files) total_tests = len(test_files)
def run_tests(test_number: int = 0) -> Tuple[int, int]: def run_tests(test_number: int = 0) -> tuple[int, int]:
current_test_num = test_number current_test_num = test_number
for test_file in test_files[test_number:]: for test_file in test_files[test_number:]:
return_code = run_single_test(test_file, current_test_num + 1, total_tests) return_code = run_single_test(test_file, current_test_num + 1, total_tests)

View File

@ -8,7 +8,6 @@ import shutil
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
from typing import List
import orjson import orjson
@ -74,13 +73,13 @@ def maybe_set_up_cache() -> None:
fp.write(orjson.dumps([])) fp.write(orjson.dumps([]))
def load_cache() -> List[str]: def load_cache() -> list[str]:
with open(CACHE_FILE, "rb") as fp: with open(CACHE_FILE, "rb") as fp:
hash_list = orjson.loads(fp.read()) hash_list = orjson.loads(fp.read())
return hash_list return hash_list
def update_cache(hash_list: List[str]) -> None: def update_cache(hash_list: list[str]) -> None:
# We store last 100 hash entries. Aggressive caching is # We store last 100 hash entries. Aggressive caching is
# not a problem as it is cheap to do. # not a problem as it is cheap to do.
if len(hash_list) > 100: if len(hash_list) > 100:

View File

@ -5,7 +5,6 @@ import subprocess
import sys import sys
import tempfile import tempfile
import time import time
from typing import Tuple
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
@ -16,7 +15,7 @@ sanity_check.check_venv(__file__)
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__)) TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
def start_server(logfile_name: str) -> Tuple[bool, str]: def start_server(logfile_name: str) -> tuple[bool, str]:
failure = True failure = True
key = "Quit the server with CONTROL-C." key = "Quit the server with CONTROL-C."
datalog = [] datalog = []

View File

@ -5,7 +5,6 @@ import subprocess
import sys import sys
from collections import defaultdict from collections import defaultdict
from pathlib import Path from pathlib import Path
from typing import Dict, List
bot_commits = 0 bot_commits = 0
@ -13,7 +12,7 @@ ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
os.chdir(ZULIP_PATH) os.chdir(ZULIP_PATH)
def add_log(committer_dict: Dict[str, int], input: List[str]) -> None: def add_log(committer_dict: dict[str, int], input: list[str]) -> None:
for dataset in input: for dataset in input:
committer_name = dataset.split("\t")[1] committer_name = dataset.split("\t")[1]
commit_count = int(dataset.split("\t")[0]) commit_count = int(dataset.split("\t")[0])
@ -27,7 +26,7 @@ def add_log(committer_dict: Dict[str, int], input: List[str]) -> None:
committer_dict[committer_name] += commit_count committer_dict[committer_name] += commit_count
def retrieve_log(repo: str, revisions: str) -> List[str]: def retrieve_log(repo: str, revisions: str) -> list[str]:
return subprocess.check_output( return subprocess.check_output(
["git", "shortlog", "-s", revisions], ["git", "shortlog", "-s", revisions],
cwd=find_path(repo), cwd=find_path(repo),
@ -41,7 +40,7 @@ def find_path(repository: str) -> str:
def process_repo( def process_repo(
*, *,
out_dict: Dict[str, int], out_dict: dict[str, int],
repo_short: str, repo_short: str,
repo_full: str, repo_full: str,
lower_version: str, lower_version: str,
@ -164,8 +163,8 @@ print(
f"Commit range {lower_zulip_version}..{upper_zulip_version} corresponds to {lower_time} to {upper_time}" f"Commit range {lower_zulip_version}..{upper_zulip_version} corresponds to {lower_time} to {upper_time}"
) )
repository_dict: Dict[str, int] = defaultdict(int) repository_dict: dict[str, int] = defaultdict(int)
out_dict: Dict[str, int] = defaultdict(int) out_dict: dict[str, int] = defaultdict(int)
subprocess.check_call(["git", "fetch"], cwd=find_path("zulip")) subprocess.check_call(["git", "fetch"], cwd=find_path("zulip"))
process_repo( process_repo(
out_dict=out_dict, out_dict=out_dict,

View File

@ -5,7 +5,7 @@ import os
import re import re
import sys import sys
import tempfile import tempfile
from typing import IO, Dict, List, cast from typing import IO, cast
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from scripts.lib.setup_path import setup_path from scripts.lib.setup_path import setup_path
@ -41,7 +41,7 @@ session = boto3.session.Session()
client: S3Client = session.client("s3") client: S3Client = session.client("s3")
bucket = session.resource("s3", region_name="us-east-1").Bucket("zulip-download") bucket = session.resource("s3", region_name="us-east-1").Bucket("zulip-download")
file_hashes: Dict[str, str] = {} file_hashes: dict[str, str] = {}
with open(args.filename, "rb") as new_file: with open(args.filename, "rb") as new_file:
print(f"Hashing {new_basename}..") print(f"Hashing {new_basename}..")
file_hashes[new_basename] = sha256_contents(new_file) file_hashes[new_basename] = sha256_contents(new_file)
@ -75,7 +75,7 @@ for obj_summary in bucket.objects.filter(Prefix="server/zulip-server-"):
file_hashes[filename] = metadata["sha256sum"] file_hashes[filename] = metadata["sha256sum"]
ordered_filenames = cast(List[str], natsorted(file_hashes.keys(), reverse=True)) ordered_filenames = cast(list[str], natsorted(file_hashes.keys(), reverse=True))
assert ordered_filenames[0] == "zulip-server-latest.tar.gz" assert ordered_filenames[0] == "zulip-server-latest.tar.gz"
print(f"Uploading {new_basename}..") print(f"Uploading {new_basename}..")

View File

@ -1,6 +1,6 @@
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, Dict, Optional from typing import Any, Optional
from django.conf import settings from django.conf import settings
from django.db import transaction from django.db import transaction
@ -136,7 +136,7 @@ def set_realm_permissions_based_on_org_type(realm: Realm) -> None:
@transaction.atomic(savepoint=False) @transaction.atomic(savepoint=False)
def set_default_for_realm_permission_group_settings( def set_default_for_realm_permission_group_settings(
realm: Realm, group_settings_defaults_for_org_types: Optional[Dict[str, Dict[int, str]]] = None realm: Realm, group_settings_defaults_for_org_types: Optional[dict[str, dict[int, str]]] = None
) -> None: ) -> None:
system_groups_dict = get_role_based_system_groups_dict(realm) system_groups_dict = get_role_based_system_groups_dict(realm)
@ -206,7 +206,7 @@ def do_create_realm(
logging.info("Server not yet initialized. Creating the internal realm first.") logging.info("Server not yet initialized. Creating the internal realm first.")
create_internal_realm() create_internal_realm()
kwargs: Dict[str, Any] = {} kwargs: dict[str, Any] = {}
if emails_restricted_to_domains is not None: if emails_restricted_to_domains is not None:
kwargs["emails_restricted_to_domains"] = emails_restricted_to_domains kwargs["emails_restricted_to_domains"] = emails_restricted_to_domains
if description is not None: if description is not None:

View File

@ -1,6 +1,6 @@
from collections import defaultdict from collections import defaultdict
from datetime import timedelta from datetime import timedelta
from typing import Any, Dict, Iterable, List, Optional, Sequence, Set from typing import Any, Iterable, Optional, Sequence
from django.conf import settings from django.conf import settings
from django.db import transaction from django.db import transaction
@ -133,7 +133,7 @@ def set_up_streams_for_new_human_user(
realm = user_profile.realm realm = user_profile.realm
if prereg_user is not None: if prereg_user is not None:
streams: List[Stream] = list(prereg_user.streams.all()) streams: list[Stream] = list(prereg_user.streams.all())
acting_user: Optional[UserProfile] = prereg_user.referred_by acting_user: Optional[UserProfile] = prereg_user.referred_by
# A PregistrationUser should not be used for another UserProfile # A PregistrationUser should not be used for another UserProfile
@ -351,10 +351,10 @@ def process_new_human_user(
OnboardingStep.objects.create(user=user_profile, onboarding_step="visibility_policy_banner") OnboardingStep.objects.create(user=user_profile, onboarding_step="visibility_policy_banner")
def notify_created_user(user_profile: UserProfile, notify_user_ids: List[int]) -> None: def notify_created_user(user_profile: UserProfile, notify_user_ids: list[int]) -> None:
user_row = user_profile_to_user_row(user_profile) user_row = user_profile_to_user_row(user_profile)
format_user_row_kwargs: Dict[str, Any] = { format_user_row_kwargs: dict[str, Any] = {
"realm_id": user_profile.realm_id, "realm_id": user_profile.realm_id,
"row": user_row, "row": user_row,
# Since we don't know what the client # Since we don't know what the client
@ -370,8 +370,8 @@ def notify_created_user(user_profile: UserProfile, notify_user_ids: List[int]) -
"custom_profile_field_data": {}, "custom_profile_field_data": {},
} }
user_ids_without_access_to_created_user: List[int] = [] user_ids_without_access_to_created_user: list[int] = []
users_with_access_to_created_users: List[UserProfile] = [] users_with_access_to_created_users: list[UserProfile] = []
if notify_user_ids: if notify_user_ids:
# This is currently used to send creation event when a guest # This is currently used to send creation event when a guest
@ -427,7 +427,7 @@ def notify_created_user(user_profile: UserProfile, notify_user_ids: List[int]) -
if user_ids_with_real_email_access: if user_ids_with_real_email_access:
assert person_for_real_email_access_users is not None assert person_for_real_email_access_users is not None
event: Dict[str, Any] = dict( event: dict[str, Any] = dict(
type="realm_user", op="add", person=person_for_real_email_access_users type="realm_user", op="add", person=person_for_real_email_access_users
) )
send_event_on_commit(user_profile.realm, event, user_ids_with_real_email_access) send_event_on_commit(user_profile.realm, event, user_ids_with_real_email_access)
@ -447,7 +447,7 @@ def notify_created_user(user_profile: UserProfile, notify_user_ids: List[int]) -
send_event_on_commit(user_profile.realm, event, user_ids_without_access_to_created_user) send_event_on_commit(user_profile.realm, event, user_ids_without_access_to_created_user)
def created_bot_event(user_profile: UserProfile) -> Dict[str, Any]: def created_bot_event(user_profile: UserProfile) -> dict[str, Any]:
def stream_name(stream: Optional[Stream]) -> Optional[str]: def stream_name(stream: Optional[Stream]) -> Optional[str]:
if not stream: if not stream:
return None return None
@ -749,7 +749,7 @@ def do_reactivate_user(user_profile: UserProfile, *, acting_user: Optional[UserP
streams=subscribed_streams, streams=subscribed_streams,
) )
altered_user_dict: Dict[int, Set[int]] = defaultdict(set) altered_user_dict: dict[int, set[int]] = defaultdict(set)
for stream in subscribed_streams: for stream in subscribed_streams:
altered_user_dict[stream.id] = {user_profile.id} altered_user_dict[stream.id] = {user_profile.id}

View File

@ -1,4 +1,4 @@
from typing import Dict, Iterable, List, Optional, Union from typing import Iterable, Optional, Union
import orjson import orjson
from django.db import transaction from django.db import transaction
@ -146,7 +146,7 @@ def try_reorder_realm_custom_profile_fields(realm: Realm, order: Iterable[int])
def notify_user_update_custom_profile_data( def notify_user_update_custom_profile_data(
user_profile: UserProfile, field: Dict[str, Union[int, str, List[int], None]] user_profile: UserProfile, field: dict[str, Union[int, str, list[int], None]]
) -> None: ) -> None:
data = dict(id=field["id"], value=field["value"]) data = dict(id=field["id"], value=field["value"])
@ -159,7 +159,7 @@ def notify_user_update_custom_profile_data(
def do_update_user_custom_profile_data_if_changed( def do_update_user_custom_profile_data_if_changed(
user_profile: UserProfile, user_profile: UserProfile,
data: List[ProfileDataElementUpdateDict], data: list[ProfileDataElementUpdateDict],
) -> None: ) -> None:
with transaction.atomic(): with transaction.atomic():
for custom_profile_field in data: for custom_profile_field in data:

View File

@ -1,4 +1,4 @@
from typing import Any, Dict, Iterable, List from typing import Any, Iterable
from django.db import transaction from django.db import transaction
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -37,8 +37,8 @@ def check_default_stream_group_name(group_name: str) -> None:
def lookup_default_stream_groups( def lookup_default_stream_groups(
default_stream_group_names: List[str], realm: Realm default_stream_group_names: list[str], realm: Realm
) -> List[DefaultStreamGroup]: ) -> list[DefaultStreamGroup]:
default_stream_groups = [] default_stream_groups = []
for group_name in default_stream_group_names: for group_name in default_stream_group_names:
try: try:
@ -86,7 +86,7 @@ def do_remove_default_stream(stream: Stream) -> None:
def do_create_default_stream_group( def do_create_default_stream_group(
realm: Realm, group_name: str, description: str, streams: List[Stream] realm: Realm, group_name: str, description: str, streams: list[Stream]
) -> None: ) -> None:
default_stream_ids = get_default_stream_ids_for_realm(realm.id) default_stream_ids = get_default_stream_ids_for_realm(realm.id)
for stream in streams: for stream in streams:
@ -113,7 +113,7 @@ def do_create_default_stream_group(
def do_add_streams_to_default_stream_group( def do_add_streams_to_default_stream_group(
realm: Realm, group: DefaultStreamGroup, streams: List[Stream] realm: Realm, group: DefaultStreamGroup, streams: list[Stream]
) -> None: ) -> None:
default_stream_ids = get_default_stream_ids_for_realm(realm.id) default_stream_ids = get_default_stream_ids_for_realm(realm.id)
for stream in streams: for stream in streams:
@ -136,7 +136,7 @@ def do_add_streams_to_default_stream_group(
def do_remove_streams_from_default_stream_group( def do_remove_streams_from_default_stream_group(
realm: Realm, group: DefaultStreamGroup, streams: List[Stream] realm: Realm, group: DefaultStreamGroup, streams: list[Stream]
) -> None: ) -> None:
group_stream_ids = {stream.id for stream in group.streams.all()} group_stream_ids = {stream.id for stream in group.streams.all()}
for stream in streams: for stream in streams:
@ -190,5 +190,5 @@ def do_remove_default_stream_group(realm: Realm, group: DefaultStreamGroup) -> N
def default_stream_groups_to_dicts_sorted( def default_stream_groups_to_dicts_sorted(
groups: Iterable[DefaultStreamGroup], groups: Iterable[DefaultStreamGroup],
) -> List[Dict[str, Any]]: ) -> list[dict[str, Any]]:
return sorted((group.to_dict() for group in groups), key=lambda elt: elt["name"]) return sorted((group.to_dict() for group in groups), key=lambda elt: elt["name"])

View File

@ -1,6 +1,6 @@
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, Collection, Dict, List, Optional, Sequence, Set, Tuple from typing import Any, Collection, Optional, Sequence
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -179,7 +179,7 @@ def do_invite_users(
invite_expires_in_minutes: Optional[int], invite_expires_in_minutes: Optional[int],
include_realm_default_subscriptions: bool, include_realm_default_subscriptions: bool,
invite_as: int = PreregistrationUser.INVITE_AS["MEMBER"], invite_as: int = PreregistrationUser.INVITE_AS["MEMBER"],
) -> List[Tuple[str, str, bool]]: ) -> list[tuple[str, str, bool]]:
num_invites = len(invitee_emails) num_invites = len(invitee_emails)
# Lock the realm, since we need to not race with other invitations # Lock the realm, since we need to not race with other invitations
@ -211,8 +211,8 @@ def do_invite_users(
sent_invitations=False, sent_invitations=False,
) )
good_emails: Set[str] = set() good_emails: set[str] = set()
errors: List[Tuple[str, str, bool]] = [] errors: list[tuple[str, str, bool]] = []
validate_email_allowed_in_realm = get_realm_email_validator(realm) validate_email_allowed_in_realm = get_realm_email_validator(realm)
for email in invitee_emails: for email in invitee_emails:
if email == "": if email == "":
@ -234,7 +234,7 @@ def do_invite_users(
""" """
error_dict = get_existing_user_errors(realm, good_emails) error_dict = get_existing_user_errors(realm, good_emails)
skipped: List[Tuple[str, str, bool]] = [] skipped: list[tuple[str, str, bool]] = []
for email in error_dict: for email in error_dict:
msg, deactivated = error_dict[email] msg, deactivated = error_dict[email]
skipped.append((email, msg, deactivated)) skipped.append((email, msg, deactivated))
@ -292,7 +292,7 @@ def get_invitation_expiry_date(confirmation_obj: Confirmation) -> Optional[int]:
return datetime_to_timestamp(expiry_date) return datetime_to_timestamp(expiry_date)
def do_get_invites_controlled_by_user(user_profile: UserProfile) -> List[Dict[str, Any]]: def do_get_invites_controlled_by_user(user_profile: UserProfile) -> list[dict[str, Any]]:
""" """
Returns a list of dicts representing invitations that can be controlled by user_profile. Returns a list of dicts representing invitations that can be controlled by user_profile.
This isn't necessarily the same as all the invitations generated by the user, as administrators This isn't necessarily the same as all the invitations generated by the user, as administrators

View File

@ -1,4 +1,4 @@
from typing import Iterable, List, TypedDict from typing import Iterable, TypedDict
from zerver.lib import retention from zerver.lib import retention
from zerver.lib.retention import move_messages_to_archive from zerver.lib.retention import move_messages_to_archive
@ -9,14 +9,14 @@ from zerver.tornado.django_api import send_event_on_commit
class DeleteMessagesEvent(TypedDict, total=False): class DeleteMessagesEvent(TypedDict, total=False):
type: str type: str
message_ids: List[int] message_ids: list[int]
message_type: str message_type: str
topic: str topic: str
stream_id: int stream_id: int
def check_update_first_message_id( def check_update_first_message_id(
realm: Realm, stream: Stream, message_ids: List[int], users_to_notify: Iterable[int] realm: Realm, stream: Stream, message_ids: list[int], users_to_notify: Iterable[int]
) -> None: ) -> None:
# This will not update the `first_message_id` of streams where the # This will not update the `first_message_id` of streams where the
# first message was deleted prior to the implementation of this function. # first message was deleted prior to the implementation of this function.

View File

@ -1,7 +1,7 @@
import itertools import itertools
from collections import defaultdict from collections import defaultdict
from datetime import timedelta from datetime import timedelta
from typing import AbstractSet, Any, Dict, Iterable, List, Optional, Set, Tuple from typing import AbstractSet, Any, Iterable, Optional
from django.conf import settings from django.conf import settings
from django.db import transaction from django.db import transaction
@ -84,7 +84,7 @@ from zerver.models.users import get_system_bot
from zerver.tornado.django_api import send_event_on_commit from zerver.tornado.django_api import send_event_on_commit
def subscriber_info(user_id: int) -> Dict[str, Any]: def subscriber_info(user_id: int) -> dict[str, Any]:
return {"id": user_id, "flags": ["read"]} return {"id": user_id, "flags": ["read"]}
@ -148,7 +148,7 @@ def maybe_send_resolve_topic_notifications(
new_topic_name: str, new_topic_name: str,
changed_messages: QuerySet[Message], changed_messages: QuerySet[Message],
pre_truncation_new_topic_name: str, pre_truncation_new_topic_name: str,
) -> Tuple[Optional[int], bool]: ) -> tuple[Optional[int], bool]:
"""Returns resolved_topic_message_id if resolve topic notifications were in fact sent.""" """Returns resolved_topic_message_id if resolve topic notifications were in fact sent."""
# Note that topics will have already been stripped in check_update_message. # Note that topics will have already been stripped in check_update_message.
resolved_prefix_len = len(RESOLVED_TOPIC_PREFIX) resolved_prefix_len = len(RESOLVED_TOPIC_PREFIX)
@ -299,7 +299,7 @@ def send_message_moved_breadcrumbs(
) )
def get_mentions_for_message_updates(message_id: int) -> Set[int]: def get_mentions_for_message_updates(message_id: int) -> set[int]:
# We exclude UserMessage.flags.historical rows since those # We exclude UserMessage.flags.historical rows since those
# users did not receive the message originally, and thus # users did not receive the message originally, and thus
# probably are not relevant for reprocessed alert_words, # probably are not relevant for reprocessed alert_words,
@ -330,7 +330,7 @@ def update_user_message_flags(
) -> None: ) -> None:
mentioned_ids = rendering_result.mentions_user_ids mentioned_ids = rendering_result.mentions_user_ids
ids_with_alert_words = rendering_result.user_ids_with_alert_words ids_with_alert_words = rendering_result.user_ids_with_alert_words
changed_ums: Set[UserMessage] = set() changed_ums: set[UserMessage] = set()
def update_flag(um: UserMessage, should_set: bool, flag: int) -> None: def update_flag(um: UserMessage, should_set: bool, flag: int) -> None:
if should_set: if should_set:
@ -366,7 +366,7 @@ def do_update_embedded_data(
rendering_result: MessageRenderingResult, rendering_result: MessageRenderingResult,
) -> None: ) -> None:
timestamp = timezone_now() timestamp = timezone_now()
event: Dict[str, Any] = { event: dict[str, Any] = {
"type": "update_message", "type": "update_message",
"user_id": None, "user_id": None,
"edit_timestamp": datetime_to_timestamp(timestamp), "edit_timestamp": datetime_to_timestamp(timestamp),
@ -390,7 +390,7 @@ def do_update_embedded_data(
event["message_ids"] = update_message_cache(changed_messages) event["message_ids"] = update_message_cache(changed_messages)
def user_info(um: UserMessage) -> Dict[str, Any]: def user_info(um: UserMessage) -> dict[str, Any]:
return { return {
"id": um.user_profile_id, "id": um.user_profile_id,
"flags": um.flags_list(), "flags": um.flags_list(),
@ -433,7 +433,7 @@ def do_update_message(
send_notification_to_new_thread: bool, send_notification_to_new_thread: bool,
content: Optional[str], content: Optional[str],
rendering_result: Optional[MessageRenderingResult], rendering_result: Optional[MessageRenderingResult],
prior_mention_user_ids: Set[int], prior_mention_user_ids: set[int],
mention_data: Optional[MentionData] = None, mention_data: Optional[MentionData] = None,
) -> int: ) -> int:
""" """
@ -452,7 +452,7 @@ def do_update_message(
timestamp = timezone_now() timestamp = timezone_now()
target_message.last_edit_time = timestamp target_message.last_edit_time = timestamp
event: Dict[str, Any] = { event: dict[str, Any] = {
"type": "update_message", "type": "update_message",
"user_id": user_profile.id, "user_id": user_profile.id,
"edit_timestamp": datetime_to_timestamp(timestamp), "edit_timestamp": datetime_to_timestamp(timestamp),
@ -586,7 +586,7 @@ def do_update_message(
event["propagate_mode"] = propagate_mode event["propagate_mode"] = propagate_mode
users_losing_access = UserProfile.objects.none() users_losing_access = UserProfile.objects.none()
user_ids_gaining_usermessages: List[int] = [] user_ids_gaining_usermessages: list[int] = []
if new_stream is not None: if new_stream is not None:
assert content is None assert content is None
assert target_message.is_stream_message() assert target_message.is_stream_message()
@ -798,7 +798,7 @@ def do_update_message(
event["message_ids"] = update_message_cache(changed_messages, realm_id) event["message_ids"] = update_message_cache(changed_messages, realm_id)
def user_info(um: UserMessage) -> Dict[str, Any]: def user_info(um: UserMessage) -> dict[str, Any]:
return { return {
"id": um.user_profile_id, "id": um.user_profile_id,
"flags": um.flags_list(), "flags": um.flags_list(),
@ -908,9 +908,9 @@ def do_update_message(
assert target_stream is not None assert target_stream is not None
assert target_topic_name is not None assert target_topic_name is not None
stream_inaccessible_to_user_profiles: List[UserProfile] = [] stream_inaccessible_to_user_profiles: list[UserProfile] = []
orig_topic_user_profile_to_visibility_policy: Dict[UserProfile, int] = {} orig_topic_user_profile_to_visibility_policy: dict[UserProfile, int] = {}
target_topic_user_profile_to_visibility_policy: Dict[UserProfile, int] = {} target_topic_user_profile_to_visibility_policy: dict[UserProfile, int] = {}
user_ids_losing_access = {user.id for user in users_losing_access} user_ids_losing_access = {user.id for user in users_losing_access}
for user_topic in get_users_with_user_topic_visibility_policy( for user_topic in get_users_with_user_topic_visibility_policy(
stream_being_edited.id, orig_topic_name stream_being_edited.id, orig_topic_name
@ -930,14 +930,14 @@ def do_update_message(
) )
# User profiles having any of the visibility policies set for either the original or target topic. # User profiles having any of the visibility policies set for either the original or target topic.
user_profiles_having_visibility_policy: Set[UserProfile] = set( user_profiles_having_visibility_policy: set[UserProfile] = set(
itertools.chain( itertools.chain(
orig_topic_user_profile_to_visibility_policy.keys(), orig_topic_user_profile_to_visibility_policy.keys(),
target_topic_user_profile_to_visibility_policy.keys(), target_topic_user_profile_to_visibility_policy.keys(),
) )
) )
user_profiles_for_visibility_policy_pair: Dict[Tuple[int, int], List[UserProfile]] = ( user_profiles_for_visibility_policy_pair: dict[tuple[int, int], list[UserProfile]] = (
defaultdict(list) defaultdict(list)
) )
for user_profile_with_policy in user_profiles_having_visibility_policy: for user_profile_with_policy in user_profiles_having_visibility_policy:
@ -1197,7 +1197,7 @@ def check_time_limit_for_change_all_propagate_mode(
message__recipient_id=message.recipient_id, message__recipient_id=message.recipient_id,
message__subject__iexact=message.topic_name(), message__subject__iexact=message.topic_name(),
).values_list("message_id", flat=True) ).values_list("message_id", flat=True)
messages_allowed_to_move: List[int] = list( messages_allowed_to_move: list[int] = list(
Message.objects.filter( Message.objects.filter(
# Uses index: zerver_message_pkey # Uses index: zerver_message_pkey
id__in=accessible_messages_in_topic, id__in=accessible_messages_in_topic,
@ -1291,8 +1291,8 @@ def check_update_message(
raise JsonableError(_("The time limit for editing this message's topic has passed.")) raise JsonableError(_("The time limit for editing this message's topic has passed."))
rendering_result = None rendering_result = None
links_for_embed: Set[str] = set() links_for_embed: set[str] = set()
prior_mention_user_ids: Set[int] = set() prior_mention_user_ids: set[int] = set()
mention_data: Optional[MentionData] = None mention_data: Optional[MentionData] = None
if content is not None: if content is not None:
if content.rstrip() == "": if content.rstrip() == "":

View File

@ -1,7 +1,7 @@
import time import time
from collections import defaultdict from collections import defaultdict
from dataclasses import asdict, dataclass, field from dataclasses import asdict, dataclass, field
from typing import List, Optional, Set from typing import Optional
from django.conf import settings from django.conf import settings
from django.db import transaction from django.db import transaction
@ -26,7 +26,7 @@ from zerver.tornado.django_api import send_event
@dataclass @dataclass
class ReadMessagesEvent: class ReadMessagesEvent:
messages: List[int] messages: list[int]
all: bool all: bool
type: str = field(default="update_message_flags", init=False) type: str = field(default="update_message_flags", init=False)
op: str = field(default="add", init=False) op: str = field(default="add", init=False)
@ -203,9 +203,9 @@ def do_mark_muted_user_messages_as_read(
def do_update_mobile_push_notification( def do_update_mobile_push_notification(
message: Message, message: Message,
prior_mention_user_ids: Set[int], prior_mention_user_ids: set[int],
mentions_user_ids: Set[int], mentions_user_ids: set[int],
stream_push_user_ids: Set[int], stream_push_user_ids: set[int],
) -> None: ) -> None:
# Called during the message edit code path to remove mobile push # Called during the message edit code path to remove mobile push
# notifications for users who are no longer mentioned following # notifications for users who are no longer mentioned following
@ -223,7 +223,7 @@ def do_update_mobile_push_notification(
def do_clear_mobile_push_notifications_for_ids( def do_clear_mobile_push_notifications_for_ids(
user_profile_ids: List[int], message_ids: List[int] user_profile_ids: list[int], message_ids: list[int]
) -> None: ) -> None:
if len(message_ids) == 0: if len(message_ids) == 0:
return return
@ -261,7 +261,7 @@ def do_clear_mobile_push_notifications_for_ids(
def do_update_message_flags( def do_update_message_flags(
user_profile: UserProfile, operation: str, flag: str, messages: List[int] user_profile: UserProfile, operation: str, flag: str, messages: list[int]
) -> int: ) -> int:
valid_flags = [item for item in UserMessage.flags if item not in UserMessage.NON_API_FLAGS] valid_flags = [item for item in UserMessage.flags if item not in UserMessage.NON_API_FLAGS]
if flag not in valid_flags: if flag not in valid_flags:

View File

@ -3,20 +3,7 @@ from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from email.headerregistry import Address from email.headerregistry import Address
from typing import ( from typing import AbstractSet, Any, Callable, Collection, Optional, Sequence, TypedDict, Union
AbstractSet,
Any,
Callable,
Collection,
Dict,
List,
Optional,
Sequence,
Set,
Tuple,
TypedDict,
Union,
)
import orjson import orjson
from django.conf import settings from django.conf import settings
@ -157,7 +144,7 @@ def render_incoming_message(
content: str, content: str,
realm: Realm, realm: Realm,
mention_data: Optional[MentionData] = None, mention_data: Optional[MentionData] = None,
url_embed_data: Optional[Dict[str, Optional[UrlEmbedData]]] = None, url_embed_data: Optional[dict[str, Optional[UrlEmbedData]]] = None,
email_gateway: bool = False, email_gateway: bool = False,
) -> MessageRenderingResult: ) -> MessageRenderingResult:
realm_alert_words_automaton = get_alert_word_automaton(realm) realm_alert_words_automaton = get_alert_word_automaton(realm)
@ -178,25 +165,25 @@ def render_incoming_message(
@dataclass @dataclass
class RecipientInfoResult: class RecipientInfoResult:
active_user_ids: Set[int] active_user_ids: set[int]
online_push_user_ids: Set[int] online_push_user_ids: set[int]
dm_mention_email_disabled_user_ids: Set[int] dm_mention_email_disabled_user_ids: set[int]
dm_mention_push_disabled_user_ids: Set[int] dm_mention_push_disabled_user_ids: set[int]
stream_email_user_ids: Set[int] stream_email_user_ids: set[int]
stream_push_user_ids: Set[int] stream_push_user_ids: set[int]
topic_wildcard_mention_user_ids: Set[int] topic_wildcard_mention_user_ids: set[int]
stream_wildcard_mention_user_ids: Set[int] stream_wildcard_mention_user_ids: set[int]
followed_topic_email_user_ids: Set[int] followed_topic_email_user_ids: set[int]
followed_topic_push_user_ids: Set[int] followed_topic_push_user_ids: set[int]
topic_wildcard_mention_in_followed_topic_user_ids: Set[int] topic_wildcard_mention_in_followed_topic_user_ids: set[int]
stream_wildcard_mention_in_followed_topic_user_ids: Set[int] stream_wildcard_mention_in_followed_topic_user_ids: set[int]
muted_sender_user_ids: Set[int] muted_sender_user_ids: set[int]
um_eligible_user_ids: Set[int] um_eligible_user_ids: set[int]
long_term_idle_user_ids: Set[int] long_term_idle_user_ids: set[int]
default_bot_user_ids: Set[int] default_bot_user_ids: set[int]
service_bot_tuples: List[Tuple[int, int]] service_bot_tuples: list[tuple[int, int]]
all_bot_user_ids: Set[int] all_bot_user_ids: set[int]
topic_participant_user_ids: Set[int] topic_participant_user_ids: set[int]
sender_muted_stream: Optional[bool] sender_muted_stream: Optional[bool]
@ -226,16 +213,16 @@ def get_recipient_info(
possible_topic_wildcard_mention: bool = True, possible_topic_wildcard_mention: bool = True,
possible_stream_wildcard_mention: bool = True, possible_stream_wildcard_mention: bool = True,
) -> RecipientInfoResult: ) -> RecipientInfoResult:
stream_push_user_ids: Set[int] = set() stream_push_user_ids: set[int] = set()
stream_email_user_ids: Set[int] = set() stream_email_user_ids: set[int] = set()
topic_wildcard_mention_user_ids: Set[int] = set() topic_wildcard_mention_user_ids: set[int] = set()
stream_wildcard_mention_user_ids: Set[int] = set() stream_wildcard_mention_user_ids: set[int] = set()
followed_topic_push_user_ids: Set[int] = set() followed_topic_push_user_ids: set[int] = set()
followed_topic_email_user_ids: Set[int] = set() followed_topic_email_user_ids: set[int] = set()
topic_wildcard_mention_in_followed_topic_user_ids: Set[int] = set() topic_wildcard_mention_in_followed_topic_user_ids: set[int] = set()
stream_wildcard_mention_in_followed_topic_user_ids: Set[int] = set() stream_wildcard_mention_in_followed_topic_user_ids: set[int] = set()
muted_sender_user_ids: Set[int] = get_muting_users(sender_id) muted_sender_user_ids: set[int] = get_muting_users(sender_id)
topic_participant_user_ids: Set[int] = set() topic_participant_user_ids: set[int] = set()
sender_muted_stream: Optional[bool] = None sender_muted_stream: Optional[bool] = None
if recipient.type == Recipient.PERSONAL: if recipient.type == Recipient.PERSONAL:
@ -315,7 +302,7 @@ def get_recipient_info(
user_id_to_visibility_policy = stream_topic.user_id_to_visibility_policy_dict() user_id_to_visibility_policy = stream_topic.user_id_to_visibility_policy_dict()
def notification_recipients(setting: str) -> Set[int]: def notification_recipients(setting: str) -> set[int]:
return { return {
row["user_profile_id"] row["user_profile_id"]
for row in subscription_rows for row in subscription_rows
@ -332,7 +319,7 @@ def get_recipient_info(
stream_push_user_ids = notification_recipients("push_notifications") stream_push_user_ids = notification_recipients("push_notifications")
stream_email_user_ids = notification_recipients("email_notifications") stream_email_user_ids = notification_recipients("email_notifications")
def followed_topic_notification_recipients(setting: str) -> Set[int]: def followed_topic_notification_recipients(setting: str) -> set[int]:
return { return {
row["user_profile_id"] row["user_profile_id"]
for row in subscription_rows for row in subscription_rows
@ -427,7 +414,7 @@ def get_recipient_info(
# to-do. # to-do.
rows = [] rows = []
def get_ids_for(f: Callable[[ActiveUserDict], bool]) -> Set[int]: def get_ids_for(f: Callable[[ActiveUserDict], bool]) -> set[int]:
"""Only includes users on the explicit message to line""" """Only includes users on the explicit message to line"""
return {row["id"] for row in rows if f(row)} & message_to_user_id_set return {row["id"] for row in rows if f(row)} & message_to_user_id_set
@ -506,12 +493,12 @@ def get_recipient_info(
def get_service_bot_events( def get_service_bot_events(
sender: UserProfile, sender: UserProfile,
service_bot_tuples: List[Tuple[int, int]], service_bot_tuples: list[tuple[int, int]],
mentioned_user_ids: Set[int], mentioned_user_ids: set[int],
active_user_ids: Set[int], active_user_ids: set[int],
recipient_type: int, recipient_type: int,
) -> Dict[str, List[Dict[str, Any]]]: ) -> dict[str, list[dict[str, Any]]]:
event_dict: Dict[str, List[Dict[str, Any]]] = defaultdict(list) event_dict: dict[str, list[dict[str, Any]]] = defaultdict(list)
# Avoid infinite loops by preventing messages sent by bots from generating # Avoid infinite loops by preventing messages sent by bots from generating
# Service events. # Service events.
@ -576,12 +563,12 @@ def build_message_send_dict(
stream: Optional[Stream] = None, stream: Optional[Stream] = None,
local_id: Optional[str] = None, local_id: Optional[str] = None,
sender_queue_id: Optional[str] = None, sender_queue_id: Optional[str] = None,
widget_content_dict: Optional[Dict[str, Any]] = None, widget_content_dict: Optional[dict[str, Any]] = None,
email_gateway: bool = False, email_gateway: bool = False,
mention_backend: Optional[MentionBackend] = None, mention_backend: Optional[MentionBackend] = None,
limit_unread_user_ids: Optional[Set[int]] = None, limit_unread_user_ids: Optional[set[int]] = None,
disable_external_notifications: bool = False, disable_external_notifications: bool = False,
recipients_for_user_creation_events: Optional[Dict[UserProfile, Set[int]]] = None, recipients_for_user_creation_events: Optional[dict[UserProfile, set[int]]] = None,
) -> SendMessageRequest: ) -> SendMessageRequest:
"""Returns a dictionary that can be passed into do_send_messages. In """Returns a dictionary that can be passed into do_send_messages. In
production, this is always called by check_message, but some production, this is always called by check_message, but some
@ -726,10 +713,10 @@ def create_user_messages(
mentioned_user_ids: AbstractSet[int], mentioned_user_ids: AbstractSet[int],
followed_topic_push_user_ids: AbstractSet[int], followed_topic_push_user_ids: AbstractSet[int],
followed_topic_email_user_ids: AbstractSet[int], followed_topic_email_user_ids: AbstractSet[int],
mark_as_read_user_ids: Set[int], mark_as_read_user_ids: set[int],
limit_unread_user_ids: Optional[Set[int]], limit_unread_user_ids: Optional[set[int]],
topic_participant_user_ids: Set[int], topic_participant_user_ids: set[int],
) -> List[UserMessageLite]: ) -> list[UserMessageLite]:
# These properties on the Message are set via # These properties on the Message are set via
# render_message_markdown by code in the Markdown inline patterns # render_message_markdown by code in the Markdown inline patterns
ids_with_alert_words = rendering_result.user_ids_with_alert_words ids_with_alert_words = rendering_result.user_ids_with_alert_words
@ -798,7 +785,7 @@ def create_user_messages(
return user_messages return user_messages
def filter_presence_idle_user_ids(user_ids: Set[int]) -> List[int]: def filter_presence_idle_user_ids(user_ids: set[int]) -> list[int]:
# Given a set of user IDs (the recipients of a message), accesses # Given a set of user IDs (the recipients of a message), accesses
# the UserPresence table to determine which of these users are # the UserPresence table to determine which of these users are
# currently idle and should potentially get email notifications # currently idle and should potentially get email notifications
@ -821,8 +808,8 @@ def filter_presence_idle_user_ids(user_ids: Set[int]) -> List[int]:
def get_active_presence_idle_user_ids( def get_active_presence_idle_user_ids(
realm: Realm, realm: Realm,
sender_id: int, sender_id: int,
user_notifications_data_list: List[UserMessageNotificationsData], user_notifications_data_list: list[UserMessageNotificationsData],
) -> List[int]: ) -> list[int]:
""" """
Given a list of active_user_ids, we build up a subset Given a list of active_user_ids, we build up a subset
of those users who fit these criteria: of those users who fit these criteria:
@ -851,7 +838,7 @@ def do_send_messages(
send_message_requests_maybe_none: Sequence[Optional[SendMessageRequest]], send_message_requests_maybe_none: Sequence[Optional[SendMessageRequest]],
*, *,
mark_as_read: Sequence[int] = [], mark_as_read: Sequence[int] = [],
) -> List[SentMessageResult]: ) -> list[SentMessageResult]:
"""See """See
https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html
for high-level documentation on this subsystem. for high-level documentation on this subsystem.
@ -865,7 +852,7 @@ def do_send_messages(
] ]
# Save the message receipts in the database # Save the message receipts in the database
user_message_flags: Dict[int, Dict[int, List[str]]] = defaultdict(dict) user_message_flags: dict[int, dict[int, list[str]]] = defaultdict(dict)
Message.objects.bulk_create(send_request.message for send_request in send_message_requests) Message.objects.bulk_create(send_request.message for send_request in send_message_requests)
@ -877,7 +864,7 @@ def do_send_messages(
send_request.message.has_attachment = True send_request.message.has_attachment = True
send_request.message.save(update_fields=["has_attachment"]) send_request.message.save(update_fields=["has_attachment"])
ums: List[UserMessageLite] = [] ums: list[UserMessageLite] = []
for send_request in send_message_requests: for send_request in send_message_requests:
# Service bots (outgoing webhook bots and embedded bots) don't store UserMessage rows; # Service bots (outgoing webhook bots and embedded bots) don't store UserMessage rows;
# they will be processed later. # they will be processed later.
@ -978,7 +965,7 @@ def do_send_messages(
human_user_personal_mentions = send_request.rendering_result.mentions_user_ids & ( human_user_personal_mentions = send_request.rendering_result.mentions_user_ids & (
send_request.active_user_ids - send_request.all_bot_user_ids send_request.active_user_ids - send_request.all_bot_user_ids
) )
expect_follow_user_profiles: Set[UserProfile] = set() expect_follow_user_profiles: set[UserProfile] = set()
if len(human_user_personal_mentions) > 0: if len(human_user_personal_mentions) > 0:
expect_follow_user_profiles = set( expect_follow_user_profiles = set(
@ -1045,10 +1032,10 @@ def do_send_messages(
class UserData(TypedDict): class UserData(TypedDict):
id: int id: int
flags: List[str] flags: list[str]
mentioned_user_group_id: Optional[int] mentioned_user_group_id: Optional[int]
users: List[UserData] = [] users: list[UserData] = []
for user_id in user_list: for user_id in user_list:
flags = user_flags.get(user_id, []) flags = user_flags.get(user_id, [])
# TODO/compatibility: The `wildcard_mentioned` flag was deprecated in favor of # TODO/compatibility: The `wildcard_mentioned` flag was deprecated in favor of
@ -1278,7 +1265,7 @@ def extract_stream_indicator(s: str) -> Union[str, int]:
raise JsonableError(_("Invalid data type for channel")) raise JsonableError(_("Invalid data type for channel"))
def extract_private_recipients(s: str) -> Union[List[str], List[int]]: def extract_private_recipients(s: str) -> Union[list[str], list[int]]:
# We try to accept multiple incoming formats for recipients. # We try to accept multiple incoming formats for recipients.
# See test_extract_recipients() for examples of what we allow. # See test_extract_recipients() for examples of what we allow.
@ -1306,7 +1293,7 @@ def extract_private_recipients(s: str) -> Union[List[str], List[int]]:
return get_validated_user_ids(data) return get_validated_user_ids(data)
def get_validated_user_ids(user_ids: Collection[int]) -> List[int]: def get_validated_user_ids(user_ids: Collection[int]) -> list[int]:
for user_id in user_ids: for user_id in user_ids:
if not isinstance(user_id, int): if not isinstance(user_id, int):
raise JsonableError(_("Recipient lists may contain emails or user IDs, but not both.")) raise JsonableError(_("Recipient lists may contain emails or user IDs, but not both."))
@ -1314,7 +1301,7 @@ def get_validated_user_ids(user_ids: Collection[int]) -> List[int]:
return list(set(user_ids)) return list(set(user_ids))
def get_validated_emails(emails: Collection[str]) -> List[str]: def get_validated_emails(emails: Collection[str]) -> list[str]:
for email in emails: for email in emails:
if not isinstance(email, str): if not isinstance(email, str):
raise JsonableError(_("Recipient lists may contain emails or user IDs, but not both.")) raise JsonableError(_("Recipient lists may contain emails or user IDs, but not both."))
@ -1457,7 +1444,7 @@ def send_pm_if_empty_stream(
if sender.bot_owner is not None: if sender.bot_owner is not None:
with override_language(sender.bot_owner.default_language): with override_language(sender.bot_owner.default_language):
arg_dict: Dict[str, Any] = { arg_dict: dict[str, Any] = {
"bot_identity": f"`{sender.delivery_email}`", "bot_identity": f"`{sender.delivery_email}`",
} }
if stream is None: if stream is None:
@ -1596,14 +1583,14 @@ def check_sender_can_access_recipients(
def get_recipients_for_user_creation_events( def get_recipients_for_user_creation_events(
realm: Realm, sender: UserProfile, user_profiles: Sequence[UserProfile] realm: Realm, sender: UserProfile, user_profiles: Sequence[UserProfile]
) -> Dict[UserProfile, Set[int]]: ) -> dict[UserProfile, set[int]]:
""" """
This function returns a dictionary with data about which users would This function returns a dictionary with data about which users would
receive stream creation events due to gaining access to a user. receive stream creation events due to gaining access to a user.
The key of the dictionary is a user object and the value is a set of The key of the dictionary is a user object and the value is a set of
user_ids that would gain access to that user. user_ids that would gain access to that user.
""" """
recipients_for_user_creation_events: Dict[UserProfile, Set[int]] = defaultdict(set) recipients_for_user_creation_events: dict[UserProfile, set[int]] = defaultdict(set)
# If none of the users in the direct message conversation are # If none of the users in the direct message conversation are
# guests, then there is no possible can_access_all_users_group # guests, then there is no possible can_access_all_users_group
@ -1666,7 +1653,7 @@ def check_message(
skip_stream_access_check: bool = False, skip_stream_access_check: bool = False,
message_type: int = Message.MessageType.NORMAL, message_type: int = Message.MessageType.NORMAL,
mention_backend: Optional[MentionBackend] = None, mention_backend: Optional[MentionBackend] = None,
limit_unread_user_ids: Optional[Set[int]] = None, limit_unread_user_ids: Optional[set[int]] = None,
disable_external_notifications: bool = False, disable_external_notifications: bool = False,
) -> SendMessageRequest: ) -> SendMessageRequest:
"""See """See
@ -1841,7 +1828,7 @@ def _internal_prep_message(
email_gateway: bool = False, email_gateway: bool = False,
message_type: int = Message.MessageType.NORMAL, message_type: int = Message.MessageType.NORMAL,
mention_backend: Optional[MentionBackend] = None, mention_backend: Optional[MentionBackend] = None,
limit_unread_user_ids: Optional[Set[int]] = None, limit_unread_user_ids: Optional[set[int]] = None,
disable_external_notifications: bool = False, disable_external_notifications: bool = False,
forged: bool = False, forged: bool = False,
forged_timestamp: Optional[float] = None, forged_timestamp: Optional[float] = None,
@ -1896,7 +1883,7 @@ def internal_prep_stream_message(
*, *,
email_gateway: bool = False, email_gateway: bool = False,
message_type: int = Message.MessageType.NORMAL, message_type: int = Message.MessageType.NORMAL,
limit_unread_user_ids: Optional[Set[int]] = None, limit_unread_user_ids: Optional[set[int]] = None,
forged: bool = False, forged: bool = False,
forged_timestamp: Optional[float] = None, forged_timestamp: Optional[float] = None,
) -> Optional[SendMessageRequest]: ) -> Optional[SendMessageRequest]:
@ -1993,7 +1980,7 @@ def internal_send_stream_message(
*, *,
email_gateway: bool = False, email_gateway: bool = False,
message_type: int = Message.MessageType.NORMAL, message_type: int = Message.MessageType.NORMAL,
limit_unread_user_ids: Optional[Set[int]] = None, limit_unread_user_ids: Optional[set[int]] = None,
) -> Optional[int]: ) -> Optional[int]:
message = internal_prep_stream_message( message = internal_prep_stream_message(
sender, sender,
@ -2038,8 +2025,8 @@ def internal_prep_group_direct_message(
sender: UserProfile, sender: UserProfile,
content: str, content: str,
*, *,
emails: Optional[List[str]] = None, emails: Optional[list[str]] = None,
recipient_users: Optional[List[UserProfile]] = None, recipient_users: Optional[list[UserProfile]] = None,
) -> Optional[SendMessageRequest]: ) -> Optional[SendMessageRequest]:
if recipient_users is not None: if recipient_users is not None:
addressee = Addressee.for_user_profiles(recipient_users) addressee = Addressee.for_user_profiles(recipient_users)
@ -2060,8 +2047,8 @@ def internal_send_group_direct_message(
sender: UserProfile, sender: UserProfile,
content: str, content: str,
*, *,
emails: Optional[List[str]] = None, emails: Optional[list[str]] = None,
recipient_users: Optional[List[UserProfile]] = None, recipient_users: Optional[list[UserProfile]] = None,
) -> Optional[int]: ) -> Optional[int]:
message = internal_prep_group_direct_message( message = internal_prep_group_direct_message(
realm, sender, content, emails=emails, recipient_users=recipient_users realm, sender, content, emails=emails, recipient_users=recipient_users

View File

@ -1,4 +1,4 @@
from typing import Any, Dict, Optional from typing import Any, Optional
from zerver.actions.user_topics import do_set_user_topic_visibility_policy from zerver.actions.user_topics import do_set_user_topic_visibility_policy
from zerver.lib.emoji import check_emoji_request, get_emoji_data from zerver.lib.emoji import check_emoji_request, get_emoji_data
@ -26,7 +26,7 @@ def notify_reaction_update(
"full_name": user_profile.full_name, "full_name": user_profile.full_name,
} }
event: Dict[str, Any] = { event: dict[str, Any] = {
"type": "reaction", "type": "reaction",
"op": op, "op": op,
"user_id": user_profile.id, "user_id": user_profile.id,

View File

@ -1,4 +1,4 @@
from typing import IO, Dict, Optional from typing import IO, Optional
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import transaction from django.db import transaction
@ -17,7 +17,7 @@ from zerver.models.users import active_user_ids
from zerver.tornado.django_api import send_event_on_commit from zerver.tornado.django_api import send_event_on_commit
def notify_realm_emoji(realm: Realm, realm_emoji: Dict[str, EmojiInfo]) -> None: def notify_realm_emoji(realm: Realm, realm_emoji: dict[str, EmojiInfo]) -> None:
event = dict(type="realm_emoji", op="update", realm_emoji=realm_emoji) event = dict(type="realm_emoji", op="update", realm_emoji=realm_emoji)
send_event_on_commit(realm, event, active_user_ids(realm.id)) send_event_on_commit(realm, event, active_user_ids(realm.id))

View File

@ -1,4 +1,4 @@
from typing import Dict, List, Optional from typing import Optional
from django.db import transaction from django.db import transaction
from django.db.models import Max from django.db.models import Max
@ -13,8 +13,8 @@ from zerver.models.users import active_user_ids
from zerver.tornado.django_api import send_event_on_commit from zerver.tornado.django_api import send_event_on_commit
def notify_linkifiers(realm: Realm, realm_linkifiers: List[LinkifierDict]) -> None: def notify_linkifiers(realm: Realm, realm_linkifiers: list[LinkifierDict]) -> None:
event: Dict[str, object] = dict(type="realm_linkifiers", realm_linkifiers=realm_linkifiers) event: dict[str, object] = dict(type="realm_linkifiers", realm_linkifiers=realm_linkifiers)
send_event_on_commit(realm, event, active_user_ids(realm.id)) send_event_on_commit(realm, event, active_user_ids(realm.id))
@ -137,7 +137,7 @@ def do_update_linkifier(
@transaction.atomic(durable=True) @transaction.atomic(durable=True)
def check_reorder_linkifiers( def check_reorder_linkifiers(
realm: Realm, ordered_linkifier_ids: List[int], *, acting_user: Optional[UserProfile] realm: Realm, ordered_linkifier_ids: list[int], *, acting_user: Optional[UserProfile]
) -> None: ) -> None:
"""ordered_linkifier_ids should contain ids of all existing linkifiers. """ordered_linkifier_ids should contain ids of all existing linkifiers.
In the rare situation when any of the linkifier gets deleted that more ids In the rare situation when any of the linkifier gets deleted that more ids

Some files were not shown because too many files have changed in this diff Show More