diff --git a/analytics/lib/counts.py b/analytics/lib/counts.py index d6d80dfcb6..07fefdfd1d 100644 --- a/analytics/lib/counts.py +++ b/analytics/lib/counts.py @@ -2,7 +2,7 @@ import logging import time from collections import OrderedDict, defaultdict 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.db import connection, models @@ -82,7 +82,7 @@ class 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) @@ -102,7 +102,7 @@ class DependentCountStat(CountStat): class DataCollector: def __init__( self, - output_table: Type[BaseCount], + output_table: type[BaseCount], pull_function: Optional[Callable[[str, datetime, datetime, Optional[Realm]], int]], ) -> None: self.output_table = output_table @@ -311,8 +311,8 @@ def do_increment_logging_stat( return table = stat.data_collector.output_table - id_args: Dict[str, Union[int, None]] = {} - conflict_args: List[str] = [] + id_args: dict[str, Union[int, None]] = {} + conflict_args: list[str] = [] if table == RealmCount: assert isinstance(model_object_for_bucket, Realm) id_args = {"realm_id": model_object_for_bucket.id} @@ -425,7 +425,7 @@ def do_drop_single_stat(property: str) -> None: ## DataCollector-level operations ## -QueryFn: TypeAlias = Callable[[Dict[str, Composable]], Composable] +QueryFn: TypeAlias = Callable[[dict[str, Composable]], Composable] def do_pull_by_sql_query( @@ -433,7 +433,7 @@ def do_pull_by_sql_query( start_time: datetime, end_time: datetime, query: QueryFn, - group_by: Optional[Tuple[Type[models.Model], str]], + group_by: Optional[tuple[type[models.Model], str]], ) -> int: if group_by is None: subgroup: Composable = SQL("NULL") @@ -467,9 +467,9 @@ def do_pull_by_sql_query( def sql_data_collector( - output_table: Type[BaseCount], + output_table: type[BaseCount], query: QueryFn, - group_by: Optional[Tuple[Type[models.Model], str]], + group_by: Optional[tuple[type[models.Model], str]], ) -> DataCollector: def pull_function( 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") ) - 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: if realm is None or realm.id == realm_id: start = max(start_time, interval_start) @@ -817,7 +817,7 @@ count_stream_by_realm_query = lambda kwargs: SQL( ).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 ## count_stats_ = [ diff --git a/analytics/lib/fixtures.py b/analytics/lib/fixtures.py index ad50e263bd..d52a80bb20 100644 --- a/analytics/lib/fixtures.py +++ b/analytics/lib/fixtures.py @@ -1,6 +1,5 @@ from math import sqrt from random import Random -from typing import List from analytics.lib.counts import CountStat @@ -16,7 +15,7 @@ def generate_time_series_data( frequency: str = CountStat.DAY, partial_sum: bool = False, random_seed: int = 26, -) -> List[int]: +) -> list[int]: """ Generate semi-realistic looking time series data for testing analytics graphs. diff --git a/analytics/lib/time_utils.py b/analytics/lib/time_utils.py index 0afa73632e..04b4baaa4f 100644 --- a/analytics/lib/time_utils.py +++ b/analytics/lib/time_utils.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta -from typing import List, Optional +from typing import Optional from analytics.lib.counts import CountStat 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] def time_range( start: datetime, end: datetime, frequency: str, min_length: Optional[int] -) -> List[datetime]: +) -> list[datetime]: verify_UTC(start) verify_UTC(end) if frequency == CountStat.HOUR: diff --git a/analytics/management/commands/populate_analytics_db.py b/analytics/management/commands/populate_analytics_db.py index ffbed95dd4..c9c23a6a44 100644 --- a/analytics/management/commands/populate_analytics_db.py +++ b/analytics/management/commands/populate_analytics_db.py @@ -1,5 +1,5 @@ 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.utils.timezone import now as timezone_now @@ -53,7 +53,7 @@ class Command(ZulipBaseCommand): spikiness: float, holiday_rate: float = 0, partial_sum: bool = False, - ) -> List[int]: + ) -> list[int]: self.random_seed += 1 return generate_time_series_data( days=self.DAYS_OF_DATA, @@ -147,18 +147,18 @@ class Command(ZulipBaseCommand): with open(IMAGE_FILE_PATH, "rb") as fp: 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( stat: CountStat, fixture_data: FixtureData, - table: Type[BaseCount], + table: type[BaseCount], ) -> None: end_times = time_range( last_end_time, last_end_time, stat.frequency, len(next(iter(fixture_data.values()))) ) if table == InstallationCount: - id_args: Dict[str, Any] = {} + id_args: dict[str, Any] = {} if table == RealmCount: id_args = {"realm": realm} if table == UserCount: @@ -330,7 +330,7 @@ class Command(ZulipBaseCommand): "true": self.generate_fixture_data(stat, 20, 2, 3, 0.2, 3), } 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), "true": self.generate_fixture_data(stat, 5, 3, 2, 0.4, 2), } diff --git a/analytics/management/commands/update_analytics_counts.py b/analytics/management/commands/update_analytics_counts.py index 2da50bc51c..18de7989c5 100644 --- a/analytics/management/commands/update_analytics_counts.py +++ b/analytics/management/commands/update_analytics_counts.py @@ -2,7 +2,7 @@ import hashlib import time from argparse import ArgumentParser from datetime import timezone -from typing import Any, Dict +from typing import Any from django.conf import settings from django.utils.dateparse import parse_datetime @@ -43,7 +43,7 @@ class Command(ZulipBaseCommand): def handle(self, *args: Any, **options: Any) -> None: 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 # shouldn't run the analytics code if that condition isn't satisfied if not Realm.objects.exists(): diff --git a/analytics/tests/test_counts.py b/analytics/tests/test_counts.py index 8145fde78d..37453d1d22 100644 --- a/analytics/tests/test_counts.py +++ b/analytics/tests/test_counts.py @@ -1,6 +1,6 @@ from contextlib import AbstractContextManager, ExitStack, contextmanager 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 import time_machine @@ -132,7 +132,7 @@ class AnalyticsTestCase(ZulipTestCase): kwargs[key] = kwargs.get(key, value) kwargs["delivery_email"] = kwargs["email"] with time_machine.travel(kwargs["date_joined"], tick=False): - pass_kwargs: Dict[str, Any] = {} + pass_kwargs: dict[str, Any] = {} if kwargs["is_bot"]: pass_kwargs["bot_type"] = UserProfile.DEFAULT_BOT pass_kwargs["bot_owner"] = None @@ -158,7 +158,7 @@ class AnalyticsTestCase(ZulipTestCase): ) 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 defaults = { "name": f"stream name {self.name_counter}", @@ -174,7 +174,7 @@ class AnalyticsTestCase(ZulipTestCase): stream.save(update_fields=["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 defaults = {"huddle_hash": f"hash{self.name_counter}"} for key, value in defaults.items(): @@ -224,7 +224,7 @@ class AnalyticsTestCase(ZulipTestCase): # kwargs should only ever be a UserProfile or Stream. def assert_table_count( self, - table: Type[BaseCount], + table: type[BaseCount], value: int, property: Optional[str] = None, subgroup: Optional[str] = None, @@ -246,7 +246,7 @@ class AnalyticsTestCase(ZulipTestCase): self.assertEqual(queryset.values_list("value", flat=True)[0], value) 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: """Assert that the state of a *Count table is what it should be. @@ -276,7 +276,7 @@ class AnalyticsTestCase(ZulipTestCase): "value": 1, } for values in arg_values: - kwargs: Dict[str, Any] = {} + kwargs: dict[str, Any] = {} for i in range(len(values)): kwargs[arg_keys[i]] = values[i] for key, value in defaults.items(): @@ -1619,7 +1619,7 @@ class TestLoggingCountStats(AnalyticsTestCase): def invite_context( too_many_recent_realm_invites: bool = False, failure: bool = False ) -> Iterator[None]: - managers: List[AbstractContextManager[Any]] = [ + managers: list[AbstractContextManager[Any]] = [ mock.patch( "zerver.actions.invites.too_many_recent_realm_invites", return_value=False ), diff --git a/analytics/tests/test_stats_views.py b/analytics/tests/test_stats_views.py index 6846c295cd..dd4cbf27e3 100644 --- a/analytics/tests/test_stats_views.py +++ b/analytics/tests/test_stats_views.py @@ -1,5 +1,5 @@ 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 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) ] - def data(self, i: int) -> List[int]: + def data(self, i: int) -> list[int]: return [0, 0, i, 0] 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: if stat.frequency == CountStat.HOUR: insert_time = self.end_times_hour[2] @@ -605,7 +605,7 @@ class TestGetChartData(ZulipTestCase): class TestGetChartDataHelpers(ZulipTestCase): 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} self.assertEqual(sort_by_totals(value_arrays), ["a", "b", "c", "d"]) diff --git a/analytics/urls.py b/analytics/urls.py index 0a68f49c97..58b0cf5574 100644 --- a/analytics/urls.py +++ b/analytics/urls.py @@ -1,4 +1,4 @@ -from typing import List, Union +from typing import Union from django.conf import settings from django.conf.urls import include @@ -16,7 +16,7 @@ from analytics.views.stats import ( ) 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 path("stats/realm//", stats_for_realm), path("stats/installation", stats_for_installation), diff --git a/analytics/views/stats.py b/analytics/views/stats.py index 1fe1fa72bd..62d3c94388 100644 --- a/analytics/views/stats.py +++ b/analytics/views/stats.py @@ -1,7 +1,7 @@ import logging from collections import defaultdict 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.db.models import QuerySet @@ -260,10 +260,10 @@ def get_chart_data( stream: Optional[Stream] = None, ) -> HttpResponse: TableType: TypeAlias = Union[ - Type["RemoteInstallationCount"], - Type[InstallationCount], - Type["RemoteRealmCount"], - Type[RealmCount], + type["RemoteInstallationCount"], + type[InstallationCount], + type["RemoteRealmCount"], + type[RealmCount], ] if for_installation: if remote: @@ -282,7 +282,7 @@ def get_chart_data( aggregate_table = RealmCount 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": @@ -292,7 +292,7 @@ def get_chart_data( COUNT_STATS["active_users_audit:is_bot:day"], ] 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[1]: {None: "_15day"}, stats[2]: {"false": "all_time"}, @@ -372,7 +372,7 @@ def get_chart_data( assert server is not None assert aggregate_table is RemoteInstallationCount or aggregate_table is RemoteRealmCount 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 if not aggregate_table_remote.objects.filter(server=server).exists(): raise JsonableError( @@ -418,7 +418,7 @@ def get_chart_data( assert len({stat.frequency for stat in stats}) == 1 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], "frequency": stats[0].frequency, } @@ -471,7 +471,7 @@ def get_chart_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) 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 # tries to rank the clients so that taking the first N elements of the # 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"]) 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): 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])] @@ -494,7 +494,7 @@ def sort_client_labels(data: Dict[str, Dict[str, List[int]]]) -> List[str]: 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: return table._default_manager.filter(realm_id=key_id) elif table == UserCount: @@ -535,8 +535,8 @@ def client_label_map(name: str) -> str: return name -def rewrite_client_arrays(value_arrays: Dict[str, List[int]]) -> Dict[str, List[int]]: - mapped_arrays: Dict[str, List[int]] = {} +def rewrite_client_arrays(value_arrays: dict[str, list[int]]) -> dict[str, list[int]]: + mapped_arrays: dict[str, list[int]] = {} for label, array in value_arrays.items(): mapped_label = client_label_map(label) 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( stat: CountStat, - table: Type[BaseCount], + table: type[BaseCount], key_id: int, - end_times: List[datetime], - subgroup_to_label: Dict[Optional[str], str], + end_times: list[datetime], + subgroup_to_label: dict[Optional[str], str], include_empty_subgroups: bool, -) -> Dict[str, List[int]]: +) -> dict[str, list[int]]: queryset = ( table_filtered_to_id(table, key_id) .filter(property=stat.property) .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: value_dicts[subgroup][end_time] = value value_arrays = {} diff --git a/confirmation/models.py b/confirmation/models.py index 6740b30cb5..b360539a5d 100644 --- a/confirmation/models.py +++ b/confirmation/models.py @@ -4,7 +4,7 @@ __revision__ = "$Id: models.py 28 2009-10-22 15:03:02Z jarek.zgoda $" import secrets from base64 import b32encode 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 django.conf import settings @@ -80,7 +80,7 @@ ConfirmationObjT: TypeAlias = Union[NoZilencerConfirmationObjT, ZilencerConfirma 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: """Access a confirmation object from one of the provided confirmation types with the provided key. diff --git a/corporate/lib/activity.py b/corporate/lib/activity.py index 90a6bf1aa4..dc8a9aed0b 100644 --- a/corporate/lib/activity.py +++ b/corporate/lib/activity.py @@ -2,7 +2,7 @@ from collections import defaultdict from dataclasses import dataclass from datetime import datetime 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 django.conf import settings @@ -52,7 +52,7 @@ def make_table( ) -> str: 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) rows = list(map(fix_row, rows)) @@ -68,7 +68,7 @@ def make_table( def fix_rows( - rows: List[List[Any]], + rows: list[list[Any]], i: int, fixup_func: Union[Callable[[str], Markup], Callable[[datetime], str], Callable[[int], int]], ) -> None: @@ -76,7 +76,7 @@ def fix_rows( 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.execute(query) rows = cursor.fetchall() @@ -85,7 +85,7 @@ def get_query_data(query: Composable) -> List[List[Any]]: 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""" desc = cursor.description 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 # are not included in `plan_rate`. 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 -def get_plan_data_by_remote_server() -> Dict[int, RemoteActivityPlanData]: # nocoverage - remote_server_plan_data: Dict[int, RemoteActivityPlanData] = {} +def get_plan_data_by_remote_server() -> dict[int, RemoteActivityPlanData]: # nocoverage + remote_server_plan_data: dict[int, RemoteActivityPlanData] = {} plans = ( CustomerPlan.objects.filter( 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 -def get_plan_data_by_remote_realm() -> Dict[int, Dict[int, RemoteActivityPlanData]]: # nocoverage - remote_server_plan_data_by_realm: Dict[int, Dict[int, RemoteActivityPlanData]] = {} +def get_plan_data_by_remote_realm() -> dict[int, dict[int, RemoteActivityPlanData]]: # nocoverage + remote_server_plan_data_by_realm: dict[int, dict[int, RemoteActivityPlanData]] = {} plans = ( CustomerPlan.objects.filter( 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( event_time: datetime = timezone_now(), -) -> Dict[int, RemoteCustomerUserCount]: # nocoverage - user_counts_by_realm: Dict[int, RemoteCustomerUserCount] = {} +) -> dict[int, RemoteCustomerUserCount]: # nocoverage + user_counts_by_realm: dict[int, RemoteCustomerUserCount] = {} for log in ( RemoteRealmAuditLog.objects.filter( event_type__in=RemoteRealmAuditLog.SYNCED_BILLING_EVENTS, @@ -378,8 +378,8 @@ def get_remote_realm_user_counts( def get_remote_server_audit_logs( event_time: datetime = timezone_now(), -) -> Dict[int, List[RemoteRealmAuditLog]]: - logs_per_server: Dict[int, List[RemoteRealmAuditLog]] = defaultdict(list) +) -> dict[int, list[RemoteRealmAuditLog]]: + logs_per_server: dict[int, list[RemoteRealmAuditLog]] = defaultdict(list) for log in ( RemoteRealmAuditLog.objects.filter( event_type__in=RemoteRealmAuditLog.SYNCED_BILLING_EVENTS, diff --git a/corporate/lib/remote_billing_util.py b/corporate/lib/remote_billing_util.py index c0629208b1..f36b90266e 100644 --- a/corporate/lib/remote_billing_util.py +++ b/corporate/lib/remote_billing_util.py @@ -1,5 +1,5 @@ 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.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( request: HttpRequest, realm_uuid: Optional[str], -) -> Tuple[RemoteRealm, RemoteRealmBillingUser]: +) -> tuple[RemoteRealm, RemoteRealmBillingUser]: # Cannot use isinstance with TypeDicts, to make mypy know # which of the TypedDicts in the Union this is - so just cast it. identity_dict = cast( @@ -151,7 +151,7 @@ def get_remote_realm_and_user_from_session( def get_remote_server_and_user_from_session( request: HttpRequest, server_uuid: str, -) -> Tuple[RemoteZulipServer, Optional[RemoteServerBillingUser]]: +) -> tuple[RemoteZulipServer, Optional[RemoteServerBillingUser]]: identity_dict: Optional[LegacyServerIdentityDict] = get_identity_dict_from_session( request, realm_uuid=None, server_uuid=server_uuid ) diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index f61535a830..626f4d1994 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -8,18 +8,7 @@ from datetime import datetime, timedelta, timezone from decimal import Decimal from enum import Enum from functools import wraps -from typing import ( - Any, - Callable, - Dict, - Generator, - Literal, - Optional, - Tuple, - TypedDict, - TypeVar, - Union, -) +from typing import Any, Callable, Generator, Literal, Optional, TypedDict, TypeVar, Union from urllib.parse import urlencode, urljoin import stripe @@ -187,7 +176,7 @@ def get_seat_count( 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) signer = Signer(salt=salt) return signer.sign(string), salt @@ -541,7 +530,7 @@ class PriceArgs(TypedDict, total=False): class StripeCustomerData: description: str email: str - metadata: Dict[str, Any] + metadata: dict[str, Any] @dataclass @@ -754,7 +743,7 @@ class BillingSession(ABC): event_time: datetime, *, background_update: bool = False, - extra_data: Optional[Dict[str, Any]] = None, + extra_data: Optional[dict[str, Any]] = None, ) -> None: pass @@ -764,8 +753,8 @@ class BillingSession(ABC): @abstractmethod def update_data_for_checkout_session_and_invoice_payment( - self, metadata: Dict[str, Any] - ) -> Dict[str, Any]: + self, metadata: dict[str, Any] + ) -> dict[str, Any]: pass @abstractmethod @@ -956,7 +945,7 @@ class BillingSession(ABC): @abstractmethod 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: pass @@ -1013,11 +1002,11 @@ class BillingSession(ABC): pass @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 @abstractmethod - def get_metadata_for_stripe_update_card(self) -> Dict[str, str]: + def get_metadata_for_stripe_update_card(self) -> dict[str, str]: pass @abstractmethod @@ -1142,7 +1131,7 @@ class BillingSession(ABC): def create_stripe_invoice_and_charge( self, - metadata: Dict[str, Any], + metadata: dict[str, Any], ) -> str: """ Charge customer based on `billing_modality`. If `billing_modality` is `charge_automatically`, @@ -1217,7 +1206,7 @@ class BillingSession(ABC): self, manual_license_management: bool, tier: int, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: metadata = self.get_metadata_for_stripe_update_card() customer = self.update_or_create_stripe_customer() assert customer.stripe_customer_id is not None @@ -1252,7 +1241,7 @@ class BillingSession(ABC): "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() customer = self.get_customer() 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) 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, "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 self.write_to_audit_log( event_type=AuditLogEventType.CUSTOMER_PLAN_PROPERTY_CHANGED, @@ -1942,7 +1931,7 @@ class BillingSession(ABC): 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() if customer is not None: self.ensure_current_plan_is_upgradable(customer, upgrade_request.tier) @@ -1977,7 +1966,7 @@ class BillingSession(ABC): "annual": CustomerPlan.BILLING_SCHEDULE_ANNUAL, "monthly": CustomerPlan.BILLING_SCHEDULE_MONTHLY, }[schedule] - data: Dict[str, Any] = {} + data: dict[str, Any] = {} is_self_hosted_billing = not isinstance(self, RealmBillingSession) free_trial = is_free_trial_offer_enabled(is_self_hosted_billing, upgrade_request.tier) @@ -2120,7 +2109,7 @@ class BillingSession(ABC): @transaction.atomic def make_end_of_cycle_updates_if_needed( self, plan: CustomerPlan, event_time: datetime - ) -> Tuple[Optional[CustomerPlan], Optional[LicenseLedger]]: + ) -> tuple[Optional[CustomerPlan], Optional[LicenseLedger]]: last_ledger_entry = ( LicenseLedger.objects.filter(plan=plan, event_time__lte=event_time) .order_by("-id") @@ -2338,7 +2327,7 @@ class BillingSession(ABC): plan: CustomerPlan, last_ledger_entry: LicenseLedger, now: datetime, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: 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_free_trial = plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL @@ -2483,7 +2472,7 @@ class BillingSession(ABC): } return context - def get_billing_page_context(self) -> Dict[str, Any]: + def get_billing_page_context(self) -> dict[str, Any]: now = timezone_now() customer = self.get_customer() @@ -2524,7 +2513,7 @@ class BillingSession(ABC): context[key] = next_plan_context[key] 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) flat_discount = 0 flat_discounted_months = 0 @@ -2542,7 +2531,7 @@ class BillingSession(ABC): def get_initial_upgrade_context( self, initial_upgrade_request: InitialUpgradeRequest - ) -> Tuple[Optional[str], Optional[UpgradePageContext]]: + ) -> tuple[Optional[str], Optional[UpgradePageContext]]: customer = self.get_customer() # 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 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() if customer is None: @@ -3261,7 +3250,7 @@ class BillingSession(ABC): 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() is_remotely_hosted = isinstance( self, (RemoteRealmBillingSession, RemoteServerBillingSession) @@ -3271,7 +3260,7 @@ class BillingSession(ABC): if is_remotely_hosted: plan_name = "Free" - context: Dict[str, Any] = { + context: dict[str, Any] = { "billing_base_url": self.billing_base_url, "is_remotely_hosted": 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, *, background_update: bool = False, - extra_data: Optional[Dict[str, Any]] = None, + extra_data: Optional[dict[str, Any]] = None, ) -> None: audit_log_event = self.get_audit_log_event(event_type) audit_log_data = { @@ -3859,7 +3848,7 @@ class RealmBillingSession(BillingSession): # Support requests do not set any stripe billing information. assert self.support_session is False assert self.user is not None - metadata: Dict[str, Any] = {} + metadata: dict[str, Any] = {} metadata["realm_id"] = self.realm.id metadata["realm_str"] = self.realm.string_id realm_stripe_customer_data = StripeCustomerData( @@ -3871,8 +3860,8 @@ class RealmBillingSession(BillingSession): @override def update_data_for_checkout_session_and_invoice_payment( - self, metadata: Dict[str, Any] - ) -> Dict[str, Any]: + self, metadata: dict[str, Any] + ) -> dict[str, Any]: assert self.user is not None updated_metadata = dict( user_email=self.get_email(), @@ -3885,7 +3874,7 @@ class RealmBillingSession(BillingSession): @override 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: if stripe_customer_id is not None: # 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 @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 return { "type": "card_update", @@ -4051,7 +4040,7 @@ class RealmBillingSession(BillingSession): return self.realm.name @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( realm_org_type=self.realm.org_type, sorted_org_types=sorted( @@ -4166,7 +4155,7 @@ class RemoteRealmBillingSession(BillingSession): # possible, in that the self-hosted server will have uploaded # current audit log data as needed as part of logging the user # in. - missing_data_context: Dict[str, Any] = { + missing_data_context: dict[str, Any] = { "remote_realm_session": True, "supports_remote_realms": self.remote_realm.server.last_api_feature_level is not None, } @@ -4216,7 +4205,7 @@ class RemoteRealmBillingSession(BillingSession): event_time: datetime, *, background_update: bool = False, - extra_data: Optional[Dict[str, Any]] = None, + extra_data: Optional[dict[str, Any]] = None, ) -> None: # 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: # Support requests do not set any stripe billing information. 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_host"] = str(self.remote_realm.host) realm_stripe_customer_data = StripeCustomerData( @@ -4262,8 +4251,8 @@ class RemoteRealmBillingSession(BillingSession): @override def update_data_for_checkout_session_and_invoice_payment( - self, metadata: Dict[str, Any] - ) -> Dict[str, Any]: + self, metadata: dict[str, Any] + ) -> dict[str, Any]: assert self.remote_billing_user is not None updated_metadata = dict( remote_realm_user_id=self.remote_billing_user.id, @@ -4275,7 +4264,7 @@ class RemoteRealmBillingSession(BillingSession): @override 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: if stripe_customer_id is not None: # 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 @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 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 @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( realm_org_type=self.remote_realm.org_type, sorted_org_types=sorted( @@ -4659,7 +4648,7 @@ class RemoteServerBillingSession(BillingSession): event_time: datetime, *, background_update: bool = False, - extra_data: Optional[Dict[str, Any]] = None, + extra_data: Optional[dict[str, Any]] = None, ) -> None: audit_log_event = self.get_audit_log_event(event_type) log_data = { @@ -4687,7 +4676,7 @@ class RemoteServerBillingSession(BillingSession): def get_data_for_stripe_customer(self) -> StripeCustomerData: # Support requests do not set any stripe billing information. 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_str"] = str(self.remote_server) realm_stripe_customer_data = StripeCustomerData( @@ -4699,8 +4688,8 @@ class RemoteServerBillingSession(BillingSession): @override def update_data_for_checkout_session_and_invoice_payment( - self, metadata: Dict[str, Any] - ) -> Dict[str, Any]: + self, metadata: dict[str, Any] + ) -> dict[str, Any]: assert self.remote_billing_user is not None updated_metadata = dict( remote_server_user_id=self.remote_billing_user.id, @@ -4712,7 +4701,7 @@ class RemoteServerBillingSession(BillingSession): @override 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: if stripe_customer_id is not None: # 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 @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 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 @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( realm_org_type=self.remote_server.org_type, sorted_org_types=sorted( @@ -5025,7 +5014,7 @@ def get_price_per_license( # We already have a set discounted price for the current tier. 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_PLUS: {"Annual": 12000, "Monthly": 1200}, 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( 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) if customer is None: return original_price_per_license, None @@ -5070,7 +5059,7 @@ def compute_plan_parameters( billing_cycle_anchor: Optional[datetime] = None, is_self_hosted_billing: 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, # so standardize on 1 second resolution. # 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.downgrade_now_without_creating_additional_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')}", "realm": realm, } diff --git a/corporate/models.py b/corporate/models.py index 2de314b70c..30d61d9903 100644 --- a/corporate/models.py +++ b/corporate/models.py @@ -1,5 +1,5 @@ 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.models import ContentType @@ -109,7 +109,7 @@ class Event(models.Model): 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["status"] = { Event.RECEIVED: "not_started", @@ -158,8 +158,8 @@ class Session(models.Model): Session.CARD_UPDATE_FROM_UPGRADE_PAGE: "card_update_from_upgrade_page", }[self.type] - def to_dict(self) -> Dict[str, Any]: - session_dict: Dict[str, Any] = {} + def to_dict(self) -> dict[str, Any]: + session_dict: dict[str, Any] = {} session_dict["status"] = self.get_status_as_string() session_dict["type"] = self.get_type_as_string() @@ -216,8 +216,8 @@ class PaymentIntent(models.Model): # nocoverage return None # nocoverage return get_last_associated_event_by_type(self, event_type) - def to_dict(self) -> Dict[str, Any]: - payment_intent_dict: Dict[str, Any] = {} + def to_dict(self) -> dict[str, Any]: + payment_intent_dict: dict[str, Any] = {} payment_intent_dict["status"] = self.get_status_as_string() event = self.get_last_associated_event() if event is not None: @@ -251,8 +251,8 @@ class Invoice(models.Model): return None # nocoverage return get_last_associated_event_by_type(self, event_type) - def to_dict(self) -> Dict[str, Any]: - stripe_invoice_dict: Dict[str, Any] = {} + def to_dict(self) -> dict[str, Any]: + stripe_invoice_dict: dict[str, Any] = {} stripe_invoice_dict["status"] = self.get_status_as_string() event = self.get_last_associated_event() if event is not None: diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py index eacbc6fbe4..63d1295b3a 100644 --- a/corporate/tests/test_stripe.py +++ b/corporate/tests/test_stripe.py @@ -14,14 +14,10 @@ from typing import ( TYPE_CHECKING, Any, Callable, - Dict, - List, Literal, Mapping, Optional, Sequence, - Tuple, - Type, TypeVar, Union, cast, @@ -139,7 +135,7 @@ def stripe_fixture_path( 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__ if decorated_function_name[:5] == "test_": decorated_function_name = decorated_function_name[5:] @@ -269,7 +265,7 @@ def normalize_fixture_data( 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): with open(fixture_file) as f: file_content = f.read() @@ -470,7 +466,7 @@ class StripeTestCase(ZulipTestCase): return "tok_visa_chargeDeclined" 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: json_response = self.client_billing_get( "/billing/event/status", @@ -484,7 +480,7 @@ class StripeTestCase(ZulipTestCase): def assert_details_of_valid_invoice_payment_from_event_status_endpoint( self, stripe_invoice_id: str, - expected_details: Dict[str, Any], + expected_details: dict[str, Any], ) -> None: json_response = self.client_billing_get( "/billing/event/status", @@ -626,7 +622,7 @@ class StripeTestCase(ZulipTestCase): upgrade_page_response = self.client_get(upgrade_url, {}, subdomain="selfhosting") else: upgrade_page_response = self.client_get(upgrade_url, {}) - params: Dict[str, Any] = { + params: dict[str, Any] = { "schedule": "annual", "signed_seat_count": self.get_signed_seat_count_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: 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 = [] assert customer.stripe_customer_id is not None for _ in range(num_invoices): @@ -4499,7 +4495,7 @@ class StripeTest(StripeTestCase): create_stripe_customer: bool, create_plan: bool, num_invoices: Optional[int] = None, - ) -> Tuple[Realm, Optional[CustomerPlan], List[stripe.Invoice]]: + ) -> tuple[Realm, Optional[CustomerPlan], list[stripe.Invoice]]: nonlocal test_realm_count test_realm_count += 1 realm_string_id = "test-realm-" + str(test_realm_count) @@ -4541,7 +4537,7 @@ class StripeTest(StripeTestCase): expected_invoice_count: int email_expected_to_be_sent: bool - rows: List[Row] = [] + rows: list[Row] = [] # no stripe customer ID (excluded from query) realm, _, _ = create_realm( @@ -4970,11 +4966,11 @@ class RequiresBillingAccessTest(StripeTestCase): tested_endpoints = set() def check_users_cant_access( - users: List[UserProfile], + users: list[UserProfile], error_message: str, url: str, method: str, - data: Dict[str, Any], + data: dict[str, Any], ) -> None: tested_endpoints.add(url) for user in users: @@ -6507,7 +6503,7 @@ class TestRemoteBillingWriteAuditLog(StripeTestCase): # Necessary cast or mypy doesn't understand that we can use Django's # model .objects. style queries on this. 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)) # No acting user: diff --git a/corporate/views/billing_page.py b/corporate/views/billing_page.py index 44d7e6a851..34f8af0926 100644 --- a/corporate/views/billing_page.py +++ b/corporate/views/billing_page.py @@ -1,5 +1,5 @@ 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.shortcuts import render @@ -55,7 +55,7 @@ def billing_page( billing_session = RealmBillingSession(user=user, realm=user.realm) - context: Dict[str, Any] = { + context: dict[str, Any] = { "admin_access": user.has_billing_access, "has_active_plan": False, "org_name": billing_session.org_name(), @@ -101,7 +101,7 @@ def remote_realm_billing_page( success_message: str = "", ) -> HttpResponse: 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. "admin_access": billing_session.has_billing_access(), "has_active_plan": False, @@ -161,7 +161,7 @@ def remote_server_billing_page( *, success_message: str = "", ) -> HttpResponse: - context: Dict[str, Any] = { + context: dict[str, Any] = { # We wouldn't be here if user didn't have access. "admin_access": billing_session.has_billing_access(), "has_active_plan": False, diff --git a/corporate/views/installation_activity.py b/corporate/views/installation_activity.py index d20ee91626..c544a909b8 100644 --- a/corporate/views/installation_activity.py +++ b/corporate/views/installation_activity.py @@ -1,5 +1,5 @@ from collections import defaultdict -from typing import Dict, Optional +from typing import Optional from django.conf import settings 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 -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 # get the start_time, since the hour that starts at midnight was # on the previous day. @@ -61,7 +61,7 @@ def get_realm_day_counts() -> Dict[str, Dict[str, Markup]]: rows = dictfetchall(cursor) cursor.close() - counts: Dict[str, Dict[int, int]] = defaultdict(dict) + counts: dict[str, dict[int, int]] = defaultdict(dict) for row in rows: counts[row["string_id"]][row["age"]] = row["cnt"] diff --git a/corporate/views/realm_activity.py b/corporate/views/realm_activity.py index 1cfcd9120b..6f289aa694 100644 --- a/corporate/views/realm_activity.py +++ b/corporate/views/realm_activity.py @@ -2,7 +2,7 @@ import itertools import re from dataclasses import dataclass 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.http import HttpRequest, HttpResponse, HttpResponseNotFound @@ -97,9 +97,9 @@ def get_user_activity_summary(records: Collection[UserActivity]) -> UserActivity 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: - user_records: Dict[str, UserActivitySummary] = {} + user_records: dict[str, UserActivitySummary] = {} def by_email(record: UserActivity) -> str: return record.user_profile.delivery_email @@ -141,7 +141,7 @@ def realm_user_summary_table( row = dict(cells=cells, row_class=row_class) 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] rows = sorted(rows, key=by_last_heard_from, reverse=True) diff --git a/corporate/views/remote_billing_page.py b/corporate/views/remote_billing_page.py index a01ef374ee..d30f633e84 100644 --- a/corporate/views/remote_billing_page.py +++ b/corporate/views/remote_billing_page.py @@ -1,5 +1,5 @@ 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 django.conf import settings @@ -526,7 +526,7 @@ def remote_billing_legacy_server_login( zulip_org_key: Optional[str] = None, next_page: VALID_NEXT_PAGES_TYPE = None, ) -> 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: context.update({"error_message": False}) return render(request, "corporate/billing/legacy_server_login.html", context) diff --git a/corporate/views/support.py b/corporate/views/support.py index dc1867feba..60283d2297 100644 --- a/corporate/views/support.py +++ b/corporate/views/support.py @@ -3,7 +3,7 @@ from contextlib import suppress from dataclasses import dataclass from datetime import timedelta 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 django import forms @@ -215,8 +215,8 @@ def get_plan_type_string(plan_type: int) -> str: def get_confirmations( - types: List[int], object_ids: Iterable[int], hostname: Optional[str] = None -) -> List[Dict[str, Any]]: + types: list[int], object_ids: Iterable[int], hostname: Optional[str] = None +) -> list[dict[str, Any]]: lowest_datetime = timezone_now() - timedelta(days=30) confirmations = Confirmation.objects.filter( type__in=types, object_id__in=object_ids, date_sent__gte=lowest_datetime @@ -265,7 +265,7 @@ class SupportSelectOption: value: int -def get_remote_plan_tier_options() -> List[SupportSelectOption]: +def get_remote_plan_tier_options() -> list[SupportSelectOption]: remote_plan_tiers = [ SupportSelectOption("None", 0), SupportSelectOption( @@ -280,7 +280,7 @@ def get_remote_plan_tier_options() -> List[SupportSelectOption]: return remote_plan_tiers -def get_realm_plan_type_options() -> List[SupportSelectOption]: +def get_realm_plan_type_options() -> list[SupportSelectOption]: plan_types = [ SupportSelectOption( 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 -def get_realm_plan_type_options_for_discount() -> List[SupportSelectOption]: +def get_realm_plan_type_options_for_discount() -> list[SupportSelectOption]: plan_types = [ SupportSelectOption("None", 0), SupportSelectOption( @@ -349,7 +349,7 @@ def support( query: Annotated[Optional[str], ApiParamConfig("q")] = None, org_type: Optional[Json[NonNegativeInt]] = None, ) -> HttpResponse: - context: Dict[str, Any] = {} + context: dict[str, Any] = {} if "success_message" in request.session: context["success_message"] = request.session["success_message"] @@ -499,7 +499,7 @@ def support( context["users"] = users context["realms"] = realms - confirmations: List[Dict[str, Any]] = [] + confirmations: list[dict[str, Any]] = [] preregistration_user_ids = [ user.id for user in PreregistrationUser.objects.filter(email__in=key_words) @@ -544,7 +544,7 @@ def support( ] + [user.realm for user in users] ) - realm_support_data: Dict[int, CloudSupportData] = {} + realm_support_data: dict[int, CloudSupportData] = {} for realm in all_realms: billing_session = RealmBillingSession(user=None, realm=realm) realm_data = get_data_for_cloud_support_view(billing_session) @@ -581,7 +581,7 @@ def support( def get_remote_servers_for_support( 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") if email_to_search: @@ -645,7 +645,7 @@ def remote_servers_support( delete_fixed_price_next_plan: Json[bool] = False, remote_server_status: Optional[VALID_STATUS_VALUES] = None, ) -> HttpResponse: - context: Dict[str, Any] = {} + context: dict[str, Any] = {} if "success_message" in request.session: context["success_message"] = request.session["success_message"] @@ -778,10 +778,10 @@ def remote_servers_support( uuid_to_search=uuid_to_search, hostname_to_search=hostname_to_search, ) - remote_server_to_max_monthly_messages: Dict[int, Union[int, str]] = dict() - server_support_data: Dict[int, RemoteSupportData] = {} - realm_support_data: Dict[int, RemoteSupportData] = {} - remote_realms: Dict[int, List[RemoteRealm]] = {} + remote_server_to_max_monthly_messages: dict[int, Union[int, str]] = dict() + server_support_data: dict[int, RemoteSupportData] = {} + realm_support_data: dict[int, RemoteSupportData] = {} + remote_realms: dict[int, list[RemoteRealm]] = {} for remote_server in remote_servers: # Get remote realms attached to remote server remote_realms_for_server = list( diff --git a/corporate/views/user_activity.py b/corporate/views/user_activity.py index b38647f795..c89fc49d4b 100644 --- a/corporate/views/user_activity.py +++ b/corporate/views/user_activity.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any from django.db.models import QuerySet from django.http import HttpRequest, HttpResponse @@ -43,7 +43,7 @@ def get_user_activity(request: HttpRequest, user_profile_id: int) -> HttpRespons "Last visit (UTC)", ] - def row(record: UserActivity) -> List[Any]: + def row(record: UserActivity) -> list[Any]: return [ record.query, record.client.name, diff --git a/manage.py b/manage.py index 271db6d23b..8139db3450 100755 --- a/manage.py +++ b/manage.py @@ -3,7 +3,7 @@ import configparser import os import sys from collections import defaultdict -from typing import Dict, List, Optional +from typing import Optional BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 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 -def get_filtered_commands() -> Dict[str, str]: +def get_filtered_commands() -> dict[str, str]: """Because Zulip uses management commands in production, `manage.py help` is a form of documentation for users. Here we exclude from that documentation built-in commands that are not constructive for @@ -110,7 +110,7 @@ class FilteredManagementUtility(ManagementUtility): 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.""" utility = FilteredManagementUtility(argv) utility.execute() diff --git a/puppet/kandra/files/memcached_exporter b/puppet/kandra/files/memcached_exporter index 5f0ca97ce7..34504e16c2 100755 --- a/puppet/kandra/files/memcached_exporter +++ b/puppet/kandra/files/memcached_exporter @@ -3,7 +3,7 @@ import contextlib import sys 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") from scripts.lib.setup_path import setup_path @@ -47,7 +47,7 @@ class MemcachedCollector(Collector): name: str, doc: str, value: Union[bytes, float], - labels: Optional[Dict[str, str]] = None, + labels: Optional[dict[str, str]] = None, ) -> CounterMetricFamily: if labels is None: labels = {} @@ -63,7 +63,7 @@ class MemcachedCollector(Collector): ) return metric - cache: Dict[str, Any] = settings.CACHES["default"] + cache: dict[str, Any] = settings.CACHES["default"] client = None with contextlib.suppress(Exception): client = bmemcached.Client((cache["LOCATION"],), **cache["OPTIONS"]) @@ -73,7 +73,7 @@ class MemcachedCollector(Collector): return 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", "The version of this memcached server.", labels=["version"] diff --git a/puppet/kandra/files/nagios_plugins/zulip_zephyr_mirror/check_personal_zephyr_mirrors b/puppet/kandra/files/nagios_plugins/zulip_zephyr_mirror/check_personal_zephyr_mirrors index df87f86097..f03560e281 100755 --- a/puppet/kandra/files/nagios_plugins/zulip_zephyr_mirror/check_personal_zephyr_mirrors +++ b/puppet/kandra/files/nagios_plugins/zulip_zephyr_mirror/check_personal_zephyr_mirrors @@ -12,11 +12,11 @@ mirrors when they receive the messages sent every minute by import os import sys import time -from typing import Dict, NoReturn +from typing import NoReturn RESULTS_DIR: str = "/home/zulip/mirror_status" -states: Dict[str, int] = { +states: dict[str, int] = { "OK": 0, "WARNING": 1, "CRITICAL": 2, diff --git a/puppet/kandra/files/nagios_plugins/zulip_zephyr_mirror/check_zephyr_mirror b/puppet/kandra/files/nagios_plugins/zulip_zephyr_mirror/check_zephyr_mirror index ce7ef8f202..c346542661 100755 --- a/puppet/kandra/files/nagios_plugins/zulip_zephyr_mirror/check_zephyr_mirror +++ b/puppet/kandra/files/nagios_plugins/zulip_zephyr_mirror/check_zephyr_mirror @@ -13,11 +13,11 @@ See puppet/kandra/files/cron.d/zephyr-mirror for the crontab details. import os import sys import time -from typing import Dict, NoReturn +from typing import NoReturn RESULTS_FILE = "/var/lib/nagios_state/check-mirroring-results" -states: Dict[str, int] = { +states: dict[str, int] = { "OK": 0, "WARNING": 1, "CRITICAL": 2, diff --git a/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_cron_file b/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_cron_file index ceddc43564..2bfeab89d3 100755 --- a/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_cron_file +++ b/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_cron_file @@ -6,10 +6,9 @@ file output by the cron job is correct. import sys 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 parsing the desired file on disk. The file on disk should be of format diff --git a/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_send_receive_time b/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_send_receive_time index dd62dc98f4..e32594c07a 100755 --- a/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_send_receive_time +++ b/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_send_receive_time @@ -13,7 +13,7 @@ import random import sys import time import traceback -from typing import Any, Dict, List, Literal, NoReturn, Optional +from typing import Any, Literal, NoReturn, Optional sys.path.append(".") sys.path.append("/home/zulip/deployments/current") @@ -58,13 +58,13 @@ def report( 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) if result["result"] != "success": 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 res = zulip_recipient.get_events(queue_id=queue_id, last_event_id=last_event_id) 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: messages = get_zulips() diff --git a/puppet/zulip/files/nagios_plugins/zulip_postgresql/check_postgresql_replication_lag b/puppet/zulip/files/nagios_plugins/zulip_postgresql/check_postgresql_replication_lag index 443324dc4d..41e6c5c70f 100755 --- a/puppet/zulip/files/nagios_plugins/zulip_postgresql/check_postgresql_replication_lag +++ b/puppet/zulip/files/nagios_plugins/zulip_postgresql/check_postgresql_replication_lag @@ -11,7 +11,6 @@ import configparser import re import subprocess import sys -from typing import Dict, List def get_config( @@ -44,7 +43,7 @@ def report(state: str, msg: str) -> None: MAXSTATE = max(MAXSTATE, states[state]) -def run_sql_query(query: str) -> List[List[str]]: +def run_sql_query(query: str) -> list[list[str]]: command = [ "psql", "-t", # Omit header line @@ -130,7 +129,7 @@ else: report("CRITICAL", f"replica {client_addr} is in state {state}, not streaming") 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["flush"] = sent_offset - loc_to_abs_offset(flush_lsn) lag["replay"] = sent_offset - loc_to_abs_offset(replay_lsn) diff --git a/puppet/zulip/files/postgresql/wal-g-exporter b/puppet/zulip/files/postgresql/wal-g-exporter index b5fb1bef52..8795e61c65 100755 --- a/puppet/zulip/files/postgresql/wal-g-exporter +++ b/puppet/zulip/files/postgresql/wal-g-exporter @@ -9,7 +9,7 @@ import sys from collections import defaultdict from datetime import datetime, timedelta, timezone 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 @@ -21,8 +21,8 @@ class GaugeMetric(Protocol): class WalGPrometheusServer(BaseHTTPRequestHandler): METRIC_PREFIX = "wal_g_backup_" - metrics: Dict[str, List[str]] = {} - metric_values: Dict[str, Dict[str, str]] = defaultdict(dict) + metrics: dict[str, list[str]] = {} + metric_values: dict[str, dict[str, str]] = defaultdict(dict) 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_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) backup_earliest_age_seconds( diff --git a/scripts/lib/check_rabbitmq_queue.py b/scripts/lib/check_rabbitmq_queue.py index ee6ed8ddf6..4359637908 100644 --- a/scripts/lib/check_rabbitmq_queue.py +++ b/scripts/lib/check_rabbitmq_queue.py @@ -5,7 +5,7 @@ import subprocess import sys import time 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__)))) @@ -43,14 +43,14 @@ states = { 3: "UNKNOWN", } -MAX_SECONDS_TO_CLEAR: DefaultDict[str, int] = defaultdict( +MAX_SECONDS_TO_CLEAR: defaultdict[str, int] = defaultdict( lambda: 30, deferred_work=600, digest_emails=1200, missedmessage_mobile_notifications=120, embed_links=60, ) -CRITICAL_SECONDS_TO_CLEAR: DefaultDict[str, int] = defaultdict( +CRITICAL_SECONDS_TO_CLEAR: defaultdict[str, int] = defaultdict( lambda: 60, deferred_work=900, missedmessage_mobile_notifications=180, @@ -60,8 +60,8 @@ CRITICAL_SECONDS_TO_CLEAR: DefaultDict[str, int] = defaultdict( def analyze_queue_stats( - queue_name: str, stats: Dict[str, Any], queue_count_rabbitmqctl: int -) -> Dict[str, Any]: + queue_name: str, stats: dict[str, Any], queue_count_rabbitmqctl: int +) -> dict[str, Any]: now = int(time.time()) if stats == {}: 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 -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.""" results = [] @@ -161,7 +161,7 @@ def check_rabbitmq_queues() -> None: [os.path.join(ZULIP_PATH, "scripts/get-django-setting"), "QUEUE_STATS_DIR"], text=True, ).strip() - queue_stats: Dict[str, Dict[str, Any]] = {} + queue_stats: dict[str, dict[str, Any]] = {} check_queues = normal_queues if mobile_notification_shards > 1: check_queues += [ diff --git a/scripts/lib/clean_emoji_cache.py b/scripts/lib/clean_emoji_cache.py index 6ad2d3c23a..12d378bf00 100755 --- a/scripts/lib/clean_emoji_cache.py +++ b/scripts/lib/clean_emoji_cache.py @@ -2,7 +2,6 @@ import argparse import os import sys -from typing import Set ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ZULIP_PATH) @@ -17,7 +16,7 @@ ENV = get_environment() 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} caches_in_use = set() diff --git a/scripts/lib/clean_node_cache.py b/scripts/lib/clean_node_cache.py index 79242358e4..8d677ca3af 100755 --- a/scripts/lib/clean_node_cache.py +++ b/scripts/lib/clean_node_cache.py @@ -7,7 +7,6 @@ import argparse import os import sys -from typing import Set ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ZULIP_PATH) @@ -22,7 +21,7 @@ ENV = get_environment() 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} caches_in_use = set() diff --git a/scripts/lib/clean_venv_cache.py b/scripts/lib/clean_venv_cache.py index 2698d9e2e8..1f9fd1437f 100755 --- a/scripts/lib/clean_venv_cache.py +++ b/scripts/lib/clean_venv_cache.py @@ -3,7 +3,6 @@ import argparse import glob import os import sys -from typing import Set ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ZULIP_PATH) @@ -19,7 +18,7 @@ ENV = get_environment() 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} caches_in_use = set() diff --git a/scripts/lib/hash_reqs.py b/scripts/lib/hash_reqs.py index 68c11bb040..65b0024fae 100755 --- a/scripts/lib/hash_reqs.py +++ b/scripts/lib/hash_reqs.py @@ -4,11 +4,11 @@ import hashlib import os import subprocess import sys -from typing import Iterable, List +from typing import Iterable -def expand_reqs_helper(fpath: str) -> List[str]: - result: List[str] = [] +def expand_reqs_helper(fpath: str) -> list[str]: + result: list[str] = [] with open(fpath) as f: for line in f: @@ -20,7 +20,7 @@ def expand_reqs_helper(fpath: str) -> List[str]: 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`. Removes comments from the output and recursively visits files specified inside `fpath`. diff --git a/scripts/lib/setup_venv.py b/scripts/lib/setup_venv.py index d4f35f6008..bf95663b5e 100644 --- a/scripts/lib/setup_venv.py +++ b/scripts/lib/setup_venv.py @@ -2,7 +2,7 @@ import logging import os import shutil 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.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(): return VENV_DEPENDENCIES 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") -def get_package_names(requirements_file: str) -> List[str]: +def get_package_names(requirements_file: str) -> list[str]: packages = expand_reqs(requirements_file) cleaned = [] operators = ["~=", "==", "!=", "<", ">"] @@ -132,7 +132,7 @@ def create_requirements_index_file(venv_path: str, requirements_file: str) -> st 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 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()} -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 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() venv_name = os.path.basename(venv_path) - overlaps: List[Tuple[int, str, Set[str]]] = [] - old_packages: Set[str] = set() + overlaps: list[tuple[int, str, set[str]]] = [] + old_packages: set[str] = set() for sha1sum in os.listdir(VENV_CACHE_PATH): 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)): @@ -230,8 +230,8 @@ def get_logfile_name(venv_path: str) -> str: def create_log_entry( target_log: str, parent: str, - copied_packages: Set[str], - new_packages: Set[str], + copied_packages: set[str], + new_packages: set[str], ) -> None: venv_path = os.path.dirname(target_log) with open(target_log, "a") as writer: diff --git a/scripts/lib/sharding.py b/scripts/lib/sharding.py index 6107a54d36..c8b6601075 100755 --- a/scripts/lib/sharding.py +++ b/scripts/lib/sharding.py @@ -5,7 +5,7 @@ import json import os import subprocess 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__)))) 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(" default http://tornado9800;\n") - shard_map: Dict[str, Union[int, List[int]]] = {} - shard_regexes: List[Tuple[str, Union[int, List[int]]]] = [] + shard_map: dict[str, Union[int, list[int]]] = {} + shard_regexes: list[tuple[str, Union[int, list[int]]]] = [] external_host = subprocess.check_output( [os.path.join(BASE_DIR, "scripts/get-django-setting"), "EXTERNAL_HOST"], text=True, diff --git a/scripts/lib/supervisor.py b/scripts/lib/supervisor.py index aee690c0e1..ee64067c93 100644 --- a/scripts/lib/supervisor.py +++ b/scripts/lib/supervisor.py @@ -1,7 +1,7 @@ import socket import time from http.client import HTTPConnection -from typing import Dict, List, Optional, Tuple, Union +from typing import Optional, Union from xmlrpc import client from typing_extensions import override @@ -33,7 +33,7 @@ class UnixStreamTransport(client.Transport): @override def make_connection( - self, host: Union[Tuple[str, Dict[str, str]], str] + self, host: Union[tuple[str, dict[str, str]], str] ) -> UnixStreamHTTPConnection: return UnixStreamHTTPConnection(self.socket_path) @@ -45,8 +45,8 @@ def rpc() -> client.ServerProxy: def list_supervisor_processes( - filter_names: Optional[List[str]] = None, *, only_running: Optional[bool] = None -) -> List[str]: + filter_names: Optional[list[str]] = None, *, only_running: Optional[bool] = None +) -> list[str]: results = [] processes = rpc().supervisor.getAllProcessInfo() assert isinstance(processes, list) diff --git a/scripts/lib/zulip_tools.py b/scripts/lib/zulip_tools.py index 38a7fff3b5..77f594b1a9 100755 --- a/scripts/lib/zulip_tools.py +++ b/scripts/lib/zulip_tools.py @@ -16,7 +16,7 @@ import sys import time import uuid 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 import zoneinfo @@ -300,7 +300,7 @@ def get_environment() -> str: 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 # including `/root/zulip` directory if it exists. recent = set() @@ -337,8 +337,8 @@ def get_threshold_timestamp(threshold_days: int) -> int: def get_caches_to_be_purged( - caches_dir: str, caches_in_use: Set[str], threshold_days: int -) -> Set[str]: + caches_dir: str, caches_in_use: set[str], threshold_days: int +) -> set[str]: # Given a directory containing caches, a list of caches in use # and threshold days, this function return a list of caches # 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( caches_dir: str, - caches_in_use: Set[str], + caches_in_use: set[str], cache_type: str, args: argparse.Namespace, ) -> None: @@ -405,8 +405,8 @@ def generate_sha1sum_emoji(zulip_path: str) -> str: def maybe_perform_purging( - dirs_to_purge: Set[str], - dirs_to_keep: Set[str], + dirs_to_purge: set[str], + dirs_to_keep: set[str], dir_type: str, dry_run: bool, verbose: bool, @@ -429,7 +429,7 @@ def maybe_perform_purging( @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: { @@ -444,7 +444,7 @@ def parse_os_release() -> Dict[str, str]: developers, but we avoid using it, as it is not available on RHEL-based platforms. """ - distro_info: Dict[str, str] = {} + distro_info: dict[str, str] = {} with open("/etc/os-release") as fp: for line in fp: line = line.strip() @@ -458,7 +458,7 @@ def parse_os_release() -> Dict[str, str]: @functools.lru_cache(None) -def os_families() -> Set[str]: +def os_families() -> set[str]: """ Known families: debian (includes: debian, ubuntu) @@ -548,7 +548,7 @@ def is_root() -> bool: 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", []) if not is_root(): args = ["sudo", *sudo_args, "--", *args] @@ -619,7 +619,7 @@ def get_config_file() -> configparser.RawConfigParser: 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", "")) @@ -632,7 +632,7 @@ def run_psql_as_postgres( 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 = [] if config_file.has_section("tornado_sharding"): ports = sorted( @@ -705,7 +705,7 @@ def start_arg_parser(action: str, add_help: bool = False) -> argparse.ArgumentPa 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}" # Parse lines that look like this: # tcp LISTEN 0 128 0.0.0.0:25672 0.0.0.0:* diff --git a/scripts/log-search b/scripts/log-search index 0bc488d29c..653bfaaa33 100755 --- a/scripts/log-search +++ b/scripts/log-search @@ -10,7 +10,7 @@ import signal import sys from datetime import date, datetime, timedelta, timezone 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__))) sys.path.append(ZULIP_PATH) @@ -242,7 +242,7 @@ def main() -> None: 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: base_path = "/var/log/nginx/access.log" else: @@ -311,7 +311,7 @@ def convert_to_nginx_date(date: str) -> str: def parse_filters( 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 # certainly count things as "IPv4" or "IPv6" addresses that are # invalid. However, we expect the input here to already be @@ -397,7 +397,7 @@ def parse_filters( def passes_filters( - string_filters: List[FilterFunc], + string_filters: list[FilterFunc], match: Match[str], args: argparse.Namespace, ) -> bool: @@ -434,7 +434,7 @@ last_match_end: Optional[datetime] = None def print_line( match: Match[str], args: argparse.Namespace, - filter_types: Set[FilterType], + filter_types: set[FilterType], use_color: bool, ) -> None: global last_match_end diff --git a/scripts/nagios/check-rabbitmq-consumers b/scripts/nagios/check-rabbitmq-consumers index 37f001703f..34c72dea2a 100755 --- a/scripts/nagios/check-rabbitmq-consumers +++ b/scripts/nagios/check-rabbitmq-consumers @@ -6,7 +6,6 @@ import subprocess import sys import time from collections import defaultdict -from typing import Dict ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 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) -consumers: Dict[str, int] = defaultdict(int) +consumers: dict[str, int] = defaultdict(int) queues = { *normal_queues, diff --git a/scripts/purge-old-deployments b/scripts/purge-old-deployments index ec6dbca2fa..a4c5fb4bd4 100755 --- a/scripts/purge-old-deployments +++ b/scripts/purge-old-deployments @@ -3,7 +3,6 @@ import argparse import os import subprocess import sys -from typing import Set ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(ZULIP_PATH) @@ -56,7 +55,7 @@ def parse_args() -> argparse.Namespace: 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 = { os.path.join(DEPLOYMENTS_DIR, deployment) for deployment in os.listdir(DEPLOYMENTS_DIR) } diff --git a/scripts/setup/generate_secrets.py b/scripts/setup/generate_secrets.py index 2d1a48ad66..e5513a2902 100755 --- a/scripts/setup/generate_secrets.py +++ b/scripts/setup/generate_secrets.py @@ -3,7 +3,6 @@ import os import sys from contextlib import suppress -from typing import Dict, List BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(BASE_DIR) @@ -62,7 +61,7 @@ def generate_django_secretkey() -> str: 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: return {} @@ -79,7 +78,7 @@ def generate_secrets(development: bool = False) -> None: OUTPUT_SETTINGS_FILENAME = "/etc/zulip/zulip-secrets.conf" current_conf = get_old_conf(OUTPUT_SETTINGS_FILENAME) - lines: List[str] = [] + lines: list[str] = [] if len(current_conf) == 0: lines = ["[secrets]\n"] diff --git a/scripts/zulip-puppet-apply b/scripts/zulip-puppet-apply index 5748f6e261..c6ba797347 100755 --- a/scripts/zulip-puppet-apply +++ b/scripts/zulip-puppet-apply @@ -6,7 +6,6 @@ import re import subprocess import sys import tempfile -from typing import List import yaml @@ -64,7 +63,7 @@ puppet_env["FACTER_zulip_conf_path"] = args.config 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 # https://tickets.puppetlabs.com/browse/PUP-686 try: diff --git a/tools/check-frontend-i18n b/tools/check-frontend-i18n index 27e9043009..5656a3584c 100755 --- a/tools/check-frontend-i18n +++ b/tools/check-frontend-i18n @@ -4,7 +4,6 @@ import json import os import subprocess import sys -from typing import List 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 -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] diff --git a/tools/check-issue-labels b/tools/check-issue-labels index 46765d71e7..081d5487d9 100755 --- a/tools/check-issue-labels +++ b/tools/check-issue-labels @@ -4,7 +4,7 @@ import configparser import os import re import sys -from typing import Any, Dict, List, Optional +from typing import Any, Optional import requests @@ -27,7 +27,7 @@ def get_config() -> configparser.ConfigParser: return config -def area_labeled(issue: Dict[str, Any]) -> bool: +def area_labeled(issue: dict[str, Any]) -> bool: for label in issue["labels"]: label_name = str(label["name"]) if "area:" in label_name: @@ -35,7 +35,7 @@ def area_labeled(issue: Dict[str, Any]) -> bool: return False -def is_issue(item: Dict[str, Any]) -> bool: +def is_issue(item: dict[str, Any]) -> bool: return "issues" in item["html_url"] @@ -64,7 +64,7 @@ def check_issue_labels() -> None: sys.exit(1) 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: try: if args.force: diff --git a/tools/check-schemas b/tools/check-schemas index 467e5a928a..2b2a18d857 100755 --- a/tools/check-schemas +++ b/tools/check-schemas @@ -14,7 +14,7 @@ import difflib import os import subprocess import sys -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable, Optional 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"] if "op" in event: name += "_" + event["op"] @@ -99,7 +99,7 @@ def get_event_checker(event: Dict[str, Any]) -> Optional[Callable[[str, Dict[str 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 checker = get_event_checker(event) 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}") -def read_fixtures() -> Dict[str, Any]: +def read_fixtures() -> dict[str, Any]: cmd = [ "node", os.path.join(TOOLS_DIR, "node_lib/dump_fixtures.js"), @@ -121,7 +121,7 @@ def read_fixtures() -> Dict[str, Any]: 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)): if names[i] < names[i - 1]: 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 definitions for convenient comparison with the types used for backend tests declared there.""" diff --git a/tools/check-templates b/tools/check-templates index ebd1772b33..19d09539b9 100755 --- a/tools/check-templates +++ b/tools/check-templates @@ -11,7 +11,7 @@ from tools.lib import sanity_check sanity_check.check_venv(__file__) -from typing import Dict, Iterable, List +from typing import Iterable 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( targets=targets, 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: 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) # TODO: Clean up these cases of duplicate ids in the code IGNORE_IDS = [ diff --git a/tools/diagnose b/tools/diagnose index b29375b55c..b805a29978 100755 --- a/tools/diagnose +++ b/tools/diagnose @@ -4,7 +4,7 @@ import platform import shlex import subprocess import sys -from typing import Callable, List +from typing import Callable TOOLS_DIR = os.path.dirname(__file__) ROOT_DIR = os.path.dirname(TOOLS_DIR) @@ -24,7 +24,7 @@ def run(check_func: Callable[[], bool]) -> None: sys.exit(1) -def run_command(args: List[str]) -> None: +def run_command(args: list[str]) -> None: print(shlex.join(args)) subprocess.check_call(args) diff --git a/tools/documentation_crawler/documentation_crawler/commands/crawl_with_status.py b/tools/documentation_crawler/documentation_crawler/commands/crawl_with_status.py index 010af602b1..184df7fa04 100644 --- a/tools/documentation_crawler/documentation_crawler/commands/crawl_with_status.py +++ b/tools/documentation_crawler/documentation_crawler/commands/crawl_with_status.py @@ -1,12 +1,12 @@ import optparse -from typing import List, Union +from typing import Union from scrapy.commands import crawl from scrapy.crawler import Crawler 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 = [] real_create_crawler = self.crawler_process.create_crawler diff --git a/tools/documentation_crawler/documentation_crawler/spiders/check_documentation.py b/tools/documentation_crawler/documentation_crawler/spiders/check_documentation.py index b72dae992b..4dc8c91792 100644 --- a/tools/documentation_crawler/documentation_crawler/spiders/check_documentation.py +++ b/tools/documentation_crawler/documentation_crawler/spiders/check_documentation.py @@ -1,11 +1,10 @@ import os import pathlib -from typing import List 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 dir_path = os.path.dirname(os.path.realpath(__file__)) start_file = os.path.join( diff --git a/tools/documentation_crawler/documentation_crawler/spiders/check_help_documentation.py b/tools/documentation_crawler/documentation_crawler/spiders/check_help_documentation.py index 0e63c094cd..7ebf4eb0ed 100644 --- a/tools/documentation_crawler/documentation_crawler/spiders/check_help_documentation.py +++ b/tools/documentation_crawler/documentation_crawler/spiders/check_help_documentation.py @@ -1,6 +1,6 @@ import os from posixpath import basename -from typing import Any, List, Set +from typing import Any from urllib.parse import urlsplit from typing_extensions import override @@ -20,7 +20,7 @@ class UnusedImagesLinterSpider(BaseDocumentationSpider): def __init__(self, *args: Any, **kwargs: Any) -> None: 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) @override @@ -45,7 +45,7 @@ class UnusedImagesLinterSpider(BaseDocumentationSpider): class HelpDocumentationSpider(UnusedImagesLinterSpider): name = "help_documentation_crawler" start_urls = ["http://localhost:9981/help/"] - deny_domains: List[str] = [] + deny_domains: list[str] = [] deny = ["/policies/privacy"] images_path = "static/images/help" @@ -53,7 +53,7 @@ class HelpDocumentationSpider(UnusedImagesLinterSpider): class APIDocumentationSpider(UnusedImagesLinterSpider): name = "api_documentation_crawler" start_urls = ["http://localhost:9981/api"] - deny_domains: List[str] = [] + deny_domains: list[str] = [] images_path = "static/images/api" @@ -84,4 +84,4 @@ class PorticoDocumentationSpider(BaseDocumentationSpider): "http://localhost:9981/for/research/", "http://localhost:9981/security/", ] - deny_domains: List[str] = [] + deny_domains: list[str] = [] diff --git a/tools/documentation_crawler/documentation_crawler/spiders/common/spiders.py b/tools/documentation_crawler/documentation_crawler/spiders/common/spiders.py index fae3e521b8..7816b4f105 100644 --- a/tools/documentation_crawler/documentation_crawler/spiders/common/spiders.py +++ b/tools/documentation_crawler/documentation_crawler/spiders/common/spiders.py @@ -1,7 +1,7 @@ import json import os import re -from typing import Callable, Iterator, List, Optional, Union +from typing import Callable, Iterator, Optional, Union from urllib.parse import urlsplit import scrapy @@ -60,10 +60,10 @@ ZULIP_SERVER_GITHUB_DIRECTORY_PATH_PREFIX = "/zulip/zulip/tree/main" class BaseDocumentationSpider(scrapy.Spider): name: Optional[str] = None # Exclude domain address. - deny_domains: List[str] = [] - start_urls: List[str] = [] - deny: List[str] = [] - file_extensions: List[str] = ["." + ext for ext in IGNORED_EXTENSIONS] + deny_domains: list[str] = [] + start_urls: list[str] = [] + deny: list[str] = [] + file_extensions: list[str] = ["." + ext for ext in IGNORED_EXTENSIONS] tags = ("a", "area", "img") attrs = ("href", "src") diff --git a/tools/droplets/add_mentor.py b/tools/droplets/add_mentor.py index 6f113ec79a..942e5c1306 100644 --- a/tools/droplets/add_mentor.py +++ b/tools/droplets/add_mentor.py @@ -14,7 +14,6 @@ import re import socket import sys from argparse import ArgumentParser -from typing import List 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" r = requests.get(url) diff --git a/tools/droplets/create.py b/tools/droplets/create.py index ad71bea6d9..d5e59a771c 100644 --- a/tools/droplets/create.py +++ b/tools/droplets/create.py @@ -20,7 +20,7 @@ import sys import time import urllib.error import urllib.request -from typing import Any, Dict, List, Tuple +from typing import Any import digitalocean import requests @@ -56,7 +56,7 @@ def assert_github_user_exists(github_username: str) -> bool: 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...") apiurl_keys = f"https://api.github.com/users/{github_username}/keys" try: @@ -108,12 +108,12 @@ def assert_droplet_does_not_exist(my_token: str, droplet_name: str, recreate: bo 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) 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: 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" @@ -159,7 +159,7 @@ su -c 'git config --global pull.rebase true' zulipdev 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) setup_root_ssh_keys = f"printf '{ssh_keys_string}' > /root/.ssh/authorized_keys" @@ -179,10 +179,10 @@ def create_droplet( my_token: str, template_id: str, name: str, - tags: List[str], + tags: list[str], user_data: str, region: str = "nyc3", -) -> Tuple[str, str]: +) -> tuple[str, str]: droplet = digitalocean.Droplet( token=my_token, name=name, @@ -215,7 +215,7 @@ def create_droplet( 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 for record in records: if ( diff --git a/tools/fetch-contributor-data b/tools/fetch-contributor-data index 98c78167b8..4346fa9f94 100755 --- a/tools/fetch-contributor-data +++ b/tools/fetch-contributor-data @@ -11,7 +11,7 @@ import os import sys import unicodedata 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__), "..")) from scripts.lib.setup_path import setup_path @@ -42,7 +42,7 @@ args = parser.parse_args() class ContributorsJSON(TypedDict): date: str - contributors: List[Dict[str, Union[int, str]]] + contributors: list[dict[str, Union[int, str]]] class Contributor(TypedDict): @@ -56,15 +56,15 @@ class Contributor(TypedDict): logger = logging.getLogger("zulip.fetch_contributors_json") -def fetch_contributors(repo_name: str, max_retries: int) -> List[Contributor]: - contributors: List[Contributor] = [] +def fetch_contributors(repo_name: str, max_retries: int) -> list[Contributor]: + contributors: list[Contributor] = [] page_index = 1 api_link = f"https://api.github.com/repos/zulip/{repo_name}/contributors" api_data = {"anon": "1"} certificates = os.environ.get("CUSTOM_CA_CERTIFICATES") - headers: Dict[str, str] = {} + headers: dict[str, str] = {} personal_access_token = get_secret("github_personal_access_token") if personal_access_token is not None: 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=[]) - 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: contributors = fetch_contributors(repo_name, args.max_retries) diff --git a/tools/i18n/process-mobile-i18n b/tools/i18n/process-mobile-i18n index 64b43a5167..f09bc8ba10 100755 --- a/tools/i18n/process-mobile-i18n +++ b/tools/i18n/process-mobile-i18n @@ -3,14 +3,13 @@ import json import os import re from subprocess import check_output -from typing import Dict, List def get_json_filename(locale: str) -> str: 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) tracked_files = output.split() regex = re.compile(r"locale/(\w+)/LC_MESSAGES/django.po") @@ -23,7 +22,7 @@ def get_locales() -> List[str]: 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: 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} -translation_stats: Dict[str, Dict[str, int]] = {} +translation_stats: dict[str, dict[str, int]] = {} locale_paths = [] # List[str] for locale in get_locales(): path = get_json_filename(locale) diff --git a/tools/i18n/unescape-contents b/tools/i18n/unescape-contents index 3b7933a073..1fd3436275 100755 --- a/tools/i18n/unescape-contents +++ b/tools/i18n/unescape-contents @@ -4,7 +4,7 @@ import argparse import html import json import sys -from typing import Dict, NamedTuple +from typing import NamedTuple class CLIArgs(NamedTuple): @@ -36,7 +36,7 @@ if __name__ == "__main__": args = parse_args() 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: json_data = json.load(source) diff --git a/tools/i18n/update-for-legacy-translations b/tools/i18n/update-for-legacy-translations index 232bff7836..15aaef8652 100755 --- a/tools/i18n/update-for-legacy-translations +++ b/tools/i18n/update-for-legacy-translations @@ -3,7 +3,6 @@ import json import os import re from subprocess import check_output -from typing import Dict, List LEGACY_STRINGS_MAP = { "

You are searching for messages that belong to more than one channel, which is not possible.

": "

You are searching for messages that belong to more than one stream, which is not possible.

", @@ -187,7 +186,7 @@ def get_legacy_filename(locale: str) -> str: 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) tracked_files = output.split() regex = re.compile(r"locale/(\w+)/LC_MESSAGES/django.po") @@ -200,7 +199,7 @@ def get_locales() -> List[str]: 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: translations = json.load(raw_resource_file) @@ -208,10 +207,10 @@ def get_translations(path: str) -> Dict[str, str]: 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: number_of_updates = 0 - updated_translations: Dict[str, str] = {} + updated_translations: dict[str, str] = {} for line in current: # If the string has a legacy string mapped and see if it's # not currently translated (e.g. an empty string), then use diff --git a/tools/lib/capitalization.py b/tools/lib/capitalization.py index ca91936269..ccfe6b5a1b 100644 --- a/tools/lib/capitalization.py +++ b/tools/lib/capitalization.py @@ -1,5 +1,5 @@ import re -from typing import List, Match, Tuple +from typing import Match 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) -def check_banned_words(text: str) -> List[str]: +def check_banned_words(text: str) -> list[str]: lower_cased_text = text.lower() errors = [] for word, reason in BANNED_WORDS.items(): @@ -266,7 +266,7 @@ def check_banned_words(text: str) -> List[str]: 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 = [] ignored = [] banned_word_errors = [] diff --git a/tools/lib/gitlint_rules.py b/tools/lib/gitlint_rules.py index 7739ceb2c7..5663c35d2e 100644 --- a/tools/lib/gitlint_rules.py +++ b/tools/lib/gitlint_rules.py @@ -1,5 +1,3 @@ -from typing import List - from gitlint.git import GitCommit from gitlint.rules import CommitMessageTitle, LineRule, RuleViolation @@ -87,7 +85,7 @@ class ImperativeMood(LineRule): '("{word}" -> "{imperative}"): "{title}"' ) - def validate(self, line: str, commit: GitCommit) -> List[RuleViolation]: + def validate(self, line: str, commit: GitCommit) -> list[RuleViolation]: violations = [] # Ignore the section tag (ie `
: .`) diff --git a/tools/lib/html_branches.py b/tools/lib/html_branches.py index 1bc1528ca3..fd751aac00 100644 --- a/tools/lib/html_branches.py +++ b/tools/lib/html_branches.py @@ -1,12 +1,11 @@ import re from collections import defaultdict -from typing import Dict, List from .template_parser import FormattedError, Token, tokenize 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.classes = classes self.ids = ids @@ -29,8 +28,8 @@ class TagInfo: def get_tag_info(token: Token) -> TagInfo: s = token.s tag = token.tag - classes: List[str] = [] - ids: List[str] = [] + classes: list[str] = [] + ids: list[str] = [] searches = [ (classes, ' class="(.*?)"'), @@ -48,7 +47,7 @@ def get_tag_info(token: Token) -> TagInfo: 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 # attributes from HTML tags. This also takes care of template variables # 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 -def build_id_dict(templates: List[str]) -> Dict[str, List[str]]: - template_id_dict: Dict[str, List[str]] = defaultdict(list) +def build_id_dict(templates: list[str]) -> dict[str, list[str]]: + template_id_dict: dict[str, list[str]] = defaultdict(list) for fn in templates: with open(fn) as f: diff --git a/tools/lib/pretty_print.py b/tools/lib/pretty_print.py index 47d2b551a4..ae322e1b9a 100644 --- a/tools/lib/pretty_print.py +++ b/tools/lib/pretty_print.py @@ -1,12 +1,12 @@ import subprocess -from typing import List, Optional +from typing import Optional from zulint.printer import BOLDRED, CYAN, ENDC, GREEN 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 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" -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 for token in tokens: @@ -106,7 +106,7 @@ def adjust_block_indentation(tokens: List[Token], fn: str) -> None: 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: frag = frag.strip() 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:]) -def apply_token_indents(tokens: List[Token]) -> None: +def apply_token_indents(tokens: list[Token]) -> None: for token in tokens: if token.indent: 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: 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"))) -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: html = f.read() phtml = pretty_print_html(tokens, fn) diff --git a/tools/lib/provision.py b/tools/lib/provision.py index c37ffefb64..7ec4045d7b 100755 --- a/tools/lib/provision.py +++ b/tools/lib/provision.py @@ -6,7 +6,7 @@ import os import platform import subprocess import sys -from typing import List, NoReturn +from typing import NoReturn os.environ["PYTHONUNBUFFERED"] = "y" @@ -232,7 +232,7 @@ def install_system_deps() -> None: 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. 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) 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) # Requires: perl(IPC::Run) - yum_extra_flags: List[str] = [] + yum_extra_flags: list[str] = [] if vendor == "rhel": proc = subprocess.run( ["sudo", "subscription-manager", "status"], diff --git a/tools/lib/provision_inner.py b/tools/lib/provision_inner.py index 09f513e25a..2fe44d3527 100755 --- a/tools/lib/provision_inner.py +++ b/tools/lib/provision_inner.py @@ -11,7 +11,6 @@ import glob import os import shutil import sys -from typing import List 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) -def build_pygments_data_paths() -> List[str]: +def build_pygments_data_paths() -> list[str]: paths = [ "tools/setup/build_pygments_data", "tools/setup/lang.json", @@ -65,27 +64,27 @@ def build_pygments_data_paths() -> List[str]: return paths -def build_timezones_data_paths() -> List[str]: +def build_timezones_data_paths() -> list[str]: paths = [ "tools/setup/build_timezone_values", ] 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 += glob.glob("static/images/landing-page/hello/original/*") return paths -def compilemessages_paths() -> List[str]: +def compilemessages_paths() -> list[str]: paths = ["zerver/management/commands/compilemessages.py"] paths += glob.glob("locale/*/LC_MESSAGES/*.po") paths += glob.glob("locale/*/translations.json") return paths -def configure_rabbitmq_paths() -> List[str]: +def configure_rabbitmq_paths() -> list[str]: paths = [ "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( "last_configure_rabbitmq_hash", configure_rabbitmq_paths(), diff --git a/tools/lib/template_parser.py b/tools/lib/template_parser.py index 0b5b4e729d..f5cda739e9 100644 --- a/tools/lib/template_parser.py +++ b/tools/lib/template_parser.py @@ -1,4 +1,4 @@ -from typing import Callable, List, Optional +from typing import Callable, Optional from typing_extensions import override @@ -51,7 +51,7 @@ class Token: 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 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(" ") state = TokenizerState() - tokens: List[Token] = [] + tokens: list[Token] = [] while state.i < len(text): try: @@ -355,7 +355,7 @@ def validate( fn: Optional[str] = None, text: Optional[str] = None, template_format: Optional[str] = None, -) -> List[Token]: +) -> list[Token]: assert fn or text if fn is None: @@ -500,7 +500,7 @@ def validate( 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: 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 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"): raise TemplateParserError(f" Please remove the whitespace at the beginning of {fn}.") diff --git a/tools/lib/test_script.py b/tools/lib/test_script.py index 86c1d0d6fe..6d7821a6ef 100644 --- a/tools/lib/test_script.py +++ b/tools/lib/test_script.py @@ -3,7 +3,7 @@ import os import subprocess import sys 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 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 += "\n" 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() if not os.path.exists(version_file): # 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 = [] for file in files: file = min( diff --git a/tools/linter_lib/custom_check.py b/tools/linter_lib/custom_check.py index 8499b8a75a..4d6b051d83 100644 --- a/tools/linter_lib/custom_check.py +++ b/tools/linter_lib/custom_check.py @@ -1,5 +1,3 @@ -from typing import List - from zulint.custom_rules import Rule, RuleList # Rule help: @@ -39,7 +37,7 @@ FILES_WITH_LEGACY_SUBJECT = { "zerver/tests/test_message_fetch.py", } -shebang_rules: List["Rule"] = [ +shebang_rules: list["Rule"] = [ { "pattern": r"\A#!", "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 ]+$", "exclude": {"tools/ci/success-http-headers.template.txt"}, @@ -70,7 +68,7 @@ base_whitespace_rules: List["Rule"] = [ "description": "Missing newline at end of file", }, ] -whitespace_rules: List["Rule"] = [ +whitespace_rules: list["Rule"] = [ *base_whitespace_rules, { "pattern": "http://zulip.readthedocs.io", @@ -85,7 +83,7 @@ whitespace_rules: List["Rule"] = [ "description": "Web app should be two words", }, ] -comma_whitespace_rule: List["Rule"] = [ +comma_whitespace_rule: list["Rule"] = [ { "pattern": ", {2,}[^#/ ]", "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)"], }, ] -markdown_whitespace_rules: List["Rule"] = [ +markdown_whitespace_rules: list["Rule"] = [ *(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. # 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 "exclude": {"docs/documentation/api.md", "templates/corporate/policies/privacy.md"}, @@ -531,7 +529,7 @@ prose_style_rules: List["Rule"] = [ }, *comma_whitespace_rule, ] -html_rules: List["Rule"] = [ +html_rules: list["Rule"] = [ *whitespace_rules, *prose_style_rules, { diff --git a/tools/oneclickapps/prepare_digital_ocean_one_click_app_release.py b/tools/oneclickapps/prepare_digital_ocean_one_click_app_release.py index d8da4e39f7..fdabcc20a0 100644 --- a/tools/oneclickapps/prepare_digital_ocean_one_click_app_release.py +++ b/tools/oneclickapps/prepare_digital_ocean_one_click_app_release.py @@ -2,7 +2,6 @@ import os import subprocess import time from pathlib import Path -from typing import List import digitalocean import zulip @@ -63,7 +62,7 @@ def set_api_request_retry_limits(api_object: digitalocean.baseapi.BaseAPI) -> No 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: droplet = digitalocean.Droplet( token=manager.token, diff --git a/tools/renumber-migrations b/tools/renumber-migrations index 9a1cd21ebc..0720f33c81 100755 --- a/tools/renumber-migrations +++ b/tools/renumber-migrations @@ -4,10 +4,9 @@ import glob import os import re 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: print("Please enter the sequence of all the conflicting files at once") sys.exit(1) @@ -18,8 +17,8 @@ def validate_order(order: List[int], length: int) -> None: sys.exit(1) -def renumber_migration(conflicts: List[str], order: List[int], last_correct_migration: str) -> None: - stack: List[str] = [] +def renumber_migration(conflicts: list[str], order: list[int], last_correct_migration: str) -> None: + stack: list[str] = [] for i in order: if conflicts[i - 1][0:4] not in stack: 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", "") -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:") for i in range(len(conflicts)): print(str(i + 1) + ". " + conflicts[i]) @@ -56,8 +55,8 @@ def resolve_conflicts(conflicts: List[str], files_list: List[str]) -> None: if __name__ == "__main__": MIGRATIONS_TO_SKIP = {"0209", "0261", "0501"} while True: - conflicts: List[str] = [] - stack: List[str] = [] + conflicts: list[str] = [] + stack: list[str] = [] files_list = [os.path.basename(path) for path in glob.glob("zerver/migrations/????_*.py")] file_index = [file[0:4] for file in files_list] diff --git a/tools/review b/tools/review index d00c5049b0..252a85bfb5 100755 --- a/tools/review +++ b/tools/review @@ -2,7 +2,6 @@ import shlex import subprocess import sys -from typing import List def exit(message: str) -> None: @@ -11,12 +10,12 @@ def exit(message: str) -> None: sys.exit(1) -def run(command: List[str]) -> None: +def run(command: list[str]) -> None: print(f"\n>>> {shlex.join(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) diff --git a/tools/run-dev b/tools/run-dev index 2a3e473564..4a3040a572 100755 --- a/tools/run-dev +++ b/tools/run-dev @@ -8,7 +8,6 @@ import pwd import signal import subprocess import sys -from typing import List TOOLS_DIR = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.dirname(TOOLS_DIR)) @@ -81,7 +80,7 @@ if options.interface is None: elif options.interface == "": options.interface = None -runserver_args: List[str] = [] +runserver_args: list[str] = [] base_port = 9991 if options.test: base_port = 9981 @@ -139,7 +138,7 @@ with open(pid_file_path, "w+") as f: f.write(str(os.getpgrp()) + "\n") -def server_processes() -> List[List[str]]: +def server_processes() -> list[list[str]]: main_cmds = [ [ "./manage.py", @@ -330,7 +329,7 @@ def https_log_filter(record: logging.LogRecord) -> bool: logging.getLogger("aiohttp.server").addFilter(https_log_filter) runner: web.AppRunner -children: List["subprocess.Popen[bytes]"] = [] +children: list["subprocess.Popen[bytes]"] = [] async def serve() -> None: diff --git a/tools/run-mypy b/tools/run-mypy index 2a90b69004..790cd3d7a1 100755 --- a/tools/run-mypy +++ b/tools/run-mypy @@ -3,7 +3,6 @@ import argparse import os import subprocess import sys -from typing import List 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.") sys.exit(0) -mypy_args: List[str] = [] +mypy_args: list[str] = [] # --no-error-summary is a mypy flag that comes after all dmypy options if args.use_daemon: mypy_args += ["run", "--"] diff --git a/tools/screenshots/generate-integration-docs-screenshot b/tools/screenshots/generate-integration-docs-screenshot index d2fea28738..7030715e4c 100755 --- a/tools/screenshots/generate-integration-docs-screenshot +++ b/tools/screenshots/generate-integration-docs-screenshot @@ -6,7 +6,7 @@ import base64 import os import subprocess import sys -from typing import Any, Dict, Optional, Tuple +from typing import Any, Optional from urllib.parse import parse_qsl, urlencode 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) -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") _, fixture_name = split_fixture_path(fixture_path) @@ -116,7 +116,7 @@ def get_integration(integration_name: str) -> 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) 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()} -def custom_headers(headers_json: str) -> Dict[str, str]: +def custom_headers(headers_json: str) -> dict[str, str]: if not headers_json: return {} try: diff --git a/tools/screenshots/generate-user-messages-screenshot b/tools/screenshots/generate-user-messages-screenshot index b790066958..40ad3578b2 100755 --- a/tools/screenshots/generate-user-messages-screenshot +++ b/tools/screenshots/generate-user-messages-screenshot @@ -6,7 +6,7 @@ import os import subprocess import sys from datetime import datetime, timezone -from typing import Dict, List, Optional +from typing import Optional from django.conf import settings from pydantic import BaseModel, ConfigDict @@ -59,7 +59,7 @@ realm.save() DEFAULT_USER = get_user_by_delivery_email("iago@zulip.com", realm) NOTIFICATION_BOT = get_system_bot(settings.NOTIFICATION_BOT, realm.id) -message_thread_ids: List[int] = [] +message_thread_ids: list[int] = [] USER_AVATARS_MAP = { "Ariella Drake": "tools/screenshots/user_avatars/AriellaDrake.png", @@ -82,8 +82,8 @@ class MessageThread(BaseModel): content: str starred: bool edited: bool - reactions: Dict[str, List[str]] - date: Dict[str, int] + reactions: dict[str, list[str]] + date: dict[str, int] 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( - 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: stream = ensure_stream(realm, stream_name, invite_only=invite_only, acting_user=DEFAULT_USER) bulk_add_subscriptions( @@ -123,22 +123,22 @@ def create_and_subscribe_stream( def send_stream_messages( - stream_name: str, topic: str, staged_messages_data: List[MessageThread] -) -> List[int]: + stream_name: str, topic: str, staged_messages_data: list[MessageThread] +) -> list[int]: staged_messages = [dict(staged_message) for staged_message in staged_messages_data] stream = ensure_stream(realm, stream_name, acting_user=DEFAULT_USER) subscribers_query = get_active_subscriptions_for_stream_id( stream.id, include_deactivated_users=False ).values_list("user_profile", flat=True) - subscribers: Dict[str, UserProfile] = {} + subscribers: dict[str, UserProfile] = {} for subscriber_id in subscribers_query: subscriber = UserProfile.objects.get(realm=realm, id=subscriber_id) subscribers[subscriber.full_name] = subscriber subscribers["Notification Bot"] = NOTIFICATION_BOT - messages: List[Optional[SendMessageRequest]] = [] + messages: list[Optional[SendMessageRequest]] = [] for message in staged_messages: date_sent = message["date"] @@ -186,7 +186,7 @@ def send_stream_messages( 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) emoji_data = get_emoji_data(realm.id, emoji) 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 = [ UserProfile.objects.get(realm=realm, full_name=member_name) for member_name in members ] diff --git a/tools/setup/emoji/build_emoji b/tools/setup/emoji/build_emoji index 8bd498d264..f399054f94 100755 --- a/tools/setup/emoji/build_emoji +++ b/tools/setup/emoji/build_emoji @@ -5,7 +5,7 @@ import os import shutil import sys -from typing import Any, Dict, Iterator, List, Optional, Sequence +from typing import Any, Iterator, Optional, Sequence import orjson @@ -127,13 +127,13 @@ def percent(f: float) -> str: 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 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: yield emoji_dict["sheet_x"] 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( cache_path: str, - emoji_data: List[Dict[str, Any]], + emoji_data: list[dict[str, Any]], emojiset: str, alt_name: str, - fallback_emoji_data: Sequence[Dict[str, Any]], + fallback_emoji_data: Sequence[dict[str, Any]], ) -> None: """ Spritesheets are usually NxN squares. @@ -272,9 +272,9 @@ def generate_sprite_css_files( 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( - 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: # We use individual images from emoji farm for rendering emojis # 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( emojiset: str, - emoji_data: List[Dict[str, Any]], + emoji_data: list[dict[str, Any]], alt_name: Optional[str] = None, - fallback_emoji_data: Sequence[Dict[str, Any]] = [], + fallback_emoji_data: Sequence[dict[str, Any]] = [], ) -> None: # `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 @@ -342,7 +342,7 @@ def setup_emoji_farms(cache_path: str, emoji_data: List[Dict[str, Any]]) -> None 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: # Code for setting up old emoji farm. os.chdir(cache_path) @@ -374,7 +374,7 @@ def setup_old_emoji_farm( 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 # consumed by the web app, mobile apps, Markdown processor, etc. names = emoji_names_for_picker(EMOJI_NAME_MAPS) diff --git a/tools/setup/emoji/custom_emoji_names.py b/tools/setup/emoji/custom_emoji_names.py index 40fec5edd5..4aee9b3a7e 100644 --- a/tools/setup/emoji/custom_emoji_names.py +++ b/tools/setup/emoji/custom_emoji_names.py @@ -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 "1f600": {"canonical_name": "grinning", "aliases": ["happy"]}, "1f603": {"canonical_name": "smiley", "aliases": []}, diff --git a/tools/setup/emoji/emoji_names.py b/tools/setup/emoji/emoji_names.py index 6399c9f78d..59cec97e1b 100644 --- a/tools/setup/emoji/emoji_names.py +++ b/tools/setup/emoji/emoji_names.py @@ -1,8 +1,8 @@ -from typing import Any, Dict +from typing import Any # 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": []}, "002a-20e3": {"canonical_name": "asterisk", "aliases": []}, "0030-20e3": {"canonical_name": "zero", "aliases": []}, diff --git a/tools/setup/emoji/emoji_setup_utils.py b/tools/setup/emoji/emoji_setup_utils.py index b0f7281a0c..9a34c1271a 100644 --- a/tools/setup/emoji/emoji_setup_utils.py +++ b/tools/setup/emoji/emoji_setup_utils.py @@ -1,7 +1,7 @@ # This file contains various helper functions used by `build_emoji` tool. # See docs/subsystems/emoji.md for details on how this system works. 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 @@ -50,8 +50,8 @@ EMOTICON_CONVERSIONS = { } -def emoji_names_for_picker(emoji_name_maps: Dict[str, Dict[str, Any]]) -> List[str]: - emoji_names: List[str] = [] +def emoji_names_for_picker(emoji_name_maps: dict[str, dict[str, Any]]) -> list[str]: + emoji_names: list[str] = [] for name_info in emoji_name_maps.values(): emoji_names.append(name_info["canonical_name"]) 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) -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 # inconsistently present, so we'll always use the unqualified # 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 # `emoji_data`. def generate_emoji_catalog( - emoji_data: List[Dict[str, Any]], emoji_name_maps: Dict[str, Dict[str, Any]] -) -> Dict[str, List[str]]: - sort_order: Dict[str, int] = {} - emoji_catalog: Dict[str, List[str]] = defaultdict(list) + emoji_data: list[dict[str, Any]], emoji_name_maps: dict[str, dict[str, Any]] +) -> dict[str, list[str]]: + sort_order: dict[str, int] = {} + emoji_catalog: dict[str, list[str]] = defaultdict(list) for emoji_dict in emoji_data: emoji_code = get_emoji_code(emoji_dict) @@ -93,8 +93,8 @@ def generate_emoji_catalog( return dict(emoji_catalog) -def generate_codepoint_to_name_map(emoji_name_maps: Dict[str, Dict[str, Any]]) -> Dict[str, str]: - codepoint_to_name: 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] = {} for emoji_code, name_info in emoji_name_maps.items(): codepoint_to_name[emoji_code] = name_info["canonical_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 # 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"] def generate_codepoint_to_names_map( - emoji_name_maps: Dict[str, Dict[str, Any]], -) -> Dict[str, List[str]]: + emoji_name_maps: dict[str, dict[str, Any]], +) -> dict[str, list[str]]: # The first element of the names list is always the canonical name. return { 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 = {} for emoji_code, name_info in emoji_name_maps.items(): canonical_name = name_info["canonical_name"] diff --git a/tools/setup/emoji/generate_emoji_names_table b/tools/setup/emoji/generate_emoji_names_table index 76d569f10d..f979c63cf6 100755 --- a/tools/setup/emoji/generate_emoji_names_table +++ b/tools/setup/emoji/generate_emoji_names_table @@ -7,7 +7,7 @@ # codepoint. import os import sys -from typing import Any, Dict, List +from typing import Any import orjson @@ -124,15 +124,15 @@ SORTED_CATEGORIES = [ "Skin Tones", ] -emoji_code_to_zulip_names: Dict[str, str] = {} -emoji_code_to_iamcal_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_code_to_zulip_names: dict[str, str] = {} +emoji_code_to_iamcal_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} def generate_emoji_code_to_emoji_names_maps() -> None: # 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: emoji_code = UNIFIED_REACTIONS_MAP[name] if emoji_code in reverse_unified_reactions_map: diff --git a/tools/setup/generate_zulip_bots_static_files.py b/tools/setup/generate_zulip_bots_static_files.py index f26bce9795..2e545b0cb2 100755 --- a/tools/setup/generate_zulip_bots_static_files.py +++ b/tools/setup/generate_zulip_bots_static_files.py @@ -3,7 +3,6 @@ import glob import os import shutil import sys -from typing import List ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 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() - def copy_bots_data(bot_names: List[str]) -> None: + def copy_bots_data(bot_names: list[str]) -> None: for name in bot_names: src_dir = os.path.join(package_bots_dir, name) dst_dir = os.path.join(bots_dir, name) diff --git a/tools/tail-ses b/tools/tail-ses index 05823da842..ada0f30643 100755 --- a/tools/tail-ses +++ b/tools/tail-ses @@ -13,7 +13,7 @@ os.environ["DJANGO_SETTINGS_MODULE"] = "zproject.settings" import argparse import secrets from contextlib import contextmanager -from typing import Iterator, List, Tuple, TypedDict +from typing import Iterator, TypedDict import boto3.session import orjson @@ -86,7 +86,7 @@ def get_ses_arn(session: boto3.session.Session, args: argparse.Namespace) -> str @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(":") sqs: SQSClient = session.client("sqs") @@ -153,7 +153,7 @@ def print_messages(session: boto3.session.Session, queue_url: str) -> None: MessageAttributeNames=["All"], ) messages = resp.get("Messages", []) - delete_list: List[DeleteMessageBatchRequestEntryTypeDef] = [] + delete_list: list[DeleteMessageBatchRequestEntryTypeDef] = [] for m in messages: body = orjson.loads(m["Body"]) body_message = orjson.loads(body["Message"]) diff --git a/tools/test-backend b/tools/test-backend index 796f5cc60c..fa0e81decb 100755 --- a/tools/test-backend +++ b/tools/test-backend @@ -8,7 +8,7 @@ import shlex import subprocess import sys import tempfile -from typing import TYPE_CHECKING, Iterator, List, Type, cast +from typing import TYPE_CHECKING, Iterator, cast from unittest import mock if TYPE_CHECKING: @@ -154,7 +154,7 @@ enforce_fully_covered = sorted( FAILED_TEST_PATH = "var/last_test_failure.json" -def get_failed_tests() -> List[str]: +def get_failed_tests() -> list[str]: try: with open(FAILED_TEST_PATH, "rb") as f: 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 # 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: parallel = options.processes diff --git a/tools/test-js-with-node b/tools/test-js-with-node index 15335e25d4..f0cd746b2a 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -5,7 +5,7 @@ import os import pwd import subprocess import sys -from typing import Any, Dict, List, Set +from typing import Any TOOLS_DIR = os.path.dirname(os.path.abspath(__file__)) 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)): if files[i - 1] > files[i]: 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 -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] return cleaned_files @@ -442,7 +442,7 @@ def run_tests_via_node_js() -> int: 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: missing_lines = [] for line in line_coverage: diff --git a/tools/test-js-with-puppeteer b/tools/test-js-with-puppeteer index 0ac53d9485..8f8e4533f3 100755 --- a/tools/test-js-with-puppeteer +++ b/tools/test-js-with-puppeteer @@ -39,7 +39,7 @@ from tools.lib import sanity_check sanity_check.check_venv(__file__) -from typing import Iterable, Tuple +from typing import Iterable from tools.lib.test_script import ( 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) 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 for test_file in test_files[test_number:]: return_code = run_single_test(test_file, current_test_num + 1, total_tests) diff --git a/tools/test-locked-requirements b/tools/test-locked-requirements index 6ae282eb22..8377ab723c 100755 --- a/tools/test-locked-requirements +++ b/tools/test-locked-requirements @@ -8,7 +8,6 @@ import shutil import subprocess import sys import tempfile -from typing import List import orjson @@ -74,13 +73,13 @@ def maybe_set_up_cache() -> None: fp.write(orjson.dumps([])) -def load_cache() -> List[str]: +def load_cache() -> list[str]: with open(CACHE_FILE, "rb") as fp: hash_list = orjson.loads(fp.read()) 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 # not a problem as it is cheap to do. if len(hash_list) > 100: diff --git a/tools/test-run-dev b/tools/test-run-dev index cbf38bfbea..8002efe9a1 100755 --- a/tools/test-run-dev +++ b/tools/test-run-dev @@ -5,7 +5,6 @@ import subprocess import sys import tempfile import time -from typing import Tuple 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__)) -def start_server(logfile_name: str) -> Tuple[bool, str]: +def start_server(logfile_name: str) -> tuple[bool, str]: failure = True key = "Quit the server with CONTROL-C." datalog = [] diff --git a/tools/total-contributions b/tools/total-contributions index 244748f1ae..6e6aa989aa 100755 --- a/tools/total-contributions +++ b/tools/total-contributions @@ -5,7 +5,6 @@ import subprocess import sys from collections import defaultdict from pathlib import Path -from typing import Dict, List bot_commits = 0 @@ -13,7 +12,7 @@ ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 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: committer_name = dataset.split("\t")[1] 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 -def retrieve_log(repo: str, revisions: str) -> List[str]: +def retrieve_log(repo: str, revisions: str) -> list[str]: return subprocess.check_output( ["git", "shortlog", "-s", revisions], cwd=find_path(repo), @@ -41,7 +40,7 @@ def find_path(repository: str) -> str: def process_repo( *, - out_dict: Dict[str, int], + out_dict: dict[str, int], repo_short: str, repo_full: 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}" ) -repository_dict: Dict[str, int] = defaultdict(int) -out_dict: Dict[str, int] = defaultdict(int) +repository_dict: dict[str, int] = defaultdict(int) +out_dict: dict[str, int] = defaultdict(int) subprocess.check_call(["git", "fetch"], cwd=find_path("zulip")) process_repo( out_dict=out_dict, diff --git a/tools/upload-release b/tools/upload-release index 5673e15f09..8e70ca3ac5 100755 --- a/tools/upload-release +++ b/tools/upload-release @@ -5,7 +5,7 @@ import os import re import sys 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__), "..")) from scripts.lib.setup_path import setup_path @@ -41,7 +41,7 @@ session = boto3.session.Session() client: S3Client = session.client("s3") 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: print(f"Hashing {new_basename}..") 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"] -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" print(f"Uploading {new_basename}..") diff --git a/zerver/actions/create_realm.py b/zerver/actions/create_realm.py index b5db67dc11..2f50b8f731 100644 --- a/zerver/actions/create_realm.py +++ b/zerver/actions/create_realm.py @@ -1,6 +1,6 @@ import logging from datetime import datetime, timedelta -from typing import Any, Dict, Optional +from typing import Any, Optional from django.conf import settings from django.db import transaction @@ -136,7 +136,7 @@ def set_realm_permissions_based_on_org_type(realm: Realm) -> None: @transaction.atomic(savepoint=False) 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: 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.") create_internal_realm() - kwargs: Dict[str, Any] = {} + kwargs: dict[str, Any] = {} if emails_restricted_to_domains is not None: kwargs["emails_restricted_to_domains"] = emails_restricted_to_domains if description is not None: diff --git a/zerver/actions/create_user.py b/zerver/actions/create_user.py index 88f6b32fdd..e0a85e7711 100644 --- a/zerver/actions/create_user.py +++ b/zerver/actions/create_user.py @@ -1,6 +1,6 @@ from collections import defaultdict 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.db import transaction @@ -133,7 +133,7 @@ def set_up_streams_for_new_human_user( realm = user_profile.realm 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 # 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") -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) - format_user_row_kwargs: Dict[str, Any] = { + format_user_row_kwargs: dict[str, Any] = { "realm_id": user_profile.realm_id, "row": user_row, # 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": {}, } - user_ids_without_access_to_created_user: List[int] = [] - users_with_access_to_created_users: List[UserProfile] = [] + user_ids_without_access_to_created_user: list[int] = [] + users_with_access_to_created_users: list[UserProfile] = [] if notify_user_ids: # 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: 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 ) 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) -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]: if not stream: return None @@ -749,7 +749,7 @@ def do_reactivate_user(user_profile: UserProfile, *, acting_user: Optional[UserP 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: altered_user_dict[stream.id] = {user_profile.id} diff --git a/zerver/actions/custom_profile_fields.py b/zerver/actions/custom_profile_fields.py index 8bf0242f5a..810f9db36c 100644 --- a/zerver/actions/custom_profile_fields.py +++ b/zerver/actions/custom_profile_fields.py @@ -1,4 +1,4 @@ -from typing import Dict, Iterable, List, Optional, Union +from typing import Iterable, Optional, Union import orjson 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( - 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: 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( user_profile: UserProfile, - data: List[ProfileDataElementUpdateDict], + data: list[ProfileDataElementUpdateDict], ) -> None: with transaction.atomic(): for custom_profile_field in data: diff --git a/zerver/actions/default_streams.py b/zerver/actions/default_streams.py index da999cf3e4..dc90d10f56 100644 --- a/zerver/actions/default_streams.py +++ b/zerver/actions/default_streams.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Iterable, List +from typing import Any, Iterable from django.db import transaction 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( - default_stream_group_names: List[str], realm: Realm -) -> List[DefaultStreamGroup]: + default_stream_group_names: list[str], realm: Realm +) -> list[DefaultStreamGroup]: default_stream_groups = [] for group_name in default_stream_group_names: try: @@ -86,7 +86,7 @@ def do_remove_default_stream(stream: Stream) -> None: 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: default_stream_ids = get_default_stream_ids_for_realm(realm.id) for stream in streams: @@ -113,7 +113,7 @@ def do_create_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: default_stream_ids = get_default_stream_ids_for_realm(realm.id) for stream in streams: @@ -136,7 +136,7 @@ def do_add_streams_to_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: group_stream_ids = {stream.id for stream in group.streams.all()} 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( 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"]) diff --git a/zerver/actions/invites.py b/zerver/actions/invites.py index 4e62e6befe..bf6f582dc2 100644 --- a/zerver/actions/invites.py +++ b/zerver/actions/invites.py @@ -1,6 +1,6 @@ import logging 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.contrib.contenttypes.models import ContentType @@ -179,7 +179,7 @@ def do_invite_users( invite_expires_in_minutes: Optional[int], include_realm_default_subscriptions: bool, invite_as: int = PreregistrationUser.INVITE_AS["MEMBER"], -) -> List[Tuple[str, str, bool]]: +) -> list[tuple[str, str, bool]]: num_invites = len(invitee_emails) # Lock the realm, since we need to not race with other invitations @@ -211,8 +211,8 @@ def do_invite_users( sent_invitations=False, ) - good_emails: Set[str] = set() - errors: List[Tuple[str, str, bool]] = [] + good_emails: set[str] = set() + errors: list[tuple[str, str, bool]] = [] validate_email_allowed_in_realm = get_realm_email_validator(realm) for email in invitee_emails: if email == "": @@ -234,7 +234,7 @@ def do_invite_users( """ 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: msg, deactivated = error_dict[email] 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) -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. This isn't necessarily the same as all the invitations generated by the user, as administrators diff --git a/zerver/actions/message_delete.py b/zerver/actions/message_delete.py index 0f44eb6b3c..c7ce3ef6ba 100644 --- a/zerver/actions/message_delete.py +++ b/zerver/actions/message_delete.py @@ -1,4 +1,4 @@ -from typing import Iterable, List, TypedDict +from typing import Iterable, TypedDict from zerver.lib import retention 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): type: str - message_ids: List[int] + message_ids: list[int] message_type: str topic: str stream_id: int 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: # This will not update the `first_message_id` of streams where the # first message was deleted prior to the implementation of this function. diff --git a/zerver/actions/message_edit.py b/zerver/actions/message_edit.py index a6398b2ad0..ab05cc2907 100644 --- a/zerver/actions/message_edit.py +++ b/zerver/actions/message_edit.py @@ -1,7 +1,7 @@ import itertools from collections import defaultdict 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.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 -def subscriber_info(user_id: int) -> Dict[str, Any]: +def subscriber_info(user_id: int) -> dict[str, Any]: return {"id": user_id, "flags": ["read"]} @@ -148,7 +148,7 @@ def maybe_send_resolve_topic_notifications( new_topic_name: str, changed_messages: QuerySet[Message], 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.""" # Note that topics will have already been stripped in check_update_message. 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 # users did not receive the message originally, and thus # probably are not relevant for reprocessed alert_words, @@ -330,7 +330,7 @@ def update_user_message_flags( ) -> None: mentioned_ids = rendering_result.mentions_user_ids 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: if should_set: @@ -366,7 +366,7 @@ def do_update_embedded_data( rendering_result: MessageRenderingResult, ) -> None: timestamp = timezone_now() - event: Dict[str, Any] = { + event: dict[str, Any] = { "type": "update_message", "user_id": None, "edit_timestamp": datetime_to_timestamp(timestamp), @@ -390,7 +390,7 @@ def do_update_embedded_data( 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 { "id": um.user_profile_id, "flags": um.flags_list(), @@ -433,7 +433,7 @@ def do_update_message( send_notification_to_new_thread: bool, content: Optional[str], rendering_result: Optional[MessageRenderingResult], - prior_mention_user_ids: Set[int], + prior_mention_user_ids: set[int], mention_data: Optional[MentionData] = None, ) -> int: """ @@ -452,7 +452,7 @@ def do_update_message( timestamp = timezone_now() target_message.last_edit_time = timestamp - event: Dict[str, Any] = { + event: dict[str, Any] = { "type": "update_message", "user_id": user_profile.id, "edit_timestamp": datetime_to_timestamp(timestamp), @@ -586,7 +586,7 @@ def do_update_message( event["propagate_mode"] = propagate_mode users_losing_access = UserProfile.objects.none() - user_ids_gaining_usermessages: List[int] = [] + user_ids_gaining_usermessages: list[int] = [] if new_stream is not None: assert content is None assert target_message.is_stream_message() @@ -798,7 +798,7 @@ def do_update_message( 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 { "id": um.user_profile_id, "flags": um.flags_list(), @@ -908,9 +908,9 @@ def do_update_message( assert target_stream is not None assert target_topic_name is not None - stream_inaccessible_to_user_profiles: List[UserProfile] = [] - orig_topic_user_profile_to_visibility_policy: Dict[UserProfile, int] = {} - target_topic_user_profile_to_visibility_policy: Dict[UserProfile, int] = {} + stream_inaccessible_to_user_profiles: list[UserProfile] = [] + orig_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} for user_topic in get_users_with_user_topic_visibility_policy( 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_visibility_policy: Set[UserProfile] = set( + user_profiles_having_visibility_policy: set[UserProfile] = set( itertools.chain( orig_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) ) 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__subject__iexact=message.topic_name(), ).values_list("message_id", flat=True) - messages_allowed_to_move: List[int] = list( + messages_allowed_to_move: list[int] = list( Message.objects.filter( # Uses index: zerver_message_pkey 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.")) rendering_result = None - links_for_embed: Set[str] = set() - prior_mention_user_ids: Set[int] = set() + links_for_embed: set[str] = set() + prior_mention_user_ids: set[int] = set() mention_data: Optional[MentionData] = None if content is not None: if content.rstrip() == "": diff --git a/zerver/actions/message_flags.py b/zerver/actions/message_flags.py index 507d0fe794..74a64bac52 100644 --- a/zerver/actions/message_flags.py +++ b/zerver/actions/message_flags.py @@ -1,7 +1,7 @@ import time from collections import defaultdict from dataclasses import asdict, dataclass, field -from typing import List, Optional, Set +from typing import Optional from django.conf import settings from django.db import transaction @@ -26,7 +26,7 @@ from zerver.tornado.django_api import send_event @dataclass class ReadMessagesEvent: - messages: List[int] + messages: list[int] all: bool type: str = field(default="update_message_flags", 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( message: Message, - prior_mention_user_ids: Set[int], - mentions_user_ids: Set[int], - stream_push_user_ids: Set[int], + prior_mention_user_ids: set[int], + mentions_user_ids: set[int], + stream_push_user_ids: set[int], ) -> None: # Called during the message edit code path to remove mobile push # 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( - user_profile_ids: List[int], message_ids: List[int] + user_profile_ids: list[int], message_ids: list[int] ) -> None: if len(message_ids) == 0: return @@ -261,7 +261,7 @@ def do_clear_mobile_push_notifications_for_ids( 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: valid_flags = [item for item in UserMessage.flags if item not in UserMessage.NON_API_FLAGS] if flag not in valid_flags: diff --git a/zerver/actions/message_send.py b/zerver/actions/message_send.py index 465d5d926c..c31251b784 100644 --- a/zerver/actions/message_send.py +++ b/zerver/actions/message_send.py @@ -3,20 +3,7 @@ from collections import defaultdict from dataclasses import dataclass from datetime import timedelta from email.headerregistry import Address -from typing import ( - AbstractSet, - Any, - Callable, - Collection, - Dict, - List, - Optional, - Sequence, - Set, - Tuple, - TypedDict, - Union, -) +from typing import AbstractSet, Any, Callable, Collection, Optional, Sequence, TypedDict, Union import orjson from django.conf import settings @@ -157,7 +144,7 @@ def render_incoming_message( content: str, realm: Realm, 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, ) -> MessageRenderingResult: realm_alert_words_automaton = get_alert_word_automaton(realm) @@ -178,25 +165,25 @@ def render_incoming_message( @dataclass class RecipientInfoResult: - active_user_ids: Set[int] - online_push_user_ids: Set[int] - dm_mention_email_disabled_user_ids: Set[int] - dm_mention_push_disabled_user_ids: Set[int] - stream_email_user_ids: Set[int] - stream_push_user_ids: Set[int] - topic_wildcard_mention_user_ids: Set[int] - stream_wildcard_mention_user_ids: Set[int] - followed_topic_email_user_ids: Set[int] - followed_topic_push_user_ids: Set[int] - topic_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] - um_eligible_user_ids: Set[int] - long_term_idle_user_ids: Set[int] - default_bot_user_ids: Set[int] - service_bot_tuples: List[Tuple[int, int]] - all_bot_user_ids: Set[int] - topic_participant_user_ids: Set[int] + active_user_ids: set[int] + online_push_user_ids: set[int] + dm_mention_email_disabled_user_ids: set[int] + dm_mention_push_disabled_user_ids: set[int] + stream_email_user_ids: set[int] + stream_push_user_ids: set[int] + topic_wildcard_mention_user_ids: set[int] + stream_wildcard_mention_user_ids: set[int] + followed_topic_email_user_ids: set[int] + followed_topic_push_user_ids: set[int] + topic_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] + um_eligible_user_ids: set[int] + long_term_idle_user_ids: set[int] + default_bot_user_ids: set[int] + service_bot_tuples: list[tuple[int, int]] + all_bot_user_ids: set[int] + topic_participant_user_ids: set[int] sender_muted_stream: Optional[bool] @@ -226,16 +213,16 @@ def get_recipient_info( possible_topic_wildcard_mention: bool = True, possible_stream_wildcard_mention: bool = True, ) -> RecipientInfoResult: - stream_push_user_ids: Set[int] = set() - stream_email_user_ids: Set[int] = set() - topic_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_email_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() - muted_sender_user_ids: Set[int] = get_muting_users(sender_id) - topic_participant_user_ids: Set[int] = set() + stream_push_user_ids: set[int] = set() + stream_email_user_ids: set[int] = set() + topic_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_email_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() + muted_sender_user_ids: set[int] = get_muting_users(sender_id) + topic_participant_user_ids: set[int] = set() sender_muted_stream: Optional[bool] = None 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() - def notification_recipients(setting: str) -> Set[int]: + def notification_recipients(setting: str) -> set[int]: return { row["user_profile_id"] for row in subscription_rows @@ -332,7 +319,7 @@ def get_recipient_info( stream_push_user_ids = notification_recipients("push_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 { row["user_profile_id"] for row in subscription_rows @@ -427,7 +414,7 @@ def get_recipient_info( # to-do. 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""" 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( sender: UserProfile, - service_bot_tuples: List[Tuple[int, int]], - mentioned_user_ids: Set[int], - active_user_ids: Set[int], + service_bot_tuples: list[tuple[int, int]], + mentioned_user_ids: set[int], + active_user_ids: set[int], recipient_type: int, -) -> Dict[str, List[Dict[str, Any]]]: - event_dict: Dict[str, List[Dict[str, Any]]] = defaultdict(list) +) -> dict[str, list[dict[str, Any]]]: + event_dict: dict[str, list[dict[str, Any]]] = defaultdict(list) # Avoid infinite loops by preventing messages sent by bots from generating # Service events. @@ -576,12 +563,12 @@ def build_message_send_dict( stream: Optional[Stream] = None, local_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, 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, - recipients_for_user_creation_events: Optional[Dict[UserProfile, Set[int]]] = None, + recipients_for_user_creation_events: Optional[dict[UserProfile, set[int]]] = None, ) -> SendMessageRequest: """Returns a dictionary that can be passed into do_send_messages. In production, this is always called by check_message, but some @@ -726,10 +713,10 @@ def create_user_messages( mentioned_user_ids: AbstractSet[int], followed_topic_push_user_ids: AbstractSet[int], followed_topic_email_user_ids: AbstractSet[int], - mark_as_read_user_ids: Set[int], - limit_unread_user_ids: Optional[Set[int]], - topic_participant_user_ids: Set[int], -) -> List[UserMessageLite]: + mark_as_read_user_ids: set[int], + limit_unread_user_ids: Optional[set[int]], + topic_participant_user_ids: set[int], +) -> list[UserMessageLite]: # These properties on the Message are set via # render_message_markdown by code in the Markdown inline patterns ids_with_alert_words = rendering_result.user_ids_with_alert_words @@ -798,7 +785,7 @@ def create_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 # the UserPresence table to determine which of these users are # 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( realm: Realm, sender_id: int, - user_notifications_data_list: List[UserMessageNotificationsData], -) -> List[int]: + user_notifications_data_list: list[UserMessageNotificationsData], +) -> list[int]: """ Given a list of active_user_ids, we build up a subset of those users who fit these criteria: @@ -851,7 +838,7 @@ def do_send_messages( send_message_requests_maybe_none: Sequence[Optional[SendMessageRequest]], *, mark_as_read: Sequence[int] = [], -) -> List[SentMessageResult]: +) -> list[SentMessageResult]: """See https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html for high-level documentation on this subsystem. @@ -865,7 +852,7 @@ def do_send_messages( ] # 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) @@ -877,7 +864,7 @@ def do_send_messages( send_request.message.has_attachment = True send_request.message.save(update_fields=["has_attachment"]) - ums: List[UserMessageLite] = [] + ums: list[UserMessageLite] = [] for send_request in send_message_requests: # Service bots (outgoing webhook bots and embedded bots) don't store UserMessage rows; # they will be processed later. @@ -978,7 +965,7 @@ def do_send_messages( human_user_personal_mentions = send_request.rendering_result.mentions_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: expect_follow_user_profiles = set( @@ -1045,10 +1032,10 @@ def do_send_messages( class UserData(TypedDict): id: int - flags: List[str] + flags: list[str] mentioned_user_group_id: Optional[int] - users: List[UserData] = [] + users: list[UserData] = [] for user_id in user_list: flags = user_flags.get(user_id, []) # 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")) -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. # 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) -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: if not isinstance(user_id, int): 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)) -def get_validated_emails(emails: Collection[str]) -> List[str]: +def get_validated_emails(emails: Collection[str]) -> list[str]: for email in emails: if not isinstance(email, str): 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: with override_language(sender.bot_owner.default_language): - arg_dict: Dict[str, Any] = { + arg_dict: dict[str, Any] = { "bot_identity": f"`{sender.delivery_email}`", } if stream is None: @@ -1596,14 +1583,14 @@ def check_sender_can_access_recipients( def get_recipients_for_user_creation_events( 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 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 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 # guests, then there is no possible can_access_all_users_group @@ -1666,7 +1653,7 @@ def check_message( skip_stream_access_check: bool = False, message_type: int = Message.MessageType.NORMAL, 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, ) -> SendMessageRequest: """See @@ -1841,7 +1828,7 @@ def _internal_prep_message( email_gateway: bool = False, message_type: int = Message.MessageType.NORMAL, 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, forged: bool = False, forged_timestamp: Optional[float] = None, @@ -1896,7 +1883,7 @@ def internal_prep_stream_message( *, email_gateway: bool = False, 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_timestamp: Optional[float] = None, ) -> Optional[SendMessageRequest]: @@ -1993,7 +1980,7 @@ def internal_send_stream_message( *, email_gateway: bool = False, message_type: int = Message.MessageType.NORMAL, - limit_unread_user_ids: Optional[Set[int]] = None, + limit_unread_user_ids: Optional[set[int]] = None, ) -> Optional[int]: message = internal_prep_stream_message( sender, @@ -2038,8 +2025,8 @@ def internal_prep_group_direct_message( sender: UserProfile, content: str, *, - emails: Optional[List[str]] = None, - recipient_users: Optional[List[UserProfile]] = None, + emails: Optional[list[str]] = None, + recipient_users: Optional[list[UserProfile]] = None, ) -> Optional[SendMessageRequest]: if recipient_users is not None: addressee = Addressee.for_user_profiles(recipient_users) @@ -2060,8 +2047,8 @@ def internal_send_group_direct_message( sender: UserProfile, content: str, *, - emails: Optional[List[str]] = None, - recipient_users: Optional[List[UserProfile]] = None, + emails: Optional[list[str]] = None, + recipient_users: Optional[list[UserProfile]] = None, ) -> Optional[int]: message = internal_prep_group_direct_message( realm, sender, content, emails=emails, recipient_users=recipient_users diff --git a/zerver/actions/reactions.py b/zerver/actions/reactions.py index c6c0c06e6b..201d2d056f 100644 --- a/zerver/actions/reactions.py +++ b/zerver/actions/reactions.py @@ -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.lib.emoji import check_emoji_request, get_emoji_data @@ -26,7 +26,7 @@ def notify_reaction_update( "full_name": user_profile.full_name, } - event: Dict[str, Any] = { + event: dict[str, Any] = { "type": "reaction", "op": op, "user_id": user_profile.id, diff --git a/zerver/actions/realm_emoji.py b/zerver/actions/realm_emoji.py index d813b1662d..2101f67b45 100644 --- a/zerver/actions/realm_emoji.py +++ b/zerver/actions/realm_emoji.py @@ -1,4 +1,4 @@ -from typing import IO, Dict, Optional +from typing import IO, Optional from django.core.exceptions import ValidationError 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 -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) send_event_on_commit(realm, event, active_user_ids(realm.id)) diff --git a/zerver/actions/realm_linkifiers.py b/zerver/actions/realm_linkifiers.py index c5f243d0a8..8fe7293773 100644 --- a/zerver/actions/realm_linkifiers.py +++ b/zerver/actions/realm_linkifiers.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional +from typing import Optional from django.db import transaction 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 -def notify_linkifiers(realm: Realm, realm_linkifiers: List[LinkifierDict]) -> None: - event: Dict[str, object] = dict(type="realm_linkifiers", realm_linkifiers=realm_linkifiers) +def notify_linkifiers(realm: Realm, realm_linkifiers: list[LinkifierDict]) -> None: + event: dict[str, object] = dict(type="realm_linkifiers", realm_linkifiers=realm_linkifiers) send_event_on_commit(realm, event, active_user_ids(realm.id)) @@ -137,7 +137,7 @@ def do_update_linkifier( @transaction.atomic(durable=True) 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: """ordered_linkifier_ids should contain ids of all existing linkifiers. In the rare situation when any of the linkifier gets deleted that more ids diff --git a/zerver/actions/realm_playgrounds.py b/zerver/actions/realm_playgrounds.py index b08bcf046a..e09c7cdae7 100644 --- a/zerver/actions/realm_playgrounds.py +++ b/zerver/actions/realm_playgrounds.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Optional from django.core.exceptions import ValidationError from django.db import transaction @@ -12,7 +12,7 @@ from zerver.models.users import active_user_ids from zerver.tornado.django_api import send_event_on_commit -def notify_realm_playgrounds(realm: Realm, realm_playgrounds: List[RealmPlaygroundDict]) -> None: +def notify_realm_playgrounds(realm: Realm, realm_playgrounds: list[RealmPlaygroundDict]) -> None: event = dict(type="realm_playgrounds", realm_playgrounds=realm_playgrounds) send_event_on_commit(realm, event, active_user_ids(realm.id)) diff --git a/zerver/actions/realm_settings.py b/zerver/actions/realm_settings.py index 236ccb4fc8..b70846c385 100644 --- a/zerver/actions/realm_settings.py +++ b/zerver/actions/realm_settings.py @@ -1,6 +1,6 @@ import logging from email.headerregistry import Address -from typing import Any, Dict, Literal, Optional, Tuple, Union +from typing import Any, Literal, Optional, Union import zoneinfo from django.conf import settings @@ -220,7 +220,7 @@ def do_change_realm_permission_group_setting( def parse_and_set_setting_value_if_required( realm: Realm, setting_name: str, value: Union[int, str], *, acting_user: Optional[UserProfile] -) -> Tuple[Optional[int], bool]: +) -> tuple[Optional[int], bool]: parsed_value = parse_message_time_limit_setting( value, Realm.MESSAGE_TIME_LIMIT_SETTING_SPECIAL_VALUES_MAP, @@ -245,8 +245,8 @@ def parse_and_set_setting_value_if_required( def get_realm_authentication_methods_for_page_params_api( - realm: Realm, authentication_methods: Dict[str, bool] -) -> Dict[str, Any]: + realm: Realm, authentication_methods: dict[str, bool] +) -> dict[str, Any]: # To avoid additional queries, this expects passing in the authentication_methods # dictionary directly, which is useful when the caller already has to fetch it # for other purposes - and that's the circumstance in which this function is @@ -254,7 +254,7 @@ def get_realm_authentication_methods_for_page_params_api( from zproject.backends import AUTH_BACKEND_NAME_MAP - result_dict: Dict[str, Dict[str, Union[str, bool]]] = { + result_dict: dict[str, dict[str, Union[str, bool]]] = { backend_name: {"enabled": enabled, "available": True} for backend_name, enabled in authentication_methods.items() } @@ -296,7 +296,7 @@ def get_realm_authentication_methods_for_page_params_api( def validate_authentication_methods_dict_from_api( - realm: Realm, authentication_methods: Dict[str, bool] + realm: Realm, authentication_methods: dict[str, bool] ) -> None: current_authentication_methods = realm.authentication_methods_dict() for name in authentication_methods: @@ -312,7 +312,7 @@ def validate_authentication_methods_dict_from_api( def validate_plan_for_authentication_methods( - realm: Realm, authentication_methods: Dict[str, bool] + realm: Realm, authentication_methods: dict[str, bool] ) -> None: from zproject.backends import AUTH_BACKEND_NAME_MAP @@ -335,7 +335,7 @@ def validate_plan_for_authentication_methods( def do_set_realm_authentication_methods( - realm: Realm, authentication_methods: Dict[str, bool], *, acting_user: Optional[UserProfile] + realm: Realm, authentication_methods: dict[str, bool], *, acting_user: Optional[UserProfile] ) -> None: old_value = realm.authentication_methods_dict() with transaction.atomic(): @@ -813,7 +813,7 @@ def do_send_realm_reactivation_email(realm: Realm, *, acting_user: Optional[User def do_send_realm_deactivation_email(realm: Realm, acting_user: Optional[UserProfile]) -> None: - shared_context: Dict[str, Any] = { + shared_context: dict[str, Any] = { "realm_name": realm.name, } deactivation_time = timezone_now() diff --git a/zerver/actions/scheduled_messages.py b/zerver/actions/scheduled_messages.py index d7438445e1..b5243c9a09 100644 --- a/zerver/actions/scheduled_messages.py +++ b/zerver/actions/scheduled_messages.py @@ -1,6 +1,6 @@ import logging from datetime import datetime, timedelta -from typing import List, Optional, Sequence, Tuple +from typing import Optional, Sequence from django.conf import settings from django.db import transaction @@ -33,7 +33,7 @@ def check_schedule_message( sender: UserProfile, client: Client, recipient_type_name: str, - message_to: List[int], + message_to: list[int], topic_name: Optional[str], message_content: str, deliver_at: datetime, @@ -69,8 +69,8 @@ def do_schedule_messages( sender: UserProfile, *, read_by_sender: bool = False, -) -> List[int]: - scheduled_messages: List[Tuple[ScheduledMessage, SendMessageRequest]] = [] +) -> list[int]: + scheduled_messages: list[tuple[ScheduledMessage, SendMessageRequest]] = [] for send_request in send_message_requests: scheduled_message = ScheduledMessage() diff --git a/zerver/actions/streams.py b/zerver/actions/streams.py index a3be74d971..414aeec8a0 100644 --- a/zerver/actions/streams.py +++ b/zerver/actions/streams.py @@ -1,6 +1,6 @@ import hashlib from collections import defaultdict -from typing import Any, Collection, Dict, Iterable, List, Mapping, Optional, Set, Tuple +from typing import Any, Collection, Iterable, Mapping, Optional from django.conf import settings from django.db import transaction @@ -73,7 +73,7 @@ from zerver.tornado.django_api import send_event, send_event_on_commit def send_user_remove_events_on_stream_deactivation( - stream: Stream, subscribed_users: List[UserProfile] + stream: Stream, subscribed_users: list[UserProfile] ) -> None: guest_subscribed_users = [user for user in subscribed_users if user.is_guest] @@ -90,7 +90,7 @@ def send_user_remove_events_on_stream_deactivation( ) subscriber_ids = {user.id for user in subscribed_users} - inaccessible_user_dict: Dict[int, Set[int]] = defaultdict(set) + inaccessible_user_dict: dict[int, set[int]] = defaultdict(set) for guest_user in guest_subscribed_users: users_accessible_by_guest = ( {guest_user.id} @@ -212,7 +212,7 @@ def deactivated_streams_by_old_name(realm: Realm, stream_name: str) -> QuerySet[ fixed_length_prefix = ".......!DEACTIVATED:" truncated_name = stream_name[0 : Stream.MAX_NAME_LENGTH - len(fixed_length_prefix)] - old_names: List[str] = [ + old_names: list[str] = [ ("!" * bang_length + "DEACTIVATED:" + stream_name)[: Stream.MAX_NAME_LENGTH] for bang_length in range(1, 21) ] @@ -316,7 +316,7 @@ def do_unarchive_stream( ) -def bulk_delete_cache_keys(message_ids_to_clear: List[int]) -> None: +def bulk_delete_cache_keys(message_ids_to_clear: list[int]) -> None: while len(message_ids_to_clear) > 0: batch = message_ids_to_clear[0:5000] @@ -328,7 +328,7 @@ def bulk_delete_cache_keys(message_ids_to_clear: List[int]) -> None: def merge_streams( realm: Realm, stream_to_keep: Stream, stream_to_destroy: Stream -) -> Tuple[int, int, int]: +) -> tuple[int, int, int]: recipient_to_destroy = stream_to_destroy.recipient recipient_to_keep = stream_to_keep.recipient assert recipient_to_keep is not None @@ -402,10 +402,10 @@ def get_subscriber_ids( def send_subscription_add_events( realm: Realm, - sub_info_list: List[SubInfo], - subscriber_dict: Dict[int, Set[int]], + sub_info_list: list[SubInfo], + subscriber_dict: dict[int, set[int]], ) -> None: - info_by_user: Dict[int, List[SubInfo]] = defaultdict(list) + info_by_user: dict[int, list[SubInfo]] = defaultdict(list) for sub_info in sub_info_list: info_by_user[sub_info.user.id].append(sub_info) @@ -414,7 +414,7 @@ def send_subscription_add_events( # We generally only have a few streams, so we compute subscriber # data in its own loop. - stream_subscribers_dict: Dict[int, List[int]] = {} + stream_subscribers_dict: dict[int, list[int]] = {} for sub_info in sub_info_list: stream = sub_info.stream if stream.id not in stream_subscribers_dict: @@ -425,7 +425,7 @@ def send_subscription_add_events( stream_subscribers_dict[stream.id] = subscribers for user_id, sub_infos in info_by_user.items(): - sub_dicts: List[APISubscriptionDict] = [] + sub_dicts: list[APISubscriptionDict] = [] for sub_info in sub_infos: stream = sub_info.stream stream_subscribers = stream_subscribers_dict[stream.id] @@ -481,8 +481,8 @@ def send_subscription_add_events( def bulk_add_subs_to_db_with_logging( realm: Realm, acting_user: Optional[UserProfile], - subs_to_add: List[SubInfo], - subs_to_activate: List[SubInfo], + subs_to_add: list[SubInfo], + subs_to_activate: list[SubInfo], ) -> None: Subscription.objects.bulk_create(info.sub for info in subs_to_add) sub_ids = [info.sub.id for info in subs_to_activate] @@ -514,9 +514,9 @@ def bulk_add_subs_to_db_with_logging( def send_stream_creation_events_for_previously_inaccessible_streams( realm: Realm, - stream_dict: Dict[int, Stream], - altered_user_dict: Dict[int, Set[int]], - altered_guests: Set[int], + stream_dict: dict[int, Stream], + altered_user_dict: dict[int, set[int]], + altered_guests: set[int], ) -> None: stream_ids = set(altered_user_dict.keys()) recent_traffic = get_streams_traffic(stream_ids, realm) @@ -547,8 +547,8 @@ def send_stream_creation_events_for_previously_inaccessible_streams( def send_peer_subscriber_events( op: str, realm: Realm, - stream_dict: Dict[int, Stream], - altered_user_dict: Dict[int, Set[int]], + stream_dict: dict[int, Stream], + altered_user_dict: dict[int, set[int]], subscriber_peer_info: SubscriberPeerInfo, ) -> None: # Send peer_add/peer_remove events to other users who are tracking the @@ -588,7 +588,7 @@ def send_peer_subscriber_events( subscriber_dict = subscriber_peer_info.subscribed_ids if public_stream_ids: - user_streams: Dict[int, Set[int]] = defaultdict(set) + user_streams: dict[int, set[int]] = defaultdict(set) non_guest_user_ids = set(active_non_guest_user_ids(realm.id)) @@ -643,9 +643,9 @@ def send_peer_subscriber_events( def send_user_creation_events_on_adding_subscriptions( realm: Realm, - altered_user_dict: Dict[int, Set[int]], - altered_streams_dict: Dict[UserProfile, Set[int]], - subscribers_of_altered_user_subscriptions: Dict[int, Set[int]], + altered_user_dict: dict[int, set[int]], + altered_streams_dict: dict[UserProfile, set[int]], + subscribers_of_altered_user_subscriptions: dict[int, set[int]], ) -> None: altered_users = list(altered_streams_dict.keys()) non_guest_user_ids = active_non_guest_user_ids(realm.id) @@ -655,8 +655,8 @@ def send_user_creation_events_on_adding_subscriptions( altered_stream_ids = altered_user_dict.keys() subscribers_dict = get_users_for_streams(set(altered_stream_ids)) - subscribers_user_id_map: Dict[int, UserProfile] = {} - subscriber_ids_dict: Dict[int, Set[int]] = defaultdict(set) + subscribers_user_id_map: dict[int, UserProfile] = {} + subscriber_ids_dict: dict[int, set[int]] = defaultdict(set) for stream_id, subscribers in subscribers_dict.items(): for user in subscribers: subscriber_ids_dict[stream_id].add(user.id) @@ -666,7 +666,7 @@ def send_user_creation_events_on_adding_subscriptions( for user in altered_users: streams_for_user = altered_streams_dict[user] - subscribers_in_altered_streams: Set[int] = set() + subscribers_in_altered_streams: set[int] = set() for stream_id in streams_for_user: subscribers_in_altered_streams |= subscriber_ids_dict[stream_id] @@ -700,7 +700,7 @@ def send_user_creation_events_on_adding_subscriptions( notify_created_user(accessible_user, [user.id]) -SubT: TypeAlias = Tuple[List[SubInfo], List[SubInfo]] +SubT: TypeAlias = tuple[list[SubInfo], list[SubInfo]] @transaction.atomic(savepoint=False) @@ -727,7 +727,7 @@ def bulk_add_subscriptions( recipient_id_to_stream = {stream.recipient_id: stream for stream in streams} recipient_color_map = {} - recipient_ids_set: Set[int] = set() + recipient_ids_set: set[int] = set() for stream in streams: assert stream.recipient_id is not None recipient_ids_set.add(stream.recipient_id) @@ -735,7 +735,7 @@ def bulk_add_subscriptions( if color is not None: recipient_color_map[stream.recipient_id] = color - used_colors_for_user_ids: Dict[int, Set[str]] = get_used_colors_for_user_ids(user_ids) + used_colors_for_user_ids: dict[int, set[str]] = get_used_colors_for_user_ids(user_ids) existing_subs = Subscription.objects.filter( user_profile_id__in=user_ids, @@ -743,20 +743,20 @@ def bulk_add_subscriptions( recipient_id__in=recipient_ids, ) - subs_by_user: Dict[int, List[Subscription]] = defaultdict(list) + subs_by_user: dict[int, list[Subscription]] = defaultdict(list) for sub in existing_subs: subs_by_user[sub.user_profile_id].append(sub) - already_subscribed: List[SubInfo] = [] - subs_to_activate: List[SubInfo] = [] - subs_to_add: List[SubInfo] = [] + already_subscribed: list[SubInfo] = [] + subs_to_activate: list[SubInfo] = [] + subs_to_add: list[SubInfo] = [] for user_profile in users: my_subs = subs_by_user[user_profile.id] # Make a fresh set of all new recipient ids, and then we will # remove any for which our user already has a subscription # (and we'll re-activate any subscriptions as needed). - new_recipient_ids: Set[int] = recipient_ids_set.copy() + new_recipient_ids: set[int] = recipient_ids_set.copy() for sub in my_subs: if sub.recipient_id in new_recipient_ids: @@ -789,9 +789,9 @@ def bulk_add_subscriptions( # We can return early if users are already subscribed to all the streams. return ([], already_subscribed) - altered_user_dict: Dict[int, Set[int]] = defaultdict(set) - altered_guests: Set[int] = set() - altered_streams_dict: Dict[UserProfile, Set[int]] = defaultdict(set) + altered_user_dict: dict[int, set[int]] = defaultdict(set) + altered_guests: set[int] = set() + altered_streams_dict: dict[UserProfile, set[int]] = defaultdict(set) for sub_info in subs_to_add + subs_to_activate: altered_user_dict[sub_info.stream.id].add(sub_info.user.id) altered_streams_dict[sub_info.user].add(sub_info.stream.id) @@ -862,8 +862,8 @@ def bulk_add_subscriptions( def send_peer_remove_events( realm: Realm, - streams: List[Stream], - altered_user_dict: Dict[int, Set[int]], + streams: list[Stream], + altered_user_dict: dict[int, set[int]], ) -> None: subscriber_peer_info = bulk_get_subscriber_peer_info( realm=realm, @@ -888,19 +888,19 @@ def notify_subscriptions_removed( send_event_on_commit(realm, event, [user_profile.id]) -SubAndRemovedT: TypeAlias = Tuple[ - List[Tuple[UserProfile, Stream]], List[Tuple[UserProfile, Stream]] +SubAndRemovedT: TypeAlias = tuple[ + list[tuple[UserProfile, Stream]], list[tuple[UserProfile, Stream]] ] def send_subscription_remove_events( realm: Realm, - users: List[UserProfile], - streams: List[Stream], - removed_subs: List[Tuple[UserProfile, Stream]], + users: list[UserProfile], + streams: list[Stream], + removed_subs: list[tuple[UserProfile, Stream]], ) -> None: - altered_user_dict: Dict[int, Set[int]] = defaultdict(set) - streams_by_user: Dict[int, List[Stream]] = defaultdict(list) + altered_user_dict: dict[int, set[int]] = defaultdict(set) + streams_by_user: dict[int, list[Stream]] = defaultdict(list) for user, stream in removed_subs: streams_by_user[user.id].append(stream) altered_user_dict[stream.id].add(user.id) @@ -941,9 +941,9 @@ def send_subscription_remove_events( def send_user_remove_events_on_removing_subscriptions( - realm: Realm, altered_user_dict: Dict[UserProfile, Set[int]] + realm: Realm, altered_user_dict: dict[UserProfile, set[int]] ) -> None: - altered_stream_ids: Set[int] = set() + altered_stream_ids: set[int] = set() altered_users = list(altered_user_dict.keys()) for stream_ids in altered_user_dict.values(): altered_stream_ids |= stream_ids @@ -958,7 +958,7 @@ def send_user_remove_events_on_removing_subscriptions( subscribers_dict = get_user_ids_for_streams(altered_stream_ids) for user in altered_users: - users_in_unsubscribed_streams: Set[int] = set() + users_in_unsubscribed_streams: set[int] = set() for stream_id in altered_user_dict[user]: users_in_unsubscribed_streams = ( users_in_unsubscribed_streams | subscribers_dict[stream_id] @@ -1022,10 +1022,10 @@ def bulk_remove_subscriptions( existing_subs_by_user = get_bulk_stream_subscriber_info(users, streams) - def get_non_subscribed_subs() -> List[Tuple[UserProfile, Stream]]: + def get_non_subscribed_subs() -> list[tuple[UserProfile, Stream]]: stream_ids = {stream.id for stream in streams} - not_subscribed: List[Tuple[UserProfile, Stream]] = [] + not_subscribed: list[tuple[UserProfile, Stream]] = [] for user_profile in users: user_sub_stream_info = existing_subs_by_user[user_profile.id] @@ -1084,7 +1084,7 @@ def bulk_remove_subscriptions( send_subscription_remove_events(realm, users, streams, removed_sub_tuples) if realm.can_access_all_users_group.named_user_group.name != SystemGroups.EVERYONE: - altered_user_dict: Dict[UserProfile, Set[int]] = defaultdict(set) + altered_user_dict: dict[UserProfile, set[int]] = defaultdict(set) for user, stream in removed_sub_tuples: altered_user_dict[user].add(stream.id) send_user_remove_events_on_removing_subscriptions(realm, altered_user_dict) diff --git a/zerver/actions/typing.py b/zerver/actions/typing.py index cbf198a3bc..1300da882b 100644 --- a/zerver/actions/typing.py +++ b/zerver/actions/typing.py @@ -1,5 +1,3 @@ -from typing import List - from django.conf import settings from django.utils.translation import gettext as _ @@ -11,7 +9,7 @@ from zerver.tornado.django_api import send_event def do_send_typing_notification( - realm: Realm, sender: UserProfile, recipient_user_profiles: List[UserProfile], operator: str + realm: Realm, sender: UserProfile, recipient_user_profiles: list[UserProfile], operator: str ) -> None: sender_dict = {"user_id": sender.id, "email": sender.email} @@ -39,7 +37,7 @@ def do_send_typing_notification( # check_send_typing_notification: # Checks the typing notification and sends it -def check_send_typing_notification(sender: UserProfile, user_ids: List[int], operator: str) -> None: +def check_send_typing_notification(sender: UserProfile, user_ids: list[int], operator: str) -> None: realm = sender.realm if sender.id not in user_ids: diff --git a/zerver/actions/uploads.py b/zerver/actions/uploads.py index 58419ac3d4..9fb589172d 100644 --- a/zerver/actions/uploads.py +++ b/zerver/actions/uploads.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict, List, Union +from typing import Any, Union from zerver.lib.attachments import get_old_unclaimed_attachments, validate_attachment_request from zerver.lib.markdown import MessageRenderingResult @@ -9,7 +9,7 @@ from zerver.tornado.django_api import send_event_on_commit def notify_attachment_update( - user_profile: UserProfile, op: str, attachment_dict: Dict[str, Any] + user_profile: UserProfile, op: str, attachment_dict: dict[str, Any] ) -> None: event = { "type": "attachment", @@ -21,7 +21,7 @@ def notify_attachment_update( def do_claim_attachments( - message: Union[Message, ScheduledMessage], potential_path_ids: List[str] + message: Union[Message, ScheduledMessage], potential_path_ids: list[str] ) -> bool: claimed = False for path_id in potential_path_ids: diff --git a/zerver/actions/user_groups.py b/zerver/actions/user_groups.py index 58eb92d17c..4cff32f24d 100644 --- a/zerver/actions/user_groups.py +++ b/zerver/actions/user_groups.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Dict, List, Mapping, Optional, Sequence, TypedDict, Union +from typing import Mapping, Optional, Sequence, TypedDict, Union import django.db.utils from django.db import transaction @@ -37,7 +37,7 @@ class MemberGroupUserDict(TypedDict): @transaction.atomic def create_user_group_in_database( name: str, - members: List[UserProfile], + members: list[UserProfile], realm: Realm, *, acting_user: Optional[UserProfile], @@ -101,8 +101,8 @@ def update_users_in_full_members_system_group( realm=realm, name=SystemGroups.MEMBERS, is_system_group=True ) - full_member_group_users: List[MemberGroupUserDict] = list() - member_group_users: List[MemberGroupUserDict] = list() + full_member_group_users: list[MemberGroupUserDict] = list() + member_group_users: list[MemberGroupUserDict] = list() if affected_user_ids: full_member_group_users = list( @@ -165,7 +165,7 @@ def promote_new_full_members() -> None: def do_send_create_user_group_event( user_group: NamedUserGroup, - members: List[UserProfile], + members: list[UserProfile], direct_subgroups: Sequence[UserGroup] = [], ) -> None: event = dict( @@ -187,7 +187,7 @@ def do_send_create_user_group_event( def check_add_user_group( realm: Realm, name: str, - initial_members: List[UserProfile], + initial_members: list[UserProfile], description: str = "", group_settings_map: Mapping[str, UserGroup] = {}, *, @@ -209,7 +209,7 @@ def check_add_user_group( def do_send_user_group_update_event( - user_group: NamedUserGroup, data: Dict[str, Union[str, int, AnonymousSettingGroupDict]] + user_group: NamedUserGroup, data: dict[str, Union[str, int, AnonymousSettingGroupDict]] ) -> None: event = dict(type="user_group", op="update", group_id=user_group.id, data=data) send_event(user_group.realm, event, active_user_ids(user_group.realm_id)) @@ -261,7 +261,7 @@ def do_update_user_group_description( def do_send_user_group_members_update_event( - event_name: str, user_group: NamedUserGroup, user_ids: List[int] + event_name: str, user_group: NamedUserGroup, user_ids: list[int] ) -> None: event = dict(type="user_group", op=event_name, group_id=user_group.id, user_ids=user_ids) send_event_on_commit(user_group.realm, event, active_user_ids(user_group.realm_id)) @@ -269,8 +269,8 @@ def do_send_user_group_members_update_event( @transaction.atomic(savepoint=False) def bulk_add_members_to_user_groups( - user_groups: List[NamedUserGroup], - user_profile_ids: List[int], + user_groups: list[NamedUserGroup], + user_profile_ids: list[int], *, acting_user: Optional[UserProfile], ) -> None: @@ -305,8 +305,8 @@ def bulk_add_members_to_user_groups( @transaction.atomic(savepoint=False) def bulk_remove_members_from_user_groups( - user_groups: List[NamedUserGroup], - user_profile_ids: List[int], + user_groups: list[NamedUserGroup], + user_profile_ids: list[int], *, acting_user: Optional[UserProfile], ) -> None: @@ -337,7 +337,7 @@ def bulk_remove_members_from_user_groups( def do_send_subgroups_update_event( - event_name: str, user_group: NamedUserGroup, subgroup_ids: List[int] + event_name: str, user_group: NamedUserGroup, subgroup_ids: list[int] ) -> None: event = dict( type="user_group", op=event_name, group_id=user_group.id, direct_subgroup_ids=subgroup_ids @@ -348,7 +348,7 @@ def do_send_subgroups_update_event( @transaction.atomic def add_subgroups_to_user_group( user_group: NamedUserGroup, - subgroups: List[NamedUserGroup], + subgroups: list[NamedUserGroup], *, acting_user: Optional[UserProfile], ) -> None: @@ -388,7 +388,7 @@ def add_subgroups_to_user_group( @transaction.atomic def remove_subgroups_from_user_group( user_group: NamedUserGroup, - subgroups: List[NamedUserGroup], + subgroups: list[NamedUserGroup], *, acting_user: Optional[UserProfile], ) -> None: @@ -475,7 +475,7 @@ def do_change_user_group_permission_setting( }, ) - event_data_dict: Dict[str, Union[str, int, AnonymousSettingGroupDict]] = { + event_data_dict: dict[str, Union[str, int, AnonymousSettingGroupDict]] = { setting_name: new_setting_api_value } do_send_user_group_update_event(user_group, event_data_dict) diff --git a/zerver/actions/user_topics.py b/zerver/actions/user_topics.py index f12c3334ad..811db68807 100644 --- a/zerver/actions/user_topics.py +++ b/zerver/actions/user_topics.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Any, Dict, List, Optional +from typing import Any, Optional from django.db import transaction from django.utils.timezone import now as timezone_now @@ -15,7 +15,7 @@ from zerver.tornado.django_api import send_event_on_commit @transaction.atomic(savepoint=False) def bulk_do_set_user_topic_visibility_policy( - user_profiles: List[UserProfile], + user_profiles: list[UserProfile], stream: Stream, topic_name: str, *, @@ -51,7 +51,7 @@ def bulk_do_set_user_topic_visibility_policy( ) send_event_on_commit(user_profile.realm, muted_topics_event, [user_profile.id]) - user_topic_event: Dict[str, Any] = { + user_topic_event: dict[str, Any] = { "type": "user_topic", "stream_id": stream.id, "topic_name": topic_name, diff --git a/zerver/actions/users.py b/zerver/actions/users.py index f9c11d72d4..b36ff9c8cb 100644 --- a/zerver/actions/users.py +++ b/zerver/actions/users.py @@ -1,7 +1,7 @@ import secrets from collections import defaultdict from email.headerregistry import Address -from typing import Any, Dict, List, Optional +from typing import Any, Optional from django.conf import settings from django.db import transaction @@ -379,7 +379,7 @@ def do_deactivate_user( def send_stream_events_for_role_update( - user_profile: UserProfile, old_accessible_streams: List[Stream] + user_profile: UserProfile, old_accessible_streams: list[Stream] ) -> None: current_accessible_streams = get_streams_for_user( user_profile, @@ -563,7 +563,7 @@ def do_update_outgoing_webhook_service( ) -def do_update_bot_config_data(bot_profile: UserProfile, config_data: Dict[str, str]) -> None: +def do_update_bot_config_data(bot_profile: UserProfile, config_data: dict[str, str]) -> None: for key, value in config_data.items(): set_bot_config(bot_profile, key, value) updated_config_data = get_bot_config(bot_profile) @@ -581,7 +581,7 @@ def do_update_bot_config_data(bot_profile: UserProfile, config_data: Dict[str, s ) -def get_service_dicts_for_bot(user_profile_id: int) -> List[Dict[str, Any]]: +def get_service_dicts_for_bot(user_profile_id: int) -> list[dict[str, Any]]: user_profile = get_user_profile_by_id(user_profile_id) services = get_bot_services(user_profile_id) if user_profile.bot_type == UserProfile.OUTGOING_WEBHOOK_BOT: @@ -609,10 +609,10 @@ def get_service_dicts_for_bot(user_profile_id: int) -> List[Dict[str, Any]]: def get_service_dicts_for_bots( - bot_dicts: List[Dict[str, Any]], realm: Realm -) -> Dict[int, List[Dict[str, Any]]]: + bot_dicts: list[dict[str, Any]], realm: Realm +) -> dict[int, list[dict[str, Any]]]: bot_profile_ids = [bot_dict["id"] for bot_dict in bot_dicts] - bot_services_by_uid: Dict[int, List[Service]] = defaultdict(list) + bot_services_by_uid: dict[int, list[Service]] = defaultdict(list) for service in Service.objects.filter(user_profile_id__in=bot_profile_ids): bot_services_by_uid[service.user_profile_id].append(service) @@ -621,12 +621,12 @@ def get_service_dicts_for_bots( ] embedded_bot_configs = get_bot_configs(embedded_bot_ids) - service_dicts_by_uid: Dict[int, List[Dict[str, Any]]] = {} + service_dicts_by_uid: dict[int, list[dict[str, Any]]] = {} for bot_dict in bot_dicts: bot_profile_id = bot_dict["id"] bot_type = bot_dict["bot_type"] services = bot_services_by_uid[bot_profile_id] - service_dicts: List[Dict[str, Any]] = [] + service_dicts: list[dict[str, Any]] = [] if bot_type == UserProfile.OUTGOING_WEBHOOK_BOT: service_dicts = [ { @@ -650,7 +650,7 @@ def get_service_dicts_for_bots( def get_owned_bot_dicts( user_profile: UserProfile, include_all_realm_bots_if_admin: bool = True -) -> List[Dict[str, Any]]: +) -> list[dict[str, Any]]: if user_profile.is_realm_admin and include_all_realm_bots_if_admin: result = get_bot_dicts_in_realm(user_profile.realm) else: diff --git a/zerver/actions/video_calls.py b/zerver/actions/video_calls.py index f007598509..ddf57f75b0 100644 --- a/zerver/actions/video_calls.py +++ b/zerver/actions/video_calls.py @@ -1,10 +1,10 @@ -from typing import Dict, Optional +from typing import Optional from zerver.models import UserProfile from zerver.tornado.django_api import send_event -def do_set_zoom_token(user: UserProfile, token: Optional[Dict[str, object]]) -> None: +def do_set_zoom_token(user: UserProfile, token: Optional[dict[str, object]]) -> None: user.zoom_token = token user.save(update_fields=["zoom_token"]) send_event( diff --git a/zerver/context_processors.py b/zerver/context_processors.py index 4591691a32..304c435e84 100644 --- a/zerver/context_processors.py +++ b/zerver/context_processors.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Mapping, Optional +from typing import Any, Mapping, Optional from urllib.parse import urljoin from django.conf import settings @@ -38,7 +38,7 @@ DEFAULT_PAGE_PARAMS: Mapping[str, Any] = { } -def common_context(user: UserProfile) -> Dict[str, Any]: +def common_context(user: UserProfile) -> dict[str, Any]: """Common context used for things like outgoing emails that don't have a request. """ @@ -90,7 +90,7 @@ def is_isolated_page(request: HttpRequest) -> bool: return request.GET.get("nav") == "no" -def zulip_default_corporate_context(request: HttpRequest) -> Dict[str, Any]: +def zulip_default_corporate_context(request: HttpRequest) -> dict[str, Any]: from corporate.lib.decorator import is_self_hosting_management_subdomain # Check if view function is in corporate app. @@ -107,7 +107,7 @@ def zulip_default_corporate_context(request: HttpRequest) -> Dict[str, Any]: } -def zulip_default_context(request: HttpRequest) -> Dict[str, Any]: +def zulip_default_context(request: HttpRequest) -> dict[str, Any]: """Context available to all Zulip Jinja2 templates that have a request passed in. Designed to provide the long list of variables at the bottom of this function in a wide range of situations: logged-in @@ -167,7 +167,7 @@ def zulip_default_context(request: HttpRequest) -> Dict[str, Any]: ) # Sync this with default_params_schema in base_page_params.ts. - default_page_params: Dict[str, Any] = { + default_page_params: dict[str, Any] = { **DEFAULT_PAGE_PARAMS, "request_language": get_language(), } @@ -233,7 +233,7 @@ def zulip_default_context(request: HttpRequest) -> Dict[str, Any]: return context -def login_context(request: HttpRequest) -> Dict[str, Any]: +def login_context(request: HttpRequest) -> dict[str, Any]: realm = get_realm_from_request(request) if realm is None: @@ -247,7 +247,7 @@ def login_context(request: HttpRequest) -> Dict[str, Any]: # public streams configured, in addition to having it enabled. realm_web_public_access_enabled = realm.allow_web_public_streams_access() - context: Dict[str, Any] = { + context: dict[str, Any] = { "realm_invite_required": realm_invite_required, "realm_description": realm_description, "require_email_format_usernames": require_email_format_usernames(realm), @@ -276,7 +276,7 @@ def login_context(request: HttpRequest) -> Dict[str, Any]: return context -def latest_info_context() -> Dict[str, str]: +def latest_info_context() -> dict[str, str]: context = { "latest_release_version": LATEST_RELEASE_VERSION, "latest_major_version": LATEST_MAJOR_VERSION, @@ -285,7 +285,7 @@ def latest_info_context() -> Dict[str, str]: return context -def get_realm_create_form_context() -> Dict[str, Any]: +def get_realm_create_form_context() -> dict[str, Any]: context = { "language_list": get_language_list(), "MAX_REALM_NAME_LENGTH": str(Realm.MAX_REALM_NAME_LENGTH), diff --git a/zerver/data_import/import_util.py b/zerver/data_import/import_util.py index c7af3a6531..51ae37457b 100644 --- a/zerver/data_import/import_util.py +++ b/zerver/data_import/import_util.py @@ -8,15 +8,11 @@ from typing import ( AbstractSet, Any, Callable, - Dict, Iterable, Iterator, - List, Mapping, Optional, Protocol, - Set, - Tuple, TypeVar, ) @@ -44,17 +40,17 @@ from zerver.models import ( from zproject.backends import all_default_backend_names # stubs -ZerverFieldsT: TypeAlias = Dict[str, Any] +ZerverFieldsT: TypeAlias = dict[str, Any] class SubscriberHandler: def __init__(self) -> None: - self.stream_info: Dict[int, Set[int]] = {} - self.direct_message_group_info: Dict[int, Set[int]] = {} + self.stream_info: dict[int, set[int]] = {} + self.direct_message_group_info: dict[int, set[int]] = {} def set_info( self, - users: Set[int], + users: set[int], stream_id: Optional[int] = None, direct_message_group_id: Optional[int] = None, ) -> None: @@ -67,7 +63,7 @@ class SubscriberHandler: def get_users( self, stream_id: Optional[int] = None, direct_message_group_id: Optional[int] = None - ) -> Set[int]: + ) -> set[int]: if stream_id is not None: return self.stream_info[stream_id] elif direct_message_group_id is not None: @@ -78,7 +74,7 @@ class SubscriberHandler: def build_zerver_realm( realm_id: int, realm_subdomain: str, time: float, other_product: str -) -> List[ZerverFieldsT]: +) -> list[ZerverFieldsT]: realm = Realm( id=realm_id, name=realm_subdomain, @@ -142,7 +138,7 @@ def build_avatar( email: str, avatar_url: str, timestamp: Any, - avatar_list: List[ZerverFieldsT], + avatar_list: list[ZerverFieldsT], ) -> None: avatar = dict( path=avatar_url, # Save original avatar URL here, which is downloaded later @@ -158,12 +154,12 @@ def build_avatar( avatar_list.append(avatar) -def make_subscriber_map(zerver_subscription: List[ZerverFieldsT]) -> Dict[int, Set[int]]: +def make_subscriber_map(zerver_subscription: list[ZerverFieldsT]) -> dict[int, set[int]]: """ This can be convenient for building up UserMessage rows. """ - subscriber_map: Dict[int, Set[int]] = {} + subscriber_map: dict[int, set[int]] = {} for sub in zerver_subscription: user_id = sub["user_profile"] recipient_id = sub["recipient"] @@ -175,12 +171,12 @@ def make_subscriber_map(zerver_subscription: List[ZerverFieldsT]) -> Dict[int, S def make_user_messages( - zerver_message: List[ZerverFieldsT], - subscriber_map: Dict[int, Set[int]], + zerver_message: list[ZerverFieldsT], + subscriber_map: dict[int, set[int]], is_pm_data: bool, - mention_map: Dict[int, Set[int]], + mention_map: dict[int, set[int]], wildcard_mention_map: Mapping[int, bool] = {}, -) -> List[ZerverFieldsT]: +) -> list[ZerverFieldsT]: zerver_usermessage = [] for message in zerver_message: @@ -215,15 +211,15 @@ def build_subscription(recipient_id: int, user_id: int, subscription_id: int) -> class GetUsers(Protocol): - def __call__(self, stream_id: int = ..., direct_message_group_id: int = ...) -> Set[int]: ... + def __call__(self, stream_id: int = ..., direct_message_group_id: int = ...) -> set[int]: ... def build_stream_subscriptions( get_users: GetUsers, - zerver_recipient: List[ZerverFieldsT], - zerver_stream: List[ZerverFieldsT], -) -> List[ZerverFieldsT]: - subscriptions: List[ZerverFieldsT] = [] + zerver_recipient: list[ZerverFieldsT], + zerver_stream: list[ZerverFieldsT], +) -> list[ZerverFieldsT]: + subscriptions: list[ZerverFieldsT] = [] stream_ids = {stream["id"] for stream in zerver_stream} @@ -248,10 +244,10 @@ def build_stream_subscriptions( def build_direct_message_group_subscriptions( get_users: GetUsers, - zerver_recipient: List[ZerverFieldsT], - zerver_direct_message_group: List[ZerverFieldsT], -) -> List[ZerverFieldsT]: - subscriptions: List[ZerverFieldsT] = [] + zerver_recipient: list[ZerverFieldsT], + zerver_direct_message_group: list[ZerverFieldsT], +) -> list[ZerverFieldsT]: + subscriptions: list[ZerverFieldsT] = [] direct_message_group_ids = { direct_message_group["id"] for direct_message_group in zerver_direct_message_group @@ -277,8 +273,8 @@ def build_direct_message_group_subscriptions( return subscriptions -def build_personal_subscriptions(zerver_recipient: List[ZerverFieldsT]) -> List[ZerverFieldsT]: - subscriptions: List[ZerverFieldsT] = [] +def build_personal_subscriptions(zerver_recipient: list[ZerverFieldsT]) -> list[ZerverFieldsT]: + subscriptions: list[ZerverFieldsT] = [] personal_recipients = [ recipient for recipient in zerver_recipient if recipient["type"] == Recipient.PERSONAL @@ -311,7 +307,7 @@ def build_recipients( zerver_userprofile: Iterable[ZerverFieldsT], zerver_stream: Iterable[ZerverFieldsT], zerver_direct_message_group: Iterable[ZerverFieldsT] = [], -) -> List[ZerverFieldsT]: +) -> list[ZerverFieldsT]: """ This function was only used HipChat import, this function may be required for future conversions. The Slack conversions do it more @@ -356,7 +352,7 @@ def build_recipients( def build_realm( - zerver_realm: List[ZerverFieldsT], realm_id: int, domain_name: str + zerver_realm: list[ZerverFieldsT], realm_id: int, domain_name: str ) -> ZerverFieldsT: realm = dict( zerver_client=[ @@ -389,14 +385,14 @@ def build_realm( def build_usermessages( - zerver_usermessage: List[ZerverFieldsT], - subscriber_map: Dict[int, Set[int]], + zerver_usermessage: list[ZerverFieldsT], + subscriber_map: dict[int, set[int]], recipient_id: int, - mentioned_user_ids: List[int], + mentioned_user_ids: list[int], message_id: int, is_private: bool, long_term_idle: AbstractSet[int] = set(), -) -> Tuple[int, int]: +) -> tuple[int, int]: user_ids = subscriber_map.get(recipient_id, set()) user_messages_created = 0 @@ -529,11 +525,11 @@ def build_message( def build_attachment( realm_id: int, - message_ids: Set[int], + message_ids: set[int], user_id: int, fileinfo: ZerverFieldsT, s3_path: str, - zerver_attachment: List[ZerverFieldsT], + zerver_attachment: list[ZerverFieldsT], ) -> None: """ This function should be passed a 'fileinfo' dictionary, which contains @@ -558,7 +554,7 @@ def build_attachment( zerver_attachment.append(attachment_dict) -def get_avatar(avatar_dir: str, size_url_suffix: str, avatar_upload_item: List[str]) -> None: +def get_avatar(avatar_dir: str, size_url_suffix: str, avatar_upload_item: list[str]) -> None: avatar_url = avatar_upload_item[0] image_path = os.path.join(avatar_dir, avatar_upload_item[1]) @@ -571,12 +567,12 @@ def get_avatar(avatar_dir: str, size_url_suffix: str, avatar_upload_item: List[s def process_avatars( - avatar_list: List[ZerverFieldsT], + avatar_list: list[ZerverFieldsT], avatar_dir: str, realm_id: int, threads: int, size_url_suffix: str = "", -) -> List[ZerverFieldsT]: +) -> list[ZerverFieldsT]: """ This function gets the avatar of the user and saves it in the user's avatar directory with both the extensions '.png' and '.original' @@ -637,7 +633,7 @@ def wrapping_function(f: Callable[[ListJobData], None], item: ListJobData) -> No def run_parallel_wrapper( - f: Callable[[ListJobData], None], full_items: List[ListJobData], threads: int = 6 + f: Callable[[ListJobData], None], full_items: list[ListJobData], threads: int = 6 ) -> None: logging.info("Distributing %s items across %s threads", len(full_items), threads) @@ -650,7 +646,7 @@ def run_parallel_wrapper( logging.info("Finished %s items", count) -def get_uploads(upload_dir: str, upload: List[str]) -> None: +def get_uploads(upload_dir: str, upload: list[str]) -> None: upload_url = upload[0] upload_path = upload[1] upload_path = os.path.join(upload_dir, upload_path) @@ -662,8 +658,8 @@ def get_uploads(upload_dir: str, upload: List[str]) -> None: def process_uploads( - upload_list: List[ZerverFieldsT], upload_dir: str, threads: int -) -> List[ZerverFieldsT]: + upload_list: list[ZerverFieldsT], upload_dir: str, threads: int +) -> list[ZerverFieldsT]: """ This function downloads the uploads and saves it in the realm's upload directory. Required parameters: @@ -698,7 +694,7 @@ def build_realm_emoji(realm_id: int, name: str, id: int, file_name: str) -> Zerv ) -def get_emojis(emoji_dir: str, upload: List[str]) -> None: +def get_emojis(emoji_dir: str, upload: list[str]) -> None: emoji_url = upload[0] emoji_path = upload[1] upload_emoji_path = os.path.join(emoji_dir, emoji_path) @@ -710,11 +706,11 @@ def get_emojis(emoji_dir: str, upload: List[str]) -> None: def process_emojis( - zerver_realmemoji: List[ZerverFieldsT], + zerver_realmemoji: list[ZerverFieldsT], emoji_dir: str, emoji_url_map: ZerverFieldsT, threads: int, -) -> List[ZerverFieldsT]: +) -> list[ZerverFieldsT]: """ This function downloads the custom emojis and saves in the output emoji folder. Required parameters: @@ -767,16 +763,16 @@ def long_term_idle_helper( timestamp_from_message: Callable[[ZerverFieldsT], float], zulip_user_id_from_user: Callable[[ExternalId], int], all_user_ids_iterator: Iterator[ExternalId], - zerver_userprofile: List[ZerverFieldsT], -) -> Set[int]: + zerver_userprofile: list[ZerverFieldsT], +) -> set[int]: """Algorithmically, we treat users who have sent at least 10 messages or have sent a message within the last 60 days as active. Everyone else is treated as long-term idle, which means they will have a slightly slower first page load when coming back to Zulip. """ - sender_counts: Dict[ExternalId, int] = defaultdict(int) - recent_senders: Set[ExternalId] = set() + sender_counts: dict[ExternalId, int] = defaultdict(int) + recent_senders: set[ExternalId] = set() NOW = float(timezone_now().timestamp()) for message in message_iterator: timestamp = timestamp_from_message(message) diff --git a/zerver/data_import/mattermost.py b/zerver/data_import/mattermost.py index a7724e4477..d7df1f898e 100644 --- a/zerver/data_import/mattermost.py +++ b/zerver/data_import/mattermost.py @@ -10,7 +10,7 @@ import re import secrets import shutil import subprocess -from typing import Any, Callable, Dict, List, Set, Tuple +from typing import Any, Callable import orjson from django.conf import settings @@ -45,7 +45,7 @@ from zerver.lib.utils import process_list_in_batches from zerver.models import Reaction, RealmEmoji, Recipient, UserProfile -def make_realm(realm_id: int, team: Dict[str, Any]) -> ZerverFieldsT: +def make_realm(realm_id: int, team: dict[str, Any]) -> ZerverFieldsT: # set correct realm details NOW = float(timezone_now().timestamp()) domain_name = settings.EXTERNAL_HOST @@ -61,9 +61,9 @@ def make_realm(realm_id: int, team: Dict[str, Any]) -> ZerverFieldsT: def process_user( - user_dict: Dict[str, Any], realm_id: int, team_name: str, user_id_mapper: IdMapper + user_dict: dict[str, Any], realm_id: int, team_name: str, user_id_mapper: IdMapper ) -> ZerverFieldsT: - def is_team_admin(user_dict: Dict[str, Any]) -> bool: + def is_team_admin(user_dict: dict[str, Any]) -> bool: if user_dict["teams"] is None: return False return any( @@ -71,7 +71,7 @@ def process_user( for team in user_dict["teams"] ) - def is_team_guest(user_dict: Dict[str, Any]) -> bool: + def is_team_guest(user_dict: dict[str, Any]) -> bool: if user_dict["teams"] is None: return False for team in user_dict["teams"]: @@ -79,7 +79,7 @@ def process_user( return True return False - def get_full_name(user_dict: Dict[str, Any]) -> str: + def get_full_name(user_dict: dict[str, Any]) -> str: full_name = "{} {}".format(user_dict["first_name"], user_dict["last_name"]) if full_name.strip(): return full_name @@ -127,7 +127,7 @@ def process_user( def convert_user_data( user_handler: UserHandler, user_id_mapper: IdMapper, - user_data_map: Dict[str, Dict[str, Any]], + user_data_map: dict[str, dict[str, Any]], realm_id: int, team_name: str, ) -> None: @@ -143,18 +143,18 @@ def convert_user_data( def convert_channel_data( - channel_data: List[ZerverFieldsT], - user_data_map: Dict[str, Dict[str, Any]], + channel_data: list[ZerverFieldsT], + user_data_map: dict[str, dict[str, Any]], subscriber_handler: SubscriberHandler, stream_id_mapper: IdMapper, user_id_mapper: IdMapper, realm_id: int, team_name: str, -) -> List[ZerverFieldsT]: +) -> list[ZerverFieldsT]: channel_data_list = [d for d in channel_data if d["team"] == team_name] - channel_members_map: Dict[str, List[str]] = {} - channel_admins_map: Dict[str, List[str]] = {} + channel_members_map: dict[str, list[str]] = {} + channel_admins_map: dict[str, list[str]] = {} def initialize_stream_membership_dicts() -> None: for channel in channel_data: @@ -232,7 +232,7 @@ def convert_channel_data( return streams -def generate_direct_message_group_name(direct_message_group_members: List[str]) -> str: +def generate_direct_message_group_name(direct_message_group_members: list[str]) -> str: # Simple hash function to generate a unique hash key for the # members of a direct_message_group. Needs to be consistent # only within the lifetime of export tool run, as it doesn't @@ -243,14 +243,14 @@ def generate_direct_message_group_name(direct_message_group_members: List[str]) def convert_direct_message_group_data( - direct_message_group_data: List[ZerverFieldsT], - user_data_map: Dict[str, Dict[str, Any]], + direct_message_group_data: list[ZerverFieldsT], + user_data_map: dict[str, dict[str, Any]], subscriber_handler: SubscriberHandler, huddle_id_mapper: IdMapper, user_id_mapper: IdMapper, realm_id: int, team_name: str, -) -> List[ZerverFieldsT]: +) -> list[ZerverFieldsT]: zerver_direct_message_group = [] for direct_message_group in direct_message_group_data: if len(direct_message_group["members"]) > 2: @@ -272,11 +272,11 @@ def convert_direct_message_group_data( def build_reactions( realm_id: int, - total_reactions: List[ZerverFieldsT], - reactions: List[ZerverFieldsT], + total_reactions: list[ZerverFieldsT], + reactions: list[ZerverFieldsT], message_id: int, user_id_mapper: IdMapper, - zerver_realmemoji: List[ZerverFieldsT], + zerver_realmemoji: list[ZerverFieldsT], ) -> None: realmemoji = {} for realm_emoji in zerver_realmemoji: @@ -315,7 +315,7 @@ def build_reactions( total_reactions.append(reaction_dict) -def get_mentioned_user_ids(raw_message: Dict[str, Any], user_id_mapper: IdMapper) -> Set[int]: +def get_mentioned_user_ids(raw_message: dict[str, Any], user_id_mapper: IdMapper) -> set[int]: user_ids = set() content = raw_message["content"] @@ -330,16 +330,16 @@ def get_mentioned_user_ids(raw_message: Dict[str, Any], user_id_mapper: IdMapper def process_message_attachments( - attachments: List[Dict[str, Any]], + attachments: list[dict[str, Any]], realm_id: int, message_id: int, user_id: int, user_handler: UserHandler, - zerver_attachment: List[ZerverFieldsT], - uploads_list: List[ZerverFieldsT], + zerver_attachment: list[ZerverFieldsT], + uploads_list: list[ZerverFieldsT], mattermost_data_dir: str, output_dir: str, -) -> Tuple[str, bool]: +) -> tuple[str, bool]: has_image = False markdown_links = [] @@ -405,20 +405,20 @@ def process_message_attachments( def process_raw_message_batch( realm_id: int, - raw_messages: List[Dict[str, Any]], - subscriber_map: Dict[int, Set[int]], + raw_messages: list[dict[str, Any]], + subscriber_map: dict[int, set[int]], user_id_mapper: IdMapper, user_handler: UserHandler, get_recipient_id_from_receiver_name: Callable[[str, int], int], is_pm_data: bool, output_dir: str, - zerver_realmemoji: List[Dict[str, Any]], - total_reactions: List[Dict[str, Any]], - uploads_list: List[ZerverFieldsT], - zerver_attachment: List[ZerverFieldsT], + zerver_realmemoji: list[dict[str, Any]], + total_reactions: list[dict[str, Any]], + uploads_list: list[ZerverFieldsT], + zerver_attachment: list[ZerverFieldsT], mattermost_data_dir: str, ) -> None: - def fix_mentions(content: str, mention_user_ids: Set[int]) -> str: + def fix_mentions(content: str, mention_user_ids: set[int]) -> str: for user_id in mention_user_ids: user = user_handler.get_user(user_id=user_id) mattermost_mention = "@{short_name}".format(**user) @@ -432,7 +432,7 @@ def process_raw_message_batch( content = content.replace("@here", "@**all**") return content - mention_map: Dict[int, Set[int]] = {} + mention_map: dict[int, set[int]] = {} zerver_message = [] pm_members = {} @@ -544,18 +544,18 @@ def process_posts( num_teams: int, team_name: str, realm_id: int, - post_data: List[Dict[str, Any]], + post_data: list[dict[str, Any]], get_recipient_id_from_receiver_name: Callable[[str, int], int], - subscriber_map: Dict[int, Set[int]], + subscriber_map: dict[int, set[int]], output_dir: str, is_pm_data: bool, masking_content: bool, user_id_mapper: IdMapper, user_handler: UserHandler, - zerver_realmemoji: List[Dict[str, Any]], - total_reactions: List[Dict[str, Any]], - uploads_list: List[ZerverFieldsT], - zerver_attachment: List[ZerverFieldsT], + zerver_realmemoji: list[dict[str, Any]], + total_reactions: list[dict[str, Any]], + uploads_list: list[ZerverFieldsT], + zerver_attachment: list[ZerverFieldsT], mattermost_data_dir: str, ) -> None: post_data_list = [] @@ -571,7 +571,7 @@ def process_posts( if post_team == team_name: post_data_list.append(post) - def message_to_dict(post_dict: Dict[str, Any]) -> Dict[str, Any]: + def message_to_dict(post_dict: dict[str, Any]) -> dict[str, Any]: sender_username = post_dict["user"] sender_id = user_id_mapper.get(sender_username) content = post_dict["message"] @@ -624,7 +624,7 @@ def process_posts( reply["channel_members"] = post_dict["channel_members"] raw_messages.append(message_to_dict(reply)) - def process_batch(lst: List[Dict[str, Any]]) -> None: + def process_batch(lst: list[dict[str, Any]]) -> None: process_raw_message_batch( realm_id=realm_id, raw_messages=lst, @@ -654,19 +654,19 @@ def write_message_data( num_teams: int, team_name: str, realm_id: int, - post_data: Dict[str, List[Dict[str, Any]]], - zerver_recipient: List[ZerverFieldsT], - subscriber_map: Dict[int, Set[int]], + post_data: dict[str, list[dict[str, Any]]], + zerver_recipient: list[ZerverFieldsT], + subscriber_map: dict[int, set[int]], output_dir: str, masking_content: bool, stream_id_mapper: IdMapper, huddle_id_mapper: IdMapper, user_id_mapper: IdMapper, user_handler: UserHandler, - zerver_realmemoji: List[Dict[str, Any]], - total_reactions: List[Dict[str, Any]], - uploads_list: List[ZerverFieldsT], - zerver_attachment: List[ZerverFieldsT], + zerver_realmemoji: list[dict[str, Any]], + total_reactions: list[dict[str, Any]], + uploads_list: list[ZerverFieldsT], + zerver_attachment: list[ZerverFieldsT], mattermost_data_dir: str, ) -> None: stream_id_to_recipient_id = {} @@ -725,8 +725,8 @@ def write_message_data( def write_emoticon_data( - realm_id: int, custom_emoji_data: List[Dict[str, Any]], data_dir: str, output_dir: str -) -> List[ZerverFieldsT]: + realm_id: int, custom_emoji_data: list[dict[str, Any]], data_dir: str, output_dir: str +) -> list[ZerverFieldsT]: """ This function does most of the work for processing emoticons, the bulk of which is copying files. We also write a json file with metadata. @@ -809,15 +809,15 @@ def write_emoticon_data( def create_username_to_user_mapping( - user_data_list: List[Dict[str, Any]], -) -> Dict[str, Dict[str, Any]]: + user_data_list: list[dict[str, Any]], +) -> dict[str, dict[str, Any]]: username_to_user = {} for user in user_data_list: username_to_user[user["username"]] = user return username_to_user -def check_user_in_team(user: Dict[str, Any], team_name: str) -> bool: +def check_user_in_team(user: dict[str, Any], team_name: str) -> bool: if user["teams"] is None: # This is null for users not on any team return False @@ -827,8 +827,8 @@ def check_user_in_team(user: Dict[str, Any], team_name: str) -> bool: def label_mirror_dummy_users( num_teams: int, team_name: str, - mattermost_data: Dict[str, Any], - username_to_user: Dict[str, Dict[str, Any]], + mattermost_data: dict[str, Any], + username_to_user: dict[str, dict[str, Any]], ) -> None: # This function might looks like a great place to label admin users. But # that won't be fully correct since we are iterating only though posts and @@ -848,14 +848,14 @@ def label_mirror_dummy_users( user["is_mirror_dummy"] = True -def reset_mirror_dummy_users(username_to_user: Dict[str, Dict[str, Any]]) -> None: +def reset_mirror_dummy_users(username_to_user: dict[str, dict[str, Any]]) -> None: for username in username_to_user: user = username_to_user[username] user["is_mirror_dummy"] = False -def mattermost_data_file_to_dict(mattermost_data_file: str) -> Dict[str, Any]: - mattermost_data: Dict[str, Any] = {} +def mattermost_data_file_to_dict(mattermost_data_file: str) -> dict[str, Any]: + mattermost_data: dict[str, Any] = {} mattermost_data["version"] = [] mattermost_data["team"] = [] mattermost_data["channel"] = [] @@ -878,7 +878,7 @@ def mattermost_data_file_to_dict(mattermost_data_file: str) -> Dict[str, Any]: def do_convert_data(mattermost_data_dir: str, output_dir: str, masking_content: bool) -> None: - username_to_user: Dict[str, Dict[str, Any]] = {} + username_to_user: dict[str, dict[str, Any]] = {} os.makedirs(output_dir, exist_ok=True) if os.listdir(output_dir): # nocoverage @@ -927,7 +927,7 @@ def do_convert_data(mattermost_data_dir: str, output_dir: str, masking_content: ) realm["zerver_stream"] = zerver_stream - zerver_direct_message_group: List[ZerverFieldsT] = [] + zerver_direct_message_group: list[ZerverFieldsT] = [] if len(mattermost_data["team"]) == 1: zerver_direct_message_group = convert_direct_message_group_data( direct_message_group_data=mattermost_data["direct_channel"], @@ -984,9 +984,9 @@ def do_convert_data(mattermost_data_dir: str, output_dir: str, masking_content: zerver_subscription=zerver_subscription, ) - total_reactions: List[Dict[str, Any]] = [] - uploads_list: List[ZerverFieldsT] = [] - zerver_attachment: List[ZerverFieldsT] = [] + total_reactions: list[dict[str, Any]] = [] + uploads_list: list[ZerverFieldsT] = [] + zerver_attachment: list[ZerverFieldsT] = [] write_message_data( num_teams=len(mattermost_data["team"]), @@ -1016,6 +1016,6 @@ def do_convert_data(mattermost_data_dir: str, output_dir: str, masking_content: create_converted_data_files([], realm_output_dir, "/avatars/records.json") # Export message attachments - attachment: Dict[str, List[Any]] = {"zerver_attachment": zerver_attachment} + attachment: dict[str, list[Any]] = {"zerver_attachment": zerver_attachment} create_converted_data_files(uploads_list, realm_output_dir, "/uploads/records.json") create_converted_data_files(attachment, realm_output_dir, "/attachment.json") diff --git a/zerver/data_import/rocketchat.py b/zerver/data_import/rocketchat.py index 9ad1aba9ce..421f27a048 100644 --- a/zerver/data_import/rocketchat.py +++ b/zerver/data_import/rocketchat.py @@ -4,7 +4,7 @@ import os import random import secrets import uuid -from typing import Any, Dict, List, Set, Tuple +from typing import Any import bson from django.conf import settings @@ -39,7 +39,7 @@ from zerver.models import Reaction, RealmEmoji, Recipient, UserProfile def make_realm( - realm_id: int, realm_subdomain: str, domain_name: str, rc_instance: Dict[str, Any] + realm_id: int, realm_subdomain: str, domain_name: str, rc_instance: dict[str, Any] ) -> ZerverFieldsT: created_at = float(rc_instance["_createdAt"].timestamp()) @@ -53,14 +53,14 @@ def make_realm( def process_users( - user_id_to_user_map: Dict[str, Dict[str, Any]], + user_id_to_user_map: dict[str, dict[str, Any]], realm_id: int, domain_name: str, user_handler: UserHandler, user_id_mapper: IdMapper, ) -> None: - realm_owners: List[int] = [] - bots: List[int] = [] + realm_owners: list[int] = [] + bots: list[int] = [] for rc_user_id in user_id_to_user_map: user_dict = user_id_to_user_map[rc_user_id] @@ -144,7 +144,7 @@ def truncate_name(name: str, name_id: int, max_length: int = 60) -> str: return name -def get_stream_name(rc_channel: Dict[str, Any]) -> str: +def get_stream_name(rc_channel: dict[str, Any]) -> str: if rc_channel.get("teamMain"): stream_name = f'[TEAM] {rc_channel["name"]}' else: @@ -156,11 +156,11 @@ def get_stream_name(rc_channel: Dict[str, Any]) -> str: def convert_channel_data( - room_id_to_room_map: Dict[str, Dict[str, Any]], - team_id_to_team_map: Dict[str, Dict[str, Any]], + room_id_to_room_map: dict[str, dict[str, Any]], + team_id_to_team_map: dict[str, dict[str, Any]], stream_id_mapper: IdMapper, realm_id: int, -) -> List[ZerverFieldsT]: +) -> list[ZerverFieldsT]: streams = [] for rc_room_id in room_id_to_room_map: @@ -202,14 +202,14 @@ def convert_channel_data( def convert_stream_subscription_data( - user_id_to_user_map: Dict[str, Dict[str, Any]], - dsc_id_to_dsc_map: Dict[str, Dict[str, Any]], - zerver_stream: List[ZerverFieldsT], + user_id_to_user_map: dict[str, dict[str, Any]], + dsc_id_to_dsc_map: dict[str, dict[str, Any]], + zerver_stream: list[ZerverFieldsT], stream_id_mapper: IdMapper, user_id_mapper: IdMapper, subscriber_handler: SubscriberHandler, ) -> None: - stream_members_map: Dict[int, Set[int]] = {} + stream_members_map: dict[int, set[int]] = {} for rc_user_id in user_id_to_user_map: user_dict = user_id_to_user_map[rc_user_id] @@ -239,12 +239,12 @@ def convert_stream_subscription_data( def convert_direct_message_group_data( - huddle_id_to_huddle_map: Dict[str, Dict[str, Any]], + huddle_id_to_huddle_map: dict[str, dict[str, Any]], huddle_id_mapper: IdMapper, user_id_mapper: IdMapper, subscriber_handler: SubscriberHandler, -) -> List[ZerverFieldsT]: - zerver_direct_message_group: List[ZerverFieldsT] = [] +) -> list[ZerverFieldsT]: + zerver_direct_message_group: list[ZerverFieldsT] = [] for rc_huddle_id in huddle_id_to_huddle_map: direct_message_group_id = huddle_id_mapper.get(rc_huddle_id) @@ -264,15 +264,15 @@ def convert_direct_message_group_data( def build_custom_emoji( - realm_id: int, custom_emoji_data: Dict[str, List[Dict[str, Any]]], output_dir: str -) -> List[ZerverFieldsT]: + realm_id: int, custom_emoji_data: dict[str, list[dict[str, Any]]], output_dir: str +) -> list[ZerverFieldsT]: logging.info("Starting to process custom emoji") emoji_folder = os.path.join(output_dir, "emoji") os.makedirs(emoji_folder, exist_ok=True) - zerver_realmemoji: List[ZerverFieldsT] = [] - emoji_records: List[ZerverFieldsT] = [] + zerver_realmemoji: list[ZerverFieldsT] = [] + emoji_records: list[ZerverFieldsT] = [] # Map emoji file_id to emoji file data emoji_file_data = {} @@ -329,10 +329,10 @@ def build_custom_emoji( def build_reactions( - total_reactions: List[ZerverFieldsT], - reactions: List[Dict[str, Any]], + total_reactions: list[ZerverFieldsT], + reactions: list[dict[str, Any]], message_id: int, - zerver_realmemoji: List[ZerverFieldsT], + zerver_realmemoji: list[ZerverFieldsT], ) -> None: realmemoji = {} for emoji in zerver_realmemoji: @@ -369,16 +369,16 @@ def build_reactions( def process_message_attachment( - upload: Dict[str, Any], + upload: dict[str, Any], realm_id: int, message_id: int, user_id: int, user_handler: UserHandler, - zerver_attachment: List[ZerverFieldsT], - uploads_list: List[ZerverFieldsT], - upload_id_to_upload_data_map: Dict[str, Dict[str, Any]], + zerver_attachment: list[ZerverFieldsT], + uploads_list: list[ZerverFieldsT], + upload_id_to_upload_data_map: dict[str, dict[str, Any]], output_dir: str, -) -> Tuple[str, bool]: +) -> tuple[str, bool]: if upload["_id"] not in upload_id_to_upload_data_map: # nocoverage logging.info("Skipping unknown attachment of message_id: %s", message_id) return "", False @@ -456,19 +456,19 @@ def process_message_attachment( def process_raw_message_batch( realm_id: int, - raw_messages: List[Dict[str, Any]], - subscriber_map: Dict[int, Set[int]], + raw_messages: list[dict[str, Any]], + subscriber_map: dict[int, set[int]], user_handler: UserHandler, is_pm_data: bool, output_dir: str, - zerver_realmemoji: List[ZerverFieldsT], - total_reactions: List[ZerverFieldsT], - uploads_list: List[ZerverFieldsT], - zerver_attachment: List[ZerverFieldsT], - upload_id_to_upload_data_map: Dict[str, Dict[str, Any]], + zerver_realmemoji: list[ZerverFieldsT], + total_reactions: list[ZerverFieldsT], + uploads_list: list[ZerverFieldsT], + zerver_attachment: list[ZerverFieldsT], + upload_id_to_upload_data_map: dict[str, dict[str, Any]], ) -> None: def fix_mentions( - content: str, mention_user_ids: Set[int], rc_channel_mention_data: List[Dict[str, str]] + content: str, mention_user_ids: set[int], rc_channel_mention_data: list[dict[str, str]] ) -> str: # Fix user mentions for user_id in mention_user_ids: @@ -490,9 +490,9 @@ def process_raw_message_batch( return content - user_mention_map: Dict[int, Set[int]] = {} - wildcard_mention_map: Dict[int, bool] = {} - zerver_message: List[ZerverFieldsT] = [] + user_mention_map: dict[int, set[int]] = {} + wildcard_mention_map: dict[int, bool] = {} + zerver_message: list[ZerverFieldsT] = [] for raw_message in raw_messages: message_id = NEXT_ID("message") @@ -580,8 +580,8 @@ def process_raw_message_batch( def get_topic_name( - message: Dict[str, Any], - dsc_id_to_dsc_map: Dict[str, Dict[str, Any]], + message: dict[str, Any], + dsc_id_to_dsc_map: dict[str, dict[str, Any]], thread_id_mapper: IdMapper, is_pm_data: bool = False, ) -> str: @@ -605,33 +605,33 @@ def get_topic_name( def process_messages( realm_id: int, - messages: List[Dict[str, Any]], - subscriber_map: Dict[int, Set[int]], + messages: list[dict[str, Any]], + subscriber_map: dict[int, set[int]], is_pm_data: bool, - username_to_user_id_map: Dict[str, str], + username_to_user_id_map: dict[str, str], user_id_mapper: IdMapper, user_handler: UserHandler, - user_id_to_recipient_id: Dict[int, int], + user_id_to_recipient_id: dict[int, int], stream_id_mapper: IdMapper, - stream_id_to_recipient_id: Dict[int, int], + stream_id_to_recipient_id: dict[int, int], huddle_id_mapper: IdMapper, - huddle_id_to_recipient_id: Dict[int, int], + huddle_id_to_recipient_id: dict[int, int], thread_id_mapper: IdMapper, - room_id_to_room_map: Dict[str, Dict[str, Any]], - dsc_id_to_dsc_map: Dict[str, Dict[str, Any]], - direct_id_to_direct_map: Dict[str, Dict[str, Any]], - huddle_id_to_huddle_map: Dict[str, Dict[str, Any]], - zerver_realmemoji: List[ZerverFieldsT], - total_reactions: List[ZerverFieldsT], - uploads_list: List[ZerverFieldsT], - zerver_attachment: List[ZerverFieldsT], - upload_id_to_upload_data_map: Dict[str, Dict[str, Any]], + room_id_to_room_map: dict[str, dict[str, Any]], + dsc_id_to_dsc_map: dict[str, dict[str, Any]], + direct_id_to_direct_map: dict[str, dict[str, Any]], + huddle_id_to_huddle_map: dict[str, dict[str, Any]], + zerver_realmemoji: list[ZerverFieldsT], + total_reactions: list[ZerverFieldsT], + uploads_list: list[ZerverFieldsT], + zerver_attachment: list[ZerverFieldsT], + upload_id_to_upload_data_map: dict[str, dict[str, Any]], output_dir: str, ) -> None: - def list_reactions(reactions: Dict[str, Dict[str, Any]]) -> List[Dict[str, Any]]: + def list_reactions(reactions: dict[str, dict[str, Any]]) -> list[dict[str, Any]]: # List of dictionaries of form: # {"name": "smile", "user_id": 2} - reactions_list: List[Dict[str, Any]] = [] + reactions_list: list[dict[str, Any]] = [] for react_code in reactions: name = react_code.split(":")[1] usernames = reactions[react_code]["usernames"] @@ -648,7 +648,7 @@ def process_messages( return reactions_list - def message_to_dict(message: Dict[str, Any]) -> Dict[str, Any]: + def message_to_dict(message: dict[str, Any]) -> dict[str, Any]: rc_sender_id = message["u"]["_id"] sender_id = user_id_mapper.get(rc_sender_id) if "msg" in message: @@ -731,7 +731,7 @@ def process_messages( message_dict["wildcard_mention"] = wildcard_mention # Add channel mentions to message_dict - rc_channel_mention_data: List[Dict[str, str]] = [] + rc_channel_mention_data: list[dict[str, str]] = [] for mention in message.get("channels", []): mention_rc_channel_id = mention["_id"] mention_rc_channel_name = mention["name"] @@ -786,7 +786,7 @@ def process_messages( return message_dict - raw_messages: List[Dict[str, Any]] = [] + raw_messages: list[dict[str, Any]] = [] for message in messages: if message.get("t") is not None: # Messages with a type are system notifications like user_joined @@ -794,7 +794,7 @@ def process_messages( continue raw_messages.append(message_to_dict(message)) - def process_batch(lst: List[Dict[str, Any]]) -> None: + def process_batch(lst: list[dict[str, Any]]) -> None: process_raw_message_batch( realm_id=realm_id, raw_messages=lst, @@ -819,9 +819,9 @@ def process_messages( def map_upload_id_to_upload_data( - upload_data: Dict[str, List[Dict[str, Any]]], -) -> Dict[str, Dict[str, Any]]: - upload_id_to_upload_data_map: Dict[str, Dict[str, Any]] = {} + upload_data: dict[str, list[dict[str, Any]]], +) -> dict[str, dict[str, Any]]: + upload_id_to_upload_data_map: dict[str, dict[str, Any]] = {} for upload in upload_data["upload"]: upload_id_to_upload_data_map[upload["_id"]] = {**upload, "chunk": []} @@ -840,14 +840,14 @@ def map_upload_id_to_upload_data( def separate_channel_private_and_livechat_messages( - messages: List[Dict[str, Any]], - dsc_id_to_dsc_map: Dict[str, Dict[str, Any]], - direct_id_to_direct_map: Dict[str, Dict[str, Any]], - huddle_id_to_huddle_map: Dict[str, Dict[str, Any]], - livechat_id_to_livechat_map: Dict[str, Dict[str, Any]], - channel_messages: List[Dict[str, Any]], - private_messages: List[Dict[str, Any]], - livechat_messages: List[Dict[str, Any]], + messages: list[dict[str, Any]], + dsc_id_to_dsc_map: dict[str, dict[str, Any]], + direct_id_to_direct_map: dict[str, dict[str, Any]], + huddle_id_to_huddle_map: dict[str, dict[str, Any]], + livechat_id_to_livechat_map: dict[str, dict[str, Any]], + channel_messages: list[dict[str, Any]], + private_messages: list[dict[str, Any]], + livechat_messages: list[dict[str, Any]], ) -> None: private_channels_list = [*direct_id_to_direct_map, *huddle_id_to_huddle_map] for message in messages: @@ -871,10 +871,10 @@ def separate_channel_private_and_livechat_messages( def map_receiver_id_to_recipient_id( - zerver_recipient: List[ZerverFieldsT], - stream_id_to_recipient_id: Dict[int, int], - huddle_id_to_recipient_id: Dict[int, int], - user_id_to_recipient_id: Dict[int, int], + zerver_recipient: list[ZerverFieldsT], + stream_id_to_recipient_id: dict[int, int], + huddle_id_to_recipient_id: dict[int, int], + user_id_to_recipient_id: dict[int, int], ) -> None: # receiver_id represents stream_id/direct_message_group_id/user_id for recipient in zerver_recipient: @@ -897,22 +897,22 @@ def map_receiver_id_to_recipient_id( # equal and thus will have the same actual direct message group hash # once imported, that get_string_direct_message_group_hash(S) = # get_string_direct_message_group_hash(T). -def get_string_direct_message_group_hash(id_list: List[str]) -> str: +def get_string_direct_message_group_hash(id_list: list[str]) -> str: id_list = sorted(set(id_list)) hash_key = ",".join(str(x) for x in id_list) return hashlib.sha1(hash_key.encode()).hexdigest() def categorize_channels_and_map_with_id( - channel_data: List[Dict[str, Any]], - room_id_to_room_map: Dict[str, Dict[str, Any]], - team_id_to_team_map: Dict[str, Dict[str, Any]], - dsc_id_to_dsc_map: Dict[str, Dict[str, Any]], - direct_id_to_direct_map: Dict[str, Dict[str, Any]], - huddle_id_to_huddle_map: Dict[str, Dict[str, Any]], - livechat_id_to_livechat_map: Dict[str, Dict[str, Any]], + channel_data: list[dict[str, Any]], + room_id_to_room_map: dict[str, dict[str, Any]], + team_id_to_team_map: dict[str, dict[str, Any]], + dsc_id_to_dsc_map: dict[str, dict[str, Any]], + direct_id_to_direct_map: dict[str, dict[str, Any]], + huddle_id_to_huddle_map: dict[str, dict[str, Any]], + livechat_id_to_livechat_map: dict[str, dict[str, Any]], ) -> None: - direct_message_group_hashed_channels: Dict[str, Any] = {} + direct_message_group_hashed_channels: dict[str, Any] = {} for channel in channel_data: if channel.get("prid"): dsc_id_to_dsc_map[channel["_id"]] = channel @@ -972,24 +972,24 @@ def categorize_channels_and_map_with_id( team_id_to_team_map[channel["teamId"]] = channel -def map_username_to_user_id(user_id_to_user_map: Dict[str, Dict[str, Any]]) -> Dict[str, str]: - username_to_user_id_map: Dict[str, str] = {} +def map_username_to_user_id(user_id_to_user_map: dict[str, dict[str, Any]]) -> dict[str, str]: + username_to_user_id_map: dict[str, str] = {} for user_id, user_dict in user_id_to_user_map.items(): username_to_user_id_map[user_dict["username"]] = user_id return username_to_user_id_map -def map_user_id_to_user(user_data_list: List[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]: +def map_user_id_to_user(user_data_list: list[dict[str, Any]]) -> dict[str, dict[str, Any]]: user_id_to_user_map = {} for user in user_data_list: user_id_to_user_map[user["_id"]] = user return user_id_to_user_map -def rocketchat_data_to_dict(rocketchat_data_dir: str) -> Dict[str, Any]: +def rocketchat_data_to_dict(rocketchat_data_dir: str) -> dict[str, Any]: codec_options = bson.DEFAULT_CODEC_OPTIONS.with_options(tz_aware=True) - rocketchat_data: Dict[str, Any] = {} + rocketchat_data: dict[str, Any] = {} rocketchat_data["instance"] = [] rocketchat_data["user"] = [] rocketchat_data["avatar"] = {"avatar": [], "file": [], "chunk": []} @@ -1069,8 +1069,8 @@ def do_convert_data(rocketchat_data_dir: str, output_dir: str) -> None: realm = make_realm(realm_id, realm_subdomain, domain_name, rocketchat_data["instance"][0]) - user_id_to_user_map: Dict[str, Dict[str, Any]] = map_user_id_to_user(rocketchat_data["user"]) - username_to_user_id_map: Dict[str, str] = map_username_to_user_id(user_id_to_user_map) + user_id_to_user_map: dict[str, dict[str, Any]] = map_user_id_to_user(rocketchat_data["user"]) + username_to_user_id_map: dict[str, str] = map_username_to_user_id(user_id_to_user_map) user_handler = UserHandler() subscriber_handler = SubscriberHandler() @@ -1087,12 +1087,12 @@ def do_convert_data(rocketchat_data_dir: str, output_dir: str) -> None: user_id_mapper=user_id_mapper, ) - room_id_to_room_map: Dict[str, Dict[str, Any]] = {} - team_id_to_team_map: Dict[str, Dict[str, Any]] = {} - dsc_id_to_dsc_map: Dict[str, Dict[str, Any]] = {} - direct_id_to_direct_map: Dict[str, Dict[str, Any]] = {} - huddle_id_to_huddle_map: Dict[str, Dict[str, Any]] = {} - livechat_id_to_livechat_map: Dict[str, Dict[str, Any]] = {} + room_id_to_room_map: dict[str, dict[str, Any]] = {} + team_id_to_team_map: dict[str, dict[str, Any]] = {} + dsc_id_to_dsc_map: dict[str, dict[str, Any]] = {} + direct_id_to_direct_map: dict[str, dict[str, Any]] = {} + huddle_id_to_huddle_map: dict[str, dict[str, Any]] = {} + livechat_id_to_livechat_map: dict[str, dict[str, Any]] = {} categorize_channels_and_map_with_id( channel_data=rocketchat_data["room"], @@ -1171,9 +1171,9 @@ def do_convert_data(rocketchat_data_dir: str, output_dir: str) -> None: zerver_subscription=zerver_subscription, ) - stream_id_to_recipient_id: Dict[int, int] = {} - huddle_id_to_recipient_id: Dict[int, int] = {} - user_id_to_recipient_id: Dict[int, int] = {} + stream_id_to_recipient_id: dict[int, int] = {} + huddle_id_to_recipient_id: dict[int, int] = {} + user_id_to_recipient_id: dict[int, int] = {} map_receiver_id_to_recipient_id( zerver_recipient=zerver_recipient, @@ -1182,9 +1182,9 @@ def do_convert_data(rocketchat_data_dir: str, output_dir: str) -> None: user_id_to_recipient_id=user_id_to_recipient_id, ) - channel_messages: List[Dict[str, Any]] = [] - private_messages: List[Dict[str, Any]] = [] - livechat_messages: List[Dict[str, Any]] = [] + channel_messages: list[dict[str, Any]] = [] + private_messages: list[dict[str, Any]] = [] + livechat_messages: list[dict[str, Any]] = [] separate_channel_private_and_livechat_messages( messages=rocketchat_data["message"], @@ -1197,9 +1197,9 @@ def do_convert_data(rocketchat_data_dir: str, output_dir: str) -> None: livechat_messages=livechat_messages, ) - total_reactions: List[ZerverFieldsT] = [] - uploads_list: List[ZerverFieldsT] = [] - zerver_attachment: List[ZerverFieldsT] = [] + total_reactions: list[ZerverFieldsT] = [] + uploads_list: list[ZerverFieldsT] = [] + zerver_attachment: list[ZerverFieldsT] = [] upload_id_to_upload_data_map = map_upload_id_to_upload_data(rocketchat_data["upload"]) @@ -1264,6 +1264,6 @@ def do_convert_data(rocketchat_data_dir: str, output_dir: str) -> None: create_converted_data_files([], output_dir, "/avatars/records.json") # Import attachments - attachment: Dict[str, List[Any]] = {"zerver_attachment": zerver_attachment} + attachment: dict[str, list[Any]] = {"zerver_attachment": zerver_attachment} create_converted_data_files(attachment, output_dir, "/attachment.json") create_converted_data_files(uploads_list, output_dir, "/uploads/records.json") diff --git a/zerver/data_import/sequencer.py b/zerver/data_import/sequencer.py index 99de971212..d50189613c 100644 --- a/zerver/data_import/sequencer.py +++ b/zerver/data_import/sequencer.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Dict +from typing import Any, Callable """ This module helps you set up a bunch @@ -30,7 +30,7 @@ def sequencer() -> Callable[[str], int]: NEXT_ID = sequencer() message_id = NEXT_ID('message') """ - seq_dict: Dict[str, Callable[[], int]] = {} + seq_dict: dict[str, Callable[[], int]] = {} def next_one(name: str) -> int: if name not in seq_dict: @@ -64,7 +64,7 @@ def is_int(key: Any) -> bool: class IdMapper: def __init__(self) -> None: - self.map: Dict[Any, int] = {} + self.map: dict[Any, int] = {} self.cnt = 0 def has(self, their_id: Any) -> bool: diff --git a/zerver/data_import/slack.py b/zerver/data_import/slack.py index 193426448a..fc8974c798 100644 --- a/zerver/data_import/slack.py +++ b/zerver/data_import/slack.py @@ -9,7 +9,7 @@ import zipfile from collections import defaultdict from datetime import datetime, timezone from email.headerregistry import Address -from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Type, TypeVar +from typing import Any, Iterator, Optional, TypeVar from urllib.parse import urlsplit import orjson @@ -60,11 +60,11 @@ from zerver.models import ( UserProfile, ) -SlackToZulipUserIDT: TypeAlias = Dict[str, int] -AddedChannelsT: TypeAlias = Dict[str, Tuple[str, int]] -AddedMPIMsT: TypeAlias = Dict[str, Tuple[str, int]] -DMMembersT: TypeAlias = Dict[str, Tuple[str, str]] -SlackToZulipRecipientT: TypeAlias = Dict[str, int] +SlackToZulipUserIDT: TypeAlias = dict[str, int] +AddedChannelsT: TypeAlias = dict[str, tuple[str, int]] +AddedMPIMsT: TypeAlias = dict[str, tuple[str, int]] +DMMembersT: TypeAlias = dict[str, tuple[str, str]] +SlackToZulipRecipientT: TypeAlias = dict[str, int] # Generic type for SlackBotEmail class SlackBotEmailT = TypeVar("SlackBotEmailT", bound="SlackBotEmail") @@ -77,7 +77,7 @@ with open(emoji_data_file_path, "rb") as emoji_data_file: emoji_data = orjson.loads(emoji_data_file.read()) -def get_emoji_code(emoji_dict: Dict[str, Any]) -> str: +def get_emoji_code(emoji_dict: dict[str, Any]) -> str: # This function is identical with the function with the same name at # tools/setup/emoji/emoji_setup_utils.py. # This function is unlikely to be changed, unless iamcal changes their data @@ -87,7 +87,7 @@ def get_emoji_code(emoji_dict: Dict[str, Any]) -> str: # Build the translation dict from Slack emoji name to codepoint. -slack_emoji_name_to_codepoint: Dict[str, str] = {} +slack_emoji_name_to_codepoint: dict[str, str] = {} for emoji_dict in emoji_data: short_name = emoji_dict["short_name"] emoji_code = get_emoji_code(emoji_dict) @@ -98,12 +98,12 @@ for emoji_dict in emoji_data: class SlackBotEmail: - duplicate_email_count: Dict[str, int] = {} + duplicate_email_count: dict[str, int] = {} # Mapping of `bot_id` to final email assigned to the bot. - assigned_email: Dict[str, str] = {} + assigned_email: dict[str, str] = {} @classmethod - def get_email(cls: Type[SlackBotEmailT], user_profile: ZerverFieldsT, domain_name: str) -> str: + def get_email(cls: type[SlackBotEmailT], user_profile: ZerverFieldsT, domain_name: str) -> str: slack_bot_id = user_profile["bot_id"] if slack_bot_id in cls.assigned_email: return cls.assigned_email[slack_bot_id] @@ -140,18 +140,18 @@ def rm_tree(path: str) -> None: def slack_workspace_to_realm( domain_name: str, realm_id: int, - user_list: List[ZerverFieldsT], + user_list: list[ZerverFieldsT], realm_subdomain: str, slack_data_dir: str, custom_emoji_list: ZerverFieldsT, -) -> Tuple[ +) -> tuple[ ZerverFieldsT, SlackToZulipUserIDT, SlackToZulipRecipientT, AddedChannelsT, AddedMPIMsT, DMMembersT, - List[ZerverFieldsT], + list[ZerverFieldsT], ZerverFieldsT, ]: """ @@ -168,7 +168,7 @@ def slack_workspace_to_realm( """ NOW = float(timezone_now().timestamp()) - zerver_realm: List[ZerverFieldsT] = build_zerver_realm(realm_id, realm_subdomain, NOW, "Slack") + zerver_realm: list[ZerverFieldsT] = build_zerver_realm(realm_id, realm_subdomain, NOW, "Slack") realm = build_realm(zerver_realm, realm_id, domain_name) ( @@ -212,7 +212,7 @@ def slack_workspace_to_realm( def build_realmemoji( custom_emoji_list: ZerverFieldsT, realm_id: int -) -> Tuple[List[ZerverFieldsT], ZerverFieldsT]: +) -> tuple[list[ZerverFieldsT], ZerverFieldsT]: zerver_realmemoji = [] emoji_url_map = {} emoji_id = 0 @@ -241,13 +241,13 @@ def build_realmemoji( def users_to_zerver_userprofile( - slack_data_dir: str, users: List[ZerverFieldsT], realm_id: int, timestamp: Any, domain_name: str -) -> Tuple[ - List[ZerverFieldsT], - List[ZerverFieldsT], + slack_data_dir: str, users: list[ZerverFieldsT], realm_id: int, timestamp: Any, domain_name: str +) -> tuple[ + list[ZerverFieldsT], + list[ZerverFieldsT], SlackToZulipUserIDT, - List[ZerverFieldsT], - List[ZerverFieldsT], + list[ZerverFieldsT], + list[ZerverFieldsT], ]: """ Returns: @@ -260,9 +260,9 @@ def users_to_zerver_userprofile( """ logging.info("######### IMPORTING USERS STARTED #########\n") zerver_userprofile = [] - zerver_customprofilefield: List[ZerverFieldsT] = [] - zerver_customprofilefield_values: List[ZerverFieldsT] = [] - avatar_list: List[ZerverFieldsT] = [] + zerver_customprofilefield: list[ZerverFieldsT] = [] + zerver_customprofilefield_values: list[ZerverFieldsT] = [] + avatar_list: list[ZerverFieldsT] = [] slack_user_id_to_zulip_user_id = {} # The user data we get from the Slack API does not contain custom profile data @@ -282,7 +282,7 @@ def users_to_zerver_userprofile( primary_owner_id = user_id_count user_id_count += 1 - found_emails: Dict[str, int] = {} + found_emails: dict[str, int] = {} for user in users: slack_user_id = user["id"] @@ -369,12 +369,12 @@ def users_to_zerver_userprofile( def build_customprofile_field( - customprofile_field: List[ZerverFieldsT], + customprofile_field: list[ZerverFieldsT], fields: ZerverFieldsT, custom_profile_field_id: int, realm_id: int, slack_custom_field_name_to_zulip_custom_field_id: ZerverFieldsT, -) -> Tuple[ZerverFieldsT, int]: +) -> tuple[ZerverFieldsT, int]: # The name of the custom profile field is not provided in the Slack data # Hash keys of the fields are provided # Reference: https://api.slack.com/methods/users.profile.set @@ -421,7 +421,7 @@ def build_customprofilefields_values( fields: ZerverFieldsT, user_id: int, custom_field_id: int, - custom_field_values: List[ZerverFieldsT], + custom_field_values: list[ZerverFieldsT], ) -> int: for field, value in fields.items(): if value["value"] == "": @@ -440,7 +440,7 @@ def build_customprofilefields_values( def process_customprofilefields( - customprofilefield: List[ZerverFieldsT], customprofilefield_value: List[ZerverFieldsT] + customprofilefield: list[ZerverFieldsT], customprofilefield_value: list[ZerverFieldsT] ) -> None: for field in customprofilefield: for field_value in customprofilefield_value: @@ -500,11 +500,11 @@ def get_user_timezone(user: ZerverFieldsT) -> str: def channels_to_zerver_stream( slack_data_dir: str, realm_id: int, - realm: Dict[str, Any], + realm: dict[str, Any], slack_user_id_to_zulip_user_id: SlackToZulipUserIDT, - zerver_userprofile: List[ZerverFieldsT], -) -> Tuple[ - Dict[str, List[ZerverFieldsT]], AddedChannelsT, AddedMPIMsT, DMMembersT, SlackToZulipRecipientT + zerver_userprofile: list[ZerverFieldsT], +) -> tuple[ + dict[str, list[ZerverFieldsT]], AddedChannelsT, AddedMPIMsT, DMMembersT, SlackToZulipRecipientT ]: """ Returns: @@ -533,7 +533,7 @@ def channels_to_zerver_stream( stream_id_count = defaultstream_id = 0 direct_message_group_id_count = 0 - def process_channels(channels: List[Dict[str, Any]], invite_only: bool = False) -> None: + def process_channels(channels: list[dict[str, Any]], invite_only: bool = False) -> None: nonlocal stream_id_count nonlocal recipient_id_count nonlocal defaultstream_id @@ -605,7 +605,7 @@ def channels_to_zerver_stream( process_channels(private_channels, True) # mpim is the Slack equivalent of direct message group. - def process_mpims(mpims: List[Dict[str, Any]]) -> None: + def process_mpims(mpims: list[dict[str, Any]]) -> None: nonlocal direct_message_group_id_count nonlocal recipient_id_count nonlocal subscription_id_count @@ -642,7 +642,7 @@ def channels_to_zerver_stream( # This may have duplicated zulip user_ids, since we merge multiple # Slack same-email shared-channel users into one Zulip dummy user - zulip_user_to_recipient: Dict[int, int] = {} + zulip_user_to_recipient: dict[int, int] = {} for slack_user_id, zulip_user_id in slack_user_id_to_zulip_user_id.items(): if zulip_user_id in zulip_user_to_recipient: slack_recipient_name_to_zulip_recipient_id[slack_user_id] = zulip_user_to_recipient[ @@ -658,7 +658,7 @@ def channels_to_zerver_stream( recipient_id_count += 1 subscription_id_count += 1 - def process_dms(dms: List[Dict[str, Any]]) -> None: + def process_dms(dms: list[dict[str, Any]]) -> None: for dm in dms: user_a = dm["members"][0] user_b = dm["members"][1] @@ -681,8 +681,8 @@ def channels_to_zerver_stream( def get_subscription( - channel_members: List[str], - zerver_subscription: List[ZerverFieldsT], + channel_members: list[str], + zerver_subscription: list[ZerverFieldsT], recipient_id: int, slack_user_id_to_zulip_user_id: SlackToZulipUserIDT, subscription_id: int, @@ -698,13 +698,13 @@ def get_subscription( def process_long_term_idle_users( slack_data_dir: str, - users: List[ZerverFieldsT], + users: list[ZerverFieldsT], slack_user_id_to_zulip_user_id: SlackToZulipUserIDT, added_channels: AddedChannelsT, added_mpims: AddedMPIMsT, dm_members: DMMembersT, - zerver_userprofile: List[ZerverFieldsT], -) -> Set[int]: + zerver_userprofile: list[ZerverFieldsT], +) -> set[int]: return long_term_idle_helper( get_messages_iterator(slack_data_dir, added_channels, added_mpims, dm_members), get_message_sending_user, @@ -717,7 +717,7 @@ def process_long_term_idle_users( def convert_slack_workspace_messages( slack_data_dir: str, - users: List[ZerverFieldsT], + users: list[ZerverFieldsT], realm_id: int, slack_user_id_to_zulip_user_id: SlackToZulipUserIDT, slack_recipient_name_to_zulip_recipient_id: SlackToZulipRecipientT, @@ -725,13 +725,13 @@ def convert_slack_workspace_messages( added_mpims: AddedMPIMsT, dm_members: DMMembersT, realm: ZerverFieldsT, - zerver_userprofile: List[ZerverFieldsT], - zerver_realmemoji: List[ZerverFieldsT], + zerver_userprofile: list[ZerverFieldsT], + zerver_realmemoji: list[ZerverFieldsT], domain_name: str, output_dir: str, convert_slack_threads: bool, chunk_size: int = MESSAGE_BATCH_CHUNK_SIZE, -) -> Tuple[List[ZerverFieldsT], List[ZerverFieldsT], List[ZerverFieldsT]]: +) -> tuple[list[ZerverFieldsT], list[ZerverFieldsT], list[ZerverFieldsT]]: """ Returns: 1. reactions, which is a list of the reactions @@ -752,9 +752,9 @@ def convert_slack_workspace_messages( all_messages = get_messages_iterator(slack_data_dir, added_channels, added_mpims, dm_members) logging.info("######### IMPORTING MESSAGES STARTED #########\n") - total_reactions: List[ZerverFieldsT] = [] - total_attachments: List[ZerverFieldsT] = [] - total_uploads: List[ZerverFieldsT] = [] + total_reactions: list[ZerverFieldsT] = [] + total_attachments: list[ZerverFieldsT] = [] + total_uploads: list[ZerverFieldsT] = [] dump_file_id = 1 @@ -802,7 +802,7 @@ def convert_slack_workspace_messages( def get_messages_iterator( slack_data_dir: str, - added_channels: Dict[str, Any], + added_channels: dict[str, Any], added_mpims: AddedMPIMsT, dm_members: DMMembersT, ) -> Iterator[ZerverFieldsT]: @@ -812,7 +812,7 @@ def get_messages_iterator( large imports that can OOM kill.""" dir_names = [*added_channels, *added_mpims, *dm_members] - all_json_names: Dict[str, List[str]] = defaultdict(list) + all_json_names: dict[str, list[str]] = defaultdict(list) for dir_name in dir_names: dir_path = os.path.join(slack_data_dir, dir_name) json_names = os.listdir(dir_path) @@ -822,7 +822,7 @@ def get_messages_iterator( # Sort json_name by date for json_name in sorted(all_json_names.keys()): - messages_for_one_day: List[ZerverFieldsT] = [] + messages_for_one_day: list[ZerverFieldsT] = [] for dir_path in all_json_names[json_name]: message_dir = os.path.join(dir_path, json_name) dir_name = os.path.basename(dir_path) @@ -857,23 +857,23 @@ def get_messages_iterator( def channel_message_to_zerver_message( realm_id: int, - users: List[ZerverFieldsT], + users: list[ZerverFieldsT], slack_user_id_to_zulip_user_id: SlackToZulipUserIDT, slack_recipient_name_to_zulip_recipient_id: SlackToZulipRecipientT, - all_messages: List[ZerverFieldsT], - zerver_realmemoji: List[ZerverFieldsT], - subscriber_map: Dict[int, Set[int]], + all_messages: list[ZerverFieldsT], + zerver_realmemoji: list[ZerverFieldsT], + subscriber_map: dict[int, set[int]], added_channels: AddedChannelsT, dm_members: DMMembersT, domain_name: str, - long_term_idle: Set[int], + long_term_idle: set[int], convert_slack_threads: bool, -) -> Tuple[ - List[ZerverFieldsT], - List[ZerverFieldsT], - List[ZerverFieldsT], - List[ZerverFieldsT], - List[ZerverFieldsT], +) -> tuple[ + list[ZerverFieldsT], + list[ZerverFieldsT], + list[ZerverFieldsT], + list[ZerverFieldsT], + list[ZerverFieldsT], ]: """ Returns: @@ -884,15 +884,15 @@ def channel_message_to_zerver_message( 5. reaction_list, which is a list of all user reactions """ zerver_message = [] - zerver_usermessage: List[ZerverFieldsT] = [] - uploads_list: List[ZerverFieldsT] = [] - zerver_attachment: List[ZerverFieldsT] = [] - reaction_list: List[ZerverFieldsT] = [] + zerver_usermessage: list[ZerverFieldsT] = [] + uploads_list: list[ZerverFieldsT] = [] + zerver_attachment: list[ZerverFieldsT] = [] + reaction_list: list[ZerverFieldsT] = [] total_user_messages = 0 total_skipped_user_messages = 0 - thread_counter: Dict[str, int] = defaultdict(int) - thread_map: Dict[str, str] = {} + thread_counter: dict[str, int] = defaultdict(int) + thread_map: dict[str, str] = {} for message in all_messages: slack_user_id = get_message_sending_user(message) if not slack_user_id: @@ -1053,11 +1053,11 @@ def process_message_files( realm_id: int, message_id: int, slack_user_id: str, - users: List[ZerverFieldsT], + users: list[ZerverFieldsT], slack_user_id_to_zulip_user_id: SlackToZulipUserIDT, - zerver_attachment: List[ZerverFieldsT], - uploads_list: List[ZerverFieldsT], -) -> Dict[str, Any]: + zerver_attachment: list[ZerverFieldsT], + uploads_list: list[ZerverFieldsT], +) -> dict[str, Any]: has_attachment = False has_image = False has_link = False @@ -1140,7 +1140,7 @@ def process_message_files( ) -def get_attachment_path_and_content(fileinfo: ZerverFieldsT, realm_id: int) -> Tuple[str, str]: +def get_attachment_path_and_content(fileinfo: ZerverFieldsT, realm_id: int) -> tuple[str, str]: # Should be kept in sync with its equivalent in zerver/lib/uploads in the function # 'upload_message_attachment' s3_path = "/".join( @@ -1158,11 +1158,11 @@ def get_attachment_path_and_content(fileinfo: ZerverFieldsT, realm_id: int) -> T def build_reactions( - reaction_list: List[ZerverFieldsT], - reactions: List[ZerverFieldsT], + reaction_list: list[ZerverFieldsT], + reactions: list[ZerverFieldsT], slack_user_id_to_zulip_user_id: SlackToZulipUserIDT, message_id: int, - zerver_realmemoji: List[ZerverFieldsT], + zerver_realmemoji: list[ZerverFieldsT], ) -> None: realmemoji = {} for realm_emoji in zerver_realmemoji: @@ -1227,7 +1227,7 @@ def build_uploads( email: str, fileinfo: ZerverFieldsT, s3_path: str, - uploads_list: List[ZerverFieldsT], + uploads_list: list[ZerverFieldsT], ) -> None: upload = dict( path=fileinfo["url_private"], # Save Slack's URL here, which is used later while processing @@ -1255,12 +1255,12 @@ def get_timestamp_from_message(message: ZerverFieldsT) -> float: def fetch_shared_channel_users( - user_list: List[ZerverFieldsT], slack_data_dir: str, token: str + user_list: list[ZerverFieldsT], slack_data_dir: str, token: str ) -> None: normal_user_ids = set() mirror_dummy_user_ids = set() added_channels = {} - team_id_to_domain: Dict[str, str] = {} + team_id_to_domain: dict[str, str] = {} for user in user_list: user["is_mirror_dummy"] = False normal_user_ids.add(user["id"]) @@ -1311,8 +1311,8 @@ def fetch_shared_channel_users( def fetch_team_icons( - zerver_realm: Dict[str, Any], team_info_dict: Dict[str, Any], output_dir: str -) -> List[Dict[str, Any]]: + zerver_realm: dict[str, Any], team_info_dict: dict[str, Any], output_dir: str +) -> list[dict[str, Any]]: records = [] team_icons_dict = team_info_dict["icon"] diff --git a/zerver/data_import/slack_message_conversion.py b/zerver/data_import/slack_message_conversion.py index e1c27a8e15..fbd5e2c781 100644 --- a/zerver/data_import/slack_message_conversion.py +++ b/zerver/data_import/slack_message_conversion.py @@ -1,12 +1,12 @@ import re -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Optional from typing_extensions import TypeAlias # stubs -ZerverFieldsT: TypeAlias = Dict[str, Any] -SlackToZulipUserIDT: TypeAlias = Dict[str, int] -AddedChannelsT: TypeAlias = Dict[str, Tuple[str, int]] +ZerverFieldsT: TypeAlias = dict[str, Any] +SlackToZulipUserIDT: TypeAlias = dict[str, int] +AddedChannelsT: TypeAlias = dict[str, tuple[str, int]] # Slack link can be in the format and LINK_REGEX = r""" @@ -71,10 +71,10 @@ def get_user_full_name(user: ZerverFieldsT) -> str: # Markdown mapping def convert_to_zulip_markdown( text: str, - users: List[ZerverFieldsT], + users: list[ZerverFieldsT], added_channels: AddedChannelsT, slack_user_id_to_zulip_user_id: SlackToZulipUserIDT, -) -> Tuple[str, List[int], bool]: +) -> tuple[str, list[int], bool]: mentioned_users_id = [] text = convert_markdown_syntax(text, SLACK_BOLD_REGEX, "**") text = convert_markdown_syntax(text, SLACK_STRIKETHROUGH_REGEX, "~~") @@ -117,8 +117,8 @@ def convert_to_zulip_markdown( def get_user_mentions( - token: str, users: List[ZerverFieldsT], slack_user_id_to_zulip_user_id: SlackToZulipUserIDT -) -> Tuple[str, Optional[int]]: + token: str, users: list[ZerverFieldsT], slack_user_id_to_zulip_user_id: SlackToZulipUserIDT +) -> tuple[str, Optional[int]]: slack_usermention_match = re.search(SLACK_USERMENTION_REGEX, token, re.VERBOSE) assert slack_usermention_match is not None short_name = slack_usermention_match.group(4) @@ -156,7 +156,7 @@ def convert_markdown_syntax(text: str, regex: str, zulip_keyword: str) -> str: return text -def convert_link_format(text: str) -> Tuple[str, bool]: +def convert_link_format(text: str) -> tuple[str, bool]: """ 1. Converts '' to 'https://foo.com' 2. Converts '' to 'https://foo.com|foo' @@ -169,7 +169,7 @@ def convert_link_format(text: str) -> Tuple[str, bool]: return text, has_link -def convert_mailto_format(text: str) -> Tuple[str, bool]: +def convert_mailto_format(text: str) -> tuple[str, bool]: """ 1. Converts '' to 'mailto:foo@foo.com' 2. Converts '' to 'mailto:foo@foo.com' diff --git a/zerver/data_import/user_handler.py b/zerver/data_import/user_handler.py index b0d134d940..6a934d6834 100644 --- a/zerver/data_import/user_handler.py +++ b/zerver/data_import/user_handler.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List +from typing import Any class UserHandler: @@ -12,16 +12,16 @@ class UserHandler: """ def __init__(self) -> None: - self.id_to_user_map: Dict[int, Dict[str, Any]] = {} + self.id_to_user_map: dict[int, dict[str, Any]] = {} - def add_user(self, user: Dict[str, Any]) -> None: + def add_user(self, user: dict[str, Any]) -> None: user_id = user["id"] self.id_to_user_map[user_id] = user - def get_user(self, user_id: int) -> Dict[str, Any]: + def get_user(self, user_id: int) -> dict[str, Any]: user = self.id_to_user_map[user_id] return user - def get_all_users(self) -> List[Dict[str, Any]]: + def get_all_users(self) -> list[dict[str, Any]]: users = list(self.id_to_user_map.values()) return users diff --git a/zerver/decorator.py b/zerver/decorator.py index e0375fd9e4..0b3a9f5f6b 100644 --- a/zerver/decorator.py +++ b/zerver/decorator.py @@ -3,18 +3,7 @@ import logging from datetime import datetime from functools import wraps from io import BytesIO -from typing import ( - TYPE_CHECKING, - Callable, - Dict, - Optional, - Sequence, - Tuple, - TypeVar, - Union, - cast, - overload, -) +from typing import TYPE_CHECKING, Callable, Optional, Sequence, TypeVar, Union, cast, overload from urllib.parse import urlsplit import django_otp @@ -712,7 +701,7 @@ def authenticated_uploads_api_view( def get_basic_credentials( request: HttpRequest, beanstalk_email_decode: bool = False -) -> Tuple[str, str]: +) -> tuple[str, str]: """ Extracts the role and API key as a tuple from the Authorization header for HTTP basic authentication. @@ -1036,7 +1025,7 @@ def zulip_otp_required_if_logged_in( return decorator -def add_google_analytics_context(context: Dict[str, object]) -> None: +def add_google_analytics_context(context: dict[str, object]) -> None: if settings.GOOGLE_ANALYTICS_ID is not None: # nocoverage page_params = context.setdefault("page_params", {}) assert isinstance(page_params, dict) diff --git a/zerver/filters.py b/zerver/filters.py index 85e60ff333..7878587c7b 100644 --- a/zerver/filters.py +++ b/zerver/filters.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional +from typing import Any, Optional from django.http import HttpRequest from django.views.debug import SafeExceptionReporterFilter @@ -7,7 +7,7 @@ from typing_extensions import override class ZulipExceptionReporterFilter(SafeExceptionReporterFilter): @override - def get_post_parameters(self, request: Optional[HttpRequest]) -> Dict[str, Any]: + def get_post_parameters(self, request: Optional[HttpRequest]) -> dict[str, Any]: post_data = SafeExceptionReporterFilter.get_post_parameters(self, request) assert isinstance(post_data, dict) filtered_post = post_data.copy() diff --git a/zerver/forms.py b/zerver/forms.py index cf55fc1711..f98070f66f 100644 --- a/zerver/forms.py +++ b/zerver/forms.py @@ -1,7 +1,7 @@ import logging import re from email.headerregistry import Address -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Optional import DNS from django import forms @@ -376,7 +376,7 @@ class ZulipPasswordResetForm(PasswordResetForm): from_email: Optional[str] = None, request: Optional[HttpRequest] = None, html_email_template_name: Optional[str] = None, - extra_email_context: Optional[Dict[str, Any]] = None, + extra_email_context: Optional[dict[str, Any]] = None, ) -> None: """ If the email address has an account in the target realm, @@ -483,7 +483,7 @@ class RateLimitedPasswordResetByEmail(RateLimitedObject): return f"{type(self).__name__}:{self.email}" @override - def rules(self) -> List[Tuple[int, int]]: + def rules(self) -> list[tuple[int, int]]: return settings.RATE_LIMITING_RULES["password_reset_form_by_email"] @@ -502,7 +502,7 @@ class OurAuthenticationForm(AuthenticationForm): logger = logging.getLogger("zulip.auth.OurAuthenticationForm") @override - def clean(self) -> Dict[str, Any]: + def clean(self) -> dict[str, Any]: username = self.cleaned_data.get("username") password = self.cleaned_data.get("password") @@ -511,7 +511,7 @@ class OurAuthenticationForm(AuthenticationForm): subdomain = get_subdomain(self.request) realm = get_realm(subdomain) - return_data: Dict[str, Any] = {} + return_data: dict[str, Any] = {} try: self.user_cache = authenticate( request=self.request, @@ -592,7 +592,7 @@ class AuthenticationTokenForm(TwoFactorAuthenticationTokenForm): class MultiEmailField(forms.Field): @override - def to_python(self, emails: Optional[str]) -> List[str]: + def to_python(self, emails: Optional[str]) -> list[str]: """Normalize data to a list of strings.""" if not emails: return [] @@ -600,7 +600,7 @@ class MultiEmailField(forms.Field): return [email.strip() for email in emails.split(",")] @override - def validate(self, emails: List[str]) -> None: + def validate(self, emails: list[str]) -> None: """Check if value consists only of valid emails.""" super().validate(emails) for email in emails: @@ -612,7 +612,7 @@ class FindMyTeamForm(forms.Form): help_text=_("Tip: You can enter multiple email addresses with commas between them.") ) - def clean_emails(self) -> List[str]: + def clean_emails(self) -> list[str]: emails = self.cleaned_data["emails"] if len(emails) > 10: raise forms.ValidationError(_("Please enter at most 10 emails.")) diff --git a/zerver/lib/addressee.py b/zerver/lib/addressee.py index 2a1ac0eeb4..399a7dca87 100644 --- a/zerver/lib/addressee.py +++ b/zerver/lib/addressee.py @@ -1,4 +1,4 @@ -from typing import Iterable, List, Optional, Sequence, Union, cast +from typing import Iterable, Optional, Sequence, Union, cast from django.utils.translation import gettext as _ @@ -11,8 +11,8 @@ from zerver.models.users import ( ) -def get_user_profiles(emails: Iterable[str], realm: Realm) -> List[UserProfile]: - user_profiles: List[UserProfile] = [] +def get_user_profiles(emails: Iterable[str], realm: Realm) -> list[UserProfile]: + user_profiles: list[UserProfile] = [] for email in emails: try: user_profile = get_user_including_cross_realm(email, realm) @@ -22,8 +22,8 @@ def get_user_profiles(emails: Iterable[str], realm: Realm) -> List[UserProfile]: return user_profiles -def get_user_profiles_by_ids(user_ids: Iterable[int], realm: Realm) -> List[UserProfile]: - user_profiles: List[UserProfile] = [] +def get_user_profiles_by_ids(user_ids: Iterable[int], realm: Realm) -> list[UserProfile]: + user_profiles: list[UserProfile] = [] for user_id in user_ids: try: user_profile = get_user_by_id_in_realm_including_cross_realm(user_id, realm) diff --git a/zerver/lib/alert_words.py b/zerver/lib/alert_words.py index 70544049d6..aeedba2a4f 100644 --- a/zerver/lib/alert_words.py +++ b/zerver/lib/alert_words.py @@ -1,4 +1,4 @@ -from typing import Dict, Iterable, List +from typing import Iterable import ahocorasick from django.db import transaction @@ -13,11 +13,11 @@ from zerver.models.alert_words import flush_realm_alert_words @cache_with_key(lambda realm: realm_alert_words_cache_key(realm.id), timeout=3600 * 24) -def alert_words_in_realm(realm: Realm) -> Dict[int, List[str]]: +def alert_words_in_realm(realm: Realm) -> dict[int, list[str]]: user_ids_and_words = AlertWord.objects.filter(realm=realm, user_profile__is_active=True).values( "user_profile_id", "word" ) - user_ids_with_words: Dict[int, List[str]] = {} + user_ids_with_words: dict[int, list[str]] = {} for id_and_word in user_ids_and_words: user_ids_with_words.setdefault(id_and_word["user_profile_id"], []) user_ids_with_words[id_and_word["user_profile_id"]].append(id_and_word["word"]) @@ -46,17 +46,17 @@ def get_alert_word_automaton(realm: Realm) -> ahocorasick.Automaton: return alert_word_automaton -def user_alert_words(user_profile: UserProfile) -> List[str]: +def user_alert_words(user_profile: UserProfile) -> list[str]: return list(AlertWord.objects.filter(user_profile=user_profile).values_list("word", flat=True)) @transaction.atomic -def add_user_alert_words(user_profile: UserProfile, new_words: Iterable[str]) -> List[str]: +def add_user_alert_words(user_profile: UserProfile, new_words: Iterable[str]) -> list[str]: existing_words_lower = {word.lower() for word in user_alert_words(user_profile)} # Keeping the case, use a dictionary to get the set of # case-insensitive distinct, new alert words - word_dict: Dict[str, str] = {} + word_dict: dict[str, str] = {} for word in new_words: if word.lower() in existing_words_lower: continue @@ -73,7 +73,7 @@ def add_user_alert_words(user_profile: UserProfile, new_words: Iterable[str]) -> @transaction.atomic -def remove_user_alert_words(user_profile: UserProfile, delete_words: Iterable[str]) -> List[str]: +def remove_user_alert_words(user_profile: UserProfile, delete_words: Iterable[str]) -> list[str]: # TODO: Ideally, this would be a bulk query, but Django doesn't have a `__iexact`. # We can clean this up if/when PostgreSQL has more native support for case-insensitive fields. # If we turn this into a bulk operation, we will need to call flush_realm_alert_words() here. diff --git a/zerver/lib/attachments.py b/zerver/lib/attachments.py index 1686f07c59..373468aa26 100644 --- a/zerver/lib/attachments.py +++ b/zerver/lib/attachments.py @@ -1,5 +1,5 @@ from datetime import timedelta -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Optional, Union from django.conf import settings from django.contrib.auth.models import AnonymousUser @@ -22,7 +22,7 @@ from zerver.models import ( ) -def user_attachments(user_profile: UserProfile) -> List[Dict[str, Any]]: +def user_attachments(user_profile: UserProfile) -> list[dict[str, Any]]: attachments = Attachment.objects.filter(owner=user_profile).prefetch_related("messages") return [a.to_dict() for a in attachments] @@ -161,7 +161,7 @@ def validate_attachment_request( def get_old_unclaimed_attachments( weeks_ago: int, -) -> Tuple[QuerySet[Attachment], QuerySet[ArchivedAttachment]]: +) -> tuple[QuerySet[Attachment], QuerySet[ArchivedAttachment]]: """ The logic in this function is fairly tricky. The essence is that a file should be cleaned up if and only if it not referenced by any diff --git a/zerver/lib/bot_config.py b/zerver/lib/bot_config.py index aa3a0e9708..dceb0f0af5 100644 --- a/zerver/lib/bot_config.py +++ b/zerver/lib/bot_config.py @@ -2,7 +2,7 @@ import configparser import importlib import os from collections import defaultdict -from typing import Dict, List, Optional +from typing import Optional from django.conf import settings from django.db.models import F, Sum @@ -15,18 +15,18 @@ class ConfigError(Exception): pass -def get_bot_config(bot_profile: UserProfile) -> Dict[str, str]: +def get_bot_config(bot_profile: UserProfile) -> dict[str, str]: entries = BotConfigData.objects.filter(bot_profile=bot_profile) if not entries: raise ConfigError("No config data available.") return {entry.key: entry.value for entry in entries} -def get_bot_configs(bot_profile_ids: List[int]) -> Dict[int, Dict[str, str]]: +def get_bot_configs(bot_profile_ids: list[int]) -> dict[int, dict[str, str]]: if not bot_profile_ids: return {} entries = BotConfigData.objects.filter(bot_profile_id__in=bot_profile_ids) - entries_by_uid: Dict[int, Dict[str, str]] = defaultdict(dict) + entries_by_uid: dict[int, dict[str, str]] = defaultdict(dict) for entry in entries: entries_by_uid[entry.bot_profile_id].update({entry.key: entry.value}) return entries_by_uid @@ -66,7 +66,7 @@ def set_bot_config(bot_profile: UserProfile, key: str, value: str) -> None: obj.save() -def load_bot_config_template(bot: str) -> Dict[str, str]: +def load_bot_config_template(bot: str) -> dict[str, str]: bot_module_name = f"zulip_bots.bots.{bot}" bot_module = importlib.import_module(bot_module_name) assert bot_module.__file__ is not None diff --git a/zerver/lib/bot_lib.py b/zerver/lib/bot_lib.py index 9b335f9108..b79c9dd090 100644 --- a/zerver/lib/bot_lib.py +++ b/zerver/lib/bot_lib.py @@ -1,6 +1,6 @@ import importlib import json -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable, Optional from django.conf import settings from django.utils.translation import gettext as _ @@ -79,10 +79,10 @@ class EmbeddedBotHandler: def identity(self) -> BotIdentity: return BotIdentity(self.full_name, self.email) - def react(self, message: Dict[str, Any], emoji_name: str) -> Dict[str, Any]: + def react(self, message: dict[str, Any], emoji_name: str) -> dict[str, Any]: return {} # Not implemented - def send_message(self, message: Dict[str, Any]) -> Dict[str, Any]: + def send_message(self, message: dict[str, Any]) -> dict[str, Any]: if not self._rate_limit.is_legal(): self._rate_limit.show_error_and_exit() @@ -115,8 +115,8 @@ class EmbeddedBotHandler: return {"id": message_id} def send_reply( - self, message: Dict[str, Any], response: str, widget_content: Optional[str] = None - ) -> Dict[str, Any]: + self, message: dict[str, Any], response: str, widget_content: Optional[str] = None + ) -> dict[str, Any]: if message["type"] == "private": result = self.send_message( dict( @@ -138,11 +138,11 @@ class EmbeddedBotHandler: ) return {"id": result["id"]} - def update_message(self, message: Dict[str, Any]) -> None: + def update_message(self, message: dict[str, Any]) -> None: pass # Not implemented # The bot_name argument exists only to comply with ExternalBotHandler.get_config_info(). - def get_config_info(self, bot_name: str, optional: bool = False) -> Dict[str, str]: + def get_config_info(self, bot_name: str, optional: bool = False) -> dict[str, str]: try: return get_bot_config(self.user_profile) except ConfigError: diff --git a/zerver/lib/bot_storage.py b/zerver/lib/bot_storage.py index 0dccb96a2b..f218640d20 100644 --- a/zerver/lib/bot_storage.py +++ b/zerver/lib/bot_storage.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Tuple +from typing import Optional from django.conf import settings from django.db.models import F, Sum @@ -35,7 +35,7 @@ def get_bot_storage_size(bot_profile: UserProfile, key: Optional[str] = None) -> return 0 -def set_bot_storage(bot_profile: UserProfile, entries: List[Tuple[str, str]]) -> None: +def set_bot_storage(bot_profile: UserProfile, entries: list[tuple[str, str]]) -> None: storage_size_limit = settings.USER_STATE_SIZE_LIMIT storage_size_difference = 0 for key, value in entries: @@ -54,7 +54,7 @@ def set_bot_storage(bot_profile: UserProfile, entries: List[Tuple[str, str]]) -> ) -def remove_bot_storage(bot_profile: UserProfile, keys: List[str]) -> None: +def remove_bot_storage(bot_profile: UserProfile, keys: list[str]) -> None: queryset = BotStorageData.objects.filter(bot_profile=bot_profile, key__in=keys) if len(queryset) < len(keys): raise StateError("Key does not exist.") @@ -65,7 +65,7 @@ def is_key_in_bot_storage(bot_profile: UserProfile, key: str) -> bool: return BotStorageData.objects.filter(bot_profile=bot_profile, key=key).exists() -def get_keys_in_bot_storage(bot_profile: UserProfile) -> List[str]: +def get_keys_in_bot_storage(bot_profile: UserProfile) -> list[str]: return list( BotStorageData.objects.filter(bot_profile=bot_profile).values_list("key", flat=True) ) diff --git a/zerver/lib/bulk_create.py b/zerver/lib/bulk_create.py index 03be8210d4..c120427721 100644 --- a/zerver/lib/bulk_create.py +++ b/zerver/lib/bulk_create.py @@ -1,4 +1,4 @@ -from typing import Any, Collection, Dict, Iterable, List, Optional, Set, Tuple, Type, Union +from typing import Any, Collection, Iterable, Optional, Union from django.db.models import Model, QuerySet from django.utils.timezone import now as timezone_now @@ -22,7 +22,7 @@ from zerver.models.groups import SystemGroups def bulk_create_users( realm: Realm, - users_raw: Set[Tuple[str, str, bool]], + users_raw: set[tuple[str, str, bool]], bot_type: Optional[int] = None, bot_owner: Optional[UserProfile] = None, tos_version: Optional[str] = None, @@ -46,7 +46,7 @@ def bulk_create_users( email_address_visibility = UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE # Now create user_profiles - profiles_to_create: List[UserProfile] = [] + profiles_to_create: list[UserProfile] = [] for email, full_name, active in users: profile = create_user_profile( realm, @@ -112,7 +112,7 @@ def bulk_create_users( UserProfile, profiles_to_create, recipients_to_create ) - recipients_by_user_id: Dict[int, Recipient] = {} + recipients_by_user_id: dict[int, Recipient] = {} for recipient in recipients_to_create: recipients_by_user_id[recipient.type_id] = recipient @@ -133,7 +133,7 @@ def bulk_create_users( members_system_group = NamedUserGroup.objects.get( name=SystemGroups.MEMBERS, realm=realm, is_system_group=True ) - group_memberships_to_create: List[UserGroupMembership] = [] + group_memberships_to_create: list[UserGroupMembership] = [] for user_profile in profiles_to_create: # All users are members since this function is only used to create bots # and test and development environment users. @@ -162,7 +162,7 @@ def bulk_create_users( def bulk_set_users_or_streams_recipient_fields( - model: Type[Model], + model: type[Model], objects: Union[ Collection[UserProfile], QuerySet[UserProfile], Collection[Stream], QuerySet[Stream] ], @@ -194,14 +194,14 @@ def bulk_set_users_or_streams_recipient_fields( # This is only sed in populate_db, so doesn't really need tests -def bulk_create_streams(realm: Realm, stream_dict: Dict[str, Dict[str, Any]]) -> None: # nocoverage +def bulk_create_streams(realm: Realm, stream_dict: dict[str, dict[str, Any]]) -> None: # nocoverage existing_streams = { name.lower() for name in Stream.objects.filter(realm=realm).values_list("name", flat=True) } administrators_user_group = NamedUserGroup.objects.get( name=SystemGroups.ADMINISTRATORS, is_system_group=True, realm=realm ) - streams_to_create: List[Stream] = [] + streams_to_create: list[Stream] = [] for name, options in stream_dict.items(): if "history_public_to_subscribers" not in options: options["history_public_to_subscribers"] = ( @@ -247,7 +247,7 @@ def bulk_create_streams(realm: Realm, stream_dict: Dict[str, Dict[str, Any]]) -> def create_users( - realm: Realm, name_list: Iterable[Tuple[str, str]], bot_type: Optional[int] = None + realm: Realm, name_list: Iterable[tuple[str, str]], bot_type: Optional[int] = None ) -> None: user_set = set() for full_name, email in name_list: diff --git a/zerver/lib/cache.py b/zerver/lib/cache.py index 5e0a2c36b1..6db393734c 100644 --- a/zerver/lib/cache.py +++ b/zerver/lib/cache.py @@ -8,19 +8,7 @@ import sys import time import traceback from functools import _lru_cache_wrapper, lru_cache, wraps -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Dict, - Generic, - Iterable, - List, - Optional, - Sequence, - Tuple, - TypeVar, -) +from typing import TYPE_CHECKING, Any, Callable, Generic, Iterable, Optional, Sequence, TypeVar from django.conf import settings from django.core.cache import caches @@ -171,7 +159,7 @@ class InvalidCacheKeyError(Exception): pass -def log_invalid_cache_keys(stack_trace: str, key: List[str]) -> None: +def log_invalid_cache_keys(stack_trace: str, key: list[str]) -> None: logger.warning( "Invalid cache key used: %s\nStack trace: %s\n", key, @@ -220,7 +208,7 @@ def cache_get(key: str, cache_name: Optional[str] = None) -> Any: return ret -def cache_get_many(keys: List[str], cache_name: Optional[str] = None) -> Dict[str, Any]: +def cache_get_many(keys: list[str], cache_name: Optional[str] = None) -> dict[str, Any]: keys = [KEY_PREFIX + key for key in keys] for key in keys: validate_cache_key(key) @@ -230,7 +218,7 @@ def cache_get_many(keys: List[str], cache_name: Optional[str] = None) -> Dict[st return {key[len(KEY_PREFIX) :]: value for key, value in ret.items()} -def safe_cache_get_many(keys: List[str], cache_name: Optional[str] = None) -> Dict[str, Any]: +def safe_cache_get_many(keys: list[str], cache_name: Optional[str] = None) -> dict[str, Any]: """Variant of cache_get_many that drops any keys that fail validation, rather than throwing an exception visible to the caller.""" @@ -248,7 +236,7 @@ def safe_cache_get_many(keys: List[str], cache_name: Optional[str] = None) -> Di def cache_set_many( - items: Dict[str, Any], cache_name: Optional[str] = None, timeout: Optional[int] = None + items: dict[str, Any], cache_name: Optional[str] = None, timeout: Optional[int] = None ) -> None: new_items = {} for key in items: @@ -262,7 +250,7 @@ def cache_set_many( def safe_cache_set_many( - items: Dict[str, Any], cache_name: Optional[str] = None, timeout: Optional[int] = None + items: dict[str, Any], cache_name: Optional[str] = None, timeout: Optional[int] = None ) -> None: """Variant of cache_set_many that drops saving any keys that fail validation, rather than throwing an exception visible to the @@ -300,7 +288,7 @@ def cache_delete_many(items: Iterable[str], cache_name: Optional[str] = None) -> remote_cache_stats_finish() -def filter_good_and_bad_keys(keys: List[str]) -> Tuple[List[str], List[str]]: +def filter_good_and_bad_keys(keys: list[str]) -> tuple[list[str], list[str]]: good_keys = [] bad_keys = [] for key in keys: @@ -346,23 +334,23 @@ CompressedItemT = TypeVar("CompressedItemT") # function of the objects, not the objects themselves) def generic_bulk_cached_fetch( cache_key_function: Callable[[ObjKT], str], - query_function: Callable[[List[ObjKT]], Iterable[ItemT]], + query_function: Callable[[list[ObjKT]], Iterable[ItemT]], object_ids: Sequence[ObjKT], *, extractor: Callable[[CompressedItemT], CacheItemT], setter: Callable[[CacheItemT], CompressedItemT], id_fetcher: Callable[[ItemT], ObjKT], cache_transformer: Callable[[ItemT], CacheItemT], -) -> Dict[ObjKT, CacheItemT]: +) -> dict[ObjKT, CacheItemT]: if len(object_ids) == 0: # Nothing to fetch. return {} - cache_keys: Dict[ObjKT, str] = {} + cache_keys: dict[ObjKT, str] = {} for object_id in object_ids: cache_keys[object_id] = cache_key_function(object_id) - cached_objects_compressed: Dict[str, Tuple[CompressedItemT]] = safe_cache_get_many( + cached_objects_compressed: dict[str, tuple[CompressedItemT]] = safe_cache_get_many( [cache_keys[object_id] for object_id in object_ids], ) @@ -377,7 +365,7 @@ def generic_bulk_cached_fetch( else: db_objects = [] - items_for_remote_cache: Dict[str, Tuple[CompressedItemT]] = {} + items_for_remote_cache: dict[str, tuple[CompressedItemT]] = {} for obj in db_objects: key = cache_keys[id_fetcher(obj)] item = cache_transformer(obj) @@ -394,11 +382,11 @@ def generic_bulk_cached_fetch( def bulk_cached_fetch( cache_key_function: Callable[[ObjKT], str], - query_function: Callable[[List[ObjKT]], Iterable[ItemT]], + query_function: Callable[[list[ObjKT]], Iterable[ItemT]], object_ids: Sequence[ObjKT], *, id_fetcher: Callable[[ItemT], ObjKT], -) -> Dict[ObjKT, ItemT]: +) -> dict[ObjKT, ItemT]: return generic_bulk_cached_fetch( cache_key_function, query_function, @@ -453,7 +441,7 @@ def get_cross_realm_dicts_key() -> str: return f"get_cross_realm_dicts:{digest}" -realm_user_dict_fields: List[str] = [ +realm_user_dict_fields: list[str] = [ "id", "full_name", "email", @@ -497,7 +485,7 @@ def active_non_guest_user_ids_cache_key(realm_id: int) -> str: return f"active_non_guest_user_ids:{realm_id}" -bot_dict_fields: List[str] = [ +bot_dict_fields: list[str] = [ "api_key", "avatar_source", "avatar_version", @@ -548,7 +536,7 @@ def delete_display_recipient_cache(user_profile: "UserProfile") -> None: cache_delete_many(keys) -def changed(update_fields: Optional[Sequence[str]], fields: List[str]) -> bool: +def changed(update_fields: Optional[Sequence[str]], fields: list[str]) -> bool: if update_fields is None: # adds/deletes should invalidate the cache return True diff --git a/zerver/lib/cache_helpers.py b/zerver/lib/cache_helpers.py index 5dbd3efb2e..5e3788f6cb 100644 --- a/zerver/lib/cache_helpers.py +++ b/zerver/lib/cache_helpers.py @@ -1,7 +1,7 @@ # See https://zulip.readthedocs.io/en/latest/subsystems/caching.html for docs import logging from datetime import timedelta -from typing import Any, Callable, Dict, Iterable, Tuple +from typing import Any, Callable, Iterable from django.conf import settings from django.contrib.sessions.models import Session @@ -29,7 +29,7 @@ from zerver.models.clients import get_client_cache_key def user_cache_items( - items_for_remote_cache: Dict[str, Tuple[UserProfile]], user_profile: UserProfile + items_for_remote_cache: dict[str, tuple[UserProfile]], user_profile: UserProfile ) -> None: for api_key in get_all_api_keys(user_profile): items_for_remote_cache[user_profile_by_api_key_cache_key(api_key)] = (user_profile,) @@ -40,12 +40,12 @@ def user_cache_items( # core serving path for lots of requests. -def client_cache_items(items_for_remote_cache: Dict[str, Tuple[Client]], client: Client) -> None: +def client_cache_items(items_for_remote_cache: dict[str, tuple[Client]], client: Client) -> None: items_for_remote_cache[get_client_cache_key(client.name)] = (client,) def session_cache_items( - items_for_remote_cache: Dict[str, Dict[str, object]], session: Session + items_for_remote_cache: dict[str, dict[str, object]], session: Session ) -> None: if settings.SESSION_ENGINE != "zerver.lib.safe_session_cached_db": # If we're not using the cached_db session engine, we there @@ -85,8 +85,8 @@ def get_users() -> QuerySet[UserProfile]: # doing any setup for things we're unlikely to use (without the lambda # wrapper the below adds an extra 3ms or so to startup time for # anything importing this file). -cache_fillers: Dict[ - str, Tuple[Callable[[], Iterable[Any]], Callable[[Dict[str, Any], Any], None], int, int] +cache_fillers: dict[ + str, tuple[Callable[[], Iterable[Any]], Callable[[dict[str, Any], Any], None], int, int] ] = { "user": (get_users, user_cache_items, 3600 * 24 * 7, 10000), "client": ( @@ -105,11 +105,11 @@ class SQLQueryCounter: def __call__( self, - execute: Callable[[str, Any, bool, Dict[str, Any]], Any], + execute: Callable[[str, Any, bool, dict[str, Any]], Any], sql: str, params: Any, many: bool, - context: Dict[str, Any], + context: dict[str, Any], ) -> Any: self.count += 1 return execute(sql, params, many, context) @@ -118,7 +118,7 @@ class SQLQueryCounter: def fill_remote_cache(cache: str) -> None: remote_cache_time_start = get_remote_cache_time() remote_cache_requests_start = get_remote_cache_requests() - items_for_remote_cache: Dict[str, Any] = {} + items_for_remote_cache: dict[str, Any] = {} (objects, items_filler, timeout, batch_size) = cache_fillers[cache] count = 0 db_query_counter = SQLQueryCounter() diff --git a/zerver/lib/ccache.py b/zerver/lib/ccache.py index 611844f5d0..e79d3cb719 100644 --- a/zerver/lib/ccache.py +++ b/zerver/lib/ccache.py @@ -1,6 +1,6 @@ import base64 import struct -from typing import Any, Dict, List, Optional +from typing import Any, Optional # This file is adapted from samples/shellinabox/ssh-krb-wrapper in # https://github.com/davidben/webathena, which has the following @@ -101,7 +101,7 @@ def der_encode_octet_string(val: bytes) -> bytes: return der_encode_tlv(0x04, val) -def der_encode_sequence(tlvs: List[Optional[bytes]], tagged: bool = True) -> bytes: +def der_encode_sequence(tlvs: list[Optional[bytes]], tagged: bool = True) -> bytes: body = [] for i, tlv in enumerate(tlvs): # Missing optional elements represented as None. @@ -114,7 +114,7 @@ def der_encode_sequence(tlvs: List[Optional[bytes]], tagged: bool = True) -> byt return der_encode_tlv(0x30, b"".join(body)) -def der_encode_ticket(tkt: Dict[str, Any]) -> bytes: +def der_encode_ticket(tkt: dict[str, Any]) -> bytes: return der_encode_tlv( 0x61, # Ticket der_encode_sequence( @@ -155,7 +155,7 @@ def ccache_counted_octet_string(data: bytes) -> bytes: return struct.pack("!I", len(data)) + data -def ccache_principal(name: Dict[str, str], realm: str) -> bytes: +def ccache_principal(name: dict[str, str], realm: str) -> bytes: header = struct.pack("!II", name["nameType"], len(name["nameString"])) return ( header @@ -164,13 +164,13 @@ def ccache_principal(name: Dict[str, str], realm: str) -> bytes: ) -def ccache_key(key: Dict[str, str]) -> bytes: +def ccache_key(key: dict[str, str]) -> bytes: return struct.pack("!H", key["keytype"]) + ccache_counted_octet_string( base64.b64decode(key["keyvalue"]) ) -def flags_to_uint32(flags: List[str]) -> int: +def flags_to_uint32(flags: list[str]) -> int: ret = 0 for i, v in enumerate(flags): if v: @@ -178,7 +178,7 @@ def flags_to_uint32(flags: List[str]) -> int: return ret -def ccache_credential(cred: Dict[str, Any]) -> bytes: +def ccache_credential(cred: dict[str, Any]) -> bytes: out = ccache_principal(cred["cname"], cred["crealm"]) out += ccache_principal(cred["sname"], cred["srealm"]) out += ccache_key(cred["key"]) @@ -199,7 +199,7 @@ def ccache_credential(cred: Dict[str, Any]) -> bytes: return out -def make_ccache(cred: Dict[str, Any]) -> bytes: +def make_ccache(cred: dict[str, Any]) -> bytes: # Do we need a DeltaTime header? The ccache I get just puts zero # in there, so do the same. out = struct.pack( diff --git a/zerver/lib/compatibility.py b/zerver/lib/compatibility.py index 1c237cc91e..f0571f0682 100644 --- a/zerver/lib/compatibility.py +++ b/zerver/lib/compatibility.py @@ -1,7 +1,7 @@ import os import re from datetime import datetime, timedelta, timezone -from typing import List, Optional, Tuple +from typing import Optional from django.conf import settings from django.utils.timezone import now as timezone_now @@ -42,7 +42,7 @@ def is_outdated_server(user_profile: Optional[UserProfile]) -> bool: return False -def pop_numerals(ver: str) -> Tuple[List[int], str]: +def pop_numerals(ver: str) -> tuple[list[int], str]: match = re.search(r"^( \d+ (?: \. \d+ )* ) (.*)", ver, re.VERBOSE) if match is None: return [], ver @@ -101,7 +101,7 @@ def find_mobile_os(user_agent: str) -> Optional[str]: return None -def is_outdated_desktop_app(user_agent_str: str) -> Tuple[bool, bool, bool]: +def is_outdated_desktop_app(user_agent_str: str) -> tuple[bool, bool, bool]: # Returns (insecure, banned, auto_update_broken) user_agent = parse_user_agent(user_agent_str) if user_agent["name"] == "ZulipDesktop": @@ -129,7 +129,7 @@ def is_outdated_desktop_app(user_agent_str: str) -> Tuple[bool, bool, bool]: return (False, False, False) -def is_unsupported_browser(user_agent: str) -> Tuple[bool, Optional[str]]: +def is_unsupported_browser(user_agent: str) -> tuple[bool, Optional[str]]: browser_name = get_device_browser(user_agent) if browser_name == "Internet Explorer": return (True, browser_name) diff --git a/zerver/lib/data_types.py b/zerver/lib/data_types.py index 1336fb1f33..afd86742b0 100644 --- a/zerver/lib/data_types.py +++ b/zerver/lib/data_types.py @@ -11,7 +11,7 @@ easily with the native Python type system. from contextlib import suppress from dataclasses import dataclass -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple +from typing import Any, Callable, Optional, Sequence from django.core.exceptions import ValidationError from django.core.validators import URLValidator @@ -32,13 +32,13 @@ class DictType: def __init__( self, - required_keys: Sequence[Tuple[str, Any]], - optional_keys: Sequence[Tuple[str, Any]] = [], + required_keys: Sequence[tuple[str, Any]], + optional_keys: Sequence[tuple[str, Any]] = [], ) -> None: self.required_keys = required_keys self.optional_keys = optional_keys - def check_data(self, var_name: str, val: Dict[str, Any]) -> None: + def check_data(self, var_name: str, val: dict[str, Any]) -> None: if not isinstance(val, dict): raise AssertionError(f"{var_name} is not a dict") @@ -79,7 +79,7 @@ class EnumType: valid_vals: Sequence[Any] - def check_data(self, var_name: str, val: Dict[str, Any]) -> None: + def check_data(self, var_name: str, val: dict[str, Any]) -> None: if val not in self.valid_vals: raise AssertionError(f"{var_name} is not in {self.valid_vals}") @@ -97,7 +97,7 @@ class Equals: if self.expected_value is None: self.equalsNone = True - def check_data(self, var_name: str, val: Dict[str, Any]) -> None: + def check_data(self, var_name: str, val: dict[str, Any]) -> None: if val != self.expected_value: raise AssertionError(f"{var_name} should be equal to {self.expected_value}") @@ -127,7 +127,7 @@ class ListType: self.sub_type = sub_type self.length = length - def check_data(self, var_name: str, val: List[Any]) -> None: + def check_data(self, var_name: str, val: list[Any]) -> None: if not isinstance(val, list): raise AssertionError(f"{var_name} is not a list") @@ -146,7 +146,7 @@ class StringDictType: value_type: Any - def check_data(self, var_name: str, val: Dict[Any, Any]) -> None: + def check_data(self, var_name: str, val: dict[Any, Any]) -> None: if not isinstance(val, dict): raise AssertionError(f"{var_name} is not a dictionary") @@ -240,8 +240,8 @@ class UrlType: def event_dict_type( - required_keys: Sequence[Tuple[str, Any]], - optional_keys: Sequence[Tuple[str, Any]] = [], + required_keys: Sequence[tuple[str, Any]], + optional_keys: Sequence[tuple[str, Any]] = [], ) -> DictType: """ This is just a tiny wrapper on DictType, but it provides @@ -267,8 +267,8 @@ def event_dict_type( def make_checker( data_type: DictType, -) -> Callable[[str, Dict[str, object]], None]: - def f(var_name: str, event: Dict[str, Any]) -> None: +) -> Callable[[str, dict[str, object]], None]: + def f(var_name: str, event: dict[str, Any]) -> None: check_data(data_type, var_name, event) return f diff --git a/zerver/lib/db.py b/zerver/lib/db.py index 9d3fedead5..9f193e4806 100644 --- a/zerver/lib/db.py +++ b/zerver/lib/db.py @@ -1,5 +1,5 @@ import time -from typing import Any, Callable, Dict, Iterable, List, Mapping, Sequence, TypeVar, Union +from typing import Any, Callable, Iterable, Mapping, Sequence, TypeVar, Union from psycopg2.extensions import connection, cursor from psycopg2.sql import Composable @@ -49,5 +49,5 @@ class TimeTrackingConnection(connection): """A psycopg2 connection class that uses TimeTrackingCursors.""" def __init__(self, *args: Any, **kwargs: Any) -> None: - self.queries: List[Dict[str, str]] = [] + self.queries: list[dict[str, str]] = [] super().__init__(*args, **kwargs) diff --git a/zerver/lib/default_streams.py b/zerver/lib/default_streams.py index 2e39bfb9dc..5369e48ff7 100644 --- a/zerver/lib/default_streams.py +++ b/zerver/lib/default_streams.py @@ -1,10 +1,8 @@ -from typing import List, Set - from zerver.lib.types import DefaultStreamDict from zerver.models import DefaultStream, Stream -def get_slim_realm_default_streams(realm_id: int) -> List[Stream]: +def get_slim_realm_default_streams(realm_id: int) -> list[Stream]: # We really want this query to be simple and just get "thin" Stream objects # in one round trip. # @@ -18,11 +16,11 @@ def get_slim_realm_default_streams(realm_id: int) -> List[Stream]: return list(Stream.objects.filter(defaultstream__realm_id=realm_id)) -def get_default_stream_ids_for_realm(realm_id: int) -> Set[int]: +def get_default_stream_ids_for_realm(realm_id: int) -> set[int]: return set(DefaultStream.objects.filter(realm_id=realm_id).values_list("stream_id", flat=True)) -def get_default_streams_for_realm_as_dicts(realm_id: int) -> List[DefaultStreamDict]: +def get_default_streams_for_realm_as_dicts(realm_id: int) -> list[DefaultStreamDict]: """ Return all the default streams for a realm using a list of dictionaries sorted by stream name. diff --git a/zerver/lib/dev_ldap_directory.py b/zerver/lib/dev_ldap_directory.py index d710365d29..f4e24f5e05 100644 --- a/zerver/lib/dev_ldap_directory.py +++ b/zerver/lib/dev_ldap_directory.py @@ -2,7 +2,7 @@ import glob import logging import os from email.headerregistry import Address -from typing import Any, Dict, List, Optional +from typing import Any, Optional from django.conf import settings @@ -14,7 +14,7 @@ LDAP_USER_ACCOUNT_CONTROL_NORMAL = "512" LDAP_USER_ACCOUNT_CONTROL_DISABLED = "514" -def generate_dev_ldap_dir(mode: str, num_users: int = 8) -> Dict[str, Dict[str, Any]]: +def generate_dev_ldap_dir(mode: str, num_users: int = 8) -> dict[str, dict[str, Any]]: mode = mode.lower() ldap_data = [] for i in range(1, num_users + 1): @@ -60,7 +60,7 @@ def generate_dev_ldap_dir(mode: str, num_users: int = 8) -> Dict[str, Dict[str, def init_fakeldap( - directory: Optional[Dict[str, Dict[str, List[str]]]] = None, + directory: Optional[dict[str, dict[str, list[str]]]] = None, ) -> None: # nocoverage # We only use this in development. Importing mock inside # this function is an import time optimization, which diff --git a/zerver/lib/digest.py b/zerver/lib/digest.py index fd282965f1..86cd15c8f0 100644 --- a/zerver/lib/digest.py +++ b/zerver/lib/digest.py @@ -3,7 +3,7 @@ import heapq import logging from collections import defaultdict from datetime import datetime, timedelta, timezone -from typing import Any, Collection, Dict, Iterator, List, Optional, Set, Tuple +from typing import Any, Collection, Iterator, Optional from django.conf import settings from django.db import transaction @@ -38,14 +38,14 @@ log_to_file(logger, settings.DIGEST_LOG_PATH) DIGEST_CUTOFF = 5 MAX_HOT_TOPICS_TO_BE_INCLUDED_IN_DIGEST = 4 -TopicKey: TypeAlias = Tuple[int, str] +TopicKey: TypeAlias = tuple[int, str] class DigestTopic: def __init__(self, topic_key: TopicKey) -> None: self.topic_key = topic_key - self.human_senders: Set[str] = set() - self.sample_messages: List[Message] = [] + self.human_senders: set[str] = set() + self.sample_messages: list[Message] = [] self.num_human_messages = 0 def stream_id(self) -> int: @@ -66,7 +66,7 @@ class DigestTopic: def diversity(self) -> int: return len(self.human_senders) - def teaser_data(self, user: UserProfile, stream_id_map: Dict[int, Stream]) -> Dict[str, Any]: + def teaser_data(self, user: UserProfile, stream_id_map: dict[int, Stream]) -> dict[str, Any]: teaser_count = self.num_human_messages - len(self.sample_messages) first_few_messages = build_message_list( user=user, @@ -88,7 +88,7 @@ class DigestTopic: # Changes to this should also be reflected in # zerver/worker/digest_emails.py:DigestWorker.consume() -def queue_digest_user_ids(user_ids: List[int], cutoff: datetime) -> None: +def queue_digest_user_ids(user_ids: list[int], cutoff: datetime) -> None: # Convert cutoff to epoch seconds for transit. event = {"user_ids": user_ids, "cutoff": cutoff.strftime("%s")} queue_json_publish("digest_emails", event) @@ -175,7 +175,7 @@ def get_recent_topics( realm_id: int, stream_id: int, cutoff_date: datetime, -) -> List[DigestTopic]: +) -> list[DigestTopic]: # Gather information about topic conversations, then # classify by: # * topic length @@ -207,7 +207,7 @@ def get_recent_topics( ) ) - digest_topic_map: Dict[TopicKey, DigestTopic] = {} + digest_topic_map: dict[TopicKey, DigestTopic] = {} for message in messages: topic_key = (stream_id, message.topic_name()) @@ -222,9 +222,9 @@ def get_recent_topics( def get_hot_topics( - all_topics: List[DigestTopic], - stream_ids: Set[int], -) -> List[DigestTopic]: + all_topics: list[DigestTopic], + stream_ids: set[int], +) -> list[DigestTopic]: topics = [topic for topic in all_topics if topic.stream_id() in stream_ids] hot_topics = heapq.nlargest(2, topics, key=DigestTopic.diversity) @@ -240,16 +240,16 @@ def get_hot_topics( return hot_topics -def get_recently_created_streams(realm: Realm, threshold: datetime) -> List[Stream]: +def get_recently_created_streams(realm: Realm, threshold: datetime) -> list[Stream]: fields = ["id", "name", "is_web_public", "invite_only"] return list(get_active_streams(realm).filter(date_created__gt=threshold).only(*fields)) def gather_new_streams( realm: Realm, - recently_created_streams: List[Stream], # streams only need id and name + recently_created_streams: list[Stream], # streams only need id and name can_access_public: bool, -) -> Tuple[int, Dict[str, List[str]]]: +) -> tuple[int, dict[str, list[str]]]: if can_access_public: new_streams = [stream for stream in recently_created_streams if not stream.invite_only] else: @@ -271,7 +271,7 @@ def enough_traffic(hot_conversations: str, new_streams: int) -> bool: return bool(hot_conversations or new_streams) -def get_user_stream_map(user_ids: List[int], cutoff_date: datetime) -> Dict[int, Set[int]]: +def get_user_stream_map(user_ids: list[int], cutoff_date: datetime) -> dict[int, set[int]]: """Skipping streams where the user's subscription status has changed when constructing digests is critical to ensure correctness for streams without shared history, guest users, and long-term idle @@ -314,14 +314,14 @@ def get_user_stream_map(user_ids: List[int], cutoff_date: datetime) -> Dict[int, ) # maps user_id -> {stream_id, stream_id, ...} - dct: Dict[int, Set[int]] = defaultdict(set) + dct: dict[int, set[int]] = defaultdict(set) for row in rows: dct[row["user_profile_id"]].add(row["recipient__type_id"]) return dct -def get_slim_stream_id_map(realm: Realm) -> Dict[int, Stream]: +def get_slim_stream_id_map(realm: Realm) -> dict[int, Stream]: # "slim" because it only fetches the names of the stream objects, # suitable for passing into build_message_list. streams = get_active_streams(realm).only("id", "name") @@ -330,7 +330,7 @@ def get_slim_stream_id_map(realm: Realm) -> Dict[int, Stream]: def bulk_get_digest_context( users: Collection[UserProfile] | QuerySet[UserProfile], cutoff: float -) -> Iterator[Tuple[UserProfile, Dict[str, Any]]]: +) -> Iterator[tuple[UserProfile, dict[str, Any]]]: # We expect a non-empty list of users all from the same realm. assert users realm = next(iter(users)).realm @@ -380,14 +380,14 @@ def bulk_get_digest_context( yield user, context -def get_digest_context(user: UserProfile, cutoff: float) -> Dict[str, Any]: +def get_digest_context(user: UserProfile, cutoff: float) -> dict[str, Any]: for ignored, context in bulk_get_digest_context([user], cutoff): return context raise AssertionError("Unreachable") @transaction.atomic -def bulk_handle_digest_email(user_ids: List[int], cutoff: float) -> None: +def bulk_handle_digest_email(user_ids: list[int], cutoff: float) -> None: # We go directly to the database to get user objects, # since inactive users are likely to not be in the cache. users = ( @@ -418,7 +418,7 @@ def bulk_handle_digest_email(user_ids: List[int], cutoff: float) -> None: bulk_write_realm_audit_logs(digest_users) -def bulk_write_realm_audit_logs(users: List[UserProfile]) -> None: +def bulk_write_realm_audit_logs(users: list[UserProfile]) -> None: if not users: return diff --git a/zerver/lib/display_recipient.py b/zerver/lib/display_recipient.py index 8ec4a89090..2316bfb737 100644 --- a/zerver/lib/display_recipient.py +++ b/zerver/lib/display_recipient.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, TypedDict +from typing import TYPE_CHECKING, Optional, TypedDict from django_stubs_ext import ValuesQuerySet @@ -37,7 +37,7 @@ def get_display_recipient_cache_key( @cache_with_key(get_display_recipient_cache_key, timeout=3600 * 24 * 7) def get_display_recipient_remote_cache( recipient_id: int, recipient_type: int, recipient_type_id: Optional[int] -) -> List[UserDisplayRecipient]: +) -> list[UserDisplayRecipient]: """ This returns an appropriate object describing the recipient of a direct message (whether individual or group). @@ -68,7 +68,7 @@ def user_dict_id_fetcher(user_dict: UserDisplayRecipient) -> int: return user_dict["id"] -def bulk_fetch_single_user_display_recipients(uids: List[int]) -> Dict[int, UserDisplayRecipient]: +def bulk_fetch_single_user_display_recipients(uids: list[int]) -> dict[int, UserDisplayRecipient]: from zerver.models import UserProfile return bulk_cached_fetch( @@ -85,8 +85,8 @@ def bulk_fetch_single_user_display_recipients(uids: List[int]) -> Dict[int, User def bulk_fetch_stream_names( - recipient_tuples: Set[Tuple[int, int, int]], -) -> Dict[int, str]: + recipient_tuples: set[tuple[int, int, int]], +) -> dict[int, str]: """ Takes set of tuples of the form (recipient_id, recipient_type, recipient_type_id) Returns dict mapping recipient_id to corresponding display_recipient @@ -101,7 +101,7 @@ def bulk_fetch_stream_names( recipient_ids = [tup[0] for tup in recipient_tuples] def get_tiny_stream_rows( - recipient_ids: List[int], + recipient_ids: list[int], ) -> ValuesQuerySet[Stream, TinyStreamResult]: stream_ids = [recipient_id_to_stream_id[recipient_id] for recipient_id in recipient_ids] return Stream.objects.filter(id__in=stream_ids).values("recipient_id", "name") @@ -113,7 +113,7 @@ def bulk_fetch_stream_names( return row["name"] # ItemT = TinyStreamResult, CacheItemT = str (name), ObjKT = int (recipient_id) - stream_display_recipients: Dict[int, str] = generic_bulk_cached_fetch( + stream_display_recipients: dict[int, str] = generic_bulk_cached_fetch( cache_key_function=display_recipient_cache_key, query_function=get_tiny_stream_rows, object_ids=recipient_ids, @@ -127,8 +127,8 @@ def bulk_fetch_stream_names( def bulk_fetch_user_display_recipients( - recipient_tuples: Set[Tuple[int, int, int]], -) -> Dict[int, List[UserDisplayRecipient]]: + recipient_tuples: set[tuple[int, int, int]], +) -> dict[int, list[UserDisplayRecipient]]: """ Takes set of tuples of the form (recipient_id, recipient_type, recipient_type_id) Returns dict mapping recipient_id to corresponding display_recipient @@ -156,7 +156,7 @@ def bulk_fetch_user_display_recipients( ) # Find all user ids whose UserProfiles we will need to fetch: - user_ids_to_fetch: Set[int] = set() + user_ids_to_fetch: set[int] = set() for ignore_recipient_id, ignore_recipient_type, user_id in personal_tuples: user_ids_to_fetch.add(user_id) @@ -183,8 +183,8 @@ def bulk_fetch_user_display_recipients( def bulk_fetch_display_recipients( - recipient_tuples: Set[Tuple[int, int, int]], -) -> Dict[int, DisplayRecipientT]: + recipient_tuples: set[tuple[int, int, int]], +) -> dict[int, DisplayRecipientT]: """ Takes set of tuples of the form (recipient_id, recipient_type, recipient_type_id) Returns dict mapping recipient_id to corresponding display_recipient @@ -209,7 +209,7 @@ def bulk_fetch_display_recipients( @return_same_value_during_entire_request def get_display_recipient_by_id( recipient_id: int, recipient_type: int, recipient_type_id: Optional[int] -) -> List[UserDisplayRecipient]: +) -> list[UserDisplayRecipient]: """ returns: an object describing the recipient (using a cache). If the type is a stream, the type_id must be an int; a string is returned. @@ -221,7 +221,7 @@ def get_display_recipient_by_id( return get_display_recipient_remote_cache(recipient_id, recipient_type, recipient_type_id) -def get_display_recipient(recipient: "Recipient") -> List[UserDisplayRecipient]: +def get_display_recipient(recipient: "Recipient") -> list[UserDisplayRecipient]: return get_display_recipient_by_id( recipient.id, recipient.type, @@ -231,7 +231,7 @@ def get_display_recipient(recipient: "Recipient") -> List[UserDisplayRecipient]: def get_recipient_ids( recipient: Optional["Recipient"], user_profile_id: int -) -> Tuple[List[int], str]: +) -> tuple[list[int], str]: from zerver.models import Recipient if recipient is None: diff --git a/zerver/lib/drafts.py b/zerver/lib/drafts.py index eca1b2110b..81e4745d07 100644 --- a/zerver/lib/drafts.py +++ b/zerver/lib/drafts.py @@ -1,6 +1,6 @@ import time from functools import wraps -from typing import Any, Callable, Dict, List, Literal, Union +from typing import Any, Callable, Literal, Union from django.core.exceptions import ValidationError from django.http import HttpRequest, HttpResponse @@ -25,7 +25,7 @@ class DraftData(BaseModel): model_config = ConfigDict(extra="forbid") type: Literal["private", "stream", ""] - to: List[int] + to: list[int] topic: str content: Annotated[str, RequiredStringConstraint()] timestamp: Union[int, float, None] = None @@ -33,7 +33,7 @@ class DraftData(BaseModel): def further_validated_draft_dict( draft_dict: DraftData, user_profile: UserProfile -) -> Dict[str, Any]: +) -> dict[str, Any]: """Take a DraftData object that was already validated by the @typed_endpoint decorator then further sanitize, validate, and transform it. Ultimately return this "further validated" draft dict. @@ -96,7 +96,7 @@ def draft_endpoint( return draft_view_func -def do_create_drafts(drafts: List[DraftData], user_profile: UserProfile) -> List[Draft]: +def do_create_drafts(drafts: list[DraftData], user_profile: UserProfile) -> list[Draft]: """Create drafts in bulk for a given user based on the DraftData objects. Since currently, the only place this method is being used (apart from tests) is from the create_draft view, we assume that these are syntactically valid diff --git a/zerver/lib/email_mirror.py b/zerver/lib/email_mirror.py index 9f35d19fa9..b1f228e84d 100644 --- a/zerver/lib/email_mirror.py +++ b/zerver/lib/email_mirror.py @@ -3,7 +3,7 @@ import re import secrets from email.headerregistry import Address, AddressHeader from email.message import EmailMessage -from typing import Dict, List, Match, Optional, Tuple +from typing import Match, Optional from django.conf import settings from django.utils.translation import gettext as _ @@ -350,7 +350,7 @@ def extract_and_upload_attachments(message: EmailMessage, realm: Realm, sender: return "\n".join(attachment_links) -def decode_stream_email_address(email: str) -> Tuple[Stream, Dict[str, bool]]: +def decode_stream_email_address(email: str) -> tuple[Stream, dict[str, bool]]: token, options = decode_email_address(email) try: @@ -501,7 +501,7 @@ def validate_to_address(rcpt_to: str) -> None: decode_stream_email_address(rcpt_to) -def mirror_email_message(rcpt_to: str, msg_base64: str) -> Dict[str, str]: +def mirror_email_message(rcpt_to: str, msg_base64: str) -> dict[str, str]: try: validate_to_address(rcpt_to) except ZulipEmailForwardError as e: @@ -533,7 +533,7 @@ class RateLimitedRealmMirror(RateLimitedObject): return f"{type(self).__name__}:{self.realm.string_id}" @override - def rules(self) -> List[Tuple[int, int]]: + def rules(self) -> list[tuple[int, int]]: return settings.RATE_LIMITING_MIRROR_REALM_RULES diff --git a/zerver/lib/email_mirror_helpers.py b/zerver/lib/email_mirror_helpers.py index 6ee9a897bd..9ce0472192 100644 --- a/zerver/lib/email_mirror_helpers.py +++ b/zerver/lib/email_mirror_helpers.py @@ -1,5 +1,5 @@ import re -from typing import Any, Callable, Dict, Tuple +from typing import Any, Callable from django.conf import settings from django.utils.text import slugify @@ -7,8 +7,8 @@ from django.utils.text import slugify from zerver.models import Stream -def default_option_handler_factory(address_option: str) -> Callable[[Dict[str, Any]], None]: - def option_setter(options_dict: Dict[str, Any]) -> None: +def default_option_handler_factory(address_option: str) -> Callable[[dict[str, Any]], None]: + def option_setter(options_dict: dict[str, Any]) -> None: options_dict[address_option.replace("-", "_")] = True return option_setter @@ -79,7 +79,7 @@ def encode_email_address_helper(name: str, email_token: str, show_sender: bool = return settings.EMAIL_GATEWAY_PATTERN % (encoded_token,) -def decode_email_address(email: str) -> Tuple[str, Dict[str, bool]]: +def decode_email_address(email: str) -> tuple[str, dict[str, bool]]: # Perform the reverse of encode_email_address. Returns a tuple of # (email_token, options) msg_string = get_email_gateway_message_string_from_address(email) @@ -96,7 +96,7 @@ def decode_email_address(email: str) -> Tuple[str, Dict[str, bool]]: msg_string = msg_string.replace(".", "+") parts = msg_string.split("+") - options: Dict[str, bool] = {} + options: dict[str, bool] = {} for part in parts: if part in optional_address_tokens: optional_address_tokens[part](options) diff --git a/zerver/lib/email_notifications.py b/zerver/lib/email_notifications.py index 27c51c992b..e947169fd8 100644 --- a/zerver/lib/email_notifications.py +++ b/zerver/lib/email_notifications.py @@ -9,7 +9,7 @@ from collections import defaultdict from dataclasses import dataclass from datetime import timedelta from email.headerregistry import Address -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Optional, Union import lxml.html import zoneinfo @@ -92,7 +92,7 @@ def relative_to_full_url(fragment: lxml.html.HtmlElement, base_url: str) -> None def fix_emojis(fragment: lxml.html.HtmlElement, emojiset: str) -> None: - def make_emoji_img_elem(emoji_span_elem: lxml.html.HtmlElement) -> Dict[str, Any]: + def make_emoji_img_elem(emoji_span_elem: lxml.html.HtmlElement) -> dict[str, Any]: # Convert the emoji spans to img tags. classes = emoji_span_elem.get("class") match = re.search(r"emoji-(?P\S+)", classes) @@ -185,15 +185,15 @@ def add_quote_prefix_in_text(content: str) -> str: def build_message_list( user: UserProfile, - messages: List[Message], - stream_id_map: Optional[Dict[int, Stream]] = None, # only needs id, name -) -> List[Dict[str, Any]]: + messages: list[Message], + stream_id_map: Optional[dict[int, Stream]] = None, # only needs id, name +) -> list[dict[str, Any]]: """ Builds the message list object for the message notification email template. The messages are collapsed into per-recipient and per-sender blocks, like our web interface """ - messages_to_render: List[Dict[str, Any]] = [] + messages_to_render: list[dict[str, Any]] = [] def sender_string(message: Message) -> str: if message.recipient.type in (Recipient.STREAM, Recipient.DIRECT_MESSAGE_GROUP): @@ -209,7 +209,7 @@ def build_message_list( def prepend_sender_to_message( message_plain: str, message_html: str, sender: str - ) -> Tuple[str, str]: + ) -> tuple[str, str]: message_plain = f"{sender}:\n{message_plain}" message_soup = BeautifulSoup(message_html, "html.parser") sender_name_soup = BeautifulSoup(f"{sender}: ", "html.parser") @@ -222,7 +222,7 @@ def build_message_list( message_soup.insert(0, sender_name_soup) return message_plain, str(message_soup) - def build_message_payload(message: Message, sender: Optional[str] = None) -> Dict[str, str]: + def build_message_payload(message: Message, sender: Optional[str] = None) -> dict[str, str]: plain = message.content plain = fix_plaintext_image_urls(plain) # There's a small chance of colliding with non-Zulip URLs containing @@ -246,13 +246,13 @@ def build_message_list( plain, html = prepend_sender_to_message(plain, html, sender) return {"plain": plain, "html": html} - def build_sender_payload(message: Message) -> Dict[str, Any]: + def build_sender_payload(message: Message) -> dict[str, Any]: sender = sender_string(message) return {"sender": sender, "content": [build_message_payload(message, sender)]} - def message_header(message: Message) -> Dict[str, Any]: + def message_header(message: Message) -> dict[str, Any]: if message.recipient.type == Recipient.PERSONAL: - grouping: Dict[str, Any] = {"user": message.sender_id} + grouping: dict[str, Any] = {"user": message.sender_id} narrow_link = personal_narrow_url( realm=user.realm, sender=message.sender, @@ -377,7 +377,7 @@ def include_realm_name_in_missedmessage_emails_subject(user_profile: UserProfile def do_send_missedmessage_events_reply_in_zulip( - user_profile: UserProfile, missed_messages: List[Dict[str, Any]], message_count: int + user_profile: UserProfile, missed_messages: list[dict[str, Any]], message_count: int ) -> None: """ Send a reminder email to a user if she's missed some direct messages @@ -596,7 +596,7 @@ class MissedMessageData: def handle_missedmessage_emails( - user_profile_id: int, message_ids: Dict[int, MissedMessageData] + user_profile_id: int, message_ids: dict[int, MissedMessageData] ) -> None: user_profile = get_user_profile_by_id(user_profile_id) if user_profile.is_bot: # nocoverage @@ -629,7 +629,7 @@ def handle_missedmessage_emails( # We bucket messages by tuples that identify similar messages. # For streams it's recipient_id and topic. # For direct messages it's recipient id and sender. - messages_by_bucket: Dict[Tuple[int, Union[int, str]], List[Message]] = defaultdict(list) + messages_by_bucket: dict[tuple[int, Union[int, str]], list[Message]] = defaultdict(list) for msg in messages: if msg.recipient.type == Recipient.PERSONAL: # For direct messages group using (recipient, sender). @@ -649,7 +649,7 @@ def handle_missedmessage_emails( msg_list.extend(filtered_context_messages) # Sort emails by least recently-active discussion. - bucket_tups: List[Tuple[Tuple[int, Union[int, str]], int]] = [] + bucket_tups: list[tuple[tuple[int, Union[int, str]], int]] = [] for bucket_tup, msg_list in messages_by_bucket.items(): max_message_id = max(msg_list, key=lambda msg: msg.id).id bucket_tups.append((bucket_tup, max_message_id)) @@ -675,7 +675,7 @@ def handle_missedmessage_emails( ) -def get_onboarding_email_schedule(user: UserProfile) -> Dict[str, timedelta]: +def get_onboarding_email_schedule(user: UserProfile) -> dict[str, timedelta]: onboarding_emails = { # The delay should be 1 hour before the below specified number of days # as our goal is to maximize the chance that this email is near the top @@ -744,7 +744,7 @@ def get_onboarding_email_schedule(user: UserProfile) -> Dict[str, timedelta]: return onboarding_emails -def get_org_type_zulip_guide(realm: Realm) -> Tuple[Any, str]: +def get_org_type_zulip_guide(realm: Realm) -> tuple[Any, str]: for realm_type, realm_type_details in Realm.ORG_TYPES.items(): if realm_type_details["id"] == realm.org_type: organization_type_in_template = realm_type @@ -761,7 +761,7 @@ def get_org_type_zulip_guide(realm: Realm) -> Tuple[Any, str]: return (None, "") -def welcome_sender_information() -> Tuple[Optional[str], str]: +def welcome_sender_information() -> tuple[Optional[str], str]: if settings.WELCOME_EMAIL_SENDER is not None: from_name = settings.WELCOME_EMAIL_SENDER["name"] from_address = settings.WELCOME_EMAIL_SENDER["email"] diff --git a/zerver/lib/email_validation.py b/zerver/lib/email_validation.py index 8cd2340a77..9eb347117c 100644 --- a/zerver/lib/email_validation.py +++ b/zerver/lib/email_validation.py @@ -1,6 +1,6 @@ from email.errors import HeaderParseError from email.headerregistry import Address -from typing import Callable, Dict, Optional, Set, Tuple +from typing import Callable, Optional from django.core import validators from django.core.exceptions import ValidationError @@ -121,9 +121,9 @@ def email_reserved_for_system_bots_error(email: str) -> str: def get_existing_user_errors( target_realm: Realm, - emails: Set[str], + emails: set[str], verbose: bool = False, -) -> Dict[str, Tuple[str, bool]]: +) -> dict[str, tuple[str, bool]]: """ We use this function even for a list of one emails. @@ -132,7 +132,7 @@ def get_existing_user_errors( to cross-realm bots and mirror dummies too. """ - errors: Dict[str, Tuple[str, bool]] = {} + errors: dict[str, tuple[str, bool]] = {} users = get_users_by_delivery_email(emails, target_realm).only( "delivery_email", diff --git a/zerver/lib/event_schema.py b/zerver/lib/event_schema.py index a682618302..e8eb161b84 100644 --- a/zerver/lib/event_schema.py +++ b/zerver/lib/event_schema.py @@ -27,7 +27,7 @@ # See check_delete_message and check_presence for examples of this # paradigm. -from typing import Dict, List, Sequence, Set, Tuple, Union +from typing import Sequence, Union from zerver.lib.data_types import ( DictType, @@ -71,7 +71,7 @@ basic_stream_fields = [ ("stream_weekly_traffic", OptionalType(int)), ] -subscription_fields: Sequence[Tuple[str, object]] = [ +subscription_fields: Sequence[tuple[str, object]] = [ *basic_stream_fields, ("audible_notifications", OptionalType(bool)), ("color", str), @@ -231,7 +231,7 @@ _check_delete_message = make_checker(delete_message_event) def check_delete_message( var_name: str, - event: Dict[str, object], + event: dict[str, object], message_type: str, num_message_ids: int, is_legacy: bool, @@ -312,7 +312,7 @@ _check_has_zoom_token = make_checker(has_zoom_token_event) def check_has_zoom_token( var_name: str, - event: Dict[str, object], + event: dict[str, object], value: bool, ) -> None: _check_has_zoom_token(var_name, event) @@ -329,7 +329,7 @@ _check_heartbeat = make_checker(heartbeat_event) def check_heartbeat( var_name: str, - event: Dict[str, object], + event: dict[str, object], ) -> None: _check_heartbeat(var_name, event) @@ -498,7 +498,7 @@ _check_presence = make_checker(presence_event) def check_presence( var_name: str, - event: Dict[str, object], + event: dict[str, object], has_email: bool, presence_key: str, status: str, @@ -622,7 +622,7 @@ _check_realm_bot_add = make_checker(realm_bot_add_event) def check_realm_bot_add( var_name: str, - event: Dict[str, object], + event: dict[str, object], ) -> None: _check_realm_bot_add(var_name, event) @@ -699,7 +699,7 @@ _check_realm_bot_update = make_checker(realm_bot_update_event) def check_realm_bot_update( # Check schema plus the field. var_name: str, - event: Dict[str, object], + event: dict[str, object], field: str, ) -> None: # Check the overall schema first. @@ -756,7 +756,7 @@ realm_playgrounds_event = event_dict_type( _check_realm_playgrounds = make_checker(realm_playgrounds_event) -def check_realm_playgrounds(var_name: str, event: Dict[str, object]) -> None: +def check_realm_playgrounds(var_name: str, event: dict[str, object]) -> None: _check_realm_playgrounds(var_name, event) assert isinstance(event["realm_playgrounds"], list) @@ -782,7 +782,7 @@ realm_emoji_update_event = event_dict_type( _check_realm_emoji_update = make_checker(realm_emoji_update_event) -def check_realm_emoji_update(var_name: str, event: Dict[str, object]) -> None: +def check_realm_emoji_update(var_name: str, event: dict[str, object]) -> None: """ The way we send realm emojis is kinda clumsy--we send a dict mapping the emoji id to a sub_dict with @@ -823,7 +823,7 @@ _check_realm_export = make_checker(realm_export_event) def check_realm_export( var_name: str, - event: Dict[str, object], + event: dict[str, object], has_export_url: bool, has_deleted_timestamp: bool, has_failed_timestamp: bool, @@ -888,7 +888,7 @@ _check_realm_update = make_checker(realm_update_event) def check_realm_update( var_name: str, - event: Dict[str, object], + event: dict[str, object], prop: str, ) -> None: """ @@ -948,7 +948,7 @@ _check_realm_default_update = make_checker(realm_user_settings_defaults_update_e def check_realm_default_update( var_name: str, - event: Dict[str, object], + event: dict[str, object], prop: str, ) -> None: _check_realm_default_update(var_name, event) @@ -1081,7 +1081,7 @@ _check_realm_update_dict = make_checker(realm_update_dict_event) def check_realm_update_dict( # handle union types var_name: str, - event: Dict[str, object], + event: dict[str, object], ) -> None: _check_realm_update_dict(var_name, event) @@ -1254,7 +1254,7 @@ _check_realm_user_update = make_checker(realm_user_update_event) def check_realm_user_update( # person_flavor tells us which extra fields we need var_name: str, - event: Dict[str, object], + event: dict[str, object], person_flavor: str, ) -> None: _check_realm_user_update(var_name, event) @@ -1375,7 +1375,7 @@ _check_stream_update = make_checker(stream_update_event) def check_stream_update( var_name: str, - event: Dict[str, object], + event: dict[str, object], ) -> None: _check_stream_update(var_name, event) prop = event["property"] @@ -1495,7 +1495,7 @@ _check_subscription_update = make_checker(subscription_update_event) def check_subscription_update( - var_name: str, event: Dict[str, object], property: str, value: bool + var_name: str, event: dict[str, object], property: str, value: bool ) -> None: _check_subscription_update(var_name, event) assert event["property"] == property @@ -1577,7 +1577,7 @@ _check_user_settings_update = make_checker(user_settings_update_event) def check_update_display_settings( var_name: str, - event: Dict[str, object], + event: dict[str, object], ) -> None: """ Display setting events have a "setting" field that @@ -1603,7 +1603,7 @@ def check_update_display_settings( def check_user_settings_update( var_name: str, - event: Dict[str, object], + event: dict[str, object], ) -> None: _check_user_settings_update(var_name, event) setting_name = event["property"] @@ -1635,7 +1635,7 @@ _check_update_global_notifications = make_checker(update_global_notifications_ev def check_update_global_notifications( var_name: str, - event: Dict[str, object], + event: dict[str, object], desired_val: Union[bool, int, str], ) -> None: """ @@ -1663,19 +1663,19 @@ update_message_required_fields = [ ("rendering_only", bool), ] -update_message_stream_fields: List[Tuple[str, object]] = [ +update_message_stream_fields: list[tuple[str, object]] = [ ("stream_id", int), ("stream_name", str), ] -update_message_content_fields: List[Tuple[str, object]] = [ +update_message_content_fields: list[tuple[str, object]] = [ ("is_me_message", bool), ("orig_content", str), ("orig_rendered_content", str), ("prev_rendered_content_version", int), ] -update_message_content_or_embedded_data_fields: List[Tuple[str, object]] = [ +update_message_content_or_embedded_data_fields: list[tuple[str, object]] = [ ("content", str), ("rendered_content", str), ] @@ -1685,11 +1685,11 @@ update_message_topic_fields = [ (TOPIC_NAME, str), ] -update_message_change_stream_fields: List[Tuple[str, object]] = [ +update_message_change_stream_fields: list[tuple[str, object]] = [ ("new_stream_id", int), ] -update_message_change_stream_or_topic_fields: List[Tuple[str, object]] = [ +update_message_change_stream_or_topic_fields: list[tuple[str, object]] = [ ( "propagate_mode", EnumType( @@ -1723,7 +1723,7 @@ _check_update_message = make_checker(update_message_event) def check_update_message( var_name: str, - event: Dict[str, object], + event: dict[str, object], is_stream_message: bool, has_content: bool, has_topic: bool, @@ -1876,7 +1876,7 @@ user_group_update_event = event_dict_type( _check_user_group_update = make_checker(user_group_update_event) -def check_user_group_update(var_name: str, event: Dict[str, object], field: str) -> None: +def check_user_group_update(var_name: str, event: dict[str, object], field: str) -> None: _check_user_group_update(var_name, event) assert isinstance(event["data"], dict) @@ -1922,7 +1922,7 @@ user_status_event = event_dict_type( _check_user_status = make_checker(user_status_event) -def check_user_status(var_name: str, event: Dict[str, object], fields: Set[str]) -> None: +def check_user_status(var_name: str, event: dict[str, object], fields: set[str]) -> None: _check_user_status(var_name, event) assert set(event.keys()) == {"id", "type", "user_id"} | fields diff --git a/zerver/lib/events.py b/zerver/lib/events.py index cccf2e14f5..d92fa130f5 100644 --- a/zerver/lib/events.py +++ b/zerver/lib/events.py @@ -3,7 +3,7 @@ import copy import logging import time -from typing import Any, Callable, Collection, Dict, Iterable, Mapping, Optional, Sequence, Set +from typing import Any, Callable, Collection, Iterable, Mapping, Optional, Sequence from django.conf import settings from django.utils.translation import gettext as _ @@ -102,7 +102,7 @@ from zerver.tornado.django_api import get_user_events, request_event_queue from zproject.backends import email_auth_enabled, password_auth_enabled -def add_realm_logo_fields(state: Dict[str, Any], realm: Realm) -> None: +def add_realm_logo_fields(state: dict[str, Any], realm: Realm) -> None: state["realm_logo_url"] = get_realm_logo_url(realm, night=False) state["realm_logo_source"] = get_realm_logo_source(realm, night=False) state["realm_night_logo_url"] = get_realm_logo_url(realm, night=True) @@ -138,7 +138,7 @@ def fetch_initial_state_data( pronouns_field_type_supported: bool = True, linkifier_url_template: bool = False, user_list_incomplete: bool = False, -) -> Dict[str, Any]: +) -> dict[str, Any]: """When `event_types` is None, fetches the core data powering the web app's `page_params` and `/api/v1/register` (for mobile/terminal apps). Can also fetch a subset as determined by `event_types`. @@ -150,7 +150,7 @@ def fetch_initial_state_data( corresponding events for changes in the data structures and new code to apply_events (and add a test in test_events.py). """ - state: Dict[str, Any] = {"queue_id": queue_id} + state: dict[str, Any] = {"queue_id": queue_id} if event_types is None: # return True always @@ -756,8 +756,8 @@ def fetch_initial_state_data( def apply_events( user_profile: UserProfile, *, - state: Dict[str, Any], - events: Iterable[Dict[str, Any]], + state: dict[str, Any], + events: Iterable[dict[str, Any]], fetch_event_types: Optional[Collection[str]], client_gravatar: bool, slim_presence: bool, @@ -791,8 +791,8 @@ def apply_events( def apply_event( user_profile: UserProfile, *, - state: Dict[str, Any], - event: Dict[str, Any], + state: dict[str, Any], + event: dict[str, Any], client_gravatar: bool, slim_presence: bool, include_subscribers: bool, @@ -1645,7 +1645,7 @@ def do_events_register( fetch_event_types: Optional[Collection[str]] = None, spectator_requested_language: Optional[str] = None, pronouns_field_type_supported: bool = True, -) -> Dict[str, Any]: +) -> dict[str, Any]: # Technically we don't need to check this here because # build_narrow_predicate will check it, but it's nicer from an error # handling perspective to do it before contacting Tornado @@ -1662,7 +1662,7 @@ def do_events_register( user_list_incomplete = client_capabilities.get("user_list_incomplete", False) if fetch_event_types is not None: - event_types_set: Optional[Set[str]] = set(fetch_event_types) + event_types_set: Optional[set[str]] = set(fetch_event_types) elif event_types is not None: event_types_set = set(event_types) else: @@ -1771,7 +1771,7 @@ def do_events_register( def post_process_state( - user_profile: Optional[UserProfile], ret: Dict[str, Any], notification_settings_null: bool + user_profile: Optional[UserProfile], ret: dict[str, Any], notification_settings_null: bool ) -> None: """ NOTE: diff --git a/zerver/lib/exceptions.py b/zerver/lib/exceptions.py index d1acc476ce..04bd3f8a8c 100644 --- a/zerver/lib/exceptions.py +++ b/zerver/lib/exceptions.py @@ -1,5 +1,5 @@ from enum import Enum, auto -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Optional, Union from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ @@ -95,7 +95,7 @@ class JsonableError(Exception): code: ErrorCode = ErrorCode.BAD_REQUEST # Override this in subclasses if providing structured data. - data_fields: List[str] = [] + data_fields: list[str] = [] # Optionally override this in subclasses to return a different HTTP status, # like 403 or 404. @@ -120,7 +120,7 @@ class JsonableError(Exception): return "{_msg}" @property - def extra_headers(self) -> Dict[str, Any]: + def extra_headers(self) -> dict[str, Any]: return {} # @@ -135,7 +135,7 @@ class JsonableError(Exception): return self.msg_format().format(**format_data) @property - def data(self) -> Dict[str, Any]: + def data(self) -> dict[str, Any]: return dict(((f, getattr(self, f)) for f in self.data_fields), code=self.code.name) @override @@ -160,7 +160,7 @@ class UnauthorizedError(JsonableError): @property @override - def extra_headers(self) -> Dict[str, Any]: + def extra_headers(self) -> dict[str, Any]: extra_headers_dict = super().extra_headers extra_headers_dict["WWW-Authenticate"] = self.www_authenticate return extra_headers_dict @@ -195,7 +195,7 @@ class StreamWithIDDoesNotExistError(JsonableError): class IncompatibleParametersError(JsonableError): data_fields = ["parameters"] - def __init__(self, parameters: List[str]) -> None: + def __init__(self, parameters: list[str]) -> None: self.parameters = ", ".join(parameters) @staticmethod @@ -245,7 +245,7 @@ class RateLimitedError(JsonableError): @property @override - def extra_headers(self) -> Dict[str, Any]: + def extra_headers(self) -> dict[str, Any]: extra_headers_dict = super().extra_headers if self.secs_to_freedom is not None: extra_headers_dict["Retry-After"] = self.secs_to_freedom @@ -254,7 +254,7 @@ class RateLimitedError(JsonableError): @property @override - def data(self) -> Dict[str, Any]: + def data(self) -> dict[str, Any]: data_dict = super().data data_dict["retry-after"] = self.secs_to_freedom @@ -508,13 +508,13 @@ class InvitationError(JsonableError): def __init__( self, msg: str, - errors: List[Tuple[str, str, bool]], + errors: list[tuple[str, str, bool]], sent_invitations: bool, license_limit_reached: bool = False, daily_limit_reached: bool = False, ) -> None: self._msg: str = msg - self.errors: List[Tuple[str, str, bool]] = errors + self.errors: list[tuple[str, str, bool]] = errors self.sent_invitations: bool = sent_invitations self.license_limit_reached: bool = license_limit_reached self.daily_limit_reached: bool = daily_limit_reached diff --git a/zerver/lib/export.py b/zerver/lib/export.py index 8c048576f5..c5562041d4 100644 --- a/zerver/lib/export.py +++ b/zerver/lib/export.py @@ -15,7 +15,7 @@ import tempfile from contextlib import suppress from datetime import datetime from functools import cache -from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Set, Tuple, TypedDict +from typing import Any, Callable, Iterable, Mapping, Optional, TypedDict import orjson from django.apps import apps @@ -76,22 +76,22 @@ from zerver.models.realms import get_realm from zerver.models.users import get_system_bot, get_user_profile_by_id # Custom mypy types follow: -Record: TypeAlias = Dict[str, Any] +Record: TypeAlias = dict[str, Any] TableName = str -TableData: TypeAlias = Dict[TableName, List[Record]] +TableData: TypeAlias = dict[TableName, list[Record]] Field = str Path = str -Context: TypeAlias = Dict[str, Any] -FilterArgs: TypeAlias = Dict[str, Any] -IdSource: TypeAlias = Tuple[TableName, Field] +Context: TypeAlias = dict[str, Any] +FilterArgs: TypeAlias = dict[str, Any] +IdSource: TypeAlias = tuple[TableName, Field] SourceFilter: TypeAlias = Callable[[Record], bool] CustomFetch: TypeAlias = Callable[[TableData, Context], None] class MessagePartial(TypedDict): - zerver_message: List[Record] - zerver_userprofile_ids: List[int] + zerver_message: list[Record] + zerver_userprofile_ids: list[int] realm_id: int @@ -286,7 +286,7 @@ ANALYTICS_TABLES = { # # TODO: This data structure could likely eventually be replaced by # inspecting the corresponding Django models -DATE_FIELDS: Dict[TableName, List[Field]] = { +DATE_FIELDS: dict[TableName, list[Field]] = { "analytics_installationcount": ["end_time"], "analytics_realmcount": ["end_time"], "analytics_streamcount": ["end_time"], @@ -378,7 +378,7 @@ def write_data_to_file(output_file: Path, data: Any) -> None: logging.info("Finished writing %s", output_file) -def write_table_data(output_file: str, data: Dict[str, Any]) -> None: +def write_table_data(output_file: str, data: dict[str, Any]) -> None: # We sort by ids mostly so that humans can quickly do diffs # on two export jobs to see what changed (either due to new # data arriving or new code being deployed). @@ -390,7 +390,7 @@ def write_table_data(output_file: str, data: Dict[str, Any]) -> None: write_data_to_file(output_file, data) -def write_records_json_file(output_dir: str, records: List[Dict[str, Any]]) -> None: +def write_records_json_file(output_dir: str, records: list[dict[str, Any]]) -> None: # We want a somewhat deterministic sorting order here. All of our # versions of records.json include a "path" field in each element, # even though there's some variation among avatars/emoji/realm_icons/uploads @@ -409,7 +409,7 @@ def write_records_json_file(output_dir: str, records: List[Dict[str, Any]]) -> N logging.info("Finished writing %s", output_file) -def make_raw(query: Any, exclude: Optional[List[Field]] = None) -> List[Record]: +def make_raw(query: Any, exclude: Optional[list[Field]] = None) -> list[Record]: """ Takes a Django query and returns a JSONable list of dictionaries corresponding to the database rows. @@ -469,14 +469,14 @@ class Config: virtual_parent: Optional["Config"] = None, filter_args: Optional[FilterArgs] = None, custom_fetch: Optional[CustomFetch] = None, - custom_tables: Optional[List[TableName]] = None, - concat_and_destroy: Optional[List[TableName]] = None, + custom_tables: Optional[list[TableName]] = None, + concat_and_destroy: Optional[list[TableName]] = None, id_source: Optional[IdSource] = None, source_filter: Optional[SourceFilter] = None, include_rows: Optional[Field] = None, use_all: bool = False, is_seeded: bool = False, - exclude: Optional[List[Field]] = None, + exclude: Optional[list[Field]] = None, ) -> None: assert table or custom_tables self.table = table @@ -493,7 +493,7 @@ class Config: self.concat_and_destroy = concat_and_destroy self.id_source = id_source self.source_filter = source_filter - self.children: List[Config] = [] + self.children: list[Config] = [] if self.include_rows: assert self.include_rows.endswith("_id__in") @@ -596,7 +596,7 @@ def export_from_config( # When we concat_and_destroy, we are working with # temporary "tables" that are lists of records that # should already be ready to export. - data: List[Record] = [] + data: list[Record] = [] for t in config.concat_and_destroy: data += response[t] del response[t] @@ -619,7 +619,7 @@ def export_from_config( assert parent.table is not None assert config.include_rows is not None parent_ids = [r["id"] for r in response[parent.table]] - filter_params: Dict[str, Any] = {config.include_rows: parent_ids} + filter_params: dict[str, Any] = {config.include_rows: parent_ids} if config.filter_args is not None: filter_params.update(config.filter_args) assert model is not None @@ -1016,8 +1016,8 @@ def custom_fetch_user_profile(response: TableData, context: Context) -> None: exclude = EXCLUDED_USER_PROFILE_FIELDS rows = make_raw(list(query), exclude=exclude) - normal_rows: List[Record] = [] - dummy_rows: List[Record] = [] + normal_rows: list[Record] = [] + dummy_rows: list[Record] = [] for row in rows: if exportable_user_ids is not None: @@ -1072,8 +1072,8 @@ def custom_fetch_user_profile_cross_realm(response: TableData, context: Context) def fetch_attachment_data( - response: TableData, realm_id: int, message_ids: Set[int], scheduled_message_ids: Set[int] -) -> List[Attachment]: + response: TableData, realm_id: int, message_ids: set[int], scheduled_message_ids: set[int] +) -> list[Attachment]: attachments = list( Attachment.objects.filter( Q(messages__in=message_ids) | Q(scheduled_messages__in=scheduled_message_ids), @@ -1114,7 +1114,7 @@ def custom_fetch_realm_audit_logs_for_user(response: TableData, context: Context response["zerver_realmauditlog"] = rows -def fetch_reaction_data(response: TableData, message_ids: Set[int]) -> None: +def fetch_reaction_data(response: TableData, message_ids: set[int]) -> None: query = Reaction.objects.filter(message_id__in=list(message_ids)) response["zerver_reaction"] = make_raw(list(query)) @@ -1199,11 +1199,11 @@ def custom_fetch_realm_audit_logs_for_realm(response: TableData, context: Contex def fetch_usermessages( realm: Realm, - message_ids: Set[int], - user_profile_ids: Set[int], + message_ids: set[int], + user_profile_ids: set[int], message_filename: Path, consent_message_id: Optional[int] = None, -) -> List[Record]: +) -> list[Record]: # UserMessage export security rule: You can export UserMessages # for the messages you exported for the users in your realm. user_message_query = UserMessage.objects.filter( @@ -1261,11 +1261,11 @@ def export_partial_message_files( output_dir: Optional[Path] = None, public_only: bool = False, consent_message_id: Optional[int] = None, -) -> Set[int]: +) -> set[int]: if output_dir is None: output_dir = tempfile.mkdtemp(prefix="zulip-export") - def get_ids(records: Iterable[Mapping[str, Any]]) -> Set[int]: + def get_ids(records: Iterable[Mapping[str, Any]]) -> set[int]: return {x["id"] for x in records} # Basic security rule: You can export everything either... @@ -1292,7 +1292,7 @@ def export_partial_message_files( + response["zerver_userprofile_crossrealm"] ) - consented_user_ids: Set[int] = set() + consented_user_ids: set[int] = set() if consent_message_id is not None: consented_user_ids = get_consented_user_ids(consent_message_id) @@ -1397,7 +1397,7 @@ def export_partial_message_files( message_queries.append(messages_we_sent_to_them) - all_message_ids: Set[int] = set() + all_message_ids: set[int] = set() for message_query in message_queries: message_ids = set(get_id_list_gently_from_database(base_query=message_query, id_field="id")) @@ -1424,9 +1424,9 @@ def export_partial_message_files( def write_message_partials( *, realm: Realm, - message_id_chunks: List[List[int]], + message_id_chunks: list[list[int]], output_dir: Path, - user_profile_ids: Set[int], + user_profile_ids: set[int], ) -> None: dump_file_id = 1 @@ -1462,7 +1462,7 @@ def write_message_partials( def export_uploads_and_avatars( realm: Realm, *, - attachments: Optional[List[Attachment]] = None, + attachments: Optional[list[Attachment]] = None, user: Optional[UserProfile], output_dir: Path, ) -> None: @@ -1589,9 +1589,9 @@ def export_uploads_and_avatars( def _get_exported_s3_record( bucket_name: str, key: Object, processing_emoji: bool -) -> Dict[str, Any]: +) -> dict[str, Any]: # Helper function for export_files_from_s3 - record: Dict[str, Any] = dict( + record: dict[str, Any] = dict( s3_path=key.key, bucket=bucket_name, size=key.content_length, @@ -1666,8 +1666,8 @@ def export_files_from_s3( bucket_name: str, object_prefix: str, output_dir: Path, - user_ids: Set[int], - valid_hashes: Optional[Set[str]], + user_ids: set[int], + valid_hashes: Optional[set[str]], ) -> None: processing_uploads = flavor == "upload" processing_emoji = flavor == "emoji" @@ -1732,7 +1732,7 @@ def export_files_from_s3( def export_uploads_from_local( - realm: Realm, local_dir: Path, output_dir: Path, attachments: List[Attachment] + realm: Realm, local_dir: Path, output_dir: Path, attachments: list[Attachment] ) -> None: records = [] for count, attachment in enumerate(attachments, 1): @@ -1769,7 +1769,7 @@ def export_avatars_from_local( realm: Realm, local_dir: Path, output_dir: Path, - users: List[UserProfile], + users: list[UserProfile], handle_system_bots: bool, ) -> None: count = 0 @@ -1846,7 +1846,7 @@ def get_emoji_path(realm_emoji: RealmEmoji) -> str: def export_emoji_from_local( - realm: Realm, local_dir: Path, output_dir: Path, realm_emojis: List[RealmEmoji] + realm: Realm, local_dir: Path, output_dir: Path, realm_emojis: list[RealmEmoji] ) -> None: records = [] for count, realm_emoji in enumerate(realm_emojis, 1): @@ -1915,7 +1915,7 @@ def do_write_stats_file_for_realm_export(output_dir: Path) -> None: def get_exportable_scheduled_message_ids( realm: Realm, public_only: bool = False, consent_message_id: Optional[int] = None -) -> Set[int]: +) -> set[int]: """ Scheduled messages are private to the sender, so which ones we export depends on the public/consent/full export mode. @@ -1939,7 +1939,7 @@ def do_export_realm( realm: Realm, output_dir: Path, threads: int, - exportable_user_ids: Optional[Set[int]] = None, + exportable_user_ids: Optional[set[int]] = None, public_only: bool = False, consent_message_id: Optional[int] = None, export_as_active: Optional[bool] = None, @@ -2040,8 +2040,8 @@ def do_export_realm( def export_attachment_table( - realm: Realm, output_dir: Path, message_ids: Set[int], scheduled_message_ids: Set[int] -) -> List[Attachment]: + realm: Realm, output_dir: Path, message_ids: set[int], scheduled_message_ids: set[int] +) -> list[Attachment]: response: TableData = {} attachments = fetch_attachment_data( response=response, @@ -2107,7 +2107,7 @@ def do_export_user(user_profile: UserProfile, output_dir: Path) -> None: export_file = os.path.join(output_dir, "user.json") write_table_data(output_file=export_file, data=response) - reaction_message_ids: Set[int] = {row["message"] for row in response["zerver_reaction"]} + reaction_message_ids: set[int] = {row["message"] for row in response["zerver_reaction"]} logging.info("Exporting messages") export_messages_single_user( @@ -2203,7 +2203,7 @@ def get_single_user_config() -> Config: return user_profile_config -def get_id_list_gently_from_database(*, base_query: Any, id_field: str) -> List[int]: +def get_id_list_gently_from_database(*, base_query: Any, id_field: str) -> list[int]: """ Use this function if you need a HUGE number of ids from the database, and you don't mind a few extra trips. Particularly @@ -2240,7 +2240,7 @@ def get_id_list_gently_from_database(*, base_query: Any, id_field: str) -> List[ return all_ids -def chunkify(lst: List[int], chunk_size: int) -> List[List[int]]: +def chunkify(lst: list[int], chunk_size: int) -> list[list[int]]: # chunkify([1,2,3,4,5], 2) == [[1,2], [3,4], [5]] result = [] i = 0 @@ -2256,7 +2256,7 @@ def chunkify(lst: List[int], chunk_size: int) -> List[List[int]]: def export_messages_single_user( - user_profile: UserProfile, *, output_dir: Path, reaction_message_ids: Set[int] + user_profile: UserProfile, *, output_dir: Path, reaction_message_ids: set[int] ) -> None: @cache def get_recipient(recipient_id: int) -> str: @@ -2294,7 +2294,7 @@ def export_messages_single_user( ) # Find all message ids that pertain to us. - all_message_ids: Set[int] = set() + all_message_ids: set[int] = set() for query in [messages_from_me, messages_to_me]: all_message_ids |= set(get_id_list_gently_from_database(base_query=query, id_field="id")) @@ -2385,7 +2385,7 @@ def get_analytics_config() -> Config: return analytics_config -def get_consented_user_ids(consent_message_id: int) -> Set[int]: +def get_consented_user_ids(consent_message_id: int) -> set[int]: return set( Reaction.objects.filter( message_id=consent_message_id, @@ -2433,7 +2433,7 @@ def export_realm_wrapper( return public_url -def get_realm_exports_serialized(user: UserProfile) -> List[Dict[str, Any]]: +def get_realm_exports_serialized(user: UserProfile) -> list[dict[str, Any]]: # Exclude exports made via shell. 'acting_user=None', since they # aren't supported in the current API format. # diff --git a/zerver/lib/external_accounts.py b/zerver/lib/external_accounts.py index 1f1929eef5..8157bc7bb2 100644 --- a/zerver/lib/external_accounts.py +++ b/zerver/lib/external_accounts.py @@ -3,7 +3,6 @@ This module stores data for "external account" custom profile field. """ from dataclasses import dataclass -from typing import Dict from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ @@ -46,7 +45,7 @@ DEFAULT_EXTERNAL_ACCOUNTS = { } -def get_default_external_accounts() -> Dict[str, Dict[str, str]]: +def get_default_external_accounts() -> dict[str, dict[str, str]]: return { subtype: { "text": external_account.text, diff --git a/zerver/lib/fix_unreads.py b/zerver/lib/fix_unreads.py index d782ef9e26..1d6b0a9681 100644 --- a/zerver/lib/fix_unreads.py +++ b/zerver/lib/fix_unreads.py @@ -1,6 +1,6 @@ import logging import time -from typing import Callable, List, TypeVar +from typing import Callable, TypeVar from django.db import connection from django.db.backends.utils import CursorWrapper @@ -21,7 +21,7 @@ logger = logging.getLogger("zulip.fix_unreads") logger.setLevel(logging.WARNING) -def update_unread_flags(cursor: CursorWrapper, user_message_ids: List[int]) -> None: +def update_unread_flags(cursor: CursorWrapper, user_message_ids: list[int]) -> None: query = SQL( """ UPDATE zerver_usermessage @@ -43,7 +43,7 @@ def get_timing(message: str, f: Callable[[], T]) -> T: def fix_unsubscribed(cursor: CursorWrapper, user_profile: UserProfile) -> None: - def find_recipients() -> List[int]: + def find_recipients() -> list[int]: query = SQL( """ SELECT @@ -74,7 +74,7 @@ def fix_unsubscribed(cursor: CursorWrapper, user_profile: UserProfile) -> None: if not recipient_ids: return - def find() -> List[int]: + def find() -> list[int]: query = SQL( """ SELECT diff --git a/zerver/lib/generate_test_data.py b/zerver/lib/generate_test_data.py index 45e403347a..8bf34e9b41 100644 --- a/zerver/lib/generate_test_data.py +++ b/zerver/lib/generate_test_data.py @@ -1,7 +1,7 @@ import itertools import os import random -from typing import Any, Dict, List +from typing import Any import orjson @@ -9,14 +9,14 @@ from scripts.lib.zulip_tools import get_or_create_dev_uuid_var_path from zerver.lib.topic import RESOLVED_TOPIC_PREFIX -def load_config() -> Dict[str, Any]: +def load_config() -> dict[str, Any]: with open("zerver/tests/fixtures/config.generate_data.json", "rb") as infile: config = orjson.loads(infile.read()) return config -def generate_topics(num_topics: int) -> List[str]: +def generate_topics(num_topics: int) -> list[str]: config = load_config()["gen_fodder"] # Make single word topics account for 30% of total topics. @@ -54,7 +54,7 @@ def generate_topics(num_topics: int) -> List[str]: ] -def load_generators(config: Dict[str, Any]) -> Dict[str, Any]: +def load_generators(config: dict[str, Any]) -> dict[str, Any]: results = {} cfg = config["gen_fodder"] @@ -77,11 +77,11 @@ def load_generators(config: Dict[str, Any]) -> Dict[str, Any]: return results -def parse_file(config: Dict[str, Any], gens: Dict[str, Any], corpus_file: str) -> List[str]: +def parse_file(config: dict[str, Any], gens: dict[str, Any], corpus_file: str) -> list[str]: # First, load the entire file into a dictionary, # then apply our custom filters to it as needed. - paragraphs: List[str] = [] + paragraphs: list[str] = [] with open(corpus_file) as infile: # OUR DATA: we need to separate the person talking and what they say @@ -91,7 +91,7 @@ def parse_file(config: Dict[str, Any], gens: Dict[str, Any], corpus_file: str) - return paragraphs -def get_flair_gen(length: int) -> List[str]: +def get_flair_gen(length: int) -> list[str]: # Grab the percentages from the config file # create a list that we can consume that will guarantee the distribution result = [] @@ -105,7 +105,7 @@ def get_flair_gen(length: int) -> List[str]: return result -def add_flair(paragraphs: List[str], gens: Dict[str, Any]) -> List[str]: +def add_flair(paragraphs: list[str], gens: dict[str, Any]) -> list[str]: # roll the dice and see what kind of flair we should add, if any results = [] @@ -183,7 +183,7 @@ def add_link(text: str, link: str) -> str: return " ".join(vals) -def remove_line_breaks(fh: Any) -> List[str]: +def remove_line_breaks(fh: Any) -> list[str]: # We're going to remove line breaks from paragraphs results = [] # save the dialogs as tuples with (author, dialog) @@ -204,7 +204,7 @@ def remove_line_breaks(fh: Any) -> List[str]: return results -def write_file(paragraphs: List[str], filename: str) -> None: +def write_file(paragraphs: list[str], filename: str) -> None: with open(filename, "wb") as outfile: outfile.write(orjson.dumps(paragraphs)) diff --git a/zerver/lib/home.py b/zerver/lib/home.py index d255fd35e1..5e91f41ef9 100644 --- a/zerver/lib/home.py +++ b/zerver/lib/home.py @@ -1,7 +1,7 @@ import calendar import time from dataclasses import dataclass -from typing import Dict, List, Optional, Tuple +from typing import Optional from django.conf import settings from django.http import HttpRequest @@ -50,8 +50,8 @@ def get_furthest_read_time(user_profile: Optional[UserProfile]) -> Optional[floa return calendar.timegm(user_activity.last_visit.utctimetuple()) -def get_bot_types(user_profile: Optional[UserProfile]) -> List[Dict[str, object]]: - bot_types: List[Dict[str, object]] = [] +def get_bot_types(user_profile: Optional[UserProfile]) -> list[dict[str, object]]: + bot_types: list[dict[str, object]] = [] if user_profile is None: return bot_types @@ -138,11 +138,11 @@ def build_page_params_for_home_page_load( user_profile: Optional[UserProfile], realm: Realm, insecure_desktop_app: bool, - narrow: List[NarrowTerm], + narrow: list[NarrowTerm], narrow_stream: Optional[Stream], narrow_topic_name: Optional[str], needs_tutorial: bool, -) -> Tuple[int, Dict[str, object]]: +) -> tuple[int, dict[str, object]]: """ This function computes page_params for when we load the home page. @@ -200,7 +200,7 @@ def build_page_params_for_home_page_load( # These end up in a JavaScript Object named 'page_params'. # # Sync this with home_params_schema in base_page_params.ts. - page_params: Dict[str, object] = dict( + page_params: dict[str, object] = dict( page_type="home", ## Server settings. test_suite=settings.TEST_SUITE, diff --git a/zerver/lib/i18n.py b/zerver/lib/i18n.py index dc00f4b757..4c22ffca58 100644 --- a/zerver/lib/i18n.py +++ b/zerver/lib/i18n.py @@ -3,7 +3,7 @@ import logging import os from functools import lru_cache -from typing import Any, Dict, List, Optional +from typing import Any, Optional import orjson from django.conf import settings @@ -16,7 +16,7 @@ from zerver.models import Realm @lru_cache(None) -def get_language_list() -> List[Dict[str, Any]]: +def get_language_list() -> list[dict[str, Any]]: path = os.path.join(settings.DEPLOY_ROOT, "locale", "language_name_map.json") with open(path, "rb") as reader: languages = orjson.loads(reader.read()) @@ -32,13 +32,13 @@ def get_language_name(code: str) -> str: return "Unknown" -def get_available_language_codes() -> List[str]: +def get_available_language_codes() -> list[str]: language_list = get_language_list() codes = [language["code"] for language in language_list] return codes -def get_language_translation_data(language: str) -> Dict[str, str]: +def get_language_translation_data(language: str) -> dict[str, str]: if language == "en": return {} locale = translation.to_locale(language) diff --git a/zerver/lib/import_realm.py b/zerver/lib/import_realm.py index 085efb316d..3acb3425b1 100644 --- a/zerver/lib/import_realm.py +++ b/zerver/lib/import_realm.py @@ -4,7 +4,7 @@ import os import shutil from concurrent.futures import ProcessPoolExecutor, as_completed from datetime import datetime, timezone -from typing import Any, Dict, List, Optional, Set, Tuple +from typing import Any, Optional import bmemcached import orjson @@ -106,7 +106,7 @@ realm_tables = [ # # Code reviewers: give these tables extra scrutiny, as we need to # make sure to reload related tables AFTER we re-map the ids. -ID_MAP: Dict[str, Dict[int, int]] = { +ID_MAP: dict[str, dict[int, int]] = { "alertword": {}, "client": {}, "user_profile": {}, @@ -150,15 +150,15 @@ ID_MAP: Dict[str, Dict[int, int]] = { "scheduledmessage": {}, } -id_map_to_list: Dict[str, Dict[int, List[int]]] = { +id_map_to_list: dict[str, dict[int, list[int]]] = { "huddle_to_user_list": {}, } -path_maps: Dict[str, Dict[str, str]] = { +path_maps: dict[str, dict[str, str]] = { "attachment_path": {}, } -message_id_to_attachments: Dict[str, Dict[int, List[str]]] = { +message_id_to_attachments: dict[str, dict[int, list[str]]] = { "zerver_message": collections.defaultdict(list), "zerver_scheduledmessage": collections.defaultdict(list), } @@ -331,8 +331,8 @@ def fix_customprofilefield(data: TableData) -> None: def fix_message_rendered_content( realm: Realm, - sender_map: Dict[int, Record], - messages: List[Record], + sender_map: dict[int, Record], + messages: list[Record], content_key: str = "content", rendered_content_key: str = "rendered_content", ) -> None: @@ -425,7 +425,7 @@ def fix_message_rendered_content( def fix_message_edit_history( - realm: Realm, sender_map: Dict[int, Record], messages: List[Record] + realm: Realm, sender_map: dict[int, Record], messages: list[Record] ) -> None: user_id_map = ID_MAP["user_profile"] for message in messages: @@ -448,7 +448,7 @@ def fix_message_edit_history( message["edit_history"] = orjson.dumps(edit_history).decode() -def current_table_ids(data: TableData, table: TableName) -> List[int]: +def current_table_ids(data: TableData, table: TableName) -> list[int]: """ Returns the ids present in the current table """ @@ -469,7 +469,7 @@ def idseq(model_class: Any) -> str: return f"{model_class._meta.db_table}_id_seq" -def allocate_ids(model_class: Any, count: int) -> List[int]: +def allocate_ids(model_class: Any, count: int) -> list[int]: """ Increases the sequence number for a given table by the amount of objects being imported into that table. Hence, this gives a reserved range of IDs to import the @@ -528,7 +528,7 @@ def re_map_foreign_keys( def re_map_foreign_keys_internal( - data_table: List[Record], + data_table: list[Record], table: TableName, field_name: Field, related_table: TableName, @@ -634,9 +634,9 @@ def re_map_foreign_keys_many_to_many_internal( table: TableName, field_name: Field, related_table: TableName, - old_id_list: List[int], + old_id_list: list[int], verbose: bool = False, -) -> List[int]: +) -> list[int]: """ This is an internal function for tables with ManyToMany fields, which takes the old ID list of the ManyToMany relation and returns the @@ -712,7 +712,7 @@ def bulk_import_user_message_data(data: TableData, dump_file_id: int) -> None: # no tables use user_message.id as a foreign key, # so we can safely avoid all re-mapping complexity. - def process_batch(items: List[Dict[str, Any]]) -> None: + def process_batch(items: list[dict[str, Any]]) -> None: ums = [ UserMessageLite( user_profile_id=item["user_profile_id"], @@ -781,7 +781,7 @@ def bulk_import_client(data: TableData, model: Any, table: TableName) -> None: def fix_subscriptions_is_user_active_column( - data: TableData, user_profiles: List[UserProfile], crossrealm_user_ids: Set[int] + data: TableData, user_profiles: list[UserProfile], crossrealm_user_ids: set[int] ) -> None: table = get_db_table(Subscription) user_id_to_active_status = {user.id: user.is_active for user in user_profiles} @@ -792,7 +792,7 @@ def fix_subscriptions_is_user_active_column( sub["is_user_active"] = user_id_to_active_status[sub["user_profile_id"]] -def process_avatars(record: Dict[str, Any]) -> None: +def process_avatars(record: dict[str, Any]) -> None: if not record["s3_path"].endswith(".original"): return None user_profile = get_user_profile_by_id(record["user_profile_id"]) @@ -842,7 +842,7 @@ def import_uploads( records_filename = os.path.join(import_dir, "records.json") with open(records_filename, "rb") as records_file: - records: List[Dict[str, Any]] = orjson.loads(records_file.read()) + records: list[dict[str, Any]] = orjson.loads(records_file.read()) timestamp = datetime_to_timestamp(timezone_now()) re_map_foreign_keys_internal( @@ -1126,7 +1126,7 @@ def do_import_realm(import_dir: Path, subdomain: str, processes: int = 1) -> Rea # We expect Zulip server exports to contain these system groups, # this logic here is needed to handle the imports from other services. - role_system_groups_dict: Optional[Dict[int, NamedUserGroup]] = None + role_system_groups_dict: Optional[dict[int, NamedUserGroup]] = None if "zerver_usergroup" not in data: role_system_groups_dict = create_system_user_groups_for_realm(realm) @@ -1621,7 +1621,7 @@ def update_message_foreign_keys(import_dir: Path, sort_by_date: bool) -> None: # we're actually read the files a second time to get actual data. -def get_incoming_message_ids(import_dir: Path, sort_by_date: bool) -> List[int]: +def get_incoming_message_ids(import_dir: Path, sort_by_date: bool) -> list[int]: """ This function reads in our entire collection of message ids, which can be millions of integers for some installations. @@ -1634,9 +1634,9 @@ def get_incoming_message_ids(import_dir: Path, sort_by_date: bool) -> List[int]: """ if sort_by_date: - tups: List[Tuple[int, int]] = [] + tups: list[tuple[int, int]] = [] else: - message_ids: List[int] = [] + message_ids: list[int] = [] dump_file_id = 1 while True: @@ -1679,7 +1679,7 @@ def get_incoming_message_ids(import_dir: Path, sort_by_date: bool) -> List[int]: return message_ids -def import_message_data(realm: Realm, sender_map: Dict[int, Record], import_dir: Path) -> None: +def import_message_data(realm: Realm, sender_map: dict[int, Record], import_dir: Path) -> None: dump_file_id = 1 while True: message_filename = os.path.join(import_dir, f"messages-{dump_file_id:06}.json") @@ -1763,7 +1763,7 @@ def import_attachments(data: TableData) -> None: def format_m2m_data( child_singular: str, child_plural: str, m2m_table_name: str, child_id: str - ) -> Tuple[str, List[Record], str]: + ) -> tuple[str, list[Record], str]: m2m_rows = [ { parent_singular: parent_row["id"], @@ -1828,7 +1828,7 @@ def import_attachments(data: TableData) -> None: logging.info("Successfully imported M2M table %s", m2m_table_name) -def import_analytics_data(realm: Realm, import_dir: Path, crossrealm_user_ids: Set[int]) -> None: +def import_analytics_data(realm: Realm, import_dir: Path, crossrealm_user_ids: set[int]) -> None: analytics_filename = os.path.join(import_dir, "analytics.json") if not os.path.exists(analytics_filename): return @@ -1861,8 +1861,8 @@ def import_analytics_data(realm: Realm, import_dir: Path, crossrealm_user_ids: S def add_users_to_system_user_groups( realm: Realm, - user_profiles: List[UserProfile], - role_system_groups_dict: Dict[int, NamedUserGroup], + user_profiles: list[UserProfile], + role_system_groups_dict: dict[int, NamedUserGroup], ) -> None: full_members_system_group = NamedUserGroup.objects.get( name=SystemGroups.FULL_MEMBERS, diff --git a/zerver/lib/integrations.py b/zerver/lib/integrations.py index 2b4d394e3c..e06bda685c 100644 --- a/zerver/lib/integrations.py +++ b/zerver/lib/integrations.py @@ -1,6 +1,6 @@ import os from dataclasses import dataclass, field -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple +from typing import Any, Callable, Optional, Sequence from django.contrib.staticfiles.storage import staticfiles_storage from django.urls import URLResolver, path @@ -34,12 +34,12 @@ features for writing and configuring integrations efficiently. OptionValidator: TypeAlias = Callable[[str, str], Optional[str]] -META_CATEGORY: Dict[str, StrPromise] = { +META_CATEGORY: dict[str, StrPromise] = { "meta-integration": gettext_lazy("Integration frameworks"), "bots": gettext_lazy("Interactive bots"), } -CATEGORIES: Dict[str, StrPromise] = { +CATEGORIES: dict[str, StrPromise] = { **META_CATEGORY, "continuous-integration": gettext_lazy("Continuous integration"), "customer-support": gettext_lazy("Customer support"), @@ -66,14 +66,14 @@ class Integration: self, name: str, client_name: str, - categories: List[str], + categories: list[str], logo: Optional[str] = None, secondary_line_text: Optional[str] = None, display_name: Optional[str] = None, doc: Optional[str] = None, stream_name: Optional[str] = None, legacy: bool = False, - config_options: Sequence[Tuple[str, str, OptionValidator]] = [], + config_options: Sequence[tuple[str, str, OptionValidator]] = [], ) -> None: self.name = name self.client_name = client_name @@ -145,7 +145,7 @@ class BotIntegration(Integration): def __init__( self, name: str, - categories: List[str], + categories: list[str], logo: Optional[str] = None, secondary_line_text: Optional[str] = None, display_name: Optional[str] = None, @@ -186,7 +186,7 @@ class WebhookIntegration(Integration): def __init__( self, name: str, - categories: List[str], + categories: list[str], client_name: Optional[str] = None, logo: Optional[str] = None, secondary_line_text: Optional[str] = None, @@ -196,7 +196,7 @@ class WebhookIntegration(Integration): doc: Optional[str] = None, stream_name: Optional[str] = None, legacy: bool = False, - config_options: Sequence[Tuple[str, str, OptionValidator]] = [], + config_options: Sequence[tuple[str, str, OptionValidator]] = [], dir_name: Optional[str] = None, ) -> None: if client_name is None: @@ -242,7 +242,7 @@ class WebhookIntegration(Integration): return path(self.url, self.function) -def split_fixture_path(path: str) -> Tuple[str, str]: +def split_fixture_path(path: str) -> tuple[str, str]: path, fixture_name = os.path.split(path) fixture_name, _ = os.path.splitext(fixture_name) integration_name = os.path.split(os.path.dirname(path))[-1] @@ -261,14 +261,14 @@ class BaseScreenshotConfig: class ScreenshotConfig(BaseScreenshotConfig): payload_as_query_param: bool = False payload_param_name: str = "payload" - extra_params: Dict[str, str] = field(default_factory=dict) + extra_params: dict[str, str] = field(default_factory=dict) use_basic_auth: bool = False - custom_headers: Dict[str, str] = field(default_factory=dict) + custom_headers: dict[str, str] = field(default_factory=dict) def get_fixture_and_image_paths( integration: Integration, screenshot_config: BaseScreenshotConfig -) -> Tuple[str, str]: +) -> tuple[str, str]: if isinstance(integration, WebhookIntegration): fixture_dir = os.path.join("zerver", "webhooks", integration.dir_name, "fixtures") else: @@ -286,7 +286,7 @@ class HubotIntegration(Integration): def __init__( self, name: str, - categories: List[str], + categories: list[str], display_name: Optional[str] = None, logo: Optional[str] = None, logo_alt: Optional[str] = None, @@ -326,7 +326,7 @@ class EmbeddedBotIntegration(Integration): super().__init__(name, client_name, *args, **kwargs) -EMBEDDED_BOTS: List[EmbeddedBotIntegration] = [ +EMBEDDED_BOTS: list[EmbeddedBotIntegration] = [ EmbeddedBotIntegration("converter", []), EmbeddedBotIntegration("encrypt", []), EmbeddedBotIntegration("helloworld", []), @@ -335,7 +335,7 @@ EMBEDDED_BOTS: List[EmbeddedBotIntegration] = [ EmbeddedBotIntegration("followup", []), ] -WEBHOOK_INTEGRATIONS: List[WebhookIntegration] = [ +WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [ WebhookIntegration("airbrake", ["monitoring"]), WebhookIntegration( "alertmanager", @@ -493,7 +493,7 @@ WEBHOOK_INTEGRATIONS: List[WebhookIntegration] = [ WebhookIntegration("zabbix", ["monitoring"], display_name="Zabbix"), ] -INTEGRATIONS: Dict[str, Integration] = { +INTEGRATIONS: dict[str, Integration] = { "asana": Integration( "asana", "asana", ["project-management"], doc="zerver/integrations/asana.md" ), @@ -642,14 +642,14 @@ INTEGRATIONS: Dict[str, Integration] = { ), } -BOT_INTEGRATIONS: List[BotIntegration] = [ +BOT_INTEGRATIONS: list[BotIntegration] = [ BotIntegration("github_detail", ["version-control", "bots"], display_name="GitHub Detail"), BotIntegration( "xkcd", ["bots", "misc"], display_name="xkcd", logo="images/integrations/logos/xkcd.png" ), ] -HUBOT_INTEGRATIONS: List[HubotIntegration] = [ +HUBOT_INTEGRATIONS: list[HubotIntegration] = [ HubotIntegration( "assembla", ["version-control", "project-management"], @@ -700,7 +700,7 @@ NO_SCREENSHOT_WEBHOOKS = { } -DOC_SCREENSHOT_CONFIG: Dict[str, List[BaseScreenshotConfig]] = { +DOC_SCREENSHOT_CONFIG: dict[str, list[BaseScreenshotConfig]] = { "airbrake": [ScreenshotConfig("error_message.json")], "alertmanager": [ ScreenshotConfig("alert.json", extra_params={"name": "topic", "desc": "description"}) @@ -847,7 +847,7 @@ DOC_SCREENSHOT_CONFIG: Dict[str, List[BaseScreenshotConfig]] = { } -def get_all_event_types_for_integration(integration: Integration) -> Optional[List[str]]: +def get_all_event_types_for_integration(integration: Integration) -> Optional[list[str]]: integration = INTEGRATIONS[integration.name] if isinstance(integration, WebhookIntegration): if integration.name == "githubsponsors": diff --git a/zerver/lib/invites.py b/zerver/lib/invites.py index a2e03ec46d..95a9ff0e30 100644 --- a/zerver/lib/invites.py +++ b/zerver/lib/invites.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Optional from django.db.models import Q from django.utils.timezone import now as timezone_now @@ -22,7 +22,7 @@ def notify_invites_changed( def get_valid_invite_confirmations_generated_by_user( user_profile: UserProfile, -) -> List[Confirmation]: +) -> list[Confirmation]: prereg_user_ids = filter_to_valid_prereg_users( PreregistrationUser.objects.filter(referred_by=user_profile) ).values_list("id", flat=True) diff --git a/zerver/lib/logging_util.py b/zerver/lib/logging_util.py index d52eea5c26..c839d16d24 100644 --- a/zerver/lib/logging_util.py +++ b/zerver/lib/logging_util.py @@ -6,7 +6,7 @@ import traceback from contextlib import suppress from datetime import datetime, timedelta, timezone from logging import Logger -from typing import Optional, Tuple, Union +from typing import Optional, Union import orjson from django.conf import settings @@ -39,7 +39,7 @@ class _RateLimitFilter: handling_exception = threading.local() should_reset_handling_exception = False - def can_use_remote_cache(self) -> Tuple[bool, bool]: + def can_use_remote_cache(self) -> tuple[bool, bool]: if getattr(self.handling_exception, "value", False): # If we're processing an exception that occurred # while handling an exception, this almost diff --git a/zerver/lib/management.py b/zerver/lib/management.py index a818989b52..738ff37833 100644 --- a/zerver/lib/management.py +++ b/zerver/lib/management.py @@ -5,7 +5,7 @@ import sys from argparse import ArgumentParser, BooleanOptionalAction, RawTextHelpFormatter, _ActionsContainer from dataclasses import dataclass from functools import reduce, wraps -from typing import Any, Dict, Optional, Protocol +from typing import Any, Optional, Protocol from django.conf import settings from django.core import validators @@ -140,7 +140,7 @@ server via `ps -ef` or reading bash history. Prefer parser.add_argument("-a", "--all-users", action="store_true", help=all_users_help) - def get_realm(self, options: Dict[str, Any]) -> Optional[Realm]: + def get_realm(self, options: dict[str, Any]) -> Optional[Realm]: val = options["realm_id"] if val is None: return None @@ -159,7 +159,7 @@ server via `ps -ef` or reading bash history. Prefer def get_users( self, - options: Dict[str, Any], + options: dict[str, Any], realm: Optional[Realm], is_bot: Optional[bool] = None, include_deactivated: bool = False, @@ -237,7 +237,7 @@ server via `ps -ef` or reading bash history. Prefer """Returns a Zulip Client object to be used for things done in management commands""" return get_client("ZulipServer") - def get_create_user_params(self, options: Dict[str, Any]) -> CreateUserParameters: # nocoverage + def get_create_user_params(self, options: dict[str, Any]) -> CreateUserParameters: # nocoverage """ Parses parameters for user creation defined in add_create_user_args. """ diff --git a/zerver/lib/markdown/__init__.py b/zerver/lib/markdown/__init__.py index 4291155d0d..20592c2b84 100644 --- a/zerver/lib/markdown/__init__.py +++ b/zerver/lib/markdown/__init__.py @@ -10,22 +10,7 @@ from collections import deque from dataclasses import dataclass from datetime import datetime, timezone from functools import lru_cache -from typing import ( - Any, - Callable, - Dict, - Generic, - List, - Match, - Optional, - Pattern, - Set, - Tuple, - TypedDict, - TypeVar, - Union, - cast, -) +from typing import Any, Callable, Generic, Match, Optional, Pattern, TypedDict, TypeVar, Union, cast from urllib.parse import parse_qs, quote, urlencode, urljoin, urlsplit, urlunsplit from xml.etree.ElementTree import Element, SubElement @@ -121,12 +106,12 @@ class MessageRenderingResult: rendered_content: str mentions_topic_wildcard: bool mentions_stream_wildcard: bool - mentions_user_ids: Set[int] - mentions_user_group_ids: Set[int] - alert_words: Set[str] - links_for_preview: Set[str] - user_ids_with_alert_words: Set[int] - potential_attachment_path_ids: List[str] + mentions_user_ids: set[int] + mentions_user_group_ids: set[int] + alert_words: set[str] + links_for_preview: set[str] + user_ids_with_alert_words: set[int] + potential_attachment_path_ids: list[str] @dataclass @@ -134,9 +119,9 @@ class DbData: mention_data: MentionData realm_url: str realm_alert_words_automaton: Optional[ahocorasick.Automaton] - active_realm_emoji: Dict[str, EmojiInfo] + active_realm_emoji: dict[str, EmojiInfo] sent_by_bot: bool - stream_names: Dict[str, int] + stream_names: dict[str, int] translate_emoticons: bool @@ -323,7 +308,7 @@ def image_preview_enabled( return realm.inline_image_preview -def list_of_tlds() -> List[str]: +def list_of_tlds() -> list[str]: # Skip a few overly-common false-positives from file extensions common_false_positives = {"java", "md", "mov", "py", "zip"} return sorted(tld_set - common_false_positives, key=len, reverse=True) @@ -331,7 +316,7 @@ def list_of_tlds() -> List[str]: def walk_tree( root: Element, processor: Callable[[Element], Optional[_T]], stop_after_first: bool = False -) -> List[_T]: +) -> list[_T]: results = [] queue = deque([root]) @@ -381,7 +366,7 @@ class ElementPair: def walk_tree_with_family( root: Element, processor: Callable[[Element], Optional[_T]], -) -> List[ResultWithFamily[_T]]: +) -> list[ResultWithFamily[_T]]: results = [] queue = deque([ElementPair(parent=None, value=root)]) @@ -423,7 +408,7 @@ def has_blockquote_ancestor(element_pair: Optional[ElementPair]) -> bool: @cache_with_key(lambda tweet_id: tweet_id, cache_name="database") -def fetch_tweet_data(tweet_id: str) -> Optional[Dict[str, Any]]: +def fetch_tweet_data(tweet_id: str) -> Optional[dict[str, Any]]: # Twitter removed support for the v1 API that this integration # used. Given that, there's no point wasting time trying to make # network requests to Twitter. But we leave this function, because @@ -438,8 +423,8 @@ class OpenGraphSession(OutgoingSession): super().__init__(role="markdown", timeout=1) -def fetch_open_graph_image(url: str) -> Optional[Dict[str, Any]]: - og: Dict[str, Optional[str]] = {"image": None, "title": None, "desc": None} +def fetch_open_graph_image(url: str) -> Optional[dict[str, Any]]: + og: dict[str, Optional[str]] = {"image": None, "title": None, "desc": None} try: with OpenGraphSession().get( @@ -570,7 +555,7 @@ class BacktickInlineProcessor(markdown.inlinepatterns.BacktickInlineProcessor): @override def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197 self, m: Match[str], data: str - ) -> Tuple[Union[Element, str, None], Optional[int], Optional[int]]: + ) -> tuple[Union[Element, str, None], Optional[int], Optional[int]]: # Let upstream's implementation do its job as it is, we'll # just replace the text to not strip the group because it # makes it impossible to put leading/trailing whitespace in @@ -757,7 +742,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): return "https://linx.li/s" + parsed_url.path return None - def dropbox_image(self, url: str) -> Optional[Dict[str, Any]]: + def dropbox_image(self, url: str) -> Optional[dict[str, Any]]: # TODO: The returned Dict could possibly be a TypedDict in future. parsed_url = urlsplit(url) if parsed_url.netloc == "dropbox.com" or parsed_url.netloc.endswith(".dropbox.com"): @@ -864,9 +849,9 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): def twitter_text( self, text: str, - urls: List[Dict[str, str]], - user_mentions: List[Dict[str, Any]], - media: List[Dict[str, Any]], + urls: list[dict[str, str]], + user_mentions: list[dict[str, Any]], + media: list[dict[str, Any]], ) -> Element: """ Use data from the Twitter API to turn links, mentions and media into A @@ -889,7 +874,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): Finally we add any remaining text to the last node. """ - to_process: List[Dict[str, Any]] = [] + to_process: list[dict[str, Any]] = [] # Build dicts for URLs for url_data in urls: to_process.extend( @@ -990,7 +975,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): res = fetch_tweet_data(tweet_id) if res is None: return None - user: Dict[str, Any] = res["user"] + user: dict[str, Any] = res["user"] tweet = Element("div") tweet.set("class", "twitter-tweet") img_a = SubElement(tweet, "a") @@ -1008,7 +993,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): text = html.unescape(res["full_text"]) urls = res.get("urls", []) user_mentions = res.get("user_mentions", []) - media: List[Dict[str, Any]] = res.get("media", []) + media: list[dict[str, Any]] = res.get("media", []) p = self.twitter_text(text, urls, user_mentions, media) tweet.append(p) @@ -1048,7 +1033,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): markdown_logger.warning("Error building Twitter link", exc_info=True) return None - def get_url_data(self, e: Element) -> Optional[Tuple[str, Optional[str]]]: + def get_url_data(self, e: Element) -> Optional[tuple[str, Optional[str]]]: if e.tag == "a": url = e.get("href") assert url is not None @@ -1058,7 +1043,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): def get_inlining_information( self, root: Element, - found_url: ResultWithFamily[Tuple[str, Optional[str]]], + found_url: ResultWithFamily[tuple[str, Optional[str]]], ) -> LinkInfo: grandparent = found_url.family.grandparent parent = found_url.family.parent @@ -1107,7 +1092,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): def handle_image_inlining( self, root: Element, - found_url: ResultWithFamily[Tuple[str, Optional[str]]], + found_url: ResultWithFamily[tuple[str, Optional[str]]], ) -> None: info = self.get_inlining_information(root, found_url) (url, text) = found_url.result @@ -1125,7 +1110,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): def handle_tweet_inlining( self, root: Element, - found_url: ResultWithFamily[Tuple[str, Optional[str]]], + found_url: ResultWithFamily[tuple[str, Optional[str]]], twitter_data: Element, ) -> None: info = self.get_inlining_information(root, found_url) @@ -1142,7 +1127,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): def handle_youtube_url_inlining( self, root: Element, - found_url: ResultWithFamily[Tuple[str, Optional[str]]], + found_url: ResultWithFamily[tuple[str, Optional[str]]], yt_image: str, ) -> None: info = self.get_inlining_information(root, found_url) @@ -1228,7 +1213,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): video.set("preload", "metadata") def handle_video_inlining( - self, root: Element, found_url: ResultWithFamily[Tuple[str, Optional[str]]] + self, root: Element, found_url: ResultWithFamily[tuple[str, Optional[str]]] ) -> None: info = self.get_inlining_information(root, found_url) url = found_url.result[0] @@ -1280,7 +1265,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): if len(unique_previewable_urls) > self.INLINE_PREVIEW_LIMIT_PER_MESSAGE: return - processed_urls: Set[str] = set() + processed_urls: set[str] = set() rendered_tweet_count = 0 for found_url in found_urls: @@ -1506,7 +1491,7 @@ class UnicodeEmoji(CompiledInlineProcessor): @override def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197 self, match: Match[str], data: str - ) -> Tuple[Union[Element, str, None], Optional[int], Optional[int]]: + ) -> tuple[Union[Element, str, None], Optional[int], Optional[int]]: orig_syntax = match.group("syntax") # We want to avoid turning things like arrows (↔) and keycaps (numbers @@ -1534,7 +1519,7 @@ class Emoji(markdown.inlinepatterns.Pattern): orig_syntax = match.group("syntax") name = orig_syntax[1:-1] - active_realm_emoji: Dict[str, EmojiInfo] = {} + active_realm_emoji: dict[str, EmojiInfo] = {} db_data: Optional[DbData] = self.zmd.zulip_db_data if db_data is not None: active_realm_emoji = db_data.active_realm_emoji @@ -1709,7 +1694,7 @@ class BlockQuoteProcessor(markdown.blockprocessors.BlockQuoteProcessor): # run() is very slightly forked from the base class; see notes below. @override - def run(self, parent: Element, blocks: List[str]) -> None: + def run(self, parent: Element, blocks: list[str]) -> None: block = blocks.pop(0) m = self.RE.search(block) if m: @@ -1762,11 +1747,11 @@ class MarkdownListPreprocessor(markdown.preprocessors.Preprocessor): LI_RE = re.compile(r"^[ ]*([*+-]|\d\.)[ ]+(.*)", re.MULTILINE) @override - def run(self, lines: List[str]) -> List[str]: + def run(self, lines: list[str]) -> list[str]: """Insert a newline between a paragraph and ulist if missing""" inserts = 0 in_code_fence: bool = False - open_fences: List[Fence] = [] + open_fences: list[Fence] = [] copy = lines[:] for i in range(len(lines) - 1): # Ignore anything that is inside a fenced code block but not quoted. @@ -1854,7 +1839,7 @@ class LinkifierPattern(CompiledInlineProcessor): @override def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197 self, m: Match[str], data: str - ) -> Tuple[Union[Element, str, None], Optional[int], Optional[int]]: + ) -> tuple[Union[Element, str, None], Optional[int], Optional[int]]: db_data: Optional[DbData] = self.zmd.zulip_db_data url = url_to_a( db_data, @@ -1875,7 +1860,7 @@ class UserMentionPattern(CompiledInlineProcessor): @override def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197 self, m: Match[str], data: str - ) -> Tuple[Union[Element, str, None], Optional[int], Optional[int]]: + ) -> tuple[Union[Element, str, None], Optional[int], Optional[int]]: name = m.group("match") silent = m.group("silent") == "_" db_data: Optional[DbData] = self.zmd.zulip_db_data @@ -1941,7 +1926,7 @@ class UserGroupMentionPattern(CompiledInlineProcessor): @override def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197 self, m: Match[str], data: str - ) -> Tuple[Union[Element, str, None], Optional[int], Optional[int]]: + ) -> tuple[Union[Element, str, None], Optional[int], Optional[int]]: name = m.group("match") silent = m.group("silent") == "_" db_data: Optional[DbData] = self.zmd.zulip_db_data @@ -1982,7 +1967,7 @@ class StreamPattern(CompiledInlineProcessor): @override def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197 self, m: Match[str], data: str - ) -> Tuple[Union[Element, str, None], Optional[int], Optional[int]]: + ) -> tuple[Union[Element, str, None], Optional[int], Optional[int]]: name = m.group("stream_name") stream_id = self.find_stream_id(name) @@ -2014,7 +1999,7 @@ class StreamTopicPattern(CompiledInlineProcessor): @override def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197 self, m: Match[str], data: str - ) -> Tuple[Union[Element, str, None], Optional[int], Optional[int]]: + ) -> tuple[Union[Element, str, None], Optional[int], Optional[int]]: stream_name = m.group("stream_name") topic_name = m.group("topic_name") @@ -2033,7 +2018,7 @@ class StreamTopicPattern(CompiledInlineProcessor): return el, m.start(), m.end() -def possible_linked_stream_names(content: str) -> Set[str]: +def possible_linked_stream_names(content: str) -> set[str]: return { *re.findall(STREAM_LINK_REGEX, content, re.VERBOSE), *( @@ -2077,7 +2062,7 @@ class AlertWordNotificationProcessor(markdown.preprocessors.Preprocessor): return False @override - def run(self, lines: List[str]) -> List[str]: + def run(self, lines: list[str]) -> list[str]: db_data: Optional[DbData] = self.zmd.zulip_db_data if db_data is not None: # We check for alert words here, the set of which are @@ -2135,7 +2120,7 @@ class LinkInlineProcessor(markdown.inlinepatterns.LinkInlineProcessor): @override def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197 self, m: Match[str], data: str - ) -> Tuple[Union[Element, str, None], Optional[int], Optional[int]]: + ) -> tuple[Union[Element, str, None], Optional[int], Optional[int]]: ret = super().handleMatch(m, data) if ret[0] is not None: el, match_start, index = ret @@ -2146,7 +2131,7 @@ class LinkInlineProcessor(markdown.inlinepatterns.LinkInlineProcessor): return None, None, None -def get_sub_registry(r: markdown.util.Registry[T], keys: List[str]) -> markdown.util.Registry[T]: +def get_sub_registry(r: markdown.util.Registry[T], keys: list[str]) -> markdown.util.Registry[T]: # Registry is a new class added by Python-Markdown to replace OrderedDict. # Since Registry doesn't support .keys(), it is easier to make a new # object instead of removing keys from the existing object. @@ -2169,11 +2154,11 @@ class ZulipMarkdown(markdown.Markdown): zulip_rendering_result: MessageRenderingResult image_preview_enabled: bool url_embed_preview_enabled: bool - url_embed_data: Optional[Dict[str, Optional[UrlEmbedData]]] + url_embed_data: Optional[dict[str, Optional[UrlEmbedData]]] def __init__( self, - linkifiers: List[LinkifierDict], + linkifiers: list[LinkifierDict], linkifiers_key: int, email_gateway: bool, ) -> None: @@ -2385,8 +2370,8 @@ class ZulipMarkdown(markdown.Markdown): ) -md_engines: Dict[Tuple[int, bool], ZulipMarkdown] = {} -linkifier_data: Dict[int, List[LinkifierDict]] = {} +md_engines: dict[tuple[int, bool], ZulipMarkdown] = {} +linkifier_data: dict[int, list[LinkifierDict]] = {} def make_md_engine(linkifiers_key: int, email_gateway: bool) -> None: @@ -2434,8 +2419,8 @@ class TopicLinkMatch: # function on the URLs; they are expected to be HTML-escaped when # rendered by clients (just as links rendered into message bodies # are validated and escaped inside `url_to_a`). -def topic_links(linkifiers_key: int, topic_name: str) -> List[Dict[str, str]]: - matches: List[TopicLinkMatch] = [] +def topic_links(linkifiers_key: int, topic_name: str) -> list[dict[str, str]]: + matches: list[TopicLinkMatch] = [] linkifiers = linkifiers_for_realm(linkifiers_key) precedence = 0 @@ -2522,7 +2507,7 @@ def topic_links(linkifiers_key: int, topic_name: str) -> List[Dict[str, str]]: # The following removes overlapping intervals depending on the precedence of linkifier patterns. # This uses the same algorithm implemented in web/src/markdown.js. # To avoid mutating matches inside the loop, the final output gets appended to another list. - applied_matches: List[TopicLinkMatch] = [] + applied_matches: list[TopicLinkMatch] = [] for current_match in matches: # When the current match does not overlap with all existing matches, # we are confident that the link should present in the final output because @@ -2576,7 +2561,7 @@ def do_convert( message_realm: Optional[Realm] = None, sent_by_bot: bool = False, translate_emoticons: bool = False, - url_embed_data: Optional[Dict[str, Optional[UrlEmbedData]]] = None, + url_embed_data: Optional[dict[str, Optional[UrlEmbedData]]] = None, mention_data: Optional[MentionData] = None, email_gateway: bool = False, no_previews: bool = False, @@ -2737,7 +2722,7 @@ def markdown_convert( message_realm: Optional[Realm] = None, sent_by_bot: bool = False, translate_emoticons: bool = False, - url_embed_data: Optional[Dict[str, Optional[UrlEmbedData]]] = None, + url_embed_data: Optional[dict[str, Optional[UrlEmbedData]]] = None, mention_data: Optional[MentionData] = None, email_gateway: bool = False, no_previews: bool = False, @@ -2764,7 +2749,7 @@ def render_message_markdown( content: str, realm: Optional[Realm] = None, realm_alert_words_automaton: Optional[ahocorasick.Automaton] = None, - url_embed_data: Optional[Dict[str, Optional[UrlEmbedData]]] = None, + url_embed_data: Optional[dict[str, Optional[UrlEmbedData]]] = None, mention_data: Optional[MentionData] = None, email_gateway: bool = False, ) -> MessageRenderingResult: diff --git a/zerver/lib/markdown/api_arguments_table_generator.py b/zerver/lib/markdown/api_arguments_table_generator.py index b43b97cf35..5ec984006a 100644 --- a/zerver/lib/markdown/api_arguments_table_generator.py +++ b/zerver/lib/markdown/api_arguments_table_generator.py @@ -1,6 +1,6 @@ import json import re -from typing import Any, Dict, List, Mapping, Sequence +from typing import Any, Mapping, Sequence import markdown from django.utils.html import escape as escape_html @@ -83,7 +83,7 @@ class APIArgumentsTablePreprocessor(Preprocessor): super().__init__(md) @override - def run(self, lines: List[str]) -> List[str]: + def run(self, lines: list[str]) -> list[str]: done = False while not done: for line in lines: @@ -118,7 +118,7 @@ class APIArgumentsTablePreprocessor(Preprocessor): done = True return lines - def render_oneof_block(self, object_schema: Dict[str, Any], name: str) -> str: + def render_oneof_block(self, object_schema: dict[str, Any], name: str) -> str: md_engine = markdown.Markdown(extensions=[]) content = "" for element in object_schema["oneOf"]: @@ -136,7 +136,7 @@ class APIArgumentsTablePreprocessor(Preprocessor): ) return ONEOF_DETAILS_TEMPLATE.format(values=content) - def render_parameters(self, parameters: Sequence[Parameter]) -> List[str]: + def render_parameters(self, parameters: Sequence[Parameter]) -> list[str]: lines = [] md_engine = markdown.Markdown(extensions=[]) @@ -225,7 +225,7 @@ class APIArgumentsTablePreprocessor(Preprocessor): description = object_values[value]["description"] # check for default, enum, required or example in documentation - additions: List[str] = [] + additions: list[str] = [] default = object_values.get(value, {}).get("default") if default is not None: diff --git a/zerver/lib/markdown/api_return_values_table_generator.py b/zerver/lib/markdown/api_return_values_table_generator.py index 97a6a91974..c8205317c1 100644 --- a/zerver/lib/markdown/api_return_values_table_generator.py +++ b/zerver/lib/markdown/api_return_values_table_generator.py @@ -3,7 +3,7 @@ import json import re from collections import OrderedDict from dataclasses import dataclass -from typing import Any, Dict, List, Mapping, Optional +from typing import Any, Mapping, Optional import markdown from markdown.extensions import Extension @@ -50,7 +50,7 @@ TABLE_LINK_TEMPLATE = """ class EventData: type: str description: str - properties: Dict[str, Any] + properties: dict[str, Any] example: str op_type: Optional[str] = None @@ -70,7 +70,7 @@ class APIReturnValuesTablePreprocessor(Preprocessor): super().__init__(md) @override - def run(self, lines: List[str]) -> List[str]: + def run(self, lines: list[str]) -> list[str]: done = False while not done: for line in lines: @@ -147,7 +147,7 @@ class APIReturnValuesTablePreprocessor(Preprocessor): + description ) - def render_oneof_block(self, object_schema: Dict[str, Any], spacing: int) -> List[str]: + def render_oneof_block(self, object_schema: dict[str, Any], spacing: int) -> list[str]: ans = [] block_spacing = spacing for element in object_schema["oneOf"]: @@ -180,7 +180,7 @@ class APIReturnValuesTablePreprocessor(Preprocessor): ) return ans - def render_table(self, return_values: Dict[str, Any], spacing: int) -> List[str]: + def render_table(self, return_values: dict[str, Any], spacing: int) -> list[str]: IGNORE = ["result", "msg", "ignored_parameters_unsupported"] ans = [] for return_value in return_values: @@ -259,8 +259,8 @@ class APIReturnValuesTablePreprocessor(Preprocessor): ) return ans - def generate_event_strings(self, event_data: EventData) -> List[str]: - event_strings: List[str] = [] + def generate_event_strings(self, event_data: EventData) -> list[str]: + event_strings: list[str] = [] if event_data.op_type is None: event_strings.append( EVENT_HEADER_TEMPLATE.format(id=event_data.type, event=event_data.type) @@ -282,8 +282,8 @@ class APIReturnValuesTablePreprocessor(Preprocessor): event_strings.append("
") return event_strings - def generate_events_table(self, events_by_type: OrderedDict[str, List[str]]) -> List[str]: - event_links: List[str] = [] + def generate_events_table(self, events_by_type: OrderedDict[str, list[str]]) -> list[str]: + event_links: list[str] = [] for event_type, event_ops in events_by_type.items(): if not event_ops: event_links.append(TABLE_LINK_TEMPLATE.format(link_name=event_type, url=event_type)) @@ -298,13 +298,13 @@ class APIReturnValuesTablePreprocessor(Preprocessor): ) return [EVENTS_TABLE_TEMPLATE.format(events_list="\n".join(event_links))] - def render_events(self, events_dict: Dict[str, Any]) -> List[str]: - events: List[str] = [] - events_for_table: OrderedDict[str, List[str]] = OrderedDict() + def render_events(self, events_dict: dict[str, Any]) -> list[str]: + events: list[str] = [] + events_for_table: OrderedDict[str, list[str]] = OrderedDict() for event in events_dict["oneOf"]: # The op property doesn't have a description, so it must be removed # before any calls to self.render_table, which expects a description. - op: Optional[Dict[str, Any]] = event["properties"].pop("op", None) + op: Optional[dict[str, Any]] = event["properties"].pop("op", None) op_type: Optional[str] = None if op is not None: op_type = op["enum"][0] diff --git a/zerver/lib/markdown/fenced_code.py b/zerver/lib/markdown/fenced_code.py index 5859c62c55..1802185a97 100644 --- a/zerver/lib/markdown/fenced_code.py +++ b/zerver/lib/markdown/fenced_code.py @@ -77,7 +77,7 @@ Dependencies: """ import re -from typing import Any, Callable, Dict, Iterable, List, Mapping, MutableSequence, Optional, Sequence +from typing import Any, Callable, Iterable, Mapping, MutableSequence, Optional, Sequence import lxml.html from django.utils.html import escape @@ -128,7 +128,7 @@ CODE_WRAP = "
{}\n
" LANG_TAG = ' class="{}"' -def validate_curl_content(lines: List[str]) -> None: +def validate_curl_content(lines: list[str]) -> None: error_msg = """ Missing required -X argument in curl command: @@ -141,7 +141,7 @@ Missing required -X argument in curl command: raise MarkdownRenderingError(error_msg.format(command=line.strip())) -CODE_VALIDATORS: Dict[Optional[str], Callable[[List[str]], None]] = { +CODE_VALIDATORS: dict[Optional[str], Callable[[list[str]], None]] = { "curl": validate_curl_content, } @@ -182,7 +182,7 @@ class ZulipBaseHandler: self.output = output self.fence = fence self.process_contents = process_contents - self.lines: List[str] = [] + self.lines: list[str] = [] def handle_line(self, line: str) -> None: if line.rstrip() == self.fence: @@ -423,15 +423,15 @@ class FencedBlockPreprocessor(Preprocessor): self.handlers.pop() @override - def run(self, lines: Iterable[str]) -> List[str]: + def run(self, lines: Iterable[str]) -> list[str]: """Match and store Fenced Code Blocks in the HtmlStash.""" from zerver.lib.markdown import ZulipMarkdown - output: List[str] = [] + output: list[str] = [] processor = self - self.handlers: List[ZulipBaseHandler] = [] + self.handlers: list[ZulipBaseHandler] = [] default_language = None if isinstance(self.md, ZulipMarkdown) and self.md.zulip_realm is not None: diff --git a/zerver/lib/markdown/help_emoticon_translations_table.py b/zerver/lib/markdown/help_emoticon_translations_table.py index 52327ea54c..283ab1217a 100644 --- a/zerver/lib/markdown/help_emoticon_translations_table.py +++ b/zerver/lib/markdown/help_emoticon_translations_table.py @@ -1,5 +1,5 @@ import re -from typing import Any, List, Match +from typing import Any, Match from markdown import Markdown from markdown.extensions import Extension @@ -52,7 +52,7 @@ class EmoticonTranslationsHelpExtension(Extension): class EmoticonTranslation(Preprocessor): @override - def run(self, lines: List[str]) -> List[str]: + def run(self, lines: list[str]) -> list[str]: for loc, line in enumerate(lines): match = REGEXP.search(line) if match: @@ -61,7 +61,7 @@ class EmoticonTranslation(Preprocessor): break return lines - def handleMatch(self, match: Match[str]) -> List[str]: + def handleMatch(self, match: Match[str]) -> list[str]: rows = [ ROW_HTML.format( emoticon=emoticon, diff --git a/zerver/lib/markdown/help_relative_links.py b/zerver/lib/markdown/help_relative_links.py index 24334216fd..87b6842ea6 100644 --- a/zerver/lib/markdown/help_relative_links.py +++ b/zerver/lib/markdown/help_relative_links.py @@ -1,5 +1,5 @@ import re -from typing import Any, List, Match +from typing import Any, Match from markdown import Markdown from markdown.extensions import Extension @@ -216,7 +216,7 @@ def set_relative_help_links(value: bool) -> None: class RelativeLinks(Preprocessor): @override - def run(self, lines: List[str]) -> List[str]: + def run(self, lines: list[str]) -> list[str]: done = False while not done: for line in lines: diff --git a/zerver/lib/markdown/help_settings_links.py b/zerver/lib/markdown/help_settings_links.py index 41c634ed75..7bc1bc3003 100644 --- a/zerver/lib/markdown/help_settings_links.py +++ b/zerver/lib/markdown/help_settings_links.py @@ -1,5 +1,5 @@ import re -from typing import Any, List, Match +from typing import Any, Match from markdown import Markdown from markdown.extensions import Extension @@ -148,7 +148,7 @@ def set_relative_settings_links(value: bool) -> None: class Setting(Preprocessor): @override - def run(self, lines: List[str]) -> List[str]: + def run(self, lines: list[str]) -> list[str]: done = False while not done: for line in lines: diff --git a/zerver/lib/markdown/include.py b/zerver/lib/markdown/include.py index b5bf68b162..4f5a05ce1f 100644 --- a/zerver/lib/markdown/include.py +++ b/zerver/lib/markdown/include.py @@ -1,6 +1,6 @@ import os import re -from typing import List, Match +from typing import Match from xml.etree.ElementTree import Element from markdown import Extension, Markdown @@ -50,7 +50,7 @@ class IncludeBlockProcessor(BlockProcessor): return "\n".join(lines) @override - def run(self, parent: Element, blocks: List[str]) -> None: + def run(self, parent: Element, blocks: list[str]) -> None: self.parser.state.set("include") self.parser.parseChunk(parent, self.RE.sub(self.expand_include, blocks.pop(0))) self.parser.state.reset() diff --git a/zerver/lib/markdown/nested_code_blocks.py b/zerver/lib/markdown/nested_code_blocks.py index 5fa6356906..7d3f968207 100644 --- a/zerver/lib/markdown/nested_code_blocks.py +++ b/zerver/lib/markdown/nested_code_blocks.py @@ -1,4 +1,4 @@ -from typing import Any, List, Mapping, Optional, Tuple +from typing import Any, Mapping, Optional from xml.etree.ElementTree import Element, SubElement import markdown @@ -32,15 +32,15 @@ class NestedCodeBlocksRendererTreeProcessor(markdown.treeprocessors.Treeprocesso codehilite_block = self.get_codehilite_block(text) self.replace_element(block.family.grandparent, codehilite_block, block.family.parent) - def get_code_tags(self, e: Element) -> Optional[Tuple[str, Optional[str]]]: + def get_code_tags(self, e: Element) -> Optional[tuple[str, Optional[str]]]: if e.tag == "code": return (e.tag, e.text) return None def get_nested_code_blocks( self, - code_tags: List[ResultWithFamily[Tuple[str, Optional[str]]]], - ) -> List[ResultWithFamily[Tuple[str, Optional[str]]]]: + code_tags: list[ResultWithFamily[tuple[str, Optional[str]]]], + ) -> list[ResultWithFamily[tuple[str, Optional[str]]]]: nested_code_blocks = [] for code_tag in code_tags: parent: Any = code_tag.family.parent diff --git a/zerver/lib/markdown/tabbed_sections.py b/zerver/lib/markdown/tabbed_sections.py index 566c73eb96..da9ee75025 100644 --- a/zerver/lib/markdown/tabbed_sections.py +++ b/zerver/lib/markdown/tabbed_sections.py @@ -1,5 +1,5 @@ import re -from typing import Any, Dict, List, Mapping, Optional +from typing import Any, Mapping, Optional import markdown from markdown.extensions import Extension @@ -138,7 +138,7 @@ class TabbedSectionsPreprocessor(Preprocessor): super().__init__(md) @override - def run(self, lines: List[str]) -> List[str]: + def run(self, lines: list[str]) -> list[str]: tab_section = self.parse_tabs(lines) while tab_section: if "tabs" in tab_section: @@ -163,7 +163,7 @@ class TabbedSectionsPreprocessor(Preprocessor): tab_section = self.parse_tabs(lines) return lines - def generate_content_blocks(self, tab_section: Dict[str, Any], lines: List[str]) -> str: + def generate_content_blocks(self, tab_section: dict[str, Any], lines: list[str]) -> str: tab_content_blocks = [] for index, tab in enumerate(tab_section["tabs"]): start_index = tab["start"] + 1 @@ -186,7 +186,7 @@ class TabbedSectionsPreprocessor(Preprocessor): tab_content_blocks.append(tab_content_block) return "\n".join(tab_content_blocks) - def generate_nav_bar(self, tab_section: Dict[str, Any]) -> str: + def generate_nav_bar(self, tab_section: dict[str, Any]) -> str: li_elements = [] for tab in tab_section["tabs"]: tab_key = tab.get("tab_key") @@ -201,8 +201,8 @@ class TabbedSectionsPreprocessor(Preprocessor): return NAV_BAR_TEMPLATE.format(tabs="\n".join(li_elements)) - def parse_tabs(self, lines: List[str]) -> Optional[Dict[str, Any]]: - block: Dict[str, Any] = {} + def parse_tabs(self, lines: list[str]) -> Optional[dict[str, Any]]: + block: dict[str, Any] = {} for index, line in enumerate(lines): start_match = START_TABBED_SECTION_REGEX.search(line) if start_match: diff --git a/zerver/lib/mention.py b/zerver/lib/mention.py index 3ad4ce3685..5517bc7001 100644 --- a/zerver/lib/mention.py +++ b/zerver/lib/mention.py @@ -1,7 +1,7 @@ import functools import re from dataclasses import dataclass -from typing import Dict, List, Match, Optional, Set, Tuple +from typing import Match, Optional from django.conf import settings from django.db.models import Q @@ -57,7 +57,7 @@ class MentionText: @dataclass class PossibleMentions: - mention_texts: Set[str] + mention_texts: set[str] message_has_topic_wildcards: bool message_has_stream_wildcards: bool @@ -72,14 +72,14 @@ class MentionBackend: def __init__(self, realm_id: int) -> None: self.realm_id = realm_id - self.user_cache: Dict[Tuple[int, str], FullNameInfo] = {} - self.stream_cache: Dict[str, int] = {} + self.user_cache: dict[tuple[int, str], FullNameInfo] = {} + self.stream_cache: dict[str, int] = {} def get_full_name_info_list( - self, user_filters: List[UserFilter], message_sender: Optional[UserProfile] - ) -> List[FullNameInfo]: - result: List[FullNameInfo] = [] - unseen_user_filters: List[UserFilter] = [] + self, user_filters: list[UserFilter], message_sender: Optional[UserProfile] + ) -> list[FullNameInfo]: + result: list[FullNameInfo] = [] + unseen_user_filters: list[UserFilter] = [] # Try to get messages from the user_cache first. # This loop populates two lists: @@ -136,12 +136,12 @@ class MentionBackend: return result - def get_stream_name_map(self, stream_names: Set[str]) -> Dict[str, int]: + def get_stream_name_map(self, stream_names: set[str]) -> dict[str, int]: if not stream_names: return {} - result: Dict[str, int] = {} - unseen_stream_names: List[str] = [] + result: dict[str, int] = {} + unseen_stream_names: list[str] = [] for stream_name in stream_names: if stream_name in self.stream_cache: @@ -210,13 +210,13 @@ def possible_mentions(content: str) -> PossibleMentions: ) -def possible_user_group_mentions(content: str) -> Set[str]: +def possible_user_group_mentions(content: str) -> set[str]: return {m.group("match") for m in USER_GROUP_MENTIONS_RE.finditer(content)} def get_possible_mentions_info( - mention_backend: MentionBackend, mention_texts: Set[str], message_sender: Optional[UserProfile] -) -> List[FullNameInfo]: + mention_backend: MentionBackend, mention_texts: set[str], message_sender: Optional[UserProfile] +) -> list[FullNameInfo]: if not mention_texts: return [] @@ -265,8 +265,8 @@ class MentionData: return self.has_topic_wildcards def init_user_group_data(self, realm_id: int, content: str) -> None: - self.user_group_name_info: Dict[str, NamedUserGroup] = {} - self.user_group_members: Dict[int, List[int]] = {} + self.user_group_name_info: dict[str, NamedUserGroup] = {} + self.user_group_members: dict[int, list[int]] = {} user_group_names = possible_user_group_mentions(content) if user_group_names: for group in NamedUserGroup.objects.filter( @@ -284,7 +284,7 @@ class MentionData: def get_user_by_id(self, id: int) -> Optional[FullNameInfo]: return self.user_id_info.get(id, None) - def get_user_ids(self) -> Set[int]: + def get_user_ids(self) -> set[int]: """ Returns the user IDs that might have been mentioned by this content. Note that because this data structure has not parsed @@ -296,10 +296,10 @@ class MentionData: def get_user_group(self, name: str) -> Optional[NamedUserGroup]: return self.user_group_name_info.get(name.lower(), None) - def get_group_members(self, user_group_id: int) -> List[int]: + def get_group_members(self, user_group_id: int) -> list[int]: return self.user_group_members.get(user_group_id, []) - def get_stream_name_map(self, stream_names: Set[str]) -> Dict[str, int]: + def get_stream_name_map(self, stream_names: set[str]) -> dict[str, int]: return self.mention_backend.get_stream_name_map(stream_names) diff --git a/zerver/lib/message.py b/zerver/lib/message.py index 388fcacd34..168dd76e96 100644 --- a/zerver/lib/message.py +++ b/zerver/lib/message.py @@ -1,20 +1,7 @@ import re from dataclasses import dataclass, field from datetime import datetime, timedelta -from typing import ( - Any, - Callable, - Collection, - Dict, - List, - Mapping, - Optional, - Sequence, - Set, - Tuple, - TypedDict, - Union, -) +from typing import Any, Callable, Collection, Mapping, Optional, Sequence, TypedDict, Union from django.conf import settings from django.db import connection @@ -66,7 +53,7 @@ from zerver.models.users import is_cross_realm_bot_email class MessageDetailsDict(TypedDict, total=False): type: str mentioned: bool - user_ids: List[int] + user_ids: list[int] stream_id: int topic: str unmuted_stream_msg: bool @@ -86,38 +73,38 @@ class RawUnreadDirectMessageGroupDict(TypedDict): class RawUnreadMessagesResult(TypedDict): - pm_dict: Dict[int, RawUnreadDirectMessageDict] - stream_dict: Dict[int, RawUnreadStreamDict] - huddle_dict: Dict[int, RawUnreadDirectMessageGroupDict] - mentions: Set[int] - muted_stream_ids: Set[int] - unmuted_stream_msgs: Set[int] + pm_dict: dict[int, RawUnreadDirectMessageDict] + stream_dict: dict[int, RawUnreadStreamDict] + huddle_dict: dict[int, RawUnreadDirectMessageGroupDict] + mentions: set[int] + muted_stream_ids: set[int] + unmuted_stream_msgs: set[int] old_unreads_missing: bool class UnreadStreamInfo(TypedDict): stream_id: int topic: str - unread_message_ids: List[int] + unread_message_ids: list[int] class UnreadDirectMessageInfo(TypedDict): other_user_id: int # Deprecated and misleading synonym for other_user_id sender_id: int - unread_message_ids: List[int] + unread_message_ids: list[int] class UnreadDirectMessageGroupInfo(TypedDict): user_ids_string: str - unread_message_ids: List[int] + unread_message_ids: list[int] class UnreadMessagesResult(TypedDict): - pms: List[UnreadDirectMessageInfo] - streams: List[UnreadStreamInfo] - huddles: List[UnreadDirectMessageGroupInfo] - mentions: List[int] + pms: list[UnreadDirectMessageInfo] + streams: list[UnreadStreamInfo] + huddles: list[UnreadDirectMessageGroupInfo] + mentions: list[int] count: int old_unreads_missing: bool @@ -132,58 +119,58 @@ class SendMessageRequest: sender_queue_id: Optional[str] realm: Realm mention_data: MentionData - mentioned_user_groups_map: Dict[int, int] - active_user_ids: Set[int] - online_push_user_ids: Set[int] - dm_mention_push_disabled_user_ids: Set[int] - dm_mention_email_disabled_user_ids: Set[int] - stream_push_user_ids: Set[int] - stream_email_user_ids: Set[int] + mentioned_user_groups_map: dict[int, int] + active_user_ids: set[int] + online_push_user_ids: set[int] + dm_mention_push_disabled_user_ids: set[int] + dm_mention_email_disabled_user_ids: set[int] + stream_push_user_ids: set[int] + stream_email_user_ids: set[int] # IDs of users who have followed the topic the message is being sent to, # and have the followed topic push notifications setting ON. - followed_topic_push_user_ids: Set[int] + followed_topic_push_user_ids: set[int] # IDs of users who have followed the topic the message is being sent to, # and have the followed topic email notifications setting ON. - followed_topic_email_user_ids: Set[int] - muted_sender_user_ids: Set[int] - um_eligible_user_ids: Set[int] - long_term_idle_user_ids: Set[int] - default_bot_user_ids: Set[int] - service_bot_tuples: List[Tuple[int, int]] - all_bot_user_ids: Set[int] + followed_topic_email_user_ids: set[int] + muted_sender_user_ids: set[int] + um_eligible_user_ids: set[int] + long_term_idle_user_ids: set[int] + default_bot_user_ids: set[int] + service_bot_tuples: list[tuple[int, int]] + all_bot_user_ids: set[int] # IDs of topic participants who should be notified of topic wildcard mention. # The 'user_allows_notifications_in_StreamTopic' with 'wildcard_mentions_notify' # setting ON should return True. # A user_id can exist in either or both of the 'topic_wildcard_mention_user_ids' # and 'topic_wildcard_mention_in_followed_topic_user_ids' sets. - topic_wildcard_mention_user_ids: Set[int] + topic_wildcard_mention_user_ids: set[int] # IDs of users subscribed to the stream who should be notified of # stream wildcard mention. # The 'user_allows_notifications_in_StreamTopic' with 'wildcard_mentions_notify' # setting ON should return True. # A user_id can exist in either or both of the 'stream_wildcard_mention_user_ids' # and 'stream_wildcard_mention_in_followed_topic_user_ids' sets. - stream_wildcard_mention_user_ids: Set[int] + stream_wildcard_mention_user_ids: set[int] # IDs of topic participants who have followed the topic the message # (having topic wildcard) is being sent to, and have the # 'followed_topic_wildcard_mentions_notify' setting ON. - topic_wildcard_mention_in_followed_topic_user_ids: Set[int] + topic_wildcard_mention_in_followed_topic_user_ids: set[int] # IDs of users who have followed the topic the message # (having stream wildcard) is being sent to, and have the # 'followed_topic_wildcard_mentions_notify' setting ON. - stream_wildcard_mention_in_followed_topic_user_ids: Set[int] + stream_wildcard_mention_in_followed_topic_user_ids: set[int] # A topic participant is anyone who either sent or reacted to messages in the topic. - topic_participant_user_ids: Set[int] - links_for_embed: Set[str] - widget_content: Optional[Dict[str, Any]] - submessages: List[Dict[str, Any]] = field(default_factory=list) + topic_participant_user_ids: set[int] + links_for_embed: set[str] + widget_content: Optional[dict[str, Any]] + submessages: list[dict[str, Any]] = field(default_factory=list) deliver_at: Optional[datetime] = None delivery_type: Optional[str] = None - limit_unread_user_ids: Optional[Set[int]] = None - service_queue_events: Optional[Dict[str, List[Dict[str, Any]]]] = None + limit_unread_user_ids: Optional[set[int]] = None + service_queue_events: Optional[dict[str, list[dict[str, Any]]]] = None disable_external_notifications: bool = False automatic_new_visibility_policy: Optional[int] = None - recipients_for_user_creation_events: Optional[Dict[UserProfile, Set[int]]] = None + recipients_for_user_creation_events: Optional[dict[UserProfile, set[int]]] = None # We won't try to fetch more unread message IDs from the database than @@ -213,15 +200,15 @@ def truncate_topic(topic_name: str) -> str: def messages_for_ids( - message_ids: List[int], - user_message_flags: Dict[int, List[str]], - search_fields: Dict[int, Dict[str, str]], + message_ids: list[int], + user_message_flags: dict[int, list[str]], + search_fields: dict[int, dict[str, str]], apply_markdown: bool, client_gravatar: bool, allow_edit_history: bool, user_profile: Optional[UserProfile], realm: Realm, -) -> List[Dict[str, Any]]: +) -> list[dict[str, Any]]: id_fetcher = lambda row: row["id"] message_dicts = generic_bulk_cached_fetch( @@ -234,7 +221,7 @@ def messages_for_ids( setter=stringify_message_dict, ) - message_list: List[Dict[str, Any]] = [] + message_list: list[dict[str, Any]] = [] sender_ids = [message_dicts[message_id]["sender_id"] for message_id in message_ids] inaccessible_sender_ids = get_inaccessible_user_ids(sender_ids, user_profile) @@ -307,7 +294,7 @@ def access_message_and_usermessage( user_profile: UserProfile, message_id: int, lock_message: bool = False, -) -> Tuple[Message, Optional[UserMessage]]: +) -> tuple[Message, Optional[UserMessage]]: """As access_message, but also returns the usermessage, if any.""" try: base_query = Message.objects.select_related(*Message.DEFAULT_SELECT_RELATED) @@ -425,7 +412,7 @@ def bulk_access_messages( messages: Collection[Message] | QuerySet[Message], *, stream: Optional[Stream] = None, -) -> List[Message]: +) -> list[Message]: """This function does the full has_message_access check for each message. If stream is provided, it is used to avoid unnecessary database queries, and will use exactly 2 bulk queries instead. @@ -520,18 +507,18 @@ def get_messages_with_usermessage_rows_for_user( def direct_message_group_users(recipient_id: int) -> str: - display_recipient: List[UserDisplayRecipient] = get_display_recipient_by_id( + display_recipient: list[UserDisplayRecipient] = get_display_recipient_by_id( recipient_id, Recipient.DIRECT_MESSAGE_GROUP, None, ) - user_ids: List[int] = [obj["id"] for obj in display_recipient] + user_ids: list[int] = [obj["id"] for obj in display_recipient] user_ids = sorted(user_ids) return ",".join(str(uid) for uid in user_ids) -def get_inactive_recipient_ids(user_profile: UserProfile) -> List[int]: +def get_inactive_recipient_ids(user_profile: UserProfile) -> list[int]: rows = ( get_stream_subscriptions_for_user(user_profile) .filter( @@ -545,7 +532,7 @@ def get_inactive_recipient_ids(user_profile: UserProfile) -> List[int]: return inactive_recipient_ids -def get_muted_stream_ids(user_profile: UserProfile) -> Set[int]: +def get_muted_stream_ids(user_profile: UserProfile) -> set[int]: rows = ( get_stream_subscriptions_for_user(user_profile) .filter( @@ -560,7 +547,7 @@ def get_muted_stream_ids(user_profile: UserProfile) -> Set[int]: return muted_stream_ids -def get_starred_message_ids(user_profile: UserProfile) -> List[int]: +def get_starred_message_ids(user_profile: UserProfile) -> list[int]: return list( UserMessage.objects.filter( user_profile=user_profile, @@ -576,7 +563,7 @@ def get_starred_message_ids(user_profile: UserProfile) -> List[int]: def get_raw_unread_data( - user_profile: UserProfile, message_ids: Optional[List[int]] = None + user_profile: UserProfile, message_ids: Optional[list[int]] = None ) -> RawUnreadMessagesResult: excluded_recipient_ids = get_inactive_recipient_ids(user_profile) first_visible_message_id = get_first_visible_message_id(user_profile.realm) @@ -618,14 +605,14 @@ def get_raw_unread_data( def extract_unread_data_from_um_rows( - rows: List[Dict[str, Any]], user_profile: Optional[UserProfile] + rows: list[dict[str, Any]], user_profile: Optional[UserProfile] ) -> RawUnreadMessagesResult: - pm_dict: Dict[int, RawUnreadDirectMessageDict] = {} - stream_dict: Dict[int, RawUnreadStreamDict] = {} - muted_stream_ids: Set[int] = set() - unmuted_stream_msgs: Set[int] = set() - direct_message_group_dict: Dict[int, RawUnreadDirectMessageGroupDict] = {} - mentions: Set[int] = set() + pm_dict: dict[int, RawUnreadDirectMessageDict] = {} + stream_dict: dict[int, RawUnreadStreamDict] = {} + muted_stream_ids: set[int] = set() + unmuted_stream_msgs: set[int] = set() + direct_message_group_dict: dict[int, RawUnreadDirectMessageGroupDict] = {} + mentions: set[int] = set() total_unreads = 0 raw_unread_messages: RawUnreadMessagesResult = dict( @@ -668,7 +655,7 @@ def extract_unread_data_from_um_rows( return False - direct_message_group_cache: Dict[int, str] = {} + direct_message_group_cache: dict[int, str] = {} def get_direct_message_group_users(recipient_id: int) -> str: if recipient_id in direct_message_group_cache: @@ -738,8 +725,8 @@ def extract_unread_data_from_um_rows( return raw_unread_messages -def aggregate_streams(*, input_dict: Dict[int, RawUnreadStreamDict]) -> List[UnreadStreamInfo]: - lookup_dict: Dict[Tuple[int, str], UnreadStreamInfo] = {} +def aggregate_streams(*, input_dict: dict[int, RawUnreadStreamDict]) -> list[UnreadStreamInfo]: + lookup_dict: dict[tuple[int, str], UnreadStreamInfo] = {} for message_id, attribute_dict in input_dict.items(): stream_id = attribute_dict["stream_id"] topic_name = attribute_dict["topic"] @@ -764,9 +751,9 @@ def aggregate_streams(*, input_dict: Dict[int, RawUnreadStreamDict]) -> List[Unr def aggregate_pms( - *, input_dict: Dict[int, RawUnreadDirectMessageDict] -) -> List[UnreadDirectMessageInfo]: - lookup_dict: Dict[int, UnreadDirectMessageInfo] = {} + *, input_dict: dict[int, RawUnreadDirectMessageDict] +) -> list[UnreadDirectMessageInfo]: + lookup_dict: dict[int, UnreadDirectMessageInfo] = {} for message_id, attribute_dict in input_dict.items(): other_user_id = attribute_dict["other_user_id"] if other_user_id not in lookup_dict: @@ -792,9 +779,9 @@ def aggregate_pms( def aggregate_direct_message_groups( - *, input_dict: Dict[int, RawUnreadDirectMessageGroupDict] -) -> List[UnreadDirectMessageGroupInfo]: - lookup_dict: Dict[str, UnreadDirectMessageGroupInfo] = {} + *, input_dict: dict[int, RawUnreadDirectMessageGroupDict] +) -> list[UnreadDirectMessageGroupInfo]: + lookup_dict: dict[str, UnreadDirectMessageGroupInfo] = {} for message_id, attribute_dict in input_dict.items(): user_ids_string = attribute_dict["user_ids_string"] if user_ids_string not in lookup_dict: @@ -843,8 +830,8 @@ def aggregate_unread_data(raw_data: RawUnreadMessagesResult) -> UnreadMessagesRe def apply_unread_message_event( user_profile: UserProfile, state: RawUnreadMessagesResult, - message: Dict[str, Any], - flags: List[str], + message: dict[str, Any], + flags: list[str], ) -> None: message_id = message["id"] if message["type"] == "stream": @@ -919,7 +906,7 @@ def remove_message_id_from_unread_mgs(state: RawUnreadMessagesResult, message_id def format_unread_message_details( my_user_id: int, raw_unread_data: RawUnreadMessagesResult, -) -> Dict[str, MessageDetailsDict]: +) -> dict[str, MessageDetailsDict]: unread_data = {} for message_id, private_message_details in raw_unread_data["pm_dict"].items(): @@ -982,7 +969,7 @@ def add_message_to_unread_msgs( state["mentions"].add(message_id) if message_details["type"] == "private": - user_ids: List[int] = message_details["user_ids"] + user_ids: list[int] = message_details["user_ids"] user_ids = [user_id for user_id in user_ids if user_id != my_user_id] if user_ids == []: state["pm_dict"][message_id] = RawUnreadDirectMessageDict( @@ -1071,7 +1058,7 @@ def get_recent_conversations_recipient_id( return recipient_id -def get_recent_private_conversations(user_profile: UserProfile) -> Dict[int, Dict[str, Any]]: +def get_recent_private_conversations(user_profile: UserProfile) -> dict[int, dict[str, Any]]: """This function uses some carefully optimized SQL queries, designed to use the UserMessage index on private_messages. It is somewhat complicated by the fact that for 1:1 direct @@ -1216,7 +1203,7 @@ def stream_wildcard_mention_allowed(sender: UserProfile, stream: Stream, realm: return wildcard_mention_policy_authorizes_user(sender, realm) -def check_user_group_mention_allowed(sender: UserProfile, user_group_ids: List[int]) -> None: +def check_user_group_mention_allowed(sender: UserProfile, user_group_ids: list[int]) -> None: user_groups = NamedUserGroup.objects.filter(id__in=user_group_ids).select_related( "can_mention_group", "can_mention_group__named_user_group" ) diff --git a/zerver/lib/message_cache.py b/zerver/lib/message_cache.py index c9cadde085..c286e2a155 100644 --- a/zerver/lib/message_cache.py +++ b/zerver/lib/message_cache.py @@ -2,7 +2,7 @@ import copy import zlib from datetime import datetime from email.headerregistry import Address -from typing import Any, Dict, Iterable, List, Optional, TypedDict +from typing import Any, Iterable, Optional, TypedDict import orjson @@ -30,8 +30,8 @@ class RawReactionRow(TypedDict): def sew_messages_and_reactions( - messages: List[Dict[str, Any]], reactions: List[Dict[str, Any]] -) -> List[Dict[str, Any]]: + messages: list[dict[str, Any]], reactions: list[dict[str, Any]] +) -> list[dict[str, Any]]: """Given a iterable of messages and reactions stitch reactions into messages. """ @@ -49,7 +49,7 @@ def sew_messages_and_reactions( def sew_messages_and_submessages( - messages: List[Dict[str, Any]], submessages: List[Dict[str, Any]] + messages: list[dict[str, Any]], submessages: list[dict[str, Any]] ) -> None: # This is super similar to sew_messages_and_reactions. for message in messages: @@ -64,11 +64,11 @@ def sew_messages_and_submessages( message["submessages"].append(submessage) -def extract_message_dict(message_bytes: bytes) -> Dict[str, Any]: +def extract_message_dict(message_bytes: bytes) -> dict[str, Any]: return orjson.loads(zlib.decompress(message_bytes)) -def stringify_message_dict(message_dict: Dict[str, Any]) -> bytes: +def stringify_message_dict(message_dict: dict[str, Any]) -> bytes: return zlib.compress(orjson.dumps(message_dict)) @@ -79,7 +79,7 @@ def message_to_encoded_cache(message: Message, realm_id: Optional[int] = None) - def update_message_cache( changed_messages: Iterable[Message], realm_id: Optional[int] = None -) -> List[int]: +) -> list[int]: """Updates the message as stored in the to_dict cache (for serving messages).""" items_for_remote_cache = {} @@ -107,7 +107,7 @@ def save_message_rendered_content(message: Message, content: str) -> str: class ReactionDict: @staticmethod - def build_dict_from_raw_db_row(row: RawReactionRow) -> Dict[str, Any]: + def build_dict_from_raw_db_row(row: RawReactionRow) -> dict[str, Any]: return { "emoji_name": row["emoji_name"], "emoji_code": row["emoji_code"], @@ -151,7 +151,7 @@ class MessageDict: """ @staticmethod - def wide_dict(message: Message, realm_id: Optional[int] = None) -> Dict[str, Any]: + def wide_dict(message: Message, realm_id: Optional[int] = None) -> dict[str, Any]: """ The next two lines get the cacheable field related to our message object, with the side effect of @@ -173,7 +173,7 @@ class MessageDict: @staticmethod def post_process_dicts( - objs: List[Dict[str, Any]], + objs: list[dict[str, Any]], apply_markdown: bool, client_gravatar: bool, realm: Realm, @@ -202,14 +202,14 @@ class MessageDict: @staticmethod def finalize_payload( - obj: Dict[str, Any], + obj: dict[str, Any], apply_markdown: bool, client_gravatar: bool, keep_rendered_content: bool = False, skip_copy: bool = False, can_access_sender: bool = True, realm_host: str = "", - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """ By default, we make a shallow copy of the incoming dict to avoid mutation-related bugs. Code paths that are passing a unique object @@ -262,8 +262,8 @@ class MessageDict: @staticmethod def sew_submessages_and_reactions_to_msgs( - messages: List[Dict[str, Any]], - ) -> List[Dict[str, Any]]: + messages: list[dict[str, Any]], + ) -> list[dict[str, Any]]: msg_ids = [msg["id"] for msg in messages] submessages = SubMessage.get_raw_db_rows(msg_ids) sew_messages_and_submessages(messages, submessages) @@ -274,7 +274,7 @@ class MessageDict: @staticmethod def messages_to_encoded_cache( messages: Iterable[Message], realm_id: Optional[int] = None - ) -> Dict[int, bytes]: + ) -> dict[int, bytes]: messages_dict = MessageDict.messages_to_encoded_cache_helper(messages, realm_id) encoded_messages = {msg["id"]: stringify_message_dict(msg) for msg in messages_dict} return encoded_messages @@ -282,7 +282,7 @@ class MessageDict: @staticmethod def messages_to_encoded_cache_helper( messages: Iterable[Message], realm_id: Optional[int] = None - ) -> List[Dict[str, Any]]: + ) -> list[dict[str, Any]]: # Near duplicate of the build_message_dict + get_raw_db_rows # code path that accepts already fetched Message objects # rather than message IDs. @@ -321,7 +321,7 @@ class MessageDict: return [MessageDict.build_dict_from_raw_db_row(row) for row in message_rows] @staticmethod - def ids_to_dict(needed_ids: List[int]) -> List[Dict[str, Any]]: + def ids_to_dict(needed_ids: list[int]) -> list[dict[str, Any]]: # This is a special purpose function optimized for # callers like get_messages_backend(). fields = [ @@ -346,7 +346,7 @@ class MessageDict: return [MessageDict.build_dict_from_raw_db_row(row) for row in messages] @staticmethod - def build_dict_from_raw_db_row(row: Dict[str, Any]) -> Dict[str, Any]: + def build_dict_from_raw_db_row(row: dict[str, Any]) -> dict[str, Any]: """ row is a row from a .values() call, and it needs to have all the relevant fields populated @@ -388,9 +388,9 @@ class MessageDict: recipient_id: int, recipient_type: int, recipient_type_id: int, - reactions: List[RawReactionRow], - submessages: List[Dict[str, Any]], - ) -> Dict[str, Any]: + reactions: list[RawReactionRow], + submessages: list[dict[str, Any]], + ) -> dict[str, Any]: obj = dict( id=message_id, sender_id=sender_id, @@ -413,7 +413,7 @@ class MessageDict: if last_edit_time is not None: obj["last_edit_timestamp"] = datetime_to_timestamp(last_edit_time) assert edit_history_json is not None - edit_history: List[EditHistoryEvent] = orjson.loads(edit_history_json) + edit_history: list[EditHistoryEvent] = orjson.loads(edit_history_json) obj["edit_history"] = edit_history if Message.need_to_render_content( @@ -455,7 +455,7 @@ class MessageDict: return obj @staticmethod - def bulk_hydrate_sender_info(objs: List[Dict[str, Any]]) -> None: + def bulk_hydrate_sender_info(objs: list[dict[str, Any]]) -> None: sender_ids = list({obj["sender_id"] for obj in objs}) if not sender_ids: @@ -490,7 +490,7 @@ class MessageDict: obj["sender_email_address_visibility"] = user_row["email_address_visibility"] @staticmethod - def hydrate_recipient_info(obj: Dict[str, Any], display_recipient: DisplayRecipientT) -> None: + def hydrate_recipient_info(obj: dict[str, Any], display_recipient: DisplayRecipientT) -> None: """ This method hyrdrates recipient info with things like full names and emails of senders. Eventually @@ -532,7 +532,7 @@ class MessageDict: obj["stream_id"] = recipient_type_id @staticmethod - def bulk_hydrate_recipient_info(objs: List[Dict[str, Any]]) -> None: + def bulk_hydrate_recipient_info(objs: list[dict[str, Any]]) -> None: recipient_tuples = { # We use set to eliminate duplicate tuples. ( obj["recipient_id"], @@ -548,7 +548,7 @@ class MessageDict: @staticmethod def set_sender_avatar( - obj: Dict[str, Any], client_gravatar: bool, can_access_sender: bool = True + obj: dict[str, Any], client_gravatar: bool, can_access_sender: bool = True ) -> None: if not can_access_sender: obj["avatar_url"] = get_avatar_for_inaccessible_user() diff --git a/zerver/lib/migrate.py b/zerver/lib/migrate.py index f800f9ea9f..f78df11c64 100644 --- a/zerver/lib/migrate.py +++ b/zerver/lib/migrate.py @@ -1,5 +1,5 @@ import time -from typing import Any, Callable, List +from typing import Any, Callable from django.db import connection from django.db.backends.base.schema import BaseDatabaseSchemaEditor @@ -11,7 +11,7 @@ from psycopg2.sql import SQL, Composable, Identifier def do_batch_update( cursor: CursorWrapper, table: str, - assignments: List[Composable], + assignments: list[Composable], batch_size: int = 10000, sleep: float = 0.1, ) -> None: diff --git a/zerver/lib/muted_users.py b/zerver/lib/muted_users.py index d7f25ae9f0..d02a5539bc 100644 --- a/zerver/lib/muted_users.py +++ b/zerver/lib/muted_users.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Dict, List, Optional, Set +from typing import Optional from zerver.lib.cache import cache_with_key, get_muting_users_cache_key from zerver.lib.timestamp import datetime_to_timestamp @@ -7,7 +7,7 @@ from zerver.lib.utils import assert_is_not_none from zerver.models import MutedUser, UserProfile -def get_user_mutes(user_profile: UserProfile) -> List[Dict[str, int]]: +def get_user_mutes(user_profile: UserProfile) -> list[dict[str, int]]: rows = MutedUser.objects.filter(user_profile=user_profile).values( "muted_user_id", "date_muted", @@ -37,7 +37,7 @@ def get_mute_object(user_profile: UserProfile, muted_user: UserProfile) -> Optio @cache_with_key(get_muting_users_cache_key, timeout=3600 * 24 * 7) -def get_muting_users(muted_user_id: int) -> Set[int]: +def get_muting_users(muted_user_id: int) -> set[int]: """ This is kind of the inverse of `get_user_mutes` above. While `get_user_mutes` is mainly used for event system work, diff --git a/zerver/lib/narrow.py b/zerver/lib/narrow.py index 24bf9c6b92..d4bf39b02b 100644 --- a/zerver/lib/narrow.py +++ b/zerver/lib/narrow.py @@ -1,19 +1,6 @@ import re from dataclasses import dataclass -from typing import ( - Any, - Callable, - Dict, - Generic, - Iterable, - List, - Optional, - Sequence, - Set, - Tuple, - TypeVar, - Union, -) +from typing import Any, Callable, Generic, Iterable, Optional, Sequence, TypeVar, Union from django.conf import settings from django.contrib.auth.models import AnonymousUser @@ -101,7 +88,7 @@ class NarrowParameter(BaseModel): @model_validator(mode="before") @classmethod - def convert_term(cls, elem: Union[Dict[str, Any], List[str]]) -> Dict[str, Any]: + def convert_term(cls, elem: Union[dict[str, Any], list[str]]) -> dict[str, Any]: # We have to support a legacy tuple format. if isinstance(elem, list): if len(elem) != 2 or any(not isinstance(x, str) for x in elem): @@ -669,7 +656,7 @@ class NarrowBuilder: ) return query.where(maybe_negate(cond)) - def _get_direct_message_group_recipients(self, other_user: UserProfile) -> Set[int]: + def _get_direct_message_group_recipients(self, other_user: UserProfile) -> set[int]: self_recipient_ids = [ recipient_tuple["recipient_id"] for recipient_tuple in Subscription.objects.filter( @@ -823,7 +810,7 @@ class NarrowBuilder: def ok_to_include_history( - narrow: Optional[List[NarrowParameter]], + narrow: Optional[list[NarrowParameter]], user_profile: Optional[UserProfile], is_web_public_query: bool, ) -> bool: @@ -875,7 +862,7 @@ def ok_to_include_history( def get_channel_from_narrow_access_unchecked( - narrow: Optional[List[NarrowParameter]], realm: Realm + narrow: Optional[list[NarrowParameter]], realm: Realm ) -> Optional[Stream]: if narrow is not None: for term in narrow: @@ -961,9 +948,9 @@ def update_narrow_terms_containing_with_operator( def exclude_muting_conditions( - user_profile: UserProfile, narrow: Optional[List[NarrowParameter]] -) -> List[ClauseElement]: - conditions: List[ClauseElement] = [] + user_profile: UserProfile, narrow: Optional[list[NarrowParameter]] +) -> list[ClauseElement]: + conditions: list[ClauseElement] = [] channel_id = None try: # Note: It is okay here to not check access to channel @@ -1007,7 +994,7 @@ def exclude_muting_conditions( def get_base_query_for_search( realm_id: int, user_profile: Optional[UserProfile], need_message: bool, need_user_message: bool -) -> Tuple[Select, ColumnElement[Integer]]: +) -> tuple[Select, ColumnElement[Integer]]: # Handle the simple case where user_message isn't involved first. if not need_user_message: assert need_message @@ -1054,10 +1041,10 @@ def add_narrow_conditions( user_profile: Optional[UserProfile], inner_msg_id_col: ColumnElement[Integer], query: Select, - narrow: Optional[List[NarrowParameter]], + narrow: Optional[list[NarrowParameter]], is_web_public_query: bool, realm: Realm, -) -> Tuple[Select, bool]: +) -> tuple[Select, bool]: is_search = False # for now if narrow is None: @@ -1091,7 +1078,7 @@ def add_narrow_conditions( def find_first_unread_anchor( sa_conn: Connection, user_profile: Optional[UserProfile], - narrow: Optional[List[NarrowParameter]], + narrow: Optional[list[NarrowParameter]], ) -> int: # For anonymous web users, all messages are treated as read, and so # always return LARGER_THAN_MAX_MESSAGE_ID. @@ -1269,7 +1256,7 @@ MessageRowT = TypeVar("MessageRowT", bound=Sequence[Any]) @dataclass class LimitedMessages(Generic[MessageRowT]): - rows: List[MessageRowT] + rows: list[MessageRowT] found_anchor: bool found_newest: bool found_oldest: bool @@ -1352,7 +1339,7 @@ class FetchedMessages(LimitedMessages[Row]): def fetch_messages( *, - narrow: Optional[List[NarrowParameter]], + narrow: Optional[list[NarrowParameter]], user_profile: Optional[UserProfile], realm: Realm, is_web_public_query: bool, diff --git a/zerver/lib/narrow_helpers.py b/zerver/lib/narrow_helpers.py index 404cc276a0..67278886e2 100644 --- a/zerver/lib/narrow_helpers.py +++ b/zerver/lib/narrow_helpers.py @@ -20,7 +20,7 @@ from users: import os from dataclasses import dataclass -from typing import Collection, List, Optional, Sequence +from typing import Collection, Optional, Sequence from django.conf import settings @@ -40,10 +40,10 @@ def narrow_dataclasses_from_tuples(tups: Collection[Sequence[str]]) -> Collectio return [NarrowTerm(operator=tup[0], operand=tup[1]) for tup in tups] -stop_words_list: Optional[List[str]] = None +stop_words_list: Optional[list[str]] = None -def read_stop_words() -> List[str]: +def read_stop_words() -> list[str]: global stop_words_list if stop_words_list is None: file_path = os.path.join( diff --git a/zerver/lib/narrow_predicate.py b/zerver/lib/narrow_predicate.py index cd527403d2..f76241e00b 100644 --- a/zerver/lib/narrow_predicate.py +++ b/zerver/lib/narrow_predicate.py @@ -1,4 +1,4 @@ -from typing import Any, Collection, Dict, List, Protocol +from typing import Any, Collection, Protocol from django.utils.translation import gettext as _ @@ -7,9 +7,9 @@ from zerver.lib.narrow_helpers import NarrowTerm from zerver.lib.topic import RESOLVED_TOPIC_PREFIX, get_topic_from_message_info # "stream" is a legacy alias for "channel" -channel_operators: List[str] = ["channel", "stream"] +channel_operators: list[str] = ["channel", "stream"] # "streams" is a legacy alias for "channels" -channels_operators: List[str] = ["channels", "streams"] +channels_operators: list[str] = ["channels", "streams"] def check_narrow_for_events(narrow: Collection[NarrowTerm]) -> None: @@ -26,7 +26,7 @@ def check_narrow_for_events(narrow: Collection[NarrowTerm]) -> None: class NarrowPredicate(Protocol): - def __call__(self, *, message: Dict[str, Any], flags: List[str]) -> bool: ... + def __call__(self, *, message: dict[str, Any], flags: list[str]) -> bool: ... def build_narrow_predicate( @@ -36,7 +36,7 @@ def build_narrow_predicate( NarrowLibraryTest.""" check_narrow_for_events(narrow) - def narrow_predicate(*, message: Dict[str, Any], flags: List[str]) -> bool: + def narrow_predicate(*, message: dict[str, Any], flags: list[str]) -> bool: def satisfies_operator(*, operator: str, operand: str) -> bool: if operator in channel_operators: if message["type"] != "stream": diff --git a/zerver/lib/notification_data.py b/zerver/lib/notification_data.py index 2628df5d97..5a1f6be100 100644 --- a/zerver/lib/notification_data.py +++ b/zerver/lib/notification_data.py @@ -1,6 +1,6 @@ import math from dataclasses import dataclass -from typing import Any, Collection, Dict, List, Optional, Set +from typing import Any, Collection, Optional from zerver.lib.mention import MentionData from zerver.lib.user_groups import get_user_group_member_ids @@ -57,19 +57,19 @@ class UserMessageNotificationsData: flags: Collection[str], private_message: bool, disable_external_notifications: bool, - online_push_user_ids: Set[int], - dm_mention_push_disabled_user_ids: Set[int], - dm_mention_email_disabled_user_ids: Set[int], - stream_push_user_ids: Set[int], - stream_email_user_ids: Set[int], - topic_wildcard_mention_user_ids: Set[int], - stream_wildcard_mention_user_ids: Set[int], - followed_topic_push_user_ids: Set[int], - followed_topic_email_user_ids: Set[int], - topic_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], - all_bot_user_ids: Set[int], + online_push_user_ids: set[int], + dm_mention_push_disabled_user_ids: set[int], + dm_mention_email_disabled_user_ids: set[int], + stream_push_user_ids: set[int], + stream_email_user_ids: set[int], + topic_wildcard_mention_user_ids: set[int], + stream_wildcard_mention_user_ids: set[int], + followed_topic_push_user_ids: set[int], + followed_topic_email_user_ids: set[int], + topic_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], + all_bot_user_ids: set[int], ) -> "UserMessageNotificationsData": if user_id in all_bot_user_ids: # Don't send any notifications to bots @@ -284,10 +284,10 @@ def user_allows_notifications_in_StreamTopic( def get_user_group_mentions_data( - mentioned_user_ids: Set[int], mentioned_user_group_ids: List[int], mention_data: MentionData -) -> Dict[int, int]: + mentioned_user_ids: set[int], mentioned_user_group_ids: list[int], mention_data: MentionData +) -> dict[int, int]: # Maps user_id -> mentioned user_group_id - mentioned_user_groups_map: Dict[int, int] = dict() + mentioned_user_groups_map: dict[int, int] = dict() # Add members of the mentioned user groups into `mentions_user_ids`. for group_id in mentioned_user_group_ids: @@ -321,7 +321,7 @@ class MentionedUserGroup: def get_mentioned_user_group( - messages: List[Dict[str, Any]], user_profile: UserProfile + messages: list[dict[str, Any]], user_profile: UserProfile ) -> Optional[MentionedUserGroup]: """Returns the user group name to display in the email notification if user group(s) are mentioned. diff --git a/zerver/lib/onboarding.py b/zerver/lib/onboarding.py index 7c19514e7e..929d5ffde2 100644 --- a/zerver/lib/onboarding.py +++ b/zerver/lib/onboarding.py @@ -1,5 +1,3 @@ -from typing import Dict, List - from django.conf import settings from django.db import transaction from django.db.models import Count @@ -332,7 +330,7 @@ This **greetings** topic is a great place to say β€œhi” :wave: to your teammat :point_right: Click on this message to start a new message in the same conversation. """) - welcome_messages: List[Dict[str, str]] = [] + welcome_messages: list[dict[str, str]] = [] # Messages added to the "welcome messages" list last will be most # visible to users, since welcome messages will likely be browsed diff --git a/zerver/lib/onboarding_steps.py b/zerver/lib/onboarding_steps.py index 076578e0a3..149ab26dc2 100644 --- a/zerver/lib/onboarding_steps.py +++ b/zerver/lib/onboarding_steps.py @@ -1,7 +1,7 @@ # See https://zulip.readthedocs.io/en/latest/subsystems/onboarding-steps.html # for documentation on this subsystem. from dataclasses import dataclass -from typing import Any, Dict, List +from typing import Any from django.conf import settings @@ -12,14 +12,14 @@ from zerver.models import OnboardingStep, UserProfile class OneTimeNotice: name: str - def to_dict(self) -> Dict[str, str]: + def to_dict(self) -> dict[str, str]: return { "type": "one_time_notice", "name": self.name, } -ONE_TIME_NOTICES: List[OneTimeNotice] = [ +ONE_TIME_NOTICES: list[OneTimeNotice] = [ OneTimeNotice( name="visibility_policy_banner", ), @@ -42,10 +42,10 @@ ONE_TIME_NOTICES: List[OneTimeNotice] = [ # types. We can simply do: # ALL_ONBOARDING_STEPS: List[Union[OneTimeNotice, OtherType]] # to avoid API changes when new type is introduced in the future. -ALL_ONBOARDING_STEPS: List[OneTimeNotice] = ONE_TIME_NOTICES +ALL_ONBOARDING_STEPS: list[OneTimeNotice] = ONE_TIME_NOTICES -def get_next_onboarding_steps(user: UserProfile) -> List[Dict[str, Any]]: +def get_next_onboarding_steps(user: UserProfile) -> list[dict[str, Any]]: # If a Zulip server has disabled the tutorial, never send any # onboarding steps. if not settings.TUTORIAL_ENABLED: @@ -55,7 +55,7 @@ def get_next_onboarding_steps(user: UserProfile) -> List[Dict[str, Any]]: OnboardingStep.objects.filter(user=user).values_list("onboarding_step", flat=True) ) - onboarding_steps: List[Dict[str, Any]] = [] + onboarding_steps: list[dict[str, Any]] = [] for one_time_notice in ONE_TIME_NOTICES: if one_time_notice.name in seen_onboarding_steps: continue diff --git a/zerver/lib/outgoing_http.py b/zerver/lib/outgoing_http.py index 3283e0ecb5..9e25df50dc 100644 --- a/zerver/lib/outgoing_http.py +++ b/zerver/lib/outgoing_http.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union import requests from typing_extensions import override @@ -10,7 +10,7 @@ class OutgoingSession(requests.Session): self, role: str, timeout: float, - headers: Optional[Dict[str, str]] = None, + headers: Optional[dict[str, str]] = None, max_retries: Optional[Union[int, Retry]] = None, ) -> None: super().__init__() @@ -43,5 +43,5 @@ class OutgoingHTTPAdapter(requests.adapters.HTTPAdapter): return super().send(*args, **kwargs) @override - def proxy_headers(self, proxy: str) -> Dict[str, str]: + def proxy_headers(self, proxy: str) -> dict[str, str]: return {"X-Smokescreen-Role": self.role} diff --git a/zerver/lib/outgoing_webhook.py b/zerver/lib/outgoing_webhook.py index b45decb172..58b7c95c59 100644 --- a/zerver/lib/outgoing_webhook.py +++ b/zerver/lib/outgoing_webhook.py @@ -3,7 +3,7 @@ import json import logging from contextlib import suppress from time import perf_counter -from typing import Any, AnyStr, Dict, Optional +from typing import Any, AnyStr, Optional import requests from django.conf import settings @@ -38,19 +38,19 @@ class OutgoingWebhookServiceInterface(metaclass=abc.ABCMeta): @abc.abstractmethod def make_request( - self, base_url: str, event: Dict[str, Any], realm: Realm + self, base_url: str, event: dict[str, Any], realm: Realm ) -> Optional[Response]: raise NotImplementedError @abc.abstractmethod - def process_success(self, response_json: Dict[str, Any]) -> Optional[Dict[str, Any]]: + def process_success(self, response_json: dict[str, Any]) -> Optional[dict[str, Any]]: raise NotImplementedError class GenericOutgoingWebhookService(OutgoingWebhookServiceInterface): @override def make_request( - self, base_url: str, event: Dict[str, Any], realm: Realm + self, base_url: str, event: dict[str, Any], realm: Realm ) -> Optional[Response]: """ We send a simple version of the message to outgoing @@ -80,7 +80,7 @@ class GenericOutgoingWebhookService(OutgoingWebhookServiceInterface): return self.session.post(base_url, json=request_data) @override - def process_success(self, response_json: Dict[str, Any]) -> Optional[Dict[str, Any]]: + def process_success(self, response_json: dict[str, Any]) -> Optional[dict[str, Any]]: if response_json.get("response_not_required", False): return None @@ -103,7 +103,7 @@ class GenericOutgoingWebhookService(OutgoingWebhookServiceInterface): class SlackOutgoingWebhookService(OutgoingWebhookServiceInterface): @override def make_request( - self, base_url: str, event: Dict[str, Any], realm: Realm + self, base_url: str, event: dict[str, Any], realm: Realm ) -> Optional[Response]: if event["message"]["type"] == "private": failure_message = "Slack outgoing webhooks don't support direct messages." @@ -142,7 +142,7 @@ class SlackOutgoingWebhookService(OutgoingWebhookServiceInterface): return self.session.post(base_url, data=request_data) @override - def process_success(self, response_json: Dict[str, Any]) -> Optional[Dict[str, Any]]: + def process_success(self, response_json: dict[str, Any]) -> Optional[dict[str, Any]]: if "text" in response_json: content = response_json["text"] success_data = dict(content=content) @@ -151,7 +151,7 @@ class SlackOutgoingWebhookService(OutgoingWebhookServiceInterface): return None -AVAILABLE_OUTGOING_WEBHOOK_INTERFACES: Dict[str, Any] = { +AVAILABLE_OUTGOING_WEBHOOK_INTERFACES: dict[str, Any] = { GENERIC_INTERFACE: GenericOutgoingWebhookService, SLACK_INTERFACE: SlackOutgoingWebhookService, } @@ -173,7 +173,7 @@ def get_outgoing_webhook_service_handler(service: Service) -> Any: def send_response_message( - bot_id: int, message_info: Dict[str, Any], response_data: Dict[str, Any] + bot_id: int, message_info: dict[str, Any], response_data: dict[str, Any] ) -> None: """ bot_id is the user_id of the bot sending the response @@ -227,7 +227,7 @@ def send_response_message( ) -def fail_with_message(event: Dict[str, Any], failure_message: str) -> None: +def fail_with_message(event: dict[str, Any], failure_message: str) -> None: bot_id = event["user_profile_id"] message_info = event["message"] content = "Failure! " + failure_message @@ -238,7 +238,7 @@ def fail_with_message(event: Dict[str, Any], failure_message: str) -> None: send_response_message(bot_id=bot_id, message_info=message_info, response_data=response_data) -def get_message_url(event: Dict[str, Any]) -> str: +def get_message_url(event: dict[str, Any]) -> str: bot_user = get_user_profile_by_id(event["user_profile_id"]) message = event["message"] realm = bot_user.realm @@ -250,7 +250,7 @@ def get_message_url(event: Dict[str, Any]) -> str: def notify_bot_owner( - event: Dict[str, Any], + event: dict[str, Any], status_code: Optional[int] = None, response_content: Optional[AnyStr] = None, failure_message: Optional[str] = None, @@ -290,8 +290,8 @@ def notify_bot_owner( send_response_message(bot_id=bot_id, message_info=message_info, response_data=response_data) -def request_retry(event: Dict[str, Any], failure_message: Optional[str] = None) -> None: - def failure_processor(event: Dict[str, Any]) -> None: +def request_retry(event: dict[str, Any], failure_message: Optional[str] = None) -> None: + def failure_processor(event: dict[str, Any]) -> None: """ The name of the argument is 'event' on purpose. This argument will hide the 'event' argument of the request_retry function. Keeping the same name @@ -310,7 +310,7 @@ def request_retry(event: Dict[str, Any], failure_message: Optional[str] = None) def process_success_response( - event: Dict[str, Any], service_handler: Any, response: Response + event: dict[str, Any], service_handler: Any, response: Response ) -> None: try: response_json = json.loads(response.text) @@ -345,7 +345,7 @@ def process_success_response( def do_rest_call( base_url: str, - event: Dict[str, Any], + event: dict[str, Any], service_handler: OutgoingWebhookServiceInterface, ) -> Optional[Response]: """Returns response of call if no exception occurs.""" diff --git a/zerver/lib/per_request_cache.py b/zerver/lib/per_request_cache.py index e010c18695..7750079bb0 100644 --- a/zerver/lib/per_request_cache.py +++ b/zerver/lib/per_request_cache.py @@ -1,8 +1,8 @@ -from typing import Any, Callable, Dict, TypeVar +from typing import Any, Callable, TypeVar ReturnT = TypeVar("ReturnT") -FUNCTION_NAME_TO_PER_REQUEST_RESULT: Dict[str, Dict[int, Any]] = {} +FUNCTION_NAME_TO_PER_REQUEST_RESULT: dict[str, dict[int, Any]] = {} def return_same_value_during_entire_request(f: Callable[..., ReturnT]) -> Callable[..., ReturnT]: diff --git a/zerver/lib/presence.py b/zerver/lib/presence.py index 83190b5ac2..532efc8aaa 100644 --- a/zerver/lib/presence.py +++ b/zerver/lib/presence.py @@ -1,7 +1,7 @@ import time from collections import defaultdict from datetime import datetime, timedelta -from typing import Any, Dict, Mapping, Optional, Sequence, Tuple +from typing import Any, Mapping, Optional, Sequence from django.conf import settings from django.utils.timezone import now as timezone_now @@ -13,7 +13,7 @@ from zerver.models import Realm, UserPresence, UserProfile def get_presence_dicts_for_rows( all_rows: Sequence[Mapping[str, Any]], slim_presence: bool -) -> Dict[str, Dict[str, Any]]: +) -> dict[str, dict[str, Any]]: if slim_presence: # Stringify user_id here, since it's gonna be turned # into a string anyway by JSON, and it keeps mypy happy. @@ -23,7 +23,7 @@ def get_presence_dicts_for_rows( get_user_key = lambda row: row["user_profile__email"] get_user_presence_info = get_legacy_user_presence_info - user_statuses: Dict[str, Dict[str, Any]] = {} + user_statuses: dict[str, dict[str, Any]] = {} for presence_row in all_rows: user_key = get_user_key(presence_row) @@ -66,7 +66,7 @@ def user_presence_datetime_with_date_joined_default( def get_modern_user_presence_info( last_active_time: datetime, last_connected_time: datetime -) -> Dict[str, Any]: +) -> dict[str, Any]: # TODO: Do further bandwidth optimizations to this structure. result = {} result["active_timestamp"] = datetime_to_timestamp(last_active_time) @@ -76,7 +76,7 @@ def get_modern_user_presence_info( def get_legacy_user_presence_info( last_active_time: datetime, last_connected_time: datetime -) -> Dict[str, Any]: +) -> dict[str, Any]: """ Reformats the modern UserPresence data structure so that legacy API clients can still access presence data. @@ -107,7 +107,7 @@ def get_legacy_user_presence_info( def format_legacy_presence_dict( last_active_time: datetime, last_connected_time: datetime -) -> Dict[str, Any]: +) -> dict[str, Any]: """ This function assumes it's being called right after the presence object was updated, and is not meant to be used on old presence data. @@ -132,7 +132,7 @@ def format_legacy_presence_dict( def get_presence_for_user( user_profile_id: int, slim_presence: bool = False -) -> Dict[str, Dict[str, Any]]: +) -> dict[str, dict[str, Any]]: query = UserPresence.objects.filter(user_profile_id=user_profile_id).values( "last_active_time", "last_connected_time", @@ -151,9 +151,9 @@ def get_presence_dict_by_realm( slim_presence: bool = False, last_update_id_fetched_by_client: Optional[int] = None, requesting_user_profile: Optional[UserProfile] = None, -) -> Tuple[Dict[str, Dict[str, Any]], int]: +) -> tuple[dict[str, dict[str, Any]], int]: two_weeks_ago = timezone_now() - timedelta(weeks=2) - kwargs: Dict[str, object] = dict() + kwargs: dict[str, object] = dict() if last_update_id_fetched_by_client is not None: kwargs["last_update_id__gt"] = last_update_id_fetched_by_client @@ -213,7 +213,7 @@ def get_presences_for_realm( slim_presence: bool, last_update_id_fetched_by_client: Optional[int], requesting_user_profile: UserProfile, -) -> Tuple[Dict[str, Dict[str, Dict[str, Any]]], int]: +) -> tuple[dict[str, dict[str, dict[str, Any]]], int]: if realm.presence_disabled: # Return an empty dict if presence is disabled in this realm return defaultdict(dict), -1 @@ -230,7 +230,7 @@ def get_presence_response( requesting_user_profile: UserProfile, slim_presence: bool, last_update_id_fetched_by_client: Optional[int] = None, -) -> Dict[str, Any]: +) -> dict[str, Any]: realm = requesting_user_profile.realm server_timestamp = time.time() presences, last_update_id_fetched_by_server = get_presences_for_realm( diff --git a/zerver/lib/push_notifications.py b/zerver/lib/push_notifications.py index a70a821f86..dc35b2ab35 100644 --- a/zerver/lib/push_notifications.py +++ b/zerver/lib/push_notifications.py @@ -8,19 +8,7 @@ import re from dataclasses import dataclass from email.headerregistry import Address from functools import cache -from typing import ( - TYPE_CHECKING, - Any, - Dict, - Iterable, - List, - Mapping, - Optional, - Sequence, - Tuple, - Type, - Union, -) +from typing import TYPE_CHECKING, Any, Iterable, Mapping, Optional, Sequence, Union import lxml.html import orjson @@ -265,7 +253,7 @@ def send_apple_push_notification( if remote: assert settings.ZILENCER_ENABLED - DeviceTokenClass: Type[AbstractPushDeviceToken] = RemotePushDeviceToken + DeviceTokenClass: type[AbstractPushDeviceToken] = RemotePushDeviceToken else: DeviceTokenClass = PushDeviceToken @@ -298,7 +286,7 @@ def send_apple_push_notification( devices = [device for device in devices if device.ios_app_id is not None] async def send_all_notifications() -> ( - Iterable[Tuple[DeviceToken, Union[aioapns.common.NotificationResult, BaseException]]] + Iterable[tuple[DeviceToken, Union[aioapns.common.NotificationResult, BaseException]]] ): requests = [ aioapns.NotificationRequest( @@ -390,7 +378,7 @@ def has_fcm_credentials() -> bool: # nocoverage # This is purely used in testing def send_android_push_notification_to_user( - user_profile: UserProfile, data: Dict[str, Any], options: Dict[str, Any] + user_profile: UserProfile, data: dict[str, Any], options: dict[str, Any] ) -> None: devices = list(PushDeviceToken.objects.filter(user=user_profile, kind=PushDeviceToken.FCM)) send_android_push_notification( @@ -398,7 +386,7 @@ def send_android_push_notification_to_user( ) -def parse_fcm_options(options: Dict[str, Any], data: Dict[str, Any]) -> str: +def parse_fcm_options(options: dict[str, Any], data: dict[str, Any]) -> str: """ Parse FCM options, supplying defaults, and raising an error if invalid. @@ -446,8 +434,8 @@ def parse_fcm_options(options: Dict[str, Any], data: Dict[str, Any]) -> str: def send_android_push_notification( user_identity: UserPushIdentityCompat, devices: Sequence[DeviceToken], - data: Dict[str, Any], - options: Dict[str, Any], + data: dict[str, Any], + options: dict[str, Any], remote: Optional["RemoteZulipServer"] = None, ) -> int: """ @@ -504,7 +492,7 @@ def send_android_push_notification( if remote: assert settings.ZILENCER_ENABLED - DeviceTokenClass: Type[AbstractPushDeviceToken] = RemotePushDeviceToken + DeviceTokenClass: type[AbstractPushDeviceToken] = RemotePushDeviceToken else: DeviceTokenClass = PushDeviceToken @@ -549,9 +537,9 @@ def sends_notifications_directly() -> bool: def send_notifications_to_bouncer( user_profile: UserProfile, - apns_payload: Dict[str, Any], - gcm_payload: Dict[str, Any], - gcm_options: Dict[str, Any], + apns_payload: dict[str, Any], + gcm_payload: dict[str, Any], + gcm_options: dict[str, Any], android_devices: Sequence[DeviceToken], apple_devices: Sequence[DeviceToken], ) -> None: @@ -913,8 +901,8 @@ def get_mobile_push_content(rendered_content: str) -> str: ) return remaining_text.strip() == ":" - def get_collapsible_status_array(elements: List[lxml.html.HtmlElement]) -> List[bool]: - collapsible_status: List[bool] = [ + def get_collapsible_status_array(elements: list[lxml.html.HtmlElement]) -> list[bool]: + collapsible_status: list[bool] = [ element.tag == "blockquote" or is_user_said_paragraph(element) for element in elements ] return collapsible_status @@ -945,7 +933,7 @@ def get_mobile_push_content(rendered_content: str) -> str: return plain_text -def truncate_content(content: str) -> Tuple[str, bool]: +def truncate_content(content: str) -> tuple[str, bool]: # We use Unicode character 'HORIZONTAL ELLIPSIS' (U+2026) instead # of three dots as this saves two extra characters for textual # content. This function will need to be updated to handle Unicode @@ -955,9 +943,9 @@ def truncate_content(content: str) -> Tuple[str, bool]: return content[:200] + "…", True -def get_base_payload(user_profile: UserProfile) -> Dict[str, Any]: +def get_base_payload(user_profile: UserProfile) -> dict[str, Any]: """Common fields for all notification payloads.""" - data: Dict[str, Any] = {} + data: dict[str, Any] = {} # These will let the app support logging into multiple realms and servers. data["server"] = settings.EXTERNAL_HOST @@ -976,7 +964,7 @@ def get_message_payload( mentioned_user_group_id: Optional[int] = None, mentioned_user_group_name: Optional[str] = None, can_access_sender: bool = True, -) -> Dict[str, Any]: +) -> dict[str, Any]: """Common fields for `message` payloads, for all platforms.""" data = get_base_payload(user_profile) @@ -1102,7 +1090,7 @@ def get_message_payload_apns( mentioned_user_group_id: Optional[int] = None, mentioned_user_group_name: Optional[str] = None, can_access_sender: bool = True, -) -> Dict[str, Any]: +) -> dict[str, Any]: """A `message` payload for iOS, via APNs.""" zulip_data = get_message_payload( user_profile, message, mentioned_user_group_id, mentioned_user_group_name, can_access_sender @@ -1135,7 +1123,7 @@ def get_message_payload_gcm( mentioned_user_group_id: Optional[int] = None, mentioned_user_group_name: Optional[str] = None, can_access_sender: bool = True, -) -> Tuple[Dict[str, Any], Dict[str, Any]]: +) -> tuple[dict[str, Any], dict[str, Any]]: """A `message` payload + options, for Android via FCM.""" data = get_message_payload( user_profile, message, mentioned_user_group_id, mentioned_user_group_name, can_access_sender @@ -1169,8 +1157,8 @@ def get_message_payload_gcm( def get_remove_payload_gcm( user_profile: UserProfile, - message_ids: List[int], -) -> Tuple[Dict[str, Any], Dict[str, Any]]: + message_ids: list[int], +) -> tuple[dict[str, Any], dict[str, Any]]: """A `remove` payload + options, for Android via FCM.""" gcm_payload = get_base_payload(user_profile) gcm_payload.update( @@ -1184,7 +1172,7 @@ def get_remove_payload_gcm( return gcm_payload, gcm_options -def get_remove_payload_apns(user_profile: UserProfile, message_ids: List[int]) -> Dict[str, Any]: +def get_remove_payload_apns(user_profile: UserProfile, message_ids: list[int]) -> dict[str, Any]: zulip_data = get_base_payload(user_profile) zulip_data.update( event="remove", @@ -1197,7 +1185,7 @@ def get_remove_payload_apns(user_profile: UserProfile, message_ids: List[int]) - return apns_data -def handle_remove_push_notification(user_profile_id: int, message_ids: List[int]) -> None: +def handle_remove_push_notification(user_profile_id: int, message_ids: list[int]) -> None: """This should be called when a message that previously had a mobile push notification executed is read. This triggers a push to the mobile app, when the message is read on the server, to remove the @@ -1279,7 +1267,7 @@ def handle_remove_push_notification(user_profile_id: int, message_ids: List[int] ).update(flags=F("flags").bitand(~UserMessage.flags.active_mobile_push_notification)) -def handle_push_notification(user_profile_id: int, missed_message: Dict[str, Any]) -> None: +def handle_push_notification(user_profile_id: int, missed_message: dict[str, Any]) -> None: """ missed_message is the event received by the zerver.worker.missedmessage_mobile_notifications.PushNotificationWorker.consume function. @@ -1450,7 +1438,7 @@ def handle_push_notification(user_profile_id: int, missed_message: Dict[str, Any def send_test_push_notification_directly_to_devices( user_identity: UserPushIdentityCompat, devices: Sequence[DeviceToken], - base_payload: Dict[str, Any], + base_payload: dict[str, Any], remote: Optional["RemoteZulipServer"] = None, ) -> None: payload = copy.deepcopy(base_payload) @@ -1485,7 +1473,7 @@ def send_test_push_notification_directly_to_devices( ) -def send_test_push_notification(user_profile: UserProfile, devices: List[PushDeviceToken]) -> None: +def send_test_push_notification(user_profile: UserProfile, devices: list[PushDeviceToken]) -> None: base_payload = get_base_payload(user_profile) if uses_notification_bouncer(): for device in devices: diff --git a/zerver/lib/query_helpers.py b/zerver/lib/query_helpers.py index 0ccf55e279..a1568147e7 100644 --- a/zerver/lib/query_helpers.py +++ b/zerver/lib/query_helpers.py @@ -1,4 +1,4 @@ -from typing import List, TypeVar +from typing import TypeVar from django.db import models from django_stubs_ext import ValuesQuerySet @@ -9,7 +9,7 @@ RowT = TypeVar("RowT") def query_for_ids( query: ValuesQuerySet[ModelT, RowT], - user_ids: List[int], + user_ids: list[int], field: str, ) -> ValuesQuerySet[ModelT, RowT]: """ diff --git a/zerver/lib/queue.py b/zerver/lib/queue.py index e7966733ef..a23b39c3f2 100644 --- a/zerver/lib/queue.py +++ b/zerver/lib/queue.py @@ -5,7 +5,7 @@ import threading import time from abc import ABCMeta, abstractmethod from collections import defaultdict -from typing import Any, Callable, Dict, Generic, List, Mapping, Optional, Set, Type, TypeVar, Union +from typing import Any, Callable, Generic, Mapping, Optional, TypeVar, Union import orjson import pika @@ -39,10 +39,10 @@ class QueueClient(Generic[ChannelT], metaclass=ABCMeta): prefetch: int = 0, ) -> None: self.log = logging.getLogger("zulip.queue") - self.queues: Set[str] = set() + self.queues: set[str] = set() self.channel: Optional[ChannelT] = None self.prefetch = prefetch - self.consumers: Dict[str, Set[Consumer[ChannelT]]] = defaultdict(set) + self.consumers: dict[str, set[Consumer[ChannelT]]] = defaultdict(set) self.rabbitmq_heartbeat = rabbitmq_heartbeat self.is_consuming = False self._connect() @@ -80,7 +80,7 @@ class QueueClient(Generic[ChannelT], metaclass=ABCMeta): if self.rabbitmq_heartbeat == 0: tcp_options = dict(TCP_KEEPIDLE=60 * 5) - ssl_options: Union[Type[pika.ConnectionParameters._DEFAULT], pika.SSLOptions] = ( + ssl_options: Union[type[pika.ConnectionParameters._DEFAULT], pika.SSLOptions] = ( pika.ConnectionParameters._DEFAULT ) if settings.RABBITMQ_USE_TLS: @@ -186,7 +186,7 @@ class SimpleQueueClient(QueueClient[BlockingChannel]): def start_json_consumer( self, queue_name: str, - callback: Callable[[List[Dict[str, Any]]], None], + callback: Callable[[list[dict[str, Any]]], None], batch_size: int = 1, timeout: Optional[int] = None, ) -> None: @@ -194,7 +194,7 @@ class SimpleQueueClient(QueueClient[BlockingChannel]): timeout = None def do_consume(channel: BlockingChannel) -> None: - events: List[Dict[str, Any]] = [] + events: list[dict[str, Any]] = [] last_process = time.time() max_processed: Optional[int] = None self.is_consuming = True @@ -273,7 +273,7 @@ class TornadoQueueClient(QueueClient[Channel]): # the server, rather than an unbounded number. prefetch=100, ) - self._on_open_cbs: List[Callable[[Channel], None]] = [] + self._on_open_cbs: list[Callable[[Channel], None]] = [] self._connection_failure_count = 0 @override @@ -386,7 +386,7 @@ class TornadoQueueClient(QueueClient[Channel]): def start_json_consumer( self, queue_name: str, - callback: Callable[[List[Dict[str, Any]]], None], + callback: Callable[[list[dict[str, Any]]], None], batch_size: int = 1, timeout: Optional[int] = None, ) -> None: @@ -435,7 +435,7 @@ def set_queue_client(queue_client: Union[SimpleQueueClient, TornadoQueueClient]) def queue_json_publish( queue_name: str, - event: Dict[str, Any], + event: dict[str, Any], processor: Optional[Callable[[Any], None]] = None, ) -> None: if settings.USING_RABBITMQ: @@ -450,12 +450,12 @@ def queue_json_publish( get_worker(queue_name, disable_timeout=True).consume_single_event(event) -def queue_event_on_commit(queue_name: str, event: Dict[str, Any]) -> None: +def queue_event_on_commit(queue_name: str, event: dict[str, Any]) -> None: transaction.on_commit(lambda: queue_json_publish(queue_name, event)) def retry_event( - queue_name: str, event: Dict[str, Any], failure_processor: Callable[[Dict[str, Any]], None] + queue_name: str, event: dict[str, Any], failure_processor: Callable[[dict[str, Any]], None] ) -> None: if "failed_tries" not in event: event["failed_tries"] = 0 diff --git a/zerver/lib/rate_limiter.py b/zerver/lib/rate_limiter.py index 822daaaa0c..113ddb029e 100644 --- a/zerver/lib/rate_limiter.py +++ b/zerver/lib/rate_limiter.py @@ -1,7 +1,7 @@ import logging import time from abc import ABC, abstractmethod -from typing import Dict, List, Optional, Set, Tuple, Type, cast +from typing import Optional, cast import orjson import redis @@ -20,7 +20,7 @@ from zerver.models import UserProfile # https://www.domaintools.com/resources/blog/rate-limiting-with-redis client = get_redis_client() -rules: Dict[str, List[Tuple[int, int]]] = settings.RATE_LIMITING_RULES +rules: dict[str, list[tuple[int, int]]] = settings.RATE_LIMITING_RULES logger = logging.getLogger(__name__) @@ -30,13 +30,13 @@ class RateLimiterLockingError(Exception): class RateLimitedObject(ABC): - def __init__(self, backend: Optional["Type[RateLimiterBackend]"] = None) -> None: + def __init__(self, backend: Optional["type[RateLimiterBackend]"] = None) -> None: if backend is not None: - self.backend: Type[RateLimiterBackend] = backend + self.backend: type[RateLimiterBackend] = backend else: self.backend = RedisRateLimiterBackend - def rate_limit(self) -> Tuple[bool, float]: + def rate_limit(self) -> tuple[bool, float]: # Returns (ratelimited, secs_to_freedom) return self.backend.rate_limit_entity( self.key(), self.get_rules(), self.max_api_calls(), self.max_api_window() @@ -84,14 +84,14 @@ class RateLimitedObject(ABC): """Returns the API time window for the highest limit""" return self.get_rules()[-1][0] - def api_calls_left(self) -> Tuple[int, float]: + def api_calls_left(self) -> tuple[int, float]: """Returns how many API calls in this range this client has, as well as when the rate-limit will be reset to 0""" max_window = self.max_api_window() max_calls = self.max_api_calls() return self.backend.get_api_calls_left(self.key(), max_window, max_calls) - def get_rules(self) -> List[Tuple[int, int]]: + def get_rules(self) -> list[tuple[int, int]]: """ This is a simple wrapper meant to protect against having to deal with an empty list of rules, as it would require fiddling with that special case @@ -106,7 +106,7 @@ class RateLimitedObject(ABC): pass @abstractmethod - def rules(self) -> List[Tuple[int, int]]: + def rules(self) -> list[tuple[int, int]]: pass @@ -116,7 +116,7 @@ class RateLimitedUser(RateLimitedObject): self.rate_limits = user.rate_limits self.domain = domain if settings.RUNNING_INSIDE_TORNADO and domain in settings.RATE_LIMITING_DOMAINS_FOR_TORNADO: - backend: Optional[Type[RateLimiterBackend]] = TornadoInMemoryRateLimiterBackend + backend: Optional[type[RateLimiterBackend]] = TornadoInMemoryRateLimiterBackend else: backend = None super().__init__(backend=backend) @@ -126,10 +126,10 @@ class RateLimitedUser(RateLimitedObject): return f"{type(self).__name__}:{self.user_id}:{self.domain}" @override - def rules(self) -> List[Tuple[int, int]]: + def rules(self) -> list[tuple[int, int]]: # user.rate_limits are general limits, applicable to the domain 'api_by_user' if self.rate_limits != "" and self.domain == "api_by_user": - result: List[Tuple[int, int]] = [] + result: list[tuple[int, int]] = [] for limit in self.rate_limits.split(","): (seconds, requests) = limit.split(":", 2) result.append((int(seconds), int(requests))) @@ -142,7 +142,7 @@ class RateLimitedIPAddr(RateLimitedObject): self.ip_addr = ip_addr self.domain = domain if settings.RUNNING_INSIDE_TORNADO and domain in settings.RATE_LIMITING_DOMAINS_FOR_TORNADO: - backend: Optional[Type[RateLimiterBackend]] = TornadoInMemoryRateLimiterBackend + backend: Optional[type[RateLimiterBackend]] = TornadoInMemoryRateLimiterBackend else: backend = None super().__init__(backend=backend) @@ -153,7 +153,7 @@ class RateLimitedIPAddr(RateLimitedObject): return f"{type(self).__name__}:<{self.ip_addr}>:{self.domain}" @override - def rules(self) -> List[Tuple[int, int]]: + def rules(self) -> list[tuple[int, int]]: return rules[self.domain] @@ -177,14 +177,14 @@ class RateLimiterBackend(ABC): @abstractmethod def get_api_calls_left( cls, entity_key: str, range_seconds: int, max_calls: int - ) -> Tuple[int, float]: + ) -> tuple[int, float]: pass @classmethod @abstractmethod def rate_limit_entity( - cls, entity_key: str, rules: List[Tuple[int, int]], max_api_calls: int, max_api_window: int - ) -> Tuple[bool, float]: + cls, entity_key: str, rules: list[tuple[int, int]], max_api_calls: int, max_api_window: int + ) -> tuple[bool, float]: # Returns (ratelimited, secs_to_freedom) pass @@ -192,15 +192,15 @@ class RateLimiterBackend(ABC): class TornadoInMemoryRateLimiterBackend(RateLimiterBackend): # reset_times[rule][key] is the time at which the event # request from the rate-limited key will be accepted. - reset_times: Dict[Tuple[int, int], Dict[str, float]] = {} + reset_times: dict[tuple[int, int], dict[str, float]] = {} # last_gc_time is the last time when the garbage was # collected from reset_times for rule (time_window, max_count). - last_gc_time: Dict[Tuple[int, int], float] = {} + last_gc_time: dict[tuple[int, int], float] = {} # timestamps_blocked_until[key] contains the timestamp # up to which the key has been blocked manually. - timestamps_blocked_until: Dict[str, float] = {} + timestamps_blocked_until: dict[str, float] = {} @classmethod def _garbage_collect_for_rule(cls, now: float, time_window: int, max_count: int) -> None: @@ -222,7 +222,7 @@ class TornadoInMemoryRateLimiterBackend(RateLimiterBackend): del cls.reset_times[(time_window, max_count)] @classmethod - def need_to_limit(cls, entity_key: str, time_window: int, max_count: int) -> Tuple[bool, float]: + def need_to_limit(cls, entity_key: str, time_window: int, max_count: int) -> tuple[bool, float]: """ Returns a tuple of `(rate_limited, time_till_free)`. For simplicity, we have loosened the semantics here from @@ -258,7 +258,7 @@ class TornadoInMemoryRateLimiterBackend(RateLimiterBackend): @override def get_api_calls_left( cls, entity_key: str, range_seconds: int, max_calls: int - ) -> Tuple[int, float]: + ) -> tuple[int, float]: now = time.time() if (range_seconds, max_calls) in cls.reset_times and entity_key in cls.reset_times[ (range_seconds, max_calls) @@ -291,8 +291,8 @@ class TornadoInMemoryRateLimiterBackend(RateLimiterBackend): @classmethod @override def rate_limit_entity( - cls, entity_key: str, rules: List[Tuple[int, int]], max_api_calls: int, max_api_window: int - ) -> Tuple[bool, float]: + cls, entity_key: str, rules: list[tuple[int, int]], max_api_calls: int, max_api_window: int + ) -> tuple[bool, float]: now = time.time() if entity_key in cls.timestamps_blocked_until: # Check whether the key is manually blocked. @@ -314,7 +314,7 @@ class TornadoInMemoryRateLimiterBackend(RateLimiterBackend): class RedisRateLimiterBackend(RateLimiterBackend): @classmethod - def get_keys(cls, entity_key: str) -> List[str]: + def get_keys(cls, entity_key: str) -> list[str]: return [ f"{redis_utils.REDIS_KEY_PREFIX}ratelimit:{entity_key}:{keytype}" for keytype in ["list", "zset", "block"] @@ -346,7 +346,7 @@ class RedisRateLimiterBackend(RateLimiterBackend): @override def get_api_calls_left( cls, entity_key: str, range_seconds: int, max_calls: int - ) -> Tuple[int, float]: + ) -> tuple[int, float]: list_key, set_key, _ = cls.get_keys(entity_key) # Count the number of values in our sorted set # that are between now and the cutoff @@ -374,7 +374,7 @@ class RedisRateLimiterBackend(RateLimiterBackend): return calls_left, time_reset - now @classmethod - def is_ratelimited(cls, entity_key: str, rules: List[Tuple[int, int]]) -> Tuple[bool, float]: + def is_ratelimited(cls, entity_key: str, rules: list[tuple[int, int]]) -> tuple[bool, float]: """Returns a tuple of (rate_limited, time_till_free)""" assert rules list_key, set_key, blocking_key = cls.get_keys(entity_key) @@ -390,7 +390,7 @@ class RedisRateLimiterBackend(RateLimiterBackend): pipe.get(blocking_key) pipe.ttl(blocking_key) - rule_timestamps: List[Optional[bytes]] = pipe.execute() + rule_timestamps: list[Optional[bytes]] = pipe.execute() # Check if there is a manual block on this API key blocking_ttl_b = rule_timestamps.pop() @@ -477,8 +477,8 @@ class RedisRateLimiterBackend(RateLimiterBackend): @classmethod @override def rate_limit_entity( - cls, entity_key: str, rules: List[Tuple[int, int]], max_api_calls: int, max_api_window: int - ) -> Tuple[bool, float]: + cls, entity_key: str, rules: list[tuple[int, int]], max_api_calls: int, max_api_window: int + ) -> tuple[bool, float]: ratelimited, time = cls.is_ratelimited(entity_key, rules) if not ratelimited: @@ -515,7 +515,7 @@ class RateLimitedSpectatorAttachmentAccessByFile(RateLimitedObject): return f"{type(self).__name__}:{self.path_id}" @override - def rules(self) -> List[Tuple[int, int]]: + def rules(self) -> list[tuple[int, int]]: return settings.RATE_LIMITING_RULES["spectator_attachment_access_by_file"] @@ -531,7 +531,7 @@ def is_local_addr(addr: str) -> bool: @cache_with_key(lambda: "tor_ip_addresses:", timeout=60 * 60) @circuit(failure_threshold=2, recovery_timeout=60 * 10) -def get_tor_ips() -> Set[str]: +def get_tor_ips() -> set[str]: if not settings.RATE_LIMIT_TOR_TOGETHER: return set() diff --git a/zerver/lib/realm_logo.py b/zerver/lib/realm_logo.py index f89305bbdb..cad54595d2 100644 --- a/zerver/lib/realm_logo.py +++ b/zerver/lib/realm_logo.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from django.conf import settings from django.contrib.staticfiles.storage import staticfiles_storage @@ -29,7 +29,7 @@ def get_realm_logo_url(realm: Realm, night: bool) -> str: return staticfiles_storage.url("images/logo/zulip-org-logo.svg") + "?version=0" -def get_realm_logo_data(realm: Realm, night: bool) -> Dict[str, Any]: +def get_realm_logo_data(realm: Realm, night: bool) -> dict[str, Any]: if night: return dict( night_logo_url=get_realm_logo_url(realm, night), diff --git a/zerver/lib/recipient_parsing.py b/zerver/lib/recipient_parsing.py index 2386cdb4fd..8e80904d5c 100644 --- a/zerver/lib/recipient_parsing.py +++ b/zerver/lib/recipient_parsing.py @@ -1,5 +1,3 @@ -from typing import List - import orjson from django.utils.translation import gettext as _ @@ -15,7 +13,7 @@ def extract_stream_id(req_to: str) -> int: return stream_id -def extract_direct_message_recipient_ids(req_to: str) -> List[int]: +def extract_direct_message_recipient_ids(req_to: str) -> list[int]: try: user_ids = orjson.loads(req_to) except orjson.JSONDecodeError: diff --git a/zerver/lib/recipient_users.py b/zerver/lib/recipient_users.py index 9f44e249b7..b2f46d433a 100644 --- a/zerver/lib/recipient_users.py +++ b/zerver/lib/recipient_users.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional, Sequence +from typing import Optional, Sequence from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ @@ -72,7 +72,7 @@ def get_recipient_from_user_profiles( def validate_recipient_user_profiles( user_profiles: Sequence[UserProfile], sender: UserProfile, allow_deactivated: bool = False ) -> Sequence[UserProfile]: - recipient_profiles_map: Dict[int, UserProfile] = {} + recipient_profiles_map: dict[int, UserProfile] = {} # We exempt cross-realm bots from the check that all the recipients # are in the same realm. diff --git a/zerver/lib/redis_utils.py b/zerver/lib/redis_utils.py index deddca2a09..66ddd871a0 100644 --- a/zerver/lib/redis_utils.py +++ b/zerver/lib/redis_utils.py @@ -1,7 +1,7 @@ import os import re import secrets -from typing import Any, Dict, Mapping, Optional +from typing import Any, Mapping, Optional import orjson import redis @@ -62,7 +62,7 @@ def get_dict_from_redis( redis_client: "redis.StrictRedis[bytes]", key_format: str, key: str, -) -> Optional[Dict[str, Any]]: +) -> Optional[dict[str, Any]]: # This function requires inputting the intended key_format to validate # that the key fits it, as an additionally security measure. This protects # against bugs where a caller requests a key based on user input and doesn't diff --git a/zerver/lib/remote_server.py b/zerver/lib/remote_server.py index 7bb3841e4e..d081c3faff 100644 --- a/zerver/lib/remote_server.py +++ b/zerver/lib/remote_server.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict, List, Mapping, Optional, Tuple, Union +from typing import Any, Mapping, Optional, Union from urllib.parse import urljoin import orjson @@ -71,7 +71,7 @@ class RealmAuditLogDataForAnalytics(BaseModel): realm: int event_time: float backfilled: bool - extra_data: Optional[Union[str, Dict[str, Any]]] + extra_data: Optional[Union[str, dict[str, Any]]] event_type: int @@ -87,7 +87,7 @@ class RealmDataForAnalytics(BaseModel): deactivated: bool is_system_bot_realm: bool = False - authentication_methods: Dict[str, bool] = Field(default_factory=dict) + authentication_methods: dict[str, bool] = Field(default_factory=dict) uuid: UUID4 uuid_owner_secret: str @@ -102,10 +102,10 @@ class RealmDataForAnalytics(BaseModel): class AnalyticsRequest(BaseModel): - realm_counts: Json[List[RealmCountDataForAnalytics]] - installation_counts: Json[List[InstallationCountDataForAnalytics]] - realmauditlog_rows: Optional[Json[List[RealmAuditLogDataForAnalytics]]] = None - realms: Json[List[RealmDataForAnalytics]] + realm_counts: Json[list[RealmCountDataForAnalytics]] + installation_counts: Json[list[InstallationCountDataForAnalytics]] + realmauditlog_rows: Optional[Json[list[RealmAuditLogDataForAnalytics]]] = None + realms: Json[list[RealmDataForAnalytics]] version: Optional[Json[str]] merge_base: Optional[Json[str]] api_feature_level: Optional[Json[int]] @@ -122,7 +122,7 @@ def send_to_push_bouncer( endpoint: str, post_data: Union[bytes, Mapping[str, Union[str, int, None, bytes]]], extra_headers: Mapping[str, str] = {}, -) -> Dict[str, object]: +) -> dict[str, object]: """While it does actually send the notice, this function has a lot of code and comments around error handling for the push notifications bouncer. There are several classes of failures, each with its own @@ -242,7 +242,7 @@ def send_to_push_bouncer( def send_json_to_push_bouncer( method: str, endpoint: str, post_data: Mapping[str, object] -) -> Dict[str, object]: +) -> dict[str, object]: return send_to_push_bouncer( method, endpoint, @@ -309,10 +309,10 @@ def build_analytics_data( realm_count_query: QuerySet[RealmCount], installation_count_query: QuerySet[InstallationCount], realmauditlog_query: QuerySet[RealmAuditLog], -) -> Tuple[ - List[RealmCountDataForAnalytics], - List[InstallationCountDataForAnalytics], - List[RealmAuditLogDataForAnalytics], +) -> tuple[ + list[RealmCountDataForAnalytics], + list[InstallationCountDataForAnalytics], + list[RealmAuditLogDataForAnalytics], ]: # We limit the batch size on the client side to avoid OOM kills timeouts, etc. MAX_CLIENT_BATCH_SIZE = 10000 @@ -355,7 +355,7 @@ def build_analytics_data( return realm_count_data, installation_count_data, zerver_realmauditlog -def get_realms_info_for_push_bouncer(realm_id: Optional[int] = None) -> List[RealmDataForAnalytics]: +def get_realms_info_for_push_bouncer(realm_id: Optional[int] = None) -> list[RealmDataForAnalytics]: realms = Realm.objects.order_by("id") if realm_id is not None: # nocoverage realms = realms.filter(id=realm_id) diff --git a/zerver/lib/request.py b/zerver/lib/request.py index b37d8d90ca..854bfa9638 100644 --- a/zerver/lib/request.py +++ b/zerver/lib/request.py @@ -5,14 +5,11 @@ from types import FunctionType from typing import ( Any, Callable, - Dict, Generic, - List, Literal, MutableMapping, Optional, Sequence, - Set, TypeVar, Union, cast, @@ -63,12 +60,12 @@ class RequestNotes(BaseNotes[HttpRequest, "RequestNotes"]): realm: Optional[Realm] = None has_fetched_realm: bool = False set_language: Optional[str] = None - ratelimits_applied: List[rate_limiter.RateLimitResult] = field(default_factory=list) + ratelimits_applied: list[rate_limiter.RateLimitResult] = field(default_factory=list) query: Optional[str] = None error_format: Optional[str] = None saved_response: Optional[HttpResponse] = None tornado_handler_id: Optional[int] = None - processed_parameters: Set[str] = field(default_factory=set) + processed_parameters: set[str] = field(default_factory=set) remote_server: Optional["RemoteZulipServer"] = None is_webhook_view: bool = False @@ -312,7 +309,7 @@ def REQ( ) -arguments_map: Dict[str, List[str]] = defaultdict(list) +arguments_map: dict[str, list[str]] = defaultdict(list) ParamT = ParamSpec("ParamT") ReturnT = TypeVar("ReturnT") diff --git a/zerver/lib/response.py b/zerver/lib/response.py index 98807fc707..59dba4c23b 100644 --- a/zerver/lib/response.py +++ b/zerver/lib/response.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Iterator, List, Mapping, Optional +from typing import Any, Iterator, Mapping, Optional import orjson from django.http import HttpRequest, HttpResponse, HttpResponseNotAllowed @@ -10,7 +10,7 @@ from zerver.lib.exceptions import JsonableError, UnauthorizedError class MutableJsonResponse(HttpResponse): def __init__( self, - data: Dict[str, Any], + data: dict[str, Any], *, content_type: str, status: int, @@ -23,7 +23,7 @@ class MutableJsonResponse(HttpResponse): self._data = data self._needs_serialization = True - def get_data(self) -> Dict[str, Any]: + def get_data(self) -> dict[str, Any]: """Get data for this MutableJsonResponse. Calling this method after the response's content has already been serialized will mean the next time the response's content is accessed @@ -83,7 +83,7 @@ def json_unauthorized( ) -def json_method_not_allowed(methods: List[str]) -> HttpResponseNotAllowed: +def json_method_not_allowed(methods: list[str]) -> HttpResponseNotAllowed: resp = HttpResponseNotAllowed(methods) resp.content = orjson.dumps( {"result": "error", "msg": "Method Not Allowed", "allowed_methods": methods} diff --git a/zerver/lib/rest.py b/zerver/lib/rest.py index cdd84a7c38..a0486e9826 100644 --- a/zerver/lib/rest.py +++ b/zerver/lib/rest.py @@ -1,5 +1,5 @@ from functools import wraps -from typing import Callable, Dict, Set, Tuple, Union +from typing import Callable, Union from django.http import HttpRequest, HttpResponse, HttpResponseBase from django.urls import path @@ -47,8 +47,8 @@ def default_never_cache_responses( def get_target_view_function_or_response( - request: HttpRequest, rest_dispatch_kwargs: Dict[str, object] -) -> Union[Tuple[Callable[..., HttpResponse], Set[str]], HttpResponse]: + request: HttpRequest, rest_dispatch_kwargs: dict[str, object] +) -> Union[tuple[Callable[..., HttpResponse], set[str]], HttpResponse]: """Helper for REST API request dispatch. The rest_dispatch_kwargs parameter is expected to be a dictionary mapping HTTP methods to a mix of view functions and (view_function, {view_flags}) tuples. @@ -65,7 +65,7 @@ def get_target_view_function_or_response( this feature; it's not clear it's actually used. """ - supported_methods: Dict[str, object] = {} + supported_methods: dict[str, object] = {} request_notes = RequestNotes.get_notes(request) if request_notes.saved_response is not None: # For completing long-polled Tornado requests, we skip the @@ -207,7 +207,7 @@ def rest_path( route: str, **handlers: Union[ Callable[..., HttpResponseBase], - Tuple[Callable[..., HttpResponseBase], Set[str]], + tuple[Callable[..., HttpResponseBase], set[str]], ], ) -> URLPattern: return path(route, rest_dispatch, handlers) diff --git a/zerver/lib/retention.py b/zerver/lib/retention.py index 3813f2cf3b..a83888ab55 100644 --- a/zerver/lib/retention.py +++ b/zerver/lib/retention.py @@ -29,7 +29,7 @@ import logging import time from datetime import timedelta -from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple, Type, Union +from typing import Any, Iterable, Mapping, Optional, Union from django.conf import settings from django.db import connection, transaction @@ -66,7 +66,7 @@ TRANSACTION_DELETION_BATCH_SIZE = 100 # hang off the Message table (with a foreign key to Message being part # of its primary lookup key). This structure allows us to share the # code for managing these related tables. -models_with_message_key: List[Dict[str, Any]] = [ +models_with_message_key: list[dict[str, Any]] = [ { "class": Reaction, "archive_class": ArchivedReaction, @@ -92,13 +92,13 @@ EXCLUDE_FIELDS = {Message._meta.get_field("search_tsvector")} @transaction.atomic(savepoint=False) def move_rows( - base_model: Type[Model], + base_model: type[Model], raw_query: SQL, *, src_db_table: Optional[str] = None, returning_id: bool = False, **kwargs: Composable, -) -> List[int]: +) -> list[int]: """Core helper for bulk moving rows between a table and its archive table""" if src_db_table is None: # Use base_model's db_table unless otherwise specified. @@ -256,7 +256,7 @@ def move_expired_direct_messages_to_archive( return message_count -def move_models_with_message_key_to_archive(msg_ids: List[int]) -> None: +def move_models_with_message_key_to_archive(msg_ids: list[int]) -> None: assert len(msg_ids) > 0 for model in models_with_message_key: @@ -282,7 +282,7 @@ def move_models_with_message_key_to_archive(msg_ids: List[int]) -> None: # because they can be referenced by more than one Message, and we only # want to delete the Attachment if we're deleting the last message # referencing them. -def move_attachments_to_archive(msg_ids: List[int]) -> None: +def move_attachments_to_archive(msg_ids: list[int]) -> None: assert len(msg_ids) > 0 query = SQL( @@ -300,7 +300,7 @@ def move_attachments_to_archive(msg_ids: List[int]) -> None: move_rows(Attachment, query, message_ids=Literal(tuple(msg_ids))) -def move_attachment_messages_to_archive(msg_ids: List[int]) -> None: +def move_attachment_messages_to_archive(msg_ids: list[int]) -> None: assert len(msg_ids) > 0 query = SQL( @@ -317,7 +317,7 @@ def move_attachment_messages_to_archive(msg_ids: List[int]) -> None: cursor.execute(query, dict(message_ids=tuple(msg_ids))) -def delete_messages(msg_ids: List[int]) -> None: +def delete_messages(msg_ids: list[int]) -> None: # Important note: This also deletes related objects with a foreign # key to Message (due to `on_delete=CASCADE` in our models # configuration), so we need to be sure we've taken care of @@ -339,7 +339,7 @@ def delete_expired_attachments(realm: Realm) -> None: logger.info("Cleaned up %s attachments for realm %s", num_deleted, realm.string_id) -def move_related_objects_to_archive(msg_ids: List[int]) -> None: +def move_related_objects_to_archive(msg_ids: list[int]) -> None: move_models_with_message_key_to_archive(msg_ids) move_attachments_to_archive(msg_ids) move_attachment_messages_to_archive(msg_ids) @@ -363,13 +363,13 @@ def archive_direct_messages(realm: Realm, chunk_size: int = MESSAGE_BATCH_SIZE) def archive_stream_messages( - realm: Realm, streams: List[Stream], chunk_size: int = STREAM_MESSAGE_BATCH_SIZE + realm: Realm, streams: list[Stream], chunk_size: int = STREAM_MESSAGE_BATCH_SIZE ) -> None: if not streams: return # nocoverage # TODO logger.info("Archiving stream messages for realm %s", realm.string_id) - retention_policy_dict: Dict[int, int] = {} + retention_policy_dict: dict[int, int] = {} for stream in streams: # if stream.message_retention_days is null, use the realm's policy if stream.message_retention_days: @@ -404,7 +404,7 @@ def archive_messages(chunk_size: int = MESSAGE_BATCH_SIZE) -> None: delete_expired_attachments(realm) -def get_realms_and_streams_for_archiving() -> List[Tuple[Realm, List[Stream]]]: +def get_realms_and_streams_for_archiving() -> list[tuple[Realm, list[Stream]]]: """ This function constructs a list of (realm, streams_of_the_realm) tuples where each realm is a Realm that requires calling the archiving functions on it, @@ -415,7 +415,7 @@ def get_realms_and_streams_for_archiving() -> List[Tuple[Realm, List[Stream]]]: """ realm_id_to_realm = {} - realm_id_to_streams_list: Dict[int, List[Stream]] = {} + realm_id_to_streams_list: dict[int, list[Stream]] = {} # All realms with a retention policy set qualify for archiving: for realm in Realm.objects.exclude(message_retention_days=-1): @@ -455,7 +455,7 @@ def get_realms_and_streams_for_archiving() -> List[Tuple[Realm, List[Stream]]]: def move_messages_to_archive( - message_ids: List[int], realm: Optional[Realm] = None, chunk_size: int = MESSAGE_BATCH_SIZE + message_ids: list[int], realm: Optional[Realm] = None, chunk_size: int = MESSAGE_BATCH_SIZE ) -> None: # Uses index: zerver_message_pkey query = SQL( @@ -488,7 +488,7 @@ def move_messages_to_archive( ).delete() -def restore_messages_from_archive(archive_transaction_id: int) -> List[int]: +def restore_messages_from_archive(archive_transaction_id: int) -> list[int]: query = SQL( """ INSERT INTO zerver_message ({dst_fields}) diff --git a/zerver/lib/scheduled_messages.py b/zerver/lib/scheduled_messages.py index 4f6b4219bf..9245cf3163 100644 --- a/zerver/lib/scheduled_messages.py +++ b/zerver/lib/scheduled_messages.py @@ -1,4 +1,4 @@ -from typing import List, Union +from typing import Union from django.utils.translation import gettext as _ @@ -21,7 +21,7 @@ def access_scheduled_message( def get_undelivered_scheduled_messages( user_profile: UserProfile, -) -> List[Union[APIScheduledDirectMessageDict, APIScheduledStreamMessageDict]]: +) -> list[Union[APIScheduledDirectMessageDict, APIScheduledStreamMessageDict]]: scheduled_messages = ScheduledMessage.objects.filter( realm_id=user_profile.realm_id, sender=user_profile, @@ -30,7 +30,7 @@ def get_undelivered_scheduled_messages( delivered=False, delivery_type=ScheduledMessage.SEND_LATER, ).order_by("scheduled_timestamp") - scheduled_message_dicts: List[ + scheduled_message_dicts: list[ Union[APIScheduledDirectMessageDict, APIScheduledStreamMessageDict] ] = [scheduled_message.to_dict() for scheduled_message in scheduled_messages] return scheduled_message_dicts diff --git a/zerver/lib/scim.py b/zerver/lib/scim.py index 7da62f8331..d01830c1a0 100644 --- a/zerver/lib/scim.py +++ b/zerver/lib/scim.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Dict, List, Optional, Type, Union +from typing import Any, Callable, Optional, Union import django_scim.constants as scim_constants import django_scim.exceptions as scim_exceptions @@ -79,7 +79,7 @@ class ZulipSCIMUser(SCIMUser): """ return self.obj.full_name - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """ Return a ``dict`` conforming to the SCIM User Schema, ready for conversion to a JSON object. @@ -124,7 +124,7 @@ class ZulipSCIMUser(SCIMUser): "meta": self.meta, } - def from_dict(self, d: Dict[str, Any]) -> None: + def from_dict(self, d: dict[str, Any]) -> None: """Consume a dictionary conforming to the SCIM User Schema. The dictionary was originally submitted as JSON by the client in PUT (update a user) and POST (create a new user) requests. A @@ -210,7 +210,7 @@ class ZulipSCIMUser(SCIMUser): def handle_replace( self, path: Optional[AttrPath], - value: Union[str, List[object], Dict[AttrPath, object]], + value: Union[str, list[object], dict[AttrPath, object]], operation: Any, ) -> None: """ @@ -346,8 +346,8 @@ class ZulipSCIMUser(SCIMUser): def get_extra_model_filter_kwargs_getter( - model: Type[models.Model], -) -> Callable[[HttpRequest, Any, Any], Dict[str, object]]: + model: type[models.Model], +) -> Callable[[HttpRequest, Any, Any], dict[str, object]]: """Registered as GET_EXTRA_MODEL_FILTER_KWARGS_GETTER in our SCIM configuration. @@ -367,7 +367,7 @@ def get_extra_model_filter_kwargs_getter( def get_extra_filter_kwargs( request: HttpRequest, *args: Any, **kwargs: Any - ) -> Dict[str, object]: + ) -> dict[str, object]: realm = RequestNotes.get_notes(request).realm assert realm is not None return {"realm_id": realm.id, "is_bot": False} diff --git a/zerver/lib/scim_filter.py b/zerver/lib/scim_filter.py index 80cb5aa689..7bbb101d71 100644 --- a/zerver/lib/scim_filter.py +++ b/zerver/lib/scim_filter.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Tuple +from typing import Optional from django.http import HttpRequest from django_scim.filters import UserFilterQuery @@ -45,7 +45,7 @@ class ZulipUserFilterQuery(UserFilterQuery): joins = ("INNER JOIN zerver_realm ON zerver_realm.id = realm_id",) @classmethod - def get_extras(cls, q: str, request: Optional[HttpRequest] = None) -> Tuple[str, List[object]]: + def get_extras(cls, q: str, request: Optional[HttpRequest] = None) -> tuple[str, list[object]]: """ Return extra SQL and params to be attached to end of current Query's SQL and params. The return format matches the format that should be used diff --git a/zerver/lib/send_email.py b/zerver/lib/send_email.py index 785b894231..dddf54c2f0 100644 --- a/zerver/lib/send_email.py +++ b/zerver/lib/send_email.py @@ -8,7 +8,7 @@ from email.headerregistry import Address from email.parser import Parser from email.policy import default from email.utils import formataddr, parseaddr -from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Union +from typing import Any, Callable, Mapping, Optional, Union import backoff import css_inline @@ -83,8 +83,8 @@ class FromAddress: def build_email( template_prefix: str, - to_user_ids: Optional[List[int]] = None, - to_emails: Optional[List[str]] = None, + to_user_ids: Optional[list[int]] = None, + to_emails: Optional[list[str]] = None, from_name: Optional[str] = None, from_address: Optional[str] = None, reply_to_email: Optional[str] = None, @@ -140,7 +140,7 @@ def build_email( inliner = get_inliner_instance() return inliner.inline(template) - def render_templates() -> Tuple[str, str, str]: + def render_templates() -> tuple[str, str, str]: email_subject = ( loader.render_to_string( template_prefix + ".subject.txt", context=context, using="Jinja2_plaintext" @@ -244,8 +244,8 @@ class NoEmailArgumentError(CommandError): # migration to change or remove any emails in ScheduledEmail. def send_email( template_prefix: str, - to_user_ids: Optional[List[int]] = None, - to_emails: Optional[List[str]] = None, + to_user_ids: Optional[list[int]] = None, + to_emails: Optional[list[str]] = None, from_name: Optional[str] = None, from_address: Optional[str] = None, reply_to_email: Optional[str] = None, @@ -282,7 +282,7 @@ def send_email( if request is not None: cause = f" (triggered from {request.META['REMOTE_ADDR']})" - logging_recipient: Union[str, List[str]] = mail.to + logging_recipient: Union[str, list[str]] = mail.to if realm is not None: logging_recipient = f"{mail.to} in {realm.string_id}" @@ -347,8 +347,8 @@ def initialize_connection(connection: Optional[BaseEmailBackend] = None) -> Base def send_future_email( template_prefix: str, realm: Realm, - to_user_ids: Optional[List[int]] = None, - to_emails: Optional[List[str]] = None, + to_user_ids: Optional[list[int]] = None, + to_emails: Optional[list[str]] = None, from_name: Optional[str] = None, from_address: Optional[str] = None, language: Optional[str] = None, @@ -470,7 +470,7 @@ def clear_scheduled_emails(user_id: int, email_type: Optional[int] = None) -> No item.delete() -def handle_send_email_format_changes(job: Dict[str, Any]) -> None: +def handle_send_email_format_changes(job: dict[str, Any]) -> None: # Reformat any jobs that used the old to_email # and to_user_ids argument formats. if "to_email" in job: @@ -561,7 +561,7 @@ def custom_email_sender( f.write(get_header(subject, parsed_email_template.get("subject"), "subject")) def send_one_email( - context: Dict[str, Any], to_user_id: Optional[int] = None, to_email: Optional[str] = None + context: dict[str, Any], to_user_id: Optional[int] = None, to_email: Optional[str] = None ) -> None: assert to_user_id is not None or to_email is not None with suppress(EmailNotDeliveredError): @@ -583,8 +583,8 @@ def send_custom_email( users: QuerySet[UserProfile], *, dry_run: bool, - options: Dict[str, str], - add_context: Optional[Callable[[Dict[str, object], UserProfile], None]] = None, + options: dict[str, str], + add_context: Optional[Callable[[dict[str, object], UserProfile], None]] = None, distinct_email: bool = False, ) -> QuerySet[UserProfile]: """ @@ -609,7 +609,7 @@ def send_custom_email( else: users = users.order_by("id") for user_profile in users: - context: Dict[str, object] = { + context: dict[str, object] = { "realm": user_profile.realm, "realm_string_id": user_profile.realm.string_id, "realm_url": user_profile.realm.url, @@ -631,8 +631,8 @@ def send_custom_server_email( remote_servers: QuerySet["RemoteZulipServer"], *, dry_run: bool, - options: Dict[str, str], - add_context: Optional[Callable[[Dict[str, object], "RemoteZulipServer"], None]] = None, + options: dict[str, str], + add_context: Optional[Callable[[dict[str, object], "RemoteZulipServer"], None]] = None, ) -> None: assert settings.CORPORATE_ENABLED from corporate.lib.stripe import BILLING_SUPPORT_EMAIL diff --git a/zerver/lib/server_initialization.py b/zerver/lib/server_initialization.py index 7172c3490d..3f8d0af877 100644 --- a/zerver/lib/server_initialization.py +++ b/zerver/lib/server_initialization.py @@ -1,4 +1,4 @@ -from typing import Iterable, Optional, Tuple +from typing import Iterable, Optional from django.conf import settings from django.db import transaction @@ -78,7 +78,7 @@ def create_internal_realm() -> None: def create_users( realm: Realm, - name_list: Iterable[Tuple[str, str]], + name_list: Iterable[tuple[str, str]], tos_version: Optional[str] = None, bot_type: Optional[int] = None, bot_owner: Optional[UserProfile] = None, diff --git a/zerver/lib/sessions.py b/zerver/lib/sessions.py index b34636a84a..4855c0489f 100644 --- a/zerver/lib/sessions.py +++ b/zerver/lib/sessions.py @@ -1,7 +1,7 @@ import logging from datetime import timedelta from importlib import import_module -from typing import Any, List, Mapping, Optional, Protocol, Type, cast +from typing import Any, Mapping, Optional, Protocol, cast from django.conf import settings from django.contrib.auth import SESSION_KEY, get_user_model @@ -15,7 +15,7 @@ from zerver.models.users import get_user_profile_by_id class SessionEngine(Protocol): - SessionStore: Type[SessionBase] + SessionStore: type[SessionBase] session_engine = cast(SessionEngine, import_module(settings.SESSION_ENGINE)) @@ -35,7 +35,7 @@ def get_session_user_id(session: Session) -> Optional[int]: return get_session_dict_user(session.get_decoded()) -def user_sessions(user_profile: UserProfile) -> List[Session]: +def user_sessions(user_profile: UserProfile) -> list[Session]: return [s for s in Session.objects.all() if get_session_user_id(s) == user_profile.id] diff --git a/zerver/lib/singleton_bmemcached.py b/zerver/lib/singleton_bmemcached.py index 8fe00d011b..c3f4043737 100644 --- a/zerver/lib/singleton_bmemcached.py +++ b/zerver/lib/singleton_bmemcached.py @@ -1,6 +1,6 @@ import pickle from functools import lru_cache -from typing import Any, Dict +from typing import Any from django_bmemcached.memcached import BMemcached @@ -10,7 +10,7 @@ def _get_bmemcached(location: str, params: bytes) -> BMemcached: return BMemcached(location, pickle.loads(params)) # noqa: S301 -def SingletonBMemcached(location: str, params: Dict[str, Any]) -> BMemcached: +def SingletonBMemcached(location: str, params: dict[str, Any]) -> BMemcached: # Django instantiates the cache backend per-task to guard against # thread safety issues, but BMemcached is already thread-safe and # does its own per-thread pooling, so make sure we instantiate only diff --git a/zerver/lib/soft_deactivation.py b/zerver/lib/soft_deactivation.py index fa63845a21..42e2a2d240 100644 --- a/zerver/lib/soft_deactivation.py +++ b/zerver/lib/soft_deactivation.py @@ -1,7 +1,7 @@ # Documented in https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html#soft-deactivation import logging from collections import defaultdict -from typing import Any, DefaultDict, Dict, Iterable, List, Optional, Sequence, Set, TypedDict, Union +from typing import Any, Iterable, Optional, Sequence, TypedDict, Union from django.conf import settings from django.db import transaction @@ -38,10 +38,10 @@ class MissingMessageDict(TypedDict): def filter_by_subscription_history( user_profile: UserProfile, - all_stream_messages: DefaultDict[int, List[MissingMessageDict]], - all_stream_subscription_logs: DefaultDict[int, List[RealmAuditLog]], -) -> List[int]: - message_ids: Set[int] = set() + all_stream_messages: defaultdict[int, list[MissingMessageDict]], + all_stream_subscription_logs: defaultdict[int, list[RealmAuditLog]], +) -> list[int]: + message_ids: set[int] = set() for stream_id, stream_messages_raw in all_stream_messages.items(): stream_subscription_logs = all_stream_subscription_logs[stream_id] @@ -184,7 +184,7 @@ def add_missing_messages(user_profile: UserProfile) -> None: .only("id", "event_type", "modified_stream_id", "event_last_message_id") ) - all_stream_subscription_logs: DefaultDict[int, List[RealmAuditLog]] = defaultdict(list) + all_stream_subscription_logs: defaultdict[int, list[RealmAuditLog]] = defaultdict(list) for log in subscription_logs: all_stream_subscription_logs[assert_is_not_none(log.modified_stream_id)].append(log) @@ -222,7 +222,7 @@ def add_missing_messages(user_profile: UserProfile) -> None: .values("id", "recipient__type_id") ) - stream_messages: DefaultDict[int, List[MissingMessageDict]] = defaultdict(list) + stream_messages: defaultdict[int, list[MissingMessageDict]] = defaultdict(list) for msg in new_stream_msgs: stream_messages[msg["recipient__type_id"]].append( MissingMessageDict(id=msg["id"], recipient__type_id=msg["recipient__type_id"]) @@ -268,7 +268,7 @@ def do_soft_deactivate_user(user_profile: UserProfile) -> None: def do_soft_deactivate_users( users: Union[Sequence[UserProfile], QuerySet[UserProfile]], -) -> List[UserProfile]: +) -> list[UserProfile]: BATCH_SIZE = 100 users_soft_deactivated = [] while True: @@ -299,8 +299,8 @@ def do_soft_deactivate_users( def do_auto_soft_deactivate_users( inactive_for_days: int, realm: Optional[Realm] -) -> List[UserProfile]: - filter_kwargs: Dict[str, Realm] = {} +) -> list[UserProfile]: + filter_kwargs: dict[str, Realm] = {} if realm is not None: filter_kwargs = dict(user_profile__realm=realm) users_to_deactivate = get_users_for_soft_deactivation(inactive_for_days, filter_kwargs) @@ -335,7 +335,7 @@ def reactivate_user_if_soft_deactivated(user_profile: UserProfile) -> Union[User def get_users_for_soft_deactivation( inactive_for_days: int, filter_kwargs: Any -) -> List[UserProfile]: +) -> list[UserProfile]: users_activity = list( UserActivity.objects.filter( user_profile__is_active=True, @@ -356,7 +356,7 @@ def get_users_for_soft_deactivation( return users_to_deactivate -def do_soft_activate_users(users: List[UserProfile]) -> List[UserProfile]: +def do_soft_activate_users(users: list[UserProfile]) -> list[UserProfile]: return [ user_activated for user_profile in users @@ -364,7 +364,7 @@ def do_soft_activate_users(users: List[UserProfile]) -> List[UserProfile]: ] -def do_catch_up_soft_deactivated_users(users: Iterable[UserProfile]) -> List[UserProfile]: +def do_catch_up_soft_deactivated_users(users: Iterable[UserProfile]) -> list[UserProfile]: users_caught_up = [] failures = [] for user_profile in users: @@ -401,7 +401,7 @@ def queue_soft_reactivation(user_profile_id: int) -> None: def soft_reactivate_if_personal_notification( user_profile: UserProfile, - unique_triggers: Set[str], + unique_triggers: set[str], mentioned_user_group_members_count: Optional[int], ) -> None: """When we're about to send an email/push notification to a diff --git a/zerver/lib/sounds.py b/zerver/lib/sounds.py index 6ea723a1d8..12ea0c5d88 100644 --- a/zerver/lib/sounds.py +++ b/zerver/lib/sounds.py @@ -1,10 +1,9 @@ import os -from typing import List from zerver.lib.storage import static_path -def get_available_notification_sounds() -> List[str]: +def get_available_notification_sounds() -> list[str]: notification_sounds_path = static_path("audio/notification_sounds") available_notification_sounds = [] diff --git a/zerver/lib/stream_color.py b/zerver/lib/stream_color.py index 9dfcd4971a..b72397280a 100644 --- a/zerver/lib/stream_color.py +++ b/zerver/lib/stream_color.py @@ -1,5 +1,3 @@ -from typing import Dict, List, Set - STREAM_ASSIGNMENT_COLORS = [ "#76ce90", "#fae589", @@ -29,8 +27,8 @@ STREAM_ASSIGNMENT_COLORS = [ def pick_colors( - used_colors: Set[str], color_map: Dict[int, str], recipient_ids: List[int] -) -> Dict[int, str]: + used_colors: set[str], color_map: dict[int, str], recipient_ids: list[int] +) -> dict[int, str]: used_colors = set(used_colors) recipient_ids = sorted(recipient_ids) result = {} diff --git a/zerver/lib/stream_subscription.py b/zerver/lib/stream_subscription.py index 9edc7807b4..dd4180b258 100644 --- a/zerver/lib/stream_subscription.py +++ b/zerver/lib/stream_subscription.py @@ -2,7 +2,7 @@ import itertools from collections import defaultdict from dataclasses import dataclass from operator import itemgetter -from typing import AbstractSet, Any, Collection, Dict, List, Optional, Set +from typing import AbstractSet, Any, Collection, Optional from django.db.models import Q, QuerySet from django_stubs_ext import ValuesQuerySet @@ -19,8 +19,8 @@ class SubInfo: @dataclass class SubscriberPeerInfo: - subscribed_ids: Dict[int, Set[int]] - private_peer_dict: Dict[int, Set[int]] + subscribed_ids: dict[int, set[int]] + private_peer_dict: dict[int, set[int]] def get_active_subscriptions_for_stream_id( @@ -40,7 +40,7 @@ def get_active_subscriptions_for_stream_id( return query -def get_active_subscriptions_for_stream_ids(stream_ids: Set[int]) -> QuerySet[Subscription]: +def get_active_subscriptions_for_stream_ids(stream_ids: set[int]) -> QuerySet[Subscription]: return Subscription.objects.filter( recipient__type=Recipient.STREAM, recipient__type_id__in=stream_ids, @@ -76,7 +76,7 @@ def get_stream_subscriptions_for_user(user_profile: UserProfile) -> QuerySet[Sub ) -def get_used_colors_for_user_ids(user_ids: List[int]) -> Dict[int, Set[str]]: +def get_used_colors_for_user_ids(user_ids: list[int]) -> dict[int, set[str]]: """Fetch which stream colors have already been used for each user in user_ids. Uses an optimized query designed to support picking colors when bulk-adding users to streams, which requires @@ -92,7 +92,7 @@ def get_used_colors_for_user_ids(user_ids: List[int]) -> Dict[int, Set[str]]: .distinct() ) - result: Dict[int, Set[str]] = defaultdict(set) + result: dict[int, set[str]] = defaultdict(set) for row in query: assert row["color"] is not None @@ -102,9 +102,9 @@ def get_used_colors_for_user_ids(user_ids: List[int]) -> Dict[int, Set[str]]: def get_bulk_stream_subscriber_info( - users: List[UserProfile], - streams: List[Stream], -) -> Dict[int, List[SubInfo]]: + users: list[UserProfile], + streams: list[Stream], +) -> dict[int, list[SubInfo]]: stream_ids = {stream.id for stream in streams} subs = Subscription.objects.filter( @@ -117,7 +117,7 @@ def get_bulk_stream_subscriber_info( stream_map = {stream.recipient_id: stream for stream in streams} user_map = {user.id: user for user in users} - result: Dict[int, List[SubInfo]] = {user.id: [] for user in users} + result: dict[int, list[SubInfo]] = {user.id: [] for user in users} for sub in subs: user_id = sub.user_profile_id @@ -141,7 +141,7 @@ def num_subscribers_for_stream_id(stream_id: int) -> int: ).count() -def get_user_ids_for_streams(stream_ids: Set[int]) -> Dict[int, Set[int]]: +def get_user_ids_for_streams(stream_ids: set[int]) -> dict[int, set[int]]: all_subs = ( get_active_subscriptions_for_stream_ids(stream_ids) .values( @@ -155,7 +155,7 @@ def get_user_ids_for_streams(stream_ids: Set[int]) -> Dict[int, Set[int]]: get_stream_id = itemgetter("recipient__type_id") - result: Dict[int, Set[int]] = defaultdict(set) + result: dict[int, set[int]] = defaultdict(set) for stream_id, rows in itertools.groupby(all_subs, get_stream_id): user_ids = {row["user_profile_id"] for row in rows} result[stream_id] = user_ids @@ -163,14 +163,14 @@ def get_user_ids_for_streams(stream_ids: Set[int]) -> Dict[int, Set[int]]: return result -def get_users_for_streams(stream_ids: Set[int]) -> Dict[int, Set[UserProfile]]: +def get_users_for_streams(stream_ids: set[int]) -> dict[int, set[UserProfile]]: all_subs = ( get_active_subscriptions_for_stream_ids(stream_ids) .select_related("user_profile", "recipient") .order_by("recipient__type_id") ) - result: Dict[int, Set[UserProfile]] = defaultdict(set) + result: dict[int, set[UserProfile]] = defaultdict(set) for stream_id, rows in itertools.groupby(all_subs, key=lambda obj: obj.recipient.type_id): users = {row.user_profile for row in rows} result[stream_id] = users @@ -231,7 +231,7 @@ def bulk_get_subscriber_peer_info( def handle_stream_notifications_compatibility( user_profile: Optional[UserProfile], - stream_dict: Dict[str, Any], + stream_dict: dict[str, Any], notification_settings_null: bool, ) -> None: # Old versions of the mobile apps don't support `None` as a @@ -262,7 +262,7 @@ def handle_stream_notifications_compatibility( ) -def subscriber_ids_with_stream_history_access(stream: Stream) -> Set[int]: +def subscriber_ids_with_stream_history_access(stream: Stream) -> set[int]: """Returns the set of active user IDs who can access any message history on this stream (regardless of whether they have a UserMessage) based on the stream's configuration. diff --git a/zerver/lib/stream_topic.py b/zerver/lib/stream_topic.py index 264791b6c6..7c5c87c6f0 100644 --- a/zerver/lib/stream_topic.py +++ b/zerver/lib/stream_topic.py @@ -1,5 +1,3 @@ -from typing import Dict, Set - from zerver.models import UserTopic @@ -15,7 +13,7 @@ class StreamTopicTarget: self.stream_id = stream_id self.topic_name = topic_name - def user_ids_with_visibility_policy(self, visibility_policy: int) -> Set[int]: + def user_ids_with_visibility_policy(self, visibility_policy: int) -> set[int]: query = UserTopic.objects.filter( stream_id=self.stream_id, topic_name__iexact=self.topic_name, @@ -25,8 +23,8 @@ class StreamTopicTarget: ) return {row["user_profile_id"] for row in query} - def user_id_to_visibility_policy_dict(self) -> Dict[int, int]: - user_id_to_visibility_policy: Dict[int, int] = {} + def user_id_to_visibility_policy_dict(self) -> dict[int, int]: + user_id_to_visibility_policy: dict[int, int] = {} query = UserTopic.objects.filter( stream_id=self.stream_id, topic_name__iexact=self.topic_name diff --git a/zerver/lib/stream_traffic.py b/zerver/lib/stream_traffic.py index cacefa3df8..bf1c280148 100644 --- a/zerver/lib/stream_traffic.py +++ b/zerver/lib/stream_traffic.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta -from typing import Dict, Optional, Set +from typing import Optional from django.db.models import Sum from django.utils.timezone import now as timezone_now @@ -9,7 +9,7 @@ from analytics.models import StreamCount from zerver.models import Realm -def get_streams_traffic(stream_ids: Set[int], realm: Realm) -> Optional[Dict[int, int]]: +def get_streams_traffic(stream_ids: set[int], realm: Realm) -> Optional[dict[int, int]]: if realm.is_zephyr_mirror_realm: # We do not need traffic data for streams in zephyr mirroring realm. return None @@ -41,7 +41,7 @@ STREAM_TRAFFIC_CALCULATION_MIN_AGE_DAYS = 7 def get_average_weekly_stream_traffic( - stream_id: int, stream_date_created: datetime, recent_traffic: Dict[int, int] + stream_id: int, stream_date_created: datetime, recent_traffic: dict[int, int] ) -> Optional[int]: try: stream_traffic = recent_traffic[stream_id] diff --git a/zerver/lib/streams.py b/zerver/lib/streams.py index 2386fe0475..24046a54c0 100644 --- a/zerver/lib/streams.py +++ b/zerver/lib/streams.py @@ -1,4 +1,4 @@ -from typing import Collection, Dict, List, Optional, Set, Tuple, TypedDict, Union +from typing import Collection, Optional, TypedDict, Union from django.db import transaction from django.db.models import Exists, OuterRef, Q, QuerySet @@ -119,8 +119,8 @@ def render_stream_description(text: str, realm: Realm) -> str: def send_stream_creation_event( realm: Realm, stream: Stream, - user_ids: List[int], - recent_traffic: Optional[Dict[int, int]] = None, + user_ids: list[int], + recent_traffic: Optional[dict[int, int]] = None, ) -> None: event = dict(type="stream", op="create", streams=[stream_to_dict(stream, recent_traffic)]) send_event_on_commit(realm, event, user_ids) @@ -139,7 +139,7 @@ def create_stream_if_needed( message_retention_days: Optional[int] = None, can_remove_subscribers_group: Optional[UserGroup] = None, acting_user: Optional[UserProfile] = None, -) -> Tuple[Stream, bool]: +) -> tuple[Stream, bool]: history_public_to_subscribers = get_default_value_for_history_public_to_subscribers( realm, invite_only, history_public_to_subscribers ) @@ -199,12 +199,12 @@ def create_stream_if_needed( def create_streams_if_needed( - realm: Realm, stream_dicts: List[StreamDict], acting_user: Optional[UserProfile] = None -) -> Tuple[List[Stream], List[Stream]]: + realm: Realm, stream_dicts: list[StreamDict], acting_user: Optional[UserProfile] = None +) -> tuple[list[Stream], list[Stream]]: """Note that stream_dict["name"] is assumed to already be stripped of whitespace""" - added_streams: List[Stream] = [] - existing_streams: List[Stream] = [] + added_streams: list[Stream] = [] + existing_streams: list[Stream] = [] for stream_dict in stream_dicts: invite_only = stream_dict.get("invite_only", False) stream, created = create_stream_if_needed( @@ -350,7 +350,7 @@ def check_stream_access_for_delete_or_update( def access_stream_for_delete_or_update( user_profile: UserProfile, stream_id: int -) -> Tuple[Stream, Optional[Subscription]]: +) -> tuple[Stream, Optional[Subscription]]: try: stream = Stream.objects.get(id=stream_id) except Stream.DoesNotExist: @@ -436,7 +436,7 @@ def access_stream_by_id( stream_id: int, require_active: bool = True, allow_realm_admin: bool = False, -) -> Tuple[Stream, Optional[Subscription]]: +) -> tuple[Stream, Optional[Subscription]]: error = _("Invalid channel ID") try: stream = get_stream_by_id_in_realm(stream_id, user_profile.realm) @@ -486,7 +486,7 @@ def check_stream_name_available(realm: Realm, name: str) -> None: def access_stream_by_name( user_profile: UserProfile, stream_name: str, allow_realm_admin: bool = False -) -> Tuple[Stream, Optional[Subscription]]: +) -> tuple[Stream, Optional[Subscription]]: error = _("Invalid channel name '{channel_name}'").format(channel_name=stream_name) try: stream = get_realm_stream(stream_name, user_profile.realm_id) @@ -547,14 +547,14 @@ def access_stream_to_remove_visibility_policy_by_id( return stream -def private_stream_user_ids(stream_id: int) -> Set[int]: +def private_stream_user_ids(stream_id: int) -> set[int]: subscriptions = get_active_subscriptions_for_stream_id( stream_id, include_deactivated_users=False ) return {sub["user_profile_id"] for sub in subscriptions.values("user_profile_id")} -def public_stream_user_ids(stream: Stream) -> Set[int]: +def public_stream_user_ids(stream: Stream) -> set[int]: guest_subscriptions = get_active_subscriptions_for_stream_id( stream.id, include_deactivated_users=False ).filter(user_profile__role=UserProfile.ROLE_GUEST) @@ -564,7 +564,7 @@ def public_stream_user_ids(stream: Stream) -> Set[int]: return set(active_non_guest_user_ids(stream.realm_id)) | guest_subscriptions_ids -def can_access_stream_user_ids(stream: Stream) -> Set[int]: +def can_access_stream_user_ids(stream: Stream) -> set[int]: # return user ids of users who can access the attributes of a # stream, such as its name/description. Useful for sending events # to all users with access to a stream's attributes. @@ -645,7 +645,7 @@ def can_remove_subscribers_from_stream( def filter_stream_authorization( user_profile: UserProfile, streams: Collection[Stream] -) -> Tuple[List[Stream], List[Stream]]: +) -> tuple[list[Stream], list[Stream]]: recipient_ids = [stream.recipient_id for stream in streams] subscribed_recipient_ids = set( Subscription.objects.filter( @@ -653,7 +653,7 @@ def filter_stream_authorization( ).values_list("recipient_id", flat=True) ) - unauthorized_streams: List[Stream] = [] + unauthorized_streams: list[Stream] = [] for stream in streams: # The user is authorized for their own streams if stream.recipient_id in subscribed_recipient_ids: @@ -683,7 +683,7 @@ def list_to_streams( autocreate: bool = False, unsubscribing_others: bool = False, is_default_stream: bool = False, -) -> Tuple[List[Stream], List[Stream]]: +) -> tuple[list[Stream], list[Stream]]: """Converts list of dicts to a list of Streams, validating input in the process For each stream name, we validate it to ensure it meets our @@ -707,8 +707,8 @@ def list_to_streams( assert stream_name == stream_name.strip() check_stream_name(stream_name) - existing_streams: List[Stream] = [] - missing_stream_dicts: List[StreamDict] = [] + existing_streams: list[Stream] = [] + missing_stream_dicts: list[StreamDict] = [] existing_stream_map = bulk_get_streams(user_profile.realm, stream_set) if unsubscribing_others: @@ -740,7 +740,7 @@ def list_to_streams( if len(missing_stream_dicts) == 0: # This is the happy path for callers who expected all of these # streams to exist already. - created_streams: List[Stream] = [] + created_streams: list[Stream] = [] else: # autocreate=True path starts here for stream_dict in missing_stream_dicts: @@ -852,7 +852,7 @@ def get_occupied_streams(realm: Realm) -> QuerySet[Stream]: def stream_to_dict( - stream: Stream, recent_traffic: Optional[Dict[int, int]] = None + stream: Stream, recent_traffic: Optional[dict[int, int]] = None ) -> APIStreamDict: if recent_traffic is not None: stream_weekly_traffic = get_average_weekly_stream_traffic( @@ -886,7 +886,7 @@ def stream_to_dict( ) -def get_web_public_streams(realm: Realm) -> List[APIStreamDict]: # nocoverage +def get_web_public_streams(realm: Realm) -> list[APIStreamDict]: # nocoverage query = get_web_public_streams_queryset(realm) streams = query.only(*Stream.API_FIELDS) stream_dicts = [stream_to_dict(stream) for stream in streams] @@ -900,7 +900,7 @@ def get_streams_for_user( include_subscribed: bool = True, include_all_active: bool = False, include_owner_subscribed: bool = False, -) -> List[Stream]: +) -> list[Stream]: if include_all_active and not user_profile.is_realm_admin: raise JsonableError(_("User not authorized for this query")) @@ -964,7 +964,7 @@ def do_get_streams( include_all_active: bool = False, include_default: bool = False, include_owner_subscribed: bool = False, -) -> List[APIStreamDict]: +) -> list[APIStreamDict]: # This function is only used by API clients now. streams = get_streams_for_user( diff --git a/zerver/lib/subscription_info.py b/zerver/lib/subscription_info.py index ae31267285..6759c5b43e 100644 --- a/zerver/lib/subscription_info.py +++ b/zerver/lib/subscription_info.py @@ -1,6 +1,6 @@ import itertools from operator import itemgetter -from typing import Any, Callable, Collection, Dict, Iterable, List, Mapping, Optional, Set, Tuple +from typing import Any, Callable, Collection, Iterable, Mapping, Optional from django.core.exceptions import ValidationError from django.db import connection @@ -140,7 +140,7 @@ def build_stream_dict_for_sub( user: UserProfile, sub_dict: RawSubscriptionDict, raw_stream_dict: RawStreamDict, - recent_traffic: Optional[Dict[int, int]], + recent_traffic: Optional[dict[int, int]], ) -> SubscriptionStreamDict: # Handle Stream.API_FIELDS can_remove_subscribers_group_id = raw_stream_dict["can_remove_subscribers_group_id"] @@ -215,7 +215,7 @@ def build_stream_dict_for_sub( def build_stream_dict_for_never_sub( raw_stream_dict: RawStreamDict, - recent_traffic: Optional[Dict[int, int]], + recent_traffic: Optional[dict[int, int]], ) -> NeverSubscribedStreamDict: can_remove_subscribers_group_id = raw_stream_dict["can_remove_subscribers_group_id"] creator_id = raw_stream_dict["creator_id"] @@ -342,8 +342,8 @@ def validate_user_access_to_subscribers_helper( def bulk_get_subscriber_user_ids( stream_dicts: Collection[Mapping[str, Any]], user_profile: UserProfile, - subscribed_stream_ids: Set[int], -) -> Dict[int, List[int]]: + subscribed_stream_ids: set[int], +) -> dict[int, list[int]]: """sub_dict maps stream_id => whether the user is subscribed to that stream.""" target_stream_dicts = [] is_subscribed: bool @@ -366,7 +366,7 @@ def bulk_get_subscriber_user_ids( recip_to_stream_id = {stream["recipient_id"]: stream["id"] for stream in target_stream_dicts} recipient_ids = sorted(stream["recipient_id"] for stream in target_stream_dicts) - result: Dict[int, List[int]] = {stream["id"]: [] for stream in stream_dicts} + result: dict[int, list[int]] = {stream["id"]: [] for stream in stream_dicts} if not recipient_ids: return result @@ -456,10 +456,10 @@ def gather_subscriptions_helper( "realm_id", "recipient_id", ) - recip_id_to_stream_id: Dict[int, int] = { + recip_id_to_stream_id: dict[int, int] = { stream["recipient_id"]: stream["id"] for stream in all_streams } - all_streams_map: Dict[int, RawStreamDict] = {stream["id"]: stream for stream in all_streams} + all_streams_map: dict[int, RawStreamDict] = {stream["id"]: stream for stream in all_streams} sub_dicts_query: Iterable[RawSubscriptionDict] = ( get_stream_subscriptions_for_user(user_profile) @@ -472,7 +472,7 @@ def gather_subscriptions_helper( ) # We only care about subscriptions for active streams. - sub_dicts: List[RawSubscriptionDict] = [ + sub_dicts: list[RawSubscriptionDict] = [ sub_dict for sub_dict in sub_dicts_query if recip_id_to_stream_id.get(sub_dict["recipient_id"]) @@ -486,9 +486,9 @@ def gather_subscriptions_helper( # Okay, now we finally get to populating our main results, which # will be these three lists. - subscribed: List[SubscriptionStreamDict] = [] - unsubscribed: List[SubscriptionStreamDict] = [] - never_subscribed: List[NeverSubscribedStreamDict] = [] + subscribed: list[SubscriptionStreamDict] = [] + unsubscribed: list[SubscriptionStreamDict] = [] + never_subscribed: list[NeverSubscribedStreamDict] = [] sub_unsub_stream_ids = set() for sub_dict in sub_dicts: @@ -575,7 +575,7 @@ def gather_subscriptions_helper( def gather_subscriptions( user_profile: UserProfile, include_subscribers: bool = False, -) -> Tuple[List[SubscriptionStreamDict], List[SubscriptionStreamDict]]: +) -> tuple[list[SubscriptionStreamDict], list[SubscriptionStreamDict]]: helper_result = gather_subscriptions_helper( user_profile, include_subscribers=include_subscribers, diff --git a/zerver/lib/templates.py b/zerver/lib/templates.py index 65a235435e..63ce43efd6 100644 --- a/zerver/lib/templates.py +++ b/zerver/lib/templates.py @@ -1,5 +1,5 @@ import time -from typing import Any, Dict, List, Optional +from typing import Any, Optional import markdown import markdown.extensions.admonition @@ -29,7 +29,7 @@ from zerver.lib.cache import dict_to_items_tuple, ignore_unhashable_lru_cache, i register = Library() -def and_n_others(values: List[str], limit: int) -> str: +def and_n_others(values: list[str], limit: int) -> str: # A helper for the commonly appended "and N other(s)" string, with # the appropriate pluralization. return " and {} other{}".format( @@ -39,7 +39,7 @@ def and_n_others(values: List[str], limit: int) -> str: @register.filter(name="display_list", is_safe=True) -def display_list(values: List[str], display_limit: int) -> str: +def display_list(values: list[str], display_limit: int) -> str: """ Given a list of values, return a string nicely formatting those values, summarizing when you have more than `display_limit`. Eg, for a @@ -66,7 +66,7 @@ def display_list(values: List[str], display_limit: int) -> str: return display_string -md_extensions: Optional[List[markdown.Extension]] = None +md_extensions: Optional[list[markdown.Extension]] = None md_macro_extension: Optional[markdown.Extension] = None # Prevent the automatic substitution of macros in these docs. If # they contain a macro, it is always used literally for documenting @@ -85,7 +85,7 @@ docs_without_macros = [ @register.filter(name="render_markdown_path", is_safe=True) def render_markdown_path( markdown_file_path: str, - context: Optional[Dict[str, Any]] = None, + context: Optional[dict[str, Any]] = None, integration_doc: bool = False, help_center: bool = False, ) -> str: @@ -177,7 +177,7 @@ def render_markdown_path( return mark_safe(jinja.from_string(html).render(context)) # noqa: S308 -def webpack_entry(entrypoint: str) -> List[str]: +def webpack_entry(entrypoint: str) -> list[str]: while True: with open(settings.WEBPACK_STATS_FILE, "rb") as f: stats = orjson.loads(f.read()) diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py index 3269234381..33c9e56074 100644 --- a/zerver/lib/test_classes.py +++ b/zerver/lib/test_classes.py @@ -11,14 +11,10 @@ from typing import ( Any, Callable, Collection, - Dict, Iterator, - List, Mapping, Optional, Sequence, - Set, - Tuple, Union, cast, ) @@ -206,7 +202,7 @@ class ZulipTestCaseMixin(SimpleTestCase): @override def setUp(self) -> None: super().setUp() - self.API_KEYS: Dict[str, str] = {} + self.API_KEYS: dict[str, str] = {} test_name = self.id() bounce_key_prefix_for_testing(test_name) @@ -291,7 +287,7 @@ Output: token=r"[a-z0-9_]{24}" ) - def set_http_headers(self, extra: Dict[str, str], skip_user_agent: bool = False) -> None: + def set_http_headers(self, extra: dict[str, str], skip_user_agent: bool = False) -> None: if "subdomain" in extra: assert isinstance(extra["subdomain"], str) extra["HTTP_HOST"] = Realm.host_for_subdomain(extra["subdomain"]) @@ -688,7 +684,7 @@ Output: result = self.client_post("/json/bots", bot_info) self.assert_json_error(result, assert_json_error_msg) - def _get_page_params(self, result: "TestHttpResponse") -> Dict[str, Any]: + def _get_page_params(self, result: "TestHttpResponse") -> dict[str, Any]: """Helper for parsing page_params after fetching the web app's home view.""" doc = lxml.html.document_fromstring(result.content) div = cast(lxml.html.HtmlMixin, doc).get_element_by_id("page-params") @@ -698,7 +694,7 @@ Output: page_params = orjson.loads(page_params_json) return page_params - def _get_sentry_params(self, response: "TestHttpResponse") -> Optional[Dict[str, Any]]: + def _get_sentry_params(self, response: "TestHttpResponse") -> Optional[dict[str, Any]]: doc = lxml.html.document_fromstring(response.content) try: script = cast(lxml.html.HtmlMixin, doc).get_element_by_id("sentry-params") @@ -1061,7 +1057,7 @@ Output: **extra, ) - def get_streams(self, user_profile: UserProfile) -> List[str]: + def get_streams(self, user_profile: UserProfile) -> list[str]: """ Helper function to get the active stream names for a user """ @@ -1097,7 +1093,7 @@ Output: def send_group_direct_message( self, from_user: UserProfile, - to_users: List[UserProfile], + to_users: list[UserProfile], content: str = "test content", *, read_by_sender: bool = True, @@ -1169,7 +1165,7 @@ Output: num_after: int = 100, use_first_unread_anchor: bool = False, include_anchor: bool = True, - ) -> Dict[str, List[Dict[str, Any]]]: + ) -> dict[str, list[dict[str, Any]]]: post_params = { "anchor": anchor, "num_before": num_before, @@ -1187,18 +1183,18 @@ Output: num_before: int = 100, num_after: int = 100, use_first_unread_anchor: bool = False, - ) -> List[Dict[str, Any]]: + ) -> list[dict[str, Any]]: data = self.get_messages_response(anchor, num_before, num_after, use_first_unread_anchor) return data["messages"] - def users_subscribed_to_stream(self, stream_name: str, realm: Realm) -> List[UserProfile]: + def users_subscribed_to_stream(self, stream_name: str, realm: Realm) -> list[UserProfile]: stream = Stream.objects.get(name=stream_name, realm=realm) recipient = Recipient.objects.get(type_id=stream.id, type=Recipient.STREAM) subscriptions = Subscription.objects.filter(recipient=recipient, active=True) return [subscription.user_profile for subscription in subscriptions] - def not_long_term_idle_subscriber_ids(self, stream_name: str, realm: Realm) -> Set[int]: + def not_long_term_idle_subscriber_ids(self, stream_name: str, realm: Realm) -> set[int]: stream = Stream.objects.get(name=stream_name, realm=realm) recipient = Recipient.objects.get(type_id=stream.id, type=Recipient.STREAM) @@ -1213,8 +1209,8 @@ Output: self, result: Union["TestHttpResponse", HttpResponse], *, - ignored_parameters: Optional[List[str]] = None, - ) -> Dict[str, Any]: + ignored_parameters: Optional[list[str]] = None, + ) -> dict[str, Any]: """ Successful POSTs return a 200 and JSON of the form {"result": "success", "msg": ""}. @@ -1311,7 +1307,7 @@ Output: self.assertIn(substring, response.content.decode()) def assert_in_success_response( - self, substrings: List[str], response: Union["TestHttpResponse", HttpResponse] + self, substrings: list[str], response: Union["TestHttpResponse", HttpResponse] ) -> None: self.assertEqual(response.status_code, 200) decoded = response.content.decode() @@ -1319,7 +1315,7 @@ Output: self.assertIn(substring, decoded) def assert_not_in_success_response( - self, substrings: List[str], response: Union["TestHttpResponse", HttpResponse] + self, substrings: list[str], response: Union["TestHttpResponse", HttpResponse] ) -> None: self.assertEqual(response.status_code, 200) decoded = response.content.decode() @@ -1438,14 +1434,14 @@ Output: def common_subscribe_to_streams( self, user: UserProfile, - subscriptions_raw: List[str] | List[Dict[str, str]], + subscriptions_raw: list[str] | list[dict[str, str]], extra_post_data: Mapping[str, Any] = {}, invite_only: bool = False, is_web_public: bool = False, allow_fail: bool = False, **extra: str, ) -> "TestHttpResponse": - subscriptions: List[Dict[str, str]] = [] + subscriptions: list[dict[str, str]] = [] for entry in subscriptions_raw: if isinstance(entry, str): subscriptions.append({"name": entry}) @@ -1479,7 +1475,7 @@ Output: return "".join(sorted(f" * {stream['name']}\n" for stream in subscribed_streams)) - def check_user_subscribed_only_to_streams(self, user_name: str, streams: List[Stream]) -> None: + def check_user_subscribed_only_to_streams(self, user_name: str, streams: list[Stream]) -> None: streams = sorted(streams, key=lambda x: x.name) subscribed_streams = gather_subscriptions(self.nonreg_user(user_name))[0] @@ -1512,7 +1508,7 @@ Output: self, user_profile: UserProfile, url: str, - payload: Union[str, Dict[str, Any]], + payload: Union[str, dict[str, Any]], **extra: str, ) -> Message: """ @@ -1610,11 +1606,11 @@ Output: os.makedirs(output_dir, exist_ok=True) return output_dir - def get_set(self, data: List[Dict[str, Any]], field: str) -> Set[str]: + def get_set(self, data: list[dict[str, Any]], field: str) -> set[str]: values = {r[field] for r in data} return values - def find_by_id(self, data: List[Dict[str, Any]], db_id: int) -> Dict[str, Any]: + def find_by_id(self, data: list[dict[str, Any]], db_id: int) -> dict[str, Any]: [r] = (r for r in data if r["id"] == db_id) return r @@ -1803,7 +1799,7 @@ Output: def subscribe_realm_to_manual_license_management_plan( self, realm: Realm, licenses: int, licenses_at_next_renewal: int, billing_schedule: int - ) -> Tuple[CustomerPlan, LicenseLedger]: + ) -> tuple[CustomerPlan, LicenseLedger]: customer, _ = Customer.objects.get_or_create(realm=realm) plan = CustomerPlan.objects.create( customer=customer, @@ -1825,7 +1821,7 @@ Output: def subscribe_realm_to_monthly_plan_on_manual_license_management( self, realm: Realm, licenses: int, licenses_at_next_renewal: int - ) -> Tuple[CustomerPlan, LicenseLedger]: + ) -> tuple[CustomerPlan, LicenseLedger]: return self.subscribe_realm_to_manual_license_management_plan( realm, licenses, licenses_at_next_renewal, CustomerPlan.BILLING_SCHEDULE_MONTHLY ) @@ -1874,7 +1870,7 @@ Output: def get_maybe_enqueue_notifications_parameters( self, *, message_id: int, user_id: int, acting_user_id: int, **kwargs: Any - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """ Returns a dictionary with the passed parameters, after filling up the missing data with default values, for testing what was passed to the @@ -1999,8 +1995,8 @@ Output: def create_or_update_anonymous_group_for_setting( self, - direct_members: List[UserProfile], - direct_subgroups: List[NamedUserGroup], + direct_members: list[UserProfile], + direct_subgroups: list[NamedUserGroup], existing_setting_group: Optional[UserGroup] = None, ) -> UserGroup: realm = get_realm("zulip") @@ -2019,8 +2015,8 @@ class ZulipTestCase(ZulipTestCaseMixin, TestCase): @contextmanager def capture_send_event_calls( self, expected_num_events: int - ) -> Iterator[List[Mapping[str, Any]]]: - lst: List[Mapping[str, Any]] = [] + ) -> Iterator[list[Mapping[str, Any]]]: + lst: list[Mapping[str, Any]] = [] # process_notification takes a single parameter called 'notice'. # lst.append takes a single argument called 'object'. @@ -2082,7 +2078,7 @@ class ZulipTestCase(ZulipTestCaseMixin, TestCase): def send_group_direct_message( self, from_user: UserProfile, - to_users: List[UserProfile], + to_users: list[UserProfile], content: str = "test content", *, read_by_sender: bool = True, @@ -2168,7 +2164,7 @@ class ZulipTestCase(ZulipTestCaseMixin, TestCase): return message_id -def get_row_ids_in_all_tables() -> Iterator[Tuple[str, Set[int]]]: +def get_row_ids_in_all_tables() -> Iterator[tuple[str, set[int]]]: all_models = apps.get_models(include_auto_created=True) ignored_tables = {"django_session"} @@ -2475,7 +2471,7 @@ one or more new messages. return url[:-1] if has_arguments else url - def get_payload(self, fixture_name: str) -> Union[str, Dict[str, str]]: + def get_payload(self, fixture_name: str) -> Union[str, dict[str, str]]: """ Generally webhooks that override this should return dicts.""" return self.get_body(fixture_name) @@ -2510,8 +2506,8 @@ class MigrationsTestCase(ZulipTransactionTestCase): # nocoverage assert ( self.migrate_from and self.migrate_to ), f"TestCase '{type(self).__name__}' must define migrate_from and migrate_to properties" - migrate_from: List[Tuple[str, str]] = [(self.app, self.migrate_from)] - migrate_to: List[Tuple[str, str]] = [(self.app, self.migrate_to)] + migrate_from: list[tuple[str, str]] = [(self.app, self.migrate_from)] + migrate_to: list[tuple[str, str]] = [(self.app, self.migrate_to)] executor = MigrationExecutor(connection) old_apps = executor.loader.project_state(migrate_from).apps @@ -2531,7 +2527,7 @@ class MigrationsTestCase(ZulipTransactionTestCase): # nocoverage pass # nocoverage -def get_topic_messages(user_profile: UserProfile, stream: Stream, topic_name: str) -> List[Message]: +def get_topic_messages(user_profile: UserProfile, stream: Stream, topic_name: str) -> list[Message]: query = UserMessage.objects.filter( user_profile=user_profile, message__recipient=stream.recipient, @@ -2558,7 +2554,7 @@ class BouncerTestCase(ZulipTestCase): RemoteZulipServer.objects.filter(uuid=self.server_uuid).delete() super().tearDown() - def request_callback(self, request: PreparedRequest) -> Tuple[int, ResponseHeaders, bytes]: + def request_callback(self, request: PreparedRequest) -> tuple[int, ResponseHeaders, bytes]: kwargs = {} if isinstance(request.body, bytes): # send_json_to_push_bouncer sends the body as bytes containing json. @@ -2566,7 +2562,7 @@ class BouncerTestCase(ZulipTestCase): kwargs = dict(content_type="application/json") else: assert isinstance(request.body, str) or request.body is None - params: Dict[str, List[str]] = parse_qs(request.body) + params: dict[str, list[str]] = parse_qs(request.body) # In Python 3, the values of the dict from `parse_qs` are # in a list, because there might be multiple values. # But since we are sending values with no same keys, hence @@ -2588,7 +2584,7 @@ class BouncerTestCase(ZulipTestCase): responses.add_callback(responses.POST, COMPILED_URL, callback=self.request_callback) responses.add_callback(responses.GET, COMPILED_URL, callback=self.request_callback) - def get_generic_payload(self, method: str = "register") -> Dict[str, Any]: + def get_generic_payload(self, method: str = "register") -> dict[str, Any]: user_id = 10 token = "111222" token_kind = PushDeviceToken.FCM diff --git a/zerver/lib/test_console_output.py b/zerver/lib/test_console_output.py index 1661843dca..cb0a58e16b 100644 --- a/zerver/lib/test_console_output.py +++ b/zerver/lib/test_console_output.py @@ -5,7 +5,7 @@ import sys from contextlib import contextmanager from io import SEEK_SET, TextIOWrapper from types import TracebackType -from typing import IO, TYPE_CHECKING, Iterable, Iterator, List, Optional, Type +from typing import IO, TYPE_CHECKING, Iterable, Iterator, Optional from typing_extensions import override @@ -95,7 +95,7 @@ class WrappedIO(IO[bytes]): return self.stream.readline(limit) @override - def readlines(self, hint: int = -1) -> List[bytes]: + def readlines(self, hint: int = -1) -> list[bytes]: return self.stream.readlines(hint) @override @@ -147,7 +147,7 @@ class WrappedIO(IO[bytes]): @override def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[TracebackType], ) -> None: diff --git a/zerver/lib/test_fixtures.py b/zerver/lib/test_fixtures.py index e659c748ba..2cd5742762 100644 --- a/zerver/lib/test_fixtures.py +++ b/zerver/lib/test_fixtures.py @@ -8,7 +8,7 @@ import sys import time from importlib import import_module from io import StringIO -from typing import Any, List, Set +from typing import Any from django.apps import apps from django.conf import settings @@ -60,7 +60,7 @@ VERBOSE_MESSAGE_ABOUT_HASH_TRANSITION = """ """ -def migration_paths() -> List[str]: +def migration_paths() -> list[str]: return [ *glob.glob("*/migrations/*.py"), "requirements/dev.txt", @@ -79,7 +79,7 @@ class Database: ) self.migration_digest_file = "migrations_hash_" + database_name - def important_settings(self) -> List[str]: + def important_settings(self) -> list[str]: def get(setting_name: str) -> str: value = getattr(settings, setting_name, {}) return json.dumps(value, sort_keys=True) @@ -318,7 +318,7 @@ def get_migration_status(**options: Any) -> str: return re.sub(r"\x1b\[(1|0)m", "", output) -def extract_migrations_as_list(migration_status: str) -> List[str]: +def extract_migrations_as_list(migration_status: str) -> list[str]: MIGRATIONS_RE = re.compile(r"\[[X| ]\] (\d+_.+)\n") return MIGRATIONS_RE.findall(migration_status) @@ -339,7 +339,7 @@ def destroy_leaked_test_databases(expiry_time: int = 60 * 60) -> int: while also ensuring we will eventually delete all leaked databases. """ files = glob.glob(os.path.join(UUID_VAR_DIR, TEMPLATE_DATABASE_DIR, "*")) - test_databases: Set[str] = set() + test_databases: set[str] = set() try: with connection.cursor() as cursor: cursor.execute("SELECT datname FROM pg_database;") @@ -350,7 +350,7 @@ def destroy_leaked_test_databases(expiry_time: int = 60 * 60) -> int: except ProgrammingError: pass - databases_in_use: Set[str] = set() + databases_in_use: set[str] = set() for file in files: if round(time.time()) - os.path.getmtime(file) < expiry_time: with open(file) as f: diff --git a/zerver/lib/test_helpers.py b/zerver/lib/test_helpers.py index 888cc629bd..b3519309df 100644 --- a/zerver/lib/test_helpers.py +++ b/zerver/lib/test_helpers.py @@ -11,13 +11,10 @@ from typing import ( TYPE_CHECKING, Any, Callable, - Dict, Iterable, Iterator, - List, Mapping, Optional, - Tuple, TypeVar, Union, cast, @@ -91,17 +88,17 @@ def stub_event_queue_user_events( @contextmanager -def cache_tries_captured() -> Iterator[List[Tuple[str, Union[str, List[str]], Optional[str]]]]: - cache_queries: List[Tuple[str, Union[str, List[str]], Optional[str]]] = [] +def cache_tries_captured() -> Iterator[list[tuple[str, Union[str, list[str]], Optional[str]]]]: + cache_queries: list[tuple[str, Union[str, list[str]], Optional[str]]] = [] orig_get = cache.cache_get orig_get_many = cache.cache_get_many - def my_cache_get(key: str, cache_name: Optional[str] = None) -> Optional[Dict[str, Any]]: + def my_cache_get(key: str, cache_name: Optional[str] = None) -> Optional[dict[str, Any]]: cache_queries.append(("get", key, cache_name)) return orig_get(key, cache_name) - def my_cache_get_many(keys: List[str], cache_name: Optional[str] = None) -> Dict[str, Any]: + def my_cache_get_many(keys: list[str], cache_name: Optional[str] = None) -> dict[str, Any]: cache_queries.append(("getmany", keys, cache_name)) return orig_get_many(keys, cache_name) @@ -110,16 +107,16 @@ def cache_tries_captured() -> Iterator[List[Tuple[str, Union[str, List[str]], Op @contextmanager -def simulated_empty_cache() -> Iterator[List[Tuple[str, Union[str, List[str]], Optional[str]]]]: - cache_queries: List[Tuple[str, Union[str, List[str]], Optional[str]]] = [] +def simulated_empty_cache() -> Iterator[list[tuple[str, Union[str, list[str]], Optional[str]]]]: + cache_queries: list[tuple[str, Union[str, list[str]], Optional[str]]] = [] - def my_cache_get(key: str, cache_name: Optional[str] = None) -> Optional[Dict[str, Any]]: + def my_cache_get(key: str, cache_name: Optional[str] = None) -> Optional[dict[str, Any]]: cache_queries.append(("get", key, cache_name)) return None def my_cache_get_many( - keys: List[str], cache_name: Optional[str] = None - ) -> Dict[str, Any]: # nocoverage -- simulated code doesn't use this + keys: list[str], cache_name: Optional[str] = None + ) -> dict[str, Any]: # nocoverage -- simulated code doesn't use this cache_queries.append(("getmany", keys, cache_name)) return {} @@ -136,13 +133,13 @@ class CapturedQuery: @contextmanager def queries_captured( include_savepoints: bool = False, keep_cache_warm: bool = False -) -> Iterator[List[CapturedQuery]]: +) -> Iterator[list[CapturedQuery]]: """ Allow a user to capture just the queries executed during the with statement. """ - queries: List[CapturedQuery] = [] + queries: list[CapturedQuery] = [] def cursor_execute(self: TimeTrackingCursor, sql: Query, vars: Optional[Params] = None) -> None: start = time.time() @@ -298,7 +295,7 @@ def get_subscription(stream_name: str, user_profile: UserProfile) -> Subscriptio ) -def get_user_messages(user_profile: UserProfile) -> List[Message]: +def get_user_messages(user_profile: UserProfile) -> list[Message]: query = ( UserMessage.objects.select_related("message") .filter(user_profile=user_profile) @@ -334,7 +331,7 @@ class HostRequestMock(HttpRequest): remote_server: Optional[RemoteZulipServer] = None, host: str = settings.EXTERNAL_HOST, client_name: Optional[str] = None, - meta_data: Optional[Dict[str, Any]] = None, + meta_data: Optional[dict[str, Any]] = None, tornado_handler: Optional[AsyncDjangoHandler] = None, path: str = "", ) -> None: @@ -377,12 +374,12 @@ class HostRequestMock(HttpRequest): INSTRUMENTING = os.environ.get("TEST_INSTRUMENT_URL_COVERAGE", "") == "TRUE" -INSTRUMENTED_CALLS: List[Dict[str, Any]] = [] +INSTRUMENTED_CALLS: list[dict[str, Any]] = [] UrlFuncT = TypeVar("UrlFuncT", bound=Callable[..., HttpResponseBase]) # TODO: make more specific -def append_instrumentation_data(data: Dict[str, Any]) -> None: +def append_instrumentation_data(data: dict[str, Any]) -> None: INSTRUMENTED_CALLS.append(data) @@ -438,7 +435,7 @@ def write_instrumentation_reports(full_suite: bool, include_webhooks: bool) -> N from zproject.urls import urlpatterns, v1_api_and_json_patterns # Find our untested urls. - pattern_cnt: Dict[str, int] = collections.defaultdict(int) + pattern_cnt: dict[str, int] = collections.defaultdict(int) def re_strip(r: str) -> str: assert r.startswith(r"^") @@ -448,7 +445,7 @@ def write_instrumentation_reports(full_suite: bool, include_webhooks: bool) -> N assert r.endswith(r"\Z") return r[1:-2] - def find_patterns(patterns: List[Any], prefixes: List[str]) -> None: + def find_patterns(patterns: list[Any], prefixes: list[str]) -> None: for pattern in patterns: find_pattern(pattern, prefixes) @@ -463,7 +460,7 @@ def write_instrumentation_reports(full_suite: bool, include_webhooks: bool) -> N url = url[len("http://testserver:9080/") :] return url - def find_pattern(pattern: Any, prefixes: List[str]) -> None: + def find_pattern(pattern: Any, prefixes: list[str]) -> None: if isinstance(pattern, type(URLResolver)): return # nocoverage -- shouldn't actually happen @@ -575,7 +572,7 @@ def use_s3_backend(method: Callable[P, None]) -> Callable[P, None]: return new_method -def create_s3_buckets(*bucket_names: str) -> List[Bucket]: +def create_s3_buckets(*bucket_names: str) -> list[Bucket]: session = boto3.session.Session(settings.S3_KEY, settings.S3_SECRET_KEY) s3 = session.resource("s3") buckets = [s3.create_bucket(Bucket=name) for name in bucket_names] @@ -705,7 +702,7 @@ def create_dummy_file(filename: str) -> str: return filepath -def zulip_reaction_info() -> Dict[str, str]: +def zulip_reaction_info() -> dict[str, str]: return dict( emoji_name="zulip", emoji_code="zulip", @@ -725,7 +722,7 @@ def mock_queue_publish( # crash in production. def verify_serialize( queue_name: str, - event: Dict[str, object], + event: dict[str, object], processor: Optional[Callable[[object], None]] = None, ) -> None: marshalled_event = orjson.loads(orjson.dumps(event)) diff --git a/zerver/lib/test_runner.py b/zerver/lib/test_runner.py index 68d8e33c57..7f458eb688 100644 --- a/zerver/lib/test_runner.py +++ b/zerver/lib/test_runner.py @@ -3,7 +3,7 @@ import os import random import shutil import unittest -from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, Union +from typing import Any, Callable, Iterable, Optional, Union from unittest import TestSuite, runner from unittest.result import TestResult @@ -56,9 +56,9 @@ class TextTestResult(runner.TextTestResult): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - self.failed_tests: List[str] = [] + self.failed_tests: list[str] = [] - def addInstrumentation(self, test: unittest.TestCase, data: Dict[str, Any]) -> None: + def addInstrumentation(self, test: unittest.TestCase, data: dict[str, Any]) -> None: append_instrumentation_data(data) @override @@ -96,7 +96,7 @@ class RemoteTestResult(django_runner.RemoteTestResult): base class. """ - def addInstrumentation(self, test: unittest.TestCase, data: Dict[str, Any]) -> None: + def addInstrumentation(self, test: unittest.TestCase, data: dict[str, Any]) -> None: # Some elements of data['info'] cannot be serialized. if "info" in data: del data["info"] @@ -104,16 +104,16 @@ class RemoteTestResult(django_runner.RemoteTestResult): self.events.append(("addInstrumentation", self.test_index, data)) -def process_instrumented_calls(func: Callable[[Dict[str, Any]], None]) -> None: +def process_instrumented_calls(func: Callable[[dict[str, Any]], None]) -> None: for call in test_helpers.INSTRUMENTED_CALLS: func(call) -SerializedSubsuite: TypeAlias = Tuple[Type[TestSuite], List[str]] -SubsuiteArgs: TypeAlias = Tuple[Type["RemoteTestRunner"], int, SerializedSubsuite, bool, bool] +SerializedSubsuite: TypeAlias = tuple[type[TestSuite], list[str]] +SubsuiteArgs: TypeAlias = tuple[type["RemoteTestRunner"], int, SerializedSubsuite, bool, bool] -def run_subsuite(args: SubsuiteArgs) -> Tuple[int, Any]: +def run_subsuite(args: SubsuiteArgs) -> tuple[int, Any]: # Reset the accumulated INSTRUMENTED_CALLS before running this subsuite. test_helpers.INSTRUMENTED_CALLS = [] # The first argument is the test runner class but we don't need it @@ -181,12 +181,12 @@ def create_test_databases(worker_id: int) -> None: def init_worker( counter: "multiprocessing.sharedctypes.Synchronized[int]", - initial_settings: Optional[Dict[str, Any]] = None, - serialized_contents: Optional[Dict[str, str]] = None, + initial_settings: Optional[dict[str, Any]] = None, + serialized_contents: Optional[dict[str, str]] = None, process_setup: Optional[Callable[..., None]] = None, - process_setup_args: Optional[Tuple[Any, ...]] = None, + process_setup_args: Optional[tuple[Any, ...]] = None, debug_mode: Optional[bool] = None, - used_aliases: Optional[Set[str]] = None, + used_aliases: Optional[set[str]] = None, ) -> None: """ This function runs only under parallel mode. It initializes the @@ -259,17 +259,17 @@ class Runner(DiscoverRunner): # `templates_rendered` holds templates which were rendered # in proper logical tests. - self.templates_rendered: Set[str] = set() + self.templates_rendered: set[str] = set() # `shallow_tested_templates` holds templates which were rendered # in `zerver.tests.test_templates`. - self.shallow_tested_templates: Set[str] = set() + self.shallow_tested_templates: set[str] = set() template_rendered.connect(self.on_template_rendered) @override - def get_resultclass(self) -> Optional[Type[TextTestResult]]: + def get_resultclass(self) -> Optional[type[TextTestResult]]: return TextTestResult - def on_template_rendered(self, sender: Any, context: Dict[str, Any], **kwargs: Any) -> None: + def on_template_rendered(self, sender: Any, context: dict[str, Any], **kwargs: Any) -> None: if hasattr(sender, "template"): template_name = sender.template.name if template_name not in self.templates_rendered: @@ -279,7 +279,7 @@ class Runner(DiscoverRunner): self.templates_rendered.add(template_name) self.shallow_tested_templates.discard(template_name) - def get_shallow_tested_templates(self) -> Set[str]: + def get_shallow_tested_templates(self) -> set[str]: return self.shallow_tested_templates @override @@ -334,7 +334,7 @@ class Runner(DiscoverRunner): return super().teardown_test_environment(*args, **kwargs) def test_imports( - self, test_labels: List[str], suite: Union[TestSuite, ParallelTestSuite] + self, test_labels: list[str], suite: Union[TestSuite, ParallelTestSuite] ) -> None: prefix = "unittest.loader._FailedTest." for test_name in get_test_names(suite): @@ -359,7 +359,7 @@ class Runner(DiscoverRunner): @override def run_tests( self, - test_labels: List[str], + test_labels: list[str], failed_tests_path: Optional[str] = None, full_suite: bool = False, include_webhooks: bool = False, @@ -396,7 +396,7 @@ class Runner(DiscoverRunner): return failed -def get_test_names(suite: Union[TestSuite, ParallelTestSuite]) -> List[str]: +def get_test_names(suite: Union[TestSuite, ParallelTestSuite]) -> list[str]: if isinstance(suite, ParallelTestSuite): return [name for subsuite in suite.subsuites for name in get_test_names(subsuite)] else: diff --git a/zerver/lib/thumbnail.py b/zerver/lib/thumbnail.py index ed9781e607..f219739e6e 100644 --- a/zerver/lib/thumbnail.py +++ b/zerver/lib/thumbnail.py @@ -1,7 +1,7 @@ import logging import os from contextlib import contextmanager -from typing import Iterator, Optional, Tuple +from typing import Iterator, Optional from urllib.parse import urljoin import pyvips @@ -132,7 +132,7 @@ def resize_logo(image_data: bytes) -> bytes: def resize_emoji( image_data: bytes, emoji_file_name: str, size: int = DEFAULT_EMOJI_SIZE -) -> Tuple[bytes, Optional[bytes]]: +) -> tuple[bytes, Optional[bytes]]: # Square brackets are used for providing options to libvips' save # operation; the extension on the filename comes from reversing # the content-type, which removes most of the attacker control of diff --git a/zerver/lib/timeout.py b/zerver/lib/timeout.py index 43278ee655..dc55524b5c 100644 --- a/zerver/lib/timeout.py +++ b/zerver/lib/timeout.py @@ -4,7 +4,7 @@ import sys import threading import time from types import TracebackType -from typing import Callable, Optional, Tuple, Type, TypeVar +from typing import Callable, Optional, TypeVar from typing_extensions import override @@ -42,8 +42,8 @@ def unsafe_timeout(timeout: float, func: Callable[[], ResultT]) -> ResultT: def __init__(self) -> None: threading.Thread.__init__(self) self.result: Optional[ResultT] = None - self.exc_info: Tuple[ - Optional[Type[BaseException]], + self.exc_info: tuple[ + Optional[type[BaseException]], Optional[BaseException], Optional[TracebackType], ] = (None, None, None) diff --git a/zerver/lib/timezone.py b/zerver/lib/timezone.py index 049b8d3fe0..48d2daef5a 100644 --- a/zerver/lib/timezone.py +++ b/zerver/lib/timezone.py @@ -1,11 +1,10 @@ from functools import cache -from typing import Dict from scripts.lib.zulip_tools import get_tzdata_zi @cache -def get_canonical_timezone_map() -> Dict[str, str]: +def get_canonical_timezone_map() -> dict[str, str]: canonical = {} with get_tzdata_zi() as f: for line in f: diff --git a/zerver/lib/topic.py b/zerver/lib/topic.py index 281b3db431..b87b049a80 100644 --- a/zerver/lib/topic.py +++ b/zerver/lib/topic.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Any, Callable, Dict, List, Optional, Set, Tuple +from typing import Any, Callable, Optional import orjson from django.db import connection @@ -30,7 +30,7 @@ where we'll want to support "subject" for a while. """ -def get_topic_from_message_info(message_info: Dict[str, Any]) -> str: +def get_topic_from_message_info(message_info: dict[str, Any]) -> str: """ Use this where you are getting dicts that are based off of messages that may come from the outside world, especially from third party @@ -119,7 +119,7 @@ def update_edit_history( ) -> None: message.last_edit_time = last_edit_time if message.edit_history is not None: - edit_history: List[EditHistoryEvent] = orjson.loads(message.edit_history) + edit_history: list[EditHistoryEvent] = orjson.loads(message.edit_history) edit_history.insert(0, edit_history_event) else: edit_history = [edit_history_event] @@ -136,7 +136,7 @@ def update_messages_for_topic_edit( old_stream: Stream, edit_history_event: EditHistoryEvent, last_edit_time: datetime, -) -> Tuple[QuerySet[Message], Callable[[], QuerySet[Message]]]: +) -> tuple[QuerySet[Message], Callable[[], QuerySet[Message]]]: # Uses index: zerver_message_realm_recipient_upper_subject messages = Message.objects.filter( realm_id=old_stream.realm_id, @@ -161,7 +161,7 @@ def update_messages_for_topic_edit( # to keep topics together. pass - update_fields: Dict[str, object] = { + update_fields: dict[str, object] = { "last_edit_time": last_edit_time, # We cast the `edit_history` column to jsonb (defaulting NULL # to `[]`), apply the `||` array concatenation operator to it, @@ -215,8 +215,8 @@ def update_messages_for_topic_edit( return messages, propagate -def generate_topic_history_from_db_rows(rows: List[Tuple[str, int]]) -> List[Dict[str, Any]]: - canonical_topic_names: Dict[str, Tuple[int, str]] = {} +def generate_topic_history_from_db_rows(rows: list[tuple[str, int]]) -> list[dict[str, Any]]: + canonical_topic_names: dict[str, tuple[int, str]] = {} # Sort rows by max_message_id so that if a topic # has many different casings, we use the most @@ -235,7 +235,7 @@ def generate_topic_history_from_db_rows(rows: List[Tuple[str, int]]) -> List[Dic return sorted(history, key=lambda x: -x["max_id"]) -def get_topic_history_for_public_stream(realm_id: int, recipient_id: int) -> List[Dict[str, Any]]: +def get_topic_history_for_public_stream(realm_id: int, recipient_id: int) -> list[dict[str, Any]]: cursor = connection.cursor() # Uses index: zerver_message_realm_recipient_subject # Note that this is *case-sensitive*, so that we can display the @@ -263,7 +263,7 @@ def get_topic_history_for_public_stream(realm_id: int, recipient_id: int) -> Lis def get_topic_history_for_stream( user_profile: UserProfile, recipient_id: int, public_history: bool -) -> List[Dict[str, Any]]: +) -> list[dict[str, Any]]: if public_history: return get_topic_history_for_public_stream(user_profile.realm_id, recipient_id) @@ -296,7 +296,7 @@ def get_topic_history_for_stream( return generate_topic_history_from_db_rows(rows) -def get_topic_resolution_and_bare_name(stored_name: str) -> Tuple[bool, str]: +def get_topic_resolution_and_bare_name(stored_name: str) -> tuple[bool, str]: """ Resolved topics are denoted only by a title change, not by a boolean toggle in a database column. This method inspects the topic name and returns a tuple of: @@ -310,7 +310,7 @@ def get_topic_resolution_and_bare_name(stored_name: str) -> Tuple[bool, str]: return (False, stored_name) -def participants_for_topic(realm_id: int, recipient_id: int, topic_name: str) -> Set[int]: +def participants_for_topic(realm_id: int, recipient_id: int, topic_name: str) -> set[int]: """ Users who either sent or reacted to the messages in the topic. The function is expensive for large numbers of messages in the topic. diff --git a/zerver/lib/typed_endpoint.py b/zerver/lib/typed_endpoint.py index d69e321cb3..9a60c14276 100644 --- a/zerver/lib/typed_endpoint.py +++ b/zerver/lib/typed_endpoint.py @@ -4,7 +4,7 @@ import types from dataclasses import dataclass from enum import Enum, auto from functools import wraps -from typing import Callable, Generic, List, Optional, Sequence, Tuple, Type, TypeVar, Union +from typing import Callable, Generic, Optional, Sequence, TypeVar, Union from django.http import HttpRequest from django.utils.translation import gettext as _ @@ -93,7 +93,7 @@ class ApiParamConfig: path_only: bool = False argument_type_is_body: bool = False documentation_status: DocumentationStatus = DOCUMENTED - aliases: Tuple[str, ...] = () + aliases: tuple[str, ...] = () # TypeAliases for common Annotated types @@ -138,7 +138,7 @@ class FuncParam(Generic[T]): param_name: str # Inspected the underlying type of the parameter by unwrapping the Annotated # type if there is one. - param_type: Type[T] + param_type: type[T] # The Pydantic TypeAdapter used to parse arbitrary input to the desired type. # We store it on the FuncParam object as soon as the view function is # decorated because it is expensive to construct. @@ -149,7 +149,7 @@ class FuncParam(Generic[T]): # annotation associated with this param: # Name of the corresponding variable in the request data to look # for. When argument_type_is_body is True, this is set to "request". - aliases: Tuple[str, ...] + aliases: tuple[str, ...] argument_type_is_body: bool documentation_status: DocumentationStatus path_only: bool @@ -162,12 +162,12 @@ class ViewFuncInfo: parameters: Sequence[FuncParam[object]] -def is_annotated(type_annotation: Type[object]) -> bool: +def is_annotated(type_annotation: type[object]) -> bool: origin = get_origin(type_annotation) return origin is Annotated -def is_optional(type_annotation: Type[object]) -> bool: +def is_optional(type_annotation: type[object]) -> bool: origin = get_origin(type_annotation) type_args = get_args(type_annotation) return origin in (Union, types.UnionType) and type(None) in type_args and len(type_args) == 2 @@ -203,7 +203,7 @@ API_PARAM_CONFIG_USAGE_HINT = f""" def parse_single_parameter( - param_name: str, param_type: Type[T], parameter: inspect.Parameter + param_name: str, param_type: type[T], parameter: inspect.Parameter ) -> FuncParam[T]: param_default = parameter.default # inspect._empty is the internal type used by inspect to indicate not @@ -291,7 +291,7 @@ def parse_view_func_signature( parameters = inspect.signature(view_func).parameters view_func_full_name = f"{view_func.__module__}.{view_func.__name__}" - process_parameters: List[FuncParam[object]] = [] + process_parameters: list[FuncParam[object]] = [] for param_name, parameter in parameters.items(): assert param_name in type_hints diff --git a/zerver/lib/typed_endpoint_validators.py b/zerver/lib/typed_endpoint_validators.py index 8b3dd4589c..286e90bc4a 100644 --- a/zerver/lib/typed_endpoint_validators.py +++ b/zerver/lib/typed_endpoint_validators.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Optional from django.core.exceptions import ValidationError from django.core.validators import URLValidator @@ -21,13 +21,13 @@ def check_string_fixed_length(string: str, length: int) -> Optional[str]: return string -def check_string_in(val: str, possible_values: List[str]) -> str: +def check_string_in(val: str, possible_values: list[str]) -> str: if val not in possible_values: raise ValueError(_("Not in the list of possible values")) return val -def check_int_in(val: int, possible_values: List[int]) -> int: +def check_int_in(val: int, possible_values: list[int]) -> int: if val not in possible_values: raise ValueError(_("Not in the list of possible values")) return val diff --git a/zerver/lib/types.py b/zerver/lib/types.py index b68bf7bcdf..fee4e2433e 100644 --- a/zerver/lib/types.py +++ b/zerver/lib/types.py @@ -1,6 +1,6 @@ from dataclasses import dataclass, field from datetime import datetime -from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union +from typing import Any, Callable, Optional, TypeVar, Union from django_stubs_ext import StrPromise from typing_extensions import NotRequired, TypeAlias, TypedDict @@ -10,10 +10,10 @@ from typing_extensions import NotRequired, TypeAlias, TypedDict ResultT = TypeVar("ResultT") Validator: TypeAlias = Callable[[str, object], ResultT] ExtendedValidator: TypeAlias = Callable[[str, str, object], str] -RealmUserValidator: TypeAlias = Callable[[int, object, bool], List[int]] +RealmUserValidator: TypeAlias = Callable[[int, object, bool], list[int]] -ProfileDataElementValue: TypeAlias = Union[str, List[int]] +ProfileDataElementValue: TypeAlias = Union[str, list[int]] class ProfileDataElementBase(TypedDict, total=False): @@ -37,17 +37,17 @@ class ProfileDataElementUpdateDict(TypedDict): value: ProfileDataElementValue -ProfileData: TypeAlias = List[ProfileDataElement] +ProfileData: TypeAlias = list[ProfileDataElement] -FieldElement: TypeAlias = Tuple[ +FieldElement: TypeAlias = tuple[ int, StrPromise, Validator[ProfileDataElementValue], Callable[[Any], Any], str ] -ExtendedFieldElement: TypeAlias = Tuple[ +ExtendedFieldElement: TypeAlias = tuple[ int, StrPromise, ExtendedValidator, Callable[[Any], Any], str ] -UserFieldElement: TypeAlias = Tuple[int, StrPromise, RealmUserValidator, Callable[[Any], Any], str] +UserFieldElement: TypeAlias = tuple[int, StrPromise, RealmUserValidator, Callable[[Any], Any], str] -ProfileFieldData: TypeAlias = Dict[str, Union[Dict[str, str], str]] +ProfileFieldData: TypeAlias = dict[str, Union[dict[str, str], str]] class UserDisplayRecipient(TypedDict): @@ -57,7 +57,7 @@ class UserDisplayRecipient(TypedDict): is_mirror_dummy: bool -DisplayRecipientT: TypeAlias = Union[str, List[UserDisplayRecipient]] +DisplayRecipientT: TypeAlias = Union[str, list[UserDisplayRecipient]] class LinkifierDict(TypedDict): @@ -200,7 +200,7 @@ class SubscriptionStreamDict(TypedDict): stream_id: int stream_post_policy: int stream_weekly_traffic: Optional[int] - subscribers: NotRequired[List[int]] + subscribers: NotRequired[list[int]] wildcard_mentions_notify: Optional[bool] @@ -220,7 +220,7 @@ class NeverSubscribedStreamDict(TypedDict): stream_id: int stream_post_policy: int stream_weekly_traffic: Optional[int] - subscribers: NotRequired[List[int]] + subscribers: NotRequired[list[int]] class DefaultStreamDict(TypedDict): @@ -266,14 +266,14 @@ class APISubscriptionDict(APIStreamDict): wildcard_mentions_notify: Optional[bool] # Computed fields not specified in `Subscription.API_FIELDS` in_home_view: bool - subscribers: List[int] + subscribers: list[int] @dataclass class SubscriptionInfo: - subscriptions: List[SubscriptionStreamDict] - unsubscribed: List[SubscriptionStreamDict] - never_subscribed: List[NeverSubscribedStreamDict] + subscriptions: list[SubscriptionStreamDict] + unsubscribed: list[SubscriptionStreamDict] + never_subscribed: list[NeverSubscribedStreamDict] class RealmPlaygroundDict(TypedDict): @@ -293,14 +293,14 @@ class GroupPermissionSetting: default_group_name: str id_field_name: str default_for_system_groups: Optional[str] = None - allowed_system_groups: List[str] = field(default_factory=list) + allowed_system_groups: list[str] = field(default_factory=list) @dataclass class ServerSupportedPermissionSettings: - realm: Dict[str, GroupPermissionSetting] - stream: Dict[str, GroupPermissionSetting] - group: Dict[str, GroupPermissionSetting] + realm: dict[str, GroupPermissionSetting] + stream: dict[str, GroupPermissionSetting] + group: dict[str, GroupPermissionSetting] class RawUserDict(TypedDict): diff --git a/zerver/lib/upload/__init__.py b/zerver/lib/upload/__init__.py index 90d185019e..5e088c3199 100644 --- a/zerver/lib/upload/__init__.py +++ b/zerver/lib/upload/__init__.py @@ -4,7 +4,7 @@ import os import re import unicodedata from datetime import datetime -from typing import IO, Any, BinaryIO, Callable, Iterator, List, Optional, Tuple, Union +from typing import IO, Any, BinaryIO, Callable, Iterator, Optional, Union from urllib.parse import unquote, urljoin from django.conf import settings @@ -65,7 +65,7 @@ def create_attachment( notify_attachment_update(user_profile, "add", attachment.to_dict()) -def get_file_info(user_file: UploadedFile) -> Tuple[str, str]: +def get_file_info(user_file: UploadedFile) -> tuple[str, str]: uploaded_file_name = user_file.name assert uploaded_file_name is not None @@ -191,11 +191,11 @@ def delete_message_attachment(path_id: str) -> bool: return upload_backend.delete_message_attachment(path_id) -def delete_message_attachments(path_ids: List[str]) -> None: +def delete_message_attachments(path_ids: list[str]) -> None: return upload_backend.delete_message_attachments(path_ids) -def all_message_attachments() -> Iterator[Tuple[str, datetime]]: +def all_message_attachments() -> Iterator[tuple[str, datetime]]: return upload_backend.all_message_attachments() @@ -377,7 +377,7 @@ def upload_emoji_image( def get_emoji_file_content( session: OutgoingSession, emoji_url: str, emoji_id: int, logger: logging.Logger -) -> Tuple[bytes, str]: # nocoverage +) -> tuple[bytes, str]: # nocoverage original_emoji_url = emoji_url + ".original" logger.info("Downloading %s", original_emoji_url) diff --git a/zerver/lib/upload/base.py b/zerver/lib/upload/base.py index edab31bfc9..770da1c8db 100644 --- a/zerver/lib/upload/base.py +++ b/zerver/lib/upload/base.py @@ -1,6 +1,6 @@ import os from datetime import datetime -from typing import IO, Any, BinaryIO, Callable, Iterator, List, Optional, Tuple +from typing import IO, Any, BinaryIO, Callable, Iterator, Optional from zerver.models import Realm, UserProfile @@ -49,18 +49,18 @@ class ZulipUploadBackend: def delete_message_attachment(self, path_id: str) -> bool: raise NotImplementedError - def delete_message_attachments(self, path_ids: List[str]) -> None: + def delete_message_attachments(self, path_ids: list[str]) -> None: for path_id in path_ids: self.delete_message_attachment(path_id) - def all_message_attachments(self) -> Iterator[Tuple[str, datetime]]: + def all_message_attachments(self) -> Iterator[tuple[str, datetime]]: raise NotImplementedError # Avatar image uploads def get_avatar_url(self, hash_key: str, medium: bool = False) -> str: raise NotImplementedError - def get_avatar_contents(self, file_path: str) -> Tuple[bytes, str]: + def get_avatar_contents(self, file_path: str) -> tuple[bytes, str]: raise NotImplementedError def get_avatar_path(self, hash_key: str, medium: bool = False) -> str: diff --git a/zerver/lib/upload/local.py b/zerver/lib/upload/local.py index f53fe675af..8ce6c57bf0 100644 --- a/zerver/lib/upload/local.py +++ b/zerver/lib/upload/local.py @@ -4,7 +4,7 @@ import random import secrets import shutil from datetime import datetime -from typing import IO, Any, BinaryIO, Callable, Iterator, Literal, Optional, Tuple +from typing import IO, Any, BinaryIO, Callable, Iterator, Literal, Optional from django.conf import settings from typing_extensions import override @@ -95,7 +95,7 @@ class LocalUploadBackend(ZulipUploadBackend): return delete_local_file("files", path_id) @override - def all_message_attachments(self) -> Iterator[Tuple[str, datetime]]: + def all_message_attachments(self) -> Iterator[tuple[str, datetime]]: assert settings.LOCAL_UPLOADS_DIR is not None for dirname, _, files in os.walk(settings.LOCAL_UPLOADS_DIR + "/files"): for f in files: @@ -110,7 +110,7 @@ class LocalUploadBackend(ZulipUploadBackend): return "/user_avatars/" + self.get_avatar_path(hash_key, medium) @override - def get_avatar_contents(self, file_path: str) -> Tuple[bytes, str]: + def get_avatar_contents(self, file_path: str) -> tuple[bytes, str]: image_data = read_local_file("avatars", file_path + ".original") content_type = guess_type(file_path)[0] return image_data, content_type or "application/octet-stream" diff --git a/zerver/lib/upload/s3.py b/zerver/lib/upload/s3.py index b454242db6..9f0d126004 100644 --- a/zerver/lib/upload/s3.py +++ b/zerver/lib/upload/s3.py @@ -2,7 +2,7 @@ import logging import os import secrets from datetime import datetime -from typing import IO, Any, BinaryIO, Callable, Dict, Iterator, List, Literal, Optional, Tuple +from typing import IO, Any, BinaryIO, Callable, Iterator, Literal, Optional from urllib.parse import urljoin, urlsplit, urlunsplit import boto3 @@ -75,7 +75,7 @@ def upload_image_to_s3( "STANDARD_IA", ] = "STANDARD", cache_control: Optional[str] = None, - extra_metadata: Optional[Dict[str, str]] = None, + extra_metadata: Optional[dict[str, str]] = None, ) -> None: key = bucket.Object(file_name) metadata = { @@ -233,13 +233,13 @@ class S3UploadBackend(ZulipUploadBackend): return self.delete_file_from_s3(path_id, self.uploads_bucket) @override - def delete_message_attachments(self, path_ids: List[str]) -> None: + def delete_message_attachments(self, path_ids: list[str]) -> None: self.uploads_bucket.delete_objects( Delete={"Objects": [{"Key": path_id} for path_id in path_ids]} ) @override - def all_message_attachments(self) -> Iterator[Tuple[str, datetime]]: + def all_message_attachments(self) -> Iterator[tuple[str, datetime]]: client = self.uploads_bucket.meta.client paginator = client.get_paginator("list_objects_v2") page_iterator = paginator.paginate(Bucket=self.uploads_bucket.name) @@ -257,7 +257,7 @@ class S3UploadBackend(ZulipUploadBackend): return self.get_public_upload_url(self.get_avatar_path(hash_key, medium)) @override - def get_avatar_contents(self, file_path: str) -> Tuple[bytes, str]: + def get_avatar_contents(self, file_path: str) -> tuple[bytes, str]: key = self.avatar_bucket.Object(file_path + ".original") image_data = key.get()["Body"].read() content_type = key.content_type diff --git a/zerver/lib/url_encoding.py b/zerver/lib/url_encoding.py index 25e22cb9db..ca112ab70b 100644 --- a/zerver/lib/url_encoding.py +++ b/zerver/lib/url_encoding.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List +from typing import Any from urllib.parse import quote, urlsplit import re2 @@ -29,7 +29,7 @@ def personal_narrow_url(*, realm: Realm, sender: UserProfile) -> str: def direct_message_group_narrow_url( - *, user: UserProfile, display_recipient: List[UserDisplayRecipient] + *, user: UserProfile, display_recipient: list[UserDisplayRecipient] ) -> str: realm = user.realm other_user_ids = [r["id"] for r in display_recipient if r["id"] != user.id] @@ -48,7 +48,7 @@ def topic_narrow_url(*, realm: Realm, stream: Stream, topic_name: str) -> str: return f"{base_url}{encode_stream(stream.id, stream.name)}/topic/{hash_util_encode(topic_name)}" -def near_message_url(realm: Realm, message: Dict[str, Any]) -> str: +def near_message_url(realm: Realm, message: dict[str, Any]) -> str: if message["type"] == "stream": url = near_stream_message_url( realm=realm, @@ -63,7 +63,7 @@ def near_message_url(realm: Realm, message: Dict[str, Any]) -> str: return url -def near_stream_message_url(realm: Realm, message: Dict[str, Any]) -> str: +def near_stream_message_url(realm: Realm, message: dict[str, Any]) -> str: message_id = str(message["id"]) stream_id = message["stream_id"] stream_name = message["display_recipient"] @@ -85,7 +85,7 @@ def near_stream_message_url(realm: Realm, message: Dict[str, Any]) -> str: return full_url -def near_pm_message_url(realm: Realm, message: Dict[str, Any]) -> str: +def near_pm_message_url(realm: Realm, message: dict[str, Any]) -> str: message_id = str(message["id"]) str_user_ids = [str(recipient["id"]) for recipient in message["display_recipient"]] diff --git a/zerver/lib/url_redirects.py b/zerver/lib/url_redirects.py index f6863adb00..de1a292e31 100644 --- a/zerver/lib/url_redirects.py +++ b/zerver/lib/url_redirects.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from typing import List @dataclass @@ -8,18 +7,18 @@ class URLRedirect: new_url: str -API_DOCUMENTATION_REDIRECTS: List[URLRedirect] = [ +API_DOCUMENTATION_REDIRECTS: list[URLRedirect] = [ # Add URL redirects for REST API documentation here: URLRedirect("/api/delete-stream", "/api/archive-stream"), ] -POLICY_DOCUMENTATION_REDIRECTS: List[URLRedirect] = [ +POLICY_DOCUMENTATION_REDIRECTS: list[URLRedirect] = [ # Add URL redirects for policy documentation here: URLRedirect("/privacy/", "/policies/privacy"), URLRedirect("/terms/", "/policies/terms"), ] -HELP_DOCUMENTATION_REDIRECTS: List[URLRedirect] = [ +HELP_DOCUMENTATION_REDIRECTS: list[URLRedirect] = [ # Add URL redirects for help center documentation here: URLRedirect("/help/pm-mention-alert-notifications", "/help/dm-mention-alert-notifications"), URLRedirect("/help/restrict-private-messages", "/help/restrict-direct-messages"), diff --git a/zerver/lib/user_agent.py b/zerver/lib/user_agent.py index 28ec2720d4..abb90b699b 100644 --- a/zerver/lib/user_agent.py +++ b/zerver/lib/user_agent.py @@ -1,5 +1,4 @@ import re -from typing import Dict # Warning: If you change this parsing, please test using # zerver/tests/test_decorators.py @@ -13,7 +12,7 @@ pattern = re.compile( ) -def parse_user_agent(user_agent: str) -> Dict[str, str]: +def parse_user_agent(user_agent: str) -> dict[str, str]: match = pattern.match(user_agent) assert match is not None return match.groupdict() diff --git a/zerver/lib/user_counts.py b/zerver/lib/user_counts.py index 9add4d77b8..7405da677a 100644 --- a/zerver/lib/user_counts.py +++ b/zerver/lib/user_counts.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from django.db.models import Count @@ -9,7 +9,7 @@ def realm_user_count(realm: Realm) -> int: return UserProfile.objects.filter(realm=realm, is_active=True, is_bot=False).count() -def realm_user_count_by_role(realm: Realm) -> Dict[str, Any]: +def realm_user_count_by_role(realm: Realm) -> dict[str, Any]: human_counts = { str(UserProfile.ROLE_REALM_ADMINISTRATOR): 0, str(UserProfile.ROLE_REALM_OWNER): 0, diff --git a/zerver/lib/user_groups.py b/zerver/lib/user_groups.py index 5c2bc4a5ae..7fcefe296f 100644 --- a/zerver/lib/user_groups.py +++ b/zerver/lib/user_groups.py @@ -1,6 +1,6 @@ from contextlib import contextmanager from dataclasses import asdict, dataclass -from typing import Collection, Dict, Iterable, Iterator, List, Mapping, Optional, TypedDict, Union +from typing import Collection, Iterable, Iterator, Mapping, Optional, TypedDict, Union from django.conf import settings from django.db import connection, transaction @@ -32,8 +32,8 @@ from zerver.models.groups import SystemGroups @dataclass class AnonymousSettingGroupDict: - direct_members: List[int] - direct_subgroups: List[int] + direct_members: list[int] + direct_subgroups: list[int] @dataclass @@ -46,8 +46,8 @@ class UserGroupDict(TypedDict): id: int name: str description: str - members: List[int] - direct_subgroup_ids: List[int] + members: list[int] + direct_subgroup_ids: list[int] is_system_group: bool can_mention_group: Union[int, AnonymousSettingGroupDict] @@ -63,8 +63,8 @@ class LockedUserGroupContext: """ supergroup: NamedUserGroup - direct_subgroups: List[NamedUserGroup] - recursive_subgroups: List[NamedUserGroup] + direct_subgroups: list[NamedUserGroup] + recursive_subgroups: list[NamedUserGroup] def has_user_group_access( @@ -252,8 +252,8 @@ def check_setting_configuration_for_system_groups( def update_or_create_user_group_for_setting( realm: Realm, - direct_members: List[int], - direct_subgroups: List[int], + direct_members: list[int], + direct_subgroups: list[int], current_setting_value: Optional[UserGroup], ) -> UserGroup: if current_setting_value is not None and not hasattr(current_setting_value, "named_user_group"): @@ -345,7 +345,7 @@ def get_group_setting_value_for_api( ) -def user_groups_in_realm_serialized(realm: Realm) -> List[UserGroupDict]: +def user_groups_in_realm_serialized(realm: Realm) -> list[UserGroupDict]: """This function is used in do_events_register code path so this code should be performant. We need to do 2 database queries because Django's ORM doesn't properly support the left join between @@ -371,7 +371,7 @@ def user_groups_in_realm_serialized(realm: Realm) -> List[UserGroupDict]: .filter(realm=realm) ) - group_dicts: Dict[int, UserGroupDict] = {} + group_dicts: dict[int, UserGroupDict] = {} for user_group in realm_groups: group_dicts[user_group.id] = dict( id=user_group.id, @@ -406,7 +406,7 @@ def user_groups_in_realm_serialized(realm: Realm) -> List[UserGroupDict]: return sorted(group_dicts.values(), key=lambda group_dict: group_dict["id"]) -def get_direct_user_groups(user_profile: UserProfile) -> List[UserGroup]: +def get_direct_user_groups(user_profile: UserProfile) -> list[UserGroup]: return list(user_profile.direct_groups.all()) @@ -422,7 +422,7 @@ def get_user_group_direct_members(user_group: UserGroup) -> QuerySet[UserProfile return user_group.direct_members.all() -def get_direct_memberships_of_users(user_group: UserGroup, members: List[UserProfile]) -> List[int]: +def get_direct_memberships_of_users(user_group: UserGroup, members: list[UserProfile]) -> list[int]: return list( UserGroupMembership.objects.filter( user_group=user_group, user_profile__in=members @@ -497,7 +497,7 @@ def is_any_user_in_group( def get_user_group_member_ids( user_group: UserGroup, *, direct_member_only: bool = False -) -> List[int]: +) -> list[int]: if direct_member_only: member_ids: Iterable[int] = get_user_group_direct_member_ids(user_group) else: @@ -506,7 +506,7 @@ def get_user_group_member_ids( return list(member_ids) -def get_subgroup_ids(user_group: UserGroup, *, direct_subgroup_only: bool = False) -> List[int]: +def get_subgroup_ids(user_group: UserGroup, *, direct_subgroup_only: bool = False) -> list[int]: if direct_subgroup_only: subgroup_ids = user_group.direct_subgroups.all().values_list("id", flat=True) else: @@ -529,7 +529,7 @@ def get_recursive_subgroups_for_groups( return recursive_subgroups -def get_role_based_system_groups_dict(realm: Realm) -> Dict[str, NamedUserGroup]: +def get_role_based_system_groups_dict(realm: Realm) -> dict[str, NamedUserGroup]: system_groups = NamedUserGroup.objects.filter(realm=realm, is_system_group=True).select_related( "usergroup_ptr" ) @@ -543,7 +543,7 @@ def get_role_based_system_groups_dict(realm: Realm) -> Dict[str, NamedUserGroup] def set_defaults_for_group_settings( user_group: NamedUserGroup, group_settings_map: Mapping[str, UserGroup], - system_groups_name_dict: Dict[str, NamedUserGroup], + system_groups_name_dict: dict[str, NamedUserGroup], ) -> NamedUserGroup: for setting_name, permission_config in NamedUserGroup.GROUP_PERMISSION_SETTINGS.items(): if setting_name in group_settings_map: @@ -562,7 +562,7 @@ def set_defaults_for_group_settings( return user_group -def bulk_create_system_user_groups(groups: List[Dict[str, str]], realm: Realm) -> None: +def bulk_create_system_user_groups(groups: list[dict[str, str]], realm: Realm) -> None: # This value will be used to set the temporary initial value for different # settings since we can only set them to the correct values after the groups # are created. @@ -602,14 +602,14 @@ def bulk_create_system_user_groups(groups: List[Dict[str, str]], realm: Realm) - @transaction.atomic(savepoint=False) -def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, NamedUserGroup]: +def create_system_user_groups_for_realm(realm: Realm) -> dict[int, NamedUserGroup]: """Any changes to this function likely require a migration to adjust existing realms. See e.g. migration 0382_create_role_based_system_groups.py, which is a copy of this function from when we introduced system groups. """ - role_system_groups_dict: Dict[int, NamedUserGroup] = {} + role_system_groups_dict: dict[int, NamedUserGroup] = {} - system_groups_info_list: List[Dict[str, str]] = [] + system_groups_info_list: list[dict[str, str]] = [] nobody_group_info = { "name": SystemGroups.NOBODY, @@ -639,7 +639,7 @@ def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, NamedUserGrou bulk_create_system_user_groups(system_groups_info_list, realm) - system_groups_name_dict: Dict[str, NamedUserGroup] = get_role_based_system_groups_dict(realm) + system_groups_name_dict: dict[str, NamedUserGroup] = get_role_based_system_groups_dict(realm) for role in NamedUserGroup.SYSTEM_USER_GROUP_ROLE_MAP: group_name = NamedUserGroup.SYSTEM_USER_GROUP_ROLE_MAP[role]["name"] role_system_groups_dict[role] = system_groups_name_dict[group_name] @@ -676,7 +676,7 @@ def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, NamedUserGrou groups_with_updated_settings.append(user_group) NamedUserGroup.objects.bulk_update(groups_with_updated_settings, ["can_mention_group"]) - subgroup_objects: List[GroupGroupMembership] = [] + subgroup_objects: list[GroupGroupMembership] = [] # "Nobody" system group is not a subgroup of any user group, since it is already empty. subgroup, remaining_groups = system_user_groups_list[1], system_user_groups_list[2:] for supergroup in remaining_groups: @@ -781,7 +781,7 @@ def validate_group_setting_value_change( def get_group_setting_value_for_audit_log_data( setting_value: Union[int, AnonymousSettingGroupDict], -) -> Union[int, Dict[str, List[int]]]: +) -> Union[int, dict[str, list[int]]]: if isinstance(setting_value, int): return setting_value diff --git a/zerver/lib/user_message.py b/zerver/lib/user_message.py index 3ef5eadeef..421118bf5b 100644 --- a/zerver/lib/user_message.py +++ b/zerver/lib/user_message.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Optional from django.db import connection from psycopg2.extras import execute_values @@ -19,7 +19,7 @@ class UserMessageLite: self.message_id = message_id self.flags = flags - def flags_list(self) -> List[str]: + def flags_list(self) -> list[str]: return UserMessage.flags_list_for_flags(self.flags) @@ -29,7 +29,7 @@ DEFAULT_HISTORICAL_FLAGS = UserMessage.flags.historical | UserMessage.flags.read def create_historical_user_messages( *, user_id: int, - message_ids: List[int], + message_ids: list[int], flagattr: Optional[int] = None, flag_target: Optional[int] = None, ) -> None: @@ -51,7 +51,7 @@ def create_historical_user_messages( bulk_insert_all_ums([user_id], message_ids, flags, conflict) -def bulk_insert_ums(ums: List[UserMessageLite]) -> None: +def bulk_insert_ums(ums: list[UserMessageLite]) -> None: """ Doing bulk inserts this way is much faster than using Django, since we don't have any ORM overhead. Profiling with 1000 @@ -76,7 +76,7 @@ def bulk_insert_ums(ums: List[UserMessageLite]) -> None: def bulk_insert_all_ums( - user_ids: List[int], message_ids: List[int], flags: int, conflict: Optional[Composable] = None + user_ids: list[int], message_ids: list[int], flags: int, conflict: Optional[Composable] = None ) -> None: if not user_ids or not message_ids: return diff --git a/zerver/lib/user_status.py b/zerver/lib/user_status.py index 854d613eab..afa02ec200 100644 --- a/zerver/lib/user_status.py +++ b/zerver/lib/user_status.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional, TypedDict +from typing import Optional, TypedDict from django.db.models import Q from django.utils.timezone import now as timezone_now @@ -49,7 +49,7 @@ def format_user_status(row: RawUserInfoDict) -> UserInfoDict: return dct -def get_all_users_status_dict(realm: Realm, user_profile: UserProfile) -> Dict[str, UserInfoDict]: +def get_all_users_status_dict(realm: Realm, user_profile: UserProfile) -> dict[str, UserInfoDict]: query = UserStatus.objects.filter( user_profile__realm_id=realm.id, user_profile__is_active=True, @@ -74,7 +74,7 @@ def get_all_users_status_dict(realm: Realm, user_profile: UserProfile) -> Dict[s "reaction_type", ) - user_dict: Dict[str, UserInfoDict] = {} + user_dict: dict[str, UserInfoDict] = {} for row in rows: user_id = row["user_profile_id"] user_dict[str(user_id)] = format_user_status(row) diff --git a/zerver/lib/user_topics.py b/zerver/lib/user_topics.py index 6285f1d88e..47cea8fb8f 100644 --- a/zerver/lib/user_topics.py +++ b/zerver/lib/user_topics.py @@ -1,7 +1,7 @@ import logging from collections import defaultdict from datetime import datetime -from typing import Callable, Dict, List, Optional, Tuple, TypedDict +from typing import Callable, Optional, TypedDict from django.db import connection, transaction from django.db.models import QuerySet @@ -22,7 +22,7 @@ def get_user_topics( include_deactivated: bool = False, include_stream_name: bool = False, visibility_policy: Optional[int] = None, -) -> List[UserTopicDict]: +) -> list[UserTopicDict]: """ Fetches UserTopic objects associated with the target user. * include_deactivated: Whether to include those associated with @@ -65,7 +65,7 @@ def get_user_topics( def get_topic_mutes( user_profile: UserProfile, include_deactivated: bool = False -) -> List[Tuple[str, str, int]]: +) -> list[tuple[str, str, int]]: user_topics = get_user_topics( user_profile=user_profile, include_deactivated=include_deactivated, @@ -82,7 +82,7 @@ def get_topic_mutes( @transaction.atomic(savepoint=False) def set_topic_visibility_policy( user_profile: UserProfile, - topics: List[List[str]], + topics: list[list[str]], visibility_policy: int, last_updated: Optional[datetime] = None, ) -> None: @@ -129,14 +129,14 @@ def get_topic_visibility_policy( @transaction.atomic(savepoint=False) def bulk_set_user_topic_visibility_policy_in_database( - user_profiles: List[UserProfile], + user_profiles: list[UserProfile], stream_id: int, topic_name: str, *, visibility_policy: int, recipient_id: Optional[int] = None, last_updated: Optional[datetime] = None, -) -> List[UserProfile]: +) -> list[UserProfile]: # returns the list of user_profiles whose user_topic row # is either deleted, updated, or created. rows = UserTopic.objects.filter( @@ -163,7 +163,7 @@ def bulk_set_user_topic_visibility_policy_in_database( assert last_updated is not None assert recipient_id is not None - user_profiles_seeking_user_topic_update_or_create: List[UserProfile] = ( + user_profiles_seeking_user_topic_update_or_create: list[UserProfile] = ( user_profiles_without_visibility_policy ) for row in rows: @@ -228,8 +228,8 @@ def topic_has_visibility_policy( def exclude_topic_mutes( - conditions: List[ClauseElement], user_profile: UserProfile, stream_id: Optional[int] -) -> List[ClauseElement]: + conditions: list[ClauseElement], user_profile: UserProfile, stream_id: Optional[int] +) -> list[ClauseElement]: # Note: Unlike get_topic_mutes, here we always want to # consider topics in deactivated streams, so they are # never filtered from the query in this method. @@ -281,7 +281,7 @@ def build_get_topic_visibility_policy( "visibility_policy", ) - topic_to_visibility_policy: Dict[Tuple[int, str], int] = defaultdict(int) + topic_to_visibility_policy: dict[tuple[int, str], int] = defaultdict(int) for row in rows: recipient_id = row["recipient_id"] topic_name = row["topic_name"] diff --git a/zerver/lib/users.py b/zerver/lib/users.py index 772c3cb21b..2d0d937905 100644 --- a/zerver/lib/users.py +++ b/zerver/lib/users.py @@ -4,7 +4,7 @@ import unicodedata from collections import defaultdict from email.headerregistry import Address from operator import itemgetter -from typing import Any, Dict, Iterable, List, Mapping, Optional, Sequence, Set, Tuple, TypedDict +from typing import Any, Iterable, Mapping, Optional, Sequence, TypedDict import dateutil.parser as date_parser from django.conf import settings @@ -211,7 +211,7 @@ def is_administrator_role(role: int) -> bool: return role in {UserProfile.ROLE_REALM_ADMINISTRATOR, UserProfile.ROLE_REALM_OWNER} -def bulk_get_cross_realm_bots() -> Dict[str, UserProfile]: +def bulk_get_cross_realm_bots() -> dict[str, UserProfile]: emails = list(settings.CROSS_REALM_BOT_EMAILS) # This should be just @@ -231,7 +231,7 @@ def bulk_get_cross_realm_bots() -> Dict[str, UserProfile]: return {user.email.lower(): user for user in users} -def user_ids_to_users(user_ids: Sequence[int], realm: Realm) -> List[UserProfile]: +def user_ids_to_users(user_ids: Sequence[int], realm: Realm) -> list[UserProfile]: # TODO: Consider adding a flag to control whether deactivated # users should be included. @@ -354,7 +354,7 @@ class Account(TypedDict): avatar: Optional[str] -def get_accounts_for_email(email: str) -> List[Account]: +def get_accounts_for_email(email: str) -> list[Account]: profiles = ( UserProfile.objects.select_related("realm") .filter( @@ -380,7 +380,7 @@ def get_api_key(user_profile: UserProfile) -> str: return user_profile.api_key -def get_all_api_keys(user_profile: UserProfile) -> List[str]: +def get_all_api_keys(user_profile: UserProfile) -> list[str]: # Users can only have one API key for now return [user_profile.api_key] @@ -408,7 +408,7 @@ def validate_user_custom_profile_field( def validate_user_custom_profile_data( - realm_id: int, profile_data: List[ProfileDataElementUpdateDict] + realm_id: int, profile_data: list[ProfileDataElementUpdateDict] ) -> None: # This function validate all custom field values according to their field type. for item in profile_data: @@ -466,7 +466,7 @@ class APIUserDict(TypedDict): delivery_email: Optional[str] bot_type: NotRequired[Optional[int]] bot_owner_id: NotRequired[Optional[int]] - profile_data: NotRequired[Optional[Dict[str, Any]]] + profile_data: NotRequired[Optional[dict[str, Any]]] is_system_bot: NotRequired[bool] max_message_id: NotRequired[int] @@ -477,7 +477,7 @@ def format_user_row( row: RawUserDict, client_gravatar: bool, user_avatar_url_field_optional: bool, - custom_profile_field_data: Optional[Dict[str, Any]] = None, + custom_profile_field_data: Optional[dict[str, Any]] = None, ) -> APIUserDict: """Formats a user row returned by a database fetch using .values(*realm_user_dict_fields) into a dictionary representation @@ -637,8 +637,8 @@ def check_can_access_user( def get_inaccessible_user_ids( - target_user_ids: List[int], acting_user: Optional[UserProfile] -) -> Set[int]: + target_user_ids: list[int], acting_user: Optional[UserProfile] +) -> set[int]: if check_user_can_access_all_users(acting_user): return set() @@ -696,7 +696,7 @@ def get_inaccessible_user_ids( return inaccessible_user_ids -def get_user_ids_who_can_access_user(target_user: UserProfile) -> List[int]: +def get_user_ids_who_can_access_user(target_user: UserProfile) -> list[int]: # We assume that caller only needs active users here, since # this function is used to get users to send events and to # send presence update. @@ -719,8 +719,8 @@ def get_user_ids_who_can_access_user(target_user: UserProfile) -> List[int]: def get_subscribers_of_target_user_subscriptions( - target_users: List[UserProfile], include_deactivated_users_for_huddles: bool = False -) -> Dict[int, Set[int]]: + target_users: list[UserProfile], include_deactivated_users_for_huddles: bool = False +) -> dict[int, set[int]]: target_user_ids = [user.id for user in target_users] target_user_subscriptions = ( Subscription.objects.filter( @@ -733,7 +733,7 @@ def get_subscribers_of_target_user_subscriptions( ) target_users_subbed_recipient_ids = set() - target_user_subscriptions_dict: Dict[int, Set[int]] = defaultdict(set) + target_user_subscriptions_dict: dict[int, set[int]] = defaultdict(set) for user_profile_id, sub_rows in itertools.groupby( target_user_subscriptions, itemgetter("user_profile_id") @@ -762,14 +762,14 @@ def get_subscribers_of_target_user_subscriptions( "recipient_id" ).values("user_profile_id", "recipient_id") - subscribers_dict_by_recipient_ids: Dict[int, Set[int]] = defaultdict(set) + subscribers_dict_by_recipient_ids: dict[int, set[int]] = defaultdict(set) for recipient_id, sub_rows in itertools.groupby( subs_in_target_user_subscriptions, itemgetter("recipient_id") ): user_ids = {row["user_profile_id"] for row in sub_rows} subscribers_dict_by_recipient_ids[recipient_id] = user_ids - users_subbed_to_target_user_subscriptions_dict: Dict[int, Set[int]] = defaultdict(set) + users_subbed_to_target_user_subscriptions_dict: dict[int, set[int]] = defaultdict(set) for user_id in target_user_ids: target_user_subbed_recipients = target_user_subscriptions_dict[user_id] for recipient_id in target_user_subbed_recipients: @@ -781,8 +781,8 @@ def get_subscribers_of_target_user_subscriptions( def get_users_involved_in_dms_with_target_users( - target_users: List[UserProfile], realm: Realm, include_deactivated_users: bool = False -) -> Dict[int, Set[int]]: + target_users: list[UserProfile], realm: Realm, include_deactivated_users: bool = False +) -> dict[int, set[int]]: target_user_ids = [user.id for user in target_users] direct_messages_recipient_users = ( @@ -801,7 +801,7 @@ def get_users_involved_in_dms_with_target_users( id__in=list(direct_messages_recipient_users_set), is_active=True ).values_list("id", flat=True) - direct_message_participants_dict: Dict[int, Set[int]] = defaultdict(set) + direct_message_participants_dict: dict[int, set[int]] = defaultdict(set) for sender_id, message_rows in itertools.groupby( direct_messages_recipient_users, itemgetter("sender_id") ): @@ -858,7 +858,7 @@ def user_profile_to_user_row(user_profile: UserProfile) -> RawUserDict: @cache_with_key(get_cross_realm_dicts_key) -def get_cross_realm_dicts() -> List[APIUserDict]: +def get_cross_realm_dicts() -> list[APIUserDict]: user_dict = bulk_get_cross_realm_bots() users = sorted(user_dict.values(), key=lambda user: user.full_name) result = [] @@ -914,7 +914,7 @@ def get_data_for_inaccessible_user(realm: Realm, user_id: int) -> APIUserDict: def get_accessible_user_ids( realm: Realm, user_profile: UserProfile, include_deactivated_users: bool = False -) -> List[int]: +) -> list[int]: subscribers_dict_of_target_user_subscriptions = get_subscribers_of_target_user_subscriptions( [user_profile], include_deactivated_users_for_huddles=include_deactivated_users ) @@ -935,7 +935,7 @@ def get_accessible_user_ids( def get_user_dicts_in_realm( realm: Realm, user_profile: Optional[UserProfile] -) -> Tuple[List[RawUserDict], List[APIUserDict]]: +) -> tuple[list[RawUserDict], list[APIUserDict]]: group_allowed_to_access_all_users = realm.can_access_all_users_group assert group_allowed_to_access_all_users is not None @@ -948,8 +948,8 @@ def get_user_dicts_in_realm( realm, user_profile, include_deactivated_users=True ) - accessible_user_dicts: List[RawUserDict] = [] - inaccessible_user_dicts: List[APIUserDict] = [] + accessible_user_dicts: list[RawUserDict] = [] + inaccessible_user_dicts: list[APIUserDict] = [] for user_dict in all_user_dicts: if user_dict["id"] in accessible_user_ids or user_dict["is_bot"]: accessible_user_dicts.append(user_dict) @@ -961,8 +961,8 @@ def get_user_dicts_in_realm( def get_custom_profile_field_values( custom_profile_field_values: Iterable[CustomProfileFieldValue], -) -> Dict[int, Dict[str, Any]]: - profiles_by_user_id: Dict[int, Dict[str, Any]] = defaultdict(dict) +) -> dict[int, dict[str, Any]]: + profiles_by_user_id: dict[int, dict[str, Any]] = defaultdict(dict) for profile_field in custom_profile_field_values: user_id = profile_field.user_profile_id if profile_field.field.is_renderable(): @@ -986,7 +986,7 @@ def get_users_for_api( user_avatar_url_field_optional: bool, include_custom_profile_fields: bool = True, user_list_incomplete: bool = False, -) -> Dict[int, APIUserDict]: +) -> dict[int, APIUserDict]: """Fetches data about the target user(s) appropriate for sending to acting_user via the standard format for the Zulip API. If target_user is None, we fetch all users in the realm. @@ -995,8 +995,8 @@ def get_users_for_api( custom_profile_field_data = None # target_user is an optional parameter which is passed when user data of a specific user # is required. It is 'None' otherwise. - accessible_user_dicts: List[RawUserDict] = [] - inaccessible_user_dicts: List[APIUserDict] = [] + accessible_user_dicts: list[RawUserDict] = [] + inaccessible_user_dicts: list[APIUserDict] = [] if target_user is not None: accessible_user_dicts = [user_profile_to_user_row(target_user)] else: @@ -1056,7 +1056,7 @@ def is_2fa_verified(user: UserProfile) -> bool: return is_verified(user) -def get_users_with_access_to_real_email(user_profile: UserProfile) -> List[int]: +def get_users_with_access_to_real_email(user_profile: UserProfile) -> list[int]: if not user_access_restricted_in_realm(user_profile): active_users = user_profile.realm.get_active_users() else: diff --git a/zerver/lib/utils.py b/zerver/lib/utils.py index 768414a2a8..b7980080f4 100644 --- a/zerver/lib/utils.py +++ b/zerver/lib/utils.py @@ -1,6 +1,6 @@ import re import secrets -from typing import Callable, List, Optional, TypeVar +from typing import Callable, Optional, TypeVar T = TypeVar("T") @@ -23,7 +23,7 @@ def assert_is_not_none(value: Optional[T]) -> T: def process_list_in_batches( - lst: List[T], chunk_size: int, process_batch: Callable[[List[T]], None] + lst: list[T], chunk_size: int, process_batch: Callable[[list[T]], None] ) -> None: offset = 0 diff --git a/zerver/lib/validator.py b/zerver/lib/validator.py index 0f8787467e..0cf2d74112 100644 --- a/zerver/lib/validator.py +++ b/zerver/lib/validator.py @@ -36,13 +36,9 @@ from typing import ( Callable, Collection, Container, - Dict, Iterator, - List, NoReturn, Optional, - Set, - Tuple, TypeVar, Union, cast, @@ -162,7 +158,7 @@ def check_int(var_name: str, val: object) -> int: return val -def check_int_in(possible_values: List[int]) -> Validator[int]: +def check_int_in(possible_values: list[int]) -> Validator[int]: """ Assert that the input is an integer and is contained in `possible_values`. If the input is not in `possible_values`, a `ValidationError` is raised containing the failing field's name. @@ -225,8 +221,8 @@ def check_none_or(sub_validator: Validator[ResultT]) -> Validator[Optional[Resul def check_list( sub_validator: Validator[ResultT], length: Optional[int] = None -) -> Validator[List[ResultT]]: - def f(var_name: str, val: object) -> List[ResultT]: +) -> Validator[list[ResultT]]: + def f(var_name: str, val: object) -> list[ResultT]: if not isinstance(val, list): raise ValidationError(_("{var_name} is not a list").format(var_name=var_name)) @@ -243,7 +239,7 @@ def check_list( valid_item = sub_validator(vname, item) assert item is valid_item # To justify the unchecked cast below - return cast(List[ResultT], val) + return cast(list[ResultT], val) return f @@ -251,27 +247,27 @@ def check_list( # https://zulip.readthedocs.io/en/latest/testing/mypy.html#using-overload-to-accurately-describe-variations @overload def check_dict( - required_keys: Collection[Tuple[str, Validator[object]]] = [], - optional_keys: Collection[Tuple[str, Validator[object]]] = [], + required_keys: Collection[tuple[str, Validator[object]]] = [], + optional_keys: Collection[tuple[str, Validator[object]]] = [], *, _allow_only_listed_keys: bool = False, -) -> Validator[Dict[str, object]]: ... +) -> Validator[dict[str, object]]: ... @overload def check_dict( - required_keys: Collection[Tuple[str, Validator[ResultT]]] = [], - optional_keys: Collection[Tuple[str, Validator[ResultT]]] = [], + required_keys: Collection[tuple[str, Validator[ResultT]]] = [], + optional_keys: Collection[tuple[str, Validator[ResultT]]] = [], *, value_validator: Validator[ResultT], _allow_only_listed_keys: bool = False, -) -> Validator[Dict[str, ResultT]]: ... +) -> Validator[dict[str, ResultT]]: ... def check_dict( - required_keys: Collection[Tuple[str, Validator[ResultT]]] = [], - optional_keys: Collection[Tuple[str, Validator[ResultT]]] = [], + required_keys: Collection[tuple[str, Validator[ResultT]]] = [], + optional_keys: Collection[tuple[str, Validator[ResultT]]] = [], *, value_validator: Optional[Validator[ResultT]] = None, _allow_only_listed_keys: bool = False, -) -> Validator[Dict[str, ResultT]]: - def f(var_name: str, val: object) -> Dict[str, ResultT]: +) -> Validator[dict[str, ResultT]]: + def f(var_name: str, val: object) -> dict[str, ResultT]: if not isinstance(val, dict): raise ValidationError(_("{var_name} is not a dict").format(var_name=var_name)) @@ -309,17 +305,17 @@ def check_dict( _("Unexpected arguments: {keys}").format(keys=", ".join(delta_keys)) ) - return cast(Dict[str, ResultT], val) + return cast(dict[str, ResultT], val) return f def check_dict_only( - required_keys: Collection[Tuple[str, Validator[ResultT]]], - optional_keys: Collection[Tuple[str, Validator[ResultT]]] = [], -) -> Validator[Dict[str, ResultT]]: + required_keys: Collection[tuple[str, Validator[ResultT]]], + optional_keys: Collection[tuple[str, Validator[ResultT]]] = [], +) -> Validator[dict[str, ResultT]]: return cast( - Validator[Dict[str, ResultT]], + Validator[dict[str, ResultT]], check_dict(required_keys, optional_keys, _allow_only_listed_keys=True), ) @@ -405,7 +401,7 @@ def check_external_account_url_pattern(var_name: str, val: object) -> str: return s -def validate_select_field_data(field_data: ProfileFieldData) -> Dict[str, Dict[str, str]]: +def validate_select_field_data(field_data: ProfileFieldData) -> dict[str, dict[str, str]]: """ This function is used to validate the data sent to the server while creating/editing choices of the choice field in Organization settings. @@ -418,7 +414,7 @@ def validate_select_field_data(field_data: ProfileFieldData) -> Dict[str, Dict[s ) # To create an array of texts of each option - distinct_field_names: Set[str] = set() + distinct_field_names: set[str] = set() for key, value in field_data.items(): if not key.strip(): @@ -433,7 +429,7 @@ def validate_select_field_data(field_data: ProfileFieldData) -> Dict[str, Dict[s if len(field_data) != len(distinct_field_names): raise ValidationError(_("Field must not have duplicate choices.")) - return cast(Dict[str, Dict[str, str]], field_data) + return cast(dict[str, dict[str, str]], field_data) def validate_select_field(var_name: str, field_data: str, value: object) -> str: @@ -449,7 +445,7 @@ def validate_select_field(var_name: str, field_data: str, value: object) -> str: return s -def check_widget_content(widget_content: object) -> Dict[str, Any]: +def check_widget_content(widget_content: object) -> dict[str, Any]: if not isinstance(widget_content, dict): raise ValidationError("widget_content is not a dict") @@ -626,7 +622,7 @@ def to_converted_or_fallback( return converter -def check_string_or_int_list(var_name: str, val: object) -> Union[str, List[int]]: +def check_string_or_int_list(var_name: str, val: object) -> Union[str, list[int]]: if isinstance(val, str): return val @@ -706,7 +702,7 @@ class WildValue: def values(self) -> Iterator["WildValue"]: self._need_dict() - def items(self) -> Iterator[Tuple[str, "WildValue"]]: + def items(self) -> Iterator[tuple[str, "WildValue"]]: self._need_dict() def tame(self, validator: Validator[ResultT]) -> ResultT: @@ -714,7 +710,7 @@ class WildValue: class WildValueList(WildValue): - value: List[object] + value: list[object] @override def __iter__(self) -> Iterator[WildValue]: @@ -737,7 +733,7 @@ class WildValueList(WildValue): class WildValueDict(WildValue): - value: Dict[str, object] + value: dict[str, object] @override def __contains__(self, key: str) -> bool: @@ -774,7 +770,7 @@ class WildValueDict(WildValue): yield wrap_wild_value(f"{self.var_name}[{key!r}]", value) @override - def items(self) -> Iterator[Tuple[str, WildValue]]: + def items(self) -> Iterator[tuple[str, WildValue]]: for key, value in self.value.items(): yield key, wrap_wild_value(f"{self.var_name}[{key!r}]", value) diff --git a/zerver/lib/webhooks/common.py b/zerver/lib/webhooks/common.py index 8b7deafb72..3fb6c32ae0 100644 --- a/zerver/lib/webhooks/common.py +++ b/zerver/lib/webhooks/common.py @@ -1,7 +1,7 @@ import fnmatch import importlib from datetime import datetime -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Callable, Optional, Union from urllib.parse import unquote from django.http import HttpRequest @@ -89,8 +89,8 @@ def check_send_webhook_message( *, stream: Optional[str] = None, user_specified_topic: OptionalUserSpecifiedTopicStr = None, - only_events: Optional[Json[List[str]]] = None, - exclude_events: Optional[Json[List[str]]] = None, + only_events: Optional[Json[list[str]]] = None, + exclude_events: Optional[Json[list[str]]] = None, unquote_url_parameters: bool = False, ) -> None: if complete_event_type is not None and ( @@ -146,7 +146,7 @@ def check_send_webhook_message( pass -def standardize_headers(input_headers: Union[None, Dict[str, Any]]) -> Dict[str, str]: +def standardize_headers(input_headers: Union[None, dict[str, Any]]) -> dict[str, str]: """This method can be used to standardize a dictionary of headers with the standard format that Django expects. For reference, refer to: https://docs.djangoproject.com/en/5.0/ref/request-response/#django.http.HttpRequest.headers @@ -194,7 +194,7 @@ def validate_extract_webhook_http_header( return extracted_header -def get_fixture_http_headers(integration_name: str, fixture_name: str) -> Dict["str", "str"]: +def get_fixture_http_headers(integration_name: str, fixture_name: str) -> dict["str", "str"]: """For integrations that require custom HTTP headers for some (or all) of their test fixtures, this method will call a specially named function from the target integration module to determine what set @@ -211,13 +211,13 @@ def get_fixture_http_headers(integration_name: str, fixture_name: str) -> Dict[" return fixture_to_headers(fixture_name) -def get_http_headers_from_filename(http_header_key: str) -> Callable[[str], Dict[str, str]]: +def get_http_headers_from_filename(http_header_key: str) -> Callable[[str], dict[str, str]]: """If an integration requires an event type kind of HTTP header which can be easily (statically) determined, then name the fixtures in the format of "header_value__other_details" or even "header_value" and the use this method in the headers.py file for the integration.""" - def fixture_to_headers(filename: str) -> Dict[str, str]: + def fixture_to_headers(filename: str) -> dict[str, str]: if "__" in filename: event_type = filename.split("__")[0] else: diff --git a/zerver/lib/webhooks/git.py b/zerver/lib/webhooks/git.py index 23c0d67716..4d5ebae1a9 100644 --- a/zerver/lib/webhooks/git.py +++ b/zerver/lib/webhooks/git.py @@ -1,6 +1,6 @@ import string from collections import defaultdict -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Optional TOPIC_WITH_BRANCH_TEMPLATE = "{repo} / {branch}" TOPIC_WITH_PR_OR_ISSUE_INFO_TEMPLATE = "{repo} / {type} #{id} {title}" @@ -77,7 +77,7 @@ TAG_WITHOUT_URL_TEMPLATE = "{tag_name}" RELEASE_MESSAGE_TEMPLATE = "{user_name} {action} release [{release_name}]({url}) for tag {tagname}." -def get_assignee_string(assignees: List[Dict[str, Any]]) -> str: +def get_assignee_string(assignees: list[dict[str, Any]]) -> str: assignees_string = "" if len(assignees) == 1: assignees_string = "{username}".format(**assignees[0]) @@ -92,7 +92,7 @@ def get_push_commits_event_message( user_name: str, compare_url: Optional[str], branch_name: str, - commits_data: List[Dict[str, Any]], + commits_data: list[dict[str, Any]], is_truncated: bool = False, deleted: bool = False, force_push: Optional[bool] = False, @@ -130,7 +130,7 @@ def get_push_commits_event_message( commit_or_commits=COMMIT_OR_COMMITS.format("s" if len(commits_data) > 1 else ""), ) - committers_items: List[Tuple[str, int]] = get_all_committers(commits_data) + committers_items: list[tuple[str, int]] = get_all_committers(commits_data) if len(committers_items) == 1 and user_name == committers_items[0][0]: return PUSH_COMMITS_MESSAGE_TEMPLATE_WITHOUT_COMMITTERS.format( user_name=user_name, @@ -197,7 +197,7 @@ def get_pull_request_event_message( base_branch: Optional[str] = None, message: Optional[str] = None, assignee: Optional[str] = None, - assignees: Optional[List[Dict[str, Any]]] = None, + assignees: Optional[list[dict[str, Any]]] = None, reviewer: Optional[str] = None, type: str = "PR", title: Optional[str] = None, @@ -263,7 +263,7 @@ def get_issue_event_message( number: Optional[int] = None, message: Optional[str] = None, assignee: Optional[str] = None, - assignees: Optional[List[Dict[str, Any]]] = None, + assignees: Optional[list[dict[str, Any]]] = None, title: Optional[str] = None, ) -> str: return get_pull_request_event_message( @@ -368,7 +368,7 @@ def get_commits_comment_action_message( return content -def get_commits_content(commits_data: List[Dict[str, Any]], is_truncated: bool = False) -> str: +def get_commits_content(commits_data: list[dict[str, Any]], is_truncated: bool = False) -> str: commits_content = "" for commit in commits_data[:COMMITS_LIMIT]: commits_content += COMMIT_ROW_TEMPLATE.format( @@ -406,18 +406,18 @@ def get_short_sha(sha: str) -> str: return sha[:11] -def get_all_committers(commits_data: List[Dict[str, Any]]) -> List[Tuple[str, int]]: - committers: Dict[str, int] = defaultdict(int) +def get_all_committers(commits_data: list[dict[str, Any]]) -> list[tuple[str, int]]: + committers: dict[str, int] = defaultdict(int) for commit in commits_data: committers[commit["name"]] += 1 # Sort by commit count, breaking ties alphabetically. - committers_items: List[Tuple[str, int]] = sorted( + committers_items: list[tuple[str, int]] = sorted( committers.items(), key=lambda item: (-item[1], item[0]), ) - committers_values: List[int] = [c_i[1] for c_i in committers_items] + committers_values: list[int] = [c_i[1] for c_i in committers_items] if len(committers) > PUSH_COMMITTERS_LIMIT_INFO: others_number_of_commits = sum(committers_values[PUSH_COMMITTERS_LIMIT_INFO:]) diff --git a/zerver/lib/widget.py b/zerver/lib/widget.py index bb3672ae16..f1160d25db 100644 --- a/zerver/lib/widget.py +++ b/zerver/lib/widget.py @@ -1,12 +1,12 @@ import json import re -from typing import Any, Optional, Tuple +from typing import Any, Optional from zerver.lib.message import SendMessageRequest from zerver.models import Message, SubMessage -def get_widget_data(content: str) -> Tuple[Optional[str], Any]: +def get_widget_data(content: str) -> tuple[Optional[str], Any]: valid_widget_types = ["poll", "todo"] tokens = re.split(r"\s+|\n+", content) diff --git a/zerver/lib/zcommand.py b/zerver/lib/zcommand.py index 7af12cd0c7..0a7be1ca3c 100644 --- a/zerver/lib/zcommand.py +++ b/zerver/lib/zcommand.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from django.utils.translation import gettext as _ @@ -7,7 +7,7 @@ from zerver.lib.exceptions import JsonableError from zerver.models import UserProfile -def process_zcommands(content: str, user_profile: UserProfile) -> Dict[str, Any]: +def process_zcommands(content: str, user_profile: UserProfile) -> dict[str, Any]: def change_mode_setting( setting_name: str, switch_command: str, setting: str, setting_value: int ) -> str: diff --git a/zerver/lib/zulip_update_announcements.py b/zerver/lib/zulip_update_announcements.py index 57587db204..72fb465571 100644 --- a/zerver/lib/zulip_update_announcements.py +++ b/zerver/lib/zulip_update_announcements.py @@ -1,7 +1,7 @@ import logging from dataclasses import dataclass from datetime import timedelta -from typing import List, Optional +from typing import Optional from django.conf import settings from django.db import transaction @@ -29,7 +29,7 @@ class ZulipUpdateAnnouncement: # We don't translate the announcement message because they are quite unlikely to be # translated during the time between when we draft them and when they are published. -zulip_update_announcements: List[ZulipUpdateAnnouncement] = [ +zulip_update_announcements: list[ZulipUpdateAnnouncement] = [ ZulipUpdateAnnouncement( level=1, message=""" @@ -223,7 +223,7 @@ def is_group_direct_message_sent_to_admins_within_days(realm: Realm, days: int) def internal_prep_zulip_update_announcements_stream_messages( current_level: int, latest_level: int, sender: UserProfile, realm: Realm -) -> List[Optional[SendMessageRequest]]: +) -> list[Optional[SendMessageRequest]]: message_requests = [] stream = realm.zulip_update_announcements_stream assert stream is not None @@ -247,7 +247,7 @@ def internal_prep_zulip_update_announcements_stream_messages( def send_messages_and_update_level( realm: Realm, new_zulip_update_announcements_level: int, - send_message_requests: List[Optional[SendMessageRequest]], + send_message_requests: list[Optional[SendMessageRequest]], ) -> None: sent_message_ids = [] if send_message_requests: diff --git a/zerver/management/commands/change_password.py b/zerver/management/commands/change_password.py index e1244f78d4..855a0cab0a 100644 --- a/zerver/management/commands/change_password.py +++ b/zerver/management/commands/change_password.py @@ -1,6 +1,6 @@ import getpass from argparse import ArgumentParser -from typing import Any, List +from typing import Any from django.contrib.auth.password_validation import validate_password from django.core.exceptions import ValidationError @@ -20,7 +20,7 @@ class Command(ZulipBaseCommand): help = "Change a user's password." requires_migrations_checks = True - requires_system_checks: List[str] = [] + requires_system_checks: list[str] = [] def _get_pass(self, prompt: str = "Password: ") -> str: p = getpass.getpass(prompt=prompt) diff --git a/zerver/management/commands/compilemessages.py b/zerver/management/commands/compilemessages.py index 4f4d2122fd..84262074e0 100644 --- a/zerver/management/commands/compilemessages.py +++ b/zerver/management/commands/compilemessages.py @@ -3,7 +3,7 @@ import os import re import unicodedata from subprocess import CalledProcessError, check_output -from typing import Any, Dict, List +from typing import Any import orjson import polib @@ -73,7 +73,7 @@ class Command(compilemessages.Command): except (KeyError, ValueError): raise Exception(f"Unknown language {locale}") - def get_locales(self) -> List[str]: + def get_locales(self) -> list[str]: output = check_output(["git", "ls-files", "locale"], text=True) tracked_files = output.split() regex = re.compile(r"locale/(\w+)/LC_MESSAGES/django.po") @@ -89,7 +89,7 @@ class Command(compilemessages.Command): locale_path = f"{settings.DEPLOY_ROOT}/locale" output_path = f"{locale_path}/language_options.json" - data: Dict[str, List[Dict[str, Any]]] = {"languages": []} + data: dict[str, list[dict[str, Any]]] = {"languages": []} try: locales = self.get_locales() @@ -117,7 +117,7 @@ class Command(compilemessages.Command): # Not a locale. continue - info: Dict[str, Any] = {} + info: dict[str, Any] = {} code = to_language(locale) percentage = self.get_translation_percentage(locale_path, locale) try: diff --git a/zerver/management/commands/export_search.py b/zerver/management/commands/export_search.py index d3974afeed..ab206b8cc4 100644 --- a/zerver/management/commands/export_search.py +++ b/zerver/management/commands/export_search.py @@ -6,7 +6,7 @@ from datetime import datetime, timezone from email.headerregistry import Address from functools import lru_cache, reduce from operator import or_ -from typing import Any, Dict, Set, Tuple +from typing import Any import orjson from django.core.management.base import CommandError @@ -181,7 +181,7 @@ This is most often used for legal compliance. ) limits &= Q(recipient=direct_message_group.recipient) - attachments_written: Set[str] = set() + attachments_written: set[str] = set() messages_query = Message.objects.filter(limits, realm=realm).order_by("date_sent") print(f"Exporting {len(messages_query)} messages...") @@ -196,7 +196,7 @@ This is most often used for legal compliance. return f"{recip_str} > {subject}" @lru_cache(maxsize=1000) - def format_recipient(recipient_id: int) -> Tuple[str, bool]: + def format_recipient(recipient_id: int) -> tuple[str, bool]: recipient = Recipient.objects.get(id=recipient_id) if recipient.type == Recipient.STREAM: @@ -213,7 +213,7 @@ This is most often used for legal compliance. return ", ".join(format_sender(e[0], e[1]) for e in users), False - def transform_message(message: Message) -> Dict[str, str]: + def transform_message(message: Message) -> dict[str, str]: row = { "id": str(message.id), "timestamp (UTC)": message.date_sent.astimezone(timezone.utc).strftime( diff --git a/zerver/management/commands/fetch_tor_exit_nodes.py b/zerver/management/commands/fetch_tor_exit_nodes.py index 58f4de0dde..15aaa698a2 100644 --- a/zerver/management/commands/fetch_tor_exit_nodes.py +++ b/zerver/management/commands/fetch_tor_exit_nodes.py @@ -1,6 +1,6 @@ import os from argparse import ArgumentParser -from typing import Any, Set +from typing import Any import orjson from django.conf import settings @@ -61,7 +61,7 @@ Does nothing unless RATE_LIMIT_TOR_TOGETHER is enabled. # Published 2021-11-02 11:01:07 # LastStatus 2021-11-02 23:00:00 # ExitAddress 176.10.99.200 2021-11-02 23:17:02 - exit_nodes: Set[str] = set() + exit_nodes: set[str] = set() for line in response.text.splitlines(): if line.startswith("ExitAddress "): exit_nodes.add(line.split()[1]) diff --git a/zerver/management/commands/makemessages.py b/zerver/management/commands/makemessages.py index 3b9efcd85c..2fa814a407 100644 --- a/zerver/management/commands/makemessages.py +++ b/zerver/management/commands/makemessages.py @@ -38,7 +38,7 @@ import json import os import re import subprocess -from typing import Any, Collection, Dict, Iterator, List, Mapping +from typing import Any, Collection, Iterator, Mapping from django.core.management.base import CommandParser from django.core.management.commands import makemessages @@ -111,8 +111,8 @@ class Command(makemessages.Command): frontend_source: str, frontend_output: str, frontend_namespace: str, - locale: List[str], - exclude: List[str], + locale: list[str], + exclude: list[str], all: bool, **options: Any, ) -> None: @@ -164,8 +164,8 @@ class Command(makemessages.Command): template.templatize = old_templatize template.constant_re = old_constant_re - def extract_strings(self, data: str) -> List[str]: - translation_strings: List[str] = [] + def extract_strings(self, data: str) -> list[str]: + translation_strings: list[str] = [] for regex in frontend_compiled_regexes: for match in regex.findall(data): match = match.strip() @@ -181,8 +181,8 @@ class Command(makemessages.Command): data = singleline_js_comment.sub("", data) return data - def get_translation_strings(self) -> List[str]: - translation_strings: List[str] = [] + def get_translation_strings(self) -> list[str]: + translation_strings: list[str] = [] dirname = self.get_template_dir() for dirpath, dirnames, filenames in os.walk(dirname): @@ -255,8 +255,8 @@ class Command(makemessages.Command): yield os.path.join(path, self.get_namespace()) def get_new_strings( - self, old_strings: Mapping[str, str], translation_strings: List[str], locale: str - ) -> Dict[str, str]: + self, old_strings: Mapping[str, str], translation_strings: list[str], locale: str + ) -> dict[str, str]: """ Missing strings are removed, new strings are added and already translated strings are not touched. @@ -271,7 +271,7 @@ class Command(makemessages.Command): return new_strings - def write_translation_strings(self, translation_strings: List[str]) -> None: + def write_translation_strings(self, translation_strings: list[str]) -> None: for locale, output_path in zip(self.get_locales(), self.get_output_paths()): self.stdout.write(f"[frontend] processing locale {locale}") try: diff --git a/zerver/management/commands/process_queue.py b/zerver/management/commands/process_queue.py index be7d110b31..51d96196fd 100644 --- a/zerver/management/commands/process_queue.py +++ b/zerver/management/commands/process_queue.py @@ -6,7 +6,7 @@ import threading from argparse import ArgumentParser from contextlib import contextmanager from types import FrameType -from typing import Any, Iterator, List, Optional +from typing import Any, Iterator, Optional from django.conf import settings from django.core.management.base import CommandError @@ -73,7 +73,7 @@ class Command(ZulipBaseCommand): logger.error("Cannot run a queue processor when USING_RABBITMQ is False!") raise CommandError - def run_threaded_workers(queues: List[str], logger: logging.Logger) -> None: + def run_threaded_workers(queues: list[str], logger: logging.Logger) -> None: cnt = 0 for queue_name in queues: if not settings.DEVELOPMENT: diff --git a/zerver/management/commands/register_server.py b/zerver/management/commands/register_server.py index edb8ae629a..8280377dfd 100644 --- a/zerver/management/commands/register_server.py +++ b/zerver/management/commands/register_server.py @@ -1,7 +1,7 @@ import os import subprocess from argparse import ArgumentParser -from typing import Any, Dict +from typing import Any import requests from django.conf import settings @@ -137,7 +137,7 @@ class Command(ZulipBaseCommand): ) print("Mobile Push Notification Service registration successfully updated!") - def _request_push_notification_bouncer_url(self, url: str, params: Dict[str, Any]) -> Response: + def _request_push_notification_bouncer_url(self, url: str, params: dict[str, Any]) -> Response: assert settings.PUSH_NOTIFICATION_BOUNCER_URL is not None registration_url = settings.PUSH_NOTIFICATION_BOUNCER_URL + url session = PushBouncerSession() diff --git a/zerver/management/commands/send_custom_email.py b/zerver/management/commands/send_custom_email.py index cf8a57f220..7b4282c178 100644 --- a/zerver/management/commands/send_custom_email.py +++ b/zerver/management/commands/send_custom_email.py @@ -1,5 +1,5 @@ from argparse import ArgumentParser -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable, Optional import orjson from django.conf import settings @@ -97,7 +97,7 @@ class Command(ZulipBaseCommand): self, *args: Any, dry_run: bool = False, admins_only: bool = False, **options: str ) -> None: users: QuerySet[UserProfile] = UserProfile.objects.none() - add_context: Optional[Callable[[Dict[str, object], UserProfile], None]] = None + add_context: Optional[Callable[[dict[str, object], UserProfile], None]] = None distinct_email = False if options["remote_servers"]: @@ -105,13 +105,13 @@ class Command(ZulipBaseCommand): add_server_context = None if options["json_file"]: with open(options["json_file"]) as f: - server_data: Dict[str, Dict[str, object]] = orjson.loads(f.read()) + server_data: dict[str, dict[str, object]] = orjson.loads(f.read()) servers = RemoteZulipServer.objects.filter( id__in=[int(server_id) for server_id in server_data] ) def add_server_context_from_dict( - context: Dict[str, object], server: RemoteZulipServer + context: dict[str, object], server: RemoteZulipServer ) -> None: context.update(server_data.get(str(server.id), {})) @@ -154,7 +154,7 @@ class Command(ZulipBaseCommand): ) distinct_email = True - def add_marketing_unsubscribe(context: Dict[str, object], user: UserProfile) -> None: + def add_marketing_unsubscribe(context: dict[str, object], user: UserProfile) -> None: context["unsubscribe_link"] = one_click_unsubscribe_link(user, "marketing") add_context = add_marketing_unsubscribe @@ -181,10 +181,10 @@ class Command(ZulipBaseCommand): if options["json_file"]: with open(options["json_file"]) as f: - user_data: Dict[str, Dict[str, object]] = orjson.loads(f.read()) + user_data: dict[str, dict[str, object]] = orjson.loads(f.read()) users = users.filter(id__in=[int(user_id) for user_id in user_data]) - def add_context_from_dict(context: Dict[str, object], user: UserProfile) -> None: + def add_context_from_dict(context: dict[str, object], user: UserProfile) -> None: context.update(user_data.get(str(user.id), {})) add_context = add_context_from_dict diff --git a/zerver/management/commands/send_webhook_fixture_message.py b/zerver/management/commands/send_webhook_fixture_message.py index 3e6456d034..2030fb92c8 100644 --- a/zerver/management/commands/send_webhook_fixture_message.py +++ b/zerver/management/commands/send_webhook_fixture_message.py @@ -1,5 +1,5 @@ import os -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union import orjson from django.conf import settings @@ -51,7 +51,7 @@ approach shown above. parser, help="Specify which realm/subdomain to connect to; default is zulip" ) - def parse_headers(self, custom_headers: Union[None, str]) -> Union[None, Dict[str, str]]: + def parse_headers(self, custom_headers: Union[None, str]) -> Union[None, dict[str, str]]: if not custom_headers: return {} try: diff --git a/zerver/management/commands/soft_deactivate_users.py b/zerver/management/commands/soft_deactivate_users.py index 7bea21846c..edd1afc710 100644 --- a/zerver/management/commands/soft_deactivate_users.py +++ b/zerver/management/commands/soft_deactivate_users.py @@ -1,6 +1,6 @@ import sys from argparse import ArgumentParser -from typing import Any, Dict, List +from typing import Any from django.conf import settings from django.core.management.base import CommandError @@ -16,7 +16,7 @@ from zerver.lib.soft_deactivation import ( from zerver.models import Realm, UserProfile -def get_users_from_emails(emails: List[str], filter_kwargs: Dict[str, Realm]) -> List[UserProfile]: +def get_users_from_emails(emails: list[str], filter_kwargs: dict[str, Realm]) -> list[UserProfile]: # Bug: Ideally, this would be case-insensitive like our other email queries. users = list(UserProfile.objects.filter(delivery_email__in=emails, **filter_kwargs)) @@ -68,7 +68,7 @@ class Command(ZulipBaseCommand): activate = options["activate"] deactivate = options["deactivate"] - filter_kwargs: Dict[str, Realm] = {} + filter_kwargs: dict[str, Realm] = {} if realm is not None: filter_kwargs = dict(realm=realm) diff --git a/zerver/middleware.py b/zerver/middleware.py index d51c869dbe..2ba73086fe 100644 --- a/zerver/middleware.py +++ b/zerver/middleware.py @@ -2,7 +2,7 @@ import cProfile import logging import tempfile import time -from typing import Any, Callable, Dict, List, MutableMapping, Optional, Tuple +from typing import Any, Callable, MutableMapping, Optional from urllib.parse import urlencode, urljoin from django.conf import settings @@ -238,14 +238,14 @@ def parse_client( req_client: Annotated[ Optional[str], ApiParamConfig("client", documentation_status=INTENTIONALLY_UNDOCUMENTED) ] = None, -) -> Tuple[str, Optional[str]]: +) -> tuple[str, Optional[str]]: # If the API request specified a client in the request content, # that has priority. Otherwise, extract the client from the # USER_AGENT. if req_client is not None: return req_client, None if "User-Agent" in request.headers: - user_agent: Optional[Dict[str, str]] = parse_user_agent(request.headers["User-Agent"]) + user_agent: Optional[dict[str, str]] = parse_user_agent(request.headers["User-Agent"]) else: user_agent = None if user_agent is None: @@ -299,8 +299,8 @@ class LogRequests(MiddlewareMixin): self, request: HttpRequest, view_func: Callable[Concatenate[HttpRequest, ParamT], HttpResponseBase], - args: List[object], - kwargs: Dict[str, Any], + args: list[object], + kwargs: dict[str, Any], ) -> None: request_notes = RequestNotes.get_notes(request) if request_notes.saved_response is not None: @@ -426,8 +426,8 @@ class TagRequests(MiddlewareMixin): self, request: HttpRequest, view_func: Callable[Concatenate[HttpRequest, ParamT], HttpResponseBase], - args: List[object], - kwargs: Dict[str, Any], + args: list[object], + kwargs: dict[str, Any], ) -> None: self.process_request(request) @@ -498,7 +498,7 @@ class LocaleMiddleware(DjangoLocaleMiddleware): class RateLimitMiddleware(MiddlewareMixin): def set_response_headers( - self, response: HttpResponseBase, rate_limit_results: List[RateLimitResult] + self, response: HttpResponseBase, rate_limit_results: list[RateLimitResult] ) -> None: # The limit on the action that was requested is the minimum of the limits that get applied: limit = min(result.entity.max_api_calls() for result in rate_limit_results) @@ -631,8 +631,8 @@ class DetectProxyMisconfiguration(MiddlewareMixin): self, request: HttpRequest, view_func: Callable[Concatenate[HttpRequest, ParamT], HttpResponseBase], - args: List[object], - kwargs: Dict[str, Any], + args: list[object], + kwargs: dict[str, Any], ) -> None: proxy_state_header = request.headers.get("X-Proxy-Misconfiguration", "") # Our nginx configuration sets this header if: diff --git a/zerver/migrations/0043_realm_filter_validators.py b/zerver/migrations/0043_realm_filter_validators.py index 7896371256..dd94f3f7b5 100644 --- a/zerver/migrations/0043_realm_filter_validators.py +++ b/zerver/migrations/0043_realm_filter_validators.py @@ -1,5 +1,3 @@ -from typing import List - from django.db import migrations @@ -8,4 +6,4 @@ class Migration(migrations.Migration): ("zerver", "0042_attachment_file_name_length"), ] - operations: List[migrations.operations.base.Operation] = [] + operations: list[migrations.operations.base.Operation] = [] diff --git a/zerver/migrations/0094_realm_filter_url_validator.py b/zerver/migrations/0094_realm_filter_url_validator.py index 3b9962d158..d2c3a8d5fc 100644 --- a/zerver/migrations/0094_realm_filter_url_validator.py +++ b/zerver/migrations/0094_realm_filter_url_validator.py @@ -1,6 +1,5 @@ # Generated by Django 1.11.2 on 2017-07-22 13:44 -from typing import List from django.db import migrations @@ -10,4 +9,4 @@ class Migration(migrations.Migration): ("zerver", "0093_subscription_event_log_backfill"), ] - operations: List[migrations.operations.base.Operation] = [] + operations: list[migrations.operations.base.Operation] = [] diff --git a/zerver/migrations/0127_disallow_chars_in_stream_and_user_name.py b/zerver/migrations/0127_disallow_chars_in_stream_and_user_name.py index be5ec990bd..a0f10813e6 100644 --- a/zerver/migrations/0127_disallow_chars_in_stream_and_user_name.py +++ b/zerver/migrations/0127_disallow_chars_in_stream_and_user_name.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any from django.db import migrations @@ -8,7 +8,7 @@ class Migration(migrations.Migration): ("zerver", "0126_prereg_remove_users_without_realm"), ] - operations: List[Any] = [ + operations: list[Any] = [ # There was a migration here, which wasn't ready for wide deployment # and was backed out. This placeholder is left behind to avoid # confusing the migration engine on any installs that applied the diff --git a/zerver/migrations/0145_reactions_realm_emoji_name_to_id.py b/zerver/migrations/0145_reactions_realm_emoji_name_to_id.py index 12322ceef5..f4f03ea841 100644 --- a/zerver/migrations/0145_reactions_realm_emoji_name_to_id.py +++ b/zerver/migrations/0145_reactions_realm_emoji_name_to_id.py @@ -1,5 +1,5 @@ from collections import defaultdict -from typing import Any, Dict +from typing import Any from django.db import migrations from django.db.backends.base.schema import BaseDatabaseSchemaEditor @@ -9,7 +9,7 @@ from django.db.migrations.state import StateApps def realm_emoji_name_to_id(apps: StateApps, schema_editor: BaseDatabaseSchemaEditor) -> None: Reaction = apps.get_model("zerver", "Reaction") RealmEmoji = apps.get_model("zerver", "RealmEmoji") - realm_emoji_by_realm_id: Dict[int, Dict[str, Any]] = defaultdict(dict) + realm_emoji_by_realm_id: dict[int, dict[str, Any]] = defaultdict(dict) for realm_emoji in RealmEmoji.objects.all(): realm_emoji_by_realm_id[realm_emoji.realm_id][realm_emoji.name] = { "id": str(realm_emoji.id), diff --git a/zerver/migrations/0209_user_profile_no_empty_password.py b/zerver/migrations/0209_user_profile_no_empty_password.py index 67276d2e9e..b1d91db416 100644 --- a/zerver/migrations/0209_user_profile_no_empty_password.py +++ b/zerver/migrations/0209_user_profile_no_empty_password.py @@ -1,6 +1,6 @@ # Generated by Django 1.11.24 on 2019-10-16 22:48 -from typing import Any, Set, Union +from typing import Any, Union import orjson from django.conf import settings @@ -87,8 +87,8 @@ def ensure_no_empty_passwords(apps: StateApps, schema_editor: BaseDatabaseSchema "modified_user_id", flat=True ) ) - password_change_user_ids_api_key_reset_needed: Set[int] = set() - password_change_user_ids_no_reset_needed: Set[int] = set() + password_change_user_ids_api_key_reset_needed: set[int] = set() + password_change_user_ids_no_reset_needed: set[int] = set() for user_id in password_change_user_ids: # Here, we check the timing for users who have changed diff --git a/zerver/migrations/0224_alter_field_realm_video_chat_provider.py b/zerver/migrations/0224_alter_field_realm_video_chat_provider.py index a897ea58e8..b29bf3bedf 100644 --- a/zerver/migrations/0224_alter_field_realm_video_chat_provider.py +++ b/zerver/migrations/0224_alter_field_realm_video_chat_provider.py @@ -1,6 +1,6 @@ # Generated by Django 1.11.20 on 2019-05-09 06:54 -from typing import Any, Dict, Optional +from typing import Any, Optional from django.db import migrations, models from django.db.backends.base.schema import BaseDatabaseSchemaEditor @@ -25,10 +25,10 @@ VIDEO_CHAT_PROVIDERS = { def get_video_chat_provider_detail( - providers_dict: Dict[str, Dict[str, Any]], + providers_dict: dict[str, dict[str, Any]], p_name: Optional[str] = None, p_id: Optional[int] = None, -) -> Dict[str, Any]: +) -> dict[str, Any]: for provider in providers_dict.values(): if p_name and provider["name"] == p_name: return provider diff --git a/zerver/migrations/0254_merge_0209_0253.py b/zerver/migrations/0254_merge_0209_0253.py index 1e98d43506..29f7b7074a 100644 --- a/zerver/migrations/0254_merge_0209_0253.py +++ b/zerver/migrations/0254_merge_0209_0253.py @@ -1,6 +1,6 @@ # Generated by Django 1.11.26 on 2019-11-21 01:47 -from typing import Any, List +from typing import Any from django.db import migrations @@ -11,4 +11,4 @@ class Migration(migrations.Migration): ("zerver", "0209_user_profile_no_empty_password"), ] - operations: List[Any] = [] + operations: list[Any] = [] diff --git a/zerver/migrations/0277_migrate_alert_word.py b/zerver/migrations/0277_migrate_alert_word.py index 6e61d7a597..e448c23881 100644 --- a/zerver/migrations/0277_migrate_alert_word.py +++ b/zerver/migrations/0277_migrate_alert_word.py @@ -1,5 +1,3 @@ -from typing import Dict, List - import orjson from django.db import migrations from django.db.backends.base.schema import BaseDatabaseSchemaEditor @@ -14,7 +12,7 @@ def move_to_separate_table(apps: StateApps, schema_editor: BaseDatabaseSchemaEdi list_of_words = orjson.loads(user_profile.alert_words) # Remove duplicates with our case-insensitive model. - word_dict: Dict[str, str] = {} + word_dict: dict[str, str] = {} for word in list_of_words: word_dict[word.lower()] = word @@ -29,7 +27,7 @@ def move_back_to_user_profile(apps: StateApps, schema_editor: BaseDatabaseSchema UserProfile = apps.get_model("zerver", "UserProfile") user_ids_and_words = AlertWord.objects.all().values("user_profile_id", "word") - user_ids_with_words: Dict[int, List[str]] = {} + user_ids_with_words: dict[int, list[str]] = {} for id_and_word in user_ids_and_words: user_ids_with_words.setdefault(id_and_word["user_profile_id"], []) diff --git a/zerver/migrations/0284_convert_realm_admins_to_realm_owners.py b/zerver/migrations/0284_convert_realm_admins_to_realm_owners.py index 3e75ca94d0..d17f8f0b4a 100644 --- a/zerver/migrations/0284_convert_realm_admins_to_realm_owners.py +++ b/zerver/migrations/0284_convert_realm_admins_to_realm_owners.py @@ -1,5 +1,5 @@ # Generated by Django 2.2.12 on 2020-05-16 18:34 -from typing import Any, Dict +from typing import Any import orjson from django.db import migrations @@ -27,7 +27,7 @@ def set_realm_admins_as_realm_owners( RealmAuditLog.ROLE_COUNT_HUMANS = "11" RealmAuditLog.ROLE_COUNT_BOTS = "12" - def realm_user_count_by_role(realm: Any) -> Dict[str, Any]: + def realm_user_count_by_role(realm: Any) -> dict[str, Any]: human_counts = { str(UserProfile.ROLE_REALM_ADMINISTRATOR): 0, str(UserProfile.ROLE_REALM_OWNER): 0, diff --git a/zerver/migrations/0286_merge_0260_0285.py b/zerver/migrations/0286_merge_0260_0285.py index 3ad89519ea..520c9c939b 100644 --- a/zerver/migrations/0286_merge_0260_0285.py +++ b/zerver/migrations/0286_merge_0260_0285.py @@ -1,6 +1,6 @@ # Generated by Django 2.2.13 on 2020-06-17 06:26 -from typing import Any, List +from typing import Any from django.db import migrations @@ -11,4 +11,4 @@ class Migration(migrations.Migration): ("zerver", "0285_remove_realm_google_hangouts_domain"), ] - operations: List[Any] = [] + operations: list[Any] = [] diff --git a/zerver/migrations/0310_jsonfield.py b/zerver/migrations/0310_jsonfield.py index 77fea73f9e..cc8c64d7ca 100644 --- a/zerver/migrations/0310_jsonfield.py +++ b/zerver/migrations/0310_jsonfield.py @@ -1,6 +1,5 @@ # Generated by Django 3.1.5 on 2021-01-10 11:30 -from typing import List from django.db import migrations @@ -14,4 +13,4 @@ class Migration(migrations.Migration): ("zerver", "0309_userprofile_can_create_users"), ] - operations: List[migrations.operations.base.Operation] = [] + operations: list[migrations.operations.base.Operation] = [] diff --git a/zerver/migrations/0360_merge_0358_0359.py b/zerver/migrations/0360_merge_0358_0359.py index 67b41e848f..923b2a365f 100644 --- a/zerver/migrations/0360_merge_0358_0359.py +++ b/zerver/migrations/0360_merge_0358_0359.py @@ -1,6 +1,6 @@ # Generated by Django 3.2.7 on 2021-10-04 17:49 -from typing import Any, List +from typing import Any from django.db import migrations @@ -11,4 +11,4 @@ class Migration(migrations.Migration): ("zerver", "0359_re2_linkifiers"), ] - operations: List[Any] = [] + operations: list[Any] = [] diff --git a/zerver/migrations/0368_alter_realmfilter_url_format_string.py b/zerver/migrations/0368_alter_realmfilter_url_format_string.py index 11bd06dd16..6ba0dff264 100644 --- a/zerver/migrations/0368_alter_realmfilter_url_format_string.py +++ b/zerver/migrations/0368_alter_realmfilter_url_format_string.py @@ -1,6 +1,5 @@ # Generated by Django 3.2.8 on 2021-10-20 23:42 -from typing import List from django.db import migrations @@ -10,4 +9,4 @@ class Migration(migrations.Migration): ("zerver", "0367_scimclient"), ] - operations: List[migrations.operations.base.Operation] = [] + operations: list[migrations.operations.base.Operation] = [] diff --git a/zerver/migrations/0377_message_edit_history_format.py b/zerver/migrations/0377_message_edit_history_format.py index 4ffda7e0f0..e2a296854a 100644 --- a/zerver/migrations/0377_message_edit_history_format.py +++ b/zerver/migrations/0377_message_edit_history_format.py @@ -1,5 +1,5 @@ import time -from typing import Any, List, Optional, Type, TypedDict +from typing import Any, Optional, TypedDict import orjson from django.db import migrations, transaction @@ -39,7 +39,7 @@ class EditHistoryEvent(TypedDict, total=False): @transaction.atomic def backfill_message_edit_history_chunk( - first_id: int, last_id: int, message_model: Type[Any] + first_id: int, last_id: int, message_model: type[Any] ) -> None: """ Migrate edit history events for the messages in the provided range to: @@ -61,9 +61,9 @@ def backfill_message_edit_history_chunk( ) for message in messages: - legacy_edit_history: List[LegacyEditHistoryEvent] = orjson.loads(message.edit_history) + legacy_edit_history: list[LegacyEditHistoryEvent] = orjson.loads(message.edit_history) message_type = message.recipient.type - modern_edit_history: List[EditHistoryEvent] = [] + modern_edit_history: list[EditHistoryEvent] = [] # Only Stream messages have topic / stream edit history data. if message_type == STREAM: diff --git a/zerver/migrations/0383_revoke_invitations_from_deactivated_users.py b/zerver/migrations/0383_revoke_invitations_from_deactivated_users.py index e57d2ba9fa..3a40b9741f 100644 --- a/zerver/migrations/0383_revoke_invitations_from_deactivated_users.py +++ b/zerver/migrations/0383_revoke_invitations_from_deactivated_users.py @@ -1,5 +1,3 @@ -from typing import List - from django.db import migrations from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.migrations.state import StateApps @@ -18,8 +16,8 @@ def revoke_invitations(apps: StateApps, schema_editor: BaseDatabaseSchemaEditor) STATUS_REVOKED = 2 def get_valid_invite_confirmations_generated_by_users( - user_ids: List[int], - ) -> List[int]: + user_ids: list[int], + ) -> list[int]: prereg_user_ids = ( PreregistrationUser.objects.filter(referred_by_id__in=user_ids) .exclude(status=STATUS_REVOKED) diff --git a/zerver/migrations/0386_fix_attachment_caches.py b/zerver/migrations/0386_fix_attachment_caches.py index be0ad42e06..0c6a8b74c2 100644 --- a/zerver/migrations/0386_fix_attachment_caches.py +++ b/zerver/migrations/0386_fix_attachment_caches.py @@ -1,6 +1,5 @@ # Generated by Django 3.2.12 on 2022-03-23 04:32 -from typing import Type from django.db import migrations, models from django.db.backends.base.schema import BaseDatabaseSchemaEditor @@ -17,7 +16,7 @@ def fix_attachment_caches(apps: StateApps, schema_editor: BaseDatabaseSchemaEdit BATCH_SIZE = 10000 def update_batch( - attachment_model: Type[Model], message_model: Type[Model], lower_bound: int + attachment_model: type[Model], message_model: type[Model], lower_bound: int ) -> None: attachment_model._default_manager.filter( id__gt=lower_bound, id__lte=lower_bound + BATCH_SIZE diff --git a/zerver/migrations/0460_backfill_realmauditlog_extradata_to_json_field.py b/zerver/migrations/0460_backfill_realmauditlog_extradata_to_json_field.py index ffdade73e8..882bd41887 100644 --- a/zerver/migrations/0460_backfill_realmauditlog_extradata_to_json_field.py +++ b/zerver/migrations/0460_backfill_realmauditlog_extradata_to_json_field.py @@ -1,7 +1,6 @@ # Generated by Django 4.0.7 on 2022-09-30 20:30 import ast -from typing import List, Tuple, Type import orjson from django.db import migrations, transaction @@ -37,7 +36,7 @@ OVERWRITE_TEMPLATE = """Audit log entry with id {id} has extra_data_json been in @transaction.atomic def do_bulk_backfill_extra_data( - audit_log_model: Type[Model], id_lower_bound: int, id_upper_bound: int + audit_log_model: type[Model], id_lower_bound: int, id_upper_bound: int ) -> None: # First handle the special case for audit logs with the # USER_FULL_NAME_CHANGED event, which stores the full name not as @@ -60,7 +59,7 @@ def do_bulk_backfill_extra_data( # https://docs.djangoproject.com/en/5.0/ref/models/database-functions/#jsonobject ).update(extra_data_json=JSONObject(**{OLD_VALUE: "extra_data", NEW_VALUE: None})) - inconsistent_extra_data_json: List[Tuple[int, str, object, object]] = [] + inconsistent_extra_data_json: list[tuple[int, str, object, object]] = [] # A dict converted with str() will start with a open bracket followed by a # single quote, as opposed to a JSON-encoded value, which will use a # _double_ quote. We use this to filter out those entries with malformed diff --git a/zerver/migrations/0509_fix_emoji_metadata.py b/zerver/migrations/0509_fix_emoji_metadata.py index 68f3ea0d0b..8c8610290f 100644 --- a/zerver/migrations/0509_fix_emoji_metadata.py +++ b/zerver/migrations/0509_fix_emoji_metadata.py @@ -1,5 +1,5 @@ import re -from typing import Any, Dict +from typing import Any import boto3 from botocore.client import Config @@ -40,7 +40,7 @@ def fix_emoji_metadata(apps: StateApps, schema_editor: BaseDatabaseSchemaEditor) ) assert fallback_owner is not None - emoji_by_filename: Dict[str, Any] = {} + emoji_by_filename: dict[str, Any] = {} for emoji in RealmEmoji.objects.filter(realm_id=realm.id).exclude(file_name=None): assert emoji.file_name is not None if emoji.author_id is None: diff --git a/zerver/migrations/0553_copy_emoji_images.py b/zerver/migrations/0553_copy_emoji_images.py index ca05b0ad00..ae083bd56f 100644 --- a/zerver/migrations/0553_copy_emoji_images.py +++ b/zerver/migrations/0553_copy_emoji_images.py @@ -2,7 +2,7 @@ import contextlib import hashlib import logging import os -from typing import Any, Iterator, Optional, Tuple +from typing import Any, Iterator, Optional import boto3 import botocore @@ -56,7 +56,7 @@ def libvips_check_image(image_data: bytes) -> Iterator[pyvips.Image]: # From zerver.lib.thumbnail, with minor exception changes def resize_emoji( image_data: bytes, emoji_file_name: str, size: int = DEFAULT_EMOJI_SIZE -) -> Tuple[bytes, Optional[bytes]]: +) -> tuple[bytes, Optional[bytes]]: if len(image_data) > MAX_EMOJI_GIF_FILE_SIZE_BYTES: raise SkipImageError(f"Image has too many bytes: {len(image_data)}") diff --git a/zerver/models/bots.py b/zerver/models/bots.py index 803d1d6c91..4a684ea2e6 100644 --- a/zerver/models/bots.py +++ b/zerver/models/bots.py @@ -1,5 +1,3 @@ -from typing import Dict, List - from django.db import models from django.db.models import CASCADE @@ -46,7 +44,7 @@ class Service(models.Model): SLACK, ] # N.B. If we used Django's choice=... we would get this for free (kinda) - _interfaces: Dict[int, str] = { + _interfaces: dict[int, str] = { GENERIC: GENERIC_INTERFACE, SLACK: SLACK_INTERFACE, } @@ -56,7 +54,7 @@ class Service(models.Model): return self._interfaces[self.interface] -def get_bot_services(user_profile_id: int) -> List[Service]: +def get_bot_services(user_profile_id: int) -> list[Service]: return list(Service.objects.filter(user_profile_id=user_profile_id)) diff --git a/zerver/models/clients.py b/zerver/models/clients.py index 114c2c5b20..0c6ae06388 100644 --- a/zerver/models/clients.py +++ b/zerver/models/clients.py @@ -1,5 +1,4 @@ import hashlib -from typing import Dict from django.conf import settings from django.db import models @@ -50,7 +49,7 @@ class Client(models.Model): ) -get_client_cache: Dict[str, Client] = {} +get_client_cache: dict[str, Client] = {} def clear_client_cache() -> None: # nocoverage diff --git a/zerver/models/custom_profile_fields.py b/zerver/models/custom_profile_fields.py index 46fe48753f..7cc3339b81 100644 --- a/zerver/models/custom_profile_fields.py +++ b/zerver/models/custom_profile_fields.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Dict, List, Tuple +from typing import Any, Callable import orjson from django.core.exceptions import ValidationError @@ -32,7 +32,7 @@ from zerver.models.realms import Realm from zerver.models.users import UserProfile -def check_valid_user_ids(realm_id: int, val: object, allow_deactivated: bool = False) -> List[int]: +def check_valid_user_ids(realm_id: int, val: object, allow_deactivated: bool = False) -> list[int]: user_ids = check_list(check_int)("User IDs", val) user_profiles = UserProfile.objects.filter(realm_id=realm_id, id__in=user_ids) @@ -93,21 +93,21 @@ class CustomProfileField(models.Model): # These are the fields whose validators require more than var_name # and value argument. i.e. SELECT require field_data, USER require # realm as argument. - SELECT_FIELD_TYPE_DATA: List[ExtendedFieldElement] = [ + SELECT_FIELD_TYPE_DATA: list[ExtendedFieldElement] = [ (SELECT, gettext_lazy("List of options"), validate_select_field, str, "SELECT"), ] - USER_FIELD_TYPE_DATA: List[UserFieldElement] = [ + USER_FIELD_TYPE_DATA: list[UserFieldElement] = [ (USER, gettext_lazy("Users"), check_valid_user_ids, orjson.loads, "USER"), ] - SELECT_FIELD_VALIDATORS: Dict[int, ExtendedValidator] = { + SELECT_FIELD_VALIDATORS: dict[int, ExtendedValidator] = { item[0]: item[2] for item in SELECT_FIELD_TYPE_DATA } - USER_FIELD_VALIDATORS: Dict[int, RealmUserValidator] = { + USER_FIELD_VALIDATORS: dict[int, RealmUserValidator] = { item[0]: item[2] for item in USER_FIELD_TYPE_DATA } - FIELD_TYPE_DATA: List[FieldElement] = [ + FIELD_TYPE_DATA: list[FieldElement] = [ # Type, display name, validator, converter, keyword (SHORT_TEXT, gettext_lazy("Text (short)"), check_short_string, str, "SHORT_TEXT"), (LONG_TEXT, gettext_lazy("Text (long)"), check_long_string, str, "LONG_TEXT"), @@ -127,13 +127,13 @@ class CustomProfileField(models.Model): [*FIELD_TYPE_DATA, *SELECT_FIELD_TYPE_DATA, *USER_FIELD_TYPE_DATA], key=lambda x: x[1] ) - FIELD_VALIDATORS: Dict[int, Validator[ProfileDataElementValue]] = { + FIELD_VALIDATORS: dict[int, Validator[ProfileDataElementValue]] = { item[0]: item[2] for item in FIELD_TYPE_DATA } - FIELD_CONVERTERS: Dict[int, Callable[[Any], Any]] = { + FIELD_CONVERTERS: dict[int, Callable[[Any], Any]] = { item[0]: item[3] for item in ALL_FIELD_TYPES } - FIELD_TYPE_CHOICES: List[Tuple[int, StrPromise]] = [ + FIELD_TYPE_CHOICES: list[tuple[int, StrPromise]] = [ (item[0], item[1]) for item in ALL_FIELD_TYPES ] diff --git a/zerver/models/drafts.py b/zerver/models/drafts.py index 614adf7895..8cc340bb2c 100644 --- a/zerver/models/drafts.py +++ b/zerver/models/drafts.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from django.db import models from typing_extensions import override @@ -24,7 +24,7 @@ class Draft(models.Model): def __str__(self) -> str: return f"{self.user_profile.email} / {self.id} / {self.last_edit_time}" - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: to, recipient_type_str = get_recipient_ids(self.recipient, self.user_profile_id) return { "id": self.id, diff --git a/zerver/models/linkifiers.py b/zerver/models/linkifiers.py index fca07e4af6..3f10d816d6 100644 --- a/zerver/models/linkifiers.py +++ b/zerver/models/linkifiers.py @@ -1,4 +1,4 @@ -from typing import List, Pattern +from typing import Pattern import re2 import uri_template @@ -114,7 +114,7 @@ def get_linkifiers_cache_key(realm_id: int) -> str: @return_same_value_during_entire_request @cache_with_key(get_linkifiers_cache_key, timeout=3600 * 24 * 7) -def linkifiers_for_realm(realm_id: int) -> List[LinkifierDict]: +def linkifiers_for_realm(realm_id: int) -> list[LinkifierDict]: return [ LinkifierDict( pattern=linkifier.pattern, diff --git a/zerver/models/lookups.py b/zerver/models/lookups.py index 484cc889ae..ad308c85ed 100644 --- a/zerver/models/lookups.py +++ b/zerver/models/lookups.py @@ -1,4 +1,4 @@ -from typing import List, Tuple, Union +from typing import Union from django.db import models from django.db.backends.base.base import BaseDatabaseWrapper @@ -13,7 +13,7 @@ class AndZero(models.Lookup[int]): @override def as_sql( self, compiler: SQLCompiler, connection: BaseDatabaseWrapper - ) -> Tuple[str, List[Union[str, int]]]: # nocoverage # currently only used in migrations + ) -> tuple[str, list[Union[str, int]]]: # nocoverage # currently only used in migrations lhs, lhs_params = self.process_lhs(compiler, connection) rhs, rhs_params = self.process_rhs(compiler, connection) return f"{lhs} & {rhs} = 0", lhs_params + rhs_params @@ -26,7 +26,7 @@ class AndNonZero(models.Lookup[int]): @override def as_sql( self, compiler: SQLCompiler, connection: BaseDatabaseWrapper - ) -> Tuple[str, List[Union[str, int]]]: # nocoverage # currently only used in migrations + ) -> tuple[str, list[Union[str, int]]]: # nocoverage # currently only used in migrations lhs, lhs_params = self.process_lhs(compiler, connection) rhs, rhs_params = self.process_rhs(compiler, connection) return f"{lhs} & {rhs} != 0", lhs_params + rhs_params diff --git a/zerver/models/messages.py b/zerver/models/messages.py index 6b2b9d8c18..2d32a45ce3 100644 --- a/zerver/models/messages.py +++ b/zerver/models/messages.py @@ -3,7 +3,7 @@ import time from datetime import timedelta -from typing import Any, Dict, List, Optional +from typing import Any, Optional from bitfield import BitField from bitfield.types import Bit, BitHandler @@ -316,7 +316,7 @@ class SubMessage(AbstractSubMessage): message = models.ForeignKey(Message, on_delete=CASCADE) @staticmethod - def get_raw_db_rows(needed_ids: List[int]) -> List[Dict[str, Any]]: + def get_raw_db_rows(needed_ids: list[int]) -> list[dict[str, Any]]: fields = ["id", "message_id", "sender_id", "msg_type", "content"] query = SubMessage.objects.filter(message_id__in=needed_ids).values(*fields) query = query.order_by("message_id", "id") @@ -395,7 +395,7 @@ class Reaction(AbstractReaction): return f"{self.user_profile.email} / {self.message.id} / {self.emoji_name}" @staticmethod - def get_raw_db_rows(needed_ids: List[int]) -> List[Dict[str, Any]]: + def get_raw_db_rows(needed_ids: list[int]) -> list[dict[str, Any]]: fields = [ "message_id", "emoji_name", @@ -527,12 +527,12 @@ class AbstractUserMessage(models.Model): AbstractUserMessage.flags.active_mobile_push_notification ) - def flags_list(self) -> List[str]: + def flags_list(self) -> list[str]: flags = int(self.flags) return self.flags_list_for_flags(flags) @staticmethod - def flags_list_for_flags(val: int) -> List[str]: + def flags_list_for_flags(val: int) -> list[str]: """ This function is highly optimized, because it actually slows down sending messages in a naive implementation. @@ -752,7 +752,7 @@ class Attachment(AbstractAttachment): def is_claimed(self) -> bool: return self.messages.exists() or self.scheduled_messages.exists() - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return { "id": self.id, "name": self.file_name, diff --git a/zerver/models/realm_emoji.py b/zerver/models/realm_emoji.py index 02766beeab..faa3292bf6 100644 --- a/zerver/models/realm_emoji.py +++ b/zerver/models/realm_emoji.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional, TypedDict +from typing import Optional, TypedDict from django.core.validators import MinLengthValidator, RegexValidator from django.db import models @@ -69,7 +69,7 @@ class RealmEmoji(models.Model): return f"{self.realm.string_id}: {self.id} {self.name} {self.deactivated} {self.file_name}" -def get_all_custom_emoji_for_realm_uncached(realm_id: int) -> Dict[str, EmojiInfo]: +def get_all_custom_emoji_for_realm_uncached(realm_id: int) -> dict[str, EmojiInfo]: # RealmEmoji objects with file_name=None are still in the process # of being uploaded, and we expect to be cleaned up by a # try/finally block if the upload fails, so it's correct to @@ -109,11 +109,11 @@ def get_all_custom_emoji_for_realm_uncached(realm_id: int) -> Dict[str, EmojiInf @cache_with_key(get_all_custom_emoji_for_realm_cache_key, timeout=3600 * 24 * 7) -def get_all_custom_emoji_for_realm(realm_id: int) -> Dict[str, EmojiInfo]: +def get_all_custom_emoji_for_realm(realm_id: int) -> dict[str, EmojiInfo]: return get_all_custom_emoji_for_realm_uncached(realm_id) -def get_name_keyed_dict_for_active_realm_emoji(realm_id: int) -> Dict[str, EmojiInfo]: +def get_name_keyed_dict_for_active_realm_emoji(realm_id: int) -> dict[str, EmojiInfo]: # It's important to use the cached version here. realm_emojis = get_all_custom_emoji_for_realm(realm_id) return {row["name"]: row for row in realm_emojis.values() if not row["deactivated"]} diff --git a/zerver/models/realm_playgrounds.py b/zerver/models/realm_playgrounds.py index 92b8df125e..ddd297b910 100644 --- a/zerver/models/realm_playgrounds.py +++ b/zerver/models/realm_playgrounds.py @@ -1,5 +1,3 @@ -from typing import List - import uri_template from django.core.exceptions import ValidationError from django.core.validators import RegexValidator @@ -76,7 +74,7 @@ class RealmPlayground(models.Model): ) -def get_realm_playgrounds(realm: Realm) -> List[RealmPlaygroundDict]: +def get_realm_playgrounds(realm: Realm) -> list[RealmPlaygroundDict]: return [ RealmPlaygroundDict( id=playground.id, diff --git a/zerver/models/realms.py b/zerver/models/realms.py index b4b0daa6c3..4d543f8d38 100644 --- a/zerver/models/realms.py +++ b/zerver/models/realms.py @@ -1,6 +1,6 @@ from email.headerregistry import Address from enum import IntEnum -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, TypedDict, Union +from typing import TYPE_CHECKING, Optional, TypedDict, Union from uuid import uuid4 import django.contrib.auth @@ -37,10 +37,10 @@ SECONDS_PER_DAY = 86400 # these values cannot change in a running production system, but do # regularly change within unit tests; we address the latter by calling # clear_supported_auth_backends_cache in our standard tearDown code. -supported_backends: Optional[List["BaseBackend"]] = None +supported_backends: Optional[list["BaseBackend"]] = None -def supported_auth_backends() -> List["BaseBackend"]: +def supported_auth_backends() -> list["BaseBackend"]: global supported_backends # Caching temporarily disabled for debugging supported_backends = django.contrib.auth.get_backends() @@ -439,7 +439,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub first_visible_message_id = models.IntegerField(default=0) # Valid org types - ORG_TYPES: Dict[str, OrgTypeDict] = { + ORG_TYPES: dict[str, OrgTypeDict] = { "unspecified": { "name": "Unspecified", "id": OrgTypeEnum.Unspecified.value, @@ -533,7 +533,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub }, } - ORG_TYPE_IDS: List[int] = [t["id"] for t in ORG_TYPES.values()] + ORG_TYPE_IDS: list[int] = [t["id"] for t in ORG_TYPES.values()] org_type = models.PositiveSmallIntegerField( default=ORG_TYPES["unspecified"]["id"], @@ -640,7 +640,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub enable_guest_user_indicator = models.BooleanField(default=True) # Define the types of the various automatically managed properties - property_types: Dict[str, Union[type, Tuple[type, ...]]] = dict( + property_types: dict[str, Union[type, tuple[type, ...]]] = dict( add_custom_emoji_policy=int, allow_edit_history=bool, allow_message_editing=bool, @@ -687,7 +687,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub wildcard_mention_policy=int, ) - REALM_PERMISSION_GROUP_SETTINGS: Dict[str, GroupPermissionSetting] = dict( + REALM_PERMISSION_GROUP_SETTINGS: dict[str, GroupPermissionSetting] = dict( create_multiuse_invite_group=GroupPermissionSetting( require_system_group=True, allow_internet_group=False, @@ -793,7 +793,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub def __str__(self) -> str: return f"{self.string_id} {self.id}" - def get_giphy_rating_options(self) -> Dict[str, Dict[str, object]]: + def get_giphy_rating_options(self) -> dict[str, dict[str, object]]: """Wrapper function for GIPHY_RATING_OPTIONS that ensures evaluation of the lazily evaluated `name` field without modifying the original.""" return { @@ -801,7 +801,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub for rating_type, rating in self.GIPHY_RATING_OPTIONS.items() } - def authentication_methods_dict(self) -> Dict[str, bool]: + def authentication_methods_dict(self) -> dict[str, bool]: """Returns the mapping from authentication flags to their status, showing only those authentication flags that are supported on the current server (i.e. if EmailAuthBackend is not configured @@ -810,7 +810,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub # dependency. from zproject.backends import AUTH_BACKEND_NAME_MAP - ret: Dict[str, bool] = {} + ret: dict[str, bool] = {} supported_backends = [type(backend) for backend in supported_auth_backends()] for backend_name, backend_class in AUTH_BACKEND_NAME_MAP.items(): @@ -1148,7 +1148,7 @@ def get_org_type_display_name(org_type: int) -> str: def get_corresponding_policy_value_for_group_setting( realm: Realm, group_setting_name: str, - valid_policy_enums: List[int], + valid_policy_enums: list[int], ) -> int: setting_group = getattr(realm, group_setting_name) if ( @@ -1196,7 +1196,7 @@ class RealmDomainDict(TypedDict): allow_subdomains: bool -def get_realm_domains(realm: Realm) -> List[RealmDomainDict]: +def get_realm_domains(realm: Realm) -> list[RealmDomainDict]: return list(realm.realmdomain_set.values("domain", "allow_subdomains")) diff --git a/zerver/models/recipients.py b/zerver/models/recipients.py index 553ede0253..b7546122fb 100644 --- a/zerver/models/recipients.py +++ b/zerver/models/recipients.py @@ -1,6 +1,6 @@ import hashlib from collections import defaultdict -from typing import TYPE_CHECKING, Dict, List, Set +from typing import TYPE_CHECKING from django.db import models, transaction from django_stubs_ext import ValuesQuerySet @@ -89,7 +89,7 @@ def get_direct_message_group_user_ids(recipient: Recipient) -> ValuesQuerySet["S ) -def bulk_get_direct_message_group_user_ids(recipient_ids: List[int]) -> Dict[int, Set[int]]: +def bulk_get_direct_message_group_user_ids(recipient_ids: list[int]) -> dict[int, set[int]]: """ Takes a list of huddle-type recipient_ids, returns a dict mapping recipient id to list of user ids in the huddle. @@ -107,7 +107,7 @@ def bulk_get_direct_message_group_user_ids(recipient_ids: List[int]) -> Dict[int recipient_id__in=recipient_ids, ).only("user_profile_id", "recipient_id") - result_dict: Dict[int, Set[int]] = defaultdict(set) + result_dict: dict[int, set[int]] = defaultdict(set) for subscription in subscriptions: result_dict[subscription.recipient_id].add(subscription.user_profile_id) @@ -142,13 +142,13 @@ class DirectMessageGroup(models.Model): db_table = "zerver_huddle" -def get_direct_message_group_hash(id_list: List[int]) -> str: +def get_direct_message_group_hash(id_list: list[int]) -> str: id_list = sorted(set(id_list)) hash_key = ",".join(str(x) for x in id_list) return hashlib.sha1(hash_key.encode()).hexdigest() -def get_or_create_direct_message_group(id_list: List[int]) -> DirectMessageGroup: +def get_or_create_direct_message_group(id_list: list[int]) -> DirectMessageGroup: """ Takes a list of user IDs and returns the DirectMessageGroup object for the group consisting of these users. If the diff --git a/zerver/models/scheduled_jobs.py b/zerver/models/scheduled_jobs.py index deea50f7f3..7fe7d38683 100644 --- a/zerver/models/scheduled_jobs.py +++ b/zerver/models/scheduled_jobs.py @@ -1,7 +1,7 @@ # https://github.com/typeddjango/django-stubs/issues/1698 # mypy: disable-error-code="explicit-override" -from typing import List, TypedDict, Union +from typing import TypedDict, Union from django.conf import settings from django.db import models @@ -133,7 +133,7 @@ class APIScheduledStreamMessageDict(TypedDict): class APIScheduledDirectMessageDict(TypedDict): scheduled_message_id: int - to: List[int] + to: list[int] type: str content: str rendered_content: str diff --git a/zerver/models/streams.py b/zerver/models/streams.py index 8e475f8d9b..06b9a71f7e 100644 --- a/zerver/models/streams.py +++ b/zerver/models/streams.py @@ -1,5 +1,5 @@ import secrets -from typing import Any, Dict, Set +from typing import Any from django.db import models from django.db.models import CASCADE, Q, QuerySet @@ -39,7 +39,7 @@ class Stream(models.Model): recipient = models.ForeignKey(Recipient, null=True, on_delete=models.SET_NULL) # Various permission policy configurations - PERMISSION_POLICIES: Dict[str, Dict[str, Any]] = { + PERMISSION_POLICIES: dict[str, dict[str, Any]] = { "web_public": { "invite_only": False, "history_public_to_subscribers": True, @@ -87,7 +87,7 @@ class Stream(models.Model): # Who in the organization has permission to send messages to this stream. stream_post_policy = models.PositiveSmallIntegerField(default=STREAM_POST_POLICY_EVERYONE) - POST_POLICIES: Dict[int, StrPromise] = { + POST_POLICIES: dict[int, StrPromise] = { # These strings should match the strings in the # stream_post_policy_values object in stream_data.js. STREAM_POST_POLICY_EVERYONE: gettext_lazy("All channel members can post"), @@ -252,8 +252,8 @@ def get_stream_by_id_in_realm(stream_id: int, realm: Realm) -> Stream: return Stream.objects.select_related("realm", "recipient").get(id=stream_id, realm=realm) -def bulk_get_streams(realm: Realm, stream_names: Set[str]) -> Dict[str, Any]: - def fetch_streams_by_name(stream_names: Set[str]) -> QuerySet[Stream]: +def bulk_get_streams(realm: Realm, stream_names: set[str]) -> dict[str, Any]: + def fetch_streams_by_name(stream_names: set[str]) -> QuerySet[Stream]: # # This should be just # @@ -377,7 +377,7 @@ class DefaultStreamGroup(models.Model): class Meta: unique_together = ("realm", "name") - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return dict( name=self.name, id=self.id, diff --git a/zerver/models/users.py b/zerver/models/users.py index 2ee072b6be..f156785969 100644 --- a/zerver/models/users.py +++ b/zerver/models/users.py @@ -2,7 +2,7 @@ # mypy: disable-error-code="explicit-override" from email.headerregistry import Address -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set +from typing import TYPE_CHECKING, Any, Optional from uuid import uuid4 from django.conf import settings @@ -354,7 +354,7 @@ class UserBaseSettings(models.Model): web_navigate_to_sent_message=bool, ) - modern_notification_settings: Dict[str, Any] = dict( + modern_notification_settings: dict[str, Any] = dict( # Add new notification settings here. enable_followed_topic_desktop_notifications=bool, enable_followed_topic_email_notifications=bool, @@ -382,7 +382,7 @@ class UserBaseSettings(models.Model): abstract = True @staticmethod - def emojiset_choices() -> List[Dict[str, str]]: + def emojiset_choices() -> list[dict[str, str]]: return [ dict(key=emojiset[0], text=emojiset[1]) for emojiset in UserProfile.EMOJISET_CHOICES ] @@ -746,7 +746,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): return self.bot_type == UserProfile.INCOMING_WEBHOOK_BOT @property - def allowed_bot_types(self) -> List[int]: + def allowed_bot_types(self) -> list[int]: from zerver.models.realms import BotCreationPolicyEnum allowed_bot_types = [] @@ -977,7 +977,7 @@ def get_user_by_delivery_email(email: str, realm: "Realm") -> UserProfile: ).get(delivery_email__iexact=email.strip(), realm=realm) -def get_users_by_delivery_email(emails: Set[str], realm: "Realm") -> QuerySet[UserProfile]: +def get_users_by_delivery_email(emails: set[str], realm: "Realm") -> QuerySet[UserProfile]: """This is similar to get_user_by_delivery_email, and it has the same security caveats. It gets multiple users and returns a QuerySet, since most callers @@ -1084,7 +1084,7 @@ def get_user_by_id_in_realm_including_cross_realm( @cache_with_key(realm_user_dicts_cache_key, timeout=3600 * 24 * 7) -def get_realm_user_dicts(realm_id: int) -> List[RawUserDict]: +def get_realm_user_dicts(realm_id: int) -> list[RawUserDict]: return list( UserProfile.objects.filter( realm_id=realm_id, @@ -1093,7 +1093,7 @@ def get_realm_user_dicts(realm_id: int) -> List[RawUserDict]: @cache_with_key(active_user_ids_cache_key, timeout=3600 * 24 * 7) -def active_user_ids(realm_id: int) -> List[int]: +def active_user_ids(realm_id: int) -> list[int]: query = UserProfile.objects.filter( realm_id=realm_id, is_active=True, @@ -1102,7 +1102,7 @@ def active_user_ids(realm_id: int) -> List[int]: @cache_with_key(active_non_guest_user_ids_cache_key, timeout=3600 * 24 * 7) -def active_non_guest_user_ids(realm_id: int) -> List[int]: +def active_non_guest_user_ids(realm_id: int) -> list[int]: query = ( UserProfile.objects.filter( realm_id=realm_id, @@ -1116,7 +1116,7 @@ def active_non_guest_user_ids(realm_id: int) -> List[int]: return list(query) -def bot_owner_user_ids(user_profile: UserProfile) -> Set[int]: +def bot_owner_user_ids(user_profile: UserProfile) -> set[int]: is_private_bot = ( user_profile.default_sending_stream and user_profile.default_sending_stream.invite_only ) or ( @@ -1143,7 +1143,7 @@ def get_source_profile(email: str, realm_id: int) -> Optional[UserProfile]: @cache_with_key(lambda realm: bot_dicts_in_realm_cache_key(realm.id), timeout=3600 * 24 * 7) -def get_bot_dicts_in_realm(realm: "Realm") -> List[Dict[str, Any]]: +def get_bot_dicts_in_realm(realm: "Realm") -> list[dict[str, Any]]: return list(UserProfile.objects.filter(realm=realm, is_bot=True).values(*bot_dict_fields)) diff --git a/zerver/openapi/curl_param_value_generators.py b/zerver/openapi/curl_param_value_generators.py index 34568cc161..3593165049 100644 --- a/zerver/openapi/curl_param_value_generators.py +++ b/zerver/openapi/curl_param_value_generators.py @@ -6,7 +6,7 @@ # fetching of appropriate parameter values to use when running the # cURL examples as part of the tools/test-api test suite. from functools import wraps -from typing import Any, Callable, Dict, List, Optional, Set, Tuple +from typing import Any, Callable, Optional from django.utils.timezone import now as timezone_now @@ -25,19 +25,19 @@ from zerver.models.realms import get_realm from zerver.models.users import get_user from zerver.openapi.openapi import Parameter -GENERATOR_FUNCTIONS: Dict[str, Callable[[], Dict[str, object]]] = {} -REGISTERED_GENERATOR_FUNCTIONS: Set[str] = set() -CALLED_GENERATOR_FUNCTIONS: Set[str] = set() +GENERATOR_FUNCTIONS: dict[str, Callable[[], dict[str, object]]] = {} +REGISTERED_GENERATOR_FUNCTIONS: set[str] = set() +CALLED_GENERATOR_FUNCTIONS: set[str] = set() # This is a List rather than just a string in order to make it easier # to write to it from another module. -AUTHENTICATION_LINE: List[str] = [""] +AUTHENTICATION_LINE: list[str] = [""] helpers = ZulipTestCase() def openapi_param_value_generator( - endpoints: List[str], -) -> Callable[[Callable[[], Dict[str, object]]], Callable[[], Dict[str, object]]]: + endpoints: list[str], +) -> Callable[[Callable[[], dict[str, object]]], Callable[[], dict[str, object]]]: """This decorator is used to register OpenAPI param value generator functions with endpoints. Example usage: @@ -45,9 +45,9 @@ def openapi_param_value_generator( def ... """ - def wrapper(generator_func: Callable[[], Dict[str, object]]) -> Callable[[], Dict[str, object]]: + def wrapper(generator_func: Callable[[], dict[str, object]]) -> Callable[[], dict[str, object]]: @wraps(generator_func) - def _record_calls_wrapper() -> Dict[str, object]: + def _record_calls_wrapper() -> dict[str, object]: CALLED_GENERATOR_FUNCTIONS.add(generator_func.__name__) return generator_func() @@ -72,13 +72,13 @@ def assert_all_helper_functions_called() -> None: def patch_openapi_example_values( entry: str, - parameters: List[Parameter], - request_body: Optional[Dict[str, Any]] = None, -) -> Tuple[List[Parameter], Optional[Dict[str, object]]]: + parameters: list[Parameter], + request_body: Optional[dict[str, Any]] = None, +) -> tuple[list[Parameter], Optional[dict[str, object]]]: if entry not in GENERATOR_FUNCTIONS: return parameters, request_body func = GENERATOR_FUNCTIONS[entry] - realm_example_values: Dict[str, object] = func() + realm_example_values: dict[str, object] = func() for parameter in parameters: if parameter.name in realm_example_values: @@ -93,7 +93,7 @@ def patch_openapi_example_values( @openapi_param_value_generator(["/fetch_api_key:post"]) -def fetch_api_key() -> Dict[str, object]: +def fetch_api_key() -> dict[str, object]: email = helpers.example_email("iago") password = initial_password(email) @@ -111,7 +111,7 @@ def fetch_api_key() -> Dict[str, object]: "/messages/{message_id}:delete", ] ) -def iago_message_id() -> Dict[str, object]: +def iago_message_id() -> dict[str, object]: iago = helpers.example_user("iago") helpers.subscribe(iago, "Denmark") return { @@ -120,7 +120,7 @@ def iago_message_id() -> Dict[str, object]: @openapi_param_value_generator(["/messages/{message_id}/reactions:delete"]) -def add_emoji_to_message() -> Dict[str, object]: +def add_emoji_to_message() -> dict[str, object]: user_profile = helpers.example_user("iago") # The message ID here is hardcoded based on the corresponding value @@ -137,7 +137,7 @@ def add_emoji_to_message() -> Dict[str, object]: @openapi_param_value_generator(["/messages/flags:post"]) -def update_flags_message_ids() -> Dict[str, object]: +def update_flags_message_ids() -> dict[str, object]: stream_name = "Venice" helpers.subscribe(helpers.example_user("iago"), stream_name) @@ -150,14 +150,14 @@ def update_flags_message_ids() -> Dict[str, object]: @openapi_param_value_generator(["/mark_stream_as_read:post", "/users/me/{stream_id}/topics:get"]) -def get_venice_stream_id() -> Dict[str, object]: +def get_venice_stream_id() -> dict[str, object]: return { "stream_id": helpers.get_stream_id("Venice"), } @openapi_param_value_generator(["/streams/{stream_id}:patch"]) -def update_stream() -> Dict[str, object]: +def update_stream() -> dict[str, object]: stream = helpers.subscribe(helpers.example_user("iago"), "temp_stream 1") return { "stream_id": stream.id, @@ -165,7 +165,7 @@ def update_stream() -> Dict[str, object]: @openapi_param_value_generator(["/streams/{stream_id}:delete"]) -def create_temp_stream_and_get_id() -> Dict[str, object]: +def create_temp_stream_and_get_id() -> dict[str, object]: stream = helpers.subscribe(helpers.example_user("iago"), "temp_stream 2") return { "stream_id": stream.id, @@ -173,7 +173,7 @@ def create_temp_stream_and_get_id() -> Dict[str, object]: @openapi_param_value_generator(["/mark_topic_as_read:post"]) -def get_denmark_stream_id_and_topic() -> Dict[str, object]: +def get_denmark_stream_id_and_topic() -> dict[str, object]: stream_name = "Denmark" topic_name = "Tivoli Gardens" @@ -187,7 +187,7 @@ def get_denmark_stream_id_and_topic() -> Dict[str, object]: @openapi_param_value_generator(["/users/me/subscriptions/properties:post"]) -def update_subscription_data() -> Dict[str, object]: +def update_subscription_data() -> dict[str, object]: profile = helpers.example_user("iago") helpers.subscribe(profile, "Verona") helpers.subscribe(profile, "social") @@ -200,7 +200,7 @@ def update_subscription_data() -> Dict[str, object]: @openapi_param_value_generator(["/users/me/subscriptions:delete"]) -def delete_subscription_data() -> Dict[str, object]: +def delete_subscription_data() -> dict[str, object]: iago = helpers.example_user("iago") zoe = helpers.example_user("ZOE") helpers.subscribe(iago, "Verona") @@ -211,7 +211,7 @@ def delete_subscription_data() -> Dict[str, object]: @openapi_param_value_generator(["/events:get"]) -def get_events() -> Dict[str, object]: +def get_events() -> dict[str, object]: profile = helpers.example_user("iago") helpers.subscribe(profile, "Verona") client = Client.objects.create(name="curl-test-client-1") @@ -226,7 +226,7 @@ def get_events() -> Dict[str, object]: @openapi_param_value_generator(["/events:delete"]) -def delete_event_queue() -> Dict[str, object]: +def delete_event_queue() -> dict[str, object]: profile = helpers.example_user("iago") client = Client.objects.create(name="curl-test-client-2") response = do_events_register(profile, profile.realm, client, event_types=["message"]) @@ -237,7 +237,7 @@ def delete_event_queue() -> Dict[str, object]: @openapi_param_value_generator(["/users/{user_id_or_email}/presence:get"]) -def get_user_presence() -> Dict[str, object]: +def get_user_presence() -> dict[str, object]: iago = helpers.example_user("iago") client = Client.objects.create(name="curl-test-client-3") update_user_presence(iago, client, timezone_now(), UserPresence.LEGACY_STATUS_ACTIVE_INT, False) @@ -245,14 +245,14 @@ def get_user_presence() -> Dict[str, object]: @openapi_param_value_generator(["/users:post"]) -def create_user() -> Dict[str, object]: +def create_user() -> dict[str, object]: return { "email": helpers.nonreg_email("test"), } @openapi_param_value_generator(["/user_groups/create:post"]) -def create_user_group_data() -> Dict[str, object]: +def create_user_group_data() -> dict[str, object]: return { "members": [helpers.example_user("hamlet").id, helpers.example_user("othello").id], } @@ -261,7 +261,7 @@ def create_user_group_data() -> Dict[str, object]: @openapi_param_value_generator( ["/user_groups/{user_group_id}:patch", "/user_groups/{user_group_id}:delete"] ) -def get_temp_user_group_id() -> Dict[str, object]: +def get_temp_user_group_id() -> dict[str, object]: user_group, _ = NamedUserGroup.objects.get_or_create( name="temp", realm=get_realm("zulip"), @@ -274,7 +274,7 @@ def get_temp_user_group_id() -> Dict[str, object]: @openapi_param_value_generator(["/realm/filters/{filter_id}:delete"]) -def remove_realm_filters() -> Dict[str, object]: +def remove_realm_filters() -> dict[str, object]: filter_id = do_add_linkifier( get_realm("zulip"), "#(?P[0-9]{2,8})", @@ -287,14 +287,14 @@ def remove_realm_filters() -> Dict[str, object]: @openapi_param_value_generator(["/realm/emoji/{emoji_name}:post", "/user_uploads:post"]) -def upload_custom_emoji() -> Dict[str, object]: +def upload_custom_emoji() -> dict[str, object]: return { "filename": "zerver/tests/images/animated_img.gif", } @openapi_param_value_generator(["/realm/playgrounds:post"]) -def add_realm_playground() -> Dict[str, object]: +def add_realm_playground() -> dict[str, object]: return { "name": "Python2 playground", "pygments_language": "Python2", @@ -303,7 +303,7 @@ def add_realm_playground() -> Dict[str, object]: @openapi_param_value_generator(["/realm/playgrounds/{playground_id}:delete"]) -def remove_realm_playground() -> Dict[str, object]: +def remove_realm_playground() -> dict[str, object]: playground_id = check_add_realm_playground( get_realm("zulip"), acting_user=None, @@ -317,7 +317,7 @@ def remove_realm_playground() -> Dict[str, object]: @openapi_param_value_generator(["/users/{user_id}:delete"]) -def deactivate_user() -> Dict[str, object]: +def deactivate_user() -> dict[str, object]: user_profile = do_create_user( email="testuser@zulip.com", password=None, @@ -329,7 +329,7 @@ def deactivate_user() -> Dict[str, object]: @openapi_param_value_generator(["/users/me:delete"]) -def deactivate_own_user() -> Dict[str, object]: +def deactivate_own_user() -> dict[str, object]: test_user_email = "delete-test@zulip.com" deactivate_test_user = do_create_user( test_user_email, @@ -348,7 +348,7 @@ def deactivate_own_user() -> Dict[str, object]: @openapi_param_value_generator(["/attachments/{attachment_id}:delete"]) -def remove_attachment() -> Dict[str, object]: +def remove_attachment() -> dict[str, object]: user_profile = helpers.example_user("iago") url = upload_message_attachment("dummy.txt", "text/plain", b"zulip!", user_profile) attachment_id = url.replace("/user_uploads/", "").split("/")[0] diff --git a/zerver/openapi/markdown_extension.py b/zerver/openapi/markdown_extension.py index 1cac209885..9ea2b0c803 100644 --- a/zerver/openapi/markdown_extension.py +++ b/zerver/openapi/markdown_extension.py @@ -10,7 +10,7 @@ import json import re import shlex from textwrap import dedent -from typing import Any, Dict, List, Mapping, Match, Optional, Pattern +from typing import Any, Mapping, Match, Optional, Pattern import markdown from django.conf import settings @@ -108,8 +108,8 @@ ADMIN_CONFIG_LANGUAGES = ["python", "javascript"] def extract_code_example( - source: List[str], snippet: List[Any], example_regex: Pattern[str] -) -> List[Any]: + source: list[str], snippet: list[Any], example_regex: Pattern[str] +) -> list[Any]: start = -1 end = -1 for line in source: @@ -131,7 +131,7 @@ def extract_code_example( def render_python_code_example( function: str, admin_config: bool = False, **kwargs: Any -) -> List[str]: +) -> list[str]: if function not in zerver.openapi.python_examples.TEST_FUNCTIONS: return [] method = zerver.openapi.python_examples.TEST_FUNCTIONS[function] @@ -167,7 +167,7 @@ def render_python_code_example( def render_javascript_code_example( function: str, admin_config: bool = False, **kwargs: Any -) -> List[str]: +) -> list[str]: pattern = rf'^add_example\(\s*"[^"]*",\s*{re.escape(json.dumps(function))},\s*\d+,\s*async \(client, console\) => \{{\n(.*?)^(?:\}}| *\}},\n)\);$' with open("zerver/openapi/javascript_examples.js") as f: m = re.search(pattern, f.read(), re.MULTILINE | re.DOTALL) @@ -202,7 +202,7 @@ def render_javascript_code_example( return code_example -def curl_method_arguments(endpoint: str, method: str, api_url: str) -> List[str]: +def curl_method_arguments(endpoint: str, method: str, api_url: str) -> list[str]: # We also include the -sS verbosity arguments here. method = method.upper() url = f"{api_url}/v1{endpoint}" @@ -268,9 +268,9 @@ def generate_curl_example( api_url: str, auth_email: str = DEFAULT_AUTH_EMAIL, auth_api_key: str = DEFAULT_AUTH_API_KEY, - exclude: Optional[List[str]] = None, - include: Optional[List[str]] = None, -) -> List[str]: + exclude: Optional[list[str]] = None, + include: Optional[list[str]] = None, +) -> list[str]: lines = ["```curl"] operation = endpoint + ":" + method.lower() operation_entry = openapi_spec.openapi()["paths"][endpoint][method.lower()] @@ -355,12 +355,12 @@ def render_curl_example( function: str, api_url: str, admin_config: bool = False, -) -> List[str]: +) -> list[str]: """A simple wrapper around generate_curl_example.""" parts = function.split(":") endpoint = parts[0] method = parts[1] - kwargs: Dict[str, Any] = {} + kwargs: dict[str, Any] = {} if len(parts) > 2: kwargs["auth_email"] = parts[2] if len(parts) > 3: @@ -380,7 +380,7 @@ def render_curl_example( return rendered_example -SUPPORTED_LANGUAGES: Dict[str, Any] = { +SUPPORTED_LANGUAGES: dict[str, Any] = { "python": { "client_config": PYTHON_CLIENT_CONFIG, "admin_config": PYTHON_CLIENT_ADMIN_CONFIG, @@ -439,7 +439,7 @@ class BasePreprocessor(Preprocessor): self.REGEXP = regexp @override - def run(self, lines: List[str]) -> List[str]: + def run(self, lines: list[str]) -> list[str]: done = False while not done: for line in lines: @@ -463,12 +463,12 @@ class BasePreprocessor(Preprocessor): done = True return lines - def generate_text(self, match: Match[str]) -> List[str]: + def generate_text(self, match: Match[str]) -> list[str]: function = match.group(1) text = self.render(function) return text - def render(self, function: str) -> List[str]: + def render(self, function: str) -> list[str]: raise NotImplementedError("Must be overridden by a child class") @@ -477,7 +477,7 @@ class APICodeExamplesPreprocessor(BasePreprocessor): super().__init__(MACRO_REGEXP, md, config) @override - def generate_text(self, match: Match[str]) -> List[str]: + def generate_text(self, match: Match[str]) -> list[str]: language = match.group(1) or "" function = match.group(2) key = match.group(3) @@ -497,7 +497,7 @@ class APICodeExamplesPreprocessor(BasePreprocessor): return text @override - def render(self, function: str) -> List[str]: + def render(self, function: str) -> list[str]: path, method = function.rsplit(":", 1) return generate_openapi_fixture(path, method) @@ -507,7 +507,7 @@ class APIHeaderPreprocessor(BasePreprocessor): super().__init__(MACRO_REGEXP_HEADER, md, config) @override - def render(self, function: str) -> List[str]: + def render(self, function: str) -> list[str]: path, method = function.rsplit(":", 1) raw_title = get_openapi_summary(path, method) description_dict = get_openapi_description(path, method) @@ -526,7 +526,7 @@ class ResponseDescriptionPreprocessor(BasePreprocessor): super().__init__(MACRO_REGEXP_RESPONSE_DESC, md, config) @override - def render(self, function: str) -> List[str]: + def render(self, function: str) -> list[str]: path, method = function.rsplit(":", 1) raw_description = get_responses_description(path, method) return raw_description.splitlines() @@ -537,7 +537,7 @@ class ParameterDescriptionPreprocessor(BasePreprocessor): super().__init__(MACRO_REGEXP_PARAMETER_DESC, md, config) @override - def render(self, function: str) -> List[str]: + def render(self, function: str) -> list[str]: path, method = function.rsplit(":", 1) raw_description = get_parameters_description(path, method) return raw_description.splitlines() diff --git a/zerver/openapi/openapi.py b/zerver/openapi/openapi.py index db54905266..47e4b6e01d 100644 --- a/zerver/openapi/openapi.py +++ b/zerver/openapi/openapi.py @@ -8,7 +8,7 @@ import json import os import re -from typing import Any, Dict, List, Literal, Mapping, Optional, Set, Tuple, Union +from typing import Any, Literal, Mapping, Optional, Union import orjson from openapi_core import OpenAPI @@ -28,14 +28,14 @@ EXCLUDE_UNDOCUMENTED_ENDPOINTS = { } # Consists of endpoints with some documentation remaining. # These are skipped but return true as the validator cannot exclude objects -EXCLUDE_DOCUMENTED_ENDPOINTS: Set[Tuple[str, str]] = set() +EXCLUDE_DOCUMENTED_ENDPOINTS: set[tuple[str, str]] = set() # Most of our code expects allOf to be preprocessed away because that is what # yamole did. Its algorithm for doing so is not standards compliant, but we # replicate it here. -def naively_merge(a: Dict[str, object], b: Dict[str, object]) -> Dict[str, object]: - ret: Dict[str, object] = a.copy() +def naively_merge(a: dict[str, object], b: dict[str, object]) -> dict[str, object]: + ret: dict[str, object] = a.copy() for key, b_value in b.items(): if key == "example" or key not in ret: ret[key] = b_value @@ -59,7 +59,7 @@ def naively_merge_allOf(obj: object) -> object: return obj -def naively_merge_allOf_dict(obj: Dict[str, object]) -> Dict[str, object]: +def naively_merge_allOf_dict(obj: dict[str, object]) -> dict[str, object]: if "allOf" in obj: ret = obj.copy() subschemas = ret.pop("allOf") @@ -76,8 +76,8 @@ class OpenAPISpec: def __init__(self, openapi_path: str) -> None: self.openapi_path = openapi_path self.mtime: Optional[float] = None - self._openapi: Dict[str, Any] = {} - self._endpoints_dict: Dict[str, str] = {} + self._openapi: dict[str, Any] = {} + self._endpoints_dict: dict[str, str] = {} self._spec: Optional[OpenAPI] = None def check_reload(self) -> None: @@ -146,7 +146,7 @@ class OpenAPISpec: path_regex = path_regex.replace(r"/", r"\/") self._endpoints_dict[path_regex] = endpoint - def openapi(self) -> Dict[str, Any]: + def openapi(self) -> dict[str, Any]: """Reload the OpenAPI file if it has been modified after the last time it was read, and then return the parsed data. """ @@ -154,7 +154,7 @@ class OpenAPISpec: assert len(self._openapi) > 0 return self._openapi - def endpoints_dict(self) -> Dict[str, str]: + def endpoints_dict(self) -> dict[str, str]: """Reload the OpenAPI file if it has been modified after the last time it was read, and then return the parsed data. """ @@ -179,7 +179,7 @@ class SchemaError(Exception): openapi_spec = OpenAPISpec(OPENAPI_SPEC_PATH) -def get_schema(endpoint: str, method: str, status_code: str) -> Dict[str, Any]: +def get_schema(endpoint: str, method: str, status_code: str) -> dict[str, Any]: if len(status_code) == 3 and ( "oneOf" in openapi_spec.openapi()["paths"][endpoint][method.lower()]["responses"][status_code][ @@ -203,7 +203,7 @@ def get_schema(endpoint: str, method: str, status_code: str) -> Dict[str, Any]: return schema -def get_openapi_fixture(endpoint: str, method: str, status_code: str = "200") -> Dict[str, Any]: +def get_openapi_fixture(endpoint: str, method: str, status_code: str = "200") -> dict[str, Any]: """Fetch a fixture from the full spec object.""" return get_schema(endpoint, method, status_code)["example"] @@ -213,7 +213,7 @@ def get_openapi_fixture_description(endpoint: str, method: str, status_code: str return get_schema(endpoint, method, status_code)["description"] -def get_curl_include_exclude(endpoint: str, method: str) -> List[Dict[str, Any]]: +def get_curl_include_exclude(endpoint: str, method: str) -> list[dict[str, Any]]: """Fetch all the kinds of parameters required for curl examples.""" if ( "x-curl-examples-parameters" @@ -232,7 +232,7 @@ def check_requires_administrator(endpoint: str, method: str) -> bool: ) -def check_additional_imports(endpoint: str, method: str) -> Optional[List[str]]: +def check_additional_imports(endpoint: str, method: str) -> Optional[list[str]]: """Fetch the additional imports required for an endpoint.""" return openapi_spec.openapi()["paths"][endpoint][method.lower()].get( "x-python-examples-extra-imports", None @@ -253,7 +253,7 @@ def get_parameters_description(endpoint: str, method: str) -> str: ) -def generate_openapi_fixture(endpoint: str, method: str) -> List[str]: +def generate_openapi_fixture(endpoint: str, method: str) -> list[str]: """Generate fixture to be rendered""" fixture = [] for status_code in sorted( @@ -307,7 +307,7 @@ def get_openapi_summary(endpoint: str, method: str) -> str: return openapi_spec.openapi()["paths"][endpoint][method.lower()]["summary"] -def get_endpoint_from_operationid(operationid: str) -> Tuple[str, str]: +def get_endpoint_from_operationid(operationid: str) -> tuple[str, str]: for endpoint in openapi_spec.openapi()["paths"]: for method in openapi_spec.openapi()["paths"][endpoint]: operationId = openapi_spec.openapi()["paths"][endpoint][method].get("operationId") @@ -316,7 +316,7 @@ def get_endpoint_from_operationid(operationid: str) -> Tuple[str, str]: raise AssertionError("No such page exists in OpenAPI data.") -def get_openapi_paths() -> Set[str]: +def get_openapi_paths() -> set[str]: return set(openapi_spec.openapi()["paths"].keys()) @@ -328,7 +328,7 @@ class Parameter(BaseModel): name: str description: str json_encoded: bool - value_schema: Dict[str, Any] + value_schema: dict[str, Any] example: object required: bool deprecated: bool @@ -336,7 +336,7 @@ class Parameter(BaseModel): def get_openapi_parameters( endpoint: str, method: str, include_url_parameters: bool = True -) -> List[Parameter]: +) -> list[Parameter]: operation = openapi_spec.openapi()["paths"][endpoint][method.lower()] parameters = [] @@ -402,7 +402,7 @@ def get_openapi_parameters( return parameters -def get_openapi_return_values(endpoint: str, method: str) -> Dict[str, Any]: +def get_openapi_return_values(endpoint: str, method: str) -> dict[str, Any]: operation = openapi_spec.openapi()["paths"][endpoint][method.lower()] schema = operation["responses"]["200"]["content"]["application/json"]["schema"] # We do not currently have documented endpoints that have multiple schemas @@ -422,7 +422,7 @@ def find_openapi_endpoint(path: str) -> Optional[str]: def validate_against_openapi_schema( - content: Dict[str, Any], path: str, method: str, status_code: str + content: dict[str, Any], path: str, method: str, status_code: str ) -> bool: mock_request = MockRequest("http://localhost:9991/", method, "/api/v1" + path) mock_response = MockResponse( @@ -486,7 +486,7 @@ def validate_test_response(request: Request, response: Response) -> bool: return True -def validate_schema(schema: Dict[str, Any]) -> None: +def validate_schema(schema: dict[str, Any]) -> None: """Check if opaque objects are present in the OpenAPI spec; this is an important part of our policy for ensuring every detail of Zulip's API responses is correct. @@ -542,7 +542,7 @@ def validate_request( url: str, method: str, data: Union[str, bytes, Mapping[str, Any]], - http_headers: Dict[str, str], + http_headers: dict[str, str], json_url: bool, status_code: str, intentionally_undocumented: bool = False, diff --git a/zerver/openapi/python_examples.py b/zerver/openapi/python_examples.py index 980e7e7d84..6fb9a5f0ab 100644 --- a/zerver/openapi/python_examples.py +++ b/zerver/openapi/python_examples.py @@ -17,7 +17,7 @@ import os import sys from email.headerregistry import Address from functools import wraps -from typing import Any, Callable, Dict, List, Set, TypeVar +from typing import Any, Callable, TypeVar from typing_extensions import ParamSpec from zulip import Client @@ -28,9 +28,9 @@ from zerver.openapi.openapi import validate_against_openapi_schema ZULIP_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -TEST_FUNCTIONS: Dict[str, Callable[..., object]] = {} -REGISTERED_TEST_FUNCTIONS: Set[str] = set() -CALLED_TEST_FUNCTIONS: Set[str] = set() +TEST_FUNCTIONS: dict[str, Callable[..., object]] = {} +REGISTERED_TEST_FUNCTIONS: set[str] = set() +CALLED_TEST_FUNCTIONS: set[str] = set() ParamT = ParamSpec("ParamT") ReturnT = TypeVar("ReturnT") @@ -60,7 +60,7 @@ def openapi_test_function( return wrapper -def ensure_users(ids_list: List[int], user_names: List[str]) -> None: +def ensure_users(ids_list: list[int], user_names: list[str]) -> None: # Ensure that the list of user ids (ids_list) # matches the users we want to refer to (user_names). realm = get_realm("zulip") @@ -71,19 +71,19 @@ def ensure_users(ids_list: List[int], user_names: List[str]) -> None: assert ids_list == user_ids -def assert_success_response(response: Dict[str, Any]) -> None: +def assert_success_response(response: dict[str, Any]) -> None: assert "result" in response assert response["result"] == "success" -def assert_error_response(response: Dict[str, Any], code: str = "BAD_REQUEST") -> None: +def assert_error_response(response: dict[str, Any], code: str = "BAD_REQUEST") -> None: assert "result" in response assert response["result"] == "error" assert "code" in response assert response["code"] == code -def get_subscribed_stream_ids(client: Client) -> List[int]: +def get_subscribed_stream_ids(client: Client) -> list[int]: streams = client.get_subscriptions() stream_ids = [stream["stream_id"] for stream in streams["subscriptions"]] return stream_ids @@ -1040,7 +1040,7 @@ def get_messages(client: Client) -> None: # {code_example|start} # Get the 100 last messages sent by "iago@zulip.com" to # the channel named "Verona". - request: Dict[str, Any] = { + request: dict[str, Any] = { "anchor": "newest", "num_before": 100, "num_after": 0, @@ -1114,7 +1114,7 @@ def remove_attachment(client: Client, attachment_id: int) -> None: @openapi_test_function("/messages:post") def send_message(client: Client) -> int: - request: Dict[str, Any] = {} + request: dict[str, Any] = {} # {code_example|start} # Send a channel message. request = { @@ -1154,7 +1154,7 @@ def send_message(client: Client) -> int: @openapi_test_function("/messages/{message_id}/reactions:post") def add_reaction(client: Client, message_id: int) -> None: - request: Dict[str, Any] = {} + request: dict[str, Any] = {} # {code_example|start} # Add an emoji reaction. request = { @@ -1169,7 +1169,7 @@ def add_reaction(client: Client, message_id: int) -> None: @openapi_test_function("/messages/{message_id}/reactions:delete") def remove_reaction(client: Client, message_id: int) -> None: - request: Dict[str, Any] = {} + request: dict[str, Any] = {} # {code_example|start} # Remove an emoji reaction. request = { @@ -1295,7 +1295,7 @@ def get_realm_emoji(client: Client) -> None: @openapi_test_function("/messages/flags:post") def update_message_flags(client: Client) -> None: # Send a few test messages. - request: Dict[str, Any] = { + request: dict[str, Any] = { "type": "stream", "to": "Denmark", "topic": "Castle", diff --git a/zerver/tests/test_audit_log.py b/zerver/tests/test_audit_log.py index ac493f9c24..7aa93ce6bb 100644 --- a/zerver/tests/test_audit_log.py +++ b/zerver/tests/test_audit_log.py @@ -1,5 +1,5 @@ from datetime import timedelta -from typing import Any, Dict, Union +from typing import Any, Union from django.contrib.auth.password_validation import validate_password from django.utils.timezone import now as timezone_now @@ -92,7 +92,7 @@ from zerver.models.streams import get_stream class TestRealmAuditLog(ZulipTestCase): - def check_role_count_schema(self, role_counts: Dict[str, Any]) -> None: + def check_role_count_schema(self, role_counts: dict[str, Any]) -> None: for key in [ UserProfile.ROLE_REALM_ADMINISTRATOR, UserProfile.ROLE_MEMBER, diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py index 63d3304b69..86fec39994 100644 --- a/zerver/tests/test_auth_backends.py +++ b/zerver/tests/test_auth_backends.py @@ -9,20 +9,7 @@ from abc import ABC, abstractmethod from contextlib import contextmanager from datetime import timedelta from email.headerregistry import Address -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Dict, - Iterable, - Iterator, - List, - Mapping, - Optional, - Sequence, - Tuple, - Type, -) +from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator, Mapping, Optional, Sequence from unittest import mock from urllib.parse import parse_qs, urlencode, urlsplit @@ -184,8 +171,8 @@ class AuthBackendTest(ZulipTestCase): self, backend: Any, *, - good_kwargs: Dict[str, Any], - bad_kwargs: Optional[Dict[str, Any]] = None, + good_kwargs: dict[str, Any], + bad_kwargs: Optional[dict[str, Any]] = None, ) -> None: clear_supported_auth_backends_cache() user_profile = self.example_user("hamlet") @@ -294,7 +281,7 @@ class AuthBackendTest(ZulipTestCase): with mock.patch("zproject.backends.email_auth_enabled", return_value=False), mock.patch( "zproject.backends.password_auth_enabled", return_value=True ): - return_data: Dict[str, bool] = {} + return_data: dict[str, bool] = {} user = EmailAuthBackend().authenticate( request=mock.MagicMock(), username=user_profile.delivery_email, @@ -747,7 +734,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase, ABC): according to the respective backend. """ - BACKEND_CLASS: "Type[SocialAuthMixin]" + BACKEND_CLASS: "type[SocialAuthMixin]" LOGIN_URL: str SIGNUP_URL: str AUTHORIZATION_URL: str @@ -758,7 +745,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase, ABC): CLIENT_SECRET_SETTING: str @abstractmethod - def get_account_data_dict(self, email: str, name: str) -> Dict[str, Any]: + def get_account_data_dict(self, email: str, name: str) -> dict[str, Any]: raise NotImplementedError @override @@ -786,7 +773,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase, ABC): def register_extra_endpoints( self, requests_mock: responses.RequestsMock, - account_data_dict: Dict[str, str], + account_data_dict: dict[str, str], **extra_data: Any, ) -> None: pass @@ -802,8 +789,8 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase, ABC): alternative_start_url: Optional[str] = None, *, user_agent: Optional[str] = None, - extra_headers: Optional[Dict[str, Any]] = None, - ) -> Tuple[str, Dict[str, Any]]: + extra_headers: Optional[dict[str, Any]] = None, + ) -> tuple[str, dict[str, Any]]: url = self.LOGIN_URL if alternative_start_url is not None: url = alternative_start_url @@ -838,7 +825,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase, ABC): def social_auth_test_finish( self, result: "TestHttpResponse", - account_data_dict: Dict[str, str], + account_data_dict: dict[str, str], expect_choose_email_screen: bool, headers: Any, **extra_data: Any, @@ -848,7 +835,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase, ABC): result = self.client_get(self.AUTH_FINISH_URL, dict(state=csrf_state), **headers) return result - def generate_access_token_url_payload(self, account_data_dict: Dict[str, str]) -> str: + def generate_access_token_url_payload(self, account_data_dict: dict[str, str]) -> str: return json.dumps( { "access_token": "foobar", @@ -858,7 +845,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase, ABC): def social_auth_test( self, - account_data_dict: Dict[str, str], + account_data_dict: dict[str, str], *, subdomain: str, mobile_flow_otp: Optional[str] = None, @@ -869,7 +856,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase, ABC): expect_choose_email_screen: bool = False, alternative_start_url: Optional[str] = None, user_agent: Optional[str] = None, - extra_headers: Optional[Dict[str, Any]] = None, + extra_headers: Optional[dict[str, Any]] = None, **extra_data: Any, ) -> "TestHttpResponse": """Main entry point for all social authentication tests. @@ -1647,7 +1634,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase, ABC): realm.invite_required = True realm.save() - streams: List[Stream] = [] + streams: list[Stream] = [] # Generate an invitation for a different realm than the one we'll attempt to join: lear_realm = get_realm("lear") @@ -1934,7 +1921,7 @@ class SAMLAuthBackendTest(SocialAuthBase): @override def social_auth_test( self, - account_data_dict: Dict[str, str], + account_data_dict: dict[str, str], *, subdomain: str, mobile_flow_otp: Optional[str] = None, @@ -1943,8 +1930,8 @@ class SAMLAuthBackendTest(SocialAuthBase): next: str = "", multiuse_object_key: str = "", user_agent: Optional[str] = None, - extra_attributes: Mapping[str, List[str]] = {}, - extra_headers: Optional[Dict[str, Any]] = None, + extra_attributes: Mapping[str, list[str]] = {}, + extra_headers: Optional[dict[str, Any]] = None, **extra_data: Any, ) -> "TestHttpResponse": url, headers = self.prepare_login_url_and_headers( @@ -2011,7 +1998,7 @@ class SAMLAuthBackendTest(SocialAuthBase): self, email: str, name: str, - extra_attributes: Mapping[str, List[str]] = {}, + extra_attributes: Mapping[str, list[str]] = {}, include_session_index: bool = True, ) -> str: """ @@ -2095,7 +2082,7 @@ class SAMLAuthBackendTest(SocialAuthBase): return result @override - def get_account_data_dict(self, email: str, name: str) -> Dict[str, Any]: + def get_account_data_dict(self, email: str, name: str) -> dict[str, Any]: return dict(email=email, name=name) def test_saml_sp_initiated_logout_success(self) -> None: @@ -3281,7 +3268,7 @@ class AppleAuthMixin: AUTH_FINISH_URL = "/complete/apple/" def generate_id_token( - self, account_data_dict: Dict[str, str], audience: Optional[str] = None + self, account_data_dict: dict[str, str], audience: Optional[str] = None ) -> str: payload = dict(email=account_data_dict["email"]) @@ -3300,7 +3287,7 @@ class AppleAuthMixin: return id_token - def get_account_data_dict(self, email: str, name: str) -> Dict[str, Any]: + def get_account_data_dict(self, email: str, name: str) -> dict[str, Any]: name_parts = name.split(" ") first_name = name_parts[0] last_name = "" @@ -3322,7 +3309,7 @@ class AppleIdAuthBackendTest(AppleAuthMixin, SocialAuthBase): def social_auth_test_finish( self, result: "TestHttpResponse", - account_data_dict: Dict[str, str], + account_data_dict: dict[str, str], expect_choose_email_screen: bool, headers: Any, **extra_data: Any, @@ -3340,7 +3327,7 @@ class AppleIdAuthBackendTest(AppleAuthMixin, SocialAuthBase): def register_extra_endpoints( self, requests_mock: responses.RequestsMock, - account_data_dict: Dict[str, str], + account_data_dict: dict[str, str], **extra_data: Any, ) -> None: # This is an URL of an endpoint on Apple servers that returns @@ -3354,7 +3341,7 @@ class AppleIdAuthBackendTest(AppleAuthMixin, SocialAuthBase): ) @override - def generate_access_token_url_payload(self, account_data_dict: Dict[str, str]) -> str: + def generate_access_token_url_payload(self, account_data_dict: dict[str, str]) -> str: # The ACCESS_TOKEN_URL endpoint works a bit different than in standard Oauth2, # and here, similarly to OIDC, id_token is also returned in the response. # In Apple auth, all the user information is carried in the id_token. @@ -3459,8 +3446,8 @@ class AppleAuthBackendNativeFlowTest(AppleAuthMixin, SocialAuthBase): account_data_dict: Mapping[str, str] = {}, *, user_agent: Optional[str] = None, - extra_headers: Optional[Dict[str, Any]] = None, - ) -> Tuple[str, Dict[str, Any]]: + extra_headers: Optional[dict[str, Any]] = None, + ) -> tuple[str, dict[str, Any]]: url, headers = super().prepare_login_url_and_headers( subdomain, mobile_flow_otp, @@ -3492,7 +3479,7 @@ class AppleAuthBackendNativeFlowTest(AppleAuthMixin, SocialAuthBase): @override def social_auth_test( self, - account_data_dict: Dict[str, str], + account_data_dict: dict[str, str], *, subdomain: str, mobile_flow_otp: Optional[str] = None, @@ -3503,7 +3490,7 @@ class AppleAuthBackendNativeFlowTest(AppleAuthMixin, SocialAuthBase): alternative_start_url: Optional[str] = None, skip_id_token: bool = False, user_agent: Optional[str] = None, - extra_headers: Optional[Dict[str, Any]] = None, + extra_headers: Optional[dict[str, Any]] = None, **extra_data: Any, ) -> "TestHttpResponse": """In Apple's native authentication flow, the client app authenticates @@ -3728,7 +3715,7 @@ class GenericOpenIdConnectTest(SocialAuthBase): def register_extra_endpoints( self, requests_mock: responses.RequestsMock, - account_data_dict: Dict[str, str], + account_data_dict: dict[str, str], **extra_data: Any, ) -> None: requests_mock.add( @@ -3739,7 +3726,7 @@ class GenericOpenIdConnectTest(SocialAuthBase): ) @override - def generate_access_token_url_payload(self, account_data_dict: Dict[str, str]) -> str: + def generate_access_token_url_payload(self, account_data_dict: dict[str, str]) -> str: return json.dumps( { "access_token": "foobar", @@ -3750,7 +3737,7 @@ class GenericOpenIdConnectTest(SocialAuthBase): ) @override - def get_account_data_dict(self, email: str, name: Optional[str]) -> Dict[str, Any]: + def get_account_data_dict(self, email: str, name: Optional[str]) -> dict[str, Any]: if name is not None: name_parts = name.split(" ") given_name = name_parts[0] @@ -3898,13 +3885,13 @@ class GitHubAuthBackendTest(SocialAuthBase): ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token" USER_INFO_URL = "https://api.github.com/user" AUTH_FINISH_URL = "/complete/github/" - email_data: List[Dict[str, Any]] = [] + email_data: list[dict[str, Any]] = [] @override def social_auth_test_finish( self, result: "TestHttpResponse", - account_data_dict: Dict[str, str], + account_data_dict: dict[str, str], expect_choose_email_screen: bool, headers: Any, expect_noreply_email_allowed: bool = False, @@ -3955,7 +3942,7 @@ class GitHubAuthBackendTest(SocialAuthBase): def register_extra_endpoints( self, requests_mock: responses.RequestsMock, - account_data_dict: Dict[str, str], + account_data_dict: dict[str, str], **extra_data: Any, ) -> None: # Keeping a verified email before the primary email makes sure @@ -3981,7 +3968,7 @@ class GitHubAuthBackendTest(SocialAuthBase): @override def get_account_data_dict( self, email: str, name: str, user_avatar_url: str = "" - ) -> Dict[str, Any]: + ) -> dict[str, Any]: return dict(email=email, name=name, user_avatar_url=user_avatar_url) def test_social_auth_email_not_verified(self) -> None: @@ -4419,7 +4406,7 @@ class GitLabAuthBackendTest(SocialAuthBase): self.assertTrue(gitlab_auth_enabled()) @override - def get_account_data_dict(self, email: str, name: str) -> Dict[str, Any]: + def get_account_data_dict(self, email: str, name: str) -> dict[str, Any]: return dict(email=email, name=name, email_verified=True) @@ -4435,7 +4422,7 @@ class GoogleAuthBackendTest(SocialAuthBase): AUTH_FINISH_URL = "/complete/google/" @override - def get_account_data_dict(self, email: str, name: str) -> Dict[str, Any]: + def get_account_data_dict(self, email: str, name: str) -> dict[str, Any]: return dict(email=email, name=name, email_verified=True) def test_social_auth_email_not_verified(self) -> None: @@ -5119,7 +5106,7 @@ class ExternalMethodDictsTests(ZulipTestCase): ), ): external_auth_methods = get_external_method_dicts() - external_auth_backends: List[Type[ExternalAuthMethod]] = [ + external_auth_backends: list[type[ExternalAuthMethod]] = [ ZulipRemoteUserBackend, GitHubAuthBackend, AzureADAuthBackend, @@ -5209,7 +5196,7 @@ class ExternalMethodDictsTests(ZulipTestCase): class FetchAuthBackends(ZulipTestCase): def test_get_server_settings(self) -> None: def check_result( - result: "TestHttpResponse", extra_fields: Sequence[Tuple[str, Validator[object]]] = [] + result: "TestHttpResponse", extra_fields: Sequence[tuple[str, Validator[object]]] = [] ) -> None: authentication_methods_list = [ ("password", check_bool), @@ -5896,7 +5883,7 @@ class TestJWTLogin(ZulipTestCase): self.assert_logged_in_user_id(user_profile.id) def test_login_failure_when_email_is_missing(self) -> None: - payload: Dict[str, str] = {} + payload: dict[str, str] = {} with self.settings(JWT_AUTH_KEYS={"zulip": {"key": "key", "algorithms": ["HS256"]}}): key = settings.JWT_AUTH_KEYS["zulip"]["key"] [algorithm] = settings.JWT_AUTH_KEYS["zulip"]["algorithms"] diff --git a/zerver/tests/test_bots.py b/zerver/tests/test_bots.py index 86be14bf7a..e5fccbe3bb 100644 --- a/zerver/tests/test_bots.py +++ b/zerver/tests/test_bots.py @@ -1,6 +1,6 @@ import filecmp import os -from typing import Any, Dict, Optional +from typing import Any, Optional from unittest.mock import MagicMock, patch import orjson @@ -52,7 +52,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin): response_dict = self.assert_json_success(result) self.assert_length(response_dict["bots"], count) - def create_bot(self, **extras: Any) -> Dict[str, Any]: + def create_bot(self, **extras: Any) -> dict[str, Any]: bot_info = { "full_name": "The Bot of Hamlet", "short_name": "hambot", @@ -718,7 +718,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin): result = self.client_patch(f"/json/bots/{self.get_bot_user(email).id}", bot_info) self.assert_json_error(result, "Insufficient permission") - def get_bot(self) -> Dict[str, Any]: + def get_bot(self) -> dict[str, Any]: result = self.client_get("/json/bots") return self.assert_json_success(result)["bots"][0] @@ -1005,7 +1005,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin): self.login("hamlet") othello = self.example_user("othello") - bot_info: Dict[str, object] = { + bot_info: dict[str, object] = { "full_name": "The Bot of Hamlet", "short_name": "hambot", } @@ -1098,7 +1098,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin): self.create_bot() self.assert_num_bots_equal(1) - bot_info: Dict[str, object] = { + bot_info: dict[str, object] = { "full_name": "Another Bot of Hamlet", "short_name": "hamelbot", } diff --git a/zerver/tests/test_cache.py b/zerver/tests/test_cache.py index 5f0730dff5..65268608c9 100644 --- a/zerver/tests/test_cache.py +++ b/zerver/tests/test_cache.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional +from typing import Optional from unittest.mock import Mock, patch from django.conf import settings @@ -253,12 +253,12 @@ class GenericBulkCachedFetchTest(ZulipTestCase): class CustomError(Exception): pass - def query_function(ids: List[int]) -> List[UserProfile]: + def query_function(ids: list[int]) -> list[UserProfile]: raise CustomError("The query function was called") # query_function shouldn't be called, because the only requested object # is already cached: - result: Dict[int, UserProfile] = bulk_cached_fetch( + result: dict[int, UserProfile] = bulk_cached_fetch( cache_key_function=user_profile_by_id_cache_key, query_function=query_function, object_ids=[hamlet.id], @@ -288,13 +288,13 @@ class GenericBulkCachedFetchTest(ZulipTestCase): raise CustomError("The cache key function was called") def query_function( - emails: List[str], - ) -> List[UserProfile]: # nocoverage -- this is just here to make sure it's not called + emails: list[str], + ) -> list[UserProfile]: # nocoverage -- this is just here to make sure it's not called raise CustomError("The query function was called") # query_function and cache_key_function shouldn't be called, because # objects_ids is empty, so there's nothing to do. - result: Dict[str, UserProfile] = bulk_cached_fetch( + result: dict[str, UserProfile] = bulk_cached_fetch( cache_key_function=cache_key_function, query_function=query_function, object_ids=[], diff --git a/zerver/tests/test_custom_profile_data.py b/zerver/tests/test_custom_profile_data.py index 6d66a5e31a..57102e1b9e 100644 --- a/zerver/tests/test_custom_profile_data.py +++ b/zerver/tests/test_custom_profile_data.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Iterable, List, Optional, Tuple, Union, cast +from typing import Any, Iterable, Optional, Union, cast from unittest import mock import orjson @@ -37,7 +37,7 @@ class CreateCustomProfileFieldTest(CustomProfileFieldTestCase): def test_create(self) -> None: self.login("iago") realm = get_realm("zulip") - data: Dict[str, Any] = {"name": "Phone", "field_type": "text id"} + data: dict[str, Any] = {"name": "Phone", "field_type": "text id"} result = self.client_post("/json/realm/profile_fields", info=data) self.assert_json_error(result, "field_type is not valid JSON") @@ -100,7 +100,7 @@ class CreateCustomProfileFieldTest(CustomProfileFieldTestCase): def test_create_select_field(self) -> None: self.login("iago") - data: Dict[str, Union[str, int]] = {} + data: dict[str, Union[str, int]] = {} data["name"] = "Favorite programming language" data["field_type"] = CustomProfileField.SELECT @@ -241,7 +241,7 @@ class CreateCustomProfileFieldTest(CustomProfileFieldTestCase): def test_create_external_account_field(self) -> None: self.login("iago") realm = get_realm("zulip") - data: Dict[str, Union[str, int, Dict[str, str]]] = {} + data: dict[str, Union[str, int, dict[str, str]]] = {} data["name"] = "Twitter username" data["field_type"] = CustomProfileField.EXTERNAL_ACCOUNT @@ -410,7 +410,7 @@ class DeleteCustomProfileFieldTest(CustomProfileFieldTestCase): self.assert_json_error(result, f"Field id {invalid_field_id} not found.") field = CustomProfileField.objects.get(name="Mentor", realm=realm) - data: List[ProfileDataElementUpdateDict] = [ + data: list[ProfileDataElementUpdateDict] = [ {"id": field.id, "value": [self.example_user("aaron").id]}, ] do_update_user_custom_profile_data_if_changed(iago, data) @@ -440,7 +440,7 @@ class DeleteCustomProfileFieldTest(CustomProfileFieldTestCase): user_profile = self.example_user("iago") realm = user_profile.realm field = CustomProfileField.objects.get(name="Phone number", realm=realm) - data: List[ProfileDataElementUpdateDict] = [ + data: list[ProfileDataElementUpdateDict] = [ {"id": field.id, "value": "123456"}, ] do_update_user_custom_profile_data_if_changed(user_profile, data) @@ -724,7 +724,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase): def test_update_profile_data_successfully(self) -> None: self.login("iago") realm = get_realm("zulip") - fields: List[Tuple[str, Union[str, List[int]]]] = [ + fields: list[tuple[str, Union[str, list[int]]]] = [ ("Phone number", "*short* text data"), ("Biography", "~~short~~ **long** text data"), ("Favorite food", "long short text data"), @@ -736,9 +736,9 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase): ("Pronouns", "he/him"), ] - data: List[ProfileDataElementUpdateDict] = [] - expected_value: Dict[int, ProfileDataElementValue] = {} - expected_rendered_value: Dict[int, Optional[str]] = {} + data: list[ProfileDataElementUpdateDict] = [] + expected_value: dict[int, ProfileDataElementValue] = {} + expected_rendered_value: dict[int, Optional[str]] = {} for i, field_value in enumerate(fields): name, value = field_value field = CustomProfileField.objects.get(name=name, realm=realm) @@ -849,7 +849,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase): # Set field value: field = CustomProfileField.objects.get(name="Mentor", realm=realm) - data: List[ProfileDataElementUpdateDict] = [ + data: list[ProfileDataElementUpdateDict] = [ {"id": field.id, "value": [self.example_user("aaron").id]}, ] do_update_user_custom_profile_data_if_changed(iago, data) diff --git a/zerver/tests/test_decorators.py b/zerver/tests/test_decorators.py index 889d29e3d5..bd80a63e7e 100644 --- a/zerver/tests/test_decorators.py +++ b/zerver/tests/test_decorators.py @@ -3,7 +3,7 @@ import os import re import uuid from collections import defaultdict -from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Callable, Optional, Union from unittest import mock, skipUnless import orjson @@ -1429,7 +1429,7 @@ class RestAPITest(ZulipTestCase): class TestUserAgentParsing(ZulipTestCase): def test_user_agent_parsing(self) -> None: """Test for our user agent parsing logic, using a large data set.""" - user_agents_parsed: Dict[str, int] = defaultdict(int) + user_agents_parsed: dict[str, int] = defaultdict(int) user_agents_path = os.path.join( settings.DEPLOY_ROOT, "zerver/tests/fixtures/user_agents_unique" ) @@ -1584,7 +1584,7 @@ class TestRequestNotes(ZulipTestCase): class ClientTestCase(ZulipTestCase): def test_process_client(self) -> None: - def request_user_agent(user_agent: str) -> Tuple[Client, str]: + def request_user_agent(user_agent: str) -> tuple[Client, str]: request = HttpRequest() request.META["HTTP_USER_AGENT"] = user_agent LogRequests(lambda request: HttpResponse()).process_request(request) diff --git a/zerver/tests/test_digest.py b/zerver/tests/test_digest.py index 9a81b77104..fd37595cdd 100644 --- a/zerver/tests/test_digest.py +++ b/zerver/tests/test_digest.py @@ -1,6 +1,5 @@ import time from datetime import datetime, timedelta, timezone -from typing import List, Set from unittest import mock import time_machine @@ -285,7 +284,7 @@ class TestDigestEmailMessages(ZulipTestCase): scotland = get_stream("Scotland", realm) denmark = get_stream("Denmark", realm) - def user_streams(user: UserProfile) -> Set[Stream]: + def user_streams(user: UserProfile) -> set[Stream]: data = get_user_stream_map([user.id], one_hour_ago) return {Stream.objects.get(id=stream_id) for stream_id in data[user.id]} @@ -331,7 +330,7 @@ class TestDigestEmailMessages(ZulipTestCase): self.assertEqual(streams[othello.id], {scotland.id, denmark.id}) self.assertEqual(streams[cordelia.id], {verona.id, scotland.id}) - def active_human_users(self, realm: Realm) -> List[UserProfile]: + def active_human_users(self, realm: Realm) -> list[UserProfile]: users = list( UserProfile.objects.filter( realm=realm, @@ -536,7 +535,7 @@ class TestDigestEmailMessages(ZulipTestCase): self.assertEqual(stream_count, 0) self.assertEqual(stream_info["html"], []) - def simulate_stream_conversation(self, stream: str, senders: List[str]) -> List[int]: + def simulate_stream_conversation(self, stream: str, senders: list[str]) -> list[int]: message_ids = [] # List[int] for sender_name in senders: sender = self.example_user(sender_name) diff --git a/zerver/tests/test_docs.py b/zerver/tests/test_docs.py index 1843afb436..bb437344b6 100644 --- a/zerver/tests/test_docs.py +++ b/zerver/tests/test_docs.py @@ -1,6 +1,6 @@ import os import re -from typing import TYPE_CHECKING, Any, Dict, Sequence +from typing import TYPE_CHECKING, Any, Sequence from unittest import mock, skipUnless from urllib.parse import urlsplit @@ -449,7 +449,7 @@ class IntegrationTest(ZulipTestCase): self.assertTrue(os.path.isfile(settings.DEPLOY_ROOT + path), integration.name) def test_api_url_view_subdomains_base(self) -> None: - context: Dict[str, Any] = {} + context: dict[str, Any] = {} add_api_url_context(context, HostRequestMock()) self.assertEqual(context["api_url_scheme_relative"], "testserver/api") self.assertEqual(context["api_url"], "http://testserver/api") @@ -457,14 +457,14 @@ class IntegrationTest(ZulipTestCase): @override_settings(ROOT_DOMAIN_LANDING_PAGE=True) def test_api_url_view_subdomains_homepage_base(self) -> None: - context: Dict[str, Any] = {} + context: dict[str, Any] = {} add_api_url_context(context, HostRequestMock()) self.assertEqual(context["api_url_scheme_relative"], "yourZulipDomain.testserver/api") self.assertEqual(context["api_url"], "http://yourZulipDomain.testserver/api") self.assertFalse(context["html_settings_links"]) def test_api_url_view_subdomains_full(self) -> None: - context: Dict[str, Any] = {} + context: dict[str, Any] = {} request = HostRequestMock(host="mysubdomain.testserver") add_api_url_context(context, request) self.assertEqual(context["api_url_scheme_relative"], "mysubdomain.testserver/api") diff --git a/zerver/tests/test_drafts.py b/zerver/tests/test_drafts.py index 186e69a74d..c86d13430d 100644 --- a/zerver/tests/test_drafts.py +++ b/zerver/tests/test_drafts.py @@ -1,6 +1,6 @@ import time from copy import deepcopy -from typing import Any, Dict, List, Optional +from typing import Any, Optional import orjson @@ -11,8 +11,8 @@ from zerver.models import Draft class DraftCreationTests(ZulipTestCase): def create_and_check_drafts_for_success( self, - draft_dicts: List[Dict[str, Any]], - expected_draft_dicts: Optional[List[Dict[str, Any]]] = None, + draft_dicts: list[dict[str, Any]], + expected_draft_dicts: Optional[list[dict[str, Any]]] = None, ) -> None: hamlet = self.example_user("hamlet") @@ -32,7 +32,7 @@ class DraftCreationTests(ZulipTestCase): self.assertEqual(new_draft_dicts, expected_draft_dicts) def create_and_check_drafts_for_error( - self, draft_dicts: List[Dict[str, Any]], expected_message: str + self, draft_dicts: list[dict[str, Any]], expected_message: str ) -> None: hamlet = self.example_user("hamlet") @@ -566,7 +566,7 @@ class DraftFetchTest(ZulipTestCase): self.assertEqual(data["count"], 3) first_draft_id = Draft.objects.filter(user_profile=hamlet).order_by("id")[0].id - expected_draft_contents: List[Dict[str, object]] = [ + expected_draft_contents: list[dict[str, object]] = [ {"id": first_draft_id + i, **draft_dicts[i]} for i in range(3) ] diff --git a/zerver/tests/test_email_mirror.py b/zerver/tests/test_email_mirror.py index dbe41a36b4..9438ee2a9c 100644 --- a/zerver/tests/test_email_mirror.py +++ b/zerver/tests/test_email_mirror.py @@ -5,7 +5,7 @@ import subprocess from email import message_from_string from email.headerregistry import Address from email.message import EmailMessage, MIMEPart -from typing import TYPE_CHECKING, Any, Callable, Dict, Mapping, Optional +from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional from unittest import mock import orjson @@ -52,7 +52,7 @@ logger_name = "zerver.lib.email_mirror" class TestEncodeDecode(ZulipTestCase): def _assert_options( self, - options: Dict[str, bool], + options: dict[str, bool], show_sender: bool = False, include_footer: bool = False, include_quotes: bool = False, diff --git a/zerver/tests/test_email_notifications.py b/zerver/tests/test_email_notifications.py index d2b3b82d42..26cb9431d8 100644 --- a/zerver/tests/test_email_notifications.py +++ b/zerver/tests/test_email_notifications.py @@ -1,6 +1,5 @@ import tempfile from datetime import datetime, timedelta, timezone -from typing import Dict from unittest.mock import patch import ldap @@ -133,7 +132,7 @@ class TestCustomEmails(ZulipTestCase): "zerver/tests/fixtures/email/custom_emails/email_base_headers_custom_test.md" ) - def add_context(context: Dict[str, object], user: UserProfile) -> None: + def add_context(context: dict[str, object], user: UserProfile) -> None: context["unsubscribe_link"] = "some@email" context["custom"] = str(user.id) diff --git a/zerver/tests/test_event_queue.py b/zerver/tests/test_event_queue.py index e81bb9ce6f..5e4adf3763 100644 --- a/zerver/tests/test_event_queue.py +++ b/zerver/tests/test_event_queue.py @@ -1,5 +1,5 @@ import time -from typing import Any, Callable, Collection, Dict, List +from typing import Any, Callable, Collection from unittest import mock import orjson @@ -132,7 +132,7 @@ class MissedMessageHookTest(ZulipTestCase): self, view_func: Callable[[HttpRequest, UserProfile], HttpResponse], user_profile: UserProfile, - post_data: Dict[str, Any], + post_data: dict[str, Any], ) -> HttpResponse: request = HostRequestMock(post_data, user_profile, tornado_handler=dummy_handler) return view_func(request, user_profile) @@ -172,7 +172,7 @@ class MissedMessageHookTest(ZulipTestCase): ) self.assertEqual(args_dict, expected_args_dict) - def change_subscription_properties(self, properties: Dict[str, bool]) -> None: + def change_subscription_properties(self, properties: dict[str, bool]) -> None: stream = get_stream("Denmark", self.user_profile.realm) sub = Subscription.objects.get( user_profile=self.user_profile, @@ -1299,7 +1299,7 @@ class EventQueueTest(ZulipTestCase): * concatenate the messages """ - def umfe(timestamp: int, messages: List[int]) -> Dict[str, Any]: + def umfe(timestamp: int, messages: list[int]) -> dict[str, Any]: return dict( type="update_message_flags", operation="add", diff --git a/zerver/tests/test_event_system.py b/zerver/tests/test_event_system.py index 5ab5f9f01d..6c699d9b22 100644 --- a/zerver/tests/test_event_system.py +++ b/zerver/tests/test_event_system.py @@ -1,5 +1,5 @@ import time -from typing import Any, Callable, Dict, List +from typing import Any, Callable from unittest import mock from urllib.parse import urlsplit @@ -76,12 +76,12 @@ class EventsEndpointTest(ZulipTestCase): self.assert_json_error(result, "Could not allocate event queue") return_event_queue = "15:11" - return_user_events: List[Dict[str, Any]] = [] + return_user_events: list[dict[str, Any]] = [] # We choose realm_emoji somewhat randomly--we want # a "boring" event type for the purpose of this test. event_type = "realm_emoji" - empty_realm_emoji_dict: Dict[str, Any] = {} + empty_realm_emoji_dict: dict[str, Any] = {} test_event = dict(id=6, type=event_type, realm_emoji=empty_realm_emoji_dict) # Test that call is made to deal with a returning soft deactivated user. @@ -286,7 +286,7 @@ class GetEventsTest(ZulipTestCase): self, view_func: Callable[[HttpRequest, UserProfile], HttpResponse], user_profile: UserProfile, - post_data: Dict[str, Any], + post_data: dict[str, Any], ) -> HttpResponse: request = HostRequestMock(post_data, user_profile, tornado_handler=dummy_handler) return view_func(request, user_profile) @@ -430,7 +430,7 @@ class GetEventsTest(ZulipTestCase): user_profile = self.example_user("hamlet") self.login_user(user_profile) - def get_message(apply_markdown: bool, client_gravatar: bool) -> Dict[str, Any]: + def get_message(apply_markdown: bool, client_gravatar: bool) -> dict[str, Any]: result = self.tornado_call( get_events, user_profile, @@ -943,16 +943,16 @@ class ClientDescriptorsTest(ZulipTestCase): self.apply_markdown = apply_markdown self.client_gravatar = client_gravatar self.client_type_name = "whatever" - self.events: List[Dict[str, Any]] = [] + self.events: list[dict[str, Any]] = [] def accepts_messages(self) -> bool: return True - def accepts_event(self, event: Dict[str, Any]) -> bool: + def accepts_event(self, event: dict[str, Any]) -> bool: assert event["type"] == "message" return True - def add_event(self, event: Dict[str, Any]) -> None: + def add_event(self, event: dict[str, Any]) -> None: self.events.append(event) client1 = MockClient( @@ -1026,7 +1026,7 @@ class ClientDescriptorsTest(ZulipTestCase): # Setting users to `[]` bypasses code we don't care about # for this test--we assume client_info is correct in our mocks, # and we are interested in how messages are put on event queue. - users: List[Dict[str, Any]] = [] + users: list[dict[str, Any]] = [] with mock.patch( "zerver.tornado.event_queue.get_client_info_for_message_event", return_value=client_info diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index f10d908bfb..343060aea0 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -9,7 +9,7 @@ import time from contextlib import contextmanager from datetime import timedelta from io import StringIO -from typing import Any, Dict, Iterator, List, Optional, Set +from typing import Any, Iterator, Optional from unittest import mock import orjson @@ -276,7 +276,7 @@ class BaseAction(ZulipTestCase): def verify_action( self, *, - event_types: Optional[List[str]] = None, + event_types: Optional[list[str]] = None, include_subscribers: bool = True, state_change_expected: bool = True, notification_settings_null: bool = False, @@ -292,7 +292,7 @@ class BaseAction(ZulipTestCase): linkifier_url_template: bool = True, user_list_incomplete: bool = False, client_is_old: bool = False, - ) -> Iterator[List[Dict[str, Any]]]: + ) -> Iterator[list[dict[str, Any]]]: """ Make sure we have a clean slate of client descriptors for these tests. If we don't do this, then certain failures will only manifest when you @@ -344,7 +344,7 @@ class BaseAction(ZulipTestCase): if client_is_old: mark_clients_to_reload([client.event_queue.id]) - events: List[Dict[str, Any]] = [] + events: list[dict[str, Any]] = [] # We want even those `send_event` calls which have been hooked to # `transaction.on_commit` to execute in tests. @@ -413,9 +413,9 @@ class BaseAction(ZulipTestCase): self.match_states(hybrid_state, normal_state, events) def match_states( - self, state1: Dict[str, Any], state2: Dict[str, Any], events: List[Dict[str, Any]] + self, state1: dict[str, Any], state2: dict[str, Any], events: list[dict[str, Any]] ) -> None: - def normalize(state: Dict[str, Any]) -> None: + def normalize(state: dict[str, Any]) -> None: if "never_subscribed" in state: for u in state["never_subscribed"]: if "subscribers" in u: @@ -566,7 +566,7 @@ class NormalActionsTest(BaseAction): pm = Message.objects.order_by("-id")[0] content = "new content" rendering_result = render_message_markdown(pm, content) - prior_mention_user_ids: Set[int] = set() + prior_mention_user_ids: set[int] = set() mention_backend = MentionBackend(self.user_profile.realm_id) mention_data = MentionData( mention_backend=mention_backend, @@ -654,7 +654,7 @@ class NormalActionsTest(BaseAction): do_change_subscription_property(hamlet, sub, stream, "is_muted", True, acting_user=None) def verify_events_generated_and_reset_visibility_policy( - events: List[Dict[str, Any]], stream_name: str, topic_name: str + events: list[dict[str, Any]], stream_name: str, topic_name: str ) -> None: # event-type: muted_topics check_muted_topics("events[0]", events[0]) @@ -870,7 +870,7 @@ class NormalActionsTest(BaseAction): message = Message.objects.order_by("-id")[0] content = "new content" rendering_result = render_message_markdown(message, content) - prior_mention_user_ids: Set[int] = set() + prior_mention_user_ids: set[int] = set() mention_backend = MentionBackend(self.user_profile.realm_id) mention_data = MentionData( mention_backend=mention_backend, @@ -3398,8 +3398,8 @@ class NormalActionsTest(BaseAction): class RealmPropertyActionTest(BaseAction): def do_set_realm_property_test(self, name: str) -> None: - bool_tests: List[bool] = [True, False, True] - test_values: Dict[str, Any] = dict( + bool_tests: list[bool] = [True, False, True] + test_values: dict[str, Any] = dict( default_language=["es", "de", "en"], description=["Realm description", "New description"], digest_weekday=[0, 1, 2], @@ -3742,8 +3742,8 @@ class RealmPropertyActionTest(BaseAction): self.do_set_realm_permission_group_setting_to_anonymous_groups_test(prop) def do_set_realm_user_default_setting_test(self, name: str) -> None: - bool_tests: List[bool] = [True, False, True] - test_values: Dict[str, Any] = dict( + bool_tests: list[bool] = [True, False, True] + test_values: dict[str, Any] = dict( web_font_size_px=[UserProfile.WEB_FONT_SIZE_PX_LEGACY], web_line_height_percent=[UserProfile.WEB_LINE_HEIGHT_PERCENT_LEGACY], color_scheme=UserProfile.COLOR_SCHEME_CHOICES, @@ -3874,7 +3874,7 @@ class UserDisplayActionTest(BaseAction): def do_change_user_settings_test(self, setting_name: str) -> None: """Test updating each setting in UserProfile.property_types dict.""" - test_changes: Dict[str, Any] = dict( + test_changes: dict[str, Any] = dict( emojiset=["twitter"], default_language=["es", "de", "en"], web_home_view=["all_messages", "inbox", "recent_topics"], diff --git a/zerver/tests/test_has_request_variables.py b/zerver/tests/test_has_request_variables.py index 47692357a1..4109340477 100644 --- a/zerver/tests/test_has_request_variables.py +++ b/zerver/tests/test_has_request_variables.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Optional, Sequence +from typing import Any, Optional, Sequence import orjson from django.http import HttpRequest, HttpResponse @@ -45,7 +45,7 @@ class REQTestCase(ZulipTestCase): self.assertEqual(str(cm.exception), "Can't decide between 'number' and 'x' arguments") def test_REQ_converter(self) -> None: - def my_converter(var_name: str, data: str) -> List[int]: + def my_converter(var_name: str, data: str) -> list[int]: lst = orjson.loads(data) if not isinstance(lst, list): raise ValueError("not a list") @@ -135,7 +135,7 @@ class REQTestCase(ZulipTestCase): def test_REQ_argument_type(self) -> None: @has_request_variables def get_payload( - request: HttpRequest, payload: Dict[str, Any] = REQ(argument_type="body") + request: HttpRequest, payload: dict[str, Any] = REQ(argument_type="body") ) -> HttpResponse: return json_response(data={"payload": payload}) diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index 2d9a51664a..474916a968 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -1,6 +1,6 @@ import calendar from datetime import timedelta, timezone -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any from unittest.mock import patch from urllib.parse import urlsplit @@ -497,7 +497,7 @@ class HomeTest(ZulipTestCase): ) def test_sentry_keys(self) -> None: - def sentry_params() -> Dict[str, Any] | None: + def sentry_params() -> dict[str, Any] | None: result = self._get_home_page() self.assertEqual(result.status_code, 200) return self._get_sentry_params(result) diff --git a/zerver/tests/test_import_export.py b/zerver/tests/test_import_export.py index 3e15ffc4f6..d1f675263b 100644 --- a/zerver/tests/test_import_export.py +++ b/zerver/tests/test_import_export.py @@ -4,7 +4,7 @@ import shutil import uuid from collections import defaultdict from datetime import datetime, timedelta, timezone -from typing import Any, Callable, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple +from typing import Any, Callable, Iterable, Optional from unittest.mock import patch import orjson @@ -321,7 +321,7 @@ class RealmImportExportTest(ExportFile): def export_realm( self, realm: Realm, - exportable_user_ids: Optional[Set[int]] = None, + exportable_user_ids: Optional[set[int]] = None, consent_message_id: Optional[int] = None, public_only: bool = False, ) -> None: @@ -351,7 +351,7 @@ class RealmImportExportTest(ExportFile): def export_realm_and_create_auditlog( self, original_realm: Realm, - exportable_user_ids: Optional[Set[int]] = None, + exportable_user_ids: Optional[set[int]] = None, consent_message_id: Optional[int] = None, public_only: bool = False, ) -> None: @@ -953,7 +953,7 @@ class RealmImportExportTest(ExportFile): getters = self.get_realm_getters() - snapshots: Dict[str, object] = {} + snapshots: dict[str, object] = {} for f in getters: snapshots[f.__name__] = f(original_realm) @@ -1169,9 +1169,9 @@ class RealmImportExportTest(ExportFile): f'data-user-id="{imported_hamlet_id}"', prev_version_of_message["prev_rendered_content"] ) - def get_realm_getters(self) -> List[Callable[[Realm], object]]: + def get_realm_getters(self) -> list[Callable[[Realm], object]]: names = set() - getters: List[Callable[[Realm], object]] = [] + getters: list[Callable[[Realm], object]] = [] def getter(f: Callable[[Realm], object]) -> Callable[[Realm], object]: getters.append(f) @@ -1183,19 +1183,19 @@ class RealmImportExportTest(ExportFile): return f @getter - def get_admin_bot_emails(r: Realm) -> Set[str]: + def get_admin_bot_emails(r: Realm) -> set[str]: return {user.email for user in r.get_admin_users_and_bots()} @getter - def get_active_emails(r: Realm) -> Set[str]: + def get_active_emails(r: Realm) -> set[str]: return {user.email for user in r.get_active_users()} @getter - def get_active_stream_names(r: Realm) -> Set[str]: + def get_active_stream_names(r: Realm) -> set[str]: return {stream.name for stream in get_active_streams(r)} @getter - def get_group_names_for_group_settings(r: Realm) -> Set[str]: + def get_group_names_for_group_settings(r: Realm) -> set[str]: return { getattr(r, permission_name).named_user_group.name for permission_name in Realm.REALM_PERMISSION_GROUP_SETTINGS @@ -1219,22 +1219,22 @@ class RealmImportExportTest(ExportFile): return get_recipient_user(r).type # test subscription - def get_subscribers(recipient: Recipient) -> Set[str]: + def get_subscribers(recipient: Recipient) -> set[str]: subscriptions = Subscription.objects.filter(recipient=recipient) users = {sub.user_profile.email for sub in subscriptions} return users @getter - def get_stream_subscribers(r: Realm) -> Set[str]: + def get_stream_subscribers(r: Realm) -> set[str]: return get_subscribers(get_recipient_stream(r)) @getter - def get_user_subscribers(r: Realm) -> Set[str]: + def get_user_subscribers(r: Realm) -> set[str]: return get_subscribers(get_recipient_user(r)) # test custom profile fields @getter - def get_custom_profile_field_names(r: Realm) -> Set[str]: + def get_custom_profile_field_names(r: Realm) -> set[str]: custom_profile_fields = CustomProfileField.objects.filter(realm=r) custom_profile_field_names = {field.name for field in custom_profile_fields} return custom_profile_field_names @@ -1242,20 +1242,20 @@ class RealmImportExportTest(ExportFile): @getter def get_custom_profile_with_field_type_user( r: Realm, - ) -> Tuple[Set[str], Set[str], Set[FrozenSet[str]]]: + ) -> tuple[set[str], set[str], set[frozenset[str]]]: fields = CustomProfileField.objects.filter(field_type=CustomProfileField.USER, realm=r) def get_email(user_id: int) -> str: return UserProfile.objects.get(id=user_id).email - def get_email_from_value(field_value: CustomProfileFieldValue) -> Set[str]: + def get_email_from_value(field_value: CustomProfileFieldValue) -> set[str]: user_id_list = orjson.loads(field_value.value) return {get_email(user_id) for user_id in user_id_list} def custom_profile_field_values_for( fields: Iterable[CustomProfileField], - ) -> Set[FrozenSet[str]]: - user_emails: Set[FrozenSet[str]] = set() + ) -> set[frozenset[str]]: + user_emails: set[frozenset[str]] = set() for field in fields: values = CustomProfileFieldValue.objects.filter(field=field) for value in values: @@ -1271,7 +1271,7 @@ class RealmImportExportTest(ExportFile): # test realmauditlog @getter - def get_realm_audit_log_event_type(r: Realm) -> Set[int]: + def get_realm_audit_log_event_type(r: Realm) -> set[int]: realmauditlogs = RealmAuditLog.objects.filter(realm=r).exclude( event_type__in=[ RealmAuditLog.REALM_PLAN_TYPE_CHANGED, @@ -1296,17 +1296,17 @@ class RealmImportExportTest(ExportFile): return group_direct_message.content @getter - def get_alertwords(r: Realm) -> Set[str]: + def get_alertwords(r: Realm) -> set[str]: return {rec.word for rec in AlertWord.objects.filter(realm_id=r.id)} @getter - def get_realm_emoji_names(r: Realm) -> Set[str]: + def get_realm_emoji_names(r: Realm) -> set[str]: names = {rec.name for rec in RealmEmoji.objects.filter(realm_id=r.id)} assert "hawaii" in names return names @getter - def get_realm_user_statuses(r: Realm) -> Set[Tuple[str, str, str]]: + def get_realm_user_statuses(r: Realm) -> set[tuple[str, str, str]]: cordelia = self.example_user("cordelia") tups = { (rec.user_profile.full_name, rec.emoji_name, rec.status_text) @@ -1316,7 +1316,7 @@ class RealmImportExportTest(ExportFile): return tups @getter - def get_realm_emoji_reactions(r: Realm) -> Set[Tuple[str, str]]: + def get_realm_emoji_reactions(r: Realm) -> set[tuple[str, str]]: cordelia = self.example_user("cordelia") tups = { (rec.emoji_name, rec.user_profile.full_name) @@ -1329,7 +1329,7 @@ class RealmImportExportTest(ExportFile): # test onboarding step @getter - def get_onboarding_steps(r: Realm) -> Set[str]: + def get_onboarding_steps(r: Realm) -> set[str]: user_id = get_user_id(r, "King Hamlet") onboarding_steps = set( OnboardingStep.objects.filter(user_id=user_id).values_list( @@ -1340,7 +1340,7 @@ class RealmImportExportTest(ExportFile): # test muted topics @getter - def get_muted_topics(r: Realm) -> Set[str]: + def get_muted_topics(r: Realm) -> set[str]: user_profile_id = get_user_id(r, "King Hamlet") muted_topics = UserTopic.objects.filter( user_profile_id=user_profile_id, visibility_policy=UserTopic.VisibilityPolicy.MUTED @@ -1349,7 +1349,7 @@ class RealmImportExportTest(ExportFile): return topic_names @getter - def get_muted_users(r: Realm) -> Set[Tuple[str, str, str]]: + def get_muted_users(r: Realm) -> set[tuple[str, str, str]]: mute_objects = MutedUser.objects.filter(user_profile__realm=r) muter_tuples = { ( @@ -1362,22 +1362,22 @@ class RealmImportExportTest(ExportFile): return muter_tuples @getter - def get_user_group_names(r: Realm) -> Set[str]: + def get_user_group_names(r: Realm) -> set[str]: return {group.named_user_group.name for group in UserGroup.objects.filter(realm=r)} @getter - def get_named_user_group_names(r: Realm) -> Set[str]: + def get_named_user_group_names(r: Realm) -> set[str]: return {group.name for group in NamedUserGroup.objects.filter(realm=r)} @getter - def get_user_membership(r: Realm) -> Set[str]: + def get_user_membership(r: Realm) -> set[str]: usergroup = NamedUserGroup.objects.get(realm=r, name="hamletcharacters") usergroup_membership = UserGroupMembership.objects.filter(user_group=usergroup) users = {membership.user_profile.email for membership in usergroup_membership} return users @getter - def get_group_group_membership(r: Realm) -> Set[str]: + def get_group_group_membership(r: Realm) -> set[str]: usergroup = NamedUserGroup.objects.get(realm=r, name="role:members") group_group_membership = GroupGroupMembership.objects.filter(supergroup=usergroup) subgroups = { @@ -1386,7 +1386,7 @@ class RealmImportExportTest(ExportFile): return subgroups @getter - def get_user_group_direct_members(r: Realm) -> Set[str]: + def get_user_group_direct_members(r: Realm) -> set[str]: # We already check the members of the group through UserGroupMembership # objects, but we also want to check direct_members field is set # correctly since we do not include this in export data. @@ -1396,7 +1396,7 @@ class RealmImportExportTest(ExportFile): return direct_member_emails @getter - def get_user_group_direct_subgroups(r: Realm) -> Set[str]: + def get_user_group_direct_subgroups(r: Realm) -> set[str]: # We already check the subgroups of the group through GroupGroupMembership # objects, but we also want to check that direct_subgroups field is set # correctly since we do not include this in export data. @@ -1412,13 +1412,13 @@ class RealmImportExportTest(ExportFile): # test botstoragedata and botconfigdata @getter - def get_botstoragedata(r: Realm) -> Dict[str, object]: + def get_botstoragedata(r: Realm) -> dict[str, object]: bot_profile = UserProfile.objects.get(full_name="bot", realm=r) bot_storage_data = BotStorageData.objects.get(bot_profile=bot_profile) return {"key": bot_storage_data.key, "data": bot_storage_data.value} @getter - def get_botconfigdata(r: Realm) -> Dict[str, object]: + def get_botconfigdata(r: Realm) -> dict[str, object]: bot_profile = UserProfile.objects.get(full_name="bot", realm=r) bot_config_data = BotConfigData.objects.get(bot_profile=bot_profile) return {"key": bot_config_data.key, "data": bot_config_data.value} @@ -1430,14 +1430,14 @@ class RealmImportExportTest(ExportFile): return messages @getter - def get_stream_topics(r: Realm) -> Set[str]: + def get_stream_topics(r: Realm) -> set[str]: messages = get_stream_messages(r) topic_names = {m.topic_name() for m in messages} return topic_names # test usermessages @getter - def get_usermessages_user(r: Realm) -> Set[str]: + def get_usermessages_user(r: Realm) -> set[str]: messages = get_stream_messages(r).order_by("content") usermessage = UserMessage.objects.filter(message=messages[0]) usermessage_user = {um.user_profile.email for um in usermessage} @@ -1472,7 +1472,7 @@ class RealmImportExportTest(ExportFile): return mention_message.content @getter - def get_userpresence_timestamp(r: Realm) -> Set[object]: + def get_userpresence_timestamp(r: Realm) -> set[object]: # It should be sufficient to compare UserPresence timestamps to verify # they got exported/imported correctly. return set( @@ -1482,7 +1482,7 @@ class RealmImportExportTest(ExportFile): ) @getter - def get_realm_user_default_values(r: Realm) -> Dict[str, object]: + def get_realm_user_default_values(r: Realm) -> dict[str, object]: realm_user_default = RealmUserDefault.objects.get(realm=r) return { "default_language": realm_user_default.default_language, @@ -1560,7 +1560,7 @@ class RealmImportExportTest(ExportFile): def mock_send_to_push_bouncer_response( # type: ignore[return] method: str, *args: Any - ) -> Optional[Dict[str, int]]: + ) -> Optional[dict[str, int]]: if method == "GET": return get_response @@ -1952,7 +1952,7 @@ class SingleUserExportTest(ExportFile): # We register checkers during test setup, and then we call them at the end. checkers = {} - def checker(f: Callable[[List[Record]], None]) -> Callable[[List[Record]], None]: + def checker(f: Callable[[list[Record]], None]) -> Callable[[list[Record]], None]: # Every checker function that gets decorated here should be named # after one of the tables that we export in the single-user # export. The table name then is used by code toward the end of the @@ -1972,7 +1972,7 @@ class SingleUserExportTest(ExportFile): now = timezone_now() @checker - def zerver_userprofile(records: List[Record]) -> None: + def zerver_userprofile(records: list[Record]) -> None: (rec,) = records self.assertEqual(rec["id"], cordelia.id) self.assertEqual(rec["email"], cordelia.email) @@ -1987,7 +1987,7 @@ class SingleUserExportTest(ExportFile): do_add_alert_words(hamlet, ["bogus"]) @checker - def zerver_alertword(records: List[Record]) -> None: + def zerver_alertword(records: list[Record]) -> None: self.assertEqual(records[-1]["word"], "pizza") favorite_city = try_add_realm_custom_profile_field( @@ -2005,7 +2005,7 @@ class SingleUserExportTest(ExportFile): set_favorite_city(othello, "Moscow") @checker - def zerver_customprofilefieldvalue(records: List[Record]) -> None: + def zerver_customprofilefieldvalue(records: list[Record]) -> None: (rec,) = records self.assertEqual(rec["field"], favorite_city.id) self.assertEqual(rec["rendered_value"], "

Seattle

") @@ -2014,7 +2014,7 @@ class SingleUserExportTest(ExportFile): do_mute_user(hamlet, cordelia) # should be ignored @checker - def zerver_muteduser(records: List[Record]) -> None: + def zerver_muteduser(records: list[Record]) -> None: self.assertEqual(records[-1]["muted_user"], othello.id) smile_message_id = self.send_stream_message(hamlet, "Denmark") @@ -2029,7 +2029,7 @@ class SingleUserExportTest(ExportFile): reaction = Reaction.objects.order_by("id").last() @checker - def zerver_reaction(records: List[Record]) -> None: + def zerver_reaction(records: list[Record]) -> None: assert reaction self.assertEqual( records[-1], @@ -2049,19 +2049,19 @@ class SingleUserExportTest(ExportFile): self.subscribe(othello, "bogus") @checker - def zerver_recipient(records: List[Record]) -> None: + def zerver_recipient(records: list[Record]) -> None: last_recipient = Recipient.objects.get(id=records[-1]["id"]) self.assertEqual(last_recipient.type, Recipient.STREAM) stream_id = last_recipient.type_id self.assertEqual(stream_id, get_stream("Scotland", realm).id) @checker - def zerver_stream(records: List[Record]) -> None: + def zerver_stream(records: list[Record]) -> None: streams = {rec["name"] for rec in records} self.assertEqual(streams, {"Scotland", "Verona"}) @checker - def zerver_subscription(records: List[Record]) -> None: + def zerver_subscription(records: list[Record]) -> None: last_recipient = Recipient.objects.get(id=records[-1]["recipient"]) self.assertEqual(last_recipient.type, Recipient.STREAM) stream_id = last_recipient.type_id @@ -2083,7 +2083,7 @@ class SingleUserExportTest(ExportFile): ) @checker - def zerver_useractivity(records: List[Record]) -> None: + def zerver_useractivity(records: list[Record]) -> None: (rec,) = records self.assertEqual( rec, @@ -2102,7 +2102,7 @@ class SingleUserExportTest(ExportFile): do_update_user_activity_interval(othello, now) @checker - def zerver_useractivityinterval(records: List[Record]) -> None: + def zerver_useractivityinterval(records: list[Record]) -> None: (rec,) = records self.assertEqual(rec["user_profile"], cordelia.id) self.assertEqual(make_datetime(rec["start"]), now) @@ -2111,7 +2111,7 @@ class SingleUserExportTest(ExportFile): do_update_user_presence(othello, client, now, UserPresence.LEGACY_STATUS_IDLE_INT) @checker - def zerver_userpresence(records: List[Record]) -> None: + def zerver_userpresence(records: list[Record]) -> None: self.assertEqual(make_datetime(records[-1]["last_connected_time"]), now) self.assertEqual(make_datetime(records[-1]["last_active_time"]), now) @@ -2136,7 +2136,7 @@ class SingleUserExportTest(ExportFile): ) @checker - def zerver_userstatus(records: List[Record]) -> None: + def zerver_userstatus(records: list[Record]) -> None: rec = records[-1] self.assertEqual(rec["status_text"], "on vacation") @@ -2151,7 +2151,7 @@ class SingleUserExportTest(ExportFile): ) @checker - def zerver_usertopic(records: List[Record]) -> None: + def zerver_usertopic(records: list[Record]) -> None: rec = records[-1] self.assertEqual(rec["topic_name"], "bagpipe music") self.assertEqual(rec["visibility_policy"], UserTopic.VisibilityPolicy.MUTED) @@ -2168,7 +2168,7 @@ class SingleUserExportTest(ExportFile): ) @checker - def analytics_usercount(records: List[Record]) -> None: + def analytics_usercount(records: list[Record]) -> None: (rec,) = records self.assertEqual(rec["value"], 42) @@ -2176,7 +2176,7 @@ class SingleUserExportTest(ExportFile): OnboardingStep.objects.create(user=othello, onboarding_step="bogus") @checker - def zerver_onboardingstep(records: List[Record]) -> None: + def zerver_onboardingstep(records: list[Record]) -> None: self.assertEqual(records[-1]["onboarding_step"], "topics") """ @@ -2185,7 +2185,7 @@ class SingleUserExportTest(ExportFile): """ @checker - def zerver_realmauditlog(records: List[Record]) -> None: + def zerver_realmauditlog(records: list[Record]) -> None: self.assertEqual(records[-1]["modified_stream"], scotland.id) output_dir = make_export_output_dir() diff --git a/zerver/tests/test_invite.py b/zerver/tests/test_invite.py index 184accc8db..aa725b34d2 100644 --- a/zerver/tests/test_invite.py +++ b/zerver/tests/test_invite.py @@ -1,6 +1,6 @@ import re from datetime import datetime, timedelta -from typing import TYPE_CHECKING, List, Optional, Sequence, Union +from typing import TYPE_CHECKING, Optional, Sequence, Union from unittest.mock import patch from urllib.parse import quote, urlencode @@ -154,7 +154,7 @@ class StreamSetupTest(ZulipTestCase): class InviteUserBase(ZulipTestCase): - def check_sent_emails(self, correct_recipients: List[str], clear: bool = False) -> None: + def check_sent_emails(self, correct_recipients: list[str], clear: bool = False) -> None: self.assert_length(mail.outbox, len(correct_recipients)) email_recipients = [email.recipients()[0] for email in mail.outbox] self.assertEqual(sorted(email_recipients), sorted(correct_recipients)) @@ -2446,7 +2446,7 @@ class MultiuseInviteTest(ZulipTestCase): def generate_multiuse_invite_link( self, - streams: Optional[List[Stream]] = None, + streams: Optional[list[Stream]] = None, date_sent: Optional[datetime] = None, include_realm_default_subscriptions: bool = False, ) -> str: diff --git a/zerver/tests/test_management_commands.py b/zerver/tests/test_management_commands.py index 4a524f5e1c..c53f90a7af 100644 --- a/zerver/tests/test_management_commands.py +++ b/zerver/tests/test_management_commands.py @@ -1,7 +1,7 @@ import os import re from datetime import timedelta -from typing import Any, Dict, List, Optional +from typing import Any, Optional from unittest import mock, skipUnless from unittest.mock import MagicMock, call, patch from urllib.parse import quote, quote_plus @@ -89,12 +89,12 @@ class TestZulipBaseCommand(ZulipTestCase): self.assertEqual(get_user_profile_by_email(email), user_profile) def get_users_sorted( - self, options: Dict[str, Any], realm: Optional[Realm], **kwargs: Any - ) -> List[UserProfile]: + self, options: dict[str, Any], realm: Optional[Realm], **kwargs: Any + ) -> list[UserProfile]: user_profiles = self.command.get_users(options, realm, **kwargs) return sorted(user_profiles, key=lambda x: x.email) - def sorted_users(self, users: List[UserProfile]) -> List[UserProfile]: + def sorted_users(self, users: list[UserProfile]) -> list[UserProfile]: return sorted(users, key=lambda x: x.email) def test_get_users(self) -> None: diff --git a/zerver/tests/test_markdown.py b/zerver/tests/test_markdown.py index 88dc21d5eb..505377514f 100644 --- a/zerver/tests/test_markdown.py +++ b/zerver/tests/test_markdown.py @@ -3,7 +3,7 @@ import os import re from html import escape from textwrap import dedent -from typing import Any, Dict, List, Optional, Set, Tuple +from typing import Any, Optional from unittest import mock import orjson @@ -377,7 +377,7 @@ class MarkdownMiscTest(ZulipTestCase): class MarkdownListPreprocessorTest(ZulipTestCase): # We test that the preprocessor inserts blank lines at correct places. # We use <> to indicate that we need to insert a blank line here. - def split_message(self, msg: str) -> Tuple[List[str], List[str]]: + def split_message(self, msg: str) -> tuple[list[str], list[str]]: original = msg.replace("<>", "").split("\n") expected = re.split(r"\n|<>", msg) return original, expected @@ -482,7 +482,7 @@ class MarkdownTest(ZulipTestCase): else: super().assertEqual(first, second) - def load_markdown_tests(self) -> Tuple[Dict[str, Any], List[List[str]]]: + def load_markdown_tests(self) -> tuple[dict[str, Any], list[list[str]]]: test_fixtures = {} with open( os.path.join(os.path.dirname(__file__), "fixtures/markdown_test_cases.json"), "rb" @@ -503,7 +503,7 @@ class MarkdownTest(ZulipTestCase): def test_markdown_fixtures_unique_names(self) -> None: # All markdown fixtures must have unique names. - found_names: Set[str] = set() + found_names: set[str] = set() with open( os.path.join(os.path.dirname(__file__), "fixtures/markdown_test_cases.json"), "rb" ) as f: @@ -1271,7 +1271,7 @@ class MarkdownTest(ZulipTestCase): ) def check_add_linkifiers( - self, linkifiers: List[RealmFilter], expected_linkifier_reprs: List[str] + self, linkifiers: list[RealmFilter], expected_linkifier_reprs: list[str] ) -> None: self.assert_length(linkifiers, len(expected_linkifier_reprs)) for linkifier, expected_linkifier_repr in zip(linkifiers, expected_linkifier_reprs): @@ -1760,7 +1760,7 @@ class MarkdownTest(ZulipTestCase): self.assertEqual(rendering_result.user_ids_with_alert_words, set()) def test_alert_words_returns_user_ids_with_alert_words(self) -> None: - alert_words_for_users: Dict[str, List[str]] = { + alert_words_for_users: dict[str, list[str]] = { "hamlet": ["how"], "cordelia": ["this possible"], "iago": ["hello"], @@ -1768,7 +1768,7 @@ class MarkdownTest(ZulipTestCase): "othello": ["how are you"], "aaron": ["hey"], } - user_profiles: Dict[str, UserProfile] = {} + user_profiles: dict[str, UserProfile] = {} for username, alert_words in alert_words_for_users.items(): user_profile = self.example_user(username) user_profiles.update({username: user_profile}) @@ -1788,7 +1788,7 @@ class MarkdownTest(ZulipTestCase): content = "hello how is this possible how are you doing today" rendering_result = render(msg, content) - expected_user_ids: Set[int] = { + expected_user_ids: set[int] = { user_profiles["hamlet"].id, user_profiles["cordelia"].id, user_profiles["iago"].id, @@ -1799,14 +1799,14 @@ class MarkdownTest(ZulipTestCase): self.assertEqual(rendering_result.user_ids_with_alert_words, expected_user_ids) def test_alert_words_returns_user_ids_with_alert_words_1(self) -> None: - alert_words_for_users: Dict[str, List[str]] = { + alert_words_for_users: dict[str, list[str]] = { "hamlet": ["provisioning", "Prod deployment"], "cordelia": ["test", "Prod"], "iago": ["prod"], "prospero": ["deployment"], "othello": ["last"], } - user_profiles: Dict[str, UserProfile] = {} + user_profiles: dict[str, UserProfile] = {} for username, alert_words in alert_words_for_users.items(): user_profile = self.example_user(username) user_profiles.update({username: user_profile}) @@ -1830,7 +1830,7 @@ class MarkdownTest(ZulipTestCase): and this is a new line last""" rendering_result = render(msg, content) - expected_user_ids: Set[int] = { + expected_user_ids: set[int] = { user_profiles["hamlet"].id, user_profiles["cordelia"].id, user_profiles["iago"].id, @@ -1841,14 +1841,14 @@ class MarkdownTest(ZulipTestCase): self.assertEqual(rendering_result.user_ids_with_alert_words, expected_user_ids) def test_alert_words_returns_user_ids_with_alert_words_in_french(self) -> None: - alert_words_for_users: Dict[str, List[str]] = { + alert_words_for_users: dict[str, list[str]] = { "hamlet": ["rΓ©glementaire", "une politique", "une merveille"], "cordelia": ["Γ©normΓ©ment", "Prod"], "iago": ["prod"], "prospero": ["deployment"], "othello": ["last"], } - user_profiles: Dict[str, UserProfile] = {} + user_profiles: dict[str, UserProfile] = {} for username, alert_words in alert_words_for_users.items(): user_profile = self.example_user(username) user_profiles.update({username: user_profile}) @@ -1871,12 +1871,12 @@ class MarkdownTest(ZulipTestCase): et j'espΓ¨re qu'il n'y n' rΓ©glementaire a pas de mots d'alerte dans ce texte franΓ§ais """ rendering_result = render(msg, content) - expected_user_ids: Set[int] = {user_profiles["hamlet"].id, user_profiles["cordelia"].id} + expected_user_ids: set[int] = {user_profiles["hamlet"].id, user_profiles["cordelia"].id} # Only hamlet and cordelia have their alert-words appear in the message content self.assertEqual(rendering_result.user_ids_with_alert_words, expected_user_ids) def test_alert_words_returns_empty_user_ids_with_alert_words(self) -> None: - alert_words_for_users: Dict[str, List[str]] = { + alert_words_for_users: dict[str, list[str]] = { "hamlet": [], "cordelia": [], "iago": [], @@ -1884,7 +1884,7 @@ class MarkdownTest(ZulipTestCase): "othello": [], "aaron": [], } - user_profiles: Dict[str, UserProfile] = {} + user_profiles: dict[str, UserProfile] = {} for username, alert_words in alert_words_for_users.items(): user_profile = self.example_user(username) user_profiles.update({username: user_profile}) @@ -1905,22 +1905,22 @@ class MarkdownTest(ZulipTestCase): in sending of the message """ rendering_result = render(msg, content) - expected_user_ids: Set[int] = set() + expected_user_ids: set[int] = set() # None of the users have their alert-words appear in the message content self.assertEqual(rendering_result.user_ids_with_alert_words, expected_user_ids) - def get_mock_alert_words(self, num_words: int, word_length: int) -> List[str]: + def get_mock_alert_words(self, num_words: int, word_length: int) -> list[str]: alert_words = ["x" * word_length] * num_words # type List[str] return alert_words def test_alert_words_with_empty_alert_words(self) -> None: - alert_words_for_users: Dict[str, List[str]] = { + alert_words_for_users: dict[str, list[str]] = { "hamlet": [], "cordelia": [], "iago": [], "othello": [], } - user_profiles: Dict[str, UserProfile] = {} + user_profiles: dict[str, UserProfile] = {} for username, alert_words in alert_words_for_users.items(): user_profile = self.example_user(username) user_profiles.update({username: user_profile}) @@ -1940,17 +1940,17 @@ class MarkdownTest(ZulipTestCase): content = """This is to test a empty alert words i.e. no user has any alert-words set""" rendering_result = render(msg, content) - expected_user_ids: Set[int] = set() + expected_user_ids: set[int] = set() self.assertEqual(rendering_result.user_ids_with_alert_words, expected_user_ids) def test_alert_words_returns_user_ids_with_alert_words_with_huge_alert_words(self) -> None: - alert_words_for_users: Dict[str, List[str]] = { + alert_words_for_users: dict[str, list[str]] = { "hamlet": ["issue124"], "cordelia": self.get_mock_alert_words(500, 10), "iago": self.get_mock_alert_words(500, 10), "othello": self.get_mock_alert_words(500, 10), } - user_profiles: Dict[str, UserProfile] = {} + user_profiles: dict[str, UserProfile] = {} for username, alert_words in alert_words_for_users.items(): user_profile = self.example_user(username) user_profiles.update({username: user_profile}) @@ -1976,7 +1976,7 @@ class MarkdownTest(ZulipTestCase): between 1 and 100 for you. The process is fairly simple """ rendering_result = render(msg, content) - expected_user_ids: Set[int] = {user_profiles["hamlet"].id} + expected_user_ids: set[int] = {user_profiles["hamlet"].id} # Only hamlet has alert-word 'issue124' present in the message content self.assertEqual(rendering_result.user_ids_with_alert_words, expected_user_ids) @@ -2382,7 +2382,7 @@ class MarkdownTest(ZulipTestCase): def test_possible_mentions(self) -> None: def assert_mentions( content: str, - names: Set[str], + names: set[str], has_topic_wildcards: bool = False, has_stream_wildcards: bool = False, ) -> None: @@ -2705,7 +2705,7 @@ class MarkdownTest(ZulipTestCase): self.assertEqual(rendering_result.mentions_user_group_ids, {user_group.id}) def test_possible_user_group_mentions(self) -> None: - def assert_mentions(content: str, names: Set[str]) -> None: + def assert_mentions(content: str, names: set[str]) -> None: self.assertEqual(possible_user_group_mentions(content), names) assert_mentions("", set()) diff --git a/zerver/tests/test_mattermost_importer.py b/zerver/tests/test_mattermost_importer.py index f6dafb92f5..b6edbb0a15 100644 --- a/zerver/tests/test_mattermost_importer.py +++ b/zerver/tests/test_mattermost_importer.py @@ -1,6 +1,6 @@ import filecmp import os -from typing import Any, Dict, List +from typing import Any from unittest.mock import call, patch import orjson @@ -443,8 +443,8 @@ class MatterMostImporter(ZulipTestCase): team_name=team_name, ) - zerver_attachments: List[ZerverFieldsT] = [] - uploads_list: List[ZerverFieldsT] = [] + zerver_attachments: list[ZerverFieldsT] = [] + uploads_list: list[ZerverFieldsT] = [] process_message_attachments( attachments=mattermost_data["post"]["direct_post"][0]["attachments"], @@ -594,7 +594,7 @@ class MatterMostImporter(ZulipTestCase): fixture_file_name = self.fixture_file_name("export.json", "mattermost_fixtures") mattermost_data = mattermost_data_file_to_dict(fixture_file_name) - total_reactions: List[Dict[str, Any]] = [] + total_reactions: list[dict[str, Any]] = [] reactions = [ {"user": "harry", "create_at": 1553165521410, "emoji_name": "tick"}, diff --git a/zerver/tests/test_message_dict.py b/zerver/tests/test_message_dict.py index 078cc80585..35edd120bc 100644 --- a/zerver/tests/test_message_dict.py +++ b/zerver/tests/test_message_dict.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List +from typing import Any from unittest import mock from django.utils.timezone import now as timezone_now @@ -72,7 +72,7 @@ class MessageDictTest(ZulipTestCase): def get_send_message_payload( msg_id: int, apply_markdown: bool, client_gravatar: bool - ) -> Dict[str, Any]: + ) -> dict[str, Any]: msg = reload_message(msg_id) wide_dict = MessageDict.wide_dict(msg) @@ -85,7 +85,7 @@ class MessageDictTest(ZulipTestCase): def get_fetch_payload( msg_id: int, apply_markdown: bool, client_gravatar: bool - ) -> Dict[str, Any]: + ) -> dict[str, Any]: msg = reload_message(msg_id) unhydrated_dict = MessageDict.messages_to_encoded_cache_helper([msg])[0] # The next step mutates the dict in place @@ -260,7 +260,7 @@ class MessageDictTest(ZulipTestCase): msg_id = self.send_stream_message(sender, "Denmark", "hello world", topic_name, realm) return Message.objects.get(id=msg_id) - def assert_topic_links(links: List[Dict[str, str]], msg: Message) -> None: + def assert_topic_links(links: list[dict[str, str]], msg: Message) -> None: dct = MessageDict.messages_to_encoded_cache_helper([msg])[0] self.assertEqual(dct[TOPIC_LINKS], links) @@ -348,7 +348,7 @@ class MessageHydrationTest(ZulipTestCase): def test_hydrate_pm_recipient_info(self) -> None: cordelia = self.example_user("cordelia") - display_recipient: List[UserDisplayRecipient] = [ + display_recipient: list[UserDisplayRecipient] = [ dict( email="aaron@example.com", full_name="Aaron Smith", @@ -524,7 +524,7 @@ class TestMessageForIdsDisplayRecipientFetching(ZulipTestCase): def _verify_display_recipient( self, display_recipient: DisplayRecipientT, - expected_recipient_objects: List[UserProfile], + expected_recipient_objects: list[UserProfile], ) -> None: for user_profile in expected_recipient_objects: recipient_dict: UserDisplayRecipient = { diff --git a/zerver/tests/test_message_edit_notifications.py b/zerver/tests/test_message_edit_notifications.py index 1695ce32e5..f787c52599 100644 --- a/zerver/tests/test_message_edit_notifications.py +++ b/zerver/tests/test_message_edit_notifications.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Mapping, Union +from typing import Any, Mapping, Union from unittest import mock from django.utils.timezone import now as timezone_now @@ -75,7 +75,7 @@ class EditMessageSideEffectsTest(ZulipTestCase): def _get_queued_data_for_message_update( self, message_id: int, content: str, expect_short_circuit: bool = False - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """ This function updates a message with a post to /json/messages/(message_id). @@ -149,7 +149,7 @@ class EditMessageSideEffectsTest(ZulipTestCase): expect_short_circuit: bool = False, connected_to_zulip: bool = False, present_on_web: bool = False, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: message_id = self._login_and_send_original_stream_message( content=original_content, enable_online_push_notifications=enable_online_push_notifications, diff --git a/zerver/tests/test_message_fetch.py b/zerver/tests/test_message_fetch.py index 2010a0d0ed..4afb36e9ae 100644 --- a/zerver/tests/test_message_fetch.py +++ b/zerver/tests/test_message_fetch.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from datetime import timedelta -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple, Union +from typing import TYPE_CHECKING, Any, Optional, Sequence, Union from unittest import mock import orjson @@ -84,7 +84,7 @@ def get_sqlalchemy_sql(query: ClauseElement) -> str: return str(comp) -def get_sqlalchemy_query_params(query: ClauseElement) -> Dict[str, object]: +def get_sqlalchemy_query_params(query: ClauseElement) -> dict[str, object]: with get_sqlalchemy_connection() as conn: dialect = conn.dialect comp = query.compile(dialect=dialect) @@ -154,7 +154,7 @@ class NarrowBuilderTest(ZulipTestCase): ) # Add new channels - channel_dicts: List[StreamDict] = [ + channel_dicts: list[StreamDict] = [ { "name": "public-channel", "description": "Public channel with public history", @@ -190,7 +190,7 @@ class NarrowBuilderTest(ZulipTestCase): ) # Add new channels - channel_dicts: List[StreamDict] = [ + channel_dicts: list[StreamDict] = [ { "name": "public-channel", "description": "Public channel with public history", @@ -715,7 +715,7 @@ class NarrowBuilderTest(ZulipTestCase): ) def _do_add_term_test( - self, term: NarrowParameter, where_clause: str, params: Optional[Dict[str, Any]] = None + self, term: NarrowParameter, where_clause: str, params: Optional[dict[str, Any]] = None ) -> None: query = self._build_query(term) if params is not None: @@ -1213,14 +1213,14 @@ class IncludeHistoryTest(ZulipTestCase): class PostProcessTest(ZulipTestCase): def test_basics(self) -> None: def verify( - in_ids: List[int], + in_ids: list[int], num_before: int, num_after: int, first_visible_message_id: int, anchor: int, anchored_to_left: bool, anchored_to_right: bool, - out_ids: List[int], + out_ids: list[int], found_anchor: bool, found_oldest: bool, found_newest: bool, @@ -1794,9 +1794,9 @@ class PostProcessTest(ZulipTestCase): class GetOldMessagesTest(ZulipTestCase): def get_and_check_messages( - self, modified_params: Dict[str, Union[str, int]], **kwargs: Any - ) -> Dict[str, Any]: - post_params: Dict[str, Union[str, int]] = {"anchor": 1, "num_before": 1, "num_after": 1} + self, modified_params: dict[str, Union[str, int]], **kwargs: Any + ) -> dict[str, Any]: + post_params: dict[str, Union[str, int]] = {"anchor": 1, "num_before": 1, "num_after": 1} post_params.update(modified_params) payload = self.client_get("/json/messages", dict(post_params), **kwargs) self.assert_json_success(payload) @@ -1824,7 +1824,7 @@ class GetOldMessagesTest(ZulipTestCase): return result def message_visibility_test( - self, narrow: List[Dict[str, str]], message_ids: List[int], pivot_index: int + self, narrow: list[dict[str, str]], message_ids: list[int], pivot_index: int ) -> None: num_before = len(message_ids) @@ -1854,11 +1854,11 @@ class GetOldMessagesTest(ZulipTestCase): for message in result["messages"]: assert message["id"] in message_ids - def get_query_ids(self) -> Dict[str, Union[int, str]]: + def get_query_ids(self) -> dict[str, Union[int, str]]: hamlet_user = self.example_user("hamlet") othello_user = self.example_user("othello") - query_ids: Dict[str, Union[int, str]] = {} + query_ids: dict[str, Union[int, str]] = {} scotland_channel = get_stream("Scotland", hamlet_user.realm) assert scotland_channel.recipient_id is not None @@ -1901,7 +1901,7 @@ class GetOldMessagesTest(ZulipTestCase): self.login("hamlet") def get_content_type(apply_markdown: bool) -> str: - req: Dict[str, Any] = dict( + req: dict[str, Any] = dict( apply_markdown=orjson.dumps(apply_markdown).decode(), ) result = self.get_and_check_messages(req) @@ -1926,7 +1926,7 @@ class GetOldMessagesTest(ZulipTestCase): self.login("hamlet") - get_messages_params: Dict[str, Union[int, str]] = {"anchor": "newest", "num_before": 1} + get_messages_params: dict[str, Union[int, str]] = {"anchor": "newest", "num_before": 1} messages = self.get_and_check_messages(get_messages_params)["messages"] self.assert_length(messages, 1) message_id = messages[0]["id"] @@ -1993,7 +1993,7 @@ class GetOldMessagesTest(ZulipTestCase): self.check_unauthenticated_response(result, www_authenticate='Basic realm="zulip"') # Successful access to web-public channel messages. - web_public_channel_get_params: Dict[str, Union[int, str, bool]] = { + web_public_channel_get_params: dict[str, Union[int, str, bool]] = { **get_params, "narrow": orjson.dumps([dict(operator="channels", operand="web-public")]).decode(), } @@ -2007,14 +2007,14 @@ class GetOldMessagesTest(ZulipTestCase): self.assert_json_error(result, "Invalid subdomain", status_code=404) # Cannot access direct messages without login. - direct_messages_get_params: Dict[str, Union[int, str, bool]] = { + direct_messages_get_params: dict[str, Union[int, str, bool]] = { **get_params, "narrow": orjson.dumps([dict(operator="is", operand="dm")]).decode(), } result = self.client_get("/json/messages", dict(direct_messages_get_params)) self.check_unauthenticated_response(result) # "is:private" is a legacy alias for "is:dm". - private_message_get_params: Dict[str, Union[int, str, bool]] = { + private_message_get_params: dict[str, Union[int, str, bool]] = { **get_params, "narrow": orjson.dumps([dict(operator="is", operand="private")]).decode(), } @@ -2022,7 +2022,7 @@ class GetOldMessagesTest(ZulipTestCase): self.check_unauthenticated_response(result) # narrow should pass conditions in `is_spectator_compatible`. - non_spectator_compatible_narrow_get_params: Dict[str, Union[int, str, bool]] = { + non_spectator_compatible_narrow_get_params: dict[str, Union[int, str, bool]] = { **get_params, # "is:dm" is not a is_spectator_compatible narrow. "narrow": orjson.dumps( @@ -2047,7 +2047,7 @@ class GetOldMessagesTest(ZulipTestCase): self.assert_json_success(result) # Cannot access even web-public channels without channels:web-public narrow. - non_web_public_channel_get_params: Dict[str, Union[int, str, bool]] = { + non_web_public_channel_get_params: dict[str, Union[int, str, bool]] = { **get_params, "narrow": orjson.dumps([dict(operator="channel", operand="Rome")]).decode(), } @@ -2055,7 +2055,7 @@ class GetOldMessagesTest(ZulipTestCase): self.check_unauthenticated_response(result) # Verify that same request would work with channels:web-public added. - rome_web_public_get_params: Dict[str, Union[int, str, bool]] = { + rome_web_public_get_params: dict[str, Union[int, str, bool]] = { **get_params, "narrow": orjson.dumps( [ @@ -2069,7 +2069,7 @@ class GetOldMessagesTest(ZulipTestCase): self.assert_json_success(result) # Cannot access non-web-public channel even with channels:web-public narrow. - scotland_web_public_get_params: Dict[str, Union[int, str, bool]] = { + scotland_web_public_get_params: dict[str, Union[int, str, bool]] = { **get_params, "narrow": orjson.dumps( [ @@ -2127,7 +2127,7 @@ class GetOldMessagesTest(ZulipTestCase): def test_unauthenticated_narrow_to_web_public_channels(self) -> None: self.setup_web_public_test() - post_params: Dict[str, Union[int, str, bool]] = { + post_params: dict[str, Union[int, str, bool]] = { "anchor": 1, "num_before": 1, "num_after": 1, @@ -2210,11 +2210,11 @@ class GetOldMessagesTest(ZulipTestCase): """ me = self.example_user("hamlet") - def dr_emails(dr: List[UserDisplayRecipient]) -> str: + def dr_emails(dr: list[UserDisplayRecipient]) -> str: assert isinstance(dr, list) return ",".join(sorted({*(r["email"] for r in dr), me.email})) - def dr_ids(dr: List[UserDisplayRecipient]) -> List[int]: + def dr_ids(dr: list[UserDisplayRecipient]) -> list[int]: assert isinstance(dr, list) return sorted({*(r["id"] for r in dr), self.example_user("hamlet").id}) @@ -2243,7 +2243,7 @@ class GetOldMessagesTest(ZulipTestCase): for personal in personals: emails = dr_emails(get_display_recipient(personal.recipient)) self.login_user(me) - narrow: List[Dict[str, Any]] = [dict(operator="dm", operand=emails)] + narrow: list[dict[str, Any]] = [dict(operator="dm", operand=emails)] result = self.get_and_check_messages(dict(narrow=orjson.dumps(narrow).decode())) for message in result["messages"]: @@ -2266,7 +2266,7 @@ class GetOldMessagesTest(ZulipTestCase): self.example_user("othello").id, ] self.login_user(me) - narrow: List[Dict[str, Any]] = [ + narrow: list[dict[str, Any]] = [ dict(operator="dm", operand=non_existent_direct_message_group) ] result = self.get_and_check_messages(dict(narrow=orjson.dumps(narrow).decode())) @@ -2518,7 +2518,7 @@ class GetOldMessagesTest(ZulipTestCase): result = self.get_and_check_messages( dict(narrow=orjson.dumps(narrow).decode(), num_after=100) ) - fetched_messages: List[Dict[str, object]] = result["messages"] + fetched_messages: list[dict[str, object]] = result["messages"] self.assert_length(fetched_messages, num_messages_per_channel) for message_dict in fetched_messages: @@ -2942,7 +2942,7 @@ class GetOldMessagesTest(ZulipTestCase): dict(operator="sender", operand=cordelia.email), dict(operator="search", operand="lunch"), ] - result: Dict[str, Any] = self.get_and_check_messages( + result: dict[str, Any] = self.get_and_check_messages( dict( narrow=orjson.dumps(narrow).decode(), anchor=next_message_id, @@ -2954,7 +2954,7 @@ class GetOldMessagesTest(ZulipTestCase): messages = result["messages"] narrow = [dict(operator="search", operand="https://google.com")] - link_search_result: Dict[str, Any] = self.get_and_check_messages( + link_search_result: dict[str, Any] = self.get_and_check_messages( dict( narrow=orjson.dumps(narrow).decode(), anchor=next_message_id, @@ -2987,7 +2987,7 @@ class GetOldMessagesTest(ZulipTestCase): dict(operator="search", operand="discuss"), dict(operator="search", operand="after"), ] - multi_search_result: Dict[str, Any] = self.get_and_check_messages( + multi_search_result: dict[str, Any] = self.get_and_check_messages( dict( narrow=orjson.dumps(multi_search_narrow).decode(), anchor=next_message_id, @@ -3098,7 +3098,7 @@ class GetOldMessagesTest(ZulipTestCase): dict(operator="search", operand="special"), dict(operator="channel", operand="new-channel"), ] - channel_search_result: Dict[str, Any] = self.get_and_check_messages( + channel_search_result: dict[str, Any] = self.get_and_check_messages( dict( narrow=orjson.dumps(channel_search_narrow).decode(), anchor=0, @@ -3152,7 +3152,7 @@ class GetOldMessagesTest(ZulipTestCase): narrow = [ dict(operator="search", operand="ζ—₯本"), ] - result: Dict[str, Any] = self.get_and_check_messages( + result: dict[str, Any] = self.get_and_check_messages( dict( narrow=orjson.dumps(narrow).decode(), anchor=next_message_id, @@ -3183,7 +3183,7 @@ class GetOldMessagesTest(ZulipTestCase): dict(operator="search", operand="speak"), dict(operator="search", operand="wiki"), ] - multi_search_result: Dict[str, Any] = self.get_and_check_messages( + multi_search_result: dict[str, Any] = self.get_and_check_messages( dict( narrow=orjson.dumps(multi_search_narrow).decode(), anchor=next_message_id, @@ -3218,7 +3218,7 @@ class GetOldMessagesTest(ZulipTestCase): def search(operand: str, link: Optional[str], highlight: str) -> None: narrow = [dict(operator="search", operand=operand)] - link_search_result: Dict[str, Any] = self.get_and_check_messages( + link_search_result: dict[str, Any] = self.get_and_check_messages( dict( narrow=orjson.dumps(narrow).decode(), anchor=next_message_id, @@ -3298,7 +3298,7 @@ class GetOldMessagesTest(ZulipTestCase): special_search_narrow = [ dict(operator="search", operand="butter"), ] - special_search_result: Dict[str, Any] = self.get_and_check_messages( + special_search_result: dict[str, Any] = self.get_and_check_messages( dict( narrow=orjson.dumps(special_search_narrow).decode(), anchor=next_message_id, @@ -3377,7 +3377,7 @@ class GetOldMessagesTest(ZulipTestCase): anchor = self.send_stream_message(cordelia, "Verona") narrow = [dict(operator="sender", operand=cordelia.email)] - result: Dict[str, Any] = self.get_and_check_messages( + result: dict[str, Any] = self.get_and_check_messages( dict( narrow=orjson.dumps(narrow).decode(), anchor=anchor, @@ -3410,7 +3410,7 @@ class GetOldMessagesTest(ZulipTestCase): self.assertEqual(result["messages"][0]["id"], anchor) def test_get_visible_messages_with_anchor(self) -> None: - def messages_matches_ids(messages: List[Dict[str, Any]], message_ids: List[int]) -> None: + def messages_matches_ids(messages: list[dict[str, Any]], message_ids: list[int]) -> None: self.assert_length(messages, len(message_ids)) for message in messages: assert message["id"] in message_ids @@ -3688,7 +3688,7 @@ class GetOldMessagesTest(ZulipTestCase): """ self.login("hamlet") - required_args: Tuple[Tuple[str, int], ...] = (("num_before", 1), ("num_after", 1)) + required_args: tuple[tuple[str, int], ...] = (("num_before", 1), ("num_after", 1)) for i in range(len(required_args)): post_params = dict(required_args[:i] + required_args[i + 1 :]) @@ -3718,7 +3718,7 @@ class GetOldMessagesTest(ZulipTestCase): other_params = {"narrow": {}, "anchor": 0} int_params = ["num_before", "num_after"] - invalid_parameters: List[InvalidParam] = [ + invalid_parameters: list[InvalidParam] = [ InvalidParam(value=False, expected_error="is not valid JSON"), InvalidParam(value="", expected_error="is not valid JSON"), InvalidParam(value="-1", expected_error="is too small"), @@ -3752,7 +3752,7 @@ class GetOldMessagesTest(ZulipTestCase): other_params = {"anchor": 0, "num_before": 0, "num_after": 0} - invalid_parameters: List[InvalidParam] = [ + invalid_parameters: list[InvalidParam] = [ InvalidParam(value=False, expected_error="narrow is not valid JSON"), InvalidParam(value=0, expected_error="narrow is not a list"), InvalidParam(value="", expected_error="narrow is not valid JSON"), @@ -3788,7 +3788,7 @@ class GetOldMessagesTest(ZulipTestCase): # str or int is required for "id", "sender", "channel", "dm-including" and "group-pm-with" # operators - invalid_operands: List[InvalidParam] = [ + invalid_operands: list[InvalidParam] = [ InvalidParam(value=["1"], expected_error="operand is not a string or integer"), InvalidParam(value=["2"], expected_error="operand is not a string or integer"), InvalidParam( @@ -3860,7 +3860,7 @@ class GetOldMessagesTest(ZulipTestCase): """ self.login("hamlet") error_msg = "Invalid narrow[0]: Value error, element is not a string pair" - bad_channel_content: List[InvalidParam] = [ + bad_channel_content: list[InvalidParam] = [ InvalidParam(value=0, expected_error=error_msg), InvalidParam(value=[], expected_error=error_msg), InvalidParam(value=["x", "y"], expected_error=error_msg), @@ -3874,7 +3874,7 @@ class GetOldMessagesTest(ZulipTestCase): """ self.login("hamlet") error_msg = "Invalid narrow[0]: Value error, element is not a string pair" - bad_channel_content: List[InvalidParam] = [ + bad_channel_content: list[InvalidParam] = [ InvalidParam(value=0, expected_error=error_msg), InvalidParam(value=[], expected_error=error_msg), InvalidParam(value=["x", "y"], expected_error=error_msg), @@ -3884,7 +3884,7 @@ class GetOldMessagesTest(ZulipTestCase): def test_bad_narrow_nonexistent_channel(self) -> None: self.login("hamlet") - non_existing_channel_id_operand: List[InvalidParam] = [ + non_existing_channel_id_operand: list[InvalidParam] = [ InvalidParam( value="non-existent channel", expected_error="Invalid narrow operator: unknown channel", @@ -3904,14 +3904,14 @@ class GetOldMessagesTest(ZulipTestCase): def test_bad_narrow_nonexistent_email(self) -> None: self.login("hamlet") error_msg = "Invalid narrow operator: unknown user" - invalid_operands: List[InvalidParam] = [ + invalid_operands: list[InvalidParam] = [ InvalidParam(value="non-existent-user@zulip.com", expected_error=error_msg), ] self.exercise_bad_narrow_operand("dm", invalid_operands) def test_bad_narrow_dm_id_list(self) -> None: self.login("hamlet") - invalid_operands: List[InvalidParam] = [ + invalid_operands: list[InvalidParam] = [ InvalidParam( value=-24, expected_error="Invalid narrow[0]: Value error, element is not a string pair", @@ -3932,7 +3932,7 @@ class GetOldMessagesTest(ZulipTestCase): ) self.assertEqual(final_dict["content"], "

test content

") - def common_check_get_messages_query(self, query_params: Dict[str, Any], expected: str) -> None: + def common_check_get_messages_query(self, query_params: dict[str, Any], expected: str) -> None: user_profile = self.example_user("hamlet") request = HostRequestMock(query_params, user_profile) with queries_captured() as queries: @@ -4335,7 +4335,7 @@ class GetOldMessagesTest(ZulipTestCase): # If nothing relevant is muted, then exclude_muting_conditions() # should return an empty list. - narrow: List[NarrowParameter] = [ + narrow: list[NarrowParameter] = [ NarrowParameter(operator="channel", operand="Scotland"), ] muting_conditions = exclude_muting_conditions(user_profile, narrow) @@ -4662,7 +4662,7 @@ WHERE user_profile_id = {hamlet_id} AND (content ILIKE '%jumping%' OR subject IL dict(operator="sender", operand=cordelia.email), dict(operator="search", operand=othello.email), ] - result: Dict[str, Any] = self.get_and_check_messages( + result: dict[str, Any] = self.get_and_check_messages( dict( narrow=orjson.dumps(narrow).decode(), anchor=next_message_id, @@ -4697,7 +4697,7 @@ WHERE user_profile_id = {hamlet_id} AND (content ILIKE '%jumping%' OR subject IL class MessageHasKeywordsTest(ZulipTestCase): """Test for keywords like has_link, has_image, has_attachment.""" - def setup_dummy_attachments(self, user_profile: UserProfile) -> List[str]: + def setup_dummy_attachments(self, user_profile: UserProfile) -> list[str]: realm_id = user_profile.realm_id dummy_files = [ ("zulip.txt", f"{realm_id}/31/4CBjtTLYZhk66pZrF8hnYGwc/zulip.txt"), diff --git a/zerver/tests/test_message_flags.py b/zerver/tests/test_message_flags.py index 4117bd6db3..c71a2d10d0 100644 --- a/zerver/tests/test_message_flags.py +++ b/zerver/tests/test_message_flags.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, List, Optional, Set +from typing import TYPE_CHECKING, Any, Optional from unittest import mock import orjson @@ -42,7 +42,7 @@ if TYPE_CHECKING: from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse -def check_flags(flags: List[str], expected: Set[str]) -> None: +def check_flags(flags: list[str], expected: set[str]) -> None: """ The has_alert_word flag can be ignored for most tests. """ @@ -280,7 +280,7 @@ class UnreadCountTests(ZulipTestCase): def race_creation( *, user_id: int, - message_ids: List[int], + message_ids: list[int], flagattr: Optional[int] = None, flag_target: Optional[int] = None, ) -> None: @@ -782,7 +782,7 @@ class FixUnreadTests(ZulipTestCase): class PushNotificationMarkReadFlowsTest(ZulipTestCase): - def get_mobile_push_notification_ids(self, user_profile: UserProfile) -> List[int]: + def get_mobile_push_notification_ids(self, user_profile: UserProfile) -> list[int]: return list( UserMessage.objects.filter( user_profile=user_profile, @@ -945,7 +945,7 @@ class GetUnreadMsgsTest(ZulipTestCase): self.subscribe(hamlet, stream_name) self.subscribe(cordelia, stream_name) - all_message_ids: Set[int] = set() + all_message_ids: set[int] = set() message_ids = {} tups = [ @@ -1438,7 +1438,7 @@ class MessageAccessTests(ZulipTestCase): self.assert_json_error(result, "Invalid message flag operation: 'bogus'") def change_star( - self, messages: List[int], add: bool = True, **kwargs: Any + self, messages: list[int], add: bool = True, **kwargs: Any ) -> "TestHttpResponse": return self.client_post( "/json/messages/flags", @@ -1726,11 +1726,11 @@ class MessageAccessTests(ZulipTestCase): def assert_bulk_access( self, user: UserProfile, - message_ids: List[int], + message_ids: list[int], stream: Stream, bulk_access_messages_count: int, bulk_access_stream_messages_query_count: int, - ) -> List[Message]: + ) -> list[Message]: with self.assert_database_query_count(bulk_access_messages_count): messages = [ Message.objects.select_related("recipient").get(id=message_id) diff --git a/zerver/tests/test_message_move_stream.py b/zerver/tests/test_message_move_stream.py index eefa5e1971..421d1c3142 100644 --- a/zerver/tests/test_message_move_stream.py +++ b/zerver/tests/test_message_move_stream.py @@ -1,5 +1,5 @@ from datetime import timedelta -from typing import Dict, Optional, Tuple, Union +from typing import Optional, Union import orjson @@ -43,7 +43,7 @@ class MessageMoveStreamTest(ZulipTestCase): new_stream: str, topic_name: str, language: Optional[str] = None, - ) -> Tuple[UserProfile, Stream, Stream, int, int]: + ) -> tuple[UserProfile, Stream, Stream, int, int]: user_profile = self.example_user(user_email) if language is not None: user_profile.default_language = language @@ -139,7 +139,7 @@ class MessageMoveStreamTest(ZulipTestCase): new_topic_name: Optional[str] = None, new_stream: Optional[Stream] = None ) -> None: self.login("hamlet") - params_dict: Dict[str, Union[str, int]] = { + params_dict: dict[str, Union[str, int]] = { "propagate_mode": "change_all", "send_notification_to_new_thread": "false", } diff --git a/zerver/tests/test_message_move_topic.py b/zerver/tests/test_message_move_topic.py index e5b5ac5441..81a8646f0e 100644 --- a/zerver/tests/test_message_move_topic.py +++ b/zerver/tests/test_message_move_topic.py @@ -1,5 +1,5 @@ from datetime import timedelta -from typing import Any, Dict, List +from typing import Any from unittest import mock import orjson @@ -137,7 +137,7 @@ class MessageMoveTopicTest(ZulipTestCase): user_profile: UserProfile, message: Message, topic_name: str, - users_to_be_notified: List[Dict[str, Any]], + users_to_be_notified: list[dict[str, Any]], ) -> None: do_update_message( user_profile=user_profile, @@ -156,7 +156,7 @@ class MessageMoveTopicTest(ZulipTestCase): mock_send_event.assert_called_with(mock.ANY, mock.ANY, users_to_be_notified) # Returns the users that need to be notified when a message topic is changed - def notify(user_id: int) -> Dict[str, Any]: + def notify(user_id: int) -> dict[str, Any]: um = UserMessage.objects.get(message=message_id) if um.user_profile_id == user_id: return { @@ -249,8 +249,8 @@ class MessageMoveTopicTest(ZulipTestCase): set_topic_visibility_policy(cordelia, muted_topics, UserTopic.VisibilityPolicy.MUTED) # users that need to be notified by send_event in the case of change-topic-name operation. - users_to_be_notified_via_muted_topics_event: List[int] = [] - users_to_be_notified_via_user_topic_event: List[int] = [] + users_to_be_notified_via_muted_topics_event: list[int] = [] + users_to_be_notified_via_user_topic_event: list[int] = [] for user_topic in get_users_with_user_topic_visibility_policy(stream.id, "Topic1"): # We are appending the same data twice because 'user_topic' event notifies # the user during delete and create operation. @@ -280,8 +280,8 @@ class MessageMoveTopicTest(ZulipTestCase): # Extract the send_event call where event type is 'user_topic' or 'muted_topics. # Here we assert that the expected users are notified properly. - users_notified_via_muted_topics_event: List[int] = [] - users_notified_via_user_topic_event: List[int] = [] + users_notified_via_muted_topics_event: list[int] = [] + users_notified_via_user_topic_event: list[int] = [] for call_args in mock_send_event_on_commit.call_args_list: (arg_realm, arg_event, arg_notified_users) = call_args[0] if arg_event["type"] == "user_topic": @@ -516,8 +516,8 @@ class MessageMoveTopicTest(ZulipTestCase): set_topic_visibility_policy(othello, topics, UserTopic.VisibilityPolicy.UNMUTED) # users that need to be notified by send_event in the case of change-topic-name operation. - users_to_be_notified_via_muted_topics_event: List[int] = [] - users_to_be_notified_via_user_topic_event: List[int] = [] + users_to_be_notified_via_muted_topics_event: list[int] = [] + users_to_be_notified_via_user_topic_event: list[int] = [] for user_topic in get_users_with_user_topic_visibility_policy(stream.id, "Topic1"): # We are appending the same data twice because 'user_topic' event notifies # the user during delete and create operation. @@ -542,8 +542,8 @@ class MessageMoveTopicTest(ZulipTestCase): # Extract the send_event call where event type is 'user_topic' or 'muted_topics. # Here we assert that the expected users are notified properly. - users_notified_via_muted_topics_event: List[int] = [] - users_notified_via_user_topic_event: List[int] = [] + users_notified_via_muted_topics_event: list[int] = [] + users_notified_via_user_topic_event: list[int] = [] for call_args in mock_send_event_on_commit.call_args_list: (arg_realm, arg_event, arg_notified_users) = call_args[0] if arg_event["type"] == "user_topic": diff --git a/zerver/tests/test_message_notification_emails.py b/zerver/tests/test_message_notification_emails.py index 94dab19336..59c49205f1 100644 --- a/zerver/tests/test_message_notification_emails.py +++ b/zerver/tests/test_message_notification_emails.py @@ -1,7 +1,7 @@ import random import re from email.headerregistry import Address -from typing import Dict, List, Optional, Sequence, Union +from typing import Optional, Sequence, Union from unittest import mock from unittest.mock import patch @@ -93,13 +93,13 @@ class TestMessageNotificationEmails(ZulipTestCase): s = s.strip() return re.sub(r"\s+", " ", s) - def _get_tokens(self) -> List[str]: + def _get_tokens(self) -> list[str]: return ["mm" + str(random.getrandbits(32)) for _ in range(30)] def _test_cases( self, msg_id: int, - verify_body_include: List[str], + verify_body_include: list[str], email_subject: str, verify_html_body: bool = False, show_message_content: bool = True, @@ -186,7 +186,7 @@ class TestMessageNotificationEmails(ZulipTestCase): "You are receiving this because you were personally mentioned.", ] email_subject = "#Denmark > test" - verify_body_does_not_include: List[str] = [] + verify_body_does_not_include: list[str] = [] else: # Test in case if message content in missed email message are disabled. verify_body_include = [ @@ -246,7 +246,7 @@ class TestMessageNotificationEmails(ZulipTestCase): "You are receiving this because you have email notifications enabled for #Denmark.", ] email_subject = "#Denmark > test" - verify_body_does_not_include: List[str] = [] + verify_body_does_not_include: list[str] = [] else: # Test in case if message content in missed email message are disabled. verify_body_include = [ @@ -286,7 +286,7 @@ class TestMessageNotificationEmails(ZulipTestCase): "You are receiving this because you have wildcard mention notifications enabled for topics you follow.", ] email_subject = "#Denmark > test" - verify_body_does_not_include: List[str] = [] + verify_body_does_not_include: list[str] = [] else: # Test in case if message content in missed email message are disabled. verify_body_include = [ @@ -345,7 +345,7 @@ class TestMessageNotificationEmails(ZulipTestCase): "You are receiving this because you have email notifications enabled for #Denmark.", ] email_subject = "#Denmark > test" - verify_body_does_not_include: List[str] = [] + verify_body_does_not_include: list[str] = [] else: # Test in case if message content in missed email message are disabled. verify_body_include = [ @@ -385,7 +385,7 @@ class TestMessageNotificationEmails(ZulipTestCase): "You are receiving this because everyone was mentioned in #Denmark.", ] email_subject = "#Denmark > test" - verify_body_does_not_include: List[str] = [] + verify_body_does_not_include: list[str] = [] else: # Test in case if message content in missed email message are disabled. verify_body_include = [ @@ -493,7 +493,7 @@ class TestMessageNotificationEmails(ZulipTestCase): if show_message_content: verify_body_include = ["> Extremely personal message!"] email_subject = "DMs with Othello, the Moor of Venice" - verify_body_does_not_include: List[str] = [] + verify_body_does_not_include: list[str] = [] else: if message_content_disabled_by_realm: verify_body_include = [ @@ -562,7 +562,7 @@ class TestMessageNotificationEmails(ZulipTestCase): "Othello, the Moor of Venice: > Group personal message! -- Reply" ] email_subject = "Group DMs with Iago and Othello, the Moor of Venice" - verify_body_does_not_include: List[str] = [] + verify_body_does_not_include: list[str] = [] else: verify_body_include = [ "This email does not include message content because you have disabled message ", @@ -1318,7 +1318,7 @@ class TestMessageNotificationEmails(ZulipTestCase): othello = self.example_user("othello") iago = self.example_user("iago") - message_ids: Dict[int, MissedMessageData] = {} + message_ids: dict[int, MissedMessageData] = {} for i in range(1, 4): msg_id = self.send_stream_message(othello, "Denmark", content=str(i)) message_ids[msg_id] = MissedMessageData(trigger=NotificationTriggers.STREAM_EMAIL) diff --git a/zerver/tests/test_message_send.py b/zerver/tests/test_message_send.py index d68006be33..0f2fc709a7 100644 --- a/zerver/tests/test_message_send.py +++ b/zerver/tests/test_message_send.py @@ -1,6 +1,6 @@ from datetime import timedelta from email.headerregistry import Address -from typing import Any, Optional, Set +from typing import Any, Optional from unittest import mock import orjson @@ -1770,7 +1770,7 @@ class StreamMessagesTest(ZulipTestCase): ).flags.is_private.is_set ) - def _send_stream_message(self, user: UserProfile, stream_name: str, content: str) -> Set[int]: + def _send_stream_message(self, user: UserProfile, stream_name: str, content: str) -> set[int]: with self.capture_send_event_calls(expected_num_events=1) as events: self.send_stream_message( user, @@ -1794,7 +1794,7 @@ class StreamMessagesTest(ZulipTestCase): user_profile=cordelia, ).delete() - def mention_cordelia() -> Set[int]: + def mention_cordelia() -> set[int]: content = "test @**Cordelia, Lear's daughter** rules" user_ids = self._send_stream_message( diff --git a/zerver/tests/test_messages.py b/zerver/tests/test_messages.py index 30be7ac969..3ca26c73e0 100644 --- a/zerver/tests/test_messages.py +++ b/zerver/tests/test_messages.py @@ -1,5 +1,4 @@ from datetime import timedelta -from typing import List from django.utils.timezone import now as timezone_now @@ -24,7 +23,7 @@ class MissedMessageTest(ZulipTestCase): hamlet_notifications_data = self.create_user_notifications_data_object(user_id=hamlet.id) othello_notifications_data = self.create_user_notifications_data_object(user_id=othello.id) - def assert_active_presence_idle_user_ids(user_ids: List[int]) -> None: + def assert_active_presence_idle_user_ids(user_ids: list[int]) -> None: presence_idle_user_ids = get_active_presence_idle_user_ids( realm=realm, sender_id=sender.id, diff --git a/zerver/tests/test_middleware.py b/zerver/tests/test_middleware.py index 446cb4c891..61d7814a11 100644 --- a/zerver/tests/test_middleware.py +++ b/zerver/tests/test_middleware.py @@ -1,5 +1,4 @@ import time -from typing import List from unittest.mock import patch from bs4 import BeautifulSoup @@ -65,8 +64,8 @@ class OpenGraphTest(ZulipTestCase): self, path: str, title: str, - in_description: List[str], - not_in_description: List[str], + in_description: list[str], + not_in_description: list[str], status_code: int = 200, ) -> None: response = self.client_get(path) diff --git a/zerver/tests/test_mirror_users.py b/zerver/tests/test_mirror_users.py index 2f7e9e461a..3e25dc120c 100644 --- a/zerver/tests/test_mirror_users.py +++ b/zerver/tests/test_mirror_users.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any from unittest import mock from django.db import IntegrityError @@ -20,7 +20,7 @@ class MirroredMessageUsersTest(ZulipTestCase): user = self.example_user("hamlet") sender = user - recipients: List[str] = [] + recipients: list[str] = [] recipient_type_name = "private" client = get_client("banned_mirror") diff --git a/zerver/tests/test_openapi.py b/zerver/tests/test_openapi.py index e36cb0c175..bdae5069dc 100644 --- a/zerver/tests/test_openapi.py +++ b/zerver/tests/test_openapi.py @@ -2,20 +2,7 @@ import inspect import os import types from collections import abc -from typing import ( - Any, - Callable, - Dict, - List, - Mapping, - Optional, - Sequence, - Set, - Tuple, - Union, - get_args, - get_origin, -) +from typing import Any, Callable, Mapping, Optional, Sequence, Union, get_args, get_origin from unittest.mock import MagicMock, patch import yaml @@ -61,8 +48,8 @@ VARMAP = { def schema_type( - schema: Dict[str, Any], defs: Mapping[str, Any] = {} -) -> Union[type, Tuple[type, object]]: + schema: dict[str, Any], defs: Mapping[str, Any] = {} +) -> Union[type, tuple[type, object]]: if "oneOf" in schema: # Hack: Just use the type of the first value # Ideally, we'd turn this into a Union type. @@ -114,7 +101,7 @@ class OpenAPIToolsTest(ZulipTestCase): with self.assertRaisesRegex( SchemaError, r"Additional properties are not allowed \('foo' was unexpected\)" ): - bad_content: Dict[str, object] = { + bad_content: dict[str, object] = { "msg": "", "result": "success", "foo": "bar", @@ -150,7 +137,7 @@ class OpenAPIToolsTest(ZulipTestCase): ) # Overwrite the exception list with a mocked one - test_dict: Dict[str, Any] = {} + test_dict: dict[str, Any] = {} # Check that validate_against_openapi_schema correctly # descends into 'deep' objects and arrays. Test 1 should @@ -218,7 +205,7 @@ class OpenAPIToolsTest(ZulipTestCase): class OpenAPIArgumentsTest(ZulipTestCase): # This will be filled during test_openapi_arguments: - checked_endpoints: Set[str] = set() + checked_endpoints: set[str] = set() pending_endpoints = { #### TODO: These endpoints are a priority to document: # These are a priority to document but don't match our normal URL schemes @@ -289,7 +276,7 @@ class OpenAPIArgumentsTest(ZulipTestCase): # Endpoints where the documentation is currently failing our # consistency tests. We aim to keep this list empty. - buggy_documentation_endpoints: Set[str] = set() + buggy_documentation_endpoints: set[str] = set() def ensure_no_documentation_if_intentionally_undocumented( self, url_pattern: str, method: str, msg: Optional[str] = None @@ -325,8 +312,8 @@ so maybe we shouldn't mark it as intentionally undocumented in the URLs. raise AssertionError(msg) def get_type_by_priority( - self, types: Sequence[Union[type, Tuple[type, object]]] - ) -> Union[type, Tuple[type, object]]: + self, types: Sequence[Union[type, tuple[type, object]]] + ) -> Union[type, tuple[type, object]]: priority = {list: 1, dict: 2, str: 3, int: 4, bool: 5} tyiroirp = {1: list, 2: dict, 3: str, 4: int, 5: bool} val = 6 @@ -338,7 +325,7 @@ so maybe we shouldn't mark it as intentionally undocumented in the URLs. val = v return tyiroirp.get(val, types[0]) - def get_standardized_argument_type(self, t: Any) -> Union[type, Tuple[type, object]]: + def get_standardized_argument_type(self, t: Any) -> Union[type, tuple[type, object]]: """Given a type from the typing module such as List[str] or Union[str, int], convert it into a corresponding Python type. Unions are mapped to a canonical choice among the options. @@ -364,9 +351,9 @@ so maybe we shouldn't mark it as intentionally undocumented in the URLs. def render_openapi_type_exception( self, function: Callable[..., HttpResponse], - openapi_params: Set[Tuple[str, Union[type, Tuple[type, object]]]], - function_params: Set[Tuple[str, Union[type, Tuple[type, object]]]], - diff: Set[Tuple[str, Union[type, Tuple[type, object]]]], + openapi_params: set[tuple[str, Union[type, tuple[type, object]]]], + function_params: set[tuple[str, Union[type, tuple[type, object]]]], + diff: set[tuple[str, Union[type, tuple[type, object]]]], ) -> None: # nocoverage """Print a *VERY* clear and verbose error message for when the types (between the OpenAPI documentation and the function declaration) don't match.""" @@ -395,7 +382,7 @@ do not match the types declared in the implementation of {function.__name__}.\n" raise AssertionError(msg) def validate_json_schema( - self, function: Callable[..., HttpResponse], openapi_parameters: List[Parameter] + self, function: Callable[..., HttpResponse], openapi_parameters: list[Parameter] ) -> None: """Validate against the Pydantic generated JSON schema against our OpenAPI definitions""" USE_JSON_CONTENT_TYPE_HINT = f""" @@ -475,7 +462,7 @@ do not match the types declared in the implementation of {function.__name__}.\n" self.render_openapi_type_exception(function, openapi_params, function_params, diff) def check_argument_types( - self, function: Callable[..., HttpResponse], openapi_parameters: List[Parameter] + self, function: Callable[..., HttpResponse], openapi_parameters: list[Parameter] ) -> None: """We construct for both the OpenAPI data and the function's definition a set of tuples of the form (var_name, type) and then compare those sets to see if the @@ -496,8 +483,8 @@ do not match the types declared in the implementation of {function.__name__}.\n" if use_endpoint_decorator: return self.validate_json_schema(function, openapi_parameters) - openapi_params: Set[Tuple[str, Union[type, Tuple[type, object]]]] = set() - json_params: Dict[str, Union[type, Tuple[type, object]]] = {} + openapi_params: set[tuple[str, Union[type, tuple[type, object]]]] = set() + json_params: dict[str, Union[type, tuple[type, object]]] = {} for openapi_parameter in openapi_parameters: name = openapi_parameter.name if openapi_parameter.json_encoded: @@ -515,7 +502,7 @@ do not match the types declared in the implementation of {function.__name__}.\n" continue openapi_params.add((name, schema_type(openapi_parameter.value_schema))) - function_params: Set[Tuple[str, Union[type, Tuple[type, object]]]] = set() + function_params: set[tuple[str, Union[type, tuple[type, object]]]] = set() for pname, defval in inspect.signature(function).parameters.items(): defval = defval.default @@ -562,7 +549,7 @@ do not match the types declared in the implementation of {function.__name__}.\n" function_name: str, function: Callable[..., HttpResponse], method: str, - tags: Set[str], + tags: set[str], ) -> None: # Our accounting logic in the `has_request_variables()` # code means we have the list of all arguments @@ -665,7 +652,7 @@ so maybe we shouldn't include it in pending_endpoints. # for those using the rest_dispatch decorator; we then parse # its mapping of (HTTP_METHOD -> FUNCTION). for p in urlconf.v1_api_and_json_patterns + urlconf.v1_api_mobile_patterns: - methods_endpoints: Dict[str, Any] = {} + methods_endpoints: dict[str, Any] = {} if p.callback is not rest_dispatch: # Endpoints not using rest_dispatch don't have extra data. if str(p.pattern) in self.documented_post_only_endpoints: @@ -680,7 +667,7 @@ so maybe we shouldn't include it in pending_endpoints. for method, value in methods_endpoints.items(): if callable(value): function: Callable[..., HttpResponse] = value - tags: Set[str] = set() + tags: set[str] = set() else: function, tags = value @@ -734,7 +721,7 @@ class TestCurlExampleGeneration(ZulipTestCase): }, } - spec_mock_with_invalid_method: Dict[str, object] = { + spec_mock_with_invalid_method: dict[str, object] = { "security": [{"basicAuth": []}], "paths": { "/endpoint": { @@ -851,7 +838,7 @@ class TestCurlExampleGeneration(ZulipTestCase): }, } - def curl_example(self, endpoint: str, method: str, *args: Any, **kwargs: Any) -> List[str]: + def curl_example(self, endpoint: str, method: str, *args: Any, **kwargs: Any) -> list[str]: return generate_curl_example(endpoint, method, "http://localhost:9991/api", *args, **kwargs) def test_generate_and_render_curl_example(self) -> None: diff --git a/zerver/tests/test_outgoing_webhook_interfaces.py b/zerver/tests/test_outgoing_webhook_interfaces.py index c01b41f5e9..6b87dfad4a 100644 --- a/zerver/tests/test_outgoing_webhook_interfaces.py +++ b/zerver/tests/test_outgoing_webhook_interfaces.py @@ -1,5 +1,5 @@ import json -from typing import Any, Dict +from typing import Any from unittest import mock import requests @@ -130,7 +130,7 @@ class TestGenericOutgoingWebhookService(ZulipTestCase): self.assertEqual(wide_message_dict["sender_realm_id"], othello.realm_id) def test_process_success(self) -> None: - response: Dict[str, Any] = dict(response_not_required=True) + response: dict[str, Any] = dict(response_not_required=True) success_response = self.handler.process_success(response) self.assertEqual(success_response, None) @@ -240,7 +240,7 @@ class TestSlackOutgoingWebhookService(ZulipTestCase): self.assertTrue(mock_fail_with_message.called) def test_process_success(self) -> None: - response: Dict[str, Any] = dict(response_not_required=True) + response: dict[str, Any] = dict(response_not_required=True) success_response = self.handler.process_success(response) self.assertEqual(success_response, None) diff --git a/zerver/tests/test_outgoing_webhook_system.py b/zerver/tests/test_outgoing_webhook_system.py index 233ff9c041..9d977fca50 100644 --- a/zerver/tests/test_outgoing_webhook_system.py +++ b/zerver/tests/test_outgoing_webhook_system.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional +from typing import Any, Optional from unittest import mock import orjson @@ -44,7 +44,7 @@ def connection_error(final_url: Any, **request_kwargs: Any) -> Any: class DoRestCallTests(ZulipTestCase): - def mock_event(self, bot_user: UserProfile) -> Dict[str, Any]: + def mock_event(self, bot_user: UserProfile) -> dict[str, Any]: return { # In the tests there is no active queue processor, so retries don't get processed. # Therefore, we need to emulate `retry_event` in the last stage when the maximum @@ -610,7 +610,7 @@ class TestOutgoingWebhookMessaging(ZulipTestCase): bot_owner = self.example_user("othello") bot = self.create_outgoing_bot(bot_owner) - def wrapped(event: Dict[str, Any], failure_message: str) -> None: + def wrapped(event: dict[str, Any], failure_message: str) -> None: do_deactivate_stream(get_stream("Denmark", get_realm("zulip")), acting_user=None) fail_with_message(event, failure_message) diff --git a/zerver/tests/test_presence.py b/zerver/tests/test_presence.py index c57333a89e..22d2b27348 100644 --- a/zerver/tests/test_presence.py +++ b/zerver/tests/test_presence.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta -from typing import Any, Dict +from typing import Any from unittest import mock import time_machine @@ -547,7 +547,7 @@ class UserPresenceTests(ZulipTestCase): user_profile = self.mit_user("espuser") self.login_user(user_profile) - def post_presence() -> Dict[str, Any]: + def post_presence() -> dict[str, Any]: result = self.client_post( "/json/users/me/presence", {"status": "idle"}, subdomain="zephyr" ) @@ -738,7 +738,7 @@ class SingleUserPresenceTests(ZulipTestCase): class UserPresenceAggregationTests(ZulipTestCase): def _send_presence_for_aggregated_tests( self, user: UserProfile, status: str, validate_time: datetime - ) -> Dict[str, Dict[str, Any]]: + ) -> dict[str, dict[str, Any]]: self.login_user(user) # First create some initial, old presence to avoid the details of the edge case of initial # presence creation messing with the intended setup. diff --git a/zerver/tests/test_push_notifications.py b/zerver/tests/test_push_notifications.py index 17d557f0b1..ccceb11be4 100644 --- a/zerver/tests/test_push_notifications.py +++ b/zerver/tests/test_push_notifications.py @@ -4,7 +4,7 @@ import logging import uuid from contextlib import contextmanager from datetime import datetime, timedelta, timezone -from typing import Any, Dict, Iterator, List, Mapping, Optional, Tuple, Union +from typing import Any, Iterator, Mapping, Optional, Union from unittest import mock, skipUnless import aioapns @@ -1090,7 +1090,7 @@ class PushBouncerNotificationTest(BouncerTestCase): "last_realmauditlog_id": 0, } - def mock_send_to_push_bouncer_response(method: str, *args: Any) -> Dict[str, Any]: + def mock_send_to_push_bouncer_response(method: str, *args: Any) -> dict[str, Any]: if method == "POST": return post_response return get_response @@ -1159,7 +1159,7 @@ class PushBouncerNotificationTest(BouncerTestCase): self.login_user(user) server = self.server - endpoints: List[Tuple[str, str, int, Mapping[str, str]]] = [ + endpoints: list[tuple[str, str, int, Mapping[str, str]]] = [ ( "/json/users/me/apns_device_token", "apple-tokenaz", @@ -2233,7 +2233,7 @@ class AnalyticsBouncerTest(BouncerTestCase): endpoint: str, post_data: Union[bytes, Mapping[str, Union[str, int, None, bytes]]], extra_headers: Mapping[str, str] = {}, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: if endpoint == "server/analytics": assert isinstance(post_data, dict) assert isinstance(post_data["realmauditlog_rows"], str) @@ -2544,7 +2544,7 @@ class AnalyticsBouncerTest(BouncerTestCase): "last_realmauditlog_id": 0, } - def mock_send_to_push_bouncer_response(method: str, *args: Any) -> Dict[str, int]: + def mock_send_to_push_bouncer_response(method: str, *args: Any) -> dict[str, int]: if method == "POST": raise PushNotificationBouncerRetryLaterError("Some problem") return get_response @@ -2721,7 +2721,7 @@ class PushNotificationTest(BouncerTestCase): return message @contextmanager - def mock_apns(self) -> Iterator[Tuple[APNsContext, mock.AsyncMock]]: + def mock_apns(self) -> Iterator[tuple[APNsContext, mock.AsyncMock]]: apns = mock.Mock(spec=aioapns.APNs) apns.send_notification = mock.AsyncMock() apns_context = APNsContext( @@ -2770,7 +2770,7 @@ class PushNotificationTest(BouncerTestCase): ) @contextmanager - def mock_fcm(self) -> Iterator[Tuple[mock.MagicMock, mock.MagicMock]]: + def mock_fcm(self) -> Iterator[tuple[mock.MagicMock, mock.MagicMock]]: with mock.patch("zerver.lib.push_notifications.fcm_app") as mock_fcm_app, mock.patch( "zerver.lib.push_notifications.firebase_messaging" ) as mock_fcm_messaging: @@ -2801,7 +2801,7 @@ class PushNotificationTest(BouncerTestCase): server=self.server, ) - def make_fcm_success_response(self, tokens: List[str]) -> firebase_messaging.BatchResponse: + def make_fcm_success_response(self, tokens: list[str]) -> firebase_messaging.BatchResponse: responses = [ firebase_messaging.SendResponse(exception=None, resp=dict(name=str(idx))) for idx, _ in enumerate(tokens) @@ -2823,7 +2823,7 @@ class HandlePushNotificationTest(PushNotificationTest): self.soft_deactivate_user(self.user_profile) @override - def request_callback(self, request: PreparedRequest) -> Tuple[int, ResponseHeaders, bytes]: + def request_callback(self, request: PreparedRequest) -> tuple[int, ResponseHeaders, bytes]: assert request.url is not None # allow mypy to infer url is present. assert settings.PUSH_NOTIFICATION_BOUNCER_URL is not None local_url = request.url.replace(settings.PUSH_NOTIFICATION_BOUNCER_URL, "") @@ -3786,14 +3786,14 @@ class HandlePushNotificationTest(PushNotificationTest): class TestAPNs(PushNotificationTest): - def devices(self) -> List[DeviceToken]: + def devices(self) -> list[DeviceToken]: return list( PushDeviceToken.objects.filter(user=self.user_profile, kind=PushDeviceToken.APNS) ) def send( self, - devices: Optional[List[Union[PushDeviceToken, RemotePushDeviceToken]]] = None, + devices: Optional[list[Union[PushDeviceToken, RemotePushDeviceToken]]] = None, payload_data: Mapping[str, Any] = {}, ) -> None: send_apple_push_notification( @@ -4675,7 +4675,7 @@ class TestPushApi(BouncerTestCase): user = self.example_user("cordelia") self.login_user(user) - endpoints: List[Tuple[str, str, Mapping[str, str]]] = [ + endpoints: list[tuple[str, str, Mapping[str, str]]] = [ ("/json/users/me/apns_device_token", "apple-tokenaz", {"appid": "org.zulip.Zulip"}), ("/json/users/me/android_gcm_reg_id", "android-token", {}), ] @@ -4724,12 +4724,12 @@ class TestPushApi(BouncerTestCase): user = self.example_user("cordelia") self.login_user(user) - no_bouncer_requests: List[Tuple[str, str, Mapping[str, str]]] = [ + no_bouncer_requests: list[tuple[str, str, Mapping[str, str]]] = [ ("/json/users/me/apns_device_token", "apple-tokenaa", {"appid": "org.zulip.Zulip"}), ("/json/users/me/android_gcm_reg_id", "android-token-1", {}), ] - bouncer_requests: List[Tuple[str, str, Mapping[str, str]]] = [ + bouncer_requests: list[tuple[str, str, Mapping[str, str]]] = [ ("/json/users/me/apns_device_token", "apple-tokenbb", {"appid": "org.zulip.Zulip"}), ("/json/users/me/android_gcm_reg_id", "android-token-2", {}), ] @@ -4831,7 +4831,7 @@ class FCMSendTest(PushNotificationTest): super().setUp() self.setup_fcm_tokens() - def get_fcm_data(self, **kwargs: Any) -> Dict[str, Any]: + def get_fcm_data(self, **kwargs: Any) -> dict[str, Any]: data = { "key 1": "Data 1", "key 2": "Data 2", diff --git a/zerver/tests/test_queue.py b/zerver/tests/test_queue.py index fb2f246c10..c6362d45ed 100644 --- a/zerver/tests/test_queue.py +++ b/zerver/tests/test_queue.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List +from typing import Any from unittest import mock import orjson @@ -37,7 +37,7 @@ class TestQueueImplementation(ZulipTestCase): queue_client = get_queue_client() - def collect(events: List[Dict[str, Any]]) -> None: + def collect(events: list[dict[str, Any]]) -> None: assert isinstance(queue_client, SimpleQueueClient) assert len(events) == 1 output.append(events[0]) @@ -57,7 +57,7 @@ class TestQueueImplementation(ZulipTestCase): queue_client = get_queue_client() - def collect(events: List[Dict[str, Any]]) -> None: + def collect(events: list[dict[str, Any]]) -> None: assert isinstance(queue_client, SimpleQueueClient) assert len(events) == 1 queue_client.stop_consuming() diff --git a/zerver/tests/test_queue_worker.py b/zerver/tests/test_queue_worker.py index e24dff4672..e841c316a1 100644 --- a/zerver/tests/test_queue_worker.py +++ b/zerver/tests/test_queue_worker.py @@ -5,7 +5,7 @@ import time from collections import defaultdict from contextlib import contextmanager, suppress from datetime import datetime, timedelta, timezone -from typing import Any, Callable, Dict, Iterator, List, Mapping, Optional +from typing import Any, Callable, Iterator, Mapping, Optional from unittest.mock import MagicMock, patch import orjson @@ -37,24 +37,24 @@ from zerver.worker.missedmessage_emails import MissedMessageWorker from zerver.worker.missedmessage_mobile_notifications import PushNotificationsWorker from zerver.worker.user_activity import UserActivityWorker -Event: TypeAlias = Dict[str, Any] +Event: TypeAlias = dict[str, Any] class FakeClient: def __init__(self, prefetch: int = 0) -> None: - self.queues: Dict[str, List[Dict[str, Any]]] = defaultdict(list) + self.queues: dict[str, list[dict[str, Any]]] = defaultdict(list) - def enqueue(self, queue_name: str, data: Dict[str, Any]) -> None: + def enqueue(self, queue_name: str, data: dict[str, Any]) -> None: self.queues[queue_name].append(data) def start_json_consumer( self, queue_name: str, - callback: Callable[[List[Dict[str, Any]]], None], + callback: Callable[[list[dict[str, Any]]], None], batch_size: int = 1, timeout: Optional[int] = None, ) -> None: - chunk: List[Dict[str, Any]] = [] + chunk: list[dict[str, Any]] = [] queue = self.queues[queue_name] while queue: chunk.append(queue.pop(0)) @@ -482,14 +482,14 @@ class WorkerTest(ZulipTestCase): fake_client = FakeClient() def fake_publish( - queue_name: str, event: Dict[str, Any], processor: Callable[[Any], None] + queue_name: str, event: dict[str, Any], processor: Callable[[Any], None] ) -> None: fake_client.enqueue(queue_name, event) - def generate_new_message_notification() -> Dict[str, Any]: + def generate_new_message_notification() -> dict[str, Any]: return build_offline_notification(1, 1) - def generate_remove_notification() -> Dict[str, Any]: + def generate_remove_notification() -> dict[str, Any]: return { "type": "remove", "user_profile_id": 1, @@ -660,7 +660,7 @@ class WorkerTest(ZulipTestCase): fake_client.enqueue("email_senders", data) def fake_publish( - queue_name: str, event: Dict[str, Any], processor: Optional[Callable[[Any], None]] + queue_name: str, event: dict[str, Any], processor: Optional[Callable[[Any], None]] ) -> None: fake_client.enqueue(queue_name, event) @@ -719,7 +719,7 @@ class WorkerTest(ZulipTestCase): @base_worker.assign_queue("unreliable_loopworker", is_test_queue=True) class UnreliableLoopWorker(base_worker.LoopQueueProcessingWorker): @override - def consume_batch(self, events: List[Dict[str, Any]]) -> None: + def consume_batch(self, events: list[dict[str, Any]]) -> None: for event in events: if event["type"] == "unexpected behaviour": raise Exception("Worker task not performing as expected!") diff --git a/zerver/tests/test_rate_limiter.py b/zerver/tests/test_rate_limiter.py index b56c8c9995..fddc78937c 100644 --- a/zerver/tests/test_rate_limiter.py +++ b/zerver/tests/test_rate_limiter.py @@ -1,7 +1,6 @@ import secrets import time from abc import ABC, abstractmethod -from typing import Dict, List, Tuple, Type from unittest import mock from typing_extensions import override @@ -22,7 +21,7 @@ RANDOM_KEY_PREFIX = secrets.token_hex(16) class RateLimitedTestObject(RateLimitedObject): def __init__( - self, name: str, rules: List[Tuple[int, int]], backend: Type[RateLimiterBackend] + self, name: str, rules: list[tuple[int, int]], backend: type[RateLimiterBackend] ) -> None: self.name = name self._rules = rules @@ -34,19 +33,19 @@ class RateLimitedTestObject(RateLimitedObject): return RANDOM_KEY_PREFIX + self.name @override - def rules(self) -> List[Tuple[int, int]]: + def rules(self) -> list[tuple[int, int]]: return self._rules class RateLimiterBackendBase(ZulipTestCase, ABC): - backend: Type[RateLimiterBackend] + backend: type[RateLimiterBackend] @override def setUp(self) -> None: super().setUp() - self.requests_record: Dict[str, List[float]] = {} + self.requests_record: dict[str, list[float]] = {} - def create_object(self, name: str, rules: List[Tuple[int, int]]) -> RateLimitedTestObject: + def create_object(self, name: str, rules: list[tuple[int, int]]) -> RateLimitedTestObject: obj = RateLimitedTestObject(name, rules, self.backend) obj.clear_history() @@ -80,7 +79,7 @@ class RateLimiterBackendBase(ZulipTestCase, ABC): self.assertEqual(expected_calls_remaining, calls_remaining) self.assertEqual(expected_time_till_reset, time_till_reset) - def expected_api_calls_left(self, obj: RateLimitedTestObject, now: float) -> Tuple[int, float]: + def expected_api_calls_left(self, obj: RateLimitedTestObject, now: float) -> tuple[int, float]: longest_rule = obj.get_rules()[-1] max_window, max_calls = longest_rule history = self.requests_record.get(obj.key()) @@ -92,8 +91,8 @@ class RateLimiterBackendBase(ZulipTestCase, ABC): @abstractmethod def api_calls_left_from_history( - self, history: List[float], max_window: int, max_calls: int, now: float - ) -> Tuple[int, float]: + self, history: list[float], max_window: int, max_calls: int, now: float + ) -> tuple[int, float]: """ This depends on the algorithm used in the backend, and should be defined by the test class. """ @@ -162,8 +161,8 @@ class RedisRateLimiterBackendTest(RateLimiterBackendBase): @override def api_calls_left_from_history( - self, history: List[float], max_window: int, max_calls: int, now: float - ) -> Tuple[int, float]: + self, history: list[float], max_window: int, max_calls: int, now: float + ) -> tuple[int, float]: latest_timestamp = history[-1] relevant_requests = [t for t in history if t >= now - max_window] relevant_requests_amount = len(relevant_requests) @@ -188,8 +187,8 @@ class TornadoInMemoryRateLimiterBackendTest(RateLimiterBackendBase): @override def api_calls_left_from_history( - self, history: List[float], max_window: int, max_calls: int, now: float - ) -> Tuple[int, float]: + self, history: list[float], max_window: int, max_calls: int, now: float + ) -> tuple[int, float]: reset_time = 0.0 for timestamp in history: reset_time = max(reset_time, timestamp) + (max_window / max_calls) diff --git a/zerver/tests/test_reactions.py b/zerver/tests/test_reactions.py index c3be54441d..8bce4d9584 100644 --- a/zerver/tests/test_reactions.py +++ b/zerver/tests/test_reactions.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, Dict, List +from typing import TYPE_CHECKING, Any from unittest import mock import orjson @@ -639,7 +639,7 @@ class EmojiReactionBase(ZulipTestCase): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - def post_reaction(self, reaction_info: Dict[str, str]) -> "TestHttpResponse": + def post_reaction(self, reaction_info: dict[str, str]) -> "TestHttpResponse": message_id = 1 result = self.api_post( @@ -647,7 +647,7 @@ class EmojiReactionBase(ZulipTestCase): ) return result - def post_other_reaction(self, reaction_info: Dict[str, str]) -> "TestHttpResponse": + def post_other_reaction(self, reaction_info: dict[str, str]) -> "TestHttpResponse": message_id = 1 result = self.api_post( @@ -655,7 +655,7 @@ class EmojiReactionBase(ZulipTestCase): ) return result - def delete_reaction(self, reaction_info: Dict[str, str]) -> "TestHttpResponse": + def delete_reaction(self, reaction_info: dict[str, str]) -> "TestHttpResponse": message_id = 1 result = self.api_delete( @@ -665,7 +665,7 @@ class EmojiReactionBase(ZulipTestCase): def get_message_reactions( self, message_id: int, emoji_code: str, reaction_type: str - ) -> List[Reaction]: + ) -> list[Reaction]: message = Message.objects.get(id=message_id) reactions = Reaction.objects.filter( message=message, emoji_code=emoji_code, reaction_type=reaction_type diff --git a/zerver/tests/test_realm.py b/zerver/tests/test_realm.py index 145ea2913a..5a7fac5c8a 100644 --- a/zerver/tests/test_realm.py +++ b/zerver/tests/test_realm.py @@ -4,7 +4,7 @@ import random import re import string from datetime import datetime, timedelta -from typing import Any, Dict, List, Union +from typing import Any, Union from unittest import mock, skipUnless import orjson @@ -1382,7 +1382,7 @@ class RealmTest(ZulipTestCase): "last_realmauditlog_id": 0, } - def mock_send_to_push_bouncer_response(method: str, *args: Any) -> Dict[str, Any]: + def mock_send_to_push_bouncer_response(method: str, *args: Any) -> dict[str, Any]: if method == "GET": return get_response return dummy_send_realms_only_response @@ -1544,8 +1544,8 @@ class RealmAPITest(ZulipTestCase): assertion error. """ - bool_tests: List[bool] = [False, True] - test_values: Dict[str, Any] = dict( + bool_tests: list[bool] = [False, True] + test_values: dict[str, Any] = dict( default_language=["de", "en"], default_code_block_language=["javascript", ""], description=["Realm description", "New description"], @@ -1930,8 +1930,8 @@ class RealmAPITest(ZulipTestCase): self.assert_json_success(result) def do_test_realm_default_setting_update_api(self, name: str) -> None: - bool_tests: List[bool] = [False, True] - test_values: Dict[str, Any] = dict( + bool_tests: list[bool] = [False, True] + test_values: dict[str, Any] = dict( web_font_size_px=[UserProfile.WEB_FONT_SIZE_PX_LEGACY], web_line_height_percent=[UserProfile.WEB_LINE_HEIGHT_PERCENT_LEGACY], color_scheme=UserProfile.COLOR_SCHEME_CHOICES, @@ -2083,8 +2083,8 @@ class RealmAPITest(ZulipTestCase): ) def do_test_changing_settings_by_owners_only(self, setting_name: str) -> None: - bool_tests: List[bool] = [False, True] - test_values: Dict[str, Any] = dict( + bool_tests: list[bool] = [False, True] + test_values: dict[str, Any] = dict( invite_to_realm_policy=[ InviteToRealmPolicyEnum.MEMBERS_ONLY, InviteToRealmPolicyEnum.ADMINS_ONLY, diff --git a/zerver/tests/test_realm_export.py b/zerver/tests/test_realm_export.py index 4dd16e8ed5..dfaef7e826 100644 --- a/zerver/tests/test_realm_export.py +++ b/zerver/tests/test_realm_export.py @@ -1,5 +1,5 @@ import os -from typing import Optional, Set +from typing import Optional from unittest.mock import patch import botocore.exceptions @@ -118,7 +118,7 @@ class RealmExportTest(ZulipTestCase): realm: Realm, output_dir: str, threads: int, - exportable_user_ids: Optional[Set[int]] = None, + exportable_user_ids: Optional[set[int]] = None, public_only: bool = False, consent_message_id: Optional[int] = None, export_as_active: Optional[bool] = None, diff --git a/zerver/tests/test_realm_linkifiers.py b/zerver/tests/test_realm_linkifiers.py index a46a26013b..59115ec601 100644 --- a/zerver/tests/test_realm_linkifiers.py +++ b/zerver/tests/test_realm_linkifiers.py @@ -1,5 +1,4 @@ import re -from typing import List import orjson from django.core.exceptions import ValidationError @@ -275,7 +274,7 @@ class RealmFilterTest(ZulipTestCase): iago = self.example_user("iago") self.login("iago") - def assert_linkifier_audit_logs(expected_id_order: List[int]) -> None: + def assert_linkifier_audit_logs(expected_id_order: list[int]) -> None: """Check if the audit log created orders the linkifiers correctly""" extra_data = ( RealmAuditLog.objects.filter( @@ -289,7 +288,7 @@ class RealmFilterTest(ZulipTestCase): ] self.assertListEqual(expected_id_order, audit_logged_ids) - def assert_linkifier_order(expected_id_order: List[int]) -> None: + def assert_linkifier_order(expected_id_order: list[int]) -> None: """Verify that the realm audit log created matches the expected ordering""" result = self.client_get("/json/realm/linkifiers") actual_id_order = [ @@ -297,7 +296,7 @@ class RealmFilterTest(ZulipTestCase): ] self.assertListEqual(expected_id_order, actual_id_order) - def reorder_verify_succeed(expected_id_order: List[int]) -> None: + def reorder_verify_succeed(expected_id_order: list[int]) -> None: """Send a reorder request and verify that it succeeds""" result = self.client_patch( "/json/realm/linkifiers", diff --git a/zerver/tests/test_retention.py b/zerver/tests/test_retention.py index edbf8ae3c3..90be76f117 100644 --- a/zerver/tests/test_retention.py +++ b/zerver/tests/test_retention.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Optional from unittest import mock import time_machine @@ -52,13 +52,13 @@ MIT_REALM_DAYS = 100 class RetentionTestingBase(ZulipTestCase): - def _get_usermessage_ids(self, message_ids: List[int]) -> List[int]: + def _get_usermessage_ids(self, message_ids: list[int]) -> list[int]: return list( UserMessage.objects.filter(message_id__in=message_ids).values_list("id", flat=True) ) def _verify_archive_data( - self, expected_message_ids: List[int], expected_usermessage_ids: List[int] + self, expected_message_ids: list[int], expected_usermessage_ids: list[int] ) -> None: self.assertEqual( set(ArchivedMessage.objects.values_list("id", flat=True)), @@ -75,7 +75,7 @@ class RetentionTestingBase(ZulipTestCase): self.assertEqual(UserMessage.objects.filter(id__in=expected_usermessage_ids).count(), 0) def _verify_restored_data( - self, expected_message_ids: List[int], expected_usermessage_ids: List[int] + self, expected_message_ids: list[int], expected_usermessage_ids: list[int] ) -> None: # Check that the data was restored: self.assertEqual( @@ -127,7 +127,7 @@ class ArchiveMessagesTestingBase(RetentionTestingBase): stream.message_retention_days = retention_period stream.save() - def _change_messages_date_sent(self, msgs_ids: List[int], date_sent: datetime) -> None: + def _change_messages_date_sent(self, msgs_ids: list[int], date_sent: datetime) -> None: Message.objects.filter(id__in=msgs_ids).update(date_sent=date_sent) def _make_mit_messages(self, message_quantity: int, date_sent: datetime) -> Any: @@ -165,7 +165,7 @@ class ArchiveMessagesTestingBase(RetentionTestingBase): assert msg_id is not None return msg_id - def _make_expired_zulip_messages(self, message_quantity: int) -> List[int]: + def _make_expired_zulip_messages(self, message_quantity: int) -> list[int]: msg_ids = list( Message.objects.order_by("id") .filter(realm=self.zulip_realm) @@ -178,7 +178,7 @@ class ArchiveMessagesTestingBase(RetentionTestingBase): return msg_ids - def _send_messages_with_attachments(self) -> Dict[str, int]: + def _send_messages_with_attachments(self) -> dict[str, int]: user_profile = self.example_user("hamlet") host = user_profile.realm.host realm_id = get_realm("zulip").id @@ -703,7 +703,7 @@ class MoveMessageToArchiveGeneral(MoveMessageToArchiveBase): self.send_personal_message(self.sender, self.recipient, body2), ] - attachment_id_to_message_ids: Dict[int, List[int]] = {} + attachment_id_to_message_ids: dict[int, list[int]] = {} attachment_ids = list( Attachment.objects.filter(messages__id__in=msg_ids).values_list("id", flat=True), ) @@ -984,7 +984,7 @@ class TestCleaningArchive(ArchiveMessagesTestingBase): class TestGetRealmAndStreamsForArchiving(ZulipTestCase): - def fix_ordering_of_result(self, result: List[Tuple[Realm, List[Stream]]]) -> None: + def fix_ordering_of_result(self, result: list[tuple[Realm, list[Stream]]]) -> None: """ This is a helper for giving the structure returned by get_realms_and_streams_for_archiving a consistent ordering. @@ -996,7 +996,7 @@ class TestGetRealmAndStreamsForArchiving(ZulipTestCase): for realm, streams_list in result: streams_list.sort(key=lambda stream: stream.id) - def simple_get_realms_and_streams_for_archiving(self) -> List[Tuple[Realm, List[Stream]]]: + def simple_get_realms_and_streams_for_archiving(self) -> list[tuple[Realm, list[Stream]]]: """ This is an implementation of the function we're testing, but using the obvious, unoptimized algorithm. We can use this for additional verification of correctness, @@ -1068,7 +1068,7 @@ class TestGetRealmAndStreamsForArchiving(ZulipTestCase): # so we use a helper to order both structures in a consistent manner. This wouldn't be necessary # if python had a true "unordered list" data structure. Set doesn't do the job, because it requires # elements to be hashable. - expected_result: List[Tuple[Realm, List[Stream]]] = [ + expected_result: list[tuple[Realm, list[Stream]]] = [ (zulip_realm, list(Stream.objects.filter(realm=zulip_realm).exclude(id=verona.id))), (zephyr_realm, [archiving_enabled_zephyr_stream]), (realm_all_streams_archiving_disabled, []), diff --git a/zerver/tests/test_rocketchat_importer.py b/zerver/tests/test_rocketchat_importer.py index 001883c6dd..64fb3b02ea 100644 --- a/zerver/tests/test_rocketchat_importer.py +++ b/zerver/tests/test_rocketchat_importer.py @@ -1,6 +1,6 @@ import os from datetime import datetime, timezone -from typing import Any, Dict, List +from typing import Any import orjson @@ -184,12 +184,12 @@ class RocketChatImporter(ZulipTestCase): fixture_dir_name = self.fixture_file_name("", "rocketchat_fixtures") rocketchat_data = rocketchat_data_to_dict(fixture_dir_name) - room_id_to_room_map: Dict[str, Dict[str, Any]] = {} - team_id_to_team_map: Dict[str, Dict[str, Any]] = {} - dsc_id_to_dsc_map: Dict[str, Dict[str, Any]] = {} - direct_id_to_direct_map: Dict[str, Dict[str, Any]] = {} - huddle_id_to_huddle_map: Dict[str, Dict[str, Any]] = {} - livechat_id_to_livechat_map: Dict[str, Dict[str, Any]] = {} + room_id_to_room_map: dict[str, dict[str, Any]] = {} + team_id_to_team_map: dict[str, dict[str, Any]] = {} + dsc_id_to_dsc_map: dict[str, dict[str, Any]] = {} + direct_id_to_direct_map: dict[str, dict[str, Any]] = {} + huddle_id_to_huddle_map: dict[str, dict[str, Any]] = {} + livechat_id_to_livechat_map: dict[str, dict[str, Any]] = {} with self.assertLogs(level="INFO"): categorize_channels_and_map_with_id( @@ -244,12 +244,12 @@ class RocketChatImporter(ZulipTestCase): realm_id = 3 stream_id_mapper = IdMapper() - room_id_to_room_map: Dict[str, Dict[str, Any]] = {} - team_id_to_team_map: Dict[str, Dict[str, Any]] = {} - dsc_id_to_dsc_map: Dict[str, Dict[str, Any]] = {} - direct_id_to_direct_map: Dict[str, Dict[str, Any]] = {} - huddle_id_to_huddle_map: Dict[str, Dict[str, Any]] = {} - livechat_id_to_livechat_map: Dict[str, Dict[str, Any]] = {} + room_id_to_room_map: dict[str, dict[str, Any]] = {} + team_id_to_team_map: dict[str, dict[str, Any]] = {} + dsc_id_to_dsc_map: dict[str, dict[str, Any]] = {} + direct_id_to_direct_map: dict[str, dict[str, Any]] = {} + huddle_id_to_huddle_map: dict[str, dict[str, Any]] = {} + livechat_id_to_livechat_map: dict[str, dict[str, Any]] = {} with self.assertLogs(level="INFO"): categorize_channels_and_map_with_id( @@ -329,12 +329,12 @@ class RocketChatImporter(ZulipTestCase): user_id_mapper=user_id_mapper, ) - room_id_to_room_map: Dict[str, Dict[str, Any]] = {} - team_id_to_team_map: Dict[str, Dict[str, Any]] = {} - dsc_id_to_dsc_map: Dict[str, Dict[str, Any]] = {} - direct_id_to_direct_map: Dict[str, Dict[str, Any]] = {} - huddle_id_to_huddle_map: Dict[str, Dict[str, Any]] = {} - livechat_id_to_livechat_map: Dict[str, Dict[str, Any]] = {} + room_id_to_room_map: dict[str, dict[str, Any]] = {} + team_id_to_team_map: dict[str, dict[str, Any]] = {} + dsc_id_to_dsc_map: dict[str, dict[str, Any]] = {} + direct_id_to_direct_map: dict[str, dict[str, Any]] = {} + huddle_id_to_huddle_map: dict[str, dict[str, Any]] = {} + livechat_id_to_livechat_map: dict[str, dict[str, Any]] = {} with self.assertLogs(level="INFO"): categorize_channels_and_map_with_id( @@ -387,7 +387,7 @@ class RocketChatImporter(ZulipTestCase): self.assertEqual(subscriber_handler.get_users(stream_id=zerver_stream[5]["id"]), {harry_id}) # Add a new channel with no user. - no_user_channel: Dict[str, Any] = { + no_user_channel: dict[str, Any] = { "_id": "rand0mID", "ts": datetime(2021, 7, 15, 10, 58, 23, 647000, tzinfo=timezone.utc), "t": "c", @@ -436,12 +436,12 @@ class RocketChatImporter(ZulipTestCase): user_id_mapper=user_id_mapper, ) - room_id_to_room_map: Dict[str, Dict[str, Any]] = {} - team_id_to_team_map: Dict[str, Dict[str, Any]] = {} - dsc_id_to_dsc_map: Dict[str, Dict[str, Any]] = {} - direct_id_to_direct_map: Dict[str, Dict[str, Any]] = {} - huddle_id_to_huddle_map: Dict[str, Dict[str, Any]] = {} - livechat_id_to_livechat_map: Dict[str, Dict[str, Any]] = {} + room_id_to_room_map: dict[str, dict[str, Any]] = {} + team_id_to_team_map: dict[str, dict[str, Any]] = {} + dsc_id_to_dsc_map: dict[str, dict[str, Any]] = {} + direct_id_to_direct_map: dict[str, dict[str, Any]] = {} + huddle_id_to_huddle_map: dict[str, dict[str, Any]] = {} + livechat_id_to_livechat_map: dict[str, dict[str, Any]] = {} with self.assertLogs(level="INFO"): categorize_channels_and_map_with_id( @@ -539,12 +539,12 @@ class RocketChatImporter(ZulipTestCase): user_id_mapper=user_id_mapper, ) - room_id_to_room_map: Dict[str, Dict[str, Any]] = {} - team_id_to_team_map: Dict[str, Dict[str, Any]] = {} - dsc_id_to_dsc_map: Dict[str, Dict[str, Any]] = {} - direct_id_to_direct_map: Dict[str, Dict[str, Any]] = {} - huddle_id_to_huddle_map: Dict[str, Dict[str, Any]] = {} - livechat_id_to_livechat_map: Dict[str, Dict[str, Any]] = {} + room_id_to_room_map: dict[str, dict[str, Any]] = {} + team_id_to_team_map: dict[str, dict[str, Any]] = {} + dsc_id_to_dsc_map: dict[str, dict[str, Any]] = {} + direct_id_to_direct_map: dict[str, dict[str, Any]] = {} + huddle_id_to_huddle_map: dict[str, dict[str, Any]] = {} + livechat_id_to_livechat_map: dict[str, dict[str, Any]] = {} with self.assertLogs(level="INFO"): categorize_channels_and_map_with_id( @@ -579,9 +579,9 @@ class RocketChatImporter(ZulipTestCase): zerver_direct_message_group=zerver_direct_message_group, ) - stream_id_to_recipient_id: Dict[int, int] = {} - user_id_to_recipient_id: Dict[int, int] = {} - huddle_id_to_recipient_id: Dict[int, int] = {} + stream_id_to_recipient_id: dict[int, int] = {} + user_id_to_recipient_id: dict[int, int] = {} + huddle_id_to_recipient_id: dict[int, int] = {} map_receiver_id_to_recipient_id( zerver_recipient=zerver_recipient, @@ -619,12 +619,12 @@ class RocketChatImporter(ZulipTestCase): fixture_dir_name = self.fixture_file_name("", "rocketchat_fixtures") rocketchat_data = rocketchat_data_to_dict(fixture_dir_name) - room_id_to_room_map: Dict[str, Dict[str, Any]] = {} - team_id_to_team_map: Dict[str, Dict[str, Any]] = {} - dsc_id_to_dsc_map: Dict[str, Dict[str, Any]] = {} - direct_id_to_direct_map: Dict[str, Dict[str, Any]] = {} - huddle_id_to_huddle_map: Dict[str, Dict[str, Any]] = {} - livechat_id_to_livechat_map: Dict[str, Dict[str, Any]] = {} + room_id_to_room_map: dict[str, dict[str, Any]] = {} + team_id_to_team_map: dict[str, dict[str, Any]] = {} + dsc_id_to_dsc_map: dict[str, dict[str, Any]] = {} + direct_id_to_direct_map: dict[str, dict[str, Any]] = {} + huddle_id_to_huddle_map: dict[str, dict[str, Any]] = {} + livechat_id_to_livechat_map: dict[str, dict[str, Any]] = {} with self.assertLogs(level="INFO"): categorize_channels_and_map_with_id( @@ -637,9 +637,9 @@ class RocketChatImporter(ZulipTestCase): livechat_id_to_livechat_map=livechat_id_to_livechat_map, ) - channel_messages: List[Dict[str, Any]] = [] - private_messages: List[Dict[str, Any]] = [] - livechat_messages: List[Dict[str, Any]] = [] + channel_messages: list[dict[str, Any]] = [] + private_messages: list[dict[str, Any]] = [] + livechat_messages: list[dict[str, Any]] = [] separate_channel_private_and_livechat_messages( messages=rocketchat_data["message"], @@ -743,7 +743,7 @@ class RocketChatImporter(ZulipTestCase): output_dir=output_dir, ) - total_reactions: List[ZerverFieldsT] = [] + total_reactions: list[ZerverFieldsT] = [] reactions = [ {"name": "grin", "user_id": 3}, @@ -833,8 +833,8 @@ class RocketChatImporter(ZulipTestCase): user_id_mapper=user_id_mapper, ) - zerver_attachments: List[ZerverFieldsT] = [] - uploads_list: List[ZerverFieldsT] = [] + zerver_attachments: list[ZerverFieldsT] = [] + uploads_list: list[ZerverFieldsT] = [] upload_id_to_upload_data_map = map_upload_id_to_upload_data(rocketchat_data["upload"]) diff --git a/zerver/tests/test_scheduled_messages.py b/zerver/tests/test_scheduled_messages.py index 9166445517..f718864152 100644 --- a/zerver/tests/test_scheduled_messages.py +++ b/zerver/tests/test_scheduled_messages.py @@ -2,7 +2,7 @@ import re import time from datetime import timedelta from io import StringIO -from typing import TYPE_CHECKING, Any, Dict, List, Union +from typing import TYPE_CHECKING, Any, Union from unittest import mock import orjson @@ -33,7 +33,7 @@ class ScheduledMessageTest(ZulipTestCase): def do_schedule_message( self, msg_type: str, - to: Union[int, List[str], List[int]], + to: Union[int, list[str], list[int]], msg: str, scheduled_delivery_timestamp: int, ) -> "TestHttpResponse": @@ -443,7 +443,7 @@ class ScheduledMessageTest(ZulipTestCase): timestamp_to_datetime(scheduled_delivery_timestamp), ) scheduled_message_id = scheduled_message.id - payload: Dict[str, Any] + payload: dict[str, Any] # Edit message with other stream message type ("stream") and no other changes # results in no changes to the scheduled message. diff --git a/zerver/tests/test_scim.py b/zerver/tests/test_scim.py index af69e98b88..2b39b8ff26 100644 --- a/zerver/tests/test_scim.py +++ b/zerver/tests/test_scim.py @@ -1,6 +1,6 @@ import copy from contextlib import contextmanager -from typing import TYPE_CHECKING, Any, Dict, Iterator, TypedDict +from typing import TYPE_CHECKING, Any, Iterator, TypedDict from unittest import mock import orjson @@ -31,7 +31,7 @@ class SCIMTestCase(ZulipTestCase): def scim_headers(self) -> SCIMHeadersDict: return {"HTTP_AUTHORIZATION": f"Bearer {settings.SCIM_CONFIG['zulip']['bearer_token']}"} - def generate_user_schema(self, user_profile: UserProfile) -> Dict[str, Any]: + def generate_user_schema(self, user_profile: UserProfile) -> dict[str, Any]: return { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "id": user_profile.id, diff --git a/zerver/tests/test_service_bot_system.py b/zerver/tests/test_service_bot_system.py index bda0f93c01..cbf93fd8a1 100644 --- a/zerver/tests/test_service_bot_system.py +++ b/zerver/tests/test_service_bot_system.py @@ -1,5 +1,5 @@ from functools import wraps -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable, Optional from unittest import mock import orjson @@ -491,7 +491,7 @@ class TestServiceBotEventTriggers(ZulipTestCase): def check_values_passed( queue_name: Any, - trigger_event: Dict[str, Any], + trigger_event: dict[str, Any], processor: Optional[Callable[[Any], None]] = None, ) -> None: assert self.bot_profile.bot_type @@ -534,7 +534,7 @@ class TestServiceBotEventTriggers(ZulipTestCase): def check_values_passed( queue_name: Any, - trigger_event: Dict[str, Any], + trigger_event: dict[str, Any], processor: Optional[Callable[[Any], None]] = None, ) -> None: assert self.bot_profile.bot_type @@ -578,7 +578,7 @@ class TestServiceBotEventTriggers(ZulipTestCase): def check_values_passed( queue_name: Any, - trigger_event: Dict[str, Any], + trigger_event: dict[str, Any], processor: Optional[Callable[[Any], None]] = None, ) -> None: assert self.bot_profile.bot_type diff --git a/zerver/tests/test_settings.py b/zerver/tests/test_settings.py index 6eca17d883..cf776967ad 100644 --- a/zerver/tests/test_settings.py +++ b/zerver/tests/test_settings.py @@ -1,6 +1,6 @@ import time from datetime import datetime, timezone -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any from unittest import mock import orjson @@ -343,7 +343,7 @@ class ChangeSettingsTest(ZulipTestCase): self.assert_json_error(result, "Your Zulip password is managed in LDAP") def do_test_change_user_setting(self, setting_name: str) -> None: - test_changes: Dict[str, Any] = dict( + test_changes: dict[str, Any] = dict( default_language="de", web_home_view="all_messages", emojiset="google", diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py index 699b0916f9..1deabbb072 100644 --- a/zerver/tests/test_signup.py +++ b/zerver/tests/test_signup.py @@ -1,7 +1,7 @@ import re import time from datetime import timedelta -from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Union +from typing import TYPE_CHECKING, Any, Optional, Sequence, Union from unittest.mock import MagicMock, patch from urllib.parse import quote, quote_plus, urlencode, urlsplit @@ -2240,7 +2240,7 @@ class UserSignUpTest(ZulipTestCase): if realm is None: # nocoverage realm = get_realm("zulip") - client_kwargs: Dict[str, Any] = {} + client_kwargs: dict[str, Any] = {} if subdomain: client_kwargs["subdomain"] = subdomain diff --git a/zerver/tests/test_slack_importer.py b/zerver/tests/test_slack_importer.py index 63c9343775..1ddf85ef0b 100644 --- a/zerver/tests/test_slack_importer.py +++ b/zerver/tests/test_slack_importer.py @@ -1,7 +1,7 @@ import os import shutil from io import BytesIO -from typing import Any, Dict, Iterator, List, Set, Tuple +from typing import Any, Iterator from unittest import mock from unittest.mock import ANY from urllib.parse import parse_qs, urlsplit @@ -57,7 +57,7 @@ def remove_folder(path: str) -> None: shutil.rmtree(path) -def request_callback(request: PreparedRequest) -> Tuple[int, Dict[str, str], bytes]: +def request_callback(request: PreparedRequest) -> tuple[int, dict[str, str], bytes]: valid_endpoint = False endpoints = [ "https://slack.com/api/users.list", @@ -95,7 +95,7 @@ def request_callback(request: PreparedRequest) -> Tuple[int, Dict[str, str], byt return (200, {}, orjson.dumps({"ok": False, "error": "user_not_found"})) return (200, {}, orjson.dumps({"ok": True, "user": {"id": user_id, "team_id": team_id}})) # Else, https://slack.com/api/team.info - team_not_found: Tuple[int, Dict[str, str], bytes] = ( + team_not_found: tuple[int, dict[str, str], bytes] = ( 200, {}, orjson.dumps({"ok": False, "error": "team_not_found"}), @@ -176,7 +176,7 @@ class SlackImporter(ZulipTestCase): realm_id = 2 realm_subdomain = "test-realm" time = float(timezone_now().timestamp()) - test_realm: List[Dict[str, Any]] = build_zerver_realm( + test_realm: list[dict[str, Any]] = build_zerver_realm( realm_id, realm_subdomain, time, "Slack" ) test_zerver_realm_dict = test_realm[0] @@ -190,7 +190,7 @@ class SlackImporter(ZulipTestCase): @responses.activate def test_check_token_access(self) -> None: - def token_request_callback(request: PreparedRequest) -> Tuple[int, Dict[str, str], bytes]: + def token_request_callback(request: PreparedRequest) -> tuple[int, dict[str, str], bytes]: auth = request.headers.get("Authorization") if auth == "Bearer xoxb-broken-request": return (400, {}, orjson.dumps({"ok": False, "error": "invalid_auth"})) @@ -293,7 +293,7 @@ class SlackImporter(ZulipTestCase): def test_get_timezone(self) -> None: user_chicago_timezone = {"tz": "America/Chicago"} user_timezone_none = {"tz": None} - user_no_timezone: Dict[str, Any] = {} + user_no_timezone: dict[str, Any] = {} self.assertEqual(get_user_timezone(user_chicago_timezone), "America/Chicago") self.assertEqual(get_user_timezone(user_timezone_none), "America/New_York") @@ -671,7 +671,7 @@ class SlackImporter(ZulipTestCase): } subscription_id_count = 0 recipient_id = 12 - zerver_subscription: List[Dict[str, Any]] = [] + zerver_subscription: list[dict[str, Any]] = [] final_subscription_id = get_subscription( channel_members, zerver_subscription, @@ -798,7 +798,7 @@ class SlackImporter(ZulipTestCase): self, mock_channels_to_zerver_stream: mock.Mock, mock_users_to_zerver_userprofile: mock.Mock ) -> None: realm_id = 1 - user_list: List[Dict[str, Any]] = [] + user_list: list[dict[str, Any]] = [] ( realm, slack_user_id_to_zulip_user_id, @@ -848,7 +848,7 @@ class SlackImporter(ZulipTestCase): self.assertEqual(user_without_file, "U064KUGRJ") def test_build_zerver_message(self) -> None: - zerver_usermessage: List[Dict[str, Any]] = [] + zerver_usermessage: list[dict[str, Any]] = [] # recipient_id -> set of user_ids subscriber_map = { @@ -899,7 +899,7 @@ class SlackImporter(ZulipTestCase): reactions = [{"name": "grinning", "users": ["U061A5N1G"], "count": 1}] - all_messages: List[Dict[str, Any]] = [ + all_messages: list[dict[str, Any]] = [ { "text": "<@U066MTL5U> has joined the channel", "subtype": "channel_join", @@ -1003,9 +1003,9 @@ class SlackImporter(ZulipTestCase): "DHX1UP7EG": ("U061A5N1G", "U061A1R2R"), } - zerver_usermessage: List[Dict[str, Any]] = [] - subscriber_map: Dict[int, Set[int]] = {} - added_channels: Dict[str, Tuple[str, int]] = {"random": ("c5", 1), "general": ("c6", 2)} + zerver_usermessage: list[dict[str, Any]] = [] + subscriber_map: dict[int, set[int]] = {} + added_channels: dict[str, tuple[str, int]] = {"random": ("c5", 1), "general": ("c6", 2)} ( zerver_message, @@ -1100,7 +1100,7 @@ class SlackImporter(ZulipTestCase): slack_user_id_to_zulip_user_id = {"U066MTL5U": 5, "U061A5N1G": 24, "U061A1R2R": 43} - all_messages: List[Dict[str, Any]] = [ + all_messages: list[dict[str, Any]] = [ { "text": "<@U066MTL5U> has joined the channel", "subtype": "channel_join", @@ -1154,9 +1154,9 @@ class SlackImporter(ZulipTestCase): } dm_members: DMMembersT = {} - zerver_usermessage: List[Dict[str, Any]] = [] - subscriber_map: Dict[int, Set[int]] = {} - added_channels: Dict[str, Tuple[str, int]] = {"random": ("c5", 1), "general": ("c6", 2)} + zerver_usermessage: list[dict[str, Any]] = [] + subscriber_map: dict[int, set[int]] = {} + added_channels: dict[str, tuple[str, int]] = {"random": ("c5", 1), "general": ("c6", 2)} ( zerver_message, @@ -1209,7 +1209,7 @@ class SlackImporter(ZulipTestCase): output_dir = os.path.join(settings.TEST_WORKER_DIR, "test-slack-import") os.makedirs(output_dir, exist_ok=True) - added_channels: Dict[str, Tuple[str, int]] = {"random": ("c5", 1), "general": ("c6", 2)} + added_channels: dict[str, tuple[str, int]] = {"random": ("c5", 1), "general": ("c6", 2)} time = float(timezone_now().timestamp()) zerver_message = [{"id": 1, "ts": time}, {"id": 5, "ts": time}] @@ -1224,11 +1224,11 @@ class SlackImporter(ZulipTestCase): return iter(copy.deepcopy(zerver_message)) - realm: Dict[str, Any] = {"zerver_subscription": []} - user_list: List[Dict[str, Any]] = [] + realm: dict[str, Any] = {"zerver_subscription": []} + user_list: list[dict[str, Any]] = [] reactions = [{"name": "grinning", "users": ["U061A5N1G"], "count": 1}] - attachments: List[Dict[str, Any]] = [] - uploads: List[Dict[str, Any]] = [] + attachments: list[dict[str, Any]] = [] + uploads: list[dict[str, Any]] = [] zerver_usermessage = [{"id": 3}, {"id": 5}, {"id": 6}, {"id": 9}] @@ -1411,8 +1411,8 @@ class SlackImporter(ZulipTestCase): "alice": alice_id, } - zerver_attachment: List[Dict[str, Any]] = [] - uploads_list: List[Dict[str, Any]] = [] + zerver_attachment: list[dict[str, Any]] = [] + uploads_list: list[dict[str, Any]] = [] info = process_message_files( message=message, diff --git a/zerver/tests/test_slack_message_conversion.py b/zerver/tests/test_slack_message_conversion.py index 6de5ba07f8..f24dc857b8 100644 --- a/zerver/tests/test_slack_message_conversion.py +++ b/zerver/tests/test_slack_message_conversion.py @@ -1,5 +1,5 @@ import os -from typing import Any, Dict, List, Tuple +from typing import Any import orjson from typing_extensions import override @@ -25,7 +25,7 @@ class SlackMessageConversion(ZulipTestCase): else: super().assertEqual(first, second) - def load_slack_message_conversion_tests(self) -> Dict[Any, Any]: + def load_slack_message_conversion_tests(self) -> dict[Any, Any]: test_fixtures = {} with open( os.path.join(os.path.dirname(__file__), "fixtures/slack_message_conversion.json"), "rb" @@ -43,9 +43,9 @@ class SlackMessageConversion(ZulipTestCase): for name, test in format_tests.items(): # Check that there aren't any unexpected keys as those are often typos self.assert_length(set(test.keys()) - valid_keys, 0) - slack_user_map: Dict[str, int] = {} - users: List[Dict[str, Any]] = [{}] - channel_map: Dict[str, Tuple[str, int]] = {} + slack_user_map: dict[str, int] = {} + users: list[dict[str, Any]] = [{}] + channel_map: dict[str, tuple[str, int]] = {} converted = convert_to_zulip_markdown(test["input"], users, channel_map, slack_user_map) converted_text = converted[0] with self.subTest(slack_message_conversion=name): @@ -106,7 +106,7 @@ class SlackMessageConversion(ZulipTestCase): self.assertEqual(mentioned_users, []) def test_has_link(self) -> None: - slack_user_map: Dict[str, int] = {} + slack_user_map: dict[str, int] = {} message = "" text, mentioned_users, has_link = convert_to_zulip_markdown(message, [], {}, slack_user_map) diff --git a/zerver/tests/test_submessage.py b/zerver/tests/test_submessage.py index f2fb1d7a1b..274eea29c5 100644 --- a/zerver/tests/test_submessage.py +++ b/zerver/tests/test_submessage.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List +from typing import Any from unittest import mock from zerver.actions.submessage import do_add_submessage @@ -18,7 +18,7 @@ class TestBasics(ZulipTestCase): stream_name=stream_name, ) - def get_raw_rows() -> List[Dict[str, Any]]: + def get_raw_rows() -> list[dict[str, Any]]: query = SubMessage.get_raw_db_rows([message_id]) rows = list(query) return rows diff --git a/zerver/tests/test_subs.py b/zerver/tests/test_subs.py index 0737e40da7..45f5f3970f 100644 --- a/zerver/tests/test_subs.py +++ b/zerver/tests/test_subs.py @@ -2,7 +2,7 @@ import hashlib import random from datetime import timedelta from io import StringIO -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Set, Union +from typing import TYPE_CHECKING, Any, Optional, Sequence, Union from unittest import mock import orjson @@ -136,8 +136,8 @@ class TestMiscStuff(ZulipTestCase): self.assertNotIn("* Denmark", s) def test_pick_colors(self) -> None: - used_colors: Set[str] = set() - color_map: Dict[int, str] = {} + used_colors: set[str] = set() + color_map: dict[int, str] = {} recipient_ids = list(range(30)) user_color_map = pick_colors(used_colors, color_map, recipient_ids) self.assertEqual( @@ -350,7 +350,7 @@ class TestCreateStreams(ZulipTestCase): def test_history_public_to_subscribers_on_stream_creation(self) -> None: realm = get_realm("zulip") - stream_dicts: List[StreamDict] = [ + stream_dicts: list[StreamDict] = [ { "name": "publicstream", "description": "Public stream with public history", @@ -508,7 +508,7 @@ class TestCreateStreams(ZulipTestCase): final_usermessage_count - initial_usermessage_count, 4 + announce_stream_subs.count() ) - def get_unread_stream_data(user: UserProfile) -> List[UnreadStreamInfo]: + def get_unread_stream_data(user: UserProfile) -> list[UnreadStreamInfo]: raw_unread_data = get_raw_unread_data(user) aggregated_data = aggregate_unread_data(raw_unread_data) return aggregated_data["streams"] @@ -796,7 +796,7 @@ class StreamAdminTest(ZulipTestCase): stream_names = ["new1", "new2", "new3"] stream_descriptions = ["des1", "des2", "des3"] - streams_raw: List[StreamDict] = [ + streams_raw: list[StreamDict] = [ {"name": stream_name, "description": stream_description, "is_web_public": True} for (stream_name, stream_description) in zip(stream_names, stream_descriptions) ] @@ -1427,7 +1427,7 @@ class StreamAdminTest(ZulipTestCase): streams_to_remove = [ensure_stream(realm, "stream3", acting_user=None)] all_streams = streams_to_keep + streams_to_remove - def get_streams(group: DefaultStreamGroup) -> List[Stream]: + def get_streams(group: DefaultStreamGroup) -> list[Stream]: return list(group.streams.all().order_by("name")) group_name = "group1" @@ -2369,7 +2369,7 @@ class StreamAdminTest(ZulipTestCase): """ admin = self.example_user("iago") - streams_raw: List[StreamDict] = [ + streams_raw: list[StreamDict] = [ { "name": "new_stream", "message_retention_days": 10, @@ -2566,7 +2566,7 @@ class StreamAdminTest(ZulipTestCase): def attempt_unsubscribe_of_principal( self, - target_users: List[UserProfile], + target_users: list[UserProfile], query_count: int, cache_count: Optional[int] = None, is_realm_admin: bool = False, @@ -2895,7 +2895,7 @@ class StreamAdminTest(ZulipTestCase): class DefaultStreamTest(ZulipTestCase): - def get_default_stream_names(self, realm: Realm) -> Set[str]: + def get_default_stream_names(self, realm: Realm) -> set[str]: streams = get_default_streams_for_realm_as_dicts(realm.id) return {s["name"] for s in streams} @@ -3023,7 +3023,7 @@ class DefaultStreamGroupTest(ZulipTestCase): for stream_name in ["stream1", "stream2", "stream3"] ] - def get_streams(group: DefaultStreamGroup) -> List[Stream]: + def get_streams(group: DefaultStreamGroup) -> list[Stream]: return list(group.streams.all().order_by("name")) group_name = "group1" @@ -3999,7 +3999,7 @@ class SubscriptionAPITest(ZulipTestCase): self.test_realm = self.user_profile.realm self.streams = self.get_streams(self.user_profile) - def make_random_stream_names(self, existing_stream_names: List[str]) -> List[str]: + def make_random_stream_names(self, existing_stream_names: list[str]) -> list[str]: """ Helper function to make up random stream names. It takes existing_stream_names and randomly appends a digit to the end of each, @@ -4096,12 +4096,12 @@ class SubscriptionAPITest(ZulipTestCase): def helper_check_subs_before_and_after_add( self, - subscriptions: List[str], - other_params: Dict[str, Any], - subscribed: List[str], - already_subscribed: List[str], + subscriptions: list[str], + other_params: dict[str, Any], + subscribed: list[str], + already_subscribed: list[str], email: str, - new_subs: List[str], + new_subs: list[str], realm: Realm, invite_only: bool = False, ) -> None: @@ -4690,7 +4690,7 @@ class SubscriptionAPITest(ZulipTestCase): self, invitee_data: Union[str, int], invitee_realm: Realm, - streams: List[str], + streams: list[str], policy_name: str, invite_only: bool = False, ) -> None: @@ -5326,10 +5326,10 @@ class SubscriptionAPITest(ZulipTestCase): def helper_check_subs_before_and_after_remove( self, - subscriptions: List[str], - json_dict: Dict[str, Any], + subscriptions: list[str], + json_dict: dict[str, Any], email: str, - new_subs: List[str], + new_subs: list[str], realm: Realm, ) -> None: """ @@ -5454,7 +5454,7 @@ class SubscriptionAPITest(ZulipTestCase): self.send_stream_message(random_user, "stream2", "test", "test") self.send_stream_message(random_user, "private_stream", "test", "test") - def get_unread_stream_data() -> List[UnreadStreamInfo]: + def get_unread_stream_data() -> list[UnreadStreamInfo]: raw_unread_data = get_raw_unread_data(user) aggregated_data = aggregate_unread_data(raw_unread_data) return aggregated_data["streams"] @@ -6000,7 +6000,7 @@ class GetSubscribersTest(ZulipTestCase): self.assertEqual(non_ws(msg.content), non_ws(expected_msg)) def check_well_formed_result( - self, result: Dict[str, Any], stream_name: str, realm: Realm + self, result: dict[str, Any], stream_name: str, realm: Realm ) -> None: """ A successful call to get_subscribers returns the list of subscribers in @@ -6182,7 +6182,7 @@ class GetSubscribersTest(ZulipTestCase): create_private_streams() - def get_never_subscribed() -> List[NeverSubscribedStreamDict]: + def get_never_subscribed() -> list[NeverSubscribedStreamDict]: with self.assert_database_query_count(4): sub_data = gather_subscriptions_helper(self.user_profile) self.verify_sub_fields(sub_data) @@ -6460,7 +6460,7 @@ class GetSubscribersTest(ZulipTestCase): result_dict = self.assert_json_success(result) self.assertIn("subscribers", result_dict) self.assertIsInstance(result_dict["subscribers"], list) - subscribers: List[int] = [] + subscribers: list[int] = [] for subscriber in result_dict["subscribers"]: self.assertIsInstance(subscriber, int) subscribers.append(subscriber) diff --git a/zerver/tests/test_tornado.py b/zerver/tests/test_tornado.py index 4b24f6dc7c..b5a2f31a57 100644 --- a/zerver/tests/test_tornado.py +++ b/zerver/tests/test_tornado.py @@ -1,7 +1,7 @@ import asyncio import socket from functools import wraps -from typing import Any, Awaitable, Callable, Dict, Optional, TypeVar +from typing import Any, Awaitable, Callable, Optional, TypeVar from unittest import TestResult, mock from urllib.parse import urlencode @@ -51,7 +51,7 @@ class TornadoWebTestCase(ZulipTestCase): self.http_client = AsyncHTTPClient() signals.request_started.disconnect(close_old_connections) signals.request_finished.disconnect(close_old_connections) - self.session_cookie: Optional[Dict[str, str]] = None + self.session_cookie: Optional[dict[str, str]] = None @async_to_sync_decorator @override @@ -85,10 +85,10 @@ class TornadoWebTestCase(ZulipTestCase): "Cookie": f"{session_cookie}={session_key}", } - def get_session_cookie(self) -> Dict[str, str]: + def get_session_cookie(self) -> dict[str, str]: return {} if self.session_cookie is None else self.session_cookie - def add_session_cookie(self, kwargs: Dict[str, Any]) -> None: + def add_session_cookie(self, kwargs: dict[str, Any]) -> None: # TODO: Currently only allows session cookie headers = kwargs.get("headers", {}) headers.update(self.get_session_cookie()) @@ -130,7 +130,7 @@ class EventsTestCase(TornadoWebTestCase): ) process_event(event, users) - def wrapped_fetch_events(**query: Any) -> Dict[str, Any]: + def wrapped_fetch_events(**query: Any) -> dict[str, Any]: ret = event_queue.fetch_events(**query) asyncio.get_running_loop().call_soon(process_events) return ret diff --git a/zerver/tests/test_typed_endpoint.py b/zerver/tests/test_typed_endpoint.py index 2069f96d0c..def6661f44 100644 --- a/zerver/tests/test_typed_endpoint.py +++ b/zerver/tests/test_typed_endpoint.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Dict, List, Literal, Optional, Type, TypeVar, Union, cast +from typing import Any, Callable, Literal, Optional, TypeVar, Union, cast import orjson from django.core.exceptions import ValidationError as DjangoValidationError @@ -42,7 +42,7 @@ class TestEndpoint(ZulipTestCase): """This test is only needed because we don't have coverage of is_optional in Python 3.11. """ - self.assertTrue(is_optional(cast(Type[Optional[str]], Optional[str]))) + self.assertTrue(is_optional(cast(type[Optional[str]], Optional[str]))) self.assertFalse(is_optional(str)) def test_coerce(self) -> None: @@ -247,7 +247,7 @@ class TestEndpoint(ZulipTestCase): *, body: JsonBodyPayload[WildValue], non_body: Json[int] = 0, - ) -> Dict[str, object]: + ) -> dict[str, object]: status = body["totame"]["status"].tame(check_bool) return {"status": status, "foo": non_body} @@ -590,16 +590,16 @@ class ValidationErrorHandlingTest(ZulipTestCase): def __repr__(self) -> str: return f"Pydantic error type: {self.error_type}; Parameter type: {self.param_type}; Expected error message: {self.error_message}" - parameterized_tests: List[SubTest] = [ + parameterized_tests: list[SubTest] = [ SubTest( error_type="string_too_short", - param_type=Json[List[Annotated[str, RequiredStringConstraint()]]], + param_type=Json[list[Annotated[str, RequiredStringConstraint()]]], input_data=orjson.dumps([""]).decode(), error_message="input[0] cannot be blank", ), SubTest( error_type="string_too_short", - param_type=Json[List[Annotated[str, RequiredStringConstraint()]]], + param_type=Json[list[Annotated[str, RequiredStringConstraint()]]], input_data=orjson.dumps(["g", " "]).decode(), error_message="input[1] cannot be blank", ), diff --git a/zerver/tests/test_urls.py b/zerver/tests/test_urls.py index 2a9dd297e2..b7f89ae72e 100644 --- a/zerver/tests/test_urls.py +++ b/zerver/tests/test_urls.py @@ -1,6 +1,5 @@ import importlib import os -from typing import List from unittest import mock import django.urls.resolvers @@ -23,7 +22,7 @@ class PublicURLTest(ZulipTestCase): URLs redirect to a page. """ - def fetch(self, method: str, urls: List[str], expected_status: int) -> None: + def fetch(self, method: str, urls: list[str], expected_status: int) -> None: for url in urls: # e.g. self.client_post(url) if method is "post" response = getattr(self, method)(url) diff --git a/zerver/tests/test_user_status.py b/zerver/tests/test_user_status.py index 72a611fcc7..5a5df9ab6e 100644 --- a/zerver/tests/test_user_status.py +++ b/zerver/tests/test_user_status.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional +from typing import Any, Optional import orjson @@ -178,7 +178,7 @@ class UserStatusTest(ZulipTestCase): ) def update_status_and_assert_event( - self, payload: Dict[str, Any], expected_event: Dict[str, Any], num_events: int = 1 + self, payload: dict[str, Any], expected_event: dict[str, Any], num_events: int = 1 ) -> None: with self.capture_send_event_calls(expected_num_events=num_events) as events: result = self.client_post("/json/users/me/status", payload) @@ -192,7 +192,7 @@ class UserStatusTest(ZulipTestCase): self.login_user(hamlet) # Try to omit parameter--this should be an error. - payload: Dict[str, Any] = {} + payload: dict[str, Any] = {} result = self.client_post("/json/users/me/status", payload) self.assert_json_error(result, "Client did not pass any new values.") diff --git a/zerver/tests/test_user_topics.py b/zerver/tests/test_user_topics.py index e8a8114f2d..6e27da51ab 100644 --- a/zerver/tests/test_user_topics.py +++ b/zerver/tests/test_user_topics.py @@ -1,5 +1,5 @@ from datetime import datetime, timezone -from typing import Any, Dict, List +from typing import Any import orjson import time_machine @@ -95,7 +95,7 @@ class MutedTopicsTestsDeprecated(ZulipTestCase): url = "/api/v1/users/me/subscriptions/muted_topics" - payloads: List[Dict[str, object]] = [ + payloads: list[dict[str, object]] = [ {"stream": stream.name, "topic": "Verona3", "op": "add"}, {"stream_id": stream.id, "topic": "Verona3", "op": "add"}, ] @@ -146,7 +146,7 @@ class MutedTopicsTestsDeprecated(ZulipTestCase): stream = get_stream("Verona", realm) url = "/api/v1/users/me/subscriptions/muted_topics" - payloads: List[Dict[str, object]] = [ + payloads: list[dict[str, object]] = [ {"stream": stream.name, "topic": "vERONA3", "op": "remove"}, {"stream_id": stream.id, "topic": "vEroNA3", "op": "remove"}, ] @@ -213,7 +213,7 @@ class MutedTopicsTestsDeprecated(ZulipTestCase): stream = get_stream("Verona", realm) url = "/api/v1/users/me/subscriptions/muted_topics" - data: Dict[str, Any] = {"stream": "BOGUS", "topic": "Verona3", "op": "remove"} + data: dict[str, Any] = {"stream": "BOGUS", "topic": "Verona3", "op": "remove"} result = self.api_patch(user, url, data) self.assert_json_error(result, "Topic is not muted") @@ -349,7 +349,7 @@ class MutedTopicsTests(ZulipTestCase): ) ) # Verify if events are sent properly - user_topic_event: Dict[str, Any] = { + user_topic_event: dict[str, Any] = { "type": "user_topic", "stream_id": stream.id, "topic_name": "Verona3", @@ -415,7 +415,7 @@ class MutedTopicsTests(ZulipTestCase): ) ) # Verify if events are sent properly - user_topic_event: Dict[str, Any] = { + user_topic_event: dict[str, Any] = { "type": "user_topic", "stream_id": stream.id, "topic_name": data["topic"], @@ -564,7 +564,7 @@ class UnmutedTopicsTests(ZulipTestCase): ) ) # Verify if events are sent properly - user_topic_event: Dict[str, Any] = { + user_topic_event: dict[str, Any] = { "type": "user_topic", "stream_id": stream.id, "topic_name": "Verona3", @@ -630,7 +630,7 @@ class UnmutedTopicsTests(ZulipTestCase): ) ) # Verify if events are sent properly - user_topic_event: Dict[str, Any] = { + user_topic_event: dict[str, Any] = { "type": "user_topic", "stream_id": stream.id, "topic_name": data["topic"], @@ -1042,7 +1042,7 @@ class AutomaticallyFollowTopicsTests(ZulipTestCase): # Aaron participates in the poll. DON'T automatically follow the topic. message = self.get_last_message() - def participate_in_poll(user: UserProfile, data: Dict[str, object]) -> None: + def participate_in_poll(user: UserProfile, data: dict[str, object]) -> None: content = orjson.dumps(data).decode() payload = dict( message_id=message.id, @@ -1104,7 +1104,7 @@ class AutomaticallyFollowTopicsTests(ZulipTestCase): # Aaron edits the todo list. DON'T automatically follow the topic. message = self.get_last_message() - def edit_todo_list(user: UserProfile, data: Dict[str, object]) -> None: + def edit_todo_list(user: UserProfile, data: dict[str, object]) -> None: content = orjson.dumps(data).decode() payload = dict( message_id=message.id, @@ -1413,7 +1413,7 @@ class AutomaticallyUnmuteTopicsTests(ZulipTestCase): # Aaron participates in the poll. DON'T automatically unmute the topic. message = self.get_last_message() - def participate_in_poll(user: UserProfile, data: Dict[str, object]) -> None: + def participate_in_poll(user: UserProfile, data: dict[str, object]) -> None: content = orjson.dumps(data).decode() payload = dict( message_id=message.id, @@ -1480,7 +1480,7 @@ class AutomaticallyUnmuteTopicsTests(ZulipTestCase): # Aaron edits the todo list. DON'T automatically unmute the topic. message = self.get_last_message() - def edit_todo_list(user: UserProfile, data: Dict[str, object]) -> None: + def edit_todo_list(user: UserProfile, data: dict[str, object]) -> None: content = orjson.dumps(data).decode() payload = dict( message_id=message.id, diff --git a/zerver/tests/test_users.py b/zerver/tests/test_users.py index f59eb4c78f..8cda6ebf93 100644 --- a/zerver/tests/test_users.py +++ b/zerver/tests/test_users.py @@ -1,6 +1,6 @@ from datetime import timedelta from email.headerregistry import Address -from typing import Any, Dict, Iterable, List, Optional, TypeVar, Union +from typing import Any, Iterable, Optional, TypeVar, Union from unittest import mock import orjson @@ -91,7 +91,7 @@ K = TypeVar("K") V = TypeVar("V") -def find_dict(lst: Iterable[Dict[K, V]], k: K, v: V) -> Dict[K, V]: +def find_dict(lst: Iterable[dict[K, V]], k: K, v: V) -> dict[K, V]: for dct in lst: if dct[k] == v: return dct @@ -797,7 +797,7 @@ class PermissionTest(ZulipTestCase): empty_profile_data = [] for field_name in fields: field = CustomProfileField.objects.get(name=field_name, realm=realm) - value: Union[str, None, List[Any]] = "" + value: Union[str, None, list[Any]] = "" if field.field_type == CustomProfileField.USER: value = [] empty_profile_data.append( @@ -1232,7 +1232,7 @@ class UserProfileTest(ZulipTestCase): def test_get_accounts_for_email(self) -> None: reset_email_visibility_to_everyone_in_zulip_realm() - def check_account_present_in_accounts(user: UserProfile, accounts: List[Account]) -> None: + def check_account_present_in_accounts(user: UserProfile, accounts: list[Account]) -> None: for account in accounts: realm = user.realm if ( @@ -1425,7 +1425,7 @@ class UserProfileTest(ZulipTestCase): get_user_by_id_in_realm_including_cross_realm(hamlet.id, None) def test_cross_realm_dicts(self) -> None: - def user_row(email: str) -> Dict[str, object]: + def user_row(email: str) -> dict[str, object]: user = UserProfile.objects.get(email=email) avatar_url = get_avatar_field( user_id=user.id, diff --git a/zerver/tests/test_validators.py b/zerver/tests/test_validators.py index 6729d34dde..32f1cd7be4 100644 --- a/zerver/tests/test_validators.py +++ b/zerver/tests/test_validators.py @@ -1,5 +1,5 @@ import re -from typing import Any, Dict, List, Tuple +from typing import Any from django.conf import settings from django.core.exceptions import ValidationError @@ -174,7 +174,7 @@ class ValidatorTestCase(ZulipTestCase): check_list(check_string, length=2)("x", x) def test_check_dict(self) -> None: - keys: List[Tuple[str, Validator[object]]] = [ + keys: list[tuple[str, Validator[object]]] = [ ("names", check_list(check_string)), ("city", check_string), ] @@ -264,7 +264,7 @@ class ValidatorTestCase(ZulipTestCase): # There might be situations where we want deep # validation, but the error message should be customized. # This is an example. - def check_person(val: object) -> Dict[str, object]: + def check_person(val: object) -> dict[str, object]: try: return check_dict( [ diff --git a/zerver/tests/test_webhooks_common.py b/zerver/tests/test_webhooks_common.py index 656619430a..3fe5b30b8c 100644 --- a/zerver/tests/test_webhooks_common.py +++ b/zerver/tests/test_webhooks_common.py @@ -1,5 +1,4 @@ from types import SimpleNamespace -from typing import Dict from unittest.mock import MagicMock, patch from django.http import HttpRequest @@ -104,7 +103,7 @@ class WebhooksCommonTestCase(ZulipTestCase): @patch("zerver.lib.webhooks.common.importlib.import_module") def test_get_fixture_http_headers_for_success(self, import_module_mock: MagicMock) -> None: - def fixture_to_headers(fixture_name: str) -> Dict[str, str]: + def fixture_to_headers(fixture_name: str) -> dict[str, str]: # A sample function which would normally perform some # extra operations before returning a dictionary # corresponding to the fixture name passed. For this test, diff --git a/zerver/tests/test_widgets.py b/zerver/tests/test_widgets.py index 39990d2b41..ff0786f96c 100644 --- a/zerver/tests/test_widgets.py +++ b/zerver/tests/test_widgets.py @@ -1,5 +1,5 @@ import re -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any import orjson from django.core.exceptions import ValidationError @@ -29,7 +29,7 @@ class WidgetContentTestCase(ZulipTestCase): assert_error(dict(widget_type="bogus", extra_data={}), "unknown widget type: bogus") - extra_data: Dict[str, Any] = {} + extra_data: dict[str, Any] = {} obj = dict(widget_type="zform", extra_data=extra_data) assert_error(obj, "zform is missing type field") @@ -377,7 +377,7 @@ class WidgetContentTestCase(ZulipTestCase): message = self.get_last_message() - def post(sender: UserProfile, data: Dict[str, object]) -> "TestHttpResponse": + def post(sender: UserProfile, data: dict[str, object]) -> "TestHttpResponse": payload = dict( message_id=message.id, msg_type="widget", content=orjson.dumps(data).decode() ) @@ -406,7 +406,7 @@ class WidgetContentTestCase(ZulipTestCase): message = self.get_last_message() - def post(sender: UserProfile, data: Dict[str, object]) -> "TestHttpResponse": + def post(sender: UserProfile, data: dict[str, object]) -> "TestHttpResponse": payload = dict( message_id=message.id, msg_type="widget", content=orjson.dumps(data).decode() ) @@ -465,7 +465,7 @@ class WidgetContentTestCase(ZulipTestCase): assert_error('{"type": "new_option", "idx": 1001, "option": "pizza"}', "too large") assert_error('{"type": "new_option", "idx": "bogus", "option": "maybe"}', "not an int") - def assert_success(data: Dict[str, object]) -> None: + def assert_success(data: dict[str, object]) -> None: content = orjson.dumps(data).decode() result = post_submessage(content) self.assert_json_success(result) @@ -525,7 +525,7 @@ class WidgetContentTestCase(ZulipTestCase): assert_error('{"type": "strike"}', "key is missing") assert_error('{"type": "strike", "key": 999}', 'data["key"] is not a string') - def assert_success(data: Dict[str, object]) -> None: + def assert_success(data: dict[str, object]) -> None: content = orjson.dumps(data).decode() result = post_submessage(content) self.assert_json_success(result) diff --git a/zerver/tornado/descriptors.py b/zerver/tornado/descriptors.py index 4a91fc0f1c..532b62d08d 100644 --- a/zerver/tornado/descriptors.py +++ b/zerver/tornado/descriptors.py @@ -1,11 +1,11 @@ -from typing import TYPE_CHECKING, Dict, Optional +from typing import TYPE_CHECKING, Optional from django.conf import settings if TYPE_CHECKING: from zerver.tornado.event_queue import ClientDescriptor -descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {} +descriptors_by_handler_id: dict[int, "ClientDescriptor"] = {} def get_descriptor_by_handler_id(handler_id: int) -> Optional["ClientDescriptor"]: diff --git a/zerver/tornado/django_api.py b/zerver/tornado/django_api.py index a69f5f75ea..c5bc19e776 100644 --- a/zerver/tornado/django_api.py +++ b/zerver/tornado/django_api.py @@ -1,6 +1,6 @@ from collections import defaultdict from functools import lru_cache -from typing import Any, Dict, Iterable, List, Mapping, Optional, Sequence, Tuple, Union +from typing import Any, Iterable, Mapping, Optional, Sequence, Union from urllib.parse import urlsplit import orjson @@ -37,9 +37,9 @@ class TornadoAdapter(HTTPAdapter): self, request: PreparedRequest, stream: bool = False, - timeout: Union[None, float, Tuple[float, float], Tuple[float, None]] = 0.5, + timeout: Union[None, float, tuple[float, float], tuple[float, None]] = 0.5, verify: Union[bool, str] = True, - cert: Union[None, bytes, str, Tuple[Union[bytes, str], Union[bytes, str]]] = None, + cert: Union[None, bytes, str, tuple[Union[bytes, str], Union[bytes, str]]] = None, proxies: Optional[Mapping[str, str]] = None, ) -> Response: # Don't talk to Tornado through proxies, which only allow @@ -123,12 +123,12 @@ def request_event_queue( def get_user_events( user_profile: UserProfile, queue_id: str, last_event_id: int -) -> List[Dict[str, Any]]: +) -> list[dict[str, Any]]: if not settings.USING_TORNADO: return [] tornado_url = get_tornado_url(get_user_tornado_port(user_profile)) - post_data: Dict[str, Any] = { + post_data: dict[str, Any] = { "queue_id": queue_id, "last_event_id": last_event_id, "dont_block": "true", diff --git a/zerver/tornado/event_queue.py b/zerver/tornado/event_queue.py index 637d8ecd79..7b46d4595d 100644 --- a/zerver/tornado/event_queue.py +++ b/zerver/tornado/event_queue.py @@ -15,17 +15,12 @@ from typing import ( Any, Callable, Collection, - Deque, - Dict, Iterable, - List, Literal, Mapping, MutableMapping, Optional, Sequence, - Set, - Tuple, TypedDict, Union, cast, @@ -71,7 +66,7 @@ MAX_QUEUE_TIMEOUT_SECS = 7 * 24 * 60 * 60 HEARTBEAT_MIN_FREQ_SECS = 45 -def create_heartbeat_event() -> Dict[str, str]: +def create_heartbeat_event() -> dict[str, str]: return dict(type="heartbeat") @@ -132,7 +127,7 @@ class ClientDescriptor: lifespan_secs = DEFAULT_EVENT_QUEUE_TIMEOUT_SECS self.queue_timeout = min(lifespan_secs, MAX_QUEUE_TIMEOUT_SECS) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: # If you add a new key to this dict, make sure you add appropriate # migration code in from_dict or load_event_queues to account for # loading event queues that lack that key. @@ -319,14 +314,14 @@ class EventQueue: # When extending this list of properties, one must be sure to # update to_dict and from_dict. - self.queue: Deque[Dict[str, Any]] = deque() + self.queue: deque[dict[str, Any]] = deque() self.next_event_id: int = 0 # will only be None for migration from old versions self.newest_pruned_id: Optional[int] = -1 self.id: str = id - self.virtual_events: Dict[str, Dict[str, Any]] = {} + self.virtual_events: dict[str, dict[str, Any]] = {} - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: # If you add a new key to this dict, make sure you add appropriate # migration code in from_dict or load_event_queues to account for # loading event queues that lack that key. @@ -341,7 +336,7 @@ class EventQueue: return d @classmethod - def from_dict(cls, d: Dict[str, Any]) -> "EventQueue": + def from_dict(cls, d: dict[str, Any]) -> "EventQueue": ret = cls(d["id"]) ret.next_event_id = d["next_event_id"] ret.newest_pruned_id = d.get("newest_pruned_id") @@ -394,7 +389,7 @@ class EventQueue: # Note that pop ignores virtual events. This is fine in our # current usage since virtual events should always be resolved to # a real event before being given to users. - def pop(self) -> Dict[str, Any]: + def pop(self) -> dict[str, Any]: return self.queue.popleft() def empty(self) -> bool: @@ -406,9 +401,9 @@ class EventQueue: self.newest_pruned_id = self.queue[0]["id"] self.pop() - def contents(self, include_internal_data: bool = False) -> List[Dict[str, Any]]: - contents: List[Dict[str, Any]] = [] - virtual_id_map: Dict[str, Dict[str, Any]] = {} + def contents(self, include_internal_data: bool = False) -> list[dict[str, Any]]: + contents: list[dict[str, Any]] = [] + virtual_id_map: dict[str, dict[str, Any]] = {} for event_type in self.virtual_events: virtual_id_map[self.virtual_events[event_type]["id"]] = self.virtual_events[event_type] virtual_ids = sorted(virtual_id_map.keys()) @@ -433,7 +428,7 @@ class EventQueue: return prune_internal_data(contents) -def prune_internal_data(events: List[Dict[str, Any]]) -> List[Dict[str, Any]]: +def prune_internal_data(events: list[dict[str, Any]]) -> list[dict[str, Any]]: """Prunes the internal_data data structures, which are not intended to be exposed to API clients. """ @@ -447,21 +442,21 @@ def prune_internal_data(events: List[Dict[str, Any]]) -> List[Dict[str, Any]]: # Queue-ids which still need to be sent a web_reload_client event. # This is treated as an ordered set, which is sorted by realm-id when # loaded from disk. -web_reload_clients: Dict[str, Literal[True]] = {} +web_reload_clients: dict[str, Literal[True]] = {} # maps queue ids to client descriptors -clients: Dict[str, ClientDescriptor] = {} +clients: dict[str, ClientDescriptor] = {} # maps user id to list of client descriptors -user_clients: Dict[int, List[ClientDescriptor]] = {} +user_clients: dict[int, list[ClientDescriptor]] = {} # maps realm id to list of client descriptors with all_public_streams=True -realm_clients_all_streams: Dict[int, List[ClientDescriptor]] = {} +realm_clients_all_streams: dict[int, list[ClientDescriptor]] = {} # list of registered gc hooks. # each one will be called with a user profile id, queue, and bool # last_for_client that is true if this is the last queue pertaining # to this user_profile_id # that is about to be deleted -gc_hooks: List[Callable[[int, ClientDescriptor, bool], None]] = [] +gc_hooks: list[Callable[[int, ClientDescriptor, bool], None]] = [] def clear_client_event_queues_for_testing() -> None: @@ -492,11 +487,11 @@ def access_client_descriptor(user_id: int, queue_id: str) -> ClientDescriptor: raise BadEventQueueIdError(queue_id) -def get_client_descriptors_for_user(user_profile_id: int) -> List[ClientDescriptor]: +def get_client_descriptors_for_user(user_profile_id: int) -> list[ClientDescriptor]: return user_clients.get(user_profile_id, []) -def get_client_descriptors_for_realm_all_streams(realm_id: int) -> List[ClientDescriptor]: +def get_client_descriptors_for_realm_all_streams(realm_id: int) -> list[ClientDescriptor]: return realm_clients_all_streams.get(realm_id, []) @@ -519,7 +514,7 @@ def do_gc_event_queues( to_remove: AbstractSet[str], affected_users: AbstractSet[int], affected_realms: AbstractSet[int] ) -> None: def filter_client_dict( - client_dict: MutableMapping[int, List[ClientDescriptor]], key: int + client_dict: MutableMapping[int, list[ClientDescriptor]], key: int ) -> None: if key not in client_dict: return @@ -552,9 +547,9 @@ def gc_event_queues(port: int) -> None: # We cannot use perf_counter here, since we store and compare UNIX # timestamps to it in the queues. start = time.time() - to_remove: Set[str] = set() - affected_users: Set[int] = set() - affected_realms: Set[int] = set() + to_remove: set[str] = set() + affected_users: set[int] = set() + affected_realms: set[int] = set() for id, client in clients.items(): if client.expired(start): to_remove.add(id) @@ -643,7 +638,7 @@ def load_event_queues(port: int) -> None: def send_restart_events() -> None: - event: Dict[str, Any] = dict( + event: dict[str, Any] = dict( type="restart", zulip_version=ZULIP_VERSION, zulip_merge_base=ZULIP_MERGE_BASE, @@ -669,7 +664,7 @@ def mark_clients_to_reload(queue_ids: Iterable[str]) -> None: def send_web_reload_client_events(immediate: bool = False, count: Optional[int] = None) -> int: - event: Dict[str, Any] = dict( + event: dict[str, Any] = dict( type="web_reload_client", immediate=immediate, ) @@ -711,7 +706,7 @@ def fetch_events( new_queue_data: Optional[MutableMapping[str, Any]], client_type_name: str, handler_id: int, -) -> Dict[str, Any]: +) -> dict[str, Any]: try: was_connected = False orig_queue_id = queue_id @@ -749,7 +744,7 @@ def fetch_events( was_connected = client.finish_current_handler() if not client.event_queue.empty() or dont_block: - response: Dict[str, Any] = dict( + response: dict[str, Any] = dict( events=client.event_queue.contents(), ) if orig_queue_id is None: @@ -780,7 +775,7 @@ def fetch_events( return dict(type="async") -def build_offline_notification(user_profile_id: int, message_id: int) -> Dict[str, Any]: +def build_offline_notification(user_profile_id: int, message_id: int) -> dict[str, Any]: return { "user_profile_id": user_profile_id, "message_id": message_id, @@ -940,8 +935,8 @@ def maybe_enqueue_notifications( message_id: int, mentioned_user_group_id: Optional[int], idle: bool, - already_notified: Dict[str, bool], -) -> Dict[str, bool]: + already_notified: dict[str, bool], +) -> dict[str, bool]: """This function has a complete unit test suite in `test_enqueue_notifications` that should be expanded as we add more features here. @@ -949,7 +944,7 @@ def maybe_enqueue_notifications( See https://zulip.readthedocs.io/en/latest/subsystems/notifications.html for high-level design documentation. """ - notified: Dict[str, bool] = {} + notified: dict[str, bool] = {} if user_notifications_data.is_push_notifiable(acting_user_id, idle): notice = build_offline_notification(user_notifications_data.user_id, message_id) @@ -993,7 +988,7 @@ class ClientInfo(TypedDict): def get_client_info_for_message_event( event_template: Mapping[str, Any], users: Iterable[Mapping[str, Any]] -) -> Dict[str, ClientInfo]: +) -> dict[str, ClientInfo]: """ Return client info for all the clients interested in a message. This basically includes clients for users who are recipients @@ -1001,7 +996,7 @@ def get_client_info_for_message_event( to all streams, plus users who may be mentioned, etc. """ - send_to_clients: Dict[str, ClientInfo] = {} + send_to_clients: dict[str, ClientInfo] = {} sender_queue_id: Optional[str] = event_template.get("sender_queue_id", None) @@ -1100,7 +1095,7 @@ def process_message_event( user_ids_without_access_to_sender = event_template.get("user_ids_without_access_to_sender", []) realm_host = event_template.get("realm_host", "") - wide_dict: Dict[str, Any] = event_template["message_dict"] + wide_dict: dict[str, Any] = event_template["message_dict"] # Temporary transitional code: Zulip servers that have message # events in their event queues and upgrade to the new version @@ -1119,7 +1114,7 @@ def process_message_event( @cache def get_client_payload( apply_markdown: bool, client_gravatar: bool, can_access_sender: bool - ) -> Dict[str, Any]: + ) -> dict[str, Any]: return MessageDict.finalize_payload( wide_dict, apply_markdown=apply_markdown, @@ -1129,7 +1124,7 @@ def process_message_event( ) # Extra user-specific data to include - extra_user_data: Dict[int, Any] = {} + extra_user_data: dict[int, Any] = {} for user_data in users: user_profile_id: int = user_data["id"] @@ -1211,7 +1206,7 @@ def process_message_event( message_dict = message_dict.copy() message_dict["invite_only_stream"] = True - user_event: Dict[str, Any] = dict(type="message", message=message_dict, flags=flags) + user_event: dict[str, Any] = dict(type="message", message=message_dict, flags=flags) if extra_data is not None: user_event.update(extra_data) @@ -1530,8 +1525,8 @@ def maybe_enqueue_notifications_for_message_update( def reformat_legacy_send_message_event( - event: Mapping[str, Any], users: Union[List[int], List[Mapping[str, Any]]] -) -> Tuple[MutableMapping[str, Any], Collection[MutableMapping[str, Any]]]: + event: Mapping[str, Any], users: Union[list[int], list[Mapping[str, Any]]] +) -> tuple[MutableMapping[str, Any], Collection[MutableMapping[str, Any]]]: # do_send_messages used to send events with users in dict format, with the # dict containing the user_id and other data. We later trimmed down the user # data to only contain the user_id and the usermessage flags, and put everything @@ -1539,7 +1534,7 @@ def reformat_legacy_send_message_event( # This block handles any old-format events still in the queue during upgrade. modern_event = cast(MutableMapping[str, Any], event) - user_dicts = cast(List[MutableMapping[str, Any]], users) + user_dicts = cast(list[MutableMapping[str, Any]], users) # Back-calculate the older all-booleans format data in the `users` dicts into the newer # all-lists format, and attach the lists to the `event` object. @@ -1575,7 +1570,7 @@ def reformat_legacy_send_message_event( def process_notification(notice: Mapping[str, Any]) -> None: event: Mapping[str, Any] = notice["event"] - users: Union[List[int], List[Mapping[str, Any]]] = notice["users"] + users: Union[list[int], list[Mapping[str, Any]]] = notice["users"] start_time = time.perf_counter() if event["type"] == "message": @@ -1585,9 +1580,9 @@ def process_notification(notice: Mapping[str, Any]) -> None: modern_event, user_dicts = reformat_legacy_send_message_event(event, users) process_message_event(modern_event, user_dicts) else: - process_message_event(event, cast(List[Mapping[str, Any]], users)) + process_message_event(event, cast(list[Mapping[str, Any]], users)) elif event["type"] == "update_message": - process_message_update_event(event, cast(List[Mapping[str, Any]], users)) + process_message_update_event(event, cast(list[Mapping[str, Any]], users)) elif event["type"] == "delete_message": if len(users) > 0 and isinstance(users[0], dict): # do_delete_messages used to send events with users in @@ -1597,16 +1592,16 @@ def process_notification(notice: Mapping[str, Any]) -> None: # # TODO/compatibility: Remove this block once you can no # longer directly upgrade directly from 4.x to main. - user_ids: List[int] = [user["id"] for user in cast(List[Mapping[str, Any]], users)] + user_ids: list[int] = [user["id"] for user in cast(list[Mapping[str, Any]], users)] else: - user_ids = cast(List[int], users) + user_ids = cast(list[int], users) process_deletion_event(event, user_ids) elif event["type"] == "presence": - process_presence_event(event, cast(List[int], users)) + process_presence_event(event, cast(list[int], users)) elif event["type"] == "custom_profile_fields": - process_custom_profile_fields_event(event, cast(List[int], users)) + process_custom_profile_fields_event(event, cast(list[int], users)) elif event["type"] == "realm_user" and event["op"] == "add": - process_realm_user_add_event(event, cast(List[int], users)) + process_realm_user_add_event(event, cast(list[int], users)) elif event["type"] == "cleanup_queue": # cleanup_event_queue may generate this event to forward cleanup # requests to the right shard. @@ -1620,7 +1615,7 @@ def process_notification(notice: Mapping[str, Any]) -> None: else: client.cleanup() else: - process_event(event, cast(List[int], users)) + process_event(event, cast(list[int], users)) logging.debug( "Tornado: Event %s for %s users took %sms", event["type"], @@ -1629,15 +1624,15 @@ def process_notification(notice: Mapping[str, Any]) -> None: ) -def get_wrapped_process_notification(queue_name: str) -> Callable[[List[Dict[str, Any]]], None]: - def failure_processor(notice: Dict[str, Any]) -> None: +def get_wrapped_process_notification(queue_name: str) -> Callable[[list[dict[str, Any]]], None]: + def failure_processor(notice: dict[str, Any]) -> None: logging.error( "Maximum retries exceeded for Tornado notice:%s\nStack trace:\n%s\n", notice, traceback.format_exc(), ) - def wrapped_process_notification(notices: List[Dict[str, Any]]) -> None: + def wrapped_process_notification(notices: list[dict[str, Any]]) -> None: for notice in notices: try: process_notification(notice) diff --git a/zerver/tornado/handlers.py b/zerver/tornado/handlers.py index 5248c06e25..fb25c82b7f 100644 --- a/zerver/tornado/handlers.py +++ b/zerver/tornado/handlers.py @@ -1,6 +1,6 @@ import logging from contextlib import suppress -from typing import Any, Collection, Dict, List, Optional +from typing import Any, Collection, Optional from urllib.parse import unquote import tornado.web @@ -20,7 +20,7 @@ from zerver.lib.response import AsynchronousResponse, json_response from zerver.tornado.descriptors import get_descriptor_by_handler_id current_handler_id = 0 -handlers: Dict[int, "AsyncDjangoHandler"] = {} +handlers: dict[int, "AsyncDjangoHandler"] = {} fake_wsgi_container = WSGIContainer(lambda environ, start_response: []) @@ -45,7 +45,7 @@ def handler_stats_string() -> str: return f"{len(handlers)} handlers, latest ID {current_handler_id}" -def finish_handler(handler_id: int, event_queue_id: str, contents: List[Dict[str, Any]]) -> None: +def finish_handler(handler_id: int, event_queue_id: str, contents: list[dict[str, Any]]) -> None: try: # We do the import during runtime to avoid cyclic dependency # with zerver.lib.request @@ -162,7 +162,7 @@ class AsyncDjangoHandler(tornado.web.RequestHandler): # Copy any cookies if not hasattr(self, "_new_cookies"): - self._new_cookies: List[http.cookie.SimpleCookie] = [] + self._new_cookies: list[http.cookie.SimpleCookie] = [] self._new_cookies.append(response.cookies) # Copy the response content @@ -228,7 +228,7 @@ class AsyncDjangoHandler(tornado.web.RequestHandler): if client_descriptor is not None: client_descriptor.disconnect_handler(client_closed=True) - async def zulip_finish(self, result_dict: Dict[str, Any], old_request: HttpRequest) -> None: + async def zulip_finish(self, result_dict: dict[str, Any], old_request: HttpRequest) -> None: # Function called when we want to break a long-polled # get_events request and return a response to the client. diff --git a/zerver/tornado/ioloop_logging.py b/zerver/tornado/ioloop_logging.py index de6ac93cd8..40b6959ad1 100644 --- a/zerver/tornado/ioloop_logging.py +++ b/zerver/tornado/ioloop_logging.py @@ -1,5 +1,3 @@ -from typing import Dict - # This is used for a somewhat hacky way of passing the port number # into this early-initialized module. -logging_data: Dict[str, str] = {} +logging_data: dict[str, str] = {} diff --git a/zerver/tornado/sharding.py b/zerver/tornado/sharding.py index f876e06d9c..5792995e38 100644 --- a/zerver/tornado/sharding.py +++ b/zerver/tornado/sharding.py @@ -1,14 +1,14 @@ import json import os import re -from typing import Dict, List, Pattern, Tuple, Union +from typing import Pattern, Union from django.conf import settings from zerver.models import Realm, UserProfile -shard_map: Dict[str, Union[int, List[int]]] = {} -shard_regexes: List[Tuple[Pattern[str], Union[int, List[int]]]] = [] +shard_map: dict[str, Union[int, list[int]]] = {} +shard_regexes: list[tuple[Pattern[str], Union[int, list[int]]]] = [] if os.path.exists("/etc/zulip/sharding.json"): with open("/etc/zulip/sharding.json") as f: data = json.loads(f.read()) @@ -22,7 +22,7 @@ if os.path.exists("/etc/zulip/sharding.json"): ] -def get_realm_tornado_ports(realm: Realm) -> List[int]: +def get_realm_tornado_ports(realm: Realm) -> list[int]: if realm.host in shard_map: ports = shard_map[realm.host] return [ports] if isinstance(ports, int) else ports @@ -34,7 +34,7 @@ def get_realm_tornado_ports(realm: Realm) -> List[int]: return [settings.TORNADO_PORTS[0]] -def get_user_id_tornado_port(realm_ports: List[int], user_id: int) -> int: +def get_user_id_tornado_port(realm_ports: list[int], user_id: int) -> int: return realm_ports[user_id % len(realm_ports)] diff --git a/zerver/transaction_tests/test_user_groups.py b/zerver/transaction_tests/test_user_groups.py index caa592bd8f..df70d1ed3c 100644 --- a/zerver/transaction_tests/test_user_groups.py +++ b/zerver/transaction_tests/test_user_groups.py @@ -1,5 +1,5 @@ import threading -from typing import Any, List, Optional +from typing import Any, Optional from unittest import mock import orjson @@ -63,7 +63,7 @@ def dev_update_subgroups( class UserGroupRaceConditionTestCase(ZulipTransactionTestCase): - created_user_groups: List[UserGroup] = [] + created_user_groups: list[UserGroup] = [] counter = 0 CHAIN_LENGTH = 3 @@ -77,7 +77,7 @@ class UserGroupRaceConditionTestCase(ZulipTransactionTestCase): super().tearDown() - def create_user_group_chain(self, realm: Realm) -> List[NamedUserGroup]: + def create_user_group_chain(self, realm: Realm) -> list[NamedUserGroup]: """Build a user groups forming a chain through group-group memberships returning a list where each group is the supergroup of its subsequent group. """ @@ -101,7 +101,7 @@ class UserGroupRaceConditionTestCase(ZulipTransactionTestCase): class RacingThread(threading.Thread): def __init__( self, - subgroup_ids: List[int], + subgroup_ids: list[int], supergroup_id: int, ) -> None: threading.Thread.__init__(self) diff --git a/zerver/views/alert_words.py b/zerver/views/alert_words.py index 63cb97119c..df51d0799c 100644 --- a/zerver/views/alert_words.py +++ b/zerver/views/alert_words.py @@ -1,5 +1,3 @@ -from typing import List - from django.http import HttpRequest, HttpResponse from pydantic import Json, StringConstraints from typing_extensions import Annotated @@ -15,7 +13,7 @@ def list_alert_words(request: HttpRequest, user_profile: UserProfile) -> HttpRes return json_success(request, data={"alert_words": user_alert_words(user_profile)}) -def clean_alert_words(alert_words: List[str]) -> List[str]: +def clean_alert_words(alert_words: list[str]) -> list[str]: alert_words = [w.strip() for w in alert_words] return [w for w in alert_words if w != ""] @@ -25,7 +23,7 @@ def add_alert_words( request: HttpRequest, user_profile: UserProfile, *, - alert_words: Json[List[Annotated[str, StringConstraints(max_length=100)]]], + alert_words: Json[list[Annotated[str, StringConstraints(max_length=100)]]], ) -> HttpResponse: do_add_alert_words(user_profile, clean_alert_words(alert_words)) return json_success(request, data={"alert_words": user_alert_words(user_profile)}) @@ -36,7 +34,7 @@ def remove_alert_words( request: HttpRequest, user_profile: UserProfile, *, - alert_words: Json[List[str]], + alert_words: Json[list[str]], ) -> HttpResponse: do_remove_alert_words(user_profile, alert_words) return json_success(request, data={"alert_words": user_alert_words(user_profile)}) diff --git a/zerver/views/auth.py b/zerver/views/auth.py index 268f7831bb..e2e0da27cc 100644 --- a/zerver/views/auth.py +++ b/zerver/views/auth.py @@ -1,7 +1,7 @@ import logging import secrets from functools import wraps -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Mapping, Optional, Tuple, cast +from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, cast from urllib.parse import urlencode, urljoin import jwt @@ -102,7 +102,7 @@ if TYPE_CHECKING: from django.http.request import _ImmutableQueryDict ParamT = ParamSpec("ParamT") -ExtraContext: TypeAlias = Optional[Dict[str, Any]] +ExtraContext: TypeAlias = Optional[dict[str, Any]] EXPIRABLE_SESSION_VAR_DEFAULT_EXPIRY_SECS = 3600 @@ -161,7 +161,7 @@ def maybe_send_to_registration( is_signup: bool = False, multiuse_object_key: str = "", full_name_validated: bool = False, - params_to_store_in_authenticated_session: Optional[Dict[str, str]] = None, + params_to_store_in_authenticated_session: Optional[dict[str, str]] = None, ) -> HttpResponse: """Given a successful authentication for an email address (i.e. we've confirmed the user controls the email address) that does not @@ -323,7 +323,7 @@ def register_remote_user(request: HttpRequest, result: ExternalAuthResult) -> Ht # We have verified the user controls an email address, but # there's no associated Zulip user account. Consider sending # the request to registration. - kwargs: Dict[str, Any] = dict(result.data_dict) + kwargs: dict[str, Any] = dict(result.data_dict) # maybe_send_to_registration doesn't take these arguments, so delete them. # These are the kwargs taken by maybe_send_to_registration. Remove anything @@ -409,7 +409,7 @@ def finish_desktop_flow( request: HttpRequest, user_profile: UserProfile, otp: str, - params_to_store_in_authenticated_session: Optional[Dict[str, str]] = None, + params_to_store_in_authenticated_session: Optional[dict[str, str]] = None, ) -> HttpResponse: """ The desktop otp flow returns to the app (through the clipboard) @@ -546,7 +546,7 @@ def remote_user_sso( @has_request_variables def get_email_and_realm_from_jwt_authentication_request( request: HttpRequest, json_web_token: str -) -> Tuple[str, Realm]: +) -> tuple[str, Realm]: realm = get_realm_from_request(request) if realm is None: raise InvalidSubdomainError @@ -671,7 +671,7 @@ def start_social_login( extra_arg: Optional[str] = None, ) -> HttpResponse: backend_url = reverse("social:begin", args=[backend]) - extra_url_params: Dict[str, str] = {} + extra_url_params: dict[str, str] = {} if backend == "saml": if not SAMLAuthBackend.check_config(): return config_error(request, "saml") @@ -707,7 +707,7 @@ def start_social_signup( extra_arg: Optional[str] = None, ) -> HttpResponse: backend_url = reverse("social:begin", args=[backend]) - extra_url_params: Dict[str, str] = {} + extra_url_params: dict[str, str] = {} if backend == "saml": if not SAMLAuthBackend.check_config(): return config_error(request, "saml") @@ -780,7 +780,7 @@ def redirect_to_deactivation_notice() -> HttpResponse: return HttpResponseRedirect(reverse(show_deactivation_notice)) -def update_login_page_context(request: HttpRequest, context: Dict[str, Any]) -> None: +def update_login_page_context(request: HttpRequest, context: dict[str, Any]) -> None: for key in ("email", "already_registered"): if key in request.GET: context[key] = request.GET[key] @@ -809,7 +809,7 @@ class TwoFactorLoginView(BaseTwoFactorLoginView): self.extra_context = extra_context super().__init__(*args, **kwargs) - def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context = super().get_context_data(**kwargs) if self.extra_context is not None: context.update(self.extra_context) @@ -823,7 +823,7 @@ class TwoFactorLoginView(BaseTwoFactorLoginView): ) return context - def done(self, form_list: List[Form], **kwargs: Any) -> HttpResponse: + def done(self, form_list: list[Form], **kwargs: Any) -> HttpResponse: """ Log in the user and redirect to the desired page. @@ -985,7 +985,7 @@ def process_api_key_fetch_authenticate_result( return api_key -def get_api_key_fetch_authenticate_failure(return_data: Dict[str, bool]) -> JsonableError: +def get_api_key_fetch_authenticate_failure(return_data: dict[str, bool]) -> JsonableError: if return_data.get("inactive_user"): return UserDeactivatedError() if return_data.get("inactive_realm"): @@ -1010,7 +1010,7 @@ def jwt_fetch_api_key( ) -> HttpResponse: remote_email, realm = get_email_and_realm_from_jwt_authentication_request(request, token) - return_data: Dict[str, bool] = {} + return_data: dict[str, bool] = {} user_profile = authenticate( username=remote_email, realm=realm, return_data=return_data, use_dummy_backend=True @@ -1022,7 +1022,7 @@ def jwt_fetch_api_key( api_key = process_api_key_fetch_authenticate_result(request, user_profile) - result: Dict[str, Any] = { + result: dict[str, Any] = { "api_key": api_key, "email": user_profile.delivery_email, } @@ -1047,7 +1047,7 @@ def jwt_fetch_api_key( def api_fetch_api_key( request: HttpRequest, username: str = REQ(), password: str = REQ() ) -> HttpResponse: - return_data: Dict[str, bool] = {} + return_data: dict[str, bool] = {} realm = get_realm_from_request(request) if realm is None: @@ -1073,7 +1073,7 @@ def api_fetch_api_key( ) -def get_auth_backends_data(request: HttpRequest) -> Dict[str, Any]: +def get_auth_backends_data(request: HttpRequest) -> dict[str, Any]: """Returns which authentication methods are enabled on the server""" subdomain = get_subdomain(request) try: diff --git a/zerver/views/custom_profile_fields.py b/zerver/views/custom_profile_fields.py index 605f4d1d1f..145d9126f9 100644 --- a/zerver/views/custom_profile_fields.py +++ b/zerver/views/custom_profile_fields.py @@ -1,4 +1,4 @@ -from typing import Annotated, List, Optional +from typing import Annotated, Optional import orjson from django.core.exceptions import ValidationError @@ -288,7 +288,7 @@ def reorder_realm_custom_profile_fields( request: HttpRequest, user_profile: UserProfile, *, - order: Json[List[int]], + order: Json[list[int]], ) -> HttpResponse: try_reorder_realm_custom_profile_fields(user_profile.realm, order) return json_success(request) @@ -300,7 +300,7 @@ def remove_user_custom_profile_data( request: HttpRequest, user_profile: UserProfile, *, - data: Json[List[int]], + data: Json[list[int]], ) -> HttpResponse: for field_id in data: check_remove_custom_profile_field_value(user_profile, field_id) @@ -313,7 +313,7 @@ def update_user_custom_profile_data( request: HttpRequest, user_profile: UserProfile, *, - data: Json[List[ProfileDataElementUpdateDict]], + data: Json[list[ProfileDataElementUpdateDict]], ) -> HttpResponse: validate_user_custom_profile_data(user_profile.realm.id, data) do_update_user_custom_profile_data_if_changed(user_profile, data) diff --git a/zerver/views/development/dev_login.py b/zerver/views/development/dev_login.py index 019067da97..92caec38f4 100644 --- a/zerver/views/development/dev_login.py +++ b/zerver/views/development/dev_login.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Optional +from typing import Any, Optional from django.conf import settings from django.contrib.auth import authenticate @@ -27,7 +27,7 @@ from zerver.views.errors import config_error from zproject.backends import dev_auth_enabled -def get_dev_users(realm: Optional[Realm] = None, extra_users_count: int = 10) -> List[UserProfile]: +def get_dev_users(realm: Optional[Realm] = None, extra_users_count: int = 10) -> list[UserProfile]: # Development environments usually have only a few users, but # it still makes sense to limit how many extra users we render to # support performance testing with DevAuthBackend. @@ -48,12 +48,12 @@ def get_dev_users(realm: Optional[Realm] = None, extra_users_count: int = 10) -> return users -def add_dev_login_context(realm: Optional[Realm], context: Dict[str, Any]) -> None: +def add_dev_login_context(realm: Optional[Realm], context: dict[str, Any]) -> None: users = get_dev_users(realm) context["current_realm"] = realm context["all_realms"] = Realm.objects.all() - def sort(lst: List[UserProfile]) -> List[UserProfile]: + def sort(lst: list[UserProfile]) -> list[UserProfile]: return sorted(lst, key=lambda u: u.delivery_email) context["direct_owners"] = sort([u for u in users if u.is_realm_owner]) @@ -123,7 +123,7 @@ def api_dev_fetch_api_key(request: HttpRequest, *, username: str) -> HttpRespons realm = get_realm_from_request(request) if realm is None: raise InvalidSubdomainError - return_data: Dict[str, bool] = {} + return_data: dict[str, bool] = {} user_profile = authenticate(dev_auth_username=username, realm=realm, return_data=return_data) if return_data.get("inactive_realm"): raise RealmDeactivatedError diff --git a/zerver/views/development/integrations.py b/zerver/views/development/integrations.py index 984ec80f27..3124183140 100644 --- a/zerver/views/development/integrations.py +++ b/zerver/views/development/integrations.py @@ -1,6 +1,6 @@ import os from contextlib import suppress -from typing import TYPE_CHECKING, Any, Dict, List, Optional +from typing import TYPE_CHECKING, Any, Optional import orjson from django.http import HttpRequest, HttpResponse @@ -23,7 +23,7 @@ if TYPE_CHECKING: ZULIP_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../") -def get_webhook_integrations() -> List[str]: +def get_webhook_integrations() -> list[str]: return [integration.name for integration in WEBHOOK_INTEGRATIONS] @@ -47,7 +47,7 @@ def dev_panel(request: HttpRequest) -> HttpResponse: def send_webhook_fixture_message( - url: str, body: str, is_json: bool, custom_headers: Dict[str, Any] + url: str, body: str, is_json: bool, custom_headers: dict[str, Any] ) -> "TestHttpResponse": client = Client() realm = get_realm("zulip") diff --git a/zerver/views/documentation.py b/zerver/views/documentation.py index 2ae8ea1d39..02d4c0980b 100644 --- a/zerver/views/documentation.py +++ b/zerver/views/documentation.py @@ -3,7 +3,7 @@ import random import re from collections import OrderedDict from dataclasses import dataclass -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable, Optional from django.conf import settings from django.http import HttpRequest, HttpResponse, HttpResponseNotFound @@ -41,7 +41,7 @@ class DocumentationArticle: endpoint_method: Optional[str] -def add_api_url_context(context: Dict[str, Any], request: HttpRequest) -> None: +def add_api_url_context(context: dict[str, Any], request: HttpRequest) -> None: context.update(zulip_default_context(request)) subdomain = get_subdomain(request) @@ -67,7 +67,7 @@ def add_api_url_context(context: Dict[str, Any], request: HttpRequest) -> None: class ApiURLView(TemplateView): @override - def get_context_data(self, **kwargs: Any) -> Dict[str, str]: + def get_context_data(self, **kwargs: Any) -> dict[str, str]: context = super().get_context_data(**kwargs) add_api_url_context(context, self.request) return context @@ -85,7 +85,7 @@ class MarkdownDirectoryView(ApiURLView): def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self._post_render_callbacks: List[Callable[[HttpResponse], Optional[HttpResponse]]] = [] + self._post_render_callbacks: list[Callable[[HttpResponse], Optional[HttpResponse]]] = [] def add_post_render_callback( self, callback: Callable[[HttpResponse], Optional[HttpResponse]] @@ -162,9 +162,9 @@ class MarkdownDirectoryView(ApiURLView): ) @override - def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: article = kwargs["article"] - context: Dict[str, Any] = super().get_context_data() + context: dict[str, Any] = super().get_context_data() documentation_article = self.get_path(article) context["article"] = documentation_article.article_path @@ -252,7 +252,7 @@ class MarkdownDirectoryView(ApiURLView): self.add_post_render_callback(update_description) # An "article" might require the api_url_context to be rendered - api_url_context: Dict[str, Any] = {} + api_url_context: dict[str, Any] = {} add_api_url_context(api_url_context, self.request) api_url_context["run_content_validators"] = True context["api_url_context"] = api_url_context @@ -313,7 +313,7 @@ class MarkdownDirectoryView(ApiURLView): return result -def add_integrations_context(context: Dict[str, Any]) -> None: +def add_integrations_context(context: dict[str, Any]) -> None: alphabetical_sorted_categories = OrderedDict(sorted(CATEGORIES.items())) alphabetical_sorted_integration = OrderedDict(sorted(INTEGRATIONS.items())) enabled_integrations_count = sum(v.is_enabled() for v in INTEGRATIONS.values()) @@ -325,7 +325,7 @@ def add_integrations_context(context: Dict[str, Any]) -> None: context["integrations_count_display"] = integrations_count_display -def add_integrations_open_graph_context(context: Dict[str, Any], request: HttpRequest) -> None: +def add_integrations_open_graph_context(context: dict[str, Any], request: HttpRequest) -> None: path_name = request.path.rstrip("/").split("/")[-1] description = ( "Zulip comes with over a hundred native integrations out of the box, " @@ -355,8 +355,8 @@ class IntegrationView(ApiURLView): template_name = "zerver/integrations/index.html" @override - def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: - context: Dict[str, Any] = super().get_context_data(**kwargs) + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: + context: dict[str, Any] = super().get_context_data(**kwargs) add_integrations_context(context) add_integrations_open_graph_context(context, self.request) add_google_analytics_context(context) @@ -374,7 +374,7 @@ def integration_doc(request: HttpRequest, *, integration_name: PathOnly[str]) -> except KeyError: return HttpResponseNotFound() - context: Dict[str, Any] = {} + context: dict[str, Any] = {} add_api_url_context(context, request) context["integration_name"] = integration.name diff --git a/zerver/views/drafts.py b/zerver/views/drafts.py index 6d46fa45d3..323dfd47af 100644 --- a/zerver/views/drafts.py +++ b/zerver/views/drafts.py @@ -1,5 +1,3 @@ -from typing import List - from django.http import HttpRequest, HttpResponse from pydantic import Json @@ -28,7 +26,7 @@ def create_drafts( request: HttpRequest, user_profile: UserProfile, *, - drafts: Json[List[DraftData]], + drafts: Json[list[DraftData]], ) -> HttpResponse: created_draft_objects = do_create_drafts(drafts, user_profile) draft_ids = [draft_object.id for draft_object in created_draft_objects] diff --git a/zerver/views/events_register.py b/zerver/views/events_register.py index 8b793a7e57..ad4eb5bba1 100644 --- a/zerver/views/events_register.py +++ b/zerver/views/events_register.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional, Sequence, Union +from typing import Optional, Sequence, Union from django.conf import settings from django.contrib.auth.models import AnonymousUser @@ -49,7 +49,7 @@ def events_register_backend( slim_presence: bool = REQ(default=False, json_validator=check_bool), all_public_streams: Optional[bool] = REQ(default=None, json_validator=check_bool), include_subscribers: bool = REQ(default=False, json_validator=check_bool), - client_capabilities: Optional[Dict[str, bool]] = REQ( + client_capabilities: Optional[dict[str, bool]] = REQ( json_validator=check_dict( [ # This field was accidentally made required when it was added in v2.0.0-781; diff --git a/zerver/views/home.py b/zerver/views/home.py index 76ccfb0672..f258d920fd 100644 --- a/zerver/views/home.py +++ b/zerver/views/home.py @@ -1,6 +1,6 @@ import logging import secrets -from typing import List, Optional, Tuple +from typing import Optional from django.conf import settings from django.http import HttpRequest, HttpResponse @@ -102,13 +102,13 @@ def accounts_accept_terms(request: HttpRequest) -> HttpResponse: def detect_narrowed_window( request: HttpRequest, user_profile: Optional[UserProfile] -) -> Tuple[List[NarrowTerm], Optional[Stream], Optional[str]]: +) -> tuple[list[NarrowTerm], Optional[Stream], Optional[str]]: """This function implements Zulip's support for a mini Zulip window that just handles messages from a single narrow""" if user_profile is None: return [], None, None - narrow: List[NarrowTerm] = [] + narrow: list[NarrowTerm] = [] narrow_stream = None narrow_topic_name = request.GET.get("topic") diff --git a/zerver/views/invite.py b/zerver/views/invite.py index ac19c825ef..7dfe8fce76 100644 --- a/zerver/views/invite.py +++ b/zerver/views/invite.py @@ -1,5 +1,5 @@ import re -from typing import List, Optional, Sequence, Set +from typing import Optional, Sequence from django.conf import settings from django.http import HttpRequest, HttpResponse @@ -61,7 +61,7 @@ def invite_users_backend( notify_referrer_on_join: bool = REQ( "notify_referrer_on_join", json_validator=check_bool, default=True ), - stream_ids: List[int] = REQ(json_validator=check_list(check_int)), + stream_ids: list[int] = REQ(json_validator=check_list(check_int)), include_realm_default_subscriptions: bool = REQ(json_validator=check_bool, default=False), ) -> HttpResponse: if not user_profile.can_invite_users_by_email(): @@ -83,7 +83,7 @@ def invite_users_backend( invitee_emails = get_invitee_emails_set(invitee_emails_raw) - streams: List[Stream] = [] + streams: list[Stream] = [] for stream_id in stream_ids: try: (stream, sub) = access_stream_by_id(user_profile, stream_id) @@ -122,7 +122,7 @@ def invite_users_backend( return json_success(request) -def get_invitee_emails_set(invitee_emails_raw: str) -> Set[str]: +def get_invitee_emails_set(invitee_emails_raw: str) -> set[str]: invitee_emails_list = set(re.split(r"[,\n]", invitee_emails_raw)) invitee_emails = set() for email in invitee_emails_list: diff --git a/zerver/views/message_edit.py b/zerver/views/message_edit.py index 254eeeb455..a243e23b6c 100644 --- a/zerver/views/message_edit.py +++ b/zerver/views/message_edit.py @@ -1,5 +1,5 @@ from datetime import timedelta -from typing import List, Literal, Optional, Union +from typing import Literal, Optional, Union import orjson from django.contrib.auth.models import AnonymousUser @@ -29,8 +29,8 @@ from zerver.models import Message, UserProfile def fill_edit_history_entries( - raw_edit_history: List[EditHistoryEvent], message: Message -) -> List[FormattedEditHistoryEvent]: + raw_edit_history: list[EditHistoryEvent], message: Message +) -> list[FormattedEditHistoryEvent]: """ This fills out the message edit history entries from the database to have the current topic + content as of that time, plus data on @@ -47,7 +47,7 @@ def fill_edit_history_entries( assert message.last_edit_time is not None assert datetime_to_timestamp(message.last_edit_time) == raw_edit_history[0]["timestamp"] - formatted_edit_history: List[FormattedEditHistoryEvent] = [] + formatted_edit_history: list[FormattedEditHistoryEvent] = [] for edit_history_event in raw_edit_history: formatted_entry: FormattedEditHistoryEvent = { "content": prev_content, diff --git a/zerver/views/message_fetch.py b/zerver/views/message_fetch.py index 7d85ef0566..b0044d0ea2 100644 --- a/zerver/views/message_fetch.py +++ b/zerver/views/message_fetch.py @@ -1,4 +1,4 @@ -from typing import Dict, Iterable, List, Optional, Tuple, Union +from typing import Iterable, Optional, Union from django.conf import settings from django.contrib.auth.models import AnonymousUser @@ -34,7 +34,7 @@ from zerver.models import UserMessage, UserProfile MAX_MESSAGES_PER_FETCH = 5000 -def highlight_string(text: str, locs: Iterable[Tuple[int, int]]) -> str: +def highlight_string(text: str, locs: Iterable[tuple[int, int]]) -> str: highlight_start = '' highlight_stop = "" pos = 0 @@ -74,9 +74,9 @@ def highlight_string(text: str, locs: Iterable[Tuple[int, int]]) -> str: def get_search_fields( rendered_content: str, topic_name: str, - content_matches: Iterable[Tuple[int, int]], - topic_matches: Iterable[Tuple[int, int]], -) -> Dict[str, str]: + content_matches: Iterable[tuple[int, int]], + topic_matches: Iterable[tuple[int, int]], +) -> dict[str, str]: return { "match_content": highlight_string(rendered_content, content_matches), MATCH_TOPIC: highlight_string(escape_html(topic_name), topic_matches), @@ -84,8 +84,8 @@ def get_search_fields( def clean_narrow_for_web_public_api( - narrow: Optional[List[NarrowParameter]], -) -> Optional[List[NarrowParameter]]: + narrow: Optional[list[NarrowParameter]], +) -> Optional[list[NarrowParameter]]: if narrow is None: return None @@ -108,7 +108,7 @@ def get_messages_backend( include_anchor: Json[bool] = True, num_before: Json[NonNegativeInt], num_after: Json[NonNegativeInt], - narrow: Json[Optional[List[NarrowParameter]]] = None, + narrow: Json[Optional[list[NarrowParameter]]] = None, use_first_unread_anchor_val: Annotated[ Json[bool], ApiParamConfig("use_first_unread_anchor") ] = False, @@ -226,8 +226,8 @@ def get_messages_backend( # rendered message dict before returning it. We attempt to # bulk-fetch rendered message dicts from remote cache using the # 'messages' list. - message_ids: List[int] = [] - user_message_flags: Dict[int, List[str]] = {} + message_ids: list[int] = [] + user_message_flags: dict[int, list[str]] = {} if is_web_public_query: # For spectators, we treat all historical messages as read. for row in rows: @@ -254,7 +254,7 @@ def get_messages_backend( user_message_flags[message_id] = UserMessage.flags_list_for_flags(flags) message_ids.append(message_id) - search_fields: Dict[int, Dict[str, str]] = {} + search_fields: dict[int, dict[str, str]] = {} if is_search: for row in rows: message_id = row[0] @@ -292,8 +292,8 @@ def messages_in_narrow_backend( request: HttpRequest, user_profile: UserProfile, *, - msg_ids: Json[List[int]], - narrow: Json[List[NarrowParameter]], + msg_ids: Json[list[int]], + narrow: Json[list[NarrowParameter]], ) -> HttpResponse: first_visible_message_id = get_first_visible_message_id(user_profile.realm) msg_ids = [message_id for message_id in msg_ids if message_id >= first_visible_message_id] diff --git a/zerver/views/message_flags.py b/zerver/views/message_flags.py index af354171da..fbb2950d03 100644 --- a/zerver/views/message_flags.py +++ b/zerver/views/message_flags.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Optional from django.http import HttpRequest, HttpResponse from django.utils.translation import gettext as _ @@ -42,7 +42,7 @@ def update_message_flags( request: HttpRequest, user_profile: UserProfile, *, - messages: Json[List[int]], + messages: Json[list[int]], operation: Annotated[str, ApiParamConfig("op")], flag: str, ) -> HttpResponse: @@ -77,7 +77,7 @@ def update_message_flags_for_narrow( include_anchor: Json[bool] = True, num_before: Json[NonNegativeInt], num_after: Json[NonNegativeInt], - narrow: Json[Optional[List[NarrowParameter]]], + narrow: Json[Optional[list[NarrowParameter]]], operation: Annotated[str, ApiParamConfig("op")], flag: str, ) -> HttpResponse: diff --git a/zerver/views/message_send.py b/zerver/views/message_send.py index 6e3fce7619..a38e1c8f19 100644 --- a/zerver/views/message_send.py +++ b/zerver/views/message_send.py @@ -1,5 +1,5 @@ from email.headerregistry import Address -from typing import Dict, Iterable, Optional, Sequence, Union, cast +from typing import Iterable, Optional, Sequence, Union, cast from django.core import validators from django.core.exceptions import ValidationError @@ -233,7 +233,7 @@ def send_message_backend( # automatically marked as read for yourself. read_by_sender = client.default_read_by_sender() - data: Dict[str, int] = {} + data: dict[str, int] = {} sent_message_result = check_send_message( sender, client, diff --git a/zerver/views/presence.py b/zerver/views/presence.py index b25cd3c6f3..6702836fa3 100644 --- a/zerver/views/presence.py +++ b/zerver/views/presence.py @@ -1,5 +1,5 @@ from datetime import timedelta -from typing import Any, Dict, Optional +from typing import Any, Optional from django.conf import settings from django.http import HttpRequest, HttpResponse @@ -170,7 +170,7 @@ def update_active_status_backend( update_user_presence(user_profile, client, timezone_now(), status_val, new_user_input) if ping_only: - ret: Dict[str, Any] = {} + ret: dict[str, Any] = {} else: ret = get_presence_response( user_profile, slim_presence, last_update_id_fetched_by_client=last_update_id diff --git a/zerver/views/realm.py b/zerver/views/realm.py index 60634f0bc4..f72bf29adf 100644 --- a/zerver/views/realm.py +++ b/zerver/views/realm.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Mapping, Optional, Union +from typing import Any, Mapping, Optional, Union from django.core.exceptions import ValidationError from django.db import transaction @@ -133,7 +133,7 @@ def update_realm( allow_edit_history: Optional[Json[bool]] = None, default_language: Optional[str] = None, waiting_period_threshold: Optional[Json[NonNegativeInt]] = None, - authentication_methods: Optional[Json[Dict[str, Any]]] = None, + authentication_methods: Optional[Json[dict[str, Any]]] = None, # Note: push_notifications_enabled and push_notifications_enabled_end_timestamp # are not offered here as it is maintained by the server, not via the API. new_stream_announcements_stream_id: Optional[Json[int]] = None, @@ -246,7 +246,7 @@ def update_realm( if can_access_all_users_group_id is not None: realm.can_enable_restricted_user_access_for_guests() - data: Dict[str, Any] = {} + data: dict[str, Any] = {} message_content_delete_limit_seconds: Optional[int] = None if message_content_delete_limit_seconds_raw is not None: diff --git a/zerver/views/realm_linkifiers.py b/zerver/views/realm_linkifiers.py index 5fa3eb9813..ae9bd4b4a7 100644 --- a/zerver/views/realm_linkifiers.py +++ b/zerver/views/realm_linkifiers.py @@ -1,5 +1,3 @@ -from typing import List - from django.core.exceptions import ValidationError from django.http import HttpRequest, HttpResponse from django.utils.translation import gettext as _ @@ -85,7 +83,7 @@ def update_linkifier( def reorder_linkifiers( request: HttpRequest, user_profile: UserProfile, - ordered_linkifier_ids: List[int] = REQ(json_validator=check_list(check_int)), + ordered_linkifier_ids: list[int] = REQ(json_validator=check_list(check_int)), ) -> HttpResponse: check_reorder_linkifiers(user_profile.realm, ordered_linkifier_ids, acting_user=user_profile) return json_success(request) diff --git a/zerver/views/registration.py b/zerver/views/registration.py index 4ca44e947e..417e36cae7 100644 --- a/zerver/views/registration.py +++ b/zerver/views/registration.py @@ -1,6 +1,6 @@ import logging from contextlib import suppress -from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union +from typing import Any, Iterable, Optional, Union from urllib.parse import urlencode, urljoin import orjson @@ -159,7 +159,7 @@ def get_prereg_key_and_redirect( def check_prereg_key( request: HttpRequest, confirmation_key: str -) -> Tuple[Union[PreregistrationUser, PreregistrationRealm], bool]: +) -> tuple[Union[PreregistrationUser, PreregistrationRealm], bool]: """ Checks if the Confirmation key is valid, returning the PreregistrationUser or PreregistrationRealm object in case of success and raising an appropriate @@ -536,7 +536,7 @@ def registration_helper( existing_user_profile = None user_profile: Optional[UserProfile] = None - return_data: Dict[str, bool] = {} + return_data: dict[str, bool] = {} if ldap_auth_enabled(realm): # If the user was authenticated using an external SSO # mechanism like Google or GitHub auth, then authentication @@ -1118,7 +1118,7 @@ def accounts_home_from_multiuse_invite(request: HttpRequest, confirmation_key: s def find_account(request: HttpRequest) -> HttpResponse: url = reverse("find_account") form = FindMyTeamForm() - emails: List[str] = [] + emails: list[str] = [] if request.method == "POST": form = FindMyTeamForm(request.POST) if form.is_valid(): @@ -1152,8 +1152,8 @@ def find_account(request: HttpRequest) -> HttpResponse: # one outgoing email per provided email address, with each # email listing all of the accounts that email address has # with the current Zulip server. - emails_account_found: Set[str] = set() - context: Dict[str, Dict[str, Any]] = {} + emails_account_found: set[str] = set() + context: dict[str, dict[str, Any]] = {} for user in user_profiles: key = user.delivery_email.lower() context.setdefault(key, {}) @@ -1192,7 +1192,7 @@ def find_account(request: HttpRequest) -> HttpResponse: ) emails_lower_with_account = {email.lower() for email in emails_account_found} - emails_without_account: Set[str] = { + emails_without_account: set[str] = { email for email in emails if email.lower() not in emails_lower_with_account } if emails_without_account: diff --git a/zerver/views/sentry.py b/zerver/views/sentry.py index ec8dcdd8f2..f21fb4f219 100644 --- a/zerver/views/sentry.py +++ b/zerver/views/sentry.py @@ -1,6 +1,5 @@ import logging from contextlib import suppress -from typing import Type from urllib.parse import urlsplit import orjson @@ -100,7 +99,7 @@ def sentry_tunnel( # Smokescreen as a CONNECT proxy, so failures from Smokescreen # failing to connect at the TCP level will report as # ProxyErrors. -def open_circuit_for(exc_type: Type[Exception], exc_value: Exception) -> bool: +def open_circuit_for(exc_type: type[Exception], exc_value: Exception) -> bool: if issubclass(exc_type, (ProxyError, Timeout)): return True if isinstance(exc_value, HTTPError): diff --git a/zerver/views/storage.py b/zerver/views/storage.py index 8988907ef6..6c3ca5ad5d 100644 --- a/zerver/views/storage.py +++ b/zerver/views/storage.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional +from typing import Optional from django.http import HttpRequest, HttpResponse from pydantic import Json @@ -21,7 +21,7 @@ def update_storage( request: HttpRequest, user_profile: UserProfile, *, - storage: Json[Dict[str, str]], + storage: Json[dict[str, str]], ) -> HttpResponse: try: set_bot_storage(user_profile, list(storage.items())) @@ -35,7 +35,7 @@ def get_storage( request: HttpRequest, user_profile: UserProfile, *, - keys: Json[Optional[List[str]]] = None, + keys: Json[Optional[list[str]]] = None, ) -> HttpResponse: if keys is None: keys = get_keys_in_bot_storage(user_profile) @@ -51,7 +51,7 @@ def remove_storage( request: HttpRequest, user_profile: UserProfile, *, - keys: Json[Optional[List[str]]] = None, + keys: Json[Optional[list[str]]] = None, ) -> HttpResponse: if keys is None: keys = get_keys_in_bot_storage(user_profile) diff --git a/zerver/views/streams.py b/zerver/views/streams.py index b0a5488c84..a6bcefcc8c 100644 --- a/zerver/views/streams.py +++ b/zerver/views/streams.py @@ -1,6 +1,6 @@ import time from collections import defaultdict -from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Set, Union +from typing import Any, Callable, Mapping, Optional, Sequence, Union import orjson from django.conf import settings @@ -142,7 +142,7 @@ def create_default_stream_group( user_profile: UserProfile, group_name: str = REQ(), description: str = REQ(), - stream_names: List[str] = REQ(json_validator=check_list(check_string)), + stream_names: list[str] = REQ(json_validator=check_list(check_string)), ) -> HttpResponse: streams = [] for stream_name in stream_names: @@ -179,7 +179,7 @@ def update_default_stream_group_streams( user_profile: UserProfile, group_id: int, op: str = REQ(), - stream_names: List[str] = REQ(json_validator=check_list(check_string)), + stream_names: list[str] = REQ(json_validator=check_list(check_string)), ) -> HttpResponse: group = access_default_stream_group_by_id(user_profile.realm, group_id) streams = [] @@ -441,7 +441,7 @@ def update_subscriptions_backend( return json_success(request, data) -def compose_views(thunks: List[Callable[[], HttpResponse]]) -> Dict[str, Any]: +def compose_views(thunks: list[Callable[[], HttpResponse]]) -> dict[str, Any]: """ This takes a series of thunks and calls them in sequence, and it smushes all the json results into a single response when @@ -450,7 +450,7 @@ def compose_views(thunks: List[Callable[[], HttpResponse]]) -> Dict[str, Any]: one of the composed methods. """ - json_dict: Dict[str, Any] = {} + json_dict: dict[str, Any] = {} with transaction.atomic(): for thunk in thunks: response = thunk() @@ -458,7 +458,7 @@ def compose_views(thunks: List[Callable[[], HttpResponse]]) -> Dict[str, Any]: return json_dict -check_principals: Validator[Union[List[str], List[int]]] = check_union( +check_principals: Validator[Union[list[str], list[int]]] = check_union( [check_list(check_string), check_list(check_int)], ) @@ -468,13 +468,13 @@ def remove_subscriptions_backend( request: HttpRequest, user_profile: UserProfile, streams_raw: Sequence[str] = REQ("subscriptions", json_validator=remove_subscriptions_schema), - principals: Optional[Union[List[str], List[int]]] = REQ( + principals: Optional[Union[list[str], list[int]]] = REQ( json_validator=check_principals, default=None ), ) -> HttpResponse: realm = user_profile.realm - streams_as_dict: List[StreamDict] = [ + streams_as_dict: list[StreamDict] = [ {"name": stream_name.strip()} for stream_name in streams_raw ] @@ -495,7 +495,7 @@ def remove_subscriptions_backend( unsubscribing_others=unsubscribing_others, ) - result: Dict[str, List[str]] = dict(removed=[], not_removed=[]) + result: dict[str, list[str]] = dict(removed=[], not_removed=[]) (removed, not_subscribed) = bulk_remove_subscriptions( realm, people_to_unsub, streams, acting_user=user_profile ) @@ -509,7 +509,7 @@ def remove_subscriptions_backend( def you_were_just_subscribed_message( - acting_user: UserProfile, recipient_user: UserProfile, stream_names: Set[str] + acting_user: UserProfile, recipient_user: UserProfile, stream_names: set[str] ) -> str: subscriptions = sorted(stream_names) if len(subscriptions) == 1: @@ -663,9 +663,9 @@ def add_subscriptions_backend( # We can assume unique emails here for now, but we should eventually # convert this function to be more id-centric. - email_to_user_profile: Dict[str, UserProfile] = {} + email_to_user_profile: dict[str, UserProfile] = {} - result: Dict[str, Any] = dict( + result: dict[str, Any] = dict( subscribed=defaultdict(list), already_subscribed=defaultdict(list) ) for sub_info in subscribed: @@ -699,10 +699,10 @@ def add_subscriptions_backend( def send_messages_for_new_subscribers( user_profile: UserProfile, - subscribers: Set[UserProfile], - new_subscriptions: Dict[str, List[str]], - email_to_user_profile: Dict[str, UserProfile], - created_streams: List[Stream], + subscribers: set[UserProfile], + new_subscriptions: dict[str, list[str]], + email_to_user_profile: dict[str, UserProfile], + created_streams: list[Stream], announce: bool, ) -> None: """ @@ -970,7 +970,7 @@ def update_subscriptions_property( def update_subscription_properties_backend( request: HttpRequest, user_profile: UserProfile, - subscription_data: List[Dict[str, Any]] = REQ( + subscription_data: list[dict[str, Any]] = REQ( json_validator=check_list( check_dict( [ diff --git a/zerver/views/typing.py b/zerver/views/typing.py index c1f5a8ae4b..10281a2967 100644 --- a/zerver/views/typing.py +++ b/zerver/views/typing.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Optional from django.http import HttpRequest, HttpResponse from django.utils.translation import gettext as _ @@ -20,7 +20,7 @@ def send_notification_backend( *, req_type: Annotated[Literal["direct", "stream", "channel"], ApiParamConfig("type")] = "direct", operator: Annotated[Literal["start", "stop"], ApiParamConfig("op")], - notification_to: Annotated[Json[Optional[List[int]]], ApiParamConfig("to")] = None, + notification_to: Annotated[Json[Optional[list[int]]], ApiParamConfig("to")] = None, stream_id: Json[Optional[int]] = None, topic: OptionalTopic = None, ) -> HttpResponse: diff --git a/zerver/views/upload.py b/zerver/views/upload.py index 0b824dc2e7..feeb3d76ff 100644 --- a/zerver/views/upload.py +++ b/zerver/views/upload.py @@ -2,7 +2,7 @@ import base64 import binascii import os from datetime import timedelta -from typing import List, Optional, Union +from typing import Optional, Union from urllib.parse import quote, urlsplit from django.conf import settings @@ -171,7 +171,7 @@ def serve_file_url_backend( return serve_file(request, user_profile, realm_id_str, filename, url_only=True) -def preferred_accept(request: HttpRequest, served_types: List[str]) -> Optional[str]: +def preferred_accept(request: HttpRequest, served_types: list[str]) -> Optional[str]: # Returns the first of the served_types which the browser will # accept, based on the browser's stated quality preferences. # Returns None if none of the served_types are accepted by the diff --git a/zerver/views/user_groups.py b/zerver/views/user_groups.py index e7f3753346..f056769a2c 100644 --- a/zerver/views/user_groups.py +++ b/zerver/views/user_groups.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Sequence, Union +from typing import Optional, Sequence, Union from django.conf import settings from django.db import transaction @@ -205,7 +205,7 @@ def update_user_group_backend( def notify_for_user_group_subscription_changes( acting_user: UserProfile, - recipient_users: List[UserProfile], + recipient_users: list[UserProfile], user_group: NamedUserGroup, *, send_subscription_message: bool = False, diff --git a/zerver/views/user_settings.py b/zerver/views/user_settings.py index 25e993202c..98ee5bb829 100644 --- a/zerver/views/user_settings.py +++ b/zerver/views/user_settings.py @@ -1,5 +1,5 @@ from email.headerregistry import Address -from typing import Any, Dict, Optional +from typing import Any, Optional from django.conf import settings from django.contrib.auth import authenticate, update_session_auth_hash @@ -331,7 +331,7 @@ def json_change_settings( ) if new_password is not None: - return_data: Dict[str, Any] = {} + return_data: dict[str, Any] = {} if email_belongs_to_ldap(user_profile.realm, user_profile.delivery_email): raise JsonableError(_("Your Zulip password is managed in LDAP")) @@ -372,7 +372,7 @@ def json_change_settings( # by Django, request.session.save() - result: Dict[str, Any] = {} + result: dict[str, Any] = {} if email is not None: new_email = email.strip() diff --git a/zerver/views/users.py b/zerver/views/users.py index bdbcab3e39..91cf0c3302 100644 --- a/zerver/views/users.py +++ b/zerver/views/users.py @@ -1,5 +1,5 @@ from email.headerregistry import Address -from typing import Any, Dict, List, Mapping, Optional, TypeAlias, Union +from typing import Any, Mapping, Optional, TypeAlias, Union from django.conf import settings from django.contrib.auth.models import AnonymousUser @@ -197,7 +197,7 @@ def reactivate_user_backend( class ProfileDataElement(BaseModel): id: int - value: Optional[Union[str, List[int]]] + value: Optional[Union[str, list[int]]] @typed_endpoint @@ -208,7 +208,7 @@ def update_user_backend( user_id: PathOnly[int], full_name: Optional[str] = None, role: Optional[Json[RoleParamType]] = None, - profile_data: Optional[Json[List[ProfileDataElement]]] = None, + profile_data: Optional[Json[list[ProfileDataElement]]] = None, ) -> HttpResponse: target = access_user_by_id( user_profile, user_id, allow_deactivated=True, allow_bots=True, for_admin=True @@ -236,7 +236,7 @@ def update_user_backend( check_change_full_name(target, full_name, user_profile) if profile_data is not None: - clean_profile_data: List[ProfileDataElementUpdateDict] = [] + clean_profile_data: list[ProfileDataElementUpdateDict] = [] for entry in profile_data: assert isinstance(entry.id, int) assert not isinstance(entry.value, int) @@ -340,7 +340,7 @@ def patch_bot_backend( full_name: Optional[str] = None, role: Optional[Json[RoleParamType]] = None, bot_owner_id: Optional[Json[int]] = None, - config_data: Optional[Json[Dict[str, str]]] = None, + config_data: Optional[Json[dict[str, str]]] = None, service_payload_url: Optional[Json[Annotated[str, AfterValidator(check_url)]]] = None, service_interface: Json[int] = 1, default_sending_stream: Optional[str] = None, @@ -596,7 +596,7 @@ def get_bots_backend(request: HttpRequest, user_profile: UserProfile) -> HttpRes ) bot_profiles = bot_profiles.order_by("date_joined") - def bot_info(bot_profile: UserProfile) -> Dict[str, Any]: + def bot_info(bot_profile: UserProfile) -> dict[str, Any]: default_sending_stream = get_stream_name(bot_profile.default_sending_stream) default_events_register_stream = get_stream_name(bot_profile.default_events_register_stream) @@ -623,7 +623,7 @@ def get_user_data( include_custom_profile_fields: bool, client_gravatar: bool, target_user: Optional[UserProfile] = None, -) -> Dict[str, Any]: +) -> dict[str, Any]: """ The client_gravatar field here is set to True by default assuming that clients can compute their own gravatars, which saves bandwidth. This is more important of @@ -642,7 +642,7 @@ def get_user_data( ) if target_user is not None: - data: Dict[str, Any] = {"user": members[target_user.id]} + data: dict[str, Any] = {"user": members[target_user.id]} else: data = {"members": [members[k] for k in members]} diff --git a/zerver/views/video_calls.py b/zerver/views/video_calls.py index 3c39fcdacc..9c9be73a6b 100644 --- a/zerver/views/video_calls.py +++ b/zerver/views/video_calls.py @@ -3,7 +3,6 @@ import json import random import secrets from base64 import b32encode -from typing import Dict from urllib.parse import quote, urlencode, urljoin import requests @@ -104,7 +103,7 @@ def register_zoom_user(request: HttpRequest) -> HttpResponse: @has_request_variables def complete_zoom_user( request: HttpRequest, - state: Dict[str, str] = REQ( + state: dict[str, str] = REQ( json_validator=check_dict([("realm", check_string)], value_validator=check_string) ), ) -> HttpResponse: @@ -118,7 +117,7 @@ def complete_zoom_user( def complete_zoom_user_in_realm( request: HttpRequest, code: str = REQ(), - state: Dict[str, str] = REQ( + state: dict[str, str] = REQ( json_validator=check_dict([("sid", check_string)], value_validator=check_string) ), ) -> HttpResponse: diff --git a/zerver/webhooks/alertmanager/view.py b/zerver/webhooks/alertmanager/view.py index b337d4f911..850392c5d1 100644 --- a/zerver/webhooks/alertmanager/view.py +++ b/zerver/webhooks/alertmanager/view.py @@ -1,5 +1,4 @@ # Webhooks for external integrations. -from typing import Dict, List from django.http import HttpRequest, HttpResponse from typing_extensions import Annotated @@ -22,7 +21,7 @@ def api_alertmanager_webhook( name_field: Annotated[str, ApiParamConfig("name")] = "instance", desc_field: Annotated[str, ApiParamConfig("desc")] = "alertname", ) -> HttpResponse: - topics: Dict[str, Dict[str, List[str]]] = {} + topics: dict[str, dict[str, list[str]]] = {} for alert in payload["alerts"]: labels = alert.get("labels", {}) diff --git a/zerver/webhooks/ansibletower/view.py b/zerver/webhooks/ansibletower/view.py index f4709f536b..b6fef8b81e 100644 --- a/zerver/webhooks/ansibletower/view.py +++ b/zerver/webhooks/ansibletower/view.py @@ -1,5 +1,4 @@ import operator -from typing import Dict, List from django.http import HttpRequest, HttpResponse @@ -94,7 +93,7 @@ def get_body(payload: WildValue) -> str: return ANSIBLETOWER_DEFAULT_MESSAGE_TEMPLATE.format(**data) -def get_hosts_content(hosts_data: List[Dict[str, str]]) -> str: +def get_hosts_content(hosts_data: list[dict[str, str]]) -> str: hosts_data = sorted(hosts_data, key=operator.itemgetter("hostname")) hosts_content = "" for host in hosts_data: diff --git a/zerver/webhooks/azuredevops/view.py b/zerver/webhooks/azuredevops/view.py index eddc1ef34f..920a03fd1a 100644 --- a/zerver/webhooks/azuredevops/view.py +++ b/zerver/webhooks/azuredevops/view.py @@ -1,4 +1,4 @@ -from typing import Callable, Dict, Optional +from typing import Callable, Optional from django.http import HttpRequest, HttpResponse @@ -158,7 +158,7 @@ def get_event_name(payload: WildValue, branches: Optional[str]) -> Optional[str] raise UnsupportedWebhookEventTypeError(event_name) -EVENT_FUNCTION_MAPPER: Dict[str, Callable[[WildValue], str]] = { +EVENT_FUNCTION_MAPPER: dict[str, Callable[[WildValue], str]] = { "git.push": get_code_push_commits_body, "git.pullrequest.created": get_code_pull_request_opened_body, "git.pullrequest.merged": get_code_pull_request_merged_body, diff --git a/zerver/webhooks/beanstalk/tests.py b/zerver/webhooks/beanstalk/tests.py index 4c6ad8bca6..02bffe5af9 100644 --- a/zerver/webhooks/beanstalk/tests.py +++ b/zerver/webhooks/beanstalk/tests.py @@ -1,4 +1,3 @@ -from typing import Dict from unittest.mock import MagicMock, patch from typing_extensions import override @@ -188,5 +187,5 @@ class BeanstalkHookTests(WebhookTestCase): ) @override - def get_payload(self, fixture_name: str) -> Dict[str, str]: + def get_payload(self, fixture_name: str) -> dict[str, str]: return {"payload": self.webhook_fixture_data("beanstalk", fixture_name)} diff --git a/zerver/webhooks/beanstalk/view.py b/zerver/webhooks/beanstalk/view.py index d6d2fe3371..ee8dd8729a 100644 --- a/zerver/webhooks/beanstalk/view.py +++ b/zerver/webhooks/beanstalk/view.py @@ -1,6 +1,6 @@ # Webhooks for external integrations. import re -from typing import Dict, List, Optional, Tuple +from typing import Optional from django.http import HttpRequest, HttpResponse from pydantic import Json @@ -26,7 +26,7 @@ def build_message_from_gitlog( forced: Optional[str] = None, created: Optional[str] = None, deleted: bool = False, -) -> Tuple[str, str]: +) -> tuple[str, str]: short_ref = re.sub(r"^refs/heads/", "", ref) topic_name = TOPIC_WITH_BRANCH_TEMPLATE.format(repo=name, branch=short_ref) @@ -36,7 +36,7 @@ def build_message_from_gitlog( return topic_name, content -def _transform_commits_list_to_common_format(commits: WildValue) -> List[Dict[str, str]]: +def _transform_commits_list_to_common_format(commits: WildValue) -> list[dict[str, str]]: return [ { "name": commit["author"]["name"].tame(check_string), diff --git a/zerver/webhooks/bitbucket2/view.py b/zerver/webhooks/bitbucket2/view.py index d604f545e1..73ff7016b1 100644 --- a/zerver/webhooks/bitbucket2/view.py +++ b/zerver/webhooks/bitbucket2/view.py @@ -1,7 +1,7 @@ # Webhooks for external integrations. import re import string -from typing import Dict, List, Optional, Protocol +from typing import Optional, Protocol from django.http import HttpRequest, HttpResponse @@ -127,7 +127,7 @@ def get_topic_for_branch_specified_events( ) -def get_push_topics(payload: WildValue) -> List[str]: +def get_push_topics(payload: WildValue) -> list[str]: topics_list = [] for change in payload["push"]["changes"]: potential_tag = (change["new"] or change["old"])["type"].tame(check_string) @@ -210,7 +210,7 @@ def get_body_based_on_type( return GET_SINGLE_MESSAGE_BODY_DEPENDING_ON_TYPE_MAPPER[type] -def get_push_bodies(request: HttpRequest, payload: WildValue) -> List[str]: +def get_push_bodies(request: HttpRequest, payload: WildValue) -> list[str]: messages_list = [] for change in payload["push"]["changes"]: potential_tag = (change["new"] or change["old"])["type"].tame(check_string) @@ -534,7 +534,7 @@ def get_branch_name_for_push_event(payload: WildValue) -> Optional[str]: return (change["new"] or change["old"])["name"].tame(check_string) -GET_SINGLE_MESSAGE_BODY_DEPENDING_ON_TYPE_MAPPER: Dict[str, BodyGetter] = { +GET_SINGLE_MESSAGE_BODY_DEPENDING_ON_TYPE_MAPPER: dict[str, BodyGetter] = { "fork": get_fork_body, "commit_comment": get_commit_comment_body, "change_commit_status": get_commit_status_changed_body, diff --git a/zerver/webhooks/bitbucket3/view.py b/zerver/webhooks/bitbucket3/view.py index afc2afc5c8..cd7204bc18 100644 --- a/zerver/webhooks/bitbucket3/view.py +++ b/zerver/webhooks/bitbucket3/view.py @@ -1,5 +1,5 @@ import string -from typing import Dict, List, Optional, Protocol +from typing import Optional, Protocol from django.http import HttpRequest, HttpResponse @@ -56,7 +56,7 @@ PULL_REQUEST_OPENED_OR_MODIFIED_TEMPLATE_WITH_REVIEWERS_WITH_TITLE = """ """.strip() -def fixture_to_headers(fixture_name: str) -> Dict[str, str]: +def fixture_to_headers(fixture_name: str) -> dict[str, str]: if fixture_name == "diagnostics_ping": return {"HTTP_X_EVENT_KEY": "diagnostics:ping"} return {} @@ -74,7 +74,7 @@ def ping_handler( payload: WildValue, branches: Optional[str], include_title: Optional[str], -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: if include_title: topic_name = include_title else: @@ -88,7 +88,7 @@ def repo_comment_handler( payload: WildValue, branches: Optional[str], include_title: Optional[str], -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: repo_name = payload["repository"]["name"].tame(check_string) topic_name = BITBUCKET_TOPIC_TEMPLATE.format(repository_name=repo_name) sha = payload["commit"].tame(check_string) @@ -113,7 +113,7 @@ def repo_forked_handler( payload: WildValue, branches: Optional[str], include_title: Optional[str], -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: repo_name = payload["repository"]["origin"]["name"].tame(check_string) topic_name = BITBUCKET_TOPIC_TEMPLATE.format(repository_name=repo_name) body = BITBUCKET_FORK_BODY.format( @@ -129,7 +129,7 @@ def repo_modified_handler( payload: WildValue, branches: Optional[str], include_title: Optional[str], -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: topic_name_new = BITBUCKET_TOPIC_TEMPLATE.format( repository_name=payload["new"]["name"].tame(check_string) ) @@ -146,7 +146,7 @@ def repo_modified_handler( return [{"topic": topic_name_new, "body": body}] -def repo_push_branch_data(payload: WildValue, change: WildValue) -> Dict[str, str]: +def repo_push_branch_data(payload: WildValue, change: WildValue) -> dict[str, str]: event_type = change["type"].tame(check_string) repo_name = payload["repository"]["name"].tame(check_string) user_name = get_user_name(payload) @@ -175,7 +175,7 @@ def repo_push_branch_data(payload: WildValue, change: WildValue) -> Dict[str, st return {"topic": topic_name, "body": body} -def repo_push_tag_data(payload: WildValue, change: WildValue) -> Dict[str, str]: +def repo_push_tag_data(payload: WildValue, change: WildValue) -> dict[str, str]: event_type = change["type"].tame(check_string) repo_name = payload["repository"]["name"].tame(check_string) tag_name = change["ref"]["displayId"].tame(check_string) @@ -197,7 +197,7 @@ def repo_push_handler( payload: WildValue, branches: Optional[str], include_title: Optional[str], -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: data = [] for change in payload["changes"]: event_target_type = change["ref"]["type"].tame(check_string) @@ -340,7 +340,7 @@ def pr_handler( payload: WildValue, branches: Optional[str], include_title: Optional[str], -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: pr = payload["pullRequest"] topic_name = get_pr_topic( pr["toRef"]["repository"]["name"].tame(check_string), @@ -367,7 +367,7 @@ def pr_comment_handler( payload: WildValue, branches: Optional[str], include_title: Optional[str], -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: pr = payload["pullRequest"] topic_name = get_pr_topic( pr["toRef"]["repository"]["name"].tame(check_string), @@ -393,10 +393,10 @@ def pr_comment_handler( class EventHandler(Protocol): def __call__( self, payload: WildValue, branches: Optional[str], include_title: Optional[str] - ) -> List[Dict[str, str]]: ... + ) -> list[dict[str, str]]: ... -EVENT_HANDLER_MAP: Dict[str, EventHandler] = { +EVENT_HANDLER_MAP: dict[str, EventHandler] = { "diagnostics:ping": ping_handler, "repo:comment:added": partial(repo_comment_handler, "commented"), "repo:comment:edited": partial(repo_comment_handler, "edited their comment"), diff --git a/zerver/webhooks/clubhouse/view.py b/zerver/webhooks/clubhouse/view.py index 687defeb06..25f99e99be 100644 --- a/zerver/webhooks/clubhouse/view.py +++ b/zerver/webhooks/clubhouse/view.py @@ -1,4 +1,4 @@ -from typing import Callable, Dict, Iterable, Iterator, List, Optional +from typing import Callable, Iterable, Iterator, Optional from django.http import HttpRequest, HttpResponse @@ -456,7 +456,7 @@ def get_story_update_attachment_body(payload: WildValue, action: WildValue) -> O return FILE_ATTACHMENT_TEMPLATE.format(**kwargs) -def get_story_joined_label_list(payload: WildValue, label_ids_added: List[int]) -> str: +def get_story_joined_label_list(payload: WildValue, label_ids_added: list[int]) -> str: labels = [] for label_id in label_ids_added: @@ -701,7 +701,7 @@ def send_channel_messages_for_actions( check_send_webhook_message(request, user_profile, topic_name, body, event) -EVENT_BODY_FUNCTION_MAPPER: Dict[str, Callable[[WildValue, WildValue], Optional[str]]] = { +EVENT_BODY_FUNCTION_MAPPER: dict[str, Callable[[WildValue, WildValue], Optional[str]]] = { "story_update_archived": partial(get_update_archived_body, "story"), "epic_update_archived": partial(get_update_archived_body, "epic"), "story_create": get_story_create_body, @@ -734,7 +734,7 @@ EVENT_BODY_FUNCTION_MAPPER: Dict[str, Callable[[WildValue, WildValue], Optional[ ALL_EVENT_TYPES = list(EVENT_BODY_FUNCTION_MAPPER.keys()) -EVENT_TOPIC_FUNCTION_MAPPER: Dict[str, Callable[[WildValue, WildValue], Optional[str]]] = { +EVENT_TOPIC_FUNCTION_MAPPER: dict[str, Callable[[WildValue, WildValue], Optional[str]]] = { "story": partial(get_entity_name, "story"), "pull-request": partial(get_entity_name, "story"), "branch": partial(get_entity_name, "story"), @@ -748,7 +748,7 @@ IGNORED_EVENTS = { "story-comment_update", } -EVENTS_SECONDARY_ACTIONS_FUNCTION_MAPPER: Dict[str, Callable[[WildValue], Iterator[WildValue]]] = { +EVENTS_SECONDARY_ACTIONS_FUNCTION_MAPPER: dict[str, Callable[[WildValue], Iterator[WildValue]]] = { "pull-request_create": partial(get_secondary_actions_with_param, "story", "pull_request_ids"), "branch_create": partial(get_secondary_actions_with_param, "story", "branch_ids"), "pull-request_comment": partial(get_secondary_actions_with_param, "story", "pull_request_ids"), diff --git a/zerver/webhooks/freshdesk/view.py b/zerver/webhooks/freshdesk/view.py index 6c1f208771..51ee043426 100644 --- a/zerver/webhooks/freshdesk/view.py +++ b/zerver/webhooks/freshdesk/view.py @@ -1,7 +1,5 @@ """Webhooks for external integrations.""" -from typing import List - from django.http import HttpRequest, HttpResponse from zerver.decorator import authenticated_rest_api_view @@ -60,7 +58,7 @@ def property_name(property: str, index: int) -> str: return name -def parse_freshdesk_event(event_string: str) -> List[str]: +def parse_freshdesk_event(event_string: str) -> list[str]: """These are always of the form "{ticket_action:created}" or "{status:{from:4,to:6}}". Note the lack of string quoting: this isn't valid JSON so we have to parse it ourselves. @@ -82,7 +80,7 @@ def parse_freshdesk_event(event_string: str) -> List[str]: ] -def format_freshdesk_note_message(ticket: WildValue, event_info: List[str]) -> str: +def format_freshdesk_note_message(ticket: WildValue, event_info: list[str]) -> str: """There are public (visible to customers) and private note types.""" note_type = event_info[1] content = NOTE_TEMPLATE.format( @@ -96,7 +94,7 @@ def format_freshdesk_note_message(ticket: WildValue, event_info: List[str]) -> s return content -def format_freshdesk_property_change_message(ticket: WildValue, event_info: List[str]) -> str: +def format_freshdesk_property_change_message(ticket: WildValue, event_info: list[str]) -> str: """Freshdesk will only tell us the first event to match our webhook configuration, so if we change multiple properties, we only get the before and after data for the first one. diff --git a/zerver/webhooks/freshstatus/view.py b/zerver/webhooks/freshstatus/view.py index 5368da62bb..d533ec9c4c 100644 --- a/zerver/webhooks/freshstatus/view.py +++ b/zerver/webhooks/freshstatus/view.py @@ -1,5 +1,3 @@ -from typing import Dict, List - import dateutil.parser from django.core.exceptions import ValidationError from django.http import HttpRequest, HttpResponse @@ -108,7 +106,7 @@ def api_freshstatus_webhook( return json_success(request) -def get_services_content(services_data: List[Dict[str, str]]) -> str: +def get_services_content(services_data: list[dict[str, str]]) -> str: services_content = "" for service in services_data[:FRESHSTATUS_SERVICES_LIMIT]: services_content += FRESHSTATUS_SERVICES_ROW_TEMPLATE.format( diff --git a/zerver/webhooks/front/view.py b/zerver/webhooks/front/view.py index f323c3801d..f43de3986d 100644 --- a/zerver/webhooks/front/view.py +++ b/zerver/webhooks/front/view.py @@ -1,4 +1,4 @@ -from typing import Callable, Tuple +from typing import Callable from django.http import HttpRequest, HttpResponse from django.utils.translation import gettext as _ @@ -12,7 +12,7 @@ from zerver.lib.webhooks.common import check_send_webhook_message from zerver.models import UserProfile -def get_message_data(payload: WildValue) -> Tuple[str, str, str, str]: +def get_message_data(payload: WildValue) -> tuple[str, str, str, str]: link = "https://app.frontapp.com/open/" + payload["target"]["data"]["id"].tame(check_string) outbox = payload["conversation"]["recipient"]["handle"].tame(check_string) inbox = payload["source"]["data"][0]["address"].tame(check_string) diff --git a/zerver/webhooks/github/view.py b/zerver/webhooks/github/view.py index 8bd4a1444c..680a89a91d 100644 --- a/zerver/webhooks/github/view.py +++ b/zerver/webhooks/github/view.py @@ -1,6 +1,6 @@ import re from datetime import datetime, timezone -from typing import Callable, Dict, Optional +from typing import Callable, Optional from django.http import HttpRequest, HttpResponse @@ -811,7 +811,7 @@ def get_topic_based_on_type(payload: WildValue, event: str) -> str: return get_repository_name(payload) -EVENT_FUNCTION_MAPPER: Dict[str, Callable[[Helper], str]] = { +EVENT_FUNCTION_MAPPER: dict[str, Callable[[Helper], str]] = { "commit_comment": get_commit_comment_body, "closed_pull_request": get_closed_pull_request_body, "create": partial(get_create_or_delete_body, "created"), diff --git a/zerver/webhooks/gitlab/view.py b/zerver/webhooks/gitlab/view.py index 5537653f3f..1bc9ac47dc 100644 --- a/zerver/webhooks/gitlab/view.py +++ b/zerver/webhooks/gitlab/view.py @@ -1,5 +1,5 @@ import re -from typing import Dict, List, Optional, Protocol, Union +from typing import Optional, Protocol, Union from django.http import HttpRequest, HttpResponse from pydantic import Json @@ -28,7 +28,7 @@ from zerver.lib.webhooks.git import ( from zerver.models import UserProfile -def fixture_to_headers(fixture_name: str) -> Dict[str, str]: +def fixture_to_headers(fixture_name: str) -> dict[str, str]: if fixture_name.startswith("build"): return {} # Since there are 2 possible event types. @@ -176,7 +176,7 @@ def get_merge_request_open_or_updated_body( ) -def get_assignees(payload: WildValue) -> Union[List[WildValue], WildValue]: +def get_assignees(payload: WildValue) -> Union[list[WildValue], WildValue]: assignee_details = payload.get("assignees") if not assignee_details: single_assignee_details = payload.get("assignee") @@ -189,8 +189,8 @@ def get_assignees(payload: WildValue) -> Union[List[WildValue], WildValue]: def replace_assignees_username_with_name( - assignees: Union[List[WildValue], WildValue], -) -> List[Dict[str, str]]: + assignees: Union[list[WildValue], WildValue], +) -> list[dict[str, str]]: """Replace the username of each assignee with their (full) name. This is a hack-like adaptor so that when assignees are passed to @@ -382,7 +382,7 @@ class EventFunction(Protocol): def __call__(self, payload: WildValue, include_title: bool) -> str: ... -EVENT_FUNCTION_MAPPER: Dict[str, EventFunction] = { +EVENT_FUNCTION_MAPPER: dict[str, EventFunction] = { "Push Hook": get_push_event_body, "Tag Push Hook": get_tag_push_event_body, "Test Hook": get_test_event_body, diff --git a/zerver/webhooks/gogs/view.py b/zerver/webhooks/gogs/view.py index c0c98468ee..2e074059b8 100644 --- a/zerver/webhooks/gogs/view.py +++ b/zerver/webhooks/gogs/view.py @@ -1,5 +1,5 @@ # vim:fenc=utf-8 -from typing import Dict, List, Optional, Protocol +from typing import Optional, Protocol from django.http import HttpRequest, HttpResponse @@ -46,7 +46,7 @@ def format_push_event(payload: WildValue) -> str: ) -def _transform_commits_list_to_common_format(commits: WildValue) -> List[Dict[str, str]]: +def _transform_commits_list_to_common_format(commits: WildValue) -> list[dict[str, str]]: return [ { "name": commit["author"]["username"].tame(check_string) diff --git a/zerver/webhooks/groove/view.py b/zerver/webhooks/groove/view.py index bdfbe101bd..62e1323d3c 100644 --- a/zerver/webhooks/groove/view.py +++ b/zerver/webhooks/groove/view.py @@ -1,5 +1,5 @@ # Webhooks for external integrations. -from typing import Callable, Dict, Optional +from typing import Callable, Optional from django.http import HttpRequest, HttpResponse @@ -90,7 +90,7 @@ def replied_body(actor: str, action: str, payload: WildValue) -> str: return body -EVENTS_FUNCTION_MAPPER: Dict[str, Callable[[WildValue], Optional[str]]] = { +EVENTS_FUNCTION_MAPPER: dict[str, Callable[[WildValue], Optional[str]]] = { "ticket_started": ticket_started_body, "ticket_assigned": ticket_assigned_body, "agent_replied": partial(replied_body, "agent", "replied to"), diff --git a/zerver/webhooks/hellosign/tests.py b/zerver/webhooks/hellosign/tests.py index 891bcd14e7..e35efb811a 100644 --- a/zerver/webhooks/hellosign/tests.py +++ b/zerver/webhooks/hellosign/tests.py @@ -1,5 +1,3 @@ -from typing import Dict - from typing_extensions import override from zerver.lib.test_classes import WebhookTestCase @@ -57,5 +55,5 @@ class HelloSignHookTests(WebhookTestCase): ) @override - def get_payload(self, fixture_name: str) -> Dict[str, str]: + def get_payload(self, fixture_name: str) -> dict[str, str]: return {"json": self.webhook_fixture_data("hellosign", fixture_name, file_type="json")} diff --git a/zerver/webhooks/hellosign/view.py b/zerver/webhooks/hellosign/view.py index e9ccaa474f..108c5dfa86 100644 --- a/zerver/webhooks/hellosign/view.py +++ b/zerver/webhooks/hellosign/view.py @@ -1,5 +1,3 @@ -from typing import Dict, List - from django.http import HttpRequest, HttpResponse from pydantic import Json from typing_extensions import Annotated @@ -18,7 +16,7 @@ BODY = "The `{contract_title}` document {actions}." def get_message_body(payload: WildValue) -> str: contract_title = payload["signature_request"]["title"].tame(check_string) - recipients: Dict[str, List[str]] = {} + recipients: dict[str, list[str]] = {} signatures = payload["signature_request"]["signatures"] for signature in signatures: @@ -45,7 +43,7 @@ def get_message_body(payload: WildValue) -> str: return BODY.format(contract_title=contract_title, actions=recipients_text).strip() -def get_recipients_text(recipients: List[str]) -> str: +def get_recipients_text(recipients: list[str]) -> str: recipients_text = "" if len(recipients) == 1: recipients_text = "{}".format(*recipients) diff --git a/zerver/webhooks/intercom/view.py b/zerver/webhooks/intercom/view.py index 18598726e8..a40978b8f6 100644 --- a/zerver/webhooks/intercom/view.py +++ b/zerver/webhooks/intercom/view.py @@ -1,5 +1,5 @@ from html.parser import HTMLParser -from typing import Callable, Dict, List, Tuple +from typing import Callable from django.http import HttpRequest, HttpResponse from typing_extensions import override @@ -72,7 +72,7 @@ class MLStripper(HTMLParser): self.reset() self.strict = False self.convert_charrefs = True - self.fed: List[str] = [] + self.fed: list[str] = [] @override def handle_data(self, d: str) -> None: @@ -99,7 +99,7 @@ def get_topic_for_contacts(user: WildValue) -> str: return topic_name -def get_company_created_message(payload: WildValue) -> Tuple[str, str]: +def get_company_created_message(payload: WildValue) -> tuple[str, str]: body = COMPANY_CREATED.format( name=payload["data"]["item"]["name"].tame(check_string), user_count=payload["data"]["item"]["user_count"].tame(check_int), @@ -108,14 +108,14 @@ def get_company_created_message(payload: WildValue) -> Tuple[str, str]: return ("Companies", body) -def get_contact_added_email_message(payload: WildValue) -> Tuple[str, str]: +def get_contact_added_email_message(payload: WildValue) -> tuple[str, str]: user = payload["data"]["item"] body = CONTACT_EMAIL_ADDED.format(email=user["email"].tame(check_string)) topic_name = get_topic_for_contacts(user) return (topic_name, body) -def get_contact_created_message(payload: WildValue) -> Tuple[str, str]: +def get_contact_created_message(payload: WildValue) -> tuple[str, str]: contact = payload["data"]["item"] body = CONTACT_CREATED.format( name=contact.get("name").tame(check_none_or(check_string)) @@ -131,7 +131,7 @@ def get_contact_created_message(payload: WildValue) -> Tuple[str, str]: return (topic_name, body) -def get_contact_signed_up_message(payload: WildValue) -> Tuple[str, str]: +def get_contact_signed_up_message(payload: WildValue) -> tuple[str, str]: contact = payload["data"]["item"] body = CONTACT_SIGNED_UP.format( email=contact["email"].tame(check_string), @@ -145,7 +145,7 @@ def get_contact_signed_up_message(payload: WildValue) -> Tuple[str, str]: return (topic_name, body) -def get_contact_tag_created_message(payload: WildValue) -> Tuple[str, str]: +def get_contact_tag_created_message(payload: WildValue) -> tuple[str, str]: body = CONTACT_TAG_CREATED.format( name=payload["data"]["item"]["tag"]["name"].tame(check_string) ) @@ -154,7 +154,7 @@ def get_contact_tag_created_message(payload: WildValue) -> Tuple[str, str]: return (topic_name, body) -def get_contact_tag_deleted_message(payload: WildValue) -> Tuple[str, str]: +def get_contact_tag_deleted_message(payload: WildValue) -> tuple[str, str]: body = CONTACT_TAG_DELETED.format( name=payload["data"]["item"]["tag"]["name"].tame(check_string) ) @@ -163,7 +163,7 @@ def get_contact_tag_deleted_message(payload: WildValue) -> Tuple[str, str]: return (topic_name, body) -def get_conversation_admin_assigned_message(payload: WildValue) -> Tuple[str, str]: +def get_conversation_admin_assigned_message(payload: WildValue) -> tuple[str, str]: body = CONVERSATION_ADMIN_ASSIGNED.format( name=payload["data"]["item"]["assignee"]["name"].tame(check_string) ) @@ -175,7 +175,7 @@ def get_conversation_admin_assigned_message(payload: WildValue) -> Tuple[str, st def get_conversation_admin_message( action: str, payload: WildValue, -) -> Tuple[str, str]: +) -> tuple[str, str]: assignee = payload["data"]["item"]["assignee"] user = payload["data"]["item"]["user"] body = CONVERSATION_ADMIN_TEMPLATE.format( @@ -189,7 +189,7 @@ def get_conversation_admin_message( def get_conversation_admin_reply_message( action: str, payload: WildValue, -) -> Tuple[str, str]: +) -> tuple[str, str]: assignee = payload["data"]["item"]["assignee"] user = payload["data"]["item"]["user"] note = payload["data"]["item"]["conversation_parts"]["conversation_parts"][0] @@ -203,7 +203,7 @@ def get_conversation_admin_reply_message( return (topic_name, body) -def get_conversation_admin_single_created_message(payload: WildValue) -> Tuple[str, str]: +def get_conversation_admin_single_created_message(payload: WildValue) -> tuple[str, str]: assignee = payload["data"]["item"]["assignee"] user = payload["data"]["item"]["user"] conversation_body = payload["data"]["item"]["conversation_message"]["body"].tame(check_string) @@ -216,7 +216,7 @@ def get_conversation_admin_single_created_message(payload: WildValue) -> Tuple[s return (topic_name, body) -def get_conversation_user_created_message(payload: WildValue) -> Tuple[str, str]: +def get_conversation_user_created_message(payload: WildValue) -> tuple[str, str]: user = payload["data"]["item"]["user"] conversation_body = payload["data"]["item"]["conversation_message"]["body"].tame(check_string) content = strip_tags(conversation_body) @@ -228,7 +228,7 @@ def get_conversation_user_created_message(payload: WildValue) -> Tuple[str, str] return (topic_name, body) -def get_conversation_user_replied_message(payload: WildValue) -> Tuple[str, str]: +def get_conversation_user_replied_message(payload: WildValue) -> tuple[str, str]: user = payload["data"]["item"]["user"] note = payload["data"]["item"]["conversation_parts"]["conversation_parts"][0] content = strip_tags(note["body"].tame(check_string)) @@ -241,13 +241,13 @@ def get_conversation_user_replied_message(payload: WildValue) -> Tuple[str, str] return (topic_name, body) -def get_event_created_message(payload: WildValue) -> Tuple[str, str]: +def get_event_created_message(payload: WildValue) -> tuple[str, str]: event = payload["data"]["item"] body = EVENT_CREATED.format(event_name=event["event_name"].tame(check_string)) return ("Events", body) -def get_user_created_message(payload: WildValue) -> Tuple[str, str]: +def get_user_created_message(payload: WildValue) -> tuple[str, str]: user = payload["data"]["item"] body = USER_CREATED.format( name=user["name"].tame(check_string), email=user["email"].tame(check_string) @@ -256,13 +256,13 @@ def get_user_created_message(payload: WildValue) -> Tuple[str, str]: return (topic_name, body) -def get_user_deleted_message(payload: WildValue) -> Tuple[str, str]: +def get_user_deleted_message(payload: WildValue) -> tuple[str, str]: user = payload["data"]["item"] topic_name = get_topic_for_contacts(user) return (topic_name, "User deleted.") -def get_user_email_updated_message(payload: WildValue) -> Tuple[str, str]: +def get_user_email_updated_message(payload: WildValue) -> tuple[str, str]: user = payload["data"]["item"] body = "User's email was updated to {}.".format(user["email"].tame(check_string)) topic_name = get_topic_for_contacts(user) @@ -272,7 +272,7 @@ def get_user_email_updated_message(payload: WildValue) -> Tuple[str, str]: def get_user_tagged_message( action: str, payload: WildValue, -) -> Tuple[str, str]: +) -> tuple[str, str]: user = payload["data"]["item"]["user"] tag = payload["data"]["item"]["tag"] topic_name = get_topic_for_contacts(user) @@ -283,14 +283,14 @@ def get_user_tagged_message( return (topic_name, body) -def get_user_unsubscribed_message(payload: WildValue) -> Tuple[str, str]: +def get_user_unsubscribed_message(payload: WildValue) -> tuple[str, str]: user = payload["data"]["item"] body = "User unsubscribed from emails." topic_name = get_topic_for_contacts(user) return (topic_name, body) -EVENT_TO_FUNCTION_MAPPER: Dict[str, Callable[[WildValue], Tuple[str, str]]] = { +EVENT_TO_FUNCTION_MAPPER: dict[str, Callable[[WildValue], tuple[str, str]]] = { "company.created": get_company_created_message, "contact.added_email": get_contact_added_email_message, "contact.created": get_contact_created_message, diff --git a/zerver/webhooks/jira/view.py b/zerver/webhooks/jira/view.py index aeec57b0aa..28d5feee50 100644 --- a/zerver/webhooks/jira/view.py +++ b/zerver/webhooks/jira/view.py @@ -1,7 +1,7 @@ # Webhooks for external integrations. import re import string -from typing import Callable, Dict, List, Optional +from typing import Callable, Optional from django.core.exceptions import ValidationError from django.db.models import Q @@ -95,7 +95,7 @@ def convert_jira_markup(content: str, realm: Realm) -> str: return content -def get_in(payload: WildValue, keys: List[str], default: str = "") -> WildValue: +def get_in(payload: WildValue, keys: list[str], default: str = "") -> WildValue: try: for key in keys: payload = payload[key] @@ -339,7 +339,7 @@ def handle_comment_deleted_event(payload: WildValue, user_profile: UserProfile) ) -JIRA_CONTENT_FUNCTION_MAPPER: Dict[str, Optional[Callable[[WildValue, UserProfile], str]]] = { +JIRA_CONTENT_FUNCTION_MAPPER: dict[str, Optional[Callable[[WildValue, UserProfile], str]]] = { "jira:issue_created": handle_created_issue_event, "jira:issue_deleted": handle_deleted_issue_event, "jira:issue_updated": handle_updated_issue_event, diff --git a/zerver/webhooks/json/view.py b/zerver/webhooks/json/view.py index 584e2c1f3e..888811ff0b 100644 --- a/zerver/webhooks/json/view.py +++ b/zerver/webhooks/json/view.py @@ -1,5 +1,5 @@ import json -from typing import Any, Dict +from typing import Any from django.http import HttpRequest, HttpResponse @@ -22,7 +22,7 @@ def api_json_webhook( request: HttpRequest, user_profile: UserProfile, *, - payload: JsonBodyPayload[Dict[str, Any]], + payload: JsonBodyPayload[dict[str, Any]], ) -> HttpResponse: body = get_body_for_http_request(payload) topic_name = get_topic_for_http_request(payload) @@ -31,10 +31,10 @@ def api_json_webhook( return json_success(request) -def get_topic_for_http_request(payload: Dict[str, Any]) -> str: +def get_topic_for_http_request(payload: dict[str, Any]) -> str: return "JSON" -def get_body_for_http_request(payload: Dict[str, Any]) -> str: +def get_body_for_http_request(payload: dict[str, Any]) -> str: prettypayload = json.dumps(payload, indent=2) return JSON_MESSAGE_TEMPLATE.format(webhook_payload=prettypayload, sort_keys=True) diff --git a/zerver/webhooks/librato/view.py b/zerver/webhooks/librato/view.py index dbc95102bd..cdc96dee62 100644 --- a/zerver/webhooks/librato/view.py +++ b/zerver/webhooks/librato/view.py @@ -1,5 +1,5 @@ from datetime import datetime, timezone -from typing import Any, Callable, Dict, List, Mapping, Tuple +from typing import Any, Callable, Mapping import orjson from django.http import HttpRequest, HttpResponse @@ -21,41 +21,41 @@ SNAPSHOT = "image_url" class LibratoWebhookParser: ALERT_URL_TEMPLATE = "https://metrics.librato.com/alerts#/{alert_id}" - def __init__(self, payload: Mapping[str, Any], attachments: List[Dict[str, Any]]) -> None: + def __init__(self, payload: Mapping[str, Any], attachments: list[dict[str, Any]]) -> None: self.payload = payload self.attachments = attachments def generate_alert_url(self, alert_id: int) -> str: return self.ALERT_URL_TEMPLATE.format(alert_id=alert_id) - def parse_alert(self) -> Tuple[int, str, str, str]: + def parse_alert(self) -> tuple[int, str, str, str]: alert = self.payload["alert"] alert_id = alert["id"] return alert_id, alert["name"], self.generate_alert_url(alert_id), alert["runbook_url"] - def parse_condition(self, condition: Dict[str, Any]) -> Tuple[str, str, str, str]: + def parse_condition(self, condition: dict[str, Any]) -> tuple[str, str, str, str]: summary_function = condition["summary_function"] threshold = condition.get("threshold", "") condition_type = condition["type"] duration = condition.get("duration", "") return summary_function, threshold, condition_type, duration - def parse_violation(self, violation: Dict[str, Any]) -> Tuple[str, str]: + def parse_violation(self, violation: dict[str, Any]) -> tuple[str, str]: metric_name = violation["metric"] recorded_at = datetime.fromtimestamp(violation["recorded_at"], tz=timezone.utc).strftime( "%Y-%m-%d %H:%M:%S" ) return metric_name, recorded_at - def parse_conditions(self) -> List[Dict[str, Any]]: + def parse_conditions(self) -> list[dict[str, Any]]: conditions = self.payload["conditions"] return conditions - def parse_violations(self) -> List[Dict[str, Any]]: + def parse_violations(self) -> list[dict[str, Any]]: violations = self.payload["violations"]["test-source"] return violations - def parse_snapshot(self, snapshot: Dict[str, Any]) -> Tuple[str, str, str]: + def parse_snapshot(self, snapshot: dict[str, Any]) -> tuple[str, str, str]: author_name, image_url, title = ( snapshot["author_name"], snapshot["image_url"], @@ -65,7 +65,7 @@ class LibratoWebhookParser: class LibratoWebhookHandler(LibratoWebhookParser): - def __init__(self, payload: Mapping[str, Any], attachments: List[Dict[str, Any]]) -> None: + def __init__(self, payload: Mapping[str, Any], attachments: list[dict[str, Any]]) -> None: super().__init__(payload, attachments) self.payload_available_types = { ALERT_CLEAR: self.handle_alert_clear_message, @@ -112,7 +112,7 @@ class LibratoWebhookHandler(LibratoWebhookParser): content += self.handle_snapshot(attachment) return content - def handle_snapshot(self, snapshot: Dict[str, Any]) -> str: + def handle_snapshot(self, snapshot: dict[str, Any]) -> str: snapshot_template = "**{author_name}** sent a [snapshot]({image_url}) of [metric]({title})." author_name, image_url, title = self.parse_snapshot(snapshot) content = snapshot_template.format( @@ -139,7 +139,7 @@ class LibratoWebhookHandler(LibratoWebhookParser): return content def generate_violated_metric_condition( - self, violation: Dict[str, Any], condition: Dict[str, Any] + self, violation: dict[str, Any], condition: dict[str, Any] ) -> str: summary_function, threshold, condition_type, duration = self.parse_condition(condition) metric_name, recorded_at = self.parse_violation(violation) diff --git a/zerver/webhooks/lidarr/view.py b/zerver/webhooks/lidarr/view.py index cd45ac828a..7f5a27e2c0 100644 --- a/zerver/webhooks/lidarr/view.py +++ b/zerver/webhooks/lidarr/view.py @@ -1,5 +1,3 @@ -from typing import Dict, List - from django.http import HttpRequest, HttpResponse from zerver.decorator import webhook_view @@ -40,7 +38,7 @@ LIDARR_TRACKS_LIMIT = 20 ALL_EVENT_TYPES = ["Test", "Grab", "Rename", "Retag", "Download"] -def get_tracks_content(tracks_data: List[Dict[str, str]]) -> str: +def get_tracks_content(tracks_data: list[dict[str, str]]) -> str: tracks_content = "" for track in tracks_data[:LIDARR_TRACKS_LIMIT]: tracks_content += LIDARR_TRACKS_ROW_TEMPLATE.format(track_title=track.get("title")) diff --git a/zerver/webhooks/linear/view.py b/zerver/webhooks/linear/view.py index 5e7f3c6d9a..c578e9bee4 100644 --- a/zerver/webhooks/linear/view.py +++ b/zerver/webhooks/linear/view.py @@ -1,4 +1,4 @@ -from typing import Callable, Dict, Optional +from typing import Callable, Optional from django.http import HttpRequest, HttpResponse @@ -99,7 +99,7 @@ def get_comment_message(payload: WildValue, event: str) -> str: ) -EVENT_FUNCTION_MAPPER: Dict[str, Callable[[WildValue, str], str]] = { +EVENT_FUNCTION_MAPPER: dict[str, Callable[[WildValue, str], str]] = { "issue": get_issue_or_sub_issue_message, "sub_issue": get_issue_or_sub_issue_message, "comment": get_comment_message, diff --git a/zerver/webhooks/netlify/view.py b/zerver/webhooks/netlify/view.py index e517809d62..e8c47bdfdd 100644 --- a/zerver/webhooks/netlify/view.py +++ b/zerver/webhooks/netlify/view.py @@ -1,5 +1,3 @@ -from typing import Tuple - from django.http import HttpRequest, HttpResponse from zerver.decorator import webhook_view @@ -49,7 +47,7 @@ def api_netlify_webhook( return json_success(request) -def get_template(request: HttpRequest, payload: WildValue) -> Tuple[str, str]: +def get_template(request: HttpRequest, payload: WildValue) -> tuple[str, str]: message_template = "The build [{build_name}]({build_url}) on branch {branch_name} " event = validate_extract_webhook_http_header(request, "X-Netlify-Event", "Netlify") diff --git a/zerver/webhooks/opbeat/view.py b/zerver/webhooks/opbeat/view.py index 6110ade934..e406ef33d1 100644 --- a/zerver/webhooks/opbeat/view.py +++ b/zerver/webhooks/opbeat/view.py @@ -1,5 +1,4 @@ # Webhooks for external integrations. -from typing import Dict, List from django.http import HttpRequest, HttpResponse @@ -10,7 +9,7 @@ from zerver.lib.validator import WildValue, check_int, check_none_or, check_stri from zerver.lib.webhooks.common import check_send_webhook_message from zerver.models import UserProfile -subject_types: Dict[str, List[List[str]]] = { +subject_types: dict[str, list[list[str]]] = { "app": [ # Object type name ["name"], # Title ["html_url"], # Automatically put into title @@ -57,7 +56,7 @@ def format_object( ) -> str: if subject_type not in subject_types: return message - keys: List[List[str]] = subject_types[subject_type][1:] + keys: list[list[str]] = subject_types[subject_type][1:] title = subject_types[subject_type][0] if title[0] != "": title_str = "" diff --git a/zerver/webhooks/pagerduty/view.py b/zerver/webhooks/pagerduty/view.py index 4592996070..9f8f95f7f0 100644 --- a/zerver/webhooks/pagerduty/view.py +++ b/zerver/webhooks/pagerduty/view.py @@ -1,5 +1,5 @@ from email.headerregistry import Address -from typing import Dict, Union +from typing import Union from django.http import HttpRequest, HttpResponse from typing_extensions import TypeAlias @@ -12,7 +12,7 @@ from zerver.lib.validator import WildValue, check_int, check_none_or, check_stri from zerver.lib.webhooks.common import check_send_webhook_message from zerver.models import UserProfile -FormatDictType: TypeAlias = Dict[str, Union[str, int]] +FormatDictType: TypeAlias = dict[str, Union[str, int]] PAGER_DUTY_EVENT_NAMES = { "incident.trigger": "triggered", diff --git a/zerver/webhooks/patreon/view.py b/zerver/webhooks/patreon/view.py index c28308e0ef..d61caffffa 100644 --- a/zerver/webhooks/patreon/view.py +++ b/zerver/webhooks/patreon/view.py @@ -1,4 +1,4 @@ -from typing import Callable, Dict, Optional +from typing import Callable, Optional from django.http import HttpRequest, HttpResponse @@ -17,8 +17,8 @@ from zerver.models import UserProfile # The events for this integration contain the ":" character, which is not appropriate in a # filename and requires us to deviate from the common `get_http_headers_from_filename` method # from zerver.lib.webhooks.common. -def get_custom_http_headers_from_filename(http_header_key: str) -> Callable[[str], Dict[str, str]]: - def fixture_to_headers(filename: str) -> Dict[str, str]: +def get_custom_http_headers_from_filename(http_header_key: str) -> Callable[[str], dict[str, str]]: + def fixture_to_headers(filename: str) -> dict[str, str]: event_type = filename.replace("_", ":") return {http_header_key: event_type} @@ -128,7 +128,7 @@ def get_pay_per_name(payload: WildValue) -> str: return payload["included"][0]["attributes"]["pay_per_name"].tame(check_string) -EVENT_FUNCTION_MAPPER: Dict[str, Callable[[WildValue], Optional[str]]] = { +EVENT_FUNCTION_MAPPER: dict[str, Callable[[WildValue], Optional[str]]] = { "members:create": get_members_create_body, "members:update": get_members_update_body, "members:delete": get_members_delete_body, diff --git a/zerver/webhooks/pivotal/view.py b/zerver/webhooks/pivotal/view.py index bac835c66b..e5fc94c74a 100644 --- a/zerver/webhooks/pivotal/view.py +++ b/zerver/webhooks/pivotal/view.py @@ -1,7 +1,7 @@ """Webhooks for external integrations.""" import re -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Optional import orjson from defusedxml.ElementTree import fromstring as xml_fromstring @@ -16,10 +16,10 @@ from zerver.lib.webhooks.common import check_send_webhook_message from zerver.models import UserProfile -def api_pivotal_webhook_v3(request: HttpRequest, user_profile: UserProfile) -> Tuple[str, str, str]: +def api_pivotal_webhook_v3(request: HttpRequest, user_profile: UserProfile) -> tuple[str, str, str]: payload = xml_fromstring(request.body) - def get_text(attrs: List[str]) -> str: + def get_text(attrs: list[str]) -> str: start = payload try: for attr in attrs: @@ -87,7 +87,7 @@ ALL_EVENT_TYPES = [ ] -def api_pivotal_webhook_v5(request: HttpRequest, user_profile: UserProfile) -> Tuple[str, str, str]: +def api_pivotal_webhook_v5(request: HttpRequest, user_profile: UserProfile) -> tuple[str, str, str]: payload = orjson.loads(request.body) event_type = payload["kind"] @@ -110,7 +110,7 @@ def api_pivotal_webhook_v5(request: HttpRequest, user_profile: UserProfile) -> T content = "" topic_name = f"#{story_id}: {story_name}" - def extract_comment(change: Dict[str, Any]) -> Optional[str]: + def extract_comment(change: dict[str, Any]) -> Optional[str]: if change.get("kind") == "comment": return change.get("new_values", {}).get("text", None) return None diff --git a/zerver/webhooks/rhodecode/view.py b/zerver/webhooks/rhodecode/view.py index 0670a365a2..0cea8921b0 100644 --- a/zerver/webhooks/rhodecode/view.py +++ b/zerver/webhooks/rhodecode/view.py @@ -1,4 +1,4 @@ -from typing import Callable, Dict, Optional +from typing import Callable, Optional from django.core.exceptions import ValidationError from django.http import HttpRequest, HttpResponse @@ -68,7 +68,7 @@ def get_topic_based_on_event(payload: WildValue, event: str) -> str: return get_repository_name(payload) # nocoverage -EVENT_FUNCTION_MAPPER: Dict[str, Callable[[WildValue], str]] = { +EVENT_FUNCTION_MAPPER: dict[str, Callable[[WildValue], str]] = { "repo-push": get_push_commits_body, } diff --git a/zerver/webhooks/semaphore/view.py b/zerver/webhooks/semaphore/view.py index 8312c58823..a0a5eeb698 100644 --- a/zerver/webhooks/semaphore/view.py +++ b/zerver/webhooks/semaphore/view.py @@ -1,5 +1,5 @@ # Webhooks for external integrations. -from typing import Optional, Tuple +from typing import Optional from urllib.parse import urlsplit from django.http import HttpRequest, HttpResponse @@ -111,7 +111,7 @@ def api_semaphore_webhook( return json_success(request) -def semaphore_classic(payload: WildValue) -> Tuple[str, str, str, str]: +def semaphore_classic(payload: WildValue) -> tuple[str, str, str, str]: # semaphore only gives the last commit, even if there were multiple commits # since the last build branch_name = payload["branch_name"].tame(check_string) @@ -161,7 +161,7 @@ def semaphore_classic(payload: WildValue) -> Tuple[str, str, str, str]: return content, project_name, branch_name, event -def semaphore_2(payload: WildValue) -> Tuple[str, str, Optional[str], str]: +def semaphore_2(payload: WildValue) -> tuple[str, str, Optional[str], str]: repo_url = payload["repository"]["url"].tame(check_string) project_name = payload["project"]["name"].tame(check_string) organization_name = payload["organization"]["name"].tame(check_string) diff --git a/zerver/webhooks/sentry/view.py b/zerver/webhooks/sentry/view.py index 453e309d79..10cd20887f 100644 --- a/zerver/webhooks/sentry/view.py +++ b/zerver/webhooks/sentry/view.py @@ -1,6 +1,6 @@ import logging from datetime import datetime, timezone -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Optional from urllib.parse import urljoin from django.http import HttpRequest, HttpResponse @@ -81,7 +81,7 @@ syntax_highlight_as_map = { } -def is_sample_event(event: Dict[str, Any]) -> bool: +def is_sample_event(event: dict[str, Any]) -> bool: # This is just a heuristic to detect the sample event, this should # not be used for making important behavior decisions. title = event.get("title", "") @@ -90,7 +90,7 @@ def is_sample_event(event: Dict[str, Any]) -> bool: return False -def convert_lines_to_traceback_string(lines: Optional[List[str]]) -> str: +def convert_lines_to_traceback_string(lines: Optional[list[str]]) -> str: traceback = "" if lines is not None: for line in lines: @@ -101,7 +101,7 @@ def convert_lines_to_traceback_string(lines: Optional[List[str]]) -> str: return traceback -def handle_event_payload(event: Dict[str, Any]) -> Tuple[str, str]: +def handle_event_payload(event: dict[str, Any]) -> tuple[str, str]: """Handle either an exception type event or a message type event payload.""" topic_name = event["title"] @@ -182,8 +182,8 @@ def handle_event_payload(event: Dict[str, Any]) -> Tuple[str, str]: def handle_issue_payload( - action: str, issue: Dict[str, Any], actor: Dict[str, Any] -) -> Tuple[str, str]: + action: str, issue: dict[str, Any], actor: dict[str, Any] +) -> tuple[str, str]: """Handle either an issue type event.""" topic_name = issue["title"] datetime = issue["lastSeen"].split(".")[0].replace("T", " ") @@ -233,7 +233,7 @@ def handle_issue_payload( return (topic_name, body) -def handle_deprecated_payload(payload: Dict[str, Any]) -> Tuple[str, str]: +def handle_deprecated_payload(payload: dict[str, Any]) -> tuple[str, str]: topic_name = "{}".format(payload.get("project_name")) body = DEPRECATED_EXCEPTION_MESSAGE_TEMPLATE.format( level=payload["level"].upper(), @@ -243,7 +243,7 @@ def handle_deprecated_payload(payload: Dict[str, Any]) -> Tuple[str, str]: return (topic_name, body) -def transform_webhook_payload(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]: +def transform_webhook_payload(payload: dict[str, Any]) -> Optional[dict[str, Any]]: """Attempt to use webhook payload for the notification. When the integration is configured as a webhook, instead of being added as @@ -272,7 +272,7 @@ def api_sentry_webhook( request: HttpRequest, user_profile: UserProfile, *, - payload: JsonBodyPayload[Dict[str, Any]], + payload: JsonBodyPayload[dict[str, Any]], ) -> HttpResponse: data = payload.get("data", None) diff --git a/zerver/webhooks/slack_incoming/view.py b/zerver/webhooks/slack_incoming/view.py index addc8c5981..5ed1a73ffc 100644 --- a/zerver/webhooks/slack_incoming/view.py +++ b/zerver/webhooks/slack_incoming/view.py @@ -1,7 +1,7 @@ # Webhooks for external integrations. import re from itertools import zip_longest -from typing import List, Literal, TypedDict, cast +from typing import Literal, TypedDict, cast from django.http import HttpRequest, HttpResponse from django.utils.translation import gettext as _ @@ -60,7 +60,7 @@ def api_slack_incoming_webhook( if user_specified_topic is None: user_specified_topic = "(no topic)" - pieces: List[str] = [] + pieces: list[str] = [] if payload.get("blocks"): pieces += map(render_block, payload["blocks"]) diff --git a/zerver/webhooks/stripe/view.py b/zerver/webhooks/stripe/view.py index cb7a7a6605..cb086010eb 100644 --- a/zerver/webhooks/stripe/view.py +++ b/zerver/webhooks/stripe/view.py @@ -1,6 +1,6 @@ # Webhooks for external integrations. import time -from typing import Dict, Optional, Sequence, Tuple +from typing import Optional, Sequence from django.http import HttpRequest, HttpResponse @@ -65,7 +65,7 @@ def api_stripe_webhook( return json_success(request) -def topic_and_body(payload: WildValue) -> Tuple[str, str]: +def topic_and_body(payload: WildValue) -> tuple[str, str]: event_type = payload["type"].tame( check_string ) # invoice.created, customer.subscription.created, etc @@ -318,7 +318,7 @@ def amount_string(amount: int, currency: str) -> str: def linkified_id(object_id: str, lower: bool = False) -> str: - names_and_urls: Dict[str, Tuple[str, Optional[str]]] = { + names_and_urls: dict[str, tuple[str, Optional[str]]] = { # Core resources "ch": ("Charge", "charges"), "cus": ("Customer", "customers"), diff --git a/zerver/webhooks/taiga/view.py b/zerver/webhooks/taiga/view.py index 74b657001d..306bf7b6ca 100644 --- a/zerver/webhooks/taiga/view.py +++ b/zerver/webhooks/taiga/view.py @@ -7,7 +7,7 @@ value should always be in bold; otherwise the subject of US/task should be in bold. """ -from typing import Dict, List, Optional, Tuple, Union +from typing import Optional, Union from django.http import HttpRequest, HttpResponse from typing_extensions import TypeAlias @@ -19,8 +19,8 @@ from zerver.lib.validator import WildValue, check_bool, check_none_or, check_str from zerver.lib.webhooks.common import check_send_webhook_message from zerver.models import UserProfile -EventType: TypeAlias = Dict[str, Union[str, Dict[str, Optional[Union[str, bool]]]]] -ReturnType: TypeAlias = Tuple[WildValue, WildValue] +EventType: TypeAlias = dict[str, Union[str, dict[str, Optional[Union[str, bool]]]]] +ReturnType: TypeAlias = tuple[WildValue, WildValue] @webhook_view("Taiga") @@ -207,7 +207,7 @@ def parse_create_or_delete( def parse_change_event(change_type: str, message: WildValue) -> Optional[EventType]: """Parses change event.""" evt: EventType = {} - values: Dict[str, Optional[Union[str, bool]]] = { + values: dict[str, Optional[Union[str, bool]]] = { "user": get_owner_name(message), "user_link": get_owner_link(message), "subject": get_subject(message), @@ -301,9 +301,9 @@ def parse_webhook_test( def parse_message( message: WildValue, -) -> List[EventType]: +) -> list[EventType]: """Parses the payload by delegating to specialized functions.""" - events: List[EventType] = [] + events: list[EventType] = [] if message["action"].tame(check_string) in ["create", "delete"]: events.append(parse_create_or_delete(message)) elif message["action"].tame(check_string) == "change": diff --git a/zerver/webhooks/thinkst/view.py b/zerver/webhooks/thinkst/view.py index 87ee961f82..2518b9a082 100644 --- a/zerver/webhooks/thinkst/view.py +++ b/zerver/webhooks/thinkst/view.py @@ -1,5 +1,5 @@ # Webhooks for external integrations. -from typing import Optional, Tuple +from typing import Optional from django.http import HttpRequest, HttpResponse @@ -40,7 +40,7 @@ def canary_kind(message: WildValue) -> str: return "canary" -def source_ip_and_reverse_dns(message: WildValue) -> Tuple[Optional[str], Optional[str]]: +def source_ip_and_reverse_dns(message: WildValue) -> tuple[Optional[str], Optional[str]]: """ Extract the source IP and reverse DNS information from a canary request. """ diff --git a/zerver/webhooks/transifex/tests.py b/zerver/webhooks/transifex/tests.py index 2c66e7b0ab..835796e44f 100644 --- a/zerver/webhooks/transifex/tests.py +++ b/zerver/webhooks/transifex/tests.py @@ -1,5 +1,3 @@ -from typing import Dict - from typing_extensions import override from zerver.lib.test_classes import WebhookTestCase @@ -39,5 +37,5 @@ class TransifexHookTests(WebhookTestCase): self.check_webhook("", expected_topic_name, expected_message) @override - def get_payload(self, fixture_name: str) -> Dict[str, str]: + def get_payload(self, fixture_name: str) -> dict[str, str]: return {} diff --git a/zerver/webhooks/trello/tests.py b/zerver/webhooks/trello/tests.py index 6105096a09..db07c98872 100644 --- a/zerver/webhooks/trello/tests.py +++ b/zerver/webhooks/trello/tests.py @@ -1,4 +1,3 @@ -from typing import Dict from unittest.mock import patch import orjson @@ -155,7 +154,7 @@ class TrelloHookTests(WebhookTestCase): "pos", ] for field in fields: - card: Dict[str, object] = {} + card: dict[str, object] = {} old = {} old[field] = "should-be-ignored" data = dict( diff --git a/zerver/webhooks/trello/view/__init__.py b/zerver/webhooks/trello/view/__init__.py index 5162c61582..c80385a08c 100644 --- a/zerver/webhooks/trello/view/__init__.py +++ b/zerver/webhooks/trello/view/__init__.py @@ -1,5 +1,5 @@ # Webhooks for external integrations. -from typing import Optional, Tuple +from typing import Optional from django.http import HttpRequest, HttpResponse @@ -35,7 +35,7 @@ def api_trello_webhook( return json_success(request) -def get_topic_and_body(payload: WildValue, action_type: str) -> Optional[Tuple[str, str]]: +def get_topic_and_body(payload: WildValue, action_type: str) -> Optional[tuple[str, str]]: if action_type in SUPPORTED_CARD_ACTIONS: return process_card_action(payload, action_type) if action_type in IGNORED_CARD_ACTIONS: diff --git a/zerver/webhooks/trello/view/board_actions.py b/zerver/webhooks/trello/view/board_actions.py index 52a070b0f6..f03115dd88 100644 --- a/zerver/webhooks/trello/view/board_actions.py +++ b/zerver/webhooks/trello/view/board_actions.py @@ -1,4 +1,4 @@ -from typing import Mapping, Optional, Tuple +from typing import Mapping, Optional from zerver.lib.exceptions import UnsupportedWebhookEventTypeError from zerver.lib.validator import WildValue, check_string @@ -27,7 +27,7 @@ ACTIONS_TO_MESSAGE_MAPPER = { def process_board_action( payload: WildValue, action_type: Optional[str] -) -> Optional[Tuple[str, str]]: +) -> Optional[tuple[str, str]]: action_type = get_proper_action(payload, action_type) if action_type is not None: return get_topic(payload), get_body(payload, action_type) diff --git a/zerver/webhooks/trello/view/card_actions.py b/zerver/webhooks/trello/view/card_actions.py index b94a2f67ea..4fb950582e 100644 --- a/zerver/webhooks/trello/view/card_actions.py +++ b/zerver/webhooks/trello/view/card_actions.py @@ -1,4 +1,4 @@ -from typing import Mapping, Optional, Tuple +from typing import Mapping, Optional from zerver.lib.exceptions import UnsupportedWebhookEventTypeError from zerver.lib.validator import WildValue, check_bool, check_none_or, check_string @@ -75,7 +75,7 @@ def prettify_date(date_string: str) -> str: return date_string.replace("T", " ").replace(".000", "").replace("Z", " UTC") -def process_card_action(payload: WildValue, action_type: str) -> Optional[Tuple[str, str]]: +def process_card_action(payload: WildValue, action_type: str) -> Optional[tuple[str, str]]: proper_action = get_proper_action(payload, action_type) if proper_action is not None: return get_topic(payload), get_body(payload, proper_action) diff --git a/zerver/webhooks/zendesk/tests.py b/zerver/webhooks/zendesk/tests.py index 92c84fe2ed..22b95719b9 100644 --- a/zerver/webhooks/zendesk/tests.py +++ b/zerver/webhooks/zendesk/tests.py @@ -1,5 +1,3 @@ -from typing import Dict - from typing_extensions import override from zerver.lib.test_classes import WebhookTestCase @@ -10,7 +8,7 @@ class ZenDeskHookTests(WebhookTestCase): URL_TEMPLATE = "/api/v1/external/zendesk?stream={stream}" @override - def get_payload(self, fixture_name: str) -> Dict[str, str]: + def get_payload(self, fixture_name: str) -> dict[str, str]: return { "ticket_title": self.TICKET_TITLE, "ticket_id": str(self.TICKET_ID), diff --git a/zerver/worker/base.py b/zerver/worker/base.py index beb26e0987..da3ac9d553 100644 --- a/zerver/worker/base.py +++ b/zerver/worker/base.py @@ -6,7 +6,7 @@ import time from abc import ABC, abstractmethod from collections import deque from types import FrameType -from typing import Any, Callable, Dict, List, MutableSequence, Optional, Set, Tuple, Type, TypeVar +from typing import Any, Callable, MutableSequence, Optional, TypeVar import orjson import sentry_sdk @@ -54,8 +54,8 @@ def assign_queue( queue_name: str, enabled: bool = True, is_test_queue: bool = False, -) -> Callable[[Type[ConcreteQueueWorker]], Type[ConcreteQueueWorker]]: - def decorate(clazz: Type[ConcreteQueueWorker]) -> Type[ConcreteQueueWorker]: +) -> Callable[[type[ConcreteQueueWorker]], type[ConcreteQueueWorker]]: + def decorate(clazz: type[ConcreteQueueWorker]) -> type[ConcreteQueueWorker]: clazz.queue_name = queue_name if enabled: register_worker(queue_name, clazz, is_test_queue) @@ -64,12 +64,12 @@ def assign_queue( return decorate -worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {} -test_queues: Set[str] = set() +worker_classes: dict[str, type["QueueProcessingWorker"]] = {} +test_queues: set[str] = set() def register_worker( - queue_name: str, clazz: Type["QueueProcessingWorker"], is_test_queue: bool = False + queue_name: str, clazz: type["QueueProcessingWorker"], is_test_queue: bool = False ) -> None: worker_classes[queue_name] = clazz if is_test_queue: @@ -115,7 +115,7 @@ class QueueProcessingWorker(ABC): def initialize_statistics(self) -> None: self.queue_last_emptied_timestamp = time.time() self.consumed_since_last_emptied = 0 - self.recent_consume_times: MutableSequence[Tuple[int, float]] = deque(maxlen=50) + self.recent_consume_times: MutableSequence[tuple[int, float]] = deque(maxlen=50) self.consume_iteration_counter = 0 self.idle = True self.last_statistics_update_time = 0.0 @@ -160,11 +160,11 @@ class QueueProcessingWorker(ABC): return 0 @abstractmethod - def consume(self, data: Dict[str, Any]) -> None: + def consume(self, data: dict[str, Any]) -> None: pass def do_consume( - self, consume_func: Callable[[List[Dict[str, Any]]], None], events: List[Dict[str, Any]] + self, consume_func: Callable[[list[dict[str, Any]]], None], events: list[dict[str, Any]] ) -> None: consume_time_seconds: Optional[float] = None with sentry_sdk.start_transaction( @@ -236,16 +236,16 @@ class QueueProcessingWorker(ABC): self.consume_iteration_counter = 0 self.update_statistics() - def consume_single_event(self, event: Dict[str, Any]) -> None: + def consume_single_event(self, event: dict[str, Any]) -> None: consume_func = lambda events: self.consume(events[0]) self.do_consume(consume_func, [event]) def timer_expired( - self, limit: int, events: List[Dict[str, Any]], signal: int, frame: Optional[FrameType] + self, limit: int, events: list[dict[str, Any]], signal: int, frame: Optional[FrameType] ) -> None: raise WorkerTimeoutError(self.queue_name, limit, len(events)) - def _handle_consume_exception(self, events: List[Dict[str, Any]], exception: Exception) -> None: + def _handle_consume_exception(self, events: list[dict[str, Any]], exception: Exception) -> None: if isinstance(exception, InterruptConsumeError): # The exception signals that no further error handling # is needed and the worker can proceed. @@ -316,10 +316,10 @@ class LoopQueueProcessingWorker(QueueProcessingWorker): ) @abstractmethod - def consume_batch(self, events: List[Dict[str, Any]]) -> None: + def consume_batch(self, events: list[dict[str, Any]]) -> None: pass @override - def consume(self, event: Dict[str, Any]) -> None: + def consume(self, event: dict[str, Any]) -> None: """In LoopQueueProcessingWorker, consume is used just for automated tests""" self.consume_batch([event]) diff --git a/zerver/worker/deferred_work.py b/zerver/worker/deferred_work.py index a001700436..f0382219a9 100644 --- a/zerver/worker/deferred_work.py +++ b/zerver/worker/deferred_work.py @@ -2,7 +2,7 @@ import logging import tempfile import time -from typing import Any, Dict +from typing import Any from urllib.parse import urlsplit from django.conf import settings @@ -46,7 +46,7 @@ class DeferredWorker(QueueProcessingWorker): MAX_CONSUME_SECONDS = None @override - def consume(self, event: Dict[str, Any]) -> None: + def consume(self, event: dict[str, Any]) -> None: start = time.time() if event["type"] == "mark_stream_messages_as_read": user_profile = get_user_profile_by_id(event["user_profile_id"]) @@ -117,7 +117,7 @@ class DeferredWorker(QueueProcessingWorker): clear_push_device_tokens(event["user_profile_id"]) except PushNotificationBouncerRetryLaterError: - def failure_processor(event: Dict[str, Any]) -> None: + def failure_processor(event: dict[str, Any]) -> None: logger.warning( "Maximum retries exceeded for trigger:%s event:clear_push_device_tokens", event["user_profile_id"], diff --git a/zerver/worker/email_senders.py b/zerver/worker/email_senders.py index 5b132ed44b..59b553b1d6 100644 --- a/zerver/worker/email_senders.py +++ b/zerver/worker/email_senders.py @@ -3,7 +3,7 @@ import copy import logging import socket from functools import wraps -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable, Optional from django.core.mail.backends.base import BaseEmailBackend from typing_extensions import override @@ -25,16 +25,16 @@ logger = logging.getLogger(__name__) # function doesn't delete the "failed_tries" attribute of "data" which is needed for # "retry_event" to work correctly; see EmailSendingWorker for an example with deepcopy. def retry_send_email_failures( - func: Callable[[ConcreteQueueWorker, Dict[str, Any]], None], -) -> Callable[[ConcreteQueueWorker, Dict[str, Any]], None]: + func: Callable[[ConcreteQueueWorker, dict[str, Any]], None], +) -> Callable[[ConcreteQueueWorker, dict[str, Any]], None]: @wraps(func) - def wrapper(worker: ConcreteQueueWorker, data: Dict[str, Any]) -> None: + def wrapper(worker: ConcreteQueueWorker, data: dict[str, Any]) -> None: try: func(worker, data) except (socket.gaierror, TimeoutError, EmailNotDeliveredError) as e: error_class_name = type(e).__name__ - def on_failure(event: Dict[str, Any]) -> None: + def on_failure(event: dict[str, Any]) -> None: logging.exception( "Event %r failed due to exception %s", event, error_class_name, stack_info=True ) @@ -56,7 +56,7 @@ class EmailSendingWorker(LoopQueueProcessingWorker): self.connection: Optional[BaseEmailBackend] = None @retry_send_email_failures - def send_email(self, event: Dict[str, Any]) -> None: + def send_email(self, event: dict[str, Any]) -> None: # Copy the event, so that we don't pass the `failed_tries' # data to send_email (which neither takes that # argument nor needs that data). @@ -72,7 +72,7 @@ class EmailSendingWorker(LoopQueueProcessingWorker): send_email(**copied_event, connection=self.connection) @override - def consume_batch(self, events: List[Dict[str, Any]]) -> None: + def consume_batch(self, events: list[dict[str, Any]]) -> None: for event in events: self.send_email(event) diff --git a/zerver/worker/embed_links.py b/zerver/worker/embed_links.py index c4f9321feb..c4a03a2ab5 100644 --- a/zerver/worker/embed_links.py +++ b/zerver/worker/embed_links.py @@ -2,7 +2,7 @@ import logging import time from types import FrameType -from typing import Any, Dict, List, Mapping, Optional +from typing import Any, Mapping, Optional from django.db import transaction from typing_extensions import override @@ -25,7 +25,7 @@ class FetchLinksEmbedData(QueueProcessingWorker): @override def consume(self, event: Mapping[str, Any]) -> None: - url_embed_data: Dict[str, Optional[UrlEmbedData]] = {} + url_embed_data: dict[str, Optional[UrlEmbedData]] = {} for url in event["urls"]: start_time = time.time() url_embed_data[url] = url_preview.get_link_embed_data(url) @@ -59,7 +59,7 @@ class FetchLinksEmbedData(QueueProcessingWorker): @override def timer_expired( - self, limit: int, events: List[Dict[str, Any]], signal: int, frame: Optional[FrameType] + self, limit: int, events: list[dict[str, Any]], signal: int, frame: Optional[FrameType] ) -> None: assert len(events) == 1 event = events[0] diff --git a/zerver/worker/embedded_bots.py b/zerver/worker/embedded_bots.py index e2a1a37ff7..b6d21e97ad 100644 --- a/zerver/worker/embedded_bots.py +++ b/zerver/worker/embedded_bots.py @@ -1,6 +1,6 @@ # Documented in https://zulip.readthedocs.io/en/latest/subsystems/queuing.html import logging -from typing import Any, Dict, Mapping +from typing import Any, Mapping from typing_extensions import override from zulip_bots.lib import extract_query_without_mention @@ -24,7 +24,7 @@ class EmbeddedBotWorker(QueueProcessingWorker): user_profile_id = event["user_profile_id"] user_profile = get_user_profile_by_id(user_profile_id) - message: Dict[str, Any] = event["message"] + message: dict[str, Any] = event["message"] # TODO: Do we actually want to allow multiple Services per bot user? services = get_bot_services(user_profile_id) diff --git a/zerver/worker/missedmessage_emails.py b/zerver/worker/missedmessage_emails.py index 2345b1c8a7..ecf404f959 100644 --- a/zerver/worker/missedmessage_emails.py +++ b/zerver/worker/missedmessage_emails.py @@ -3,7 +3,7 @@ import logging import threading from collections import defaultdict from datetime import timedelta -from typing import Any, Dict, Optional +from typing import Any, Optional import sentry_sdk from django.db import transaction @@ -44,7 +44,7 @@ class MissedMessageWorker(QueueProcessingWorker): # database rows from them. @override @sentry_sdk.trace - def consume(self, event: Dict[str, Any]) -> None: + def consume(self, event: dict[str, Any]) -> None: logging.debug("Processing missedmessage_emails event: %s", event) # When we consume an event, check if there are existing pending emails # for that user, and if so use the same scheduled timestamp. @@ -222,7 +222,7 @@ class MissedMessageWorker(QueueProcessingWorker): ).select_for_update() # Batch the entries by user - events_by_recipient: Dict[int, Dict[int, MissedMessageData]] = defaultdict(dict) + events_by_recipient: dict[int, dict[int, MissedMessageData]] = defaultdict(dict) for event in events_to_process: events_by_recipient[event.user_profile_id][event.message_id] = MissedMessageData( trigger=event.trigger, mentioned_user_group_id=event.mentioned_user_group_id diff --git a/zerver/worker/missedmessage_mobile_notifications.py b/zerver/worker/missedmessage_mobile_notifications.py index 23a941defc..a776ebc290 100644 --- a/zerver/worker/missedmessage_mobile_notifications.py +++ b/zerver/worker/missedmessage_mobile_notifications.py @@ -1,6 +1,6 @@ # Documented in https://zulip.readthedocs.io/en/latest/subsystems/queuing.html import logging -from typing import Any, Dict, Optional +from typing import Any, Optional from django.conf import settings from typing_extensions import override @@ -44,7 +44,7 @@ class PushNotificationsWorker(QueueProcessingWorker): super().start() @override - def consume(self, event: Dict[str, Any]) -> None: + def consume(self, event: dict[str, Any]) -> None: try: if event.get("type", "add") == "remove": message_ids = event["message_ids"] @@ -53,7 +53,7 @@ class PushNotificationsWorker(QueueProcessingWorker): handle_push_notification(event["user_profile_id"], event) except PushNotificationBouncerRetryLaterError: - def failure_processor(event: Dict[str, Any]) -> None: + def failure_processor(event: dict[str, Any]) -> None: logger.warning( "Maximum retries exceeded for trigger:%s event:push_notification", event["user_profile_id"], diff --git a/zerver/worker/outgoing_webhooks.py b/zerver/worker/outgoing_webhooks.py index 4100123c42..aa6492bd1f 100644 --- a/zerver/worker/outgoing_webhooks.py +++ b/zerver/worker/outgoing_webhooks.py @@ -1,6 +1,6 @@ # Documented in https://zulip.readthedocs.io/en/latest/subsystems/queuing.html import logging -from typing import Any, Dict +from typing import Any from typing_extensions import override @@ -14,7 +14,7 @@ logger = logging.getLogger(__name__) @assign_queue("outgoing_webhooks") class OutgoingWebhookWorker(QueueProcessingWorker): @override - def consume(self, event: Dict[str, Any]) -> None: + def consume(self, event: dict[str, Any]) -> None: message = event["message"] event["command"] = message["content"] diff --git a/zerver/worker/queue_processors.py b/zerver/worker/queue_processors.py index 7eec596764..72427e0ca6 100644 --- a/zerver/worker/queue_processors.py +++ b/zerver/worker/queue_processors.py @@ -1,7 +1,7 @@ # Documented in https://zulip.readthedocs.io/en/latest/subsystems/queuing.html import importlib import pkgutil -from typing import List, Optional +from typing import Optional import zerver.worker from zerver.worker.base import QueueProcessingWorker, test_queues, worker_classes @@ -25,7 +25,7 @@ def get_worker( ) -def get_active_worker_queues(only_test_queues: bool = False) -> List[str]: +def get_active_worker_queues(only_test_queues: bool = False) -> list[str]: """Returns all (either test, or real) worker queues.""" for module_info in pkgutil.iter_modules(zerver.worker.__path__, "zerver.worker."): importlib.import_module(module_info.name) diff --git a/zerver/worker/test.py b/zerver/worker/test.py index 742fa6d74d..7cd701bfca 100644 --- a/zerver/worker/test.py +++ b/zerver/worker/test.py @@ -1,7 +1,7 @@ # Documented in https://zulip.readthedocs.io/en/latest/subsystems/queuing.html import logging import time -from typing import Any, Dict, List, Mapping, Optional, Sequence, Set +from typing import Any, Mapping, Optional, Sequence import orjson from django.conf import settings @@ -42,7 +42,7 @@ class NoopWorker(QueueProcessingWorker): super().__init__(threaded, disable_timeout, worker_num) self.consumed = 0 self.max_consume = max_consume - self.slow_queries: Set[int] = set(slow_queries) + self.slow_queries: set[int] = set(slow_queries) @override def consume(self, event: Mapping[str, Any]) -> None: @@ -71,10 +71,10 @@ class BatchNoopWorker(LoopQueueProcessingWorker): super().__init__(threaded, disable_timeout) self.consumed = 0 self.max_consume = max_consume - self.slow_queries: Set[int] = set(slow_queries) + self.slow_queries: set[int] = set(slow_queries) @override - def consume_batch(self, events: List[Dict[str, Any]]) -> None: + def consume_batch(self, events: list[dict[str, Any]]) -> None: event_numbers = set(range(self.consumed + 1, self.consumed + 1 + len(events))) found_slow = self.slow_queries & event_numbers if found_slow: diff --git a/zerver/worker/user_activity.py b/zerver/worker/user_activity.py index 5547a50477..36c2acceba 100644 --- a/zerver/worker/user_activity.py +++ b/zerver/worker/user_activity.py @@ -1,6 +1,6 @@ # Documented in https://zulip.readthedocs.io/en/latest/subsystems/queuing.html import logging -from typing import Any, Dict, List, Tuple +from typing import Any from django.db import connection from psycopg2.sql import SQL, Literal @@ -31,7 +31,7 @@ class UserActivityWorker(LoopQueueProcessingWorker): """ - client_id_map: Dict[str, int] = {} + client_id_map: dict[str, int] = {} @override def start(self) -> None: @@ -40,8 +40,8 @@ class UserActivityWorker(LoopQueueProcessingWorker): super().start() @override - def consume_batch(self, user_activity_events: List[Dict[str, Any]]) -> None: - uncommitted_events: Dict[Tuple[int, int, str], Tuple[int, float]] = {} + def consume_batch(self, user_activity_events: list[dict[str, Any]]) -> None: + uncommitted_events: dict[tuple[int, int, str], tuple[int, float]] = {} # First, we drain the queue of all user_activity events and # deduplicate them for insertion into the database. diff --git a/zilencer/management/commands/add_mock_conversation.py b/zilencer/management/commands/add_mock_conversation.py index 7979f58be2..a5bbd45ecc 100644 --- a/zilencer/management/commands/add_mock_conversation.py +++ b/zilencer/management/commands/add_mock_conversation.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List +from typing import Any from typing_extensions import override @@ -67,7 +67,7 @@ From image editing program: realm, [stream], list(UserProfile.objects.filter(realm=realm)), acting_user=None ) - staged_messages: List[Dict[str, Any]] = [ + staged_messages: list[dict[str, Any]] = [ { "sender": starr, "content": "Hey @**Bel Fisher**, check out Zulip's Markdown formatting! " diff --git a/zilencer/management/commands/populate_billing_realms.py b/zilencer/management/commands/populate_billing_realms.py index 931bf9ad46..b0651917c6 100644 --- a/zilencer/management/commands/populate_billing_realms.py +++ b/zilencer/management/commands/populate_billing_realms.py @@ -2,7 +2,7 @@ import contextlib import uuid from dataclasses import dataclass from datetime import datetime, timezone -from typing import Any, Dict, Optional +from typing import Any, Optional import stripe from django.conf import settings @@ -413,7 +413,7 @@ def populate_realm(customer_profile: CustomerProfile) -> Optional[Realm]: return realm -def populate_remote_server(customer_profile: CustomerProfile) -> Dict[str, str]: +def populate_remote_server(customer_profile: CustomerProfile) -> dict[str, str]: unique_id = customer_profile.unique_id if ( @@ -520,7 +520,7 @@ def populate_remote_server(customer_profile: CustomerProfile) -> Dict[str, str]: } -def populate_remote_realms(customer_profile: CustomerProfile) -> Dict[str, str]: +def populate_remote_realms(customer_profile: CustomerProfile) -> dict[str, str]: # Delete existing remote realm. RemoteRealm.objects.filter(name=customer_profile.unique_id).delete() flush_cache(None) diff --git a/zilencer/management/commands/populate_db.py b/zilencer/management/commands/populate_db.py index 8838f49b1e..bdd6f973d8 100644 --- a/zilencer/management/commands/populate_db.py +++ b/zilencer/management/commands/populate_db.py @@ -3,7 +3,7 @@ import os import random from collections import defaultdict from datetime import datetime, timedelta -from typing import Any, Dict, List, Mapping, Sequence, Tuple +from typing import Any, Mapping, Sequence import bmemcached import orjson @@ -139,7 +139,7 @@ def clear_database() -> None: post_delete.connect(flush_alert_word, sender=AlertWord) -def subscribe_users_to_streams(realm: Realm, stream_dict: Dict[str, Dict[str, Any]]) -> None: +def subscribe_users_to_streams(realm: Realm, stream_dict: dict[str, dict[str, Any]]) -> None: subscriptions_to_add = [] event_time = timezone_now() all_subscription_logs = [] @@ -191,7 +191,7 @@ def create_alert_words(realm_id: int) -> None: "study", ] - recs: List[AlertWord] = [] + recs: list[AlertWord] = [] for user_id in user_ids: random.shuffle(alert_words) recs.extend( @@ -636,7 +636,7 @@ class Command(ZulipBaseCommand): zulip_discussion_channel_name, zulip_sandbox_channel_name, ] - stream_dict: Dict[str, Dict[str, Any]] = { + stream_dict: dict[str, dict[str, Any]] = { "Denmark": {"description": "A Scandinavian country"}, "Scotland": {"description": "Located in the United Kingdom", "creator": iago}, "Venice": {"description": "A northeastern Italian city", "creator": polonius}, @@ -649,7 +649,7 @@ class Command(ZulipBaseCommand): } bulk_create_streams(zulip_realm, stream_dict) - recipient_streams: List[int] = [ + recipient_streams: list[int] = [ Stream.objects.get(name=name, realm=zulip_realm).id for name in stream_list ] @@ -660,7 +660,7 @@ class Command(ZulipBaseCommand): # subscriptions to make sure test data is consistent # across platforms. - subscriptions_list: List[Tuple[UserProfile, Recipient]] = [] + subscriptions_list: list[tuple[UserProfile, Recipient]] = [] profiles: Sequence[UserProfile] = list( UserProfile.objects.select_related("realm").filter(is_bot=False).order_by("email") ) @@ -720,9 +720,9 @@ class Command(ZulipBaseCommand): r = Recipient.objects.get(type=Recipient.STREAM, type_id=type_id) subscriptions_list.append((profile, r)) - subscriptions_to_add: List[Subscription] = [] + subscriptions_to_add: list[Subscription] = [] event_time = timezone_now() - all_subscription_logs: List[RealmAuditLog] = [] + all_subscription_logs: list[RealmAuditLog] = [] i = 0 for profile, recipient in subscriptions_list: @@ -873,7 +873,7 @@ class Command(ZulipBaseCommand): ] # Extract a list of all users - user_profiles: List[UserProfile] = list( + user_profiles: list[UserProfile] = list( UserProfile.objects.filter(is_bot=False, realm=zulip_realm) ) @@ -1003,7 +1003,7 @@ class Command(ZulipBaseCommand): # to imitate emoji insertions in stream names raw_emojis = ["😎", "πŸ˜‚", "πŸ±β€πŸ‘€"] - zulip_stream_dict: Dict[str, Dict[str, Any]] = { + zulip_stream_dict: dict[str, dict[str, Any]] = { "devel": {"description": "For developing"}, # ビデγ‚ͺγ‚²γƒΌγƒ  - VideoGames (japanese) "ビデγ‚ͺγ‚²γƒΌγƒ ": { @@ -1098,7 +1098,7 @@ class Command(ZulipBaseCommand): call_command("populate_analytics_db") threads = options["threads"] - jobs: List[Tuple[int, List[List[int]], Dict[str, Any], int]] = [] + jobs: list[tuple[int, list[list[int]], dict[str, Any], int]] = [] for i in range(threads): count = options["num_messages"] // threads if i < options["num_messages"] % threads: @@ -1147,7 +1147,7 @@ def mark_all_messages_as_read() -> None: ) -recipient_hash: Dict[int, Recipient] = {} +recipient_hash: dict[int, Recipient] = {} def get_recipient_by_id(rid: int) -> Recipient: @@ -1164,7 +1164,7 @@ def get_recipient_by_id(rid: int) -> Recipient: # - multiple messages per subject # - both single and multi-line content def generate_and_send_messages( - data: Tuple[int, Sequence[Sequence[int]], Mapping[str, Any], int], + data: tuple[int, Sequence[Sequence[int]], Mapping[str, Any], int], ) -> int: realm = get_realm("zulip") (tot_messages, personals_pairs, options, random_seed) = data @@ -1181,15 +1181,15 @@ def generate_and_send_messages( # messages to its streams - and they might also have no subscribers, which would break # our message generation mechanism below. stream_ids = Stream.objects.filter(realm=realm).values_list("id", flat=True) - recipient_streams: List[int] = [ + recipient_streams: list[int] = [ recipient.id for recipient in Recipient.objects.filter(type=Recipient.STREAM, type_id__in=stream_ids) ] - recipient_huddles: List[int] = [ + recipient_huddles: list[int] = [ h.id for h in Recipient.objects.filter(type=Recipient.DIRECT_MESSAGE_GROUP) ] - huddle_members: Dict[int, List[int]] = {} + huddle_members: dict[int, list[int]] = {} for h in recipient_huddles: huddle_members[h] = [s.user_profile.id for s in Subscription.objects.filter(recipient_id=h)] @@ -1210,10 +1210,10 @@ def generate_and_send_messages( message_batch_size = options["batch_size"] num_messages = 0 random_max = 1000000 - recipients: Dict[int, Tuple[int, int, Dict[str, Any]]] = {} - messages: List[Message] = [] + recipients: dict[int, tuple[int, int, dict[str, Any]]] = {} + messages: list[Message] = [] while num_messages < tot_messages: - saved_data: Dict[str, Any] = {} + saved_data: dict[str, Any] = {} message = Message(realm=realm) message.sending_client = get_client("populate_db") @@ -1285,7 +1285,7 @@ def generate_and_send_messages( return tot_messages -def send_messages(messages: List[Message]) -> None: +def send_messages(messages: list[Message]) -> None: # We disable USING_RABBITMQ here, so that deferred work is # executed in do_send_message_messages, rather than being # queued. This is important, because otherwise, if run-dev @@ -1298,12 +1298,12 @@ def send_messages(messages: List[Message]) -> None: settings.USING_RABBITMQ = True -def get_message_to_users(message_ids: List[int]) -> Dict[int, List[int]]: +def get_message_to_users(message_ids: list[int]) -> dict[int, list[int]]: rows = UserMessage.objects.filter( message_id__in=message_ids, ).values("message_id", "user_profile_id") - result: Dict[int, List[int]] = defaultdict(list) + result: dict[int, list[int]] = defaultdict(list) for row in rows: result[row["message_id"]].append(row["user_profile_id"]) @@ -1311,8 +1311,8 @@ def get_message_to_users(message_ids: List[int]) -> Dict[int, List[int]]: return result -def bulk_create_reactions(all_messages: List[Message]) -> None: - reactions: List[Reaction] = [] +def bulk_create_reactions(all_messages: list[Message]) -> None: + reactions: list[Reaction] = [] num_messages = int(0.2 * len(all_messages)) messages = random.sample(all_messages, num_messages) diff --git a/zilencer/migrations/0027_backfill_remote_realmauditlog_extradata_to_json_field.py b/zilencer/migrations/0027_backfill_remote_realmauditlog_extradata_to_json_field.py index 218dedc529..b37d647126 100644 --- a/zilencer/migrations/0027_backfill_remote_realmauditlog_extradata_to_json_field.py +++ b/zilencer/migrations/0027_backfill_remote_realmauditlog_extradata_to_json_field.py @@ -1,7 +1,7 @@ # Generated by Django 4.0.7 on 2022-09-30 20:30 import ast -from typing import Callable, List, Tuple, Type +from typing import Callable import orjson from django.db import migrations, transaction @@ -29,9 +29,9 @@ OVERWRITE_TEMPLATE = """Audit log entry with id {id} has extra_data_json been in @transaction.atomic def do_bulk_backfill_extra_data( - audit_log_model: Type[Model], id_lower_bound: int, id_upper_bound: int + audit_log_model: type[Model], id_lower_bound: int, id_upper_bound: int ) -> None: - inconsistent_extra_data_json: List[Tuple[int, str, object, object]] = [] + inconsistent_extra_data_json: list[tuple[int, str, object, object]] = [] # A dict converted with str() will start with a open bracket followed by a # single quote, as opposed to a JSON-encoded value, which will use a # _double_ quote. We use this to filter out those entries with malformed diff --git a/zilencer/models.py b/zilencer/models.py index 038ce90021..11740c3d7e 100644 --- a/zilencer/models.py +++ b/zilencer/models.py @@ -3,7 +3,6 @@ from dataclasses import dataclass from datetime import datetime, timedelta -from typing import List, Tuple from django.conf import settings from django.core.exceptions import ValidationError @@ -493,7 +492,7 @@ class RateLimitedRemoteZulipServer(RateLimitedObject): return f"{type(self).__name__}:<{self.uuid}>:{self.domain}" @override - def rules(self) -> List[Tuple[int, int]]: + def rules(self) -> list[tuple[int, int]]: return rate_limiter_rules[self.domain] @@ -504,7 +503,7 @@ class RemoteCustomerUserCount: def get_remote_customer_user_count( - audit_logs: List[RemoteRealmAuditLog], + audit_logs: list[RemoteRealmAuditLog], ) -> RemoteCustomerUserCount: guest_count = 0 non_guest_count = 0 diff --git a/zilencer/views.py b/zilencer/views.py index 7f81b537ce..302dd87820 100644 --- a/zilencer/views.py +++ b/zilencer/views.py @@ -2,7 +2,7 @@ import logging from collections import Counter from datetime import datetime, timezone from email.headerregistry import Address -from typing import Any, Dict, List, Optional, Type, TypedDict, TypeVar, Union +from typing import Any, Optional, TypedDict, TypeVar, Union from uuid import UUID import orjson @@ -261,7 +261,7 @@ def register_remote_push_device( if user_id is None and user_uuid is None: raise JsonableError(_("Missing user_id or user_uuid")) if user_id is not None and user_uuid is not None: - kwargs: Dict[str, object] = {"user_uuid": user_uuid, "user_id": None} + kwargs: dict[str, object] = {"user_uuid": user_uuid, "user_id": None} # Delete pre-existing user_id registration for this user+device to avoid # duplication. Further down, uuid registration will be created. RemotePushDeviceToken.objects.filter( @@ -360,8 +360,8 @@ def update_remote_realm_last_request_datetime_helper( def delete_duplicate_registrations( - registrations: List[RemotePushDeviceToken], server_id: int, user_id: int, user_uuid: str -) -> List[RemotePushDeviceToken]: + registrations: list[RemotePushDeviceToken], server_id: int, user_id: int, user_uuid: str +) -> list[RemotePushDeviceToken]: """ When migrating to support registration by UUID, we introduced a bug where duplicate registrations for the same device+user could be created - one by user_id and one by @@ -423,7 +423,7 @@ class TestNotificationPayload(BaseModel): user_id: int user_uuid: str realm_uuid: Optional[str] = None - base_payload: Dict[str, Any] + base_payload: dict[str, Any] model_config = ConfigDict(extra="forbid") @@ -521,12 +521,12 @@ class RemoteServerNotificationPayload(BaseModel): user_id: Optional[int] = None user_uuid: Optional[str] = None realm_uuid: Optional[str] = None - gcm_payload: Dict[str, Any] = {} - apns_payload: Dict[str, Any] = {} - gcm_options: Dict[str, Any] = {} + gcm_payload: dict[str, Any] = {} + apns_payload: dict[str, Any] = {} + gcm_options: dict[str, Any] = {} - android_devices: List[str] = [] - apple_devices: List[str] = [] + android_devices: list[str] = [] + apple_devices: list[str] = [] @typed_endpoint @@ -642,7 +642,7 @@ def remote_server_notify_push( # this for notifications generated natively on the server. We # apply this to remote-server pushes in case they predate that # commit. - def truncate_payload(payload: Dict[str, Any]) -> Dict[str, Any]: + def truncate_payload(payload: dict[str, Any]) -> dict[str, Any]: MAX_MESSAGE_IDS = 200 if payload and payload.get("event") == "remove" and payload.get("zulip_message_ids"): ids = [int(id) for id in payload["zulip_message_ids"].split(",")] @@ -713,15 +713,15 @@ def remote_server_notify_push( class DevicesToCleanUpDict(TypedDict): - android_devices: List[str] - apple_devices: List[str] + android_devices: list[str] + apple_devices: list[str] def get_deleted_devices( user_identity: UserPushIdentityCompat, server: RemoteZulipServer, - android_devices: List[str], - apple_devices: List[str], + android_devices: list[str], + apple_devices: list[str], ) -> DevicesToCleanUpDict: """The remote server sends us a list of (tokens of) devices that it believes it has registered. However some of them may have been @@ -756,7 +756,7 @@ def get_deleted_devices( def validate_incoming_table_data( server: RemoteZulipServer, model: Any, - rows: List[Dict[str, Any]], + rows: list[dict[str, Any]], *, is_count_stat: bool, ) -> None: @@ -793,8 +793,8 @@ ModelT = TypeVar("ModelT", bound=Model) def batch_create_table_data( server: RemoteZulipServer, - model: Type[ModelT], - row_objects: List[ModelT], + model: type[ModelT], + row_objects: list[ModelT], ) -> None: # We ignore previously-existing data, in case it was truncated and # re-created on the remote server. `ignore_conflicts=True` @@ -818,8 +818,8 @@ def batch_create_table_data( def ensure_devices_set_remote_realm( - android_devices: List[RemotePushDeviceToken], - apple_devices: List[RemotePushDeviceToken], + android_devices: list[RemotePushDeviceToken], + apple_devices: list[RemotePushDeviceToken], remote_realm: RemoteRealm, ) -> None: devices_to_update = [] @@ -832,7 +832,7 @@ def ensure_devices_set_remote_realm( def update_remote_realm_data_for_server( - server: RemoteZulipServer, server_realms_info: List[RealmDataForAnalytics] + server: RemoteZulipServer, server_realms_info: list[RealmDataForAnalytics] ) -> None: reported_uuids = [realm.uuid for realm in server_realms_info] all_registered_remote_realms_for_server = list(RemoteRealm.objects.filter(server=server)) @@ -988,7 +988,7 @@ def update_remote_realm_data_for_server( ) RemoteRealmAuditLog.objects.bulk_create(remote_realm_audit_logs) - email_dict: Dict[str, Any] = { + email_dict: dict[str, Any] = { "template_prefix": "zerver/emails/internal_billing_notice", "to_emails": [BILLING_SUPPORT_EMAIL], "from_address": FromAddress.tokenized_no_reply_address(), @@ -1000,7 +1000,7 @@ def update_remote_realm_data_for_server( def get_human_user_realm_uuids( server: RemoteZulipServer, -) -> List[UUID]: +) -> list[UUID]: query = RemoteRealm.objects.filter( server=server, realm_deactivated=False, @@ -1142,10 +1142,10 @@ def remote_server_post_analytics( request: HttpRequest, server: RemoteZulipServer, *, - realm_counts: Json[List[RealmCountDataForAnalytics]], - installation_counts: Json[List[InstallationCountDataForAnalytics]], - realmauditlog_rows: Optional[Json[List[RealmAuditLogDataForAnalytics]]] = None, - realms: Optional[Json[List[RealmDataForAnalytics]]] = None, + realm_counts: Json[list[RealmCountDataForAnalytics]], + installation_counts: Json[list[InstallationCountDataForAnalytics]], + realmauditlog_rows: Optional[Json[list[RealmAuditLogDataForAnalytics]]] = None, + realms: Optional[Json[list[RealmDataForAnalytics]]] = None, version: Optional[Json[str]] = None, merge_base: Optional[Json[str]] = None, api_feature_level: Optional[Json[int]] = None, @@ -1295,7 +1295,7 @@ def remote_server_post_analytics( can_push_values = set() # Return details on exactly the set of remote realm the client told us about. - remote_realm_dict: Dict[str, RemoteRealmDictValue] = {} + remote_realm_dict: dict[str, RemoteRealmDictValue] = {} remote_human_realm_count = len( [ remote_realm @@ -1329,8 +1329,8 @@ def remote_server_post_analytics( def build_realm_id_to_remote_realm_dict( - server: RemoteZulipServer, realms: Optional[List[RealmDataForAnalytics]] -) -> Dict[int, RemoteRealm]: + server: RemoteZulipServer, realms: Optional[list[RealmDataForAnalytics]] +) -> dict[int, RemoteRealm]: if realms is None: return {} @@ -1344,7 +1344,7 @@ def build_realm_id_to_remote_realm_dict( def fix_remote_realm_foreign_keys( - server: RemoteZulipServer, realms: List[RealmDataForAnalytics] + server: RemoteZulipServer, realms: list[RealmDataForAnalytics] ) -> None: """ Finds the RemoteRealmCount and RemoteRealmAuditLog entries without .remote_realm diff --git a/zproject/backends.py b/zproject/backends.py index bd0639c453..4d9550fec9 100644 --- a/zproject/backends.py +++ b/zproject/backends.py @@ -17,20 +17,7 @@ import json import logging from abc import ABC, abstractmethod from email.headerregistry import Address -from typing import ( - Any, - Callable, - Dict, - List, - Optional, - Set, - Tuple, - Type, - TypedDict, - TypeVar, - Union, - cast, -) +from typing import Any, Callable, Optional, TypedDict, TypeVar, Union, cast from urllib.parse import urlencode import magic @@ -129,7 +116,7 @@ from zproject.settings_types import OIDCIdPConfigDict redis_client = get_redis_client() -def all_default_backend_names() -> List[str]: +def all_default_backend_names() -> list[str]: if not settings.BILLING_ENABLED or settings.DEVELOPMENT: # If billing isn't enabled, it's a self-hosted server # and has access to all authentication backends. @@ -154,7 +141,7 @@ def all_default_backend_names() -> List[str]: # `settings.AUTHENTICATION_BACKENDS`, queried via # `django.contrib.auth.get_backends`) and at the realm level (via the # `RealmAuthenticationMethod` table). -def pad_method_dict(method_dict: Dict[str, bool]) -> Dict[str, bool]: +def pad_method_dict(method_dict: dict[str, bool]) -> dict[str, bool]: """Pads an authentication methods dict to contain all auth backends supported by the software, regardless of whether they are configured on this server""" @@ -165,9 +152,9 @@ def pad_method_dict(method_dict: Dict[str, bool]) -> Dict[str, bool]: def auth_enabled_helper( - backends_to_check: List[str], + backends_to_check: list[str], realm: Optional[Realm], - realm_authentication_methods: Optional[Dict[str, bool]] = None, + realm_authentication_methods: Optional[dict[str, bool]] = None, ) -> bool: """ realm_authentication_methods can be passed if already fetched to avoid @@ -192,19 +179,19 @@ def auth_enabled_helper( def ldap_auth_enabled( - realm: Optional[Realm] = None, realm_authentication_methods: Optional[Dict[str, bool]] = None + realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None ) -> bool: return auth_enabled_helper(["LDAP"], realm, realm_authentication_methods) def email_auth_enabled( - realm: Optional[Realm] = None, realm_authentication_methods: Optional[Dict[str, bool]] = None + realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None ) -> bool: return auth_enabled_helper(["Email"], realm, realm_authentication_methods) def password_auth_enabled( - realm: Optional[Realm] = None, realm_authentication_methods: Optional[Dict[str, bool]] = None + realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None ) -> bool: return ldap_auth_enabled(realm, realm_authentication_methods) or email_auth_enabled( realm, realm_authentication_methods @@ -212,37 +199,37 @@ def password_auth_enabled( def dev_auth_enabled( - realm: Optional[Realm] = None, realm_authentication_methods: Optional[Dict[str, bool]] = None + realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None ) -> bool: return auth_enabled_helper(["Dev"], realm, realm_authentication_methods) def google_auth_enabled( - realm: Optional[Realm] = None, realm_authentication_methods: Optional[Dict[str, bool]] = None + realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None ) -> bool: return auth_enabled_helper(["Google"], realm, realm_authentication_methods) def github_auth_enabled( - realm: Optional[Realm] = None, realm_authentication_methods: Optional[Dict[str, bool]] = None + realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None ) -> bool: return auth_enabled_helper(["GitHub"], realm, realm_authentication_methods) def gitlab_auth_enabled( - realm: Optional[Realm] = None, realm_authentication_methods: Optional[Dict[str, bool]] = None + realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None ) -> bool: return auth_enabled_helper(["GitLab"], realm, realm_authentication_methods) def apple_auth_enabled( - realm: Optional[Realm] = None, realm_authentication_methods: Optional[Dict[str, bool]] = None + realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None ) -> bool: return auth_enabled_helper(["Apple"], realm, realm_authentication_methods) def saml_auth_enabled( - realm: Optional[Realm] = None, realm_authentication_methods: Optional[Dict[str, bool]] = None + realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None ) -> bool: return auth_enabled_helper(["SAML"], realm, realm_authentication_methods) @@ -253,7 +240,7 @@ def require_email_format_usernames(realm: Optional[Realm] = None) -> bool: return True -def is_user_active(user_profile: UserProfile, return_data: Optional[Dict[str, Any]] = None) -> bool: +def is_user_active(user_profile: UserProfile, return_data: Optional[dict[str, Any]] = None) -> bool: if user_profile.realm.deactivated: if return_data is not None: return_data["inactive_realm"] = True @@ -271,7 +258,7 @@ def is_user_active(user_profile: UserProfile, return_data: Optional[Dict[str, An def common_get_active_user( - email: str, realm: Realm, return_data: Optional[Dict[str, Any]] = None + email: str, realm: Realm, return_data: Optional[dict[str, Any]] = None ) -> Optional[UserProfile]: """This is the core common function used by essentially all authentication backends to check if there's an active user account @@ -299,7 +286,7 @@ def common_get_active_user( return user_profile -def is_subdomain_in_allowed_subdomains_list(subdomain: str, allowed_subdomains: List[str]) -> bool: +def is_subdomain_in_allowed_subdomains_list(subdomain: str, allowed_subdomains: list[str]) -> bool: if subdomain in allowed_subdomains: return True @@ -331,7 +318,7 @@ class RateLimitedAuthenticationByUsername(RateLimitedObject): return f"{type(self).__name__}:{self.username}" @override - def rules(self) -> List[Tuple[int, int]]: + def rules(self) -> list[tuple[int, int]]: return settings.RATE_LIMITING_RULES["authenticate_by_username"] @@ -419,7 +406,7 @@ def log_auth_attempt( realm: Realm, username: str, succeeded: bool, - return_data: Dict[str, Any], + return_data: dict[str, Any], ) -> None: ip_addr = request.META.get("REMOTE_ADDR") outcome = "success" if succeeded else "failed" @@ -446,7 +433,7 @@ class ZulipAuthMixin: # Describes which plans gives access to this authentication method on zulipchat.com. # None means the backend is available regardless of the plan. # Otherwise, it should be a list of Realm.plan_type values that give access to the backend. - available_for_cloud_plans: Optional[List[int]] = None + available_for_cloud_plans: Optional[list[int]] = None @property def logger(self) -> logging.Logger: @@ -482,7 +469,7 @@ class ZulipDummyBackend(ZulipAuthMixin): username: str, realm: Realm, use_dummy_backend: bool = False, - return_data: Optional[Dict[str, Any]] = None, + return_data: Optional[dict[str, Any]] = None, ) -> Optional[UserProfile]: if use_dummy_backend: return common_get_active_user(username, realm, return_data) @@ -527,7 +514,7 @@ class EmailAuthBackend(ZulipAuthMixin): username: str, password: str, realm: Realm, - return_data: Optional[Dict[str, Any]] = None, + return_data: Optional[dict[str, Any]] = None, ) -> Optional[UserProfile]: """Authenticate a user based on email address as the user name.""" if not password_auth_enabled(realm): @@ -601,7 +588,7 @@ def ldap_should_sync_active_status() -> bool: return False -def find_ldap_users_by_email(email: str) -> List[_LDAPUser]: +def find_ldap_users_by_email(email: str) -> list[_LDAPUser]: """ Returns list of _LDAPUsers matching the email search """ @@ -655,7 +642,7 @@ class LDAPReverseEmailSearch(_LDAPUser): # for only making a search query, so we pass an empty string. super().__init__(LDAPBackend(), username="") - def search_for_users(self, email: str) -> List[_LDAPUser]: + def search_for_users(self, email: str) -> list[_LDAPUser]: search = settings.AUTH_LDAP_REVERSE_EMAIL_SEARCH USERNAME_ATTR = settings.AUTH_LDAP_USERNAME_ATTR @@ -725,10 +712,10 @@ class ZulipLDAPAuthBackendBase(ZulipAuthMixin, LDAPBackend): def has_module_perms(self, user: Optional[UserProfile], app_label: Optional[str]) -> bool: return False - def get_all_permissions(self, user: Optional[UserProfile], obj: Any = None) -> Set[Any]: + def get_all_permissions(self, user: Optional[UserProfile], obj: Any = None) -> set[Any]: return set() - def get_group_permissions(self, user: Optional[UserProfile], obj: Any = None) -> Set[Any]: + def get_group_permissions(self, user: Optional[UserProfile], obj: Any = None) -> set[Any]: return set() def django_to_ldap_username(self, username: str) -> str: @@ -872,7 +859,7 @@ class ZulipLDAPAuthBackendBase(ZulipAuthMixin, LDAPBackend): # org_membership takes priority over AUTH_LDAP_ADVANCED_REALM_ACCESS_CONTROL. if "org_membership" in settings.AUTH_LDAP_USER_ATTR_MAP: org_membership_attr = settings.AUTH_LDAP_USER_ATTR_MAP["org_membership"] - allowed_orgs: List[str] = ldap_user.attrs.get(org_membership_attr, []) + allowed_orgs: list[str] = ldap_user.attrs.get(org_membership_attr, []) if is_subdomain_in_allowed_subdomains_list(realm.subdomain, allowed_orgs): return False # If Advanced is not configured, forbid access @@ -942,7 +929,7 @@ class ZulipLDAPAuthBackendBase(ZulipAuthMixin, LDAPBackend): def sync_custom_profile_fields_from_ldap( self, user_profile: UserProfile, ldap_user: _LDAPUser ) -> None: - values_by_var_name: Dict[str, Union[int, str, List[int]]] = {} + values_by_var_name: dict[str, Union[int, str, list[int]]] = {} for attr, ldap_attr in settings.AUTH_LDAP_USER_ATTR_MAP.items(): if not attr.startswith("custom_profile_field__"): continue @@ -1049,7 +1036,7 @@ class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase): realm: Realm, prereg_realm: Optional[PreregistrationRealm] = None, prereg_user: Optional[PreregistrationUser] = None, - return_data: Optional[Dict[str, Any]] = None, + return_data: Optional[dict[str, Any]] = None, ) -> Optional[UserProfile]: self._realm = realm self._prereg_user = prereg_user @@ -1078,7 +1065,7 @@ class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase): # authenticated user's data from LDAP. return super().authenticate(request=request, username=username, password=password) - def get_or_build_user(self, username: str, ldap_user: _LDAPUser) -> Tuple[UserProfile, bool]: + def get_or_build_user(self, username: str, ldap_user: _LDAPUser) -> tuple[UserProfile, bool]: """The main function of our authentication backend extension of django-auth-ldap. When this is called (from `authenticate`), django-auth-ldap will already have verified that the provided @@ -1093,7 +1080,7 @@ class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase): user account in the realm (assuming the realm is configured to allow that email address to sign up). """ - return_data: Dict[str, Any] = {} + return_data: dict[str, Any] = {} username = self.user_email_from_ldapuser(username, ldap_user) @@ -1158,7 +1145,7 @@ class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase): except JsonableError as e: raise ZulipLDAPError(e.msg) - opts: Dict[str, Any] = {} + opts: dict[str, Any] = {} if self._prereg_user: invited_as = self._prereg_user.invited_as opts["prereg_user"] = self._prereg_user @@ -1222,13 +1209,13 @@ class ZulipLDAPUserPopulator(ZulipLDAPAuthBackendBase): username: str, password: str, realm: Realm, - return_data: Optional[Dict[str, Any]] = None, + return_data: Optional[dict[str, Any]] = None, ) -> Optional[UserProfile]: return None def get_or_build_user( self, username: str, ldap_user: ZulipLDAPUser - ) -> Tuple[UserProfile, bool]: + ) -> tuple[UserProfile, bool]: """This is used only in non-authentication contexts such as: ./manage.py sync_ldap_user_data """ @@ -1327,7 +1314,7 @@ def sync_user_from_ldap(user_profile: UserProfile, logger: logging.Logger) -> bo # Quick tool to test whether you're correctly authenticating to LDAP -def query_ldap(email: str) -> List[str]: +def query_ldap(email: str) -> list[str]: values = [] backend = next( (backend for backend in get_backends() if isinstance(backend, LDAPBackend)), None @@ -1365,7 +1352,7 @@ class DevAuthBackend(ZulipAuthMixin): *, dev_auth_username: str, realm: Realm, - return_data: Optional[Dict[str, Any]] = None, + return_data: Optional[dict[str, Any]] = None, ) -> Optional[UserProfile]: if not dev_auth_enabled(realm): return None @@ -1398,7 +1385,7 @@ class ExternalAuthMethod(ABC): @classmethod @abstractmethod - def dict_representation(cls, realm: Optional[Realm] = None) -> List[ExternalAuthMethodDictT]: + def dict_representation(cls, realm: Optional[Realm] = None) -> list[ExternalAuthMethodDictT]: """ Method returning dictionaries representing the authentication methods corresponding to the backend that subclasses this. The documentation @@ -1409,10 +1396,10 @@ class ExternalAuthMethod(ABC): """ -EXTERNAL_AUTH_METHODS: List[Type[ExternalAuthMethod]] = [] +EXTERNAL_AUTH_METHODS: list[type[ExternalAuthMethod]] = [] -def external_auth_method(cls: Type[ExternalAuthMethod]) -> Type[ExternalAuthMethod]: +def external_auth_method(cls: type[ExternalAuthMethod]) -> type[ExternalAuthMethod]: assert issubclass(cls, ExternalAuthMethod) EXTERNAL_AUTH_METHODS.append(cls) @@ -1434,7 +1421,7 @@ class ExternalAuthDataDict(TypedDict, total=False): full_name_validated: bool # The mobile app doesn't actually use a session, so this # data is not applicable there. - params_to_store_in_authenticated_session: Dict[str, str] + params_to_store_in_authenticated_session: dict[str, str] class ExternalAuthResult: @@ -1537,9 +1524,9 @@ class SyncUserError(Exception): def sync_user_profile_custom_fields( - user_profile: UserProfile, custom_field_name_to_value: Dict[str, Any] + user_profile: UserProfile, custom_field_name_to_value: dict[str, Any] ) -> None: - fields_by_var_name: Dict[str, CustomProfileField] = {} + fields_by_var_name: dict[str, CustomProfileField] = {} custom_profile_fields = custom_profile_fields_for_realm(user_profile.realm.id) for field in custom_profile_fields: var_name = "_".join(field.name.lower().split(" ")) @@ -1550,7 +1537,7 @@ def sync_user_profile_custom_fields( var_name = "_".join(data["name"].lower().split(" ")) existing_values[var_name] = data["value"] - profile_data: List[ProfileDataElementUpdateDict] = [] + profile_data: list[ProfileDataElementUpdateDict] = [] for var_name, value in custom_field_name_to_value.items(): try: field = fields_by_var_name[var_name] @@ -1597,7 +1584,7 @@ class ZulipRemoteUserBackend(ZulipAuthMixin, RemoteUserBackend, ExternalAuthMeth *, remote_user: str, realm: Realm, - return_data: Optional[Dict[str, Any]] = None, + return_data: Optional[dict[str, Any]] = None, ) -> Optional[UserProfile]: if not auth_enabled_helper(["RemoteUser"], realm): return None @@ -1607,7 +1594,7 @@ class ZulipRemoteUserBackend(ZulipAuthMixin, RemoteUserBackend, ExternalAuthMeth @classmethod @override - def dict_representation(cls, realm: Optional[Realm] = None) -> List[ExternalAuthMethodDictT]: + def dict_representation(cls, realm: Optional[Realm] = None) -> list[ExternalAuthMethodDictT]: return [ dict( name=cls.name, @@ -1643,7 +1630,7 @@ def redirect_deactivated_user_to_login(realm: Realm, email: str) -> HttpResponse def social_associate_user_helper( - backend: BaseAuth, return_data: Dict[str, Any], *args: Any, **kwargs: Any + backend: BaseAuth, return_data: dict[str, Any], *args: Any, **kwargs: Any ) -> Union[HttpResponse, Optional[UserProfile]]: """Responsible for doing the Zulip account lookup and validation parts of the Zulip social auth pipeline (similar to the authenticate() @@ -1796,7 +1783,7 @@ def social_associate_user_helper( @partial def social_auth_associate_user( backend: BaseAuth, *args: Any, **kwargs: Any -) -> Union[HttpResponse, Dict[str, Any]]: +) -> Union[HttpResponse, dict[str, Any]]: """A simple wrapper function to reformat the return data from social_associate_user_helper as a dictionary. The python-social-auth infrastructure will then pass those values into @@ -1804,7 +1791,7 @@ def social_auth_associate_user( social_auth_finish, as kwargs. """ partial_token = backend.strategy.request_data().get("partial_token") - return_data: Dict[str, Any] = {} + return_data: dict[str, Any] = {} user_profile = social_associate_user_helper(backend, return_data, *args, **kwargs) if isinstance(user_profile, HttpResponse): @@ -1819,7 +1806,7 @@ def social_auth_associate_user( def social_auth_finish( - backend: Any, details: Dict[str, Any], response: HttpResponse, *args: Any, **kwargs: Any + backend: Any, details: dict[str, Any], response: HttpResponse, *args: Any, **kwargs: Any ) -> Optional[HttpResponse]: """Given the determination in social_auth_associate_user for whether the user should be authenticated, this takes care of actually @@ -2053,7 +2040,7 @@ class SocialAuthMixin(ZulipAuthMixin, ExternalAuthMethod, BaseAuth): def should_auto_signup(self) -> bool: return False - def get_params_to_store_in_authenticated_session(self) -> Dict[str, str]: + def get_params_to_store_in_authenticated_session(self) -> dict[str, str]: """ Specifies a dict of keys:values to be saved in the user's session after successfully authenticating. @@ -2062,7 +2049,7 @@ class SocialAuthMixin(ZulipAuthMixin, ExternalAuthMethod, BaseAuth): @classmethod @override - def dict_representation(cls, realm: Optional[Realm] = None) -> List[ExternalAuthMethodDictT]: + def dict_representation(cls, realm: Optional[Realm] = None) -> list[ExternalAuthMethodDictT]: return [ dict( name=cls.name, @@ -2081,7 +2068,7 @@ class GitHubAuthBackend(SocialAuthMixin, GithubOAuth2): sort_order = 100 display_icon = staticfiles_storage.url("images/authentication_backends/github-icon.png") - def get_all_associated_email_objects(self, *args: Any, **kwargs: Any) -> List[Dict[str, Any]]: + def get_all_associated_email_objects(self, *args: Any, **kwargs: Any) -> list[dict[str, Any]]: access_token = kwargs["response"]["access_token"] try: emails = self._user_data(access_token, "/emails") @@ -2092,18 +2079,18 @@ class GitHubAuthBackend(SocialAuthMixin, GithubOAuth2): emails = [] return emails - def get_unverified_emails(self, realm: Realm, *args: Any, **kwargs: Any) -> List[str]: + def get_unverified_emails(self, realm: Realm, *args: Any, **kwargs: Any) -> list[str]: return [ email_obj["email"] for email_obj in self.get_usable_email_objects(realm, *args, **kwargs) if not email_obj.get("verified") ] - def get_verified_emails(self, realm: Realm, *args: Any, **kwargs: Any) -> List[str]: + def get_verified_emails(self, realm: Realm, *args: Any, **kwargs: Any) -> list[str]: # We only let users log in using email addresses that are # verified by GitHub, because the whole point is for the user # to demonstrate that they control the target email address. - verified_emails: List[str] = [] + verified_emails: list[str] = [] for email_obj in [ obj for obj in self.get_usable_email_objects(realm, *args, **kwargs) @@ -2120,7 +2107,7 @@ class GitHubAuthBackend(SocialAuthMixin, GithubOAuth2): def get_usable_email_objects( self, realm: Realm, *args: Any, **kwargs: Any - ) -> List[Dict[str, Any]]: + ) -> list[dict[str, Any]]: # We disallow creation of new accounts with # @noreply.github.com/@users.noreply.github.com email # addresses, because structurally, we only want to allow email @@ -2140,7 +2127,7 @@ class GitHubAuthBackend(SocialAuthMixin, GithubOAuth2): ) ] - def user_data(self, access_token: str, *args: Any, **kwargs: Any) -> Dict[str, str]: + def user_data(self, access_token: str, *args: Any, **kwargs: Any) -> dict[str, str]: """This patched user_data function lets us combine together the 3 social auth backends into a single Zulip backend for GitHub OAuth2""" team_id = settings.SOCIAL_AUTH_GITHUB_TEAM_ID @@ -2206,8 +2193,8 @@ class GoogleAuthBackend(SocialAuthMixin, GoogleOAuth2): name = "google" display_icon = staticfiles_storage.url("images/authentication_backends/googl_e-icon.png") - def get_verified_emails(self, *args: Any, **kwargs: Any) -> List[str]: - verified_emails: List[str] = [] + def get_verified_emails(self, *args: Any, **kwargs: Any) -> list[str]: + verified_emails: list[str] = [] details = kwargs["response"] email_verified = details.get("email_verified") if email_verified: @@ -2324,7 +2311,7 @@ class AppleAuthBackend(SocialAuthMixin, AppleIdAuth): self.strategy.session_set(param, value) return request_state - def get_user_details(self, response: Dict[str, Any]) -> Dict[str, Any]: + def get_user_details(self, response: dict[str, Any]) -> dict[str, Any]: """ Overridden to correctly grab the user's name from the request params, as current upstream code expects it in the id_token and Apple changed @@ -2399,7 +2386,7 @@ class AppleAuthBackend(SocialAuthMixin, AppleIdAuth): class ZulipSAMLIdentityProvider(SAMLIdentityProvider): - def get_user_details(self, attributes: Dict[str, Any]) -> Dict[str, Any]: + def get_user_details(self, attributes: dict[str, Any]) -> dict[str, Any]: """ Overridden to support plumbing of additional Attributes from the SAMLResponse. @@ -2491,7 +2478,7 @@ class SAMLDocument: return None @abstractmethod - def get_issuers(self) -> List[str]: + def get_issuers(self) -> list[str]: """ Returns a list of the issuers of the SAML document. """ @@ -2499,7 +2486,7 @@ class SAMLDocument: class SAMLRequest(SAMLDocument): @override - def get_issuers(self) -> List[str]: + def get_issuers(self) -> list[str]: config = self.backend.generate_saml_config() saml_settings = OneLogin_Saml2_Settings(config, sp_validation_only=True) @@ -2517,7 +2504,7 @@ class SAMLRequest(SAMLDocument): class SAMLResponse(SAMLDocument): @override - def get_issuers(self) -> List[str]: + def get_issuers(self) -> list[str]: config = self.backend.generate_saml_config() saml_settings = OneLogin_Saml2_Settings(config, sp_validation_only=True) @@ -2641,7 +2628,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth): return auth.login(return_to=relay_state) @classmethod - def put_data_in_redis(cls, data_to_relay: Dict[str, Any]) -> str: + def put_data_in_redis(cls, data_to_relay: dict[str, Any]) -> str: return put_dict_in_redis( redis_client, "saml_token_{token}", @@ -2650,7 +2637,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth): ) @classmethod - def get_data_from_redis(cls, key: str) -> Optional[Dict[str, Any]]: + def get_data_from_redis(cls, key: str) -> Optional[dict[str, Any]]: data = None if key.startswith("saml_token_"): # Safety if statement, to not allow someone to poke around arbitrary Redis keys here. @@ -2658,7 +2645,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth): return data - def get_relayed_params(self) -> Dict[str, Any]: + def get_relayed_params(self) -> dict[str, Any]: request_data = self.strategy.request_data() if "RelayState" not in request_data: return {} @@ -2677,7 +2664,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth): except orjson.JSONDecodeError: return {} - def choose_subdomain(self, relayed_params: Dict[str, Any]) -> Optional[str]: + def choose_subdomain(self, relayed_params: dict[str, Any]) -> Optional[str]: subdomain = relayed_params.get("subdomain") if subdomain is not None: return subdomain @@ -2699,7 +2686,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth): return request_subdomain def _check_entitlements( - self, idp: SAMLIdentityProvider, attributes: Dict[str, List[str]] + self, idp: SAMLIdentityProvider, attributes: dict[str, list[str]] ) -> None: """ Below is the docstring from the social_core SAML backend. @@ -2720,7 +2707,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth): return subdomain = self.strategy.session_get("subdomain") - entitlements: Union[str, List[str]] = attributes.get(org_membership_attribute, []) + entitlements: Union[str, list[str]] = attributes.get(org_membership_attribute, []) if isinstance(entitlements, str): # nocoverage # This shouldn't happen as we'd always expect a list from this attribute even # if it only has one element, but it's safer to have this defensive code. @@ -2788,7 +2775,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth): ) return None - return_data: Dict[str, Any] = {} + return_data: dict[str, Any] = {} realm = get_realm(subdomain) user_profile = common_get_active_user(name_id, realm, return_data) @@ -2961,8 +2948,8 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth): @classmethod @override - def dict_representation(cls, realm: Optional[Realm] = None) -> List[ExternalAuthMethodDictT]: - result: List[ExternalAuthMethodDictT] = [] + def dict_representation(cls, realm: Optional[Realm] = None) -> list[ExternalAuthMethodDictT]: + result: list[ExternalAuthMethodDictT] = [] for idp_name, idp_dict in settings.SOCIAL_AUTH_SAML_ENABLED_IDPS.items(): if realm and not cls.validate_idp_for_subdomain(idp_name, realm.subdomain): continue @@ -2995,7 +2982,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth): return auto_signup @override - def get_params_to_store_in_authenticated_session(self) -> Dict[str, str]: + def get_params_to_store_in_authenticated_session(self) -> dict[str, str]: idp_name = self.strategy.session_get("saml_idp_name") saml_session_index = self.strategy.session_get("saml_session_index") @@ -3041,7 +3028,7 @@ class GenericOpenIdConnectBackend(SocialAuthMixin, OpenIdConnectAuth): # configuration from. OIDC_ENDPOINT = settings_dict.get("oidc_url") - def get_key_and_secret(self) -> Tuple[str, str]: + def get_key_and_secret(self) -> tuple[str, str]: client_id = self.settings_dict.get("client_id", "") assert isinstance(client_id, str) secret = self.settings_dict.get("secret", "") @@ -3063,7 +3050,7 @@ class GenericOpenIdConnectBackend(SocialAuthMixin, OpenIdConnectAuth): @classmethod @override - def dict_representation(cls, realm: Optional[Realm] = None) -> List[ExternalAuthMethodDictT]: + def dict_representation(cls, realm: Optional[Realm] = None) -> list[ExternalAuthMethodDictT]: return [ dict( name=f"oidc:{cls.name}", @@ -3176,12 +3163,12 @@ class SAMLSPInitiatedLogout: return HttpResponseRedirect(settings.LOGIN_URL) -def get_external_method_dicts(realm: Optional[Realm] = None) -> List[ExternalAuthMethodDictT]: +def get_external_method_dicts(realm: Optional[Realm] = None) -> list[ExternalAuthMethodDictT]: """ Returns a list of dictionaries that represent social backends, sorted in the order in which they should be displayed. """ - result: List[ExternalAuthMethodDictT] = [] + result: list[ExternalAuthMethodDictT] = [] for backend in EXTERNAL_AUTH_METHODS: # EXTERNAL_AUTH_METHODS is already sorted in the correct order, # so we don't need to worry about sorting here. @@ -3191,7 +3178,7 @@ def get_external_method_dicts(realm: Optional[Realm] = None) -> List[ExternalAut return result -AUTH_BACKEND_NAME_MAP: Dict[str, Any] = { +AUTH_BACKEND_NAME_MAP: dict[str, Any] = { "Dev": DevAuthBackend, "Email": EmailAuthBackend, "LDAP": ZulipLDAPAuthBackend, diff --git a/zproject/computed_settings.py b/zproject/computed_settings.py index 9b39fdcded..6c70b5a496 100644 --- a/zproject/computed_settings.py +++ b/zproject/computed_settings.py @@ -3,7 +3,7 @@ import os import sys import time from copy import deepcopy -from typing import Any, Dict, Final, List, Literal, Tuple, Union +from typing import Any, Final, Literal, Union from urllib.parse import urljoin from scripts.lib.zulip_tools import get_tornado_ports @@ -261,7 +261,7 @@ SILENCED_SYSTEM_CHECKS = [ # We implement these options with a default DATABASES configuration # supporting peer authentication, with logic to override it as # appropriate if DEVELOPMENT or REMOTE_POSTGRES_HOST is set. -DATABASES: Dict[str, Dict[str, Any]] = { +DATABASES: dict[str, dict[str, Any]] = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": get_config("postgresql", "database_name", "zulip"), @@ -332,7 +332,7 @@ SESSION_ENGINE = "zerver.lib.safe_session_cached_db" MEMCACHED_PASSWORD = get_secret("memcached_password") -CACHES: Dict[str, Dict[str, object]] = { +CACHES: dict[str, dict[str, object]] = { "default": { "BACKEND": "zerver.lib.singleton_bmemcached.SingletonBMemcached", "LOCATION": MEMCACHED_LOCATION, @@ -484,7 +484,7 @@ INTERNAL_BOTS = [ ] # Bots that are created for each realm like the reminder-bot goes here. -REALM_INTERNAL_BOTS: List[Dict[str, str]] = [] +REALM_INTERNAL_BOTS: list[dict[str, str]] = [] # These are realm-internal bots that may exist in some organizations, # so configure power the setting, but should not be auto-created at this time. DISABLED_REALM_INTERNAL_BOTS = [ @@ -576,7 +576,7 @@ else: ######################################################################## # List of callables that know how to import templates from various sources. -LOADERS: List[Union[str, Tuple[object, ...]]] = [ +LOADERS: list[Union[str, tuple[object, ...]]] = [ "django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader", ] @@ -584,7 +584,7 @@ if PRODUCTION: # Template caching is a significant performance win in production. LOADERS = [("django.template.loaders.cached.Loader", LOADERS)] -base_template_engine_settings: Dict[str, Any] = { +base_template_engine_settings: dict[str, Any] = { "BACKEND": "django.template.backends.jinja2.Jinja2", "OPTIONS": { "environment": "zproject.jinja2.environment", @@ -730,7 +730,7 @@ def file_handler( filename: str, level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "DEBUG", formatter: str = "default", -) -> Dict[str, str]: +) -> dict[str, str]: return { "filename": filename, "level": level, @@ -739,7 +739,7 @@ def file_handler( } -LOGGING: Dict[str, Any] = { +LOGGING: dict[str, Any] = { "version": 1, "disable_existing_loggers": False, "formatters": { diff --git a/zproject/default_settings.py b/zproject/default_settings.py index e46b0f3495..e7b8bd83fd 100644 --- a/zproject/default_settings.py +++ b/zproject/default_settings.py @@ -1,6 +1,6 @@ import os from email.headerregistry import Address -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Literal, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union from django_auth_ldap.config import GroupOfUniqueNamesType, LDAPGroupType @@ -28,7 +28,7 @@ STATIC_URL: Optional[str] = None # install of the Zulip server. # Extra HTTP "Host" values to allow (standard ones added in computed_settings.py) -ALLOWED_HOSTS: List[str] = [] +ALLOWED_HOSTS: list[str] = [] # Basic email settings NOREPLY_EMAIL_ADDRESS = Address(username="noreply", domain=EXTERNAL_HOST_WITHOUT_PORT).addr_spec @@ -54,13 +54,13 @@ AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None AUTH_LDAP_USERNAME_ATTR: Optional[str] = None # AUTH_LDAP_USER_ATTR_MAP is uncommented in prod_settings_template.py, # so the value here mainly serves to help document the default. -AUTH_LDAP_USER_ATTR_MAP: Dict[str, str] = { +AUTH_LDAP_USER_ATTR_MAP: dict[str, str] = { "full_name": "cn", } # Automatically deactivate users not found by the AUTH_LDAP_USER_SEARCH query. LDAP_DEACTIVATE_NON_MATCHING_USERS: Optional[bool] = None # AUTH_LDAP_CONNECTION_OPTIONS: we set ldap.OPT_REFERRALS in settings.py if unset. -AUTH_LDAP_CONNECTION_OPTIONS: Dict[int, object] = {} +AUTH_LDAP_CONNECTION_OPTIONS: dict[int, object] = {} # Disable django-auth-ldap caching, to prevent problems with OU changes. AUTH_LDAP_CACHE_TIMEOUT = 0 # Disable syncing user on each login; Using sync_ldap_user_data cron is recommended. @@ -70,8 +70,8 @@ AUTH_LDAP_ALWAYS_UPDATE_USER = False # Detailed docs in zproject/dev_settings.py. FAKE_LDAP_MODE: Optional[str] = None FAKE_LDAP_NUM_USERS = 8 -AUTH_LDAP_ADVANCED_REALM_ACCESS_CONTROL: Optional[Dict[str, Any]] = None -LDAP_SYNCHRONIZED_GROUPS_BY_REALM: Dict[str, List[str]] = {} +AUTH_LDAP_ADVANCED_REALM_ACCESS_CONTROL: Optional[dict[str, Any]] = None +LDAP_SYNCHRONIZED_GROUPS_BY_REALM: dict[str, list[str]] = {} AUTH_LDAP_GROUP_TYPE: LDAPGroupType = GroupOfUniqueNamesType() # Social auth; we support providing values for some of these @@ -87,11 +87,11 @@ SOCIAL_AUTH_GOOGLE_KEY = get_secret("social_auth_google_key", development_only=T SOCIAL_AUTH_SAML_SP_ENTITY_ID: Optional[str] = None SOCIAL_AUTH_SAML_SP_PUBLIC_CERT = "" SOCIAL_AUTH_SAML_SP_PRIVATE_KEY = "" -SOCIAL_AUTH_SAML_ORG_INFO: Optional[Dict[str, Dict[str, str]]] = None -SOCIAL_AUTH_SAML_TECHNICAL_CONTACT: Optional[Dict[str, str]] = None -SOCIAL_AUTH_SAML_SUPPORT_CONTACT: Optional[Dict[str, str]] = None -SOCIAL_AUTH_SAML_ENABLED_IDPS: Dict[str, SAMLIdPConfigDict] = {} -SOCIAL_AUTH_SAML_SECURITY_CONFIG: Dict[str, Any] = {} +SOCIAL_AUTH_SAML_ORG_INFO: Optional[dict[str, dict[str, str]]] = None +SOCIAL_AUTH_SAML_TECHNICAL_CONTACT: Optional[dict[str, str]] = None +SOCIAL_AUTH_SAML_SUPPORT_CONTACT: Optional[dict[str, str]] = None +SOCIAL_AUTH_SAML_ENABLED_IDPS: dict[str, SAMLIdPConfigDict] = {} +SOCIAL_AUTH_SAML_SECURITY_CONFIG: dict[str, Any] = {} # Set this to True to enforce that any configured IdP needs to specify # the limit_to_subdomains setting to be considered valid: SAML_REQUIRE_LIMIT_TO_SUBDOMAINS = False @@ -108,10 +108,10 @@ SOCIAL_AUTH_APPLE_SCOPE = ["name", "email"] SOCIAL_AUTH_APPLE_EMAIL_AS_USERNAME = True # Generic OpenID Connect: -SOCIAL_AUTH_OIDC_ENABLED_IDPS: Dict[str, OIDCIdPConfigDict] = {} +SOCIAL_AUTH_OIDC_ENABLED_IDPS: dict[str, OIDCIdPConfigDict] = {} SOCIAL_AUTH_OIDC_FULL_NAME_VALIDATED = False -SOCIAL_AUTH_SYNC_CUSTOM_ATTRS_DICT: Dict[str, Dict[str, Dict[str, str]]] = {} +SOCIAL_AUTH_SYNC_CUSTOM_ATTRS_DICT: dict[str, dict[str, dict[str, str]]] = {} # Other auth SSO_APPEND_DOMAIN: Optional[str] = None @@ -136,7 +136,7 @@ LOGGING_SHOW_PID = False # Sentry.io error defaults to off SENTRY_DSN: Optional[str] = get_config("sentry", "project_dsn", None) -SENTRY_TRACE_WORKER_RATE: Union[float, Dict[str, float]] = 0.0 +SENTRY_TRACE_WORKER_RATE: Union[float, dict[str, float]] = 0.0 SENTRY_TRACE_RATE: float = 0.0 SENTRY_PROFILE_RATE: float = 0.1 SENTRY_FRONTEND_DSN: Optional[str] = get_config("sentry", "frontend_project_dsn", None) @@ -202,7 +202,7 @@ REMOTE_POSTGRES_PORT = "" REMOTE_POSTGRES_SSLMODE = "" THUMBNAIL_IMAGES = False -TORNADO_PORTS: List[int] = [] +TORNADO_PORTS: list[int] = [] USING_TORNADO = True # ToS/Privacy templates @@ -307,7 +307,7 @@ DEFAULT_RATE_LIMITING_RULES = { # Rate limiting defaults can be individually overridden by adding # entries in this object, which is merged with # DEFAULT_RATE_LIMITING_RULES. -RATE_LIMITING_RULES: Dict[str, List[Tuple[int, int]]] = {} +RATE_LIMITING_RULES: dict[str, list[tuple[int, int]]] = {} # Two factor authentication is not yet implementation-complete TWO_FACTOR_AUTHENTICATION_ENABLED = False @@ -384,13 +384,13 @@ DEMO_ORG_DEADLINE_DAYS = 30 # their usual subdomains. Keys are realm string_ids (aka subdomains), # and values are alternate hosts. # The values will also be added to ALLOWED_HOSTS. -REALM_HOSTS: Dict[str, str] = {} +REALM_HOSTS: dict[str, str] = {} # Map used to rewrite the URIs for certain realms during mobile # authentication. This, combined with adding the relevant hosts to # ALLOWED_HOSTS, can be used for environments where security policies # mean that a different hostname must be used for mobile access. -REALM_MOBILE_REMAP_URIS: Dict[str, str] = {} +REALM_MOBILE_REMAP_URIS: dict[str, str] = {} # Whether the server is using the PGroonga full-text search # backend. Plan is to turn this on for everyone after further @@ -485,7 +485,7 @@ FIRST_TIME_TERMS_OF_SERVICE_TEMPLATE: Optional[str] = None TERMS_OF_SERVICE_MESSAGE: Optional[str] = None # Configuration for JWT auth (sign in and API key fetch) -JWT_AUTH_KEYS: Dict[str, JwtAuthKey] = {} +JWT_AUTH_KEYS: dict[str, JwtAuthKey] = {} # https://docs.djangoproject.com/en/5.0/ref/settings/#std:setting-SERVER_EMAIL # Django setting for what from address to use in error emails. @@ -494,7 +494,7 @@ SERVER_EMAIL = ZULIP_ADMINISTRATOR ADMINS = (("Zulip Administrator", ZULIP_ADMINISTRATOR),) # From address for welcome emails. -WELCOME_EMAIL_SENDER: Optional[Dict[str, str]] = None +WELCOME_EMAIL_SENDER: Optional[dict[str, str]] = None # Whether to send periodic digests of activity. SEND_DIGEST_EMAILS = True diff --git a/zproject/dev_settings.py b/zproject/dev_settings.py index f191f46d72..e17310ed09 100644 --- a/zproject/dev_settings.py +++ b/zproject/dev_settings.py @@ -1,6 +1,6 @@ import os import pwd -from typing import Dict, Optional, Set, Tuple +from typing import Optional from scripts.lib.zulip_tools import deport from zproject.settings_types import SCIMConfigDict @@ -52,7 +52,7 @@ ALLOWED_HOSTS = ["*"] # Uncomment extra backends if you want to test with them. Note that # for Google and GitHub auth you'll need to do some pre-setup. -AUTHENTICATION_BACKENDS: Tuple[str, ...] = ( +AUTHENTICATION_BACKENDS: tuple[str, ...] = ( "zproject.backends.DevAuthBackend", "zproject.backends.EmailAuthBackend", "zproject.backends.GitHubAuthBackend", @@ -98,7 +98,7 @@ TERMS_OF_SERVICE_MESSAGE: Optional[str] = "Description of changes to the ToS!" EMBEDDED_BOTS_ENABLED = True -SYSTEM_ONLY_REALMS: Set[str] = set() +SYSTEM_ONLY_REALMS: set[str] = set() USING_PGROONGA = True # Flush cache after migration. POST_MIGRATION_CACHE_FLUSHING = True @@ -196,7 +196,7 @@ SOCIAL_AUTH_SUBDOMAIN = "auth" MEMCACHED_USERNAME: Optional[str] = None -SCIM_CONFIG: Dict[str, SCIMConfigDict] = { +SCIM_CONFIG: dict[str, SCIMConfigDict] = { "zulip": { "bearer_token": "token1234", "scim_client_name": "test-scim-client", diff --git a/zproject/prod_settings_template.py b/zproject/prod_settings_template.py index 911364d44d..0f2579e24f 100644 --- a/zproject/prod_settings_template.py +++ b/zproject/prod_settings_template.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Tuple +from typing import Any from .config import get_secret @@ -148,7 +148,7 @@ EMAIL_GATEWAY_IMAP_FOLDER = "INBOX" ## The install process requires EmailAuthBackend (the default) to be ## enabled. If you want to disable it, do so after creating the ## initial realm and user. -AUTHENTICATION_BACKENDS: Tuple[str, ...] = ( +AUTHENTICATION_BACKENDS: tuple[str, ...] = ( "zproject.backends.EmailAuthBackend", # Email and password; just requires SMTP setup # "zproject.backends.GoogleAuthBackend", # Google auth, setup below # "zproject.backends.GitHubAuthBackend", # GitHub auth, setup below @@ -375,7 +375,7 @@ AUTH_LDAP_USER_ATTR_MAP = { ## https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#openid-connect ## -SOCIAL_AUTH_OIDC_ENABLED_IDPS: Dict[str, Any] = { +SOCIAL_AUTH_OIDC_ENABLED_IDPS: dict[str, Any] = { ## This field (example: "idp_name") may appear in URLs during ## authentication, but is otherwise not user-visible. "idp_name": { @@ -421,7 +421,7 @@ SOCIAL_AUTH_SAML_ORG_INFO = { "url": "{}{}".format("https://", EXTERNAL_HOST), }, } -SOCIAL_AUTH_SAML_ENABLED_IDPS: Dict[str, Any] = { +SOCIAL_AUTH_SAML_ENABLED_IDPS: dict[str, Any] = { ## The fields are explained in detail here: ## https://python-social-auth.readthedocs.io/en/latest/backends/saml.html "idp_name": { @@ -479,7 +479,7 @@ SOCIAL_AUTH_SAML_ENABLED_IDPS: Dict[str, Any] = { # More complete documentation of the configurable security settings # are available in the "security" part of https://github.com/onelogin/python3-saml#settings. -SOCIAL_AUTH_SAML_SECURITY_CONFIG: Dict[str, Any] = { +SOCIAL_AUTH_SAML_SECURITY_CONFIG: dict[str, Any] = { ## If you've set up the optional private and public server keys, ## set this to True to enable signing of SAMLRequests using the ## private key. diff --git a/zproject/sentry.py b/zproject/sentry.py index abd506771a..e274c1ad41 100644 --- a/zproject/sentry.py +++ b/zproject/sentry.py @@ -1,5 +1,5 @@ import os -from typing import TYPE_CHECKING, Any, Dict, Optional, Union +from typing import TYPE_CHECKING, Any, Optional, Union import sentry_sdk from django.utils.translation import override as override_language @@ -50,7 +50,7 @@ def add_context(event: "Event", hint: "Hint") -> Optional["Event"]: return event -def traces_sampler(sampling_context: Dict[str, Any]) -> Union[float, bool]: +def traces_sampler(sampling_context: dict[str, Any]) -> Union[float, bool]: from django.conf import settings queue = sampling_context.get("queue") diff --git a/zproject/settings_types.py b/zproject/settings_types.py index 6855a41769..c14d0a16d1 100644 --- a/zproject/settings_types.py +++ b/zproject/settings_types.py @@ -1,11 +1,11 @@ -from typing import List, Optional, TypedDict +from typing import Optional, TypedDict class JwtAuthKey(TypedDict): key: str # See https://pyjwt.readthedocs.io/en/latest/algorithms.html for a list # of supported algorithms. - algorithms: List[str] + algorithms: list[str] class SAMLIdPConfigDict(TypedDict, total=False): @@ -22,8 +22,8 @@ class SAMLIdPConfigDict(TypedDict, total=False): auto_signup: bool display_name: str display_icon: str - limit_to_subdomains: List[str] - extra_attrs: List[str] + limit_to_subdomains: list[str] + extra_attrs: list[str] x509cert: str x509cert_path: str diff --git a/zproject/template_loaders.py b/zproject/template_loaders.py index 8ab119e66f..37a2132088 100644 --- a/zproject/template_loaders.py +++ b/zproject/template_loaders.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import List, Union +from typing import Union from django.template.loaders import app_directories from typing_extensions import override @@ -7,11 +7,11 @@ from typing_extensions import override class TwoFactorLoader(app_directories.Loader): @override - def get_dirs(self) -> List[Union[str, Path]]: + def get_dirs(self) -> list[Union[str, Path]]: dirs = super().get_dirs() # app_directories.Loader returns only a list of # Path objects by calling get_app_template_dirs - two_factor_dirs: List[Union[str, Path]] = [] + two_factor_dirs: list[Union[str, Path]] = [] for d in dirs: assert isinstance(d, Path) if d.match("two_factor/*"): diff --git a/zproject/test_extra_settings.py b/zproject/test_extra_settings.py index c44cd7171b..483b593d89 100644 --- a/zproject/test_extra_settings.py +++ b/zproject/test_extra_settings.py @@ -1,5 +1,5 @@ import os -from typing import Dict, List, Optional, Tuple +from typing import Optional import ldap from django_auth_ldap.config import LDAPSearch @@ -24,7 +24,7 @@ PUPPETEER_TESTS = "PUPPETEER_TESTS" in os.environ FAKE_EMAIL_DOMAIN = "zulip.testserver" # Clear out the REALM_HOSTS set in dev_settings.py -REALM_HOSTS: Dict[str, str] = {} +REALM_HOSTS: dict[str, str] = {} DATABASES["default"] = { "NAME": os.getenv("ZULIP_DB_NAME", "zulip_test"), @@ -181,7 +181,7 @@ SOCIAL_AUTH_APPLE_TEAM = "TEAMSTRING" SOCIAL_AUTH_APPLE_SECRET = get_from_file_if_exists("zerver/tests/fixtures/apple/private_key.pem") -SOCIAL_AUTH_OIDC_ENABLED_IDPS: Dict[str, OIDCIdPConfigDict] = { +SOCIAL_AUTH_OIDC_ENABLED_IDPS: dict[str, OIDCIdPConfigDict] = { "testoidc": { "display_name": "Test OIDC", "oidc_url": "https://example.com/api/openid", @@ -231,7 +231,7 @@ SOCIAL_AUTH_SAML_SUPPORT_CONTACT = { "emailAddress": "support@example.com", } -SOCIAL_AUTH_SAML_ENABLED_IDPS: Dict[str, SAMLIdPConfigDict] = { +SOCIAL_AUTH_SAML_ENABLED_IDPS: dict[str, SAMLIdPConfigDict] = { "test_idp": { "entity_id": "https://idp.testshib.org/idp/shibboleth", "url": "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO", @@ -247,7 +247,7 @@ SOCIAL_AUTH_SAML_ENABLED_IDPS: Dict[str, SAMLIdPConfigDict] = { }, } -RATE_LIMITING_RULES: Dict[str, List[Tuple[int, int]]] = { +RATE_LIMITING_RULES: dict[str, list[tuple[int, int]]] = { "api_by_user": [], "api_by_ip": [], "api_by_remote_server": [], @@ -261,7 +261,7 @@ RATE_LIMITING_RULES: Dict[str, List[Tuple[int, int]]] = { CLOUD_FREE_TRIAL_DAYS: Optional[int] = None SELF_HOSTING_FREE_TRIAL_DAYS: Optional[int] = None -SCIM_CONFIG: Dict[str, SCIMConfigDict] = { +SCIM_CONFIG: dict[str, SCIMConfigDict] = { "zulip": { "bearer_token": "token1234", "scim_client_name": "test-scim-client", diff --git a/zproject/urls.py b/zproject/urls.py index 85dc621122..41dd6497b4 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -1,5 +1,5 @@ import os -from typing import List, Union +from typing import Union from django.conf import settings from django.conf.urls import include @@ -632,7 +632,7 @@ i18n_urls = [ ] # Make a copy of i18n_urls so that they appear without prefix for english -urls: List[Union[URLPattern, URLResolver]] = list(i18n_urls) +urls: list[Union[URLPattern, URLResolver]] = list(i18n_urls) # Include the dual-use patterns twice urls += [