mirror of https://github.com/zulip/zulip.git
ruff: Fix UP006 Use `list` instead of `List` for type annotation.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
parent
c2214b3904
commit
e08a24e47f
|
@ -2,7 +2,7 @@ import logging
|
||||||
import time
|
import time
|
||||||
from collections import OrderedDict, defaultdict
|
from collections import OrderedDict, defaultdict
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Callable, Dict, List, Optional, Sequence, Tuple, Type, Union
|
from typing import Callable, Optional, Sequence, Union
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection, models
|
from django.db import connection, models
|
||||||
|
@ -82,7 +82,7 @@ class CountStat:
|
||||||
|
|
||||||
|
|
||||||
class LoggingCountStat(CountStat):
|
class LoggingCountStat(CountStat):
|
||||||
def __init__(self, property: str, output_table: Type[BaseCount], frequency: str) -> None:
|
def __init__(self, property: str, output_table: type[BaseCount], frequency: str) -> None:
|
||||||
CountStat.__init__(self, property, DataCollector(output_table, None), frequency)
|
CountStat.__init__(self, property, DataCollector(output_table, None), frequency)
|
||||||
|
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ class DependentCountStat(CountStat):
|
||||||
class DataCollector:
|
class DataCollector:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
output_table: Type[BaseCount],
|
output_table: type[BaseCount],
|
||||||
pull_function: Optional[Callable[[str, datetime, datetime, Optional[Realm]], int]],
|
pull_function: Optional[Callable[[str, datetime, datetime, Optional[Realm]], int]],
|
||||||
) -> None:
|
) -> None:
|
||||||
self.output_table = output_table
|
self.output_table = output_table
|
||||||
|
@ -311,8 +311,8 @@ def do_increment_logging_stat(
|
||||||
return
|
return
|
||||||
|
|
||||||
table = stat.data_collector.output_table
|
table = stat.data_collector.output_table
|
||||||
id_args: Dict[str, Union[int, None]] = {}
|
id_args: dict[str, Union[int, None]] = {}
|
||||||
conflict_args: List[str] = []
|
conflict_args: list[str] = []
|
||||||
if table == RealmCount:
|
if table == RealmCount:
|
||||||
assert isinstance(model_object_for_bucket, Realm)
|
assert isinstance(model_object_for_bucket, Realm)
|
||||||
id_args = {"realm_id": model_object_for_bucket.id}
|
id_args = {"realm_id": model_object_for_bucket.id}
|
||||||
|
@ -425,7 +425,7 @@ def do_drop_single_stat(property: str) -> None:
|
||||||
|
|
||||||
## DataCollector-level operations ##
|
## DataCollector-level operations ##
|
||||||
|
|
||||||
QueryFn: TypeAlias = Callable[[Dict[str, Composable]], Composable]
|
QueryFn: TypeAlias = Callable[[dict[str, Composable]], Composable]
|
||||||
|
|
||||||
|
|
||||||
def do_pull_by_sql_query(
|
def do_pull_by_sql_query(
|
||||||
|
@ -433,7 +433,7 @@ def do_pull_by_sql_query(
|
||||||
start_time: datetime,
|
start_time: datetime,
|
||||||
end_time: datetime,
|
end_time: datetime,
|
||||||
query: QueryFn,
|
query: QueryFn,
|
||||||
group_by: Optional[Tuple[Type[models.Model], str]],
|
group_by: Optional[tuple[type[models.Model], str]],
|
||||||
) -> int:
|
) -> int:
|
||||||
if group_by is None:
|
if group_by is None:
|
||||||
subgroup: Composable = SQL("NULL")
|
subgroup: Composable = SQL("NULL")
|
||||||
|
@ -467,9 +467,9 @@ def do_pull_by_sql_query(
|
||||||
|
|
||||||
|
|
||||||
def sql_data_collector(
|
def sql_data_collector(
|
||||||
output_table: Type[BaseCount],
|
output_table: type[BaseCount],
|
||||||
query: QueryFn,
|
query: QueryFn,
|
||||||
group_by: Optional[Tuple[Type[models.Model], str]],
|
group_by: Optional[tuple[type[models.Model], str]],
|
||||||
) -> DataCollector:
|
) -> DataCollector:
|
||||||
def pull_function(
|
def pull_function(
|
||||||
property: str, start_time: datetime, end_time: datetime, realm: Optional[Realm] = None
|
property: str, start_time: datetime, end_time: datetime, realm: Optional[Realm] = None
|
||||||
|
@ -533,7 +533,7 @@ def do_pull_minutes_active(
|
||||||
.values_list("user_profile_id", "user_profile__realm_id", "start", "end")
|
.values_list("user_profile_id", "user_profile__realm_id", "start", "end")
|
||||||
)
|
)
|
||||||
|
|
||||||
seconds_active: Dict[Tuple[int, int], float] = defaultdict(float)
|
seconds_active: dict[tuple[int, int], float] = defaultdict(float)
|
||||||
for user_id, realm_id, interval_start, interval_end in user_activity_intervals:
|
for user_id, realm_id, interval_start, interval_end in user_activity_intervals:
|
||||||
if realm is None or realm.id == realm_id:
|
if realm is None or realm.id == realm_id:
|
||||||
start = max(start_time, interval_start)
|
start = max(start_time, interval_start)
|
||||||
|
@ -817,7 +817,7 @@ count_stream_by_realm_query = lambda kwargs: SQL(
|
||||||
).format(**kwargs)
|
).format(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
def get_count_stats(realm: Optional[Realm] = None) -> Dict[str, CountStat]:
|
def get_count_stats(realm: Optional[Realm] = None) -> dict[str, CountStat]:
|
||||||
## CountStat declarations ##
|
## CountStat declarations ##
|
||||||
|
|
||||||
count_stats_ = [
|
count_stats_ = [
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
from math import sqrt
|
from math import sqrt
|
||||||
from random import Random
|
from random import Random
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from analytics.lib.counts import CountStat
|
from analytics.lib.counts import CountStat
|
||||||
|
|
||||||
|
@ -16,7 +15,7 @@ def generate_time_series_data(
|
||||||
frequency: str = CountStat.DAY,
|
frequency: str = CountStat.DAY,
|
||||||
partial_sum: bool = False,
|
partial_sum: bool = False,
|
||||||
random_seed: int = 26,
|
random_seed: int = 26,
|
||||||
) -> List[int]:
|
) -> list[int]:
|
||||||
"""
|
"""
|
||||||
Generate semi-realistic looking time series data for testing analytics graphs.
|
Generate semi-realistic looking time series data for testing analytics graphs.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import List, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from analytics.lib.counts import CountStat
|
from analytics.lib.counts import CountStat
|
||||||
from zerver.lib.timestamp import floor_to_day, floor_to_hour, verify_UTC
|
from zerver.lib.timestamp import floor_to_day, floor_to_hour, verify_UTC
|
||||||
|
@ -11,7 +11,7 @@ from zerver.lib.timestamp import floor_to_day, floor_to_hour, verify_UTC
|
||||||
# and time_range(Sep 20, Sep 22, day, 5) returns [Sep 18, Sep 19, Sep 20, Sep 21, Sep 22]
|
# and time_range(Sep 20, Sep 22, day, 5) returns [Sep 18, Sep 19, Sep 20, Sep 21, Sep 22]
|
||||||
def time_range(
|
def time_range(
|
||||||
start: datetime, end: datetime, frequency: str, min_length: Optional[int]
|
start: datetime, end: datetime, frequency: str, min_length: Optional[int]
|
||||||
) -> List[datetime]:
|
) -> list[datetime]:
|
||||||
verify_UTC(start)
|
verify_UTC(start)
|
||||||
verify_UTC(end)
|
verify_UTC(end)
|
||||||
if frequency == CountStat.HOUR:
|
if frequency == CountStat.HOUR:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Any, Dict, List, Mapping, Type, Union
|
from typing import Any, Mapping, Union
|
||||||
|
|
||||||
from django.core.files.uploadedfile import UploadedFile
|
from django.core.files.uploadedfile import UploadedFile
|
||||||
from django.utils.timezone import now as timezone_now
|
from django.utils.timezone import now as timezone_now
|
||||||
|
@ -53,7 +53,7 @@ class Command(ZulipBaseCommand):
|
||||||
spikiness: float,
|
spikiness: float,
|
||||||
holiday_rate: float = 0,
|
holiday_rate: float = 0,
|
||||||
partial_sum: bool = False,
|
partial_sum: bool = False,
|
||||||
) -> List[int]:
|
) -> list[int]:
|
||||||
self.random_seed += 1
|
self.random_seed += 1
|
||||||
return generate_time_series_data(
|
return generate_time_series_data(
|
||||||
days=self.DAYS_OF_DATA,
|
days=self.DAYS_OF_DATA,
|
||||||
|
@ -147,18 +147,18 @@ class Command(ZulipBaseCommand):
|
||||||
with open(IMAGE_FILE_PATH, "rb") as fp:
|
with open(IMAGE_FILE_PATH, "rb") as fp:
|
||||||
upload_message_attachment_from_request(UploadedFile(fp), shylock)
|
upload_message_attachment_from_request(UploadedFile(fp), shylock)
|
||||||
|
|
||||||
FixtureData: TypeAlias = Mapping[Union[str, int, None], List[int]]
|
FixtureData: TypeAlias = Mapping[Union[str, int, None], list[int]]
|
||||||
|
|
||||||
def insert_fixture_data(
|
def insert_fixture_data(
|
||||||
stat: CountStat,
|
stat: CountStat,
|
||||||
fixture_data: FixtureData,
|
fixture_data: FixtureData,
|
||||||
table: Type[BaseCount],
|
table: type[BaseCount],
|
||||||
) -> None:
|
) -> None:
|
||||||
end_times = time_range(
|
end_times = time_range(
|
||||||
last_end_time, last_end_time, stat.frequency, len(next(iter(fixture_data.values())))
|
last_end_time, last_end_time, stat.frequency, len(next(iter(fixture_data.values())))
|
||||||
)
|
)
|
||||||
if table == InstallationCount:
|
if table == InstallationCount:
|
||||||
id_args: Dict[str, Any] = {}
|
id_args: dict[str, Any] = {}
|
||||||
if table == RealmCount:
|
if table == RealmCount:
|
||||||
id_args = {"realm": realm}
|
id_args = {"realm": realm}
|
||||||
if table == UserCount:
|
if table == UserCount:
|
||||||
|
@ -330,7 +330,7 @@ class Command(ZulipBaseCommand):
|
||||||
"true": self.generate_fixture_data(stat, 20, 2, 3, 0.2, 3),
|
"true": self.generate_fixture_data(stat, 20, 2, 3, 0.2, 3),
|
||||||
}
|
}
|
||||||
insert_fixture_data(stat, realm_data, RealmCount)
|
insert_fixture_data(stat, realm_data, RealmCount)
|
||||||
stream_data: Mapping[Union[int, str, None], List[int]] = {
|
stream_data: Mapping[Union[int, str, None], list[int]] = {
|
||||||
"false": self.generate_fixture_data(stat, 10, 7, 5, 0.6, 4),
|
"false": self.generate_fixture_data(stat, 10, 7, 5, 0.6, 4),
|
||||||
"true": self.generate_fixture_data(stat, 5, 3, 2, 0.4, 2),
|
"true": self.generate_fixture_data(stat, 5, 3, 2, 0.4, 2),
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import hashlib
|
||||||
import time
|
import time
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.dateparse import parse_datetime
|
from django.utils.dateparse import parse_datetime
|
||||||
|
@ -43,7 +43,7 @@ class Command(ZulipBaseCommand):
|
||||||
def handle(self, *args: Any, **options: Any) -> None:
|
def handle(self, *args: Any, **options: Any) -> None:
|
||||||
self.run_update_analytics_counts(options)
|
self.run_update_analytics_counts(options)
|
||||||
|
|
||||||
def run_update_analytics_counts(self, options: Dict[str, Any]) -> None:
|
def run_update_analytics_counts(self, options: dict[str, Any]) -> None:
|
||||||
# installation_epoch relies on there being at least one realm; we
|
# installation_epoch relies on there being at least one realm; we
|
||||||
# shouldn't run the analytics code if that condition isn't satisfied
|
# shouldn't run the analytics code if that condition isn't satisfied
|
||||||
if not Realm.objects.exists():
|
if not Realm.objects.exists():
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from contextlib import AbstractContextManager, ExitStack, contextmanager
|
from contextlib import AbstractContextManager, ExitStack, contextmanager
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Any, Dict, Iterator, List, Optional, Tuple, Type
|
from typing import Any, Iterator, Optional
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import time_machine
|
import time_machine
|
||||||
|
@ -132,7 +132,7 @@ class AnalyticsTestCase(ZulipTestCase):
|
||||||
kwargs[key] = kwargs.get(key, value)
|
kwargs[key] = kwargs.get(key, value)
|
||||||
kwargs["delivery_email"] = kwargs["email"]
|
kwargs["delivery_email"] = kwargs["email"]
|
||||||
with time_machine.travel(kwargs["date_joined"], tick=False):
|
with time_machine.travel(kwargs["date_joined"], tick=False):
|
||||||
pass_kwargs: Dict[str, Any] = {}
|
pass_kwargs: dict[str, Any] = {}
|
||||||
if kwargs["is_bot"]:
|
if kwargs["is_bot"]:
|
||||||
pass_kwargs["bot_type"] = UserProfile.DEFAULT_BOT
|
pass_kwargs["bot_type"] = UserProfile.DEFAULT_BOT
|
||||||
pass_kwargs["bot_owner"] = None
|
pass_kwargs["bot_owner"] = None
|
||||||
|
@ -158,7 +158,7 @@ class AnalyticsTestCase(ZulipTestCase):
|
||||||
)
|
)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def create_stream_with_recipient(self, **kwargs: Any) -> Tuple[Stream, Recipient]:
|
def create_stream_with_recipient(self, **kwargs: Any) -> tuple[Stream, Recipient]:
|
||||||
self.name_counter += 1
|
self.name_counter += 1
|
||||||
defaults = {
|
defaults = {
|
||||||
"name": f"stream name {self.name_counter}",
|
"name": f"stream name {self.name_counter}",
|
||||||
|
@ -174,7 +174,7 @@ class AnalyticsTestCase(ZulipTestCase):
|
||||||
stream.save(update_fields=["recipient"])
|
stream.save(update_fields=["recipient"])
|
||||||
return stream, recipient
|
return stream, recipient
|
||||||
|
|
||||||
def create_huddle_with_recipient(self, **kwargs: Any) -> Tuple[DirectMessageGroup, Recipient]:
|
def create_huddle_with_recipient(self, **kwargs: Any) -> tuple[DirectMessageGroup, Recipient]:
|
||||||
self.name_counter += 1
|
self.name_counter += 1
|
||||||
defaults = {"huddle_hash": f"hash{self.name_counter}"}
|
defaults = {"huddle_hash": f"hash{self.name_counter}"}
|
||||||
for key, value in defaults.items():
|
for key, value in defaults.items():
|
||||||
|
@ -224,7 +224,7 @@ class AnalyticsTestCase(ZulipTestCase):
|
||||||
# kwargs should only ever be a UserProfile or Stream.
|
# kwargs should only ever be a UserProfile or Stream.
|
||||||
def assert_table_count(
|
def assert_table_count(
|
||||||
self,
|
self,
|
||||||
table: Type[BaseCount],
|
table: type[BaseCount],
|
||||||
value: int,
|
value: int,
|
||||||
property: Optional[str] = None,
|
property: Optional[str] = None,
|
||||||
subgroup: Optional[str] = None,
|
subgroup: Optional[str] = None,
|
||||||
|
@ -246,7 +246,7 @@ class AnalyticsTestCase(ZulipTestCase):
|
||||||
self.assertEqual(queryset.values_list("value", flat=True)[0], value)
|
self.assertEqual(queryset.values_list("value", flat=True)[0], value)
|
||||||
|
|
||||||
def assertTableState(
|
def assertTableState(
|
||||||
self, table: Type[BaseCount], arg_keys: List[str], arg_values: List[List[object]]
|
self, table: type[BaseCount], arg_keys: list[str], arg_values: list[list[object]]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Assert that the state of a *Count table is what it should be.
|
"""Assert that the state of a *Count table is what it should be.
|
||||||
|
|
||||||
|
@ -276,7 +276,7 @@ class AnalyticsTestCase(ZulipTestCase):
|
||||||
"value": 1,
|
"value": 1,
|
||||||
}
|
}
|
||||||
for values in arg_values:
|
for values in arg_values:
|
||||||
kwargs: Dict[str, Any] = {}
|
kwargs: dict[str, Any] = {}
|
||||||
for i in range(len(values)):
|
for i in range(len(values)):
|
||||||
kwargs[arg_keys[i]] = values[i]
|
kwargs[arg_keys[i]] = values[i]
|
||||||
for key, value in defaults.items():
|
for key, value in defaults.items():
|
||||||
|
@ -1619,7 +1619,7 @@ class TestLoggingCountStats(AnalyticsTestCase):
|
||||||
def invite_context(
|
def invite_context(
|
||||||
too_many_recent_realm_invites: bool = False, failure: bool = False
|
too_many_recent_realm_invites: bool = False, failure: bool = False
|
||||||
) -> Iterator[None]:
|
) -> Iterator[None]:
|
||||||
managers: List[AbstractContextManager[Any]] = [
|
managers: list[AbstractContextManager[Any]] = [
|
||||||
mock.patch(
|
mock.patch(
|
||||||
"zerver.actions.invites.too_many_recent_realm_invites", return_value=False
|
"zerver.actions.invites.too_many_recent_realm_invites", return_value=False
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import List, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from django.utils.timezone import now as timezone_now
|
from django.utils.timezone import now as timezone_now
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
@ -84,11 +84,11 @@ class TestGetChartData(ZulipTestCase):
|
||||||
ceiling_to_day(self.realm.date_created) + timedelta(days=i) for i in range(4)
|
ceiling_to_day(self.realm.date_created) + timedelta(days=i) for i in range(4)
|
||||||
]
|
]
|
||||||
|
|
||||||
def data(self, i: int) -> List[int]:
|
def data(self, i: int) -> list[int]:
|
||||||
return [0, 0, i, 0]
|
return [0, 0, i, 0]
|
||||||
|
|
||||||
def insert_data(
|
def insert_data(
|
||||||
self, stat: CountStat, realm_subgroups: List[Optional[str]], user_subgroups: List[str]
|
self, stat: CountStat, realm_subgroups: list[Optional[str]], user_subgroups: list[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
if stat.frequency == CountStat.HOUR:
|
if stat.frequency == CountStat.HOUR:
|
||||||
insert_time = self.end_times_hour[2]
|
insert_time = self.end_times_hour[2]
|
||||||
|
@ -605,7 +605,7 @@ class TestGetChartData(ZulipTestCase):
|
||||||
|
|
||||||
class TestGetChartDataHelpers(ZulipTestCase):
|
class TestGetChartDataHelpers(ZulipTestCase):
|
||||||
def test_sort_by_totals(self) -> None:
|
def test_sort_by_totals(self) -> None:
|
||||||
empty: List[int] = []
|
empty: list[int] = []
|
||||||
value_arrays = {"c": [0, 1], "a": [9], "b": [1, 1, 1], "d": empty}
|
value_arrays = {"c": [0, 1], "a": [9], "b": [1, 1, 1], "d": empty}
|
||||||
self.assertEqual(sort_by_totals(value_arrays), ["a", "b", "c", "d"])
|
self.assertEqual(sort_by_totals(value_arrays), ["a", "b", "c", "d"])
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import List, Union
|
from typing import Union
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include
|
from django.conf.urls import include
|
||||||
|
@ -16,7 +16,7 @@ from analytics.views.stats import (
|
||||||
)
|
)
|
||||||
from zerver.lib.rest import rest_path
|
from zerver.lib.rest import rest_path
|
||||||
|
|
||||||
i18n_urlpatterns: List[Union[URLPattern, URLResolver]] = [
|
i18n_urlpatterns: list[Union[URLPattern, URLResolver]] = [
|
||||||
# Server admin (user_profile.is_staff) visible stats pages
|
# Server admin (user_profile.is_staff) visible stats pages
|
||||||
path("stats/realm/<realm_str>/", stats_for_realm),
|
path("stats/realm/<realm_str>/", stats_for_realm),
|
||||||
path("stats/installation", stats_for_installation),
|
path("stats/installation", stats_for_installation),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar, Union, cast
|
from typing import Any, Optional, TypeVar, Union, cast
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
@ -260,10 +260,10 @@ def get_chart_data(
|
||||||
stream: Optional[Stream] = None,
|
stream: Optional[Stream] = None,
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
TableType: TypeAlias = Union[
|
TableType: TypeAlias = Union[
|
||||||
Type["RemoteInstallationCount"],
|
type["RemoteInstallationCount"],
|
||||||
Type[InstallationCount],
|
type[InstallationCount],
|
||||||
Type["RemoteRealmCount"],
|
type["RemoteRealmCount"],
|
||||||
Type[RealmCount],
|
type[RealmCount],
|
||||||
]
|
]
|
||||||
if for_installation:
|
if for_installation:
|
||||||
if remote:
|
if remote:
|
||||||
|
@ -282,7 +282,7 @@ def get_chart_data(
|
||||||
aggregate_table = RealmCount
|
aggregate_table = RealmCount
|
||||||
|
|
||||||
tables: Union[
|
tables: Union[
|
||||||
Tuple[TableType], Tuple[TableType, Type[UserCount]], Tuple[TableType, Type[StreamCount]]
|
tuple[TableType], tuple[TableType, type[UserCount]], tuple[TableType, type[StreamCount]]
|
||||||
]
|
]
|
||||||
|
|
||||||
if chart_name == "number_of_humans":
|
if chart_name == "number_of_humans":
|
||||||
|
@ -292,7 +292,7 @@ def get_chart_data(
|
||||||
COUNT_STATS["active_users_audit:is_bot:day"],
|
COUNT_STATS["active_users_audit:is_bot:day"],
|
||||||
]
|
]
|
||||||
tables = (aggregate_table,)
|
tables = (aggregate_table,)
|
||||||
subgroup_to_label: Dict[CountStat, Dict[Optional[str], str]] = {
|
subgroup_to_label: dict[CountStat, dict[Optional[str], str]] = {
|
||||||
stats[0]: {None: "_1day"},
|
stats[0]: {None: "_1day"},
|
||||||
stats[1]: {None: "_15day"},
|
stats[1]: {None: "_15day"},
|
||||||
stats[2]: {"false": "all_time"},
|
stats[2]: {"false": "all_time"},
|
||||||
|
@ -372,7 +372,7 @@ def get_chart_data(
|
||||||
assert server is not None
|
assert server is not None
|
||||||
assert aggregate_table is RemoteInstallationCount or aggregate_table is RemoteRealmCount
|
assert aggregate_table is RemoteInstallationCount or aggregate_table is RemoteRealmCount
|
||||||
aggregate_table_remote = cast(
|
aggregate_table_remote = cast(
|
||||||
Union[Type[RemoteInstallationCount], Type[RemoteRealmCount]], aggregate_table
|
Union[type[RemoteInstallationCount], type[RemoteRealmCount]], aggregate_table
|
||||||
) # https://stackoverflow.com/questions/68540528/mypy-assertions-on-the-types-of-types
|
) # https://stackoverflow.com/questions/68540528/mypy-assertions-on-the-types-of-types
|
||||||
if not aggregate_table_remote.objects.filter(server=server).exists():
|
if not aggregate_table_remote.objects.filter(server=server).exists():
|
||||||
raise JsonableError(
|
raise JsonableError(
|
||||||
|
@ -418,7 +418,7 @@ def get_chart_data(
|
||||||
|
|
||||||
assert len({stat.frequency for stat in stats}) == 1
|
assert len({stat.frequency for stat in stats}) == 1
|
||||||
end_times = time_range(start, end, stats[0].frequency, min_length)
|
end_times = time_range(start, end, stats[0].frequency, min_length)
|
||||||
data: Dict[str, Any] = {
|
data: dict[str, Any] = {
|
||||||
"end_times": [int(end_time.timestamp()) for end_time in end_times],
|
"end_times": [int(end_time.timestamp()) for end_time in end_times],
|
||||||
"frequency": stats[0].frequency,
|
"frequency": stats[0].frequency,
|
||||||
}
|
}
|
||||||
|
@ -471,7 +471,7 @@ def get_chart_data(
|
||||||
return json_success(request, data=data)
|
return json_success(request, data=data)
|
||||||
|
|
||||||
|
|
||||||
def sort_by_totals(value_arrays: Dict[str, List[int]]) -> List[str]:
|
def sort_by_totals(value_arrays: dict[str, list[int]]) -> list[str]:
|
||||||
totals = sorted(((sum(values), label) for label, values in value_arrays.items()), reverse=True)
|
totals = sorted(((sum(values), label) for label, values in value_arrays.items()), reverse=True)
|
||||||
return [label for total, label in totals]
|
return [label for total, label in totals]
|
||||||
|
|
||||||
|
@ -482,10 +482,10 @@ def sort_by_totals(value_arrays: Dict[str, List[int]]) -> List[str]:
|
||||||
# understanding the realm's traffic and the user's traffic. This function
|
# understanding the realm's traffic and the user's traffic. This function
|
||||||
# tries to rank the clients so that taking the first N elements of the
|
# tries to rank the clients so that taking the first N elements of the
|
||||||
# sorted list has a reasonable chance of doing so.
|
# sorted list has a reasonable chance of doing so.
|
||||||
def sort_client_labels(data: Dict[str, Dict[str, List[int]]]) -> List[str]:
|
def sort_client_labels(data: dict[str, dict[str, list[int]]]) -> list[str]:
|
||||||
realm_order = sort_by_totals(data["everyone"])
|
realm_order = sort_by_totals(data["everyone"])
|
||||||
user_order = sort_by_totals(data["user"])
|
user_order = sort_by_totals(data["user"])
|
||||||
label_sort_values: Dict[str, float] = {label: i for i, label in enumerate(realm_order)}
|
label_sort_values: dict[str, float] = {label: i for i, label in enumerate(realm_order)}
|
||||||
for i, label in enumerate(user_order):
|
for i, label in enumerate(user_order):
|
||||||
label_sort_values[label] = min(i - 0.1, label_sort_values.get(label, i))
|
label_sort_values[label] = min(i - 0.1, label_sort_values.get(label, i))
|
||||||
return [label for label, sort_value in sorted(label_sort_values.items(), key=lambda x: x[1])]
|
return [label for label, sort_value in sorted(label_sort_values.items(), key=lambda x: x[1])]
|
||||||
|
@ -494,7 +494,7 @@ def sort_client_labels(data: Dict[str, Dict[str, List[int]]]) -> List[str]:
|
||||||
CountT = TypeVar("CountT", bound=BaseCount)
|
CountT = TypeVar("CountT", bound=BaseCount)
|
||||||
|
|
||||||
|
|
||||||
def table_filtered_to_id(table: Type[CountT], key_id: int) -> QuerySet[CountT]:
|
def table_filtered_to_id(table: type[CountT], key_id: int) -> QuerySet[CountT]:
|
||||||
if table == RealmCount:
|
if table == RealmCount:
|
||||||
return table._default_manager.filter(realm_id=key_id)
|
return table._default_manager.filter(realm_id=key_id)
|
||||||
elif table == UserCount:
|
elif table == UserCount:
|
||||||
|
@ -535,8 +535,8 @@ def client_label_map(name: str) -> str:
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
def rewrite_client_arrays(value_arrays: Dict[str, List[int]]) -> Dict[str, List[int]]:
|
def rewrite_client_arrays(value_arrays: dict[str, list[int]]) -> dict[str, list[int]]:
|
||||||
mapped_arrays: Dict[str, List[int]] = {}
|
mapped_arrays: dict[str, list[int]] = {}
|
||||||
for label, array in value_arrays.items():
|
for label, array in value_arrays.items():
|
||||||
mapped_label = client_label_map(label)
|
mapped_label = client_label_map(label)
|
||||||
if mapped_label in mapped_arrays:
|
if mapped_label in mapped_arrays:
|
||||||
|
@ -549,18 +549,18 @@ def rewrite_client_arrays(value_arrays: Dict[str, List[int]]) -> Dict[str, List[
|
||||||
|
|
||||||
def get_time_series_by_subgroup(
|
def get_time_series_by_subgroup(
|
||||||
stat: CountStat,
|
stat: CountStat,
|
||||||
table: Type[BaseCount],
|
table: type[BaseCount],
|
||||||
key_id: int,
|
key_id: int,
|
||||||
end_times: List[datetime],
|
end_times: list[datetime],
|
||||||
subgroup_to_label: Dict[Optional[str], str],
|
subgroup_to_label: dict[Optional[str], str],
|
||||||
include_empty_subgroups: bool,
|
include_empty_subgroups: bool,
|
||||||
) -> Dict[str, List[int]]:
|
) -> dict[str, list[int]]:
|
||||||
queryset = (
|
queryset = (
|
||||||
table_filtered_to_id(table, key_id)
|
table_filtered_to_id(table, key_id)
|
||||||
.filter(property=stat.property)
|
.filter(property=stat.property)
|
||||||
.values_list("subgroup", "end_time", "value")
|
.values_list("subgroup", "end_time", "value")
|
||||||
)
|
)
|
||||||
value_dicts: Dict[Optional[str], Dict[datetime, int]] = defaultdict(lambda: defaultdict(int))
|
value_dicts: dict[Optional[str], dict[datetime, int]] = defaultdict(lambda: defaultdict(int))
|
||||||
for subgroup, end_time, value in queryset:
|
for subgroup, end_time, value in queryset:
|
||||||
value_dicts[subgroup][end_time] = value
|
value_dicts[subgroup][end_time] = value
|
||||||
value_arrays = {}
|
value_arrays = {}
|
||||||
|
|
|
@ -4,7 +4,7 @@ __revision__ = "$Id: models.py 28 2009-10-22 15:03:02Z jarek.zgoda $"
|
||||||
import secrets
|
import secrets
|
||||||
from base64 import b32encode
|
from base64 import b32encode
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import List, Mapping, Optional, Union, cast
|
from typing import Mapping, Optional, Union, cast
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -80,7 +80,7 @@ ConfirmationObjT: TypeAlias = Union[NoZilencerConfirmationObjT, ZilencerConfirma
|
||||||
|
|
||||||
|
|
||||||
def get_object_from_key(
|
def get_object_from_key(
|
||||||
confirmation_key: str, confirmation_types: List[int], *, mark_object_used: bool
|
confirmation_key: str, confirmation_types: list[int], *, mark_object_used: bool
|
||||||
) -> ConfirmationObjT:
|
) -> ConfirmationObjT:
|
||||||
"""Access a confirmation object from one of the provided confirmation
|
"""Access a confirmation object from one of the provided confirmation
|
||||||
types with the provided key.
|
types with the provided key.
|
||||||
|
|
|
@ -2,7 +2,7 @@ from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union
|
from typing import Any, Callable, Optional, Sequence, Union
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -52,7 +52,7 @@ def make_table(
|
||||||
) -> str:
|
) -> str:
|
||||||
if not has_row_class:
|
if not has_row_class:
|
||||||
|
|
||||||
def fix_row(row: Any) -> Dict[str, Any]:
|
def fix_row(row: Any) -> dict[str, Any]:
|
||||||
return dict(cells=row, row_class=None)
|
return dict(cells=row, row_class=None)
|
||||||
|
|
||||||
rows = list(map(fix_row, rows))
|
rows = list(map(fix_row, rows))
|
||||||
|
@ -68,7 +68,7 @@ def make_table(
|
||||||
|
|
||||||
|
|
||||||
def fix_rows(
|
def fix_rows(
|
||||||
rows: List[List[Any]],
|
rows: list[list[Any]],
|
||||||
i: int,
|
i: int,
|
||||||
fixup_func: Union[Callable[[str], Markup], Callable[[datetime], str], Callable[[int], int]],
|
fixup_func: Union[Callable[[str], Markup], Callable[[datetime], str], Callable[[int], int]],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -76,7 +76,7 @@ def fix_rows(
|
||||||
row[i] = fixup_func(row[i])
|
row[i] = fixup_func(row[i])
|
||||||
|
|
||||||
|
|
||||||
def get_query_data(query: Composable) -> List[List[Any]]:
|
def get_query_data(query: Composable) -> list[list[Any]]:
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute(query)
|
cursor.execute(query)
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
|
@ -85,7 +85,7 @@ def get_query_data(query: Composable) -> List[List[Any]]:
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
|
|
||||||
def dictfetchall(cursor: CursorWrapper) -> List[Dict[str, Any]]:
|
def dictfetchall(cursor: CursorWrapper) -> list[dict[str, Any]]:
|
||||||
"""Returns all rows from a cursor as a dict"""
|
"""Returns all rows from a cursor as a dict"""
|
||||||
desc = cursor.description
|
desc = cursor.description
|
||||||
return [dict(zip((col[0] for col in desc), row)) for row in cursor.fetchall()]
|
return [dict(zip((col[0] for col in desc), row)) for row in cursor.fetchall()]
|
||||||
|
@ -208,7 +208,7 @@ def get_remote_activity_plan_data(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_estimated_arr_and_rate_by_realm() -> Tuple[Dict[str, int], Dict[str, str]]: # nocoverage
|
def get_estimated_arr_and_rate_by_realm() -> tuple[dict[str, int], dict[str, str]]: # nocoverage
|
||||||
# NOTE: Customers without a plan might still have a discount attached to them which
|
# NOTE: Customers without a plan might still have a discount attached to them which
|
||||||
# are not included in `plan_rate`.
|
# are not included in `plan_rate`.
|
||||||
annual_revenue = {}
|
annual_revenue = {}
|
||||||
|
@ -241,8 +241,8 @@ def get_estimated_arr_and_rate_by_realm() -> Tuple[Dict[str, int], Dict[str, str
|
||||||
return annual_revenue, plan_rate
|
return annual_revenue, plan_rate
|
||||||
|
|
||||||
|
|
||||||
def get_plan_data_by_remote_server() -> Dict[int, RemoteActivityPlanData]: # nocoverage
|
def get_plan_data_by_remote_server() -> dict[int, RemoteActivityPlanData]: # nocoverage
|
||||||
remote_server_plan_data: Dict[int, RemoteActivityPlanData] = {}
|
remote_server_plan_data: dict[int, RemoteActivityPlanData] = {}
|
||||||
plans = (
|
plans = (
|
||||||
CustomerPlan.objects.filter(
|
CustomerPlan.objects.filter(
|
||||||
status__lt=CustomerPlan.LIVE_STATUS_THRESHOLD,
|
status__lt=CustomerPlan.LIVE_STATUS_THRESHOLD,
|
||||||
|
@ -290,8 +290,8 @@ def get_plan_data_by_remote_server() -> Dict[int, RemoteActivityPlanData]: # no
|
||||||
return remote_server_plan_data
|
return remote_server_plan_data
|
||||||
|
|
||||||
|
|
||||||
def get_plan_data_by_remote_realm() -> Dict[int, Dict[int, RemoteActivityPlanData]]: # nocoverage
|
def get_plan_data_by_remote_realm() -> dict[int, dict[int, RemoteActivityPlanData]]: # nocoverage
|
||||||
remote_server_plan_data_by_realm: Dict[int, Dict[int, RemoteActivityPlanData]] = {}
|
remote_server_plan_data_by_realm: dict[int, dict[int, RemoteActivityPlanData]] = {}
|
||||||
plans = (
|
plans = (
|
||||||
CustomerPlan.objects.filter(
|
CustomerPlan.objects.filter(
|
||||||
status__lt=CustomerPlan.LIVE_STATUS_THRESHOLD,
|
status__lt=CustomerPlan.LIVE_STATUS_THRESHOLD,
|
||||||
|
@ -351,8 +351,8 @@ def get_plan_data_by_remote_realm() -> Dict[int, Dict[int, RemoteActivityPlanDat
|
||||||
|
|
||||||
def get_remote_realm_user_counts(
|
def get_remote_realm_user_counts(
|
||||||
event_time: datetime = timezone_now(),
|
event_time: datetime = timezone_now(),
|
||||||
) -> Dict[int, RemoteCustomerUserCount]: # nocoverage
|
) -> dict[int, RemoteCustomerUserCount]: # nocoverage
|
||||||
user_counts_by_realm: Dict[int, RemoteCustomerUserCount] = {}
|
user_counts_by_realm: dict[int, RemoteCustomerUserCount] = {}
|
||||||
for log in (
|
for log in (
|
||||||
RemoteRealmAuditLog.objects.filter(
|
RemoteRealmAuditLog.objects.filter(
|
||||||
event_type__in=RemoteRealmAuditLog.SYNCED_BILLING_EVENTS,
|
event_type__in=RemoteRealmAuditLog.SYNCED_BILLING_EVENTS,
|
||||||
|
@ -378,8 +378,8 @@ def get_remote_realm_user_counts(
|
||||||
|
|
||||||
def get_remote_server_audit_logs(
|
def get_remote_server_audit_logs(
|
||||||
event_time: datetime = timezone_now(),
|
event_time: datetime = timezone_now(),
|
||||||
) -> Dict[int, List[RemoteRealmAuditLog]]:
|
) -> dict[int, list[RemoteRealmAuditLog]]:
|
||||||
logs_per_server: Dict[int, List[RemoteRealmAuditLog]] = defaultdict(list)
|
logs_per_server: dict[int, list[RemoteRealmAuditLog]] = defaultdict(list)
|
||||||
for log in (
|
for log in (
|
||||||
RemoteRealmAuditLog.objects.filter(
|
RemoteRealmAuditLog.objects.filter(
|
||||||
event_type__in=RemoteRealmAuditLog.SYNCED_BILLING_EVENTS,
|
event_type__in=RemoteRealmAuditLog.SYNCED_BILLING_EVENTS,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import Literal, Optional, Tuple, TypedDict, Union, cast
|
from typing import Literal, Optional, TypedDict, Union, cast
|
||||||
|
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.utils.timezone import now as timezone_now
|
from django.utils.timezone import now as timezone_now
|
||||||
|
@ -102,7 +102,7 @@ def get_identity_dict_from_session(
|
||||||
def get_remote_realm_and_user_from_session(
|
def get_remote_realm_and_user_from_session(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
realm_uuid: Optional[str],
|
realm_uuid: Optional[str],
|
||||||
) -> Tuple[RemoteRealm, RemoteRealmBillingUser]:
|
) -> tuple[RemoteRealm, RemoteRealmBillingUser]:
|
||||||
# Cannot use isinstance with TypeDicts, to make mypy know
|
# Cannot use isinstance with TypeDicts, to make mypy know
|
||||||
# which of the TypedDicts in the Union this is - so just cast it.
|
# which of the TypedDicts in the Union this is - so just cast it.
|
||||||
identity_dict = cast(
|
identity_dict = cast(
|
||||||
|
@ -151,7 +151,7 @@ def get_remote_realm_and_user_from_session(
|
||||||
def get_remote_server_and_user_from_session(
|
def get_remote_server_and_user_from_session(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
server_uuid: str,
|
server_uuid: str,
|
||||||
) -> Tuple[RemoteZulipServer, Optional[RemoteServerBillingUser]]:
|
) -> tuple[RemoteZulipServer, Optional[RemoteServerBillingUser]]:
|
||||||
identity_dict: Optional[LegacyServerIdentityDict] = get_identity_dict_from_session(
|
identity_dict: Optional[LegacyServerIdentityDict] = get_identity_dict_from_session(
|
||||||
request, realm_uuid=None, server_uuid=server_uuid
|
request, realm_uuid=None, server_uuid=server_uuid
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,18 +8,7 @@ from datetime import datetime, timedelta, timezone
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import (
|
from typing import Any, Callable, Generator, Literal, Optional, TypedDict, TypeVar, Union
|
||||||
Any,
|
|
||||||
Callable,
|
|
||||||
Dict,
|
|
||||||
Generator,
|
|
||||||
Literal,
|
|
||||||
Optional,
|
|
||||||
Tuple,
|
|
||||||
TypedDict,
|
|
||||||
TypeVar,
|
|
||||||
Union,
|
|
||||||
)
|
|
||||||
from urllib.parse import urlencode, urljoin
|
from urllib.parse import urlencode, urljoin
|
||||||
|
|
||||||
import stripe
|
import stripe
|
||||||
|
@ -187,7 +176,7 @@ def get_seat_count(
|
||||||
return max(non_guests, math.ceil(guests / 5))
|
return max(non_guests, math.ceil(guests / 5))
|
||||||
|
|
||||||
|
|
||||||
def sign_string(string: str) -> Tuple[str, str]:
|
def sign_string(string: str) -> tuple[str, str]:
|
||||||
salt = secrets.token_hex(32)
|
salt = secrets.token_hex(32)
|
||||||
signer = Signer(salt=salt)
|
signer = Signer(salt=salt)
|
||||||
return signer.sign(string), salt
|
return signer.sign(string), salt
|
||||||
|
@ -541,7 +530,7 @@ class PriceArgs(TypedDict, total=False):
|
||||||
class StripeCustomerData:
|
class StripeCustomerData:
|
||||||
description: str
|
description: str
|
||||||
email: str
|
email: str
|
||||||
metadata: Dict[str, Any]
|
metadata: dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -754,7 +743,7 @@ class BillingSession(ABC):
|
||||||
event_time: datetime,
|
event_time: datetime,
|
||||||
*,
|
*,
|
||||||
background_update: bool = False,
|
background_update: bool = False,
|
||||||
extra_data: Optional[Dict[str, Any]] = None,
|
extra_data: Optional[dict[str, Any]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -764,8 +753,8 @@ class BillingSession(ABC):
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def update_data_for_checkout_session_and_invoice_payment(
|
def update_data_for_checkout_session_and_invoice_payment(
|
||||||
self, metadata: Dict[str, Any]
|
self, metadata: dict[str, Any]
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
@ -956,7 +945,7 @@ class BillingSession(ABC):
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def update_or_create_customer(
|
def update_or_create_customer(
|
||||||
self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[Dict[str, Any]] = None
|
self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[dict[str, Any]] = None
|
||||||
) -> Customer:
|
) -> Customer:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1013,11 +1002,11 @@ class BillingSession(ABC):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def add_sponsorship_info_to_context(self, context: Dict[str, Any]) -> None:
|
def add_sponsorship_info_to_context(self, context: dict[str, Any]) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_metadata_for_stripe_update_card(self) -> Dict[str, str]:
|
def get_metadata_for_stripe_update_card(self) -> dict[str, str]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
@ -1142,7 +1131,7 @@ class BillingSession(ABC):
|
||||||
|
|
||||||
def create_stripe_invoice_and_charge(
|
def create_stripe_invoice_and_charge(
|
||||||
self,
|
self,
|
||||||
metadata: Dict[str, Any],
|
metadata: dict[str, Any],
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Charge customer based on `billing_modality`. If `billing_modality` is `charge_automatically`,
|
Charge customer based on `billing_modality`. If `billing_modality` is `charge_automatically`,
|
||||||
|
@ -1217,7 +1206,7 @@ class BillingSession(ABC):
|
||||||
self,
|
self,
|
||||||
manual_license_management: bool,
|
manual_license_management: bool,
|
||||||
tier: int,
|
tier: int,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
metadata = self.get_metadata_for_stripe_update_card()
|
metadata = self.get_metadata_for_stripe_update_card()
|
||||||
customer = self.update_or_create_stripe_customer()
|
customer = self.update_or_create_stripe_customer()
|
||||||
assert customer.stripe_customer_id is not None
|
assert customer.stripe_customer_id is not None
|
||||||
|
@ -1252,7 +1241,7 @@ class BillingSession(ABC):
|
||||||
"stripe_session_id": stripe_session.id,
|
"stripe_session_id": stripe_session.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
def create_card_update_session(self) -> Dict[str, Any]:
|
def create_card_update_session(self) -> dict[str, Any]:
|
||||||
metadata = self.get_metadata_for_stripe_update_card()
|
metadata = self.get_metadata_for_stripe_update_card()
|
||||||
customer = self.get_customer()
|
customer = self.get_customer()
|
||||||
assert customer is not None and customer.stripe_customer_id is not None
|
assert customer is not None and customer.stripe_customer_id is not None
|
||||||
|
@ -1429,7 +1418,7 @@ class BillingSession(ABC):
|
||||||
required_plan_tier_name = CustomerPlan.name_from_tier(customer.required_plan_tier)
|
required_plan_tier_name = CustomerPlan.name_from_tier(customer.required_plan_tier)
|
||||||
|
|
||||||
fixed_price_cents = fixed_price * 100
|
fixed_price_cents = fixed_price * 100
|
||||||
fixed_price_plan_params: Dict[str, Any] = {
|
fixed_price_plan_params: dict[str, Any] = {
|
||||||
"fixed_price": fixed_price_cents,
|
"fixed_price": fixed_price_cents,
|
||||||
"tier": customer.required_plan_tier,
|
"tier": customer.required_plan_tier,
|
||||||
}
|
}
|
||||||
|
@ -1591,7 +1580,7 @@ class BillingSession(ABC):
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def write_to_audit_log_plan_property_changed(extra_data: Dict[str, Any]) -> None:
|
def write_to_audit_log_plan_property_changed(extra_data: dict[str, Any]) -> None:
|
||||||
extra_data["plan_id"] = plan.id
|
extra_data["plan_id"] = plan.id
|
||||||
self.write_to_audit_log(
|
self.write_to_audit_log(
|
||||||
event_type=AuditLogEventType.CUSTOMER_PLAN_PROPERTY_CHANGED,
|
event_type=AuditLogEventType.CUSTOMER_PLAN_PROPERTY_CHANGED,
|
||||||
|
@ -1942,7 +1931,7 @@ class BillingSession(ABC):
|
||||||
current_plan_id=plan.id,
|
current_plan_id=plan.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
def do_upgrade(self, upgrade_request: UpgradeRequest) -> Dict[str, Any]:
|
def do_upgrade(self, upgrade_request: UpgradeRequest) -> dict[str, Any]:
|
||||||
customer = self.get_customer()
|
customer = self.get_customer()
|
||||||
if customer is not None:
|
if customer is not None:
|
||||||
self.ensure_current_plan_is_upgradable(customer, upgrade_request.tier)
|
self.ensure_current_plan_is_upgradable(customer, upgrade_request.tier)
|
||||||
|
@ -1977,7 +1966,7 @@ class BillingSession(ABC):
|
||||||
"annual": CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
"annual": CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
"monthly": CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
"monthly": CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
}[schedule]
|
}[schedule]
|
||||||
data: Dict[str, Any] = {}
|
data: dict[str, Any] = {}
|
||||||
|
|
||||||
is_self_hosted_billing = not isinstance(self, RealmBillingSession)
|
is_self_hosted_billing = not isinstance(self, RealmBillingSession)
|
||||||
free_trial = is_free_trial_offer_enabled(is_self_hosted_billing, upgrade_request.tier)
|
free_trial = is_free_trial_offer_enabled(is_self_hosted_billing, upgrade_request.tier)
|
||||||
|
@ -2120,7 +2109,7 @@ class BillingSession(ABC):
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def make_end_of_cycle_updates_if_needed(
|
def make_end_of_cycle_updates_if_needed(
|
||||||
self, plan: CustomerPlan, event_time: datetime
|
self, plan: CustomerPlan, event_time: datetime
|
||||||
) -> Tuple[Optional[CustomerPlan], Optional[LicenseLedger]]:
|
) -> tuple[Optional[CustomerPlan], Optional[LicenseLedger]]:
|
||||||
last_ledger_entry = (
|
last_ledger_entry = (
|
||||||
LicenseLedger.objects.filter(plan=plan, event_time__lte=event_time)
|
LicenseLedger.objects.filter(plan=plan, event_time__lte=event_time)
|
||||||
.order_by("-id")
|
.order_by("-id")
|
||||||
|
@ -2338,7 +2327,7 @@ class BillingSession(ABC):
|
||||||
plan: CustomerPlan,
|
plan: CustomerPlan,
|
||||||
last_ledger_entry: LicenseLedger,
|
last_ledger_entry: LicenseLedger,
|
||||||
now: datetime,
|
now: datetime,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
is_self_hosted_billing = not isinstance(self, RealmBillingSession)
|
is_self_hosted_billing = not isinstance(self, RealmBillingSession)
|
||||||
downgrade_at_end_of_cycle = plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE
|
downgrade_at_end_of_cycle = plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE
|
||||||
downgrade_at_end_of_free_trial = plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL
|
downgrade_at_end_of_free_trial = plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL
|
||||||
|
@ -2483,7 +2472,7 @@ class BillingSession(ABC):
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_billing_page_context(self) -> Dict[str, Any]:
|
def get_billing_page_context(self) -> dict[str, Any]:
|
||||||
now = timezone_now()
|
now = timezone_now()
|
||||||
|
|
||||||
customer = self.get_customer()
|
customer = self.get_customer()
|
||||||
|
@ -2524,7 +2513,7 @@ class BillingSession(ABC):
|
||||||
context[key] = next_plan_context[key]
|
context[key] = next_plan_context[key]
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_flat_discount_info(self, customer: Optional[Customer] = None) -> Tuple[int, int]:
|
def get_flat_discount_info(self, customer: Optional[Customer] = None) -> tuple[int, int]:
|
||||||
is_self_hosted_billing = not isinstance(self, RealmBillingSession)
|
is_self_hosted_billing = not isinstance(self, RealmBillingSession)
|
||||||
flat_discount = 0
|
flat_discount = 0
|
||||||
flat_discounted_months = 0
|
flat_discounted_months = 0
|
||||||
|
@ -2542,7 +2531,7 @@ class BillingSession(ABC):
|
||||||
|
|
||||||
def get_initial_upgrade_context(
|
def get_initial_upgrade_context(
|
||||||
self, initial_upgrade_request: InitialUpgradeRequest
|
self, initial_upgrade_request: InitialUpgradeRequest
|
||||||
) -> Tuple[Optional[str], Optional[UpgradePageContext]]:
|
) -> tuple[Optional[str], Optional[UpgradePageContext]]:
|
||||||
customer = self.get_customer()
|
customer = self.get_customer()
|
||||||
|
|
||||||
# Allow users to upgrade to business regardless of current sponsorship status.
|
# Allow users to upgrade to business regardless of current sponsorship status.
|
||||||
|
@ -3200,7 +3189,7 @@ class BillingSession(ABC):
|
||||||
assert type_of_tier_change == PlanTierChangeType.DOWNGRADE # nocoverage
|
assert type_of_tier_change == PlanTierChangeType.DOWNGRADE # nocoverage
|
||||||
return "" # nocoverage
|
return "" # nocoverage
|
||||||
|
|
||||||
def get_event_status(self, event_status_request: EventStatusRequest) -> Dict[str, Any]:
|
def get_event_status(self, event_status_request: EventStatusRequest) -> dict[str, Any]:
|
||||||
customer = self.get_customer()
|
customer = self.get_customer()
|
||||||
|
|
||||||
if customer is None:
|
if customer is None:
|
||||||
|
@ -3261,7 +3250,7 @@ class BillingSession(ABC):
|
||||||
|
|
||||||
return sponsored_plan_name
|
return sponsored_plan_name
|
||||||
|
|
||||||
def get_sponsorship_request_context(self) -> Optional[Dict[str, Any]]:
|
def get_sponsorship_request_context(self) -> Optional[dict[str, Any]]:
|
||||||
customer = self.get_customer()
|
customer = self.get_customer()
|
||||||
is_remotely_hosted = isinstance(
|
is_remotely_hosted = isinstance(
|
||||||
self, (RemoteRealmBillingSession, RemoteServerBillingSession)
|
self, (RemoteRealmBillingSession, RemoteServerBillingSession)
|
||||||
|
@ -3271,7 +3260,7 @@ class BillingSession(ABC):
|
||||||
if is_remotely_hosted:
|
if is_remotely_hosted:
|
||||||
plan_name = "Free"
|
plan_name = "Free"
|
||||||
|
|
||||||
context: Dict[str, Any] = {
|
context: dict[str, Any] = {
|
||||||
"billing_base_url": self.billing_base_url,
|
"billing_base_url": self.billing_base_url,
|
||||||
"is_remotely_hosted": is_remotely_hosted,
|
"is_remotely_hosted": is_remotely_hosted,
|
||||||
"sponsorship_plan_name": self.get_sponsorship_plan_name(customer, is_remotely_hosted),
|
"sponsorship_plan_name": self.get_sponsorship_plan_name(customer, is_remotely_hosted),
|
||||||
|
@ -3837,7 +3826,7 @@ class RealmBillingSession(BillingSession):
|
||||||
event_time: datetime,
|
event_time: datetime,
|
||||||
*,
|
*,
|
||||||
background_update: bool = False,
|
background_update: bool = False,
|
||||||
extra_data: Optional[Dict[str, Any]] = None,
|
extra_data: Optional[dict[str, Any]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
audit_log_event = self.get_audit_log_event(event_type)
|
audit_log_event = self.get_audit_log_event(event_type)
|
||||||
audit_log_data = {
|
audit_log_data = {
|
||||||
|
@ -3859,7 +3848,7 @@ class RealmBillingSession(BillingSession):
|
||||||
# Support requests do not set any stripe billing information.
|
# Support requests do not set any stripe billing information.
|
||||||
assert self.support_session is False
|
assert self.support_session is False
|
||||||
assert self.user is not None
|
assert self.user is not None
|
||||||
metadata: Dict[str, Any] = {}
|
metadata: dict[str, Any] = {}
|
||||||
metadata["realm_id"] = self.realm.id
|
metadata["realm_id"] = self.realm.id
|
||||||
metadata["realm_str"] = self.realm.string_id
|
metadata["realm_str"] = self.realm.string_id
|
||||||
realm_stripe_customer_data = StripeCustomerData(
|
realm_stripe_customer_data = StripeCustomerData(
|
||||||
|
@ -3871,8 +3860,8 @@ class RealmBillingSession(BillingSession):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def update_data_for_checkout_session_and_invoice_payment(
|
def update_data_for_checkout_session_and_invoice_payment(
|
||||||
self, metadata: Dict[str, Any]
|
self, metadata: dict[str, Any]
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
assert self.user is not None
|
assert self.user is not None
|
||||||
updated_metadata = dict(
|
updated_metadata = dict(
|
||||||
user_email=self.get_email(),
|
user_email=self.get_email(),
|
||||||
|
@ -3885,7 +3874,7 @@ class RealmBillingSession(BillingSession):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def update_or_create_customer(
|
def update_or_create_customer(
|
||||||
self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[Dict[str, Any]] = None
|
self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[dict[str, Any]] = None
|
||||||
) -> Customer:
|
) -> Customer:
|
||||||
if stripe_customer_id is not None:
|
if stripe_customer_id is not None:
|
||||||
# Support requests do not set any stripe billing information.
|
# Support requests do not set any stripe billing information.
|
||||||
|
@ -3986,7 +3975,7 @@ class RealmBillingSession(BillingSession):
|
||||||
return self.realm.plan_type == self.realm.PLAN_TYPE_STANDARD_FREE
|
return self.realm.plan_type == self.realm.PLAN_TYPE_STANDARD_FREE
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def get_metadata_for_stripe_update_card(self) -> Dict[str, str]:
|
def get_metadata_for_stripe_update_card(self) -> dict[str, str]:
|
||||||
assert self.user is not None
|
assert self.user is not None
|
||||||
return {
|
return {
|
||||||
"type": "card_update",
|
"type": "card_update",
|
||||||
|
@ -4051,7 +4040,7 @@ class RealmBillingSession(BillingSession):
|
||||||
return self.realm.name
|
return self.realm.name
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def add_sponsorship_info_to_context(self, context: Dict[str, Any]) -> None:
|
def add_sponsorship_info_to_context(self, context: dict[str, Any]) -> None:
|
||||||
context.update(
|
context.update(
|
||||||
realm_org_type=self.realm.org_type,
|
realm_org_type=self.realm.org_type,
|
||||||
sorted_org_types=sorted(
|
sorted_org_types=sorted(
|
||||||
|
@ -4166,7 +4155,7 @@ class RemoteRealmBillingSession(BillingSession):
|
||||||
# possible, in that the self-hosted server will have uploaded
|
# possible, in that the self-hosted server will have uploaded
|
||||||
# current audit log data as needed as part of logging the user
|
# current audit log data as needed as part of logging the user
|
||||||
# in.
|
# in.
|
||||||
missing_data_context: Dict[str, Any] = {
|
missing_data_context: dict[str, Any] = {
|
||||||
"remote_realm_session": True,
|
"remote_realm_session": True,
|
||||||
"supports_remote_realms": self.remote_realm.server.last_api_feature_level is not None,
|
"supports_remote_realms": self.remote_realm.server.last_api_feature_level is not None,
|
||||||
}
|
}
|
||||||
|
@ -4216,7 +4205,7 @@ class RemoteRealmBillingSession(BillingSession):
|
||||||
event_time: datetime,
|
event_time: datetime,
|
||||||
*,
|
*,
|
||||||
background_update: bool = False,
|
background_update: bool = False,
|
||||||
extra_data: Optional[Dict[str, Any]] = None,
|
extra_data: Optional[dict[str, Any]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
# These audit logs don't use all the fields of `RemoteRealmAuditLog`:
|
# These audit logs don't use all the fields of `RemoteRealmAuditLog`:
|
||||||
#
|
#
|
||||||
|
@ -4250,7 +4239,7 @@ class RemoteRealmBillingSession(BillingSession):
|
||||||
def get_data_for_stripe_customer(self) -> StripeCustomerData:
|
def get_data_for_stripe_customer(self) -> StripeCustomerData:
|
||||||
# Support requests do not set any stripe billing information.
|
# Support requests do not set any stripe billing information.
|
||||||
assert self.support_session is False
|
assert self.support_session is False
|
||||||
metadata: Dict[str, Any] = {}
|
metadata: dict[str, Any] = {}
|
||||||
metadata["remote_realm_uuid"] = self.remote_realm.uuid
|
metadata["remote_realm_uuid"] = self.remote_realm.uuid
|
||||||
metadata["remote_realm_host"] = str(self.remote_realm.host)
|
metadata["remote_realm_host"] = str(self.remote_realm.host)
|
||||||
realm_stripe_customer_data = StripeCustomerData(
|
realm_stripe_customer_data = StripeCustomerData(
|
||||||
|
@ -4262,8 +4251,8 @@ class RemoteRealmBillingSession(BillingSession):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def update_data_for_checkout_session_and_invoice_payment(
|
def update_data_for_checkout_session_and_invoice_payment(
|
||||||
self, metadata: Dict[str, Any]
|
self, metadata: dict[str, Any]
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
assert self.remote_billing_user is not None
|
assert self.remote_billing_user is not None
|
||||||
updated_metadata = dict(
|
updated_metadata = dict(
|
||||||
remote_realm_user_id=self.remote_billing_user.id,
|
remote_realm_user_id=self.remote_billing_user.id,
|
||||||
|
@ -4275,7 +4264,7 @@ class RemoteRealmBillingSession(BillingSession):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def update_or_create_customer(
|
def update_or_create_customer(
|
||||||
self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[Dict[str, Any]] = None
|
self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[dict[str, Any]] = None
|
||||||
) -> Customer:
|
) -> Customer:
|
||||||
if stripe_customer_id is not None:
|
if stripe_customer_id is not None:
|
||||||
# Support requests do not set any stripe billing information.
|
# Support requests do not set any stripe billing information.
|
||||||
|
@ -4381,7 +4370,7 @@ class RemoteRealmBillingSession(BillingSession):
|
||||||
return self.remote_realm.plan_type == self.remote_realm.PLAN_TYPE_COMMUNITY
|
return self.remote_realm.plan_type == self.remote_realm.PLAN_TYPE_COMMUNITY
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def get_metadata_for_stripe_update_card(self) -> Dict[str, str]: # nocoverage
|
def get_metadata_for_stripe_update_card(self) -> dict[str, str]: # nocoverage
|
||||||
assert self.remote_billing_user is not None
|
assert self.remote_billing_user is not None
|
||||||
return {"type": "card_update", "remote_realm_user_id": str(self.remote_billing_user.id)}
|
return {"type": "card_update", "remote_realm_user_id": str(self.remote_billing_user.id)}
|
||||||
|
|
||||||
|
@ -4487,7 +4476,7 @@ class RemoteRealmBillingSession(BillingSession):
|
||||||
return self.remote_realm.host
|
return self.remote_realm.host
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def add_sponsorship_info_to_context(self, context: Dict[str, Any]) -> None:
|
def add_sponsorship_info_to_context(self, context: dict[str, Any]) -> None:
|
||||||
context.update(
|
context.update(
|
||||||
realm_org_type=self.remote_realm.org_type,
|
realm_org_type=self.remote_realm.org_type,
|
||||||
sorted_org_types=sorted(
|
sorted_org_types=sorted(
|
||||||
|
@ -4659,7 +4648,7 @@ class RemoteServerBillingSession(BillingSession):
|
||||||
event_time: datetime,
|
event_time: datetime,
|
||||||
*,
|
*,
|
||||||
background_update: bool = False,
|
background_update: bool = False,
|
||||||
extra_data: Optional[Dict[str, Any]] = None,
|
extra_data: Optional[dict[str, Any]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
audit_log_event = self.get_audit_log_event(event_type)
|
audit_log_event = self.get_audit_log_event(event_type)
|
||||||
log_data = {
|
log_data = {
|
||||||
|
@ -4687,7 +4676,7 @@ class RemoteServerBillingSession(BillingSession):
|
||||||
def get_data_for_stripe_customer(self) -> StripeCustomerData:
|
def get_data_for_stripe_customer(self) -> StripeCustomerData:
|
||||||
# Support requests do not set any stripe billing information.
|
# Support requests do not set any stripe billing information.
|
||||||
assert self.support_session is False
|
assert self.support_session is False
|
||||||
metadata: Dict[str, Any] = {}
|
metadata: dict[str, Any] = {}
|
||||||
metadata["remote_server_uuid"] = self.remote_server.uuid
|
metadata["remote_server_uuid"] = self.remote_server.uuid
|
||||||
metadata["remote_server_str"] = str(self.remote_server)
|
metadata["remote_server_str"] = str(self.remote_server)
|
||||||
realm_stripe_customer_data = StripeCustomerData(
|
realm_stripe_customer_data = StripeCustomerData(
|
||||||
|
@ -4699,8 +4688,8 @@ class RemoteServerBillingSession(BillingSession):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def update_data_for_checkout_session_and_invoice_payment(
|
def update_data_for_checkout_session_and_invoice_payment(
|
||||||
self, metadata: Dict[str, Any]
|
self, metadata: dict[str, Any]
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
assert self.remote_billing_user is not None
|
assert self.remote_billing_user is not None
|
||||||
updated_metadata = dict(
|
updated_metadata = dict(
|
||||||
remote_server_user_id=self.remote_billing_user.id,
|
remote_server_user_id=self.remote_billing_user.id,
|
||||||
|
@ -4712,7 +4701,7 @@ class RemoteServerBillingSession(BillingSession):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def update_or_create_customer(
|
def update_or_create_customer(
|
||||||
self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[Dict[str, Any]] = None
|
self, stripe_customer_id: Optional[str] = None, *, defaults: Optional[dict[str, Any]] = None
|
||||||
) -> Customer:
|
) -> Customer:
|
||||||
if stripe_customer_id is not None:
|
if stripe_customer_id is not None:
|
||||||
# Support requests do not set any stripe billing information.
|
# Support requests do not set any stripe billing information.
|
||||||
|
@ -4848,7 +4837,7 @@ class RemoteServerBillingSession(BillingSession):
|
||||||
return self.remote_server.plan_type == self.remote_server.PLAN_TYPE_COMMUNITY
|
return self.remote_server.plan_type == self.remote_server.PLAN_TYPE_COMMUNITY
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def get_metadata_for_stripe_update_card(self) -> Dict[str, str]: # nocoverage
|
def get_metadata_for_stripe_update_card(self) -> dict[str, str]: # nocoverage
|
||||||
assert self.remote_billing_user is not None
|
assert self.remote_billing_user is not None
|
||||||
return {"type": "card_update", "remote_server_user_id": str(self.remote_billing_user.id)}
|
return {"type": "card_update", "remote_server_user_id": str(self.remote_billing_user.id)}
|
||||||
|
|
||||||
|
@ -4936,7 +4925,7 @@ class RemoteServerBillingSession(BillingSession):
|
||||||
return self.remote_server.hostname
|
return self.remote_server.hostname
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def add_sponsorship_info_to_context(self, context: Dict[str, Any]) -> None: # nocoverage
|
def add_sponsorship_info_to_context(self, context: dict[str, Any]) -> None: # nocoverage
|
||||||
context.update(
|
context.update(
|
||||||
realm_org_type=self.remote_server.org_type,
|
realm_org_type=self.remote_server.org_type,
|
||||||
sorted_org_types=sorted(
|
sorted_org_types=sorted(
|
||||||
|
@ -5025,7 +5014,7 @@ def get_price_per_license(
|
||||||
# We already have a set discounted price for the current tier.
|
# We already have a set discounted price for the current tier.
|
||||||
return price_per_license
|
return price_per_license
|
||||||
|
|
||||||
price_map: Dict[int, Dict[str, int]] = {
|
price_map: dict[int, dict[str, int]] = {
|
||||||
CustomerPlan.TIER_CLOUD_STANDARD: {"Annual": 8000, "Monthly": 800},
|
CustomerPlan.TIER_CLOUD_STANDARD: {"Annual": 8000, "Monthly": 800},
|
||||||
CustomerPlan.TIER_CLOUD_PLUS: {"Annual": 12000, "Monthly": 1200},
|
CustomerPlan.TIER_CLOUD_PLUS: {"Annual": 12000, "Monthly": 1200},
|
||||||
CustomerPlan.TIER_SELF_HOSTED_BASIC: {"Annual": 4200, "Monthly": 350},
|
CustomerPlan.TIER_SELF_HOSTED_BASIC: {"Annual": 4200, "Monthly": 350},
|
||||||
|
@ -5047,7 +5036,7 @@ def get_price_per_license(
|
||||||
|
|
||||||
def get_price_per_license_and_discount(
|
def get_price_per_license_and_discount(
|
||||||
tier: int, billing_schedule: int, customer: Optional[Customer]
|
tier: int, billing_schedule: int, customer: Optional[Customer]
|
||||||
) -> Tuple[int, Union[str, None]]:
|
) -> tuple[int, Union[str, None]]:
|
||||||
original_price_per_license = get_price_per_license(tier, billing_schedule)
|
original_price_per_license = get_price_per_license(tier, billing_schedule)
|
||||||
if customer is None:
|
if customer is None:
|
||||||
return original_price_per_license, None
|
return original_price_per_license, None
|
||||||
|
@ -5070,7 +5059,7 @@ def compute_plan_parameters(
|
||||||
billing_cycle_anchor: Optional[datetime] = None,
|
billing_cycle_anchor: Optional[datetime] = None,
|
||||||
is_self_hosted_billing: bool = False,
|
is_self_hosted_billing: bool = False,
|
||||||
should_schedule_upgrade_for_legacy_remote_server: bool = False,
|
should_schedule_upgrade_for_legacy_remote_server: bool = False,
|
||||||
) -> Tuple[datetime, datetime, datetime, int]:
|
) -> tuple[datetime, datetime, datetime, int]:
|
||||||
# Everything in Stripe is stored as timestamps with 1 second resolution,
|
# Everything in Stripe is stored as timestamps with 1 second resolution,
|
||||||
# so standardize on 1 second resolution.
|
# so standardize on 1 second resolution.
|
||||||
# TODO talk about leap seconds?
|
# TODO talk about leap seconds?
|
||||||
|
@ -5354,7 +5343,7 @@ def downgrade_small_realms_behind_on_payments_as_needed() -> None:
|
||||||
billing_session = RealmBillingSession(user=None, realm=realm)
|
billing_session = RealmBillingSession(user=None, realm=realm)
|
||||||
billing_session.downgrade_now_without_creating_additional_invoices()
|
billing_session.downgrade_now_without_creating_additional_invoices()
|
||||||
billing_session.void_all_open_invoices()
|
billing_session.void_all_open_invoices()
|
||||||
context: Dict[str, Union[str, Realm]] = {
|
context: dict[str, Union[str, Realm]] = {
|
||||||
"upgrade_url": f"{realm.url}{reverse('upgrade_page')}",
|
"upgrade_url": f"{realm.url}{reverse('upgrade_page')}",
|
||||||
"realm": realm,
|
"realm": realm,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Dict, Optional, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
@ -109,7 +109,7 @@ class Event(models.Model):
|
||||||
|
|
||||||
handler_error = models.JSONField(default=None, null=True)
|
handler_error = models.JSONField(default=None, null=True)
|
||||||
|
|
||||||
def get_event_handler_details_as_dict(self) -> Dict[str, Any]:
|
def get_event_handler_details_as_dict(self) -> dict[str, Any]:
|
||||||
details_dict = {}
|
details_dict = {}
|
||||||
details_dict["status"] = {
|
details_dict["status"] = {
|
||||||
Event.RECEIVED: "not_started",
|
Event.RECEIVED: "not_started",
|
||||||
|
@ -158,8 +158,8 @@ class Session(models.Model):
|
||||||
Session.CARD_UPDATE_FROM_UPGRADE_PAGE: "card_update_from_upgrade_page",
|
Session.CARD_UPDATE_FROM_UPGRADE_PAGE: "card_update_from_upgrade_page",
|
||||||
}[self.type]
|
}[self.type]
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> dict[str, Any]:
|
||||||
session_dict: Dict[str, Any] = {}
|
session_dict: dict[str, Any] = {}
|
||||||
|
|
||||||
session_dict["status"] = self.get_status_as_string()
|
session_dict["status"] = self.get_status_as_string()
|
||||||
session_dict["type"] = self.get_type_as_string()
|
session_dict["type"] = self.get_type_as_string()
|
||||||
|
@ -216,8 +216,8 @@ class PaymentIntent(models.Model): # nocoverage
|
||||||
return None # nocoverage
|
return None # nocoverage
|
||||||
return get_last_associated_event_by_type(self, event_type)
|
return get_last_associated_event_by_type(self, event_type)
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> dict[str, Any]:
|
||||||
payment_intent_dict: Dict[str, Any] = {}
|
payment_intent_dict: dict[str, Any] = {}
|
||||||
payment_intent_dict["status"] = self.get_status_as_string()
|
payment_intent_dict["status"] = self.get_status_as_string()
|
||||||
event = self.get_last_associated_event()
|
event = self.get_last_associated_event()
|
||||||
if event is not None:
|
if event is not None:
|
||||||
|
@ -251,8 +251,8 @@ class Invoice(models.Model):
|
||||||
return None # nocoverage
|
return None # nocoverage
|
||||||
return get_last_associated_event_by_type(self, event_type)
|
return get_last_associated_event_by_type(self, event_type)
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> dict[str, Any]:
|
||||||
stripe_invoice_dict: Dict[str, Any] = {}
|
stripe_invoice_dict: dict[str, Any] = {}
|
||||||
stripe_invoice_dict["status"] = self.get_status_as_string()
|
stripe_invoice_dict["status"] = self.get_status_as_string()
|
||||||
event = self.get_last_associated_event()
|
event = self.get_last_associated_event()
|
||||||
if event is not None:
|
if event is not None:
|
||||||
|
|
|
@ -14,14 +14,10 @@ from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
Callable,
|
Callable,
|
||||||
Dict,
|
|
||||||
List,
|
|
||||||
Literal,
|
Literal,
|
||||||
Mapping,
|
Mapping,
|
||||||
Optional,
|
Optional,
|
||||||
Sequence,
|
Sequence,
|
||||||
Tuple,
|
|
||||||
Type,
|
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
Union,
|
||||||
cast,
|
cast,
|
||||||
|
@ -139,7 +135,7 @@ def stripe_fixture_path(
|
||||||
return f"{STRIPE_FIXTURES_DIR}/{decorated_function_name}--{mocked_function_name[7:]}.{call_count}.json"
|
return f"{STRIPE_FIXTURES_DIR}/{decorated_function_name}--{mocked_function_name[7:]}.{call_count}.json"
|
||||||
|
|
||||||
|
|
||||||
def fixture_files_for_function(decorated_function: CallableT) -> List[str]: # nocoverage
|
def fixture_files_for_function(decorated_function: CallableT) -> list[str]: # nocoverage
|
||||||
decorated_function_name = decorated_function.__name__
|
decorated_function_name = decorated_function.__name__
|
||||||
if decorated_function_name[:5] == "test_":
|
if decorated_function_name[:5] == "test_":
|
||||||
decorated_function_name = decorated_function_name[5:]
|
decorated_function_name = decorated_function_name[5:]
|
||||||
|
@ -269,7 +265,7 @@ def normalize_fixture_data(
|
||||||
f'"{timestamp_field}": 1{i + 1:02}%07d'
|
f'"{timestamp_field}": 1{i + 1:02}%07d'
|
||||||
)
|
)
|
||||||
|
|
||||||
normalized_values: Dict[str, Dict[str, str]] = {pattern: {} for pattern in pattern_translations}
|
normalized_values: dict[str, dict[str, str]] = {pattern: {} for pattern in pattern_translations}
|
||||||
for fixture_file in fixture_files_for_function(decorated_function):
|
for fixture_file in fixture_files_for_function(decorated_function):
|
||||||
with open(fixture_file) as f:
|
with open(fixture_file) as f:
|
||||||
file_content = f.read()
|
file_content = f.read()
|
||||||
|
@ -470,7 +466,7 @@ class StripeTestCase(ZulipTestCase):
|
||||||
return "tok_visa_chargeDeclined"
|
return "tok_visa_chargeDeclined"
|
||||||
|
|
||||||
def assert_details_of_valid_session_from_event_status_endpoint(
|
def assert_details_of_valid_session_from_event_status_endpoint(
|
||||||
self, stripe_session_id: str, expected_details: Dict[str, Any]
|
self, stripe_session_id: str, expected_details: dict[str, Any]
|
||||||
) -> None:
|
) -> None:
|
||||||
json_response = self.client_billing_get(
|
json_response = self.client_billing_get(
|
||||||
"/billing/event/status",
|
"/billing/event/status",
|
||||||
|
@ -484,7 +480,7 @@ class StripeTestCase(ZulipTestCase):
|
||||||
def assert_details_of_valid_invoice_payment_from_event_status_endpoint(
|
def assert_details_of_valid_invoice_payment_from_event_status_endpoint(
|
||||||
self,
|
self,
|
||||||
stripe_invoice_id: str,
|
stripe_invoice_id: str,
|
||||||
expected_details: Dict[str, Any],
|
expected_details: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
json_response = self.client_billing_get(
|
json_response = self.client_billing_get(
|
||||||
"/billing/event/status",
|
"/billing/event/status",
|
||||||
|
@ -626,7 +622,7 @@ class StripeTestCase(ZulipTestCase):
|
||||||
upgrade_page_response = self.client_get(upgrade_url, {}, subdomain="selfhosting")
|
upgrade_page_response = self.client_get(upgrade_url, {}, subdomain="selfhosting")
|
||||||
else:
|
else:
|
||||||
upgrade_page_response = self.client_get(upgrade_url, {})
|
upgrade_page_response = self.client_get(upgrade_url, {})
|
||||||
params: Dict[str, Any] = {
|
params: dict[str, Any] = {
|
||||||
"schedule": "annual",
|
"schedule": "annual",
|
||||||
"signed_seat_count": self.get_signed_seat_count_from_response(upgrade_page_response),
|
"signed_seat_count": self.get_signed_seat_count_from_response(upgrade_page_response),
|
||||||
"salt": self.get_salt_from_response(upgrade_page_response),
|
"salt": self.get_salt_from_response(upgrade_page_response),
|
||||||
|
@ -4468,7 +4464,7 @@ class StripeTest(StripeTestCase):
|
||||||
for invoice in invoices:
|
for invoice in invoices:
|
||||||
self.assertEqual(invoice.status, "void")
|
self.assertEqual(invoice.status, "void")
|
||||||
|
|
||||||
def create_invoices(self, customer: Customer, num_invoices: int) -> List[stripe.Invoice]:
|
def create_invoices(self, customer: Customer, num_invoices: int) -> list[stripe.Invoice]:
|
||||||
invoices = []
|
invoices = []
|
||||||
assert customer.stripe_customer_id is not None
|
assert customer.stripe_customer_id is not None
|
||||||
for _ in range(num_invoices):
|
for _ in range(num_invoices):
|
||||||
|
@ -4499,7 +4495,7 @@ class StripeTest(StripeTestCase):
|
||||||
create_stripe_customer: bool,
|
create_stripe_customer: bool,
|
||||||
create_plan: bool,
|
create_plan: bool,
|
||||||
num_invoices: Optional[int] = None,
|
num_invoices: Optional[int] = None,
|
||||||
) -> Tuple[Realm, Optional[CustomerPlan], List[stripe.Invoice]]:
|
) -> tuple[Realm, Optional[CustomerPlan], list[stripe.Invoice]]:
|
||||||
nonlocal test_realm_count
|
nonlocal test_realm_count
|
||||||
test_realm_count += 1
|
test_realm_count += 1
|
||||||
realm_string_id = "test-realm-" + str(test_realm_count)
|
realm_string_id = "test-realm-" + str(test_realm_count)
|
||||||
|
@ -4541,7 +4537,7 @@ class StripeTest(StripeTestCase):
|
||||||
expected_invoice_count: int
|
expected_invoice_count: int
|
||||||
email_expected_to_be_sent: bool
|
email_expected_to_be_sent: bool
|
||||||
|
|
||||||
rows: List[Row] = []
|
rows: list[Row] = []
|
||||||
|
|
||||||
# no stripe customer ID (excluded from query)
|
# no stripe customer ID (excluded from query)
|
||||||
realm, _, _ = create_realm(
|
realm, _, _ = create_realm(
|
||||||
|
@ -4970,11 +4966,11 @@ class RequiresBillingAccessTest(StripeTestCase):
|
||||||
tested_endpoints = set()
|
tested_endpoints = set()
|
||||||
|
|
||||||
def check_users_cant_access(
|
def check_users_cant_access(
|
||||||
users: List[UserProfile],
|
users: list[UserProfile],
|
||||||
error_message: str,
|
error_message: str,
|
||||||
url: str,
|
url: str,
|
||||||
method: str,
|
method: str,
|
||||||
data: Dict[str, Any],
|
data: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
tested_endpoints.add(url)
|
tested_endpoints.add(url)
|
||||||
for user in users:
|
for user in users:
|
||||||
|
@ -6507,7 +6503,7 @@ class TestRemoteBillingWriteAuditLog(StripeTestCase):
|
||||||
# Necessary cast or mypy doesn't understand that we can use Django's
|
# Necessary cast or mypy doesn't understand that we can use Django's
|
||||||
# model .objects. style queries on this.
|
# model .objects. style queries on this.
|
||||||
audit_log_model = cast(
|
audit_log_model = cast(
|
||||||
Union[Type[RemoteRealmAuditLog], Type[RemoteZulipServerAuditLog]], audit_log_class
|
Union[type[RemoteRealmAuditLog], type[RemoteZulipServerAuditLog]], audit_log_class
|
||||||
)
|
)
|
||||||
assert isinstance(remote_user, (RemoteRealmBillingUser, RemoteServerBillingUser))
|
assert isinstance(remote_user, (RemoteRealmBillingUser, RemoteServerBillingUser))
|
||||||
# No acting user:
|
# No acting user:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, Literal, Optional
|
from typing import Any, Literal, Optional
|
||||||
|
|
||||||
from django.http import HttpRequest, HttpResponse, HttpResponseNotAllowed, HttpResponseRedirect
|
from django.http import HttpRequest, HttpResponse, HttpResponseNotAllowed, HttpResponseRedirect
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
@ -55,7 +55,7 @@ def billing_page(
|
||||||
|
|
||||||
billing_session = RealmBillingSession(user=user, realm=user.realm)
|
billing_session = RealmBillingSession(user=user, realm=user.realm)
|
||||||
|
|
||||||
context: Dict[str, Any] = {
|
context: dict[str, Any] = {
|
||||||
"admin_access": user.has_billing_access,
|
"admin_access": user.has_billing_access,
|
||||||
"has_active_plan": False,
|
"has_active_plan": False,
|
||||||
"org_name": billing_session.org_name(),
|
"org_name": billing_session.org_name(),
|
||||||
|
@ -101,7 +101,7 @@ def remote_realm_billing_page(
|
||||||
success_message: str = "",
|
success_message: str = "",
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
realm_uuid = billing_session.remote_realm.uuid
|
realm_uuid = billing_session.remote_realm.uuid
|
||||||
context: Dict[str, Any] = {
|
context: dict[str, Any] = {
|
||||||
# We wouldn't be here if user didn't have access.
|
# We wouldn't be here if user didn't have access.
|
||||||
"admin_access": billing_session.has_billing_access(),
|
"admin_access": billing_session.has_billing_access(),
|
||||||
"has_active_plan": False,
|
"has_active_plan": False,
|
||||||
|
@ -161,7 +161,7 @@ def remote_server_billing_page(
|
||||||
*,
|
*,
|
||||||
success_message: str = "",
|
success_message: str = "",
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
context: Dict[str, Any] = {
|
context: dict[str, Any] = {
|
||||||
# We wouldn't be here if user didn't have access.
|
# We wouldn't be here if user didn't have access.
|
||||||
"admin_access": billing_session.has_billing_access(),
|
"admin_access": billing_session.has_billing_access(),
|
||||||
"has_active_plan": False,
|
"has_active_plan": False,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Dict, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
@ -33,7 +33,7 @@ from zerver.models.realm_audit_logs import RealmAuditLog
|
||||||
from zerver.models.realms import get_org_type_display_name
|
from zerver.models.realms import get_org_type_display_name
|
||||||
|
|
||||||
|
|
||||||
def get_realm_day_counts() -> Dict[str, Dict[str, Markup]]:
|
def get_realm_day_counts() -> dict[str, dict[str, Markup]]:
|
||||||
# To align with UTC days, we subtract an hour from end_time to
|
# To align with UTC days, we subtract an hour from end_time to
|
||||||
# get the start_time, since the hour that starts at midnight was
|
# get the start_time, since the hour that starts at midnight was
|
||||||
# on the previous day.
|
# on the previous day.
|
||||||
|
@ -61,7 +61,7 @@ def get_realm_day_counts() -> Dict[str, Dict[str, Markup]]:
|
||||||
rows = dictfetchall(cursor)
|
rows = dictfetchall(cursor)
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
counts: Dict[str, Dict[int, int]] = defaultdict(dict)
|
counts: dict[str, dict[int, int]] = defaultdict(dict)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
counts[row["string_id"]][row["age"]] = row["cnt"]
|
counts[row["string_id"]][row["age"]] = row["cnt"]
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import itertools
|
||||||
import re
|
import re
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Collection, Dict, Optional, Set
|
from typing import Any, Collection, Optional
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
|
from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
|
||||||
|
@ -97,9 +97,9 @@ def get_user_activity_summary(records: Collection[UserActivity]) -> UserActivity
|
||||||
|
|
||||||
|
|
||||||
def realm_user_summary_table(
|
def realm_user_summary_table(
|
||||||
all_records: QuerySet[UserActivity], admin_emails: Set[str], title: str, stats_link: Markup
|
all_records: QuerySet[UserActivity], admin_emails: set[str], title: str, stats_link: Markup
|
||||||
) -> str:
|
) -> str:
|
||||||
user_records: Dict[str, UserActivitySummary] = {}
|
user_records: dict[str, UserActivitySummary] = {}
|
||||||
|
|
||||||
def by_email(record: UserActivity) -> str:
|
def by_email(record: UserActivity) -> str:
|
||||||
return record.user_profile.delivery_email
|
return record.user_profile.delivery_email
|
||||||
|
@ -141,7 +141,7 @@ def realm_user_summary_table(
|
||||||
row = dict(cells=cells, row_class=row_class)
|
row = dict(cells=cells, row_class=row_class)
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
|
|
||||||
def by_last_heard_from(row: Dict[str, Any]) -> str:
|
def by_last_heard_from(row: dict[str, Any]) -> str:
|
||||||
return row["cells"][4]
|
return row["cells"][4]
|
||||||
|
|
||||||
rows = sorted(rows, key=by_last_heard_from, reverse=True)
|
rows = sorted(rows, key=by_last_heard_from, reverse=True)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, Literal, Optional, Union, cast
|
from typing import Any, Literal, Optional, Union, cast
|
||||||
from urllib.parse import urlsplit, urlunsplit
|
from urllib.parse import urlsplit, urlunsplit
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -526,7 +526,7 @@ def remote_billing_legacy_server_login(
|
||||||
zulip_org_key: Optional[str] = None,
|
zulip_org_key: Optional[str] = None,
|
||||||
next_page: VALID_NEXT_PAGES_TYPE = None,
|
next_page: VALID_NEXT_PAGES_TYPE = None,
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
context: Dict[str, Any] = {"next_page": next_page}
|
context: dict[str, Any] = {"next_page": next_page}
|
||||||
if zulip_org_id is None or zulip_org_key is None:
|
if zulip_org_id is None or zulip_org_key is None:
|
||||||
context.update({"error_message": False})
|
context.update({"error_message": False})
|
||||||
return render(request, "corporate/billing/legacy_server_login.html", context)
|
return render(request, "corporate/billing/legacy_server_login.html", context)
|
||||||
|
|
|
@ -3,7 +3,7 @@ from contextlib import suppress
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from typing import Any, Dict, Iterable, List, Optional, Union
|
from typing import Any, Iterable, Optional, Union
|
||||||
from urllib.parse import urlencode, urlsplit
|
from urllib.parse import urlencode, urlsplit
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
@ -215,8 +215,8 @@ def get_plan_type_string(plan_type: int) -> str:
|
||||||
|
|
||||||
|
|
||||||
def get_confirmations(
|
def get_confirmations(
|
||||||
types: List[int], object_ids: Iterable[int], hostname: Optional[str] = None
|
types: list[int], object_ids: Iterable[int], hostname: Optional[str] = None
|
||||||
) -> List[Dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
lowest_datetime = timezone_now() - timedelta(days=30)
|
lowest_datetime = timezone_now() - timedelta(days=30)
|
||||||
confirmations = Confirmation.objects.filter(
|
confirmations = Confirmation.objects.filter(
|
||||||
type__in=types, object_id__in=object_ids, date_sent__gte=lowest_datetime
|
type__in=types, object_id__in=object_ids, date_sent__gte=lowest_datetime
|
||||||
|
@ -265,7 +265,7 @@ class SupportSelectOption:
|
||||||
value: int
|
value: int
|
||||||
|
|
||||||
|
|
||||||
def get_remote_plan_tier_options() -> List[SupportSelectOption]:
|
def get_remote_plan_tier_options() -> list[SupportSelectOption]:
|
||||||
remote_plan_tiers = [
|
remote_plan_tiers = [
|
||||||
SupportSelectOption("None", 0),
|
SupportSelectOption("None", 0),
|
||||||
SupportSelectOption(
|
SupportSelectOption(
|
||||||
|
@ -280,7 +280,7 @@ def get_remote_plan_tier_options() -> List[SupportSelectOption]:
|
||||||
return remote_plan_tiers
|
return remote_plan_tiers
|
||||||
|
|
||||||
|
|
||||||
def get_realm_plan_type_options() -> List[SupportSelectOption]:
|
def get_realm_plan_type_options() -> list[SupportSelectOption]:
|
||||||
plan_types = [
|
plan_types = [
|
||||||
SupportSelectOption(
|
SupportSelectOption(
|
||||||
get_plan_type_string(Realm.PLAN_TYPE_SELF_HOSTED), Realm.PLAN_TYPE_SELF_HOSTED
|
get_plan_type_string(Realm.PLAN_TYPE_SELF_HOSTED), Realm.PLAN_TYPE_SELF_HOSTED
|
||||||
|
@ -297,7 +297,7 @@ def get_realm_plan_type_options() -> List[SupportSelectOption]:
|
||||||
return plan_types
|
return plan_types
|
||||||
|
|
||||||
|
|
||||||
def get_realm_plan_type_options_for_discount() -> List[SupportSelectOption]:
|
def get_realm_plan_type_options_for_discount() -> list[SupportSelectOption]:
|
||||||
plan_types = [
|
plan_types = [
|
||||||
SupportSelectOption("None", 0),
|
SupportSelectOption("None", 0),
|
||||||
SupportSelectOption(
|
SupportSelectOption(
|
||||||
|
@ -349,7 +349,7 @@ def support(
|
||||||
query: Annotated[Optional[str], ApiParamConfig("q")] = None,
|
query: Annotated[Optional[str], ApiParamConfig("q")] = None,
|
||||||
org_type: Optional[Json[NonNegativeInt]] = None,
|
org_type: Optional[Json[NonNegativeInt]] = None,
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
context: Dict[str, Any] = {}
|
context: dict[str, Any] = {}
|
||||||
|
|
||||||
if "success_message" in request.session:
|
if "success_message" in request.session:
|
||||||
context["success_message"] = request.session["success_message"]
|
context["success_message"] = request.session["success_message"]
|
||||||
|
@ -499,7 +499,7 @@ def support(
|
||||||
context["users"] = users
|
context["users"] = users
|
||||||
context["realms"] = realms
|
context["realms"] = realms
|
||||||
|
|
||||||
confirmations: List[Dict[str, Any]] = []
|
confirmations: list[dict[str, Any]] = []
|
||||||
|
|
||||||
preregistration_user_ids = [
|
preregistration_user_ids = [
|
||||||
user.id for user in PreregistrationUser.objects.filter(email__in=key_words)
|
user.id for user in PreregistrationUser.objects.filter(email__in=key_words)
|
||||||
|
@ -544,7 +544,7 @@ def support(
|
||||||
]
|
]
|
||||||
+ [user.realm for user in users]
|
+ [user.realm for user in users]
|
||||||
)
|
)
|
||||||
realm_support_data: Dict[int, CloudSupportData] = {}
|
realm_support_data: dict[int, CloudSupportData] = {}
|
||||||
for realm in all_realms:
|
for realm in all_realms:
|
||||||
billing_session = RealmBillingSession(user=None, realm=realm)
|
billing_session = RealmBillingSession(user=None, realm=realm)
|
||||||
realm_data = get_data_for_cloud_support_view(billing_session)
|
realm_data = get_data_for_cloud_support_view(billing_session)
|
||||||
|
@ -581,7 +581,7 @@ def support(
|
||||||
|
|
||||||
def get_remote_servers_for_support(
|
def get_remote_servers_for_support(
|
||||||
email_to_search: Optional[str], uuid_to_search: Optional[str], hostname_to_search: Optional[str]
|
email_to_search: Optional[str], uuid_to_search: Optional[str], hostname_to_search: Optional[str]
|
||||||
) -> List["RemoteZulipServer"]:
|
) -> list["RemoteZulipServer"]:
|
||||||
remote_servers_query = RemoteZulipServer.objects.order_by("id")
|
remote_servers_query = RemoteZulipServer.objects.order_by("id")
|
||||||
|
|
||||||
if email_to_search:
|
if email_to_search:
|
||||||
|
@ -645,7 +645,7 @@ def remote_servers_support(
|
||||||
delete_fixed_price_next_plan: Json[bool] = False,
|
delete_fixed_price_next_plan: Json[bool] = False,
|
||||||
remote_server_status: Optional[VALID_STATUS_VALUES] = None,
|
remote_server_status: Optional[VALID_STATUS_VALUES] = None,
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
context: Dict[str, Any] = {}
|
context: dict[str, Any] = {}
|
||||||
|
|
||||||
if "success_message" in request.session:
|
if "success_message" in request.session:
|
||||||
context["success_message"] = request.session["success_message"]
|
context["success_message"] = request.session["success_message"]
|
||||||
|
@ -778,10 +778,10 @@ def remote_servers_support(
|
||||||
uuid_to_search=uuid_to_search,
|
uuid_to_search=uuid_to_search,
|
||||||
hostname_to_search=hostname_to_search,
|
hostname_to_search=hostname_to_search,
|
||||||
)
|
)
|
||||||
remote_server_to_max_monthly_messages: Dict[int, Union[int, str]] = dict()
|
remote_server_to_max_monthly_messages: dict[int, Union[int, str]] = dict()
|
||||||
server_support_data: Dict[int, RemoteSupportData] = {}
|
server_support_data: dict[int, RemoteSupportData] = {}
|
||||||
realm_support_data: Dict[int, RemoteSupportData] = {}
|
realm_support_data: dict[int, RemoteSupportData] = {}
|
||||||
remote_realms: Dict[int, List[RemoteRealm]] = {}
|
remote_realms: dict[int, list[RemoteRealm]] = {}
|
||||||
for remote_server in remote_servers:
|
for remote_server in remote_servers:
|
||||||
# Get remote realms attached to remote server
|
# Get remote realms attached to remote server
|
||||||
remote_realms_for_server = list(
|
remote_realms_for_server = list(
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Any, List
|
from typing import Any
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
@ -43,7 +43,7 @@ def get_user_activity(request: HttpRequest, user_profile_id: int) -> HttpRespons
|
||||||
"Last visit (UTC)",
|
"Last visit (UTC)",
|
||||||
]
|
]
|
||||||
|
|
||||||
def row(record: UserActivity) -> List[Any]:
|
def row(record: UserActivity) -> list[Any]:
|
||||||
return [
|
return [
|
||||||
record.query,
|
record.query,
|
||||||
record.client.name,
|
record.client.name,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import configparser
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Dict, List, Optional
|
from typing import Optional
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
sys.path.append(BASE_DIR)
|
sys.path.append(BASE_DIR)
|
||||||
|
@ -18,7 +18,7 @@ from typing_extensions import override
|
||||||
from scripts.lib.zulip_tools import assert_not_running_as_root
|
from scripts.lib.zulip_tools import assert_not_running_as_root
|
||||||
|
|
||||||
|
|
||||||
def get_filtered_commands() -> Dict[str, str]:
|
def get_filtered_commands() -> dict[str, str]:
|
||||||
"""Because Zulip uses management commands in production, `manage.py
|
"""Because Zulip uses management commands in production, `manage.py
|
||||||
help` is a form of documentation for users. Here we exclude from
|
help` is a form of documentation for users. Here we exclude from
|
||||||
that documentation built-in commands that are not constructive for
|
that documentation built-in commands that are not constructive for
|
||||||
|
@ -110,7 +110,7 @@ class FilteredManagementUtility(ManagementUtility):
|
||||||
return "\n".join(usage)
|
return "\n".join(usage)
|
||||||
|
|
||||||
|
|
||||||
def execute_from_command_line(argv: Optional[List[str]] = None) -> None:
|
def execute_from_command_line(argv: Optional[list[str]] = None) -> None:
|
||||||
"""Run a FilteredManagementUtility."""
|
"""Run a FilteredManagementUtility."""
|
||||||
utility = FilteredManagementUtility(argv)
|
utility = FilteredManagementUtility(argv)
|
||||||
utility.execute()
|
utility.execute()
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import contextlib
|
import contextlib
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import Any, Dict, Iterable, Optional, Sequence, Union
|
from typing import Any, Iterable, Optional, Sequence, Union
|
||||||
|
|
||||||
sys.path.append("/home/zulip/deployments/current")
|
sys.path.append("/home/zulip/deployments/current")
|
||||||
from scripts.lib.setup_path import setup_path
|
from scripts.lib.setup_path import setup_path
|
||||||
|
@ -47,7 +47,7 @@ class MemcachedCollector(Collector):
|
||||||
name: str,
|
name: str,
|
||||||
doc: str,
|
doc: str,
|
||||||
value: Union[bytes, float],
|
value: Union[bytes, float],
|
||||||
labels: Optional[Dict[str, str]] = None,
|
labels: Optional[dict[str, str]] = None,
|
||||||
) -> CounterMetricFamily:
|
) -> CounterMetricFamily:
|
||||||
if labels is None:
|
if labels is None:
|
||||||
labels = {}
|
labels = {}
|
||||||
|
@ -63,7 +63,7 @@ class MemcachedCollector(Collector):
|
||||||
)
|
)
|
||||||
return metric
|
return metric
|
||||||
|
|
||||||
cache: Dict[str, Any] = settings.CACHES["default"]
|
cache: dict[str, Any] = settings.CACHES["default"]
|
||||||
client = None
|
client = None
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
client = bmemcached.Client((cache["LOCATION"],), **cache["OPTIONS"])
|
client = bmemcached.Client((cache["LOCATION"],), **cache["OPTIONS"])
|
||||||
|
@ -73,7 +73,7 @@ class MemcachedCollector(Collector):
|
||||||
return
|
return
|
||||||
|
|
||||||
raw_stats = client.stats()
|
raw_stats = client.stats()
|
||||||
stats: Dict[str, bytes] = next(iter(raw_stats.values()))
|
stats: dict[str, bytes] = next(iter(raw_stats.values()))
|
||||||
|
|
||||||
version_gauge = gauge(
|
version_gauge = gauge(
|
||||||
"version", "The version of this memcached server.", labels=["version"]
|
"version", "The version of this memcached server.", labels=["version"]
|
||||||
|
|
|
@ -12,11 +12,11 @@ mirrors when they receive the messages sent every minute by
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import Dict, NoReturn
|
from typing import NoReturn
|
||||||
|
|
||||||
RESULTS_DIR: str = "/home/zulip/mirror_status"
|
RESULTS_DIR: str = "/home/zulip/mirror_status"
|
||||||
|
|
||||||
states: Dict[str, int] = {
|
states: dict[str, int] = {
|
||||||
"OK": 0,
|
"OK": 0,
|
||||||
"WARNING": 1,
|
"WARNING": 1,
|
||||||
"CRITICAL": 2,
|
"CRITICAL": 2,
|
||||||
|
|
|
@ -13,11 +13,11 @@ See puppet/kandra/files/cron.d/zephyr-mirror for the crontab details.
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import Dict, NoReturn
|
from typing import NoReturn
|
||||||
|
|
||||||
RESULTS_FILE = "/var/lib/nagios_state/check-mirroring-results"
|
RESULTS_FILE = "/var/lib/nagios_state/check-mirroring-results"
|
||||||
|
|
||||||
states: Dict[str, int] = {
|
states: dict[str, int] = {
|
||||||
"OK": 0,
|
"OK": 0,
|
||||||
"WARNING": 1,
|
"WARNING": 1,
|
||||||
"CRITICAL": 2,
|
"CRITICAL": 2,
|
||||||
|
|
|
@ -6,10 +6,9 @@ file output by the cron job is correct.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
|
|
||||||
def nagios_from_file(results_file: str, max_time_diff: int = 60 * 2) -> Tuple[int, str]:
|
def nagios_from_file(results_file: str, max_time_diff: int = 60 * 2) -> tuple[int, str]:
|
||||||
"""Returns a nagios-appropriate string and return code obtained by
|
"""Returns a nagios-appropriate string and return code obtained by
|
||||||
parsing the desired file on disk. The file on disk should be of format
|
parsing the desired file on disk. The file on disk should be of format
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import random
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Any, Dict, List, Literal, NoReturn, Optional
|
from typing import Any, Literal, NoReturn, Optional
|
||||||
|
|
||||||
sys.path.append(".")
|
sys.path.append(".")
|
||||||
sys.path.append("/home/zulip/deployments/current")
|
sys.path.append("/home/zulip/deployments/current")
|
||||||
|
@ -58,13 +58,13 @@ def report(
|
||||||
sys.exit(atomic_nagios_write("check_send_receive_state", state, msg))
|
sys.exit(atomic_nagios_write("check_send_receive_state", state, msg))
|
||||||
|
|
||||||
|
|
||||||
def send_zulip(sender: zulip.Client, message: Dict[str, Any]) -> None:
|
def send_zulip(sender: zulip.Client, message: dict[str, Any]) -> None:
|
||||||
result = sender.send_message(message)
|
result = sender.send_message(message)
|
||||||
if result["result"] != "success":
|
if result["result"] != "success":
|
||||||
report("critical", msg=f"Error sending Zulip, args were: {message}, {result}")
|
report("critical", msg=f"Error sending Zulip, args were: {message}, {result}")
|
||||||
|
|
||||||
|
|
||||||
def get_zulips() -> List[Dict[str, Any]]:
|
def get_zulips() -> list[dict[str, Any]]:
|
||||||
global last_event_id
|
global last_event_id
|
||||||
res = zulip_recipient.get_events(queue_id=queue_id, last_event_id=last_event_id)
|
res = zulip_recipient.get_events(queue_id=queue_id, last_event_id=last_event_id)
|
||||||
if "error" in res.get("result", {}):
|
if "error" in res.get("result", {}):
|
||||||
|
@ -128,7 +128,7 @@ send_zulip(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
msg_content: List[str] = []
|
msg_content: list[str] = []
|
||||||
|
|
||||||
while msg_to_send not in msg_content:
|
while msg_to_send not in msg_content:
|
||||||
messages = get_zulips()
|
messages = get_zulips()
|
||||||
|
|
|
@ -11,7 +11,6 @@ import configparser
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
|
|
||||||
def get_config(
|
def get_config(
|
||||||
|
@ -44,7 +43,7 @@ def report(state: str, msg: str) -> None:
|
||||||
MAXSTATE = max(MAXSTATE, states[state])
|
MAXSTATE = max(MAXSTATE, states[state])
|
||||||
|
|
||||||
|
|
||||||
def run_sql_query(query: str) -> List[List[str]]:
|
def run_sql_query(query: str) -> list[list[str]]:
|
||||||
command = [
|
command = [
|
||||||
"psql",
|
"psql",
|
||||||
"-t", # Omit header line
|
"-t", # Omit header line
|
||||||
|
@ -130,7 +129,7 @@ else:
|
||||||
report("CRITICAL", f"replica {client_addr} is in state {state}, not streaming")
|
report("CRITICAL", f"replica {client_addr} is in state {state}, not streaming")
|
||||||
|
|
||||||
sent_offset = loc_to_abs_offset(sent_lsn)
|
sent_offset = loc_to_abs_offset(sent_lsn)
|
||||||
lag: Dict[str, int] = {}
|
lag: dict[str, int] = {}
|
||||||
lag["write"] = sent_offset - loc_to_abs_offset(write_lsn)
|
lag["write"] = sent_offset - loc_to_abs_offset(write_lsn)
|
||||||
lag["flush"] = sent_offset - loc_to_abs_offset(flush_lsn)
|
lag["flush"] = sent_offset - loc_to_abs_offset(flush_lsn)
|
||||||
lag["replay"] = sent_offset - loc_to_abs_offset(replay_lsn)
|
lag["replay"] = sent_offset - loc_to_abs_offset(replay_lsn)
|
||||||
|
|
|
@ -9,7 +9,7 @@ import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
from typing import Dict, List, Mapping, Optional, Protocol
|
from typing import Mapping, Optional, Protocol
|
||||||
from urllib.parse import parse_qs, urlsplit
|
from urllib.parse import parse_qs, urlsplit
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,8 +21,8 @@ class GaugeMetric(Protocol):
|
||||||
class WalGPrometheusServer(BaseHTTPRequestHandler):
|
class WalGPrometheusServer(BaseHTTPRequestHandler):
|
||||||
METRIC_PREFIX = "wal_g_backup_"
|
METRIC_PREFIX = "wal_g_backup_"
|
||||||
|
|
||||||
metrics: Dict[str, List[str]] = {}
|
metrics: dict[str, list[str]] = {}
|
||||||
metric_values: Dict[str, Dict[str, str]] = defaultdict(dict)
|
metric_values: dict[str, dict[str, str]] = defaultdict(dict)
|
||||||
|
|
||||||
server_version = "wal-g-prometheus-server/1.0"
|
server_version = "wal-g-prometheus-server/1.0"
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ class WalGPrometheusServer(BaseHTTPRequestHandler):
|
||||||
backup_latest_compressed_size_bytes(latest["compressed_size"], labels)
|
backup_latest_compressed_size_bytes(latest["compressed_size"], labels)
|
||||||
backup_latest_uncompressed_size_bytes(latest["uncompressed_size"], labels)
|
backup_latest_uncompressed_size_bytes(latest["uncompressed_size"], labels)
|
||||||
|
|
||||||
def t(key: str, e: Dict[str, str] = latest) -> datetime:
|
def t(key: str, e: dict[str, str] = latest) -> datetime:
|
||||||
return datetime.strptime(e[key], e["date_fmt"]).replace(tzinfo=timezone.utc)
|
return datetime.strptime(e[key], e["date_fmt"]).replace(tzinfo=timezone.utc)
|
||||||
|
|
||||||
backup_earliest_age_seconds(
|
backup_earliest_age_seconds(
|
||||||
|
|
|
@ -5,7 +5,7 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Any, DefaultDict, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
@ -43,14 +43,14 @@ states = {
|
||||||
3: "UNKNOWN",
|
3: "UNKNOWN",
|
||||||
}
|
}
|
||||||
|
|
||||||
MAX_SECONDS_TO_CLEAR: DefaultDict[str, int] = defaultdict(
|
MAX_SECONDS_TO_CLEAR: defaultdict[str, int] = defaultdict(
|
||||||
lambda: 30,
|
lambda: 30,
|
||||||
deferred_work=600,
|
deferred_work=600,
|
||||||
digest_emails=1200,
|
digest_emails=1200,
|
||||||
missedmessage_mobile_notifications=120,
|
missedmessage_mobile_notifications=120,
|
||||||
embed_links=60,
|
embed_links=60,
|
||||||
)
|
)
|
||||||
CRITICAL_SECONDS_TO_CLEAR: DefaultDict[str, int] = defaultdict(
|
CRITICAL_SECONDS_TO_CLEAR: defaultdict[str, int] = defaultdict(
|
||||||
lambda: 60,
|
lambda: 60,
|
||||||
deferred_work=900,
|
deferred_work=900,
|
||||||
missedmessage_mobile_notifications=180,
|
missedmessage_mobile_notifications=180,
|
||||||
|
@ -60,8 +60,8 @@ CRITICAL_SECONDS_TO_CLEAR: DefaultDict[str, int] = defaultdict(
|
||||||
|
|
||||||
|
|
||||||
def analyze_queue_stats(
|
def analyze_queue_stats(
|
||||||
queue_name: str, stats: Dict[str, Any], queue_count_rabbitmqctl: int
|
queue_name: str, stats: dict[str, Any], queue_count_rabbitmqctl: int
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
if stats == {}:
|
if stats == {}:
|
||||||
return dict(status=UNKNOWN, name=queue_name, message="invalid or no stats data")
|
return dict(status=UNKNOWN, name=queue_name, message="invalid or no stats data")
|
||||||
|
@ -117,7 +117,7 @@ WARN_COUNT_THRESHOLD_DEFAULT = 10
|
||||||
CRITICAL_COUNT_THRESHOLD_DEFAULT = 50
|
CRITICAL_COUNT_THRESHOLD_DEFAULT = 50
|
||||||
|
|
||||||
|
|
||||||
def check_other_queues(queue_counts_dict: Dict[str, int]) -> List[Dict[str, Any]]:
|
def check_other_queues(queue_counts_dict: dict[str, int]) -> list[dict[str, Any]]:
|
||||||
"""Do a simple queue size check for queues whose workers don't publish stats files."""
|
"""Do a simple queue size check for queues whose workers don't publish stats files."""
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
@ -161,7 +161,7 @@ def check_rabbitmq_queues() -> None:
|
||||||
[os.path.join(ZULIP_PATH, "scripts/get-django-setting"), "QUEUE_STATS_DIR"],
|
[os.path.join(ZULIP_PATH, "scripts/get-django-setting"), "QUEUE_STATS_DIR"],
|
||||||
text=True,
|
text=True,
|
||||||
).strip()
|
).strip()
|
||||||
queue_stats: Dict[str, Dict[str, Any]] = {}
|
queue_stats: dict[str, dict[str, Any]] = {}
|
||||||
check_queues = normal_queues
|
check_queues = normal_queues
|
||||||
if mobile_notification_shards > 1:
|
if mobile_notification_shards > 1:
|
||||||
check_queues += [
|
check_queues += [
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Set
|
|
||||||
|
|
||||||
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
sys.path.append(ZULIP_PATH)
|
sys.path.append(ZULIP_PATH)
|
||||||
|
@ -17,7 +16,7 @@ ENV = get_environment()
|
||||||
EMOJI_CACHE_PATH = "/srv/zulip-emoji-cache"
|
EMOJI_CACHE_PATH = "/srv/zulip-emoji-cache"
|
||||||
|
|
||||||
|
|
||||||
def get_caches_in_use(threshold_days: int) -> Set[str]:
|
def get_caches_in_use(threshold_days: int) -> set[str]:
|
||||||
setups_to_check = {ZULIP_PATH}
|
setups_to_check = {ZULIP_PATH}
|
||||||
caches_in_use = set()
|
caches_in_use = set()
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Set
|
|
||||||
|
|
||||||
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
sys.path.append(ZULIP_PATH)
|
sys.path.append(ZULIP_PATH)
|
||||||
|
@ -22,7 +21,7 @@ ENV = get_environment()
|
||||||
NODE_MODULES_CACHE_PATH = "/srv/zulip-npm-cache"
|
NODE_MODULES_CACHE_PATH = "/srv/zulip-npm-cache"
|
||||||
|
|
||||||
|
|
||||||
def get_caches_in_use(threshold_days: int) -> Set[str]:
|
def get_caches_in_use(threshold_days: int) -> set[str]:
|
||||||
setups_to_check = {ZULIP_PATH}
|
setups_to_check = {ZULIP_PATH}
|
||||||
caches_in_use = set()
|
caches_in_use = set()
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import argparse
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Set
|
|
||||||
|
|
||||||
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
sys.path.append(ZULIP_PATH)
|
sys.path.append(ZULIP_PATH)
|
||||||
|
@ -19,7 +18,7 @@ ENV = get_environment()
|
||||||
VENV_CACHE_DIR = "/srv/zulip-venv-cache"
|
VENV_CACHE_DIR = "/srv/zulip-venv-cache"
|
||||||
|
|
||||||
|
|
||||||
def get_caches_in_use(threshold_days: int) -> Set[str]:
|
def get_caches_in_use(threshold_days: int) -> set[str]:
|
||||||
setups_to_check = {ZULIP_PATH}
|
setups_to_check = {ZULIP_PATH}
|
||||||
caches_in_use = set()
|
caches_in_use = set()
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,11 @@ import hashlib
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import Iterable, List
|
from typing import Iterable
|
||||||
|
|
||||||
|
|
||||||
def expand_reqs_helper(fpath: str) -> List[str]:
|
def expand_reqs_helper(fpath: str) -> list[str]:
|
||||||
result: List[str] = []
|
result: list[str] = []
|
||||||
|
|
||||||
with open(fpath) as f:
|
with open(fpath) as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
|
@ -20,7 +20,7 @@ def expand_reqs_helper(fpath: str) -> List[str]:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def expand_reqs(fpath: str) -> List[str]:
|
def expand_reqs(fpath: str) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Returns a sorted list of unique dependencies specified by the requirements file `fpath`.
|
Returns a sorted list of unique dependencies specified by the requirements file `fpath`.
|
||||||
Removes comments from the output and recursively visits files specified inside `fpath`.
|
Removes comments from the output and recursively visits files specified inside `fpath`.
|
||||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import List, Optional, Set, Tuple
|
from typing import Optional
|
||||||
|
|
||||||
from scripts.lib.hash_reqs import expand_reqs, python_version
|
from scripts.lib.hash_reqs import expand_reqs, python_version
|
||||||
from scripts.lib.zulip_tools import ENDC, WARNING, os_families, run, run_as_root
|
from scripts.lib.zulip_tools import ENDC, WARNING, os_families, run, run_as_root
|
||||||
|
@ -62,7 +62,7 @@ FEDORA_VENV_DEPENDENCIES = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_venv_dependencies(vendor: str, os_version: str) -> List[str]:
|
def get_venv_dependencies(vendor: str, os_version: str) -> list[str]:
|
||||||
if "debian" in os_families():
|
if "debian" in os_families():
|
||||||
return VENV_DEPENDENCIES
|
return VENV_DEPENDENCIES
|
||||||
elif "rhel" in os_families():
|
elif "rhel" in os_families():
|
||||||
|
@ -93,7 +93,7 @@ def get_index_filename(venv_path: str) -> str:
|
||||||
return os.path.join(venv_path, "package_index")
|
return os.path.join(venv_path, "package_index")
|
||||||
|
|
||||||
|
|
||||||
def get_package_names(requirements_file: str) -> List[str]:
|
def get_package_names(requirements_file: str) -> list[str]:
|
||||||
packages = expand_reqs(requirements_file)
|
packages = expand_reqs(requirements_file)
|
||||||
cleaned = []
|
cleaned = []
|
||||||
operators = ["~=", "==", "!=", "<", ">"]
|
operators = ["~=", "==", "!=", "<", ">"]
|
||||||
|
@ -132,7 +132,7 @@ def create_requirements_index_file(venv_path: str, requirements_file: str) -> st
|
||||||
return index_filename
|
return index_filename
|
||||||
|
|
||||||
|
|
||||||
def get_venv_packages(venv_path: str) -> Set[str]:
|
def get_venv_packages(venv_path: str) -> set[str]:
|
||||||
"""
|
"""
|
||||||
Returns the packages installed in the virtual environment using the
|
Returns the packages installed in the virtual environment using the
|
||||||
package index file.
|
package index file.
|
||||||
|
@ -141,7 +141,7 @@ def get_venv_packages(venv_path: str) -> Set[str]:
|
||||||
return {p.strip() for p in reader.read().split("\n") if p.strip()}
|
return {p.strip() for p in reader.read().split("\n") if p.strip()}
|
||||||
|
|
||||||
|
|
||||||
def try_to_copy_venv(venv_path: str, new_packages: Set[str]) -> bool:
|
def try_to_copy_venv(venv_path: str, new_packages: set[str]) -> bool:
|
||||||
"""
|
"""
|
||||||
Tries to copy packages from an old virtual environment in the cache
|
Tries to copy packages from an old virtual environment in the cache
|
||||||
to the new virtual environment. The algorithm works as follows:
|
to the new virtual environment. The algorithm works as follows:
|
||||||
|
@ -159,8 +159,8 @@ def try_to_copy_venv(venv_path: str, new_packages: Set[str]) -> bool:
|
||||||
desired_python_version = python_version()
|
desired_python_version = python_version()
|
||||||
venv_name = os.path.basename(venv_path)
|
venv_name = os.path.basename(venv_path)
|
||||||
|
|
||||||
overlaps: List[Tuple[int, str, Set[str]]] = []
|
overlaps: list[tuple[int, str, set[str]]] = []
|
||||||
old_packages: Set[str] = set()
|
old_packages: set[str] = set()
|
||||||
for sha1sum in os.listdir(VENV_CACHE_PATH):
|
for sha1sum in os.listdir(VENV_CACHE_PATH):
|
||||||
curr_venv_path = os.path.join(VENV_CACHE_PATH, sha1sum, venv_name)
|
curr_venv_path = os.path.join(VENV_CACHE_PATH, sha1sum, venv_name)
|
||||||
if curr_venv_path == venv_path or not os.path.exists(get_index_filename(curr_venv_path)):
|
if curr_venv_path == venv_path or not os.path.exists(get_index_filename(curr_venv_path)):
|
||||||
|
@ -230,8 +230,8 @@ def get_logfile_name(venv_path: str) -> str:
|
||||||
def create_log_entry(
|
def create_log_entry(
|
||||||
target_log: str,
|
target_log: str,
|
||||||
parent: str,
|
parent: str,
|
||||||
copied_packages: Set[str],
|
copied_packages: set[str],
|
||||||
new_packages: Set[str],
|
new_packages: set[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
venv_path = os.path.dirname(target_log)
|
venv_path = os.path.dirname(target_log)
|
||||||
with open(target_log, "a") as writer:
|
with open(target_log, "a") as writer:
|
||||||
|
|
|
@ -5,7 +5,7 @@ import json
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import Dict, List, Tuple, Union
|
from typing import Union
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
sys.path.append(BASE_DIR)
|
sys.path.append(BASE_DIR)
|
||||||
|
@ -46,8 +46,8 @@ def write_updated_configs() -> None:
|
||||||
|
|
||||||
nginx_sharding_conf_f.write("map $host $tornado_server {\n")
|
nginx_sharding_conf_f.write("map $host $tornado_server {\n")
|
||||||
nginx_sharding_conf_f.write(" default http://tornado9800;\n")
|
nginx_sharding_conf_f.write(" default http://tornado9800;\n")
|
||||||
shard_map: Dict[str, Union[int, List[int]]] = {}
|
shard_map: dict[str, Union[int, list[int]]] = {}
|
||||||
shard_regexes: List[Tuple[str, Union[int, List[int]]]] = []
|
shard_regexes: list[tuple[str, Union[int, list[int]]]] = []
|
||||||
external_host = subprocess.check_output(
|
external_host = subprocess.check_output(
|
||||||
[os.path.join(BASE_DIR, "scripts/get-django-setting"), "EXTERNAL_HOST"],
|
[os.path.join(BASE_DIR, "scripts/get-django-setting"), "EXTERNAL_HOST"],
|
||||||
text=True,
|
text=True,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
from http.client import HTTPConnection
|
from http.client import HTTPConnection
|
||||||
from typing import Dict, List, Optional, Tuple, Union
|
from typing import Optional, Union
|
||||||
from xmlrpc import client
|
from xmlrpc import client
|
||||||
|
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
@ -33,7 +33,7 @@ class UnixStreamTransport(client.Transport):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def make_connection(
|
def make_connection(
|
||||||
self, host: Union[Tuple[str, Dict[str, str]], str]
|
self, host: Union[tuple[str, dict[str, str]], str]
|
||||||
) -> UnixStreamHTTPConnection:
|
) -> UnixStreamHTTPConnection:
|
||||||
return UnixStreamHTTPConnection(self.socket_path)
|
return UnixStreamHTTPConnection(self.socket_path)
|
||||||
|
|
||||||
|
@ -45,8 +45,8 @@ def rpc() -> client.ServerProxy:
|
||||||
|
|
||||||
|
|
||||||
def list_supervisor_processes(
|
def list_supervisor_processes(
|
||||||
filter_names: Optional[List[str]] = None, *, only_running: Optional[bool] = None
|
filter_names: Optional[list[str]] = None, *, only_running: Optional[bool] = None
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
results = []
|
results = []
|
||||||
processes = rpc().supervisor.getAllProcessInfo()
|
processes = rpc().supervisor.getAllProcessInfo()
|
||||||
assert isinstance(processes, list)
|
assert isinstance(processes, list)
|
||||||
|
|
|
@ -16,7 +16,7 @@ import sys
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import IO, Any, Dict, List, Literal, Optional, Sequence, Set, Union, overload
|
from typing import IO, Any, Literal, Optional, Sequence, Union, overload
|
||||||
from urllib.parse import SplitResult
|
from urllib.parse import SplitResult
|
||||||
|
|
||||||
import zoneinfo
|
import zoneinfo
|
||||||
|
@ -300,7 +300,7 @@ def get_environment() -> str:
|
||||||
return "dev"
|
return "dev"
|
||||||
|
|
||||||
|
|
||||||
def get_recent_deployments(threshold_days: int) -> Set[str]:
|
def get_recent_deployments(threshold_days: int) -> set[str]:
|
||||||
# Returns a list of deployments not older than threshold days
|
# Returns a list of deployments not older than threshold days
|
||||||
# including `/root/zulip` directory if it exists.
|
# including `/root/zulip` directory if it exists.
|
||||||
recent = set()
|
recent = set()
|
||||||
|
@ -337,8 +337,8 @@ def get_threshold_timestamp(threshold_days: int) -> int:
|
||||||
|
|
||||||
|
|
||||||
def get_caches_to_be_purged(
|
def get_caches_to_be_purged(
|
||||||
caches_dir: str, caches_in_use: Set[str], threshold_days: int
|
caches_dir: str, caches_in_use: set[str], threshold_days: int
|
||||||
) -> Set[str]:
|
) -> set[str]:
|
||||||
# Given a directory containing caches, a list of caches in use
|
# Given a directory containing caches, a list of caches in use
|
||||||
# and threshold days, this function return a list of caches
|
# and threshold days, this function return a list of caches
|
||||||
# which can be purged. Remove the cache only if it is:
|
# which can be purged. Remove the cache only if it is:
|
||||||
|
@ -360,7 +360,7 @@ def get_caches_to_be_purged(
|
||||||
|
|
||||||
def purge_unused_caches(
|
def purge_unused_caches(
|
||||||
caches_dir: str,
|
caches_dir: str,
|
||||||
caches_in_use: Set[str],
|
caches_in_use: set[str],
|
||||||
cache_type: str,
|
cache_type: str,
|
||||||
args: argparse.Namespace,
|
args: argparse.Namespace,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -405,8 +405,8 @@ def generate_sha1sum_emoji(zulip_path: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
def maybe_perform_purging(
|
def maybe_perform_purging(
|
||||||
dirs_to_purge: Set[str],
|
dirs_to_purge: set[str],
|
||||||
dirs_to_keep: Set[str],
|
dirs_to_keep: set[str],
|
||||||
dir_type: str,
|
dir_type: str,
|
||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
@ -429,7 +429,7 @@ def maybe_perform_purging(
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(None)
|
@functools.lru_cache(None)
|
||||||
def parse_os_release() -> Dict[str, str]:
|
def parse_os_release() -> dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Example of the useful subset of the data:
|
Example of the useful subset of the data:
|
||||||
{
|
{
|
||||||
|
@ -444,7 +444,7 @@ def parse_os_release() -> Dict[str, str]:
|
||||||
developers, but we avoid using it, as it is not available on
|
developers, but we avoid using it, as it is not available on
|
||||||
RHEL-based platforms.
|
RHEL-based platforms.
|
||||||
"""
|
"""
|
||||||
distro_info: Dict[str, str] = {}
|
distro_info: dict[str, str] = {}
|
||||||
with open("/etc/os-release") as fp:
|
with open("/etc/os-release") as fp:
|
||||||
for line in fp:
|
for line in fp:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
|
@ -458,7 +458,7 @@ def parse_os_release() -> Dict[str, str]:
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(None)
|
@functools.lru_cache(None)
|
||||||
def os_families() -> Set[str]:
|
def os_families() -> set[str]:
|
||||||
"""
|
"""
|
||||||
Known families:
|
Known families:
|
||||||
debian (includes: debian, ubuntu)
|
debian (includes: debian, ubuntu)
|
||||||
|
@ -548,7 +548,7 @@ def is_root() -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def run_as_root(args: List[str], **kwargs: Any) -> None:
|
def run_as_root(args: list[str], **kwargs: Any) -> None:
|
||||||
sudo_args = kwargs.pop("sudo_args", [])
|
sudo_args = kwargs.pop("sudo_args", [])
|
||||||
if not is_root():
|
if not is_root():
|
||||||
args = ["sudo", *sudo_args, "--", *args]
|
args = ["sudo", *sudo_args, "--", *args]
|
||||||
|
@ -619,7 +619,7 @@ def get_config_file() -> configparser.RawConfigParser:
|
||||||
return config_file
|
return config_file
|
||||||
|
|
||||||
|
|
||||||
def get_deploy_options(config_file: configparser.RawConfigParser) -> List[str]:
|
def get_deploy_options(config_file: configparser.RawConfigParser) -> list[str]:
|
||||||
return shlex.split(get_config(config_file, "deployment", "deploy_options", ""))
|
return shlex.split(get_config(config_file, "deployment", "deploy_options", ""))
|
||||||
|
|
||||||
|
|
||||||
|
@ -632,7 +632,7 @@ def run_psql_as_postgres(
|
||||||
subprocess.check_call(["su", "postgres", "-c", subcmd])
|
subprocess.check_call(["su", "postgres", "-c", subcmd])
|
||||||
|
|
||||||
|
|
||||||
def get_tornado_ports(config_file: configparser.RawConfigParser) -> List[int]:
|
def get_tornado_ports(config_file: configparser.RawConfigParser) -> list[int]:
|
||||||
ports = []
|
ports = []
|
||||||
if config_file.has_section("tornado_sharding"):
|
if config_file.has_section("tornado_sharding"):
|
||||||
ports = sorted(
|
ports = sorted(
|
||||||
|
@ -705,7 +705,7 @@ def start_arg_parser(action: str, add_help: bool = False) -> argparse.ArgumentPa
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def listening_publicly(port: int) -> List[str]:
|
def listening_publicly(port: int) -> list[str]:
|
||||||
filter = f"sport = :{port} and not src 127.0.0.1:{port} and not src [::1]:{port}"
|
filter = f"sport = :{port} and not src 127.0.0.1:{port} and not src [::1]:{port}"
|
||||||
# Parse lines that look like this:
|
# Parse lines that look like this:
|
||||||
# tcp LISTEN 0 128 0.0.0.0:25672 0.0.0.0:*
|
# tcp LISTEN 0 128 0.0.0.0:25672 0.0.0.0:*
|
||||||
|
|
|
@ -10,7 +10,7 @@ import signal
|
||||||
import sys
|
import sys
|
||||||
from datetime import date, datetime, timedelta, timezone
|
from datetime import date, datetime, timedelta, timezone
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from typing import List, Match, Optional, Set, TextIO, Tuple
|
from typing import Match, Optional, TextIO
|
||||||
|
|
||||||
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
sys.path.append(ZULIP_PATH)
|
sys.path.append(ZULIP_PATH)
|
||||||
|
@ -242,7 +242,7 @@ def main() -> None:
|
||||||
sys.exit(signal.SIGINT + 128)
|
sys.exit(signal.SIGINT + 128)
|
||||||
|
|
||||||
|
|
||||||
def parse_logfile_names(args: argparse.Namespace) -> List[str]:
|
def parse_logfile_names(args: argparse.Namespace) -> list[str]:
|
||||||
if args.nginx:
|
if args.nginx:
|
||||||
base_path = "/var/log/nginx/access.log"
|
base_path = "/var/log/nginx/access.log"
|
||||||
else:
|
else:
|
||||||
|
@ -311,7 +311,7 @@ def convert_to_nginx_date(date: str) -> str:
|
||||||
|
|
||||||
def parse_filters(
|
def parse_filters(
|
||||||
args: argparse.Namespace,
|
args: argparse.Namespace,
|
||||||
) -> Tuple[Set[FilterType], List[FilterFunc], List[str]]:
|
) -> tuple[set[FilterType], list[FilterFunc], list[str]]:
|
||||||
# The heuristics below are not intended to be precise -- they
|
# The heuristics below are not intended to be precise -- they
|
||||||
# certainly count things as "IPv4" or "IPv6" addresses that are
|
# certainly count things as "IPv4" or "IPv6" addresses that are
|
||||||
# invalid. However, we expect the input here to already be
|
# invalid. However, we expect the input here to already be
|
||||||
|
@ -397,7 +397,7 @@ def parse_filters(
|
||||||
|
|
||||||
|
|
||||||
def passes_filters(
|
def passes_filters(
|
||||||
string_filters: List[FilterFunc],
|
string_filters: list[FilterFunc],
|
||||||
match: Match[str],
|
match: Match[str],
|
||||||
args: argparse.Namespace,
|
args: argparse.Namespace,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
@ -434,7 +434,7 @@ last_match_end: Optional[datetime] = None
|
||||||
def print_line(
|
def print_line(
|
||||||
match: Match[str],
|
match: Match[str],
|
||||||
args: argparse.Namespace,
|
args: argparse.Namespace,
|
||||||
filter_types: Set[FilterType],
|
filter_types: set[FilterType],
|
||||||
use_color: bool,
|
use_color: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
global last_match_end
|
global last_match_end
|
||||||
|
|
|
@ -6,7 +6,6 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
sys.path.append(ZULIP_PATH)
|
sys.path.append(ZULIP_PATH)
|
||||||
|
@ -30,7 +29,7 @@ TORNADO_PROCESSES = len(get_tornado_ports(config_file))
|
||||||
|
|
||||||
output = subprocess.check_output(["/usr/sbin/rabbitmqctl", "list_consumers"], text=True)
|
output = subprocess.check_output(["/usr/sbin/rabbitmqctl", "list_consumers"], text=True)
|
||||||
|
|
||||||
consumers: Dict[str, int] = defaultdict(int)
|
consumers: dict[str, int] = defaultdict(int)
|
||||||
|
|
||||||
queues = {
|
queues = {
|
||||||
*normal_queues,
|
*normal_queues,
|
||||||
|
|
|
@ -3,7 +3,6 @@ import argparse
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import Set
|
|
||||||
|
|
||||||
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
sys.path.append(ZULIP_PATH)
|
sys.path.append(ZULIP_PATH)
|
||||||
|
@ -56,7 +55,7 @@ def parse_args() -> argparse.Namespace:
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def get_deployments_to_be_purged(recent_deployments: Set[str]) -> Set[str]:
|
def get_deployments_to_be_purged(recent_deployments: set[str]) -> set[str]:
|
||||||
all_deployments = {
|
all_deployments = {
|
||||||
os.path.join(DEPLOYMENTS_DIR, deployment) for deployment in os.listdir(DEPLOYMENTS_DIR)
|
os.path.join(DEPLOYMENTS_DIR, deployment) for deployment in os.listdir(DEPLOYMENTS_DIR)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
sys.path.append(BASE_DIR)
|
sys.path.append(BASE_DIR)
|
||||||
|
@ -62,7 +61,7 @@ def generate_django_secretkey() -> str:
|
||||||
return get_random_string(50, chars)
|
return get_random_string(50, chars)
|
||||||
|
|
||||||
|
|
||||||
def get_old_conf(output_filename: str) -> Dict[str, str]:
|
def get_old_conf(output_filename: str) -> dict[str, str]:
|
||||||
if not os.path.exists(output_filename) or os.path.getsize(output_filename) == 0:
|
if not os.path.exists(output_filename) or os.path.getsize(output_filename) == 0:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@ -79,7 +78,7 @@ def generate_secrets(development: bool = False) -> None:
|
||||||
OUTPUT_SETTINGS_FILENAME = "/etc/zulip/zulip-secrets.conf"
|
OUTPUT_SETTINGS_FILENAME = "/etc/zulip/zulip-secrets.conf"
|
||||||
current_conf = get_old_conf(OUTPUT_SETTINGS_FILENAME)
|
current_conf = get_old_conf(OUTPUT_SETTINGS_FILENAME)
|
||||||
|
|
||||||
lines: List[str] = []
|
lines: list[str] = []
|
||||||
if len(current_conf) == 0:
|
if len(current_conf) == 0:
|
||||||
lines = ["[secrets]\n"]
|
lines = ["[secrets]\n"]
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
@ -64,7 +63,7 @@ puppet_env["FACTER_zulip_conf_path"] = args.config
|
||||||
puppet_env["FACTER_zulip_scripts_path"] = scripts_path
|
puppet_env["FACTER_zulip_scripts_path"] = scripts_path
|
||||||
|
|
||||||
|
|
||||||
def noop_would_change(puppet_cmd: List[str]) -> bool:
|
def noop_would_change(puppet_cmd: list[str]) -> bool:
|
||||||
# --noop does not work with --detailed-exitcodes; see
|
# --noop does not work with --detailed-exitcodes; see
|
||||||
# https://tickets.puppetlabs.com/browse/PUP-686
|
# https://tickets.puppetlabs.com/browse/PUP-686
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -4,7 +4,6 @@ import json
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
|
||||||
|
|
||||||
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
|
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
|
||||||
|
|
||||||
|
@ -16,7 +15,7 @@ sanity_check.check_venv(__file__)
|
||||||
from scripts.lib.zulip_tools import ENDC, FAIL, WARNING
|
from scripts.lib.zulip_tools import ENDC, FAIL, WARNING
|
||||||
|
|
||||||
|
|
||||||
def find_handlebars(translatable_strings: List[str]) -> List[str]:
|
def find_handlebars(translatable_strings: list[str]) -> list[str]:
|
||||||
return [string for string in translatable_strings if "{{" in string]
|
return [string for string in translatable_strings if "{{" in string]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import configparser
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ def get_config() -> configparser.ConfigParser:
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def area_labeled(issue: Dict[str, Any]) -> bool:
|
def area_labeled(issue: dict[str, Any]) -> bool:
|
||||||
for label in issue["labels"]:
|
for label in issue["labels"]:
|
||||||
label_name = str(label["name"])
|
label_name = str(label["name"])
|
||||||
if "area:" in label_name:
|
if "area:" in label_name:
|
||||||
|
@ -35,7 +35,7 @@ def area_labeled(issue: Dict[str, Any]) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_issue(item: Dict[str, Any]) -> bool:
|
def is_issue(item: dict[str, Any]) -> bool:
|
||||||
return "issues" in item["html_url"]
|
return "issues" in item["html_url"]
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ def check_issue_labels() -> None:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
next_page_url: Optional[str] = "https://api.github.com/repos/zulip/zulip/issues"
|
next_page_url: Optional[str] = "https://api.github.com/repos/zulip/zulip/issues"
|
||||||
unlabeled_issue_urls: List[str] = []
|
unlabeled_issue_urls: list[str] = []
|
||||||
while next_page_url:
|
while next_page_url:
|
||||||
try:
|
try:
|
||||||
if args.force:
|
if args.force:
|
||||||
|
|
|
@ -14,7 +14,7 @@ import difflib
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Callable, Dict, List, Optional
|
from typing import Any, Callable, Optional
|
||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ DEPRECATED_EVENTS = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_event_checker(event: Dict[str, Any]) -> Optional[Callable[[str, Dict[str, Any]], None]]:
|
def get_event_checker(event: dict[str, Any]) -> Optional[Callable[[str, dict[str, Any]], None]]:
|
||||||
name = event["type"]
|
name = event["type"]
|
||||||
if "op" in event:
|
if "op" in event:
|
||||||
name += "_" + event["op"]
|
name += "_" + event["op"]
|
||||||
|
@ -99,7 +99,7 @@ def get_event_checker(event: Dict[str, Any]) -> Optional[Callable[[str, Dict[str
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def check_event(name: str, event: Dict[str, Any]) -> None:
|
def check_event(name: str, event: dict[str, Any]) -> None:
|
||||||
event["id"] = 1
|
event["id"] = 1
|
||||||
checker = get_event_checker(event)
|
checker = get_event_checker(event)
|
||||||
if checker is not None:
|
if checker is not None:
|
||||||
|
@ -112,7 +112,7 @@ def check_event(name: str, event: Dict[str, Any]) -> None:
|
||||||
print(f"WARNING - NEED SCHEMA: {name}")
|
print(f"WARNING - NEED SCHEMA: {name}")
|
||||||
|
|
||||||
|
|
||||||
def read_fixtures() -> Dict[str, Any]:
|
def read_fixtures() -> dict[str, Any]:
|
||||||
cmd = [
|
cmd = [
|
||||||
"node",
|
"node",
|
||||||
os.path.join(TOOLS_DIR, "node_lib/dump_fixtures.js"),
|
os.path.join(TOOLS_DIR, "node_lib/dump_fixtures.js"),
|
||||||
|
@ -121,7 +121,7 @@ def read_fixtures() -> Dict[str, Any]:
|
||||||
return orjson.loads(schema)
|
return orjson.loads(schema)
|
||||||
|
|
||||||
|
|
||||||
def verify_fixtures_are_sorted(names: List[str]) -> None:
|
def verify_fixtures_are_sorted(names: list[str]) -> None:
|
||||||
for i in range(1, len(names)):
|
for i in range(1, len(names)):
|
||||||
if names[i] < names[i - 1]:
|
if names[i] < names[i - 1]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
|
@ -135,7 +135,7 @@ def verify_fixtures_are_sorted(names: List[str]) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def from_openapi(node: Dict[str, Any]) -> Any:
|
def from_openapi(node: dict[str, Any]) -> Any:
|
||||||
"""Converts the OpenAPI data into event_schema.py style type
|
"""Converts the OpenAPI data into event_schema.py style type
|
||||||
definitions for convenient comparison with the types used for backend
|
definitions for convenient comparison with the types used for backend
|
||||||
tests declared there."""
|
tests declared there."""
|
||||||
|
|
|
@ -11,7 +11,7 @@ from tools.lib import sanity_check
|
||||||
|
|
||||||
sanity_check.check_venv(__file__)
|
sanity_check.check_venv(__file__)
|
||||||
|
|
||||||
from typing import Dict, Iterable, List
|
from typing import Iterable
|
||||||
|
|
||||||
from zulint import lister
|
from zulint import lister
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ EXCLUDED_FILES = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def check_our_files(modified_only: bool, all_dups: bool, fix: bool, targets: List[str]) -> None:
|
def check_our_files(modified_only: bool, all_dups: bool, fix: bool, targets: list[str]) -> None:
|
||||||
by_lang = lister.list_files(
|
by_lang = lister.list_files(
|
||||||
targets=targets,
|
targets=targets,
|
||||||
modified_only=modified_only,
|
modified_only=modified_only,
|
||||||
|
@ -53,7 +53,7 @@ def check_html_templates(templates: Iterable[str], all_dups: bool, fix: bool) ->
|
||||||
if "templates/corporate/team.html" in templates:
|
if "templates/corporate/team.html" in templates:
|
||||||
templates.remove("templates/corporate/team.html")
|
templates.remove("templates/corporate/team.html")
|
||||||
|
|
||||||
def check_for_duplicate_ids(templates: List[str]) -> Dict[str, List[str]]:
|
def check_for_duplicate_ids(templates: list[str]) -> dict[str, list[str]]:
|
||||||
template_id_dict = build_id_dict(templates)
|
template_id_dict = build_id_dict(templates)
|
||||||
# TODO: Clean up these cases of duplicate ids in the code
|
# TODO: Clean up these cases of duplicate ids in the code
|
||||||
IGNORE_IDS = [
|
IGNORE_IDS = [
|
||||||
|
|
|
@ -4,7 +4,7 @@ import platform
|
||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import Callable, List
|
from typing import Callable
|
||||||
|
|
||||||
TOOLS_DIR = os.path.dirname(__file__)
|
TOOLS_DIR = os.path.dirname(__file__)
|
||||||
ROOT_DIR = os.path.dirname(TOOLS_DIR)
|
ROOT_DIR = os.path.dirname(TOOLS_DIR)
|
||||||
|
@ -24,7 +24,7 @@ def run(check_func: Callable[[], bool]) -> None:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def run_command(args: List[str]) -> None:
|
def run_command(args: list[str]) -> None:
|
||||||
print(shlex.join(args))
|
print(shlex.join(args))
|
||||||
subprocess.check_call(args)
|
subprocess.check_call(args)
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import optparse
|
import optparse
|
||||||
from typing import List, Union
|
from typing import Union
|
||||||
|
|
||||||
from scrapy.commands import crawl
|
from scrapy.commands import crawl
|
||||||
from scrapy.crawler import Crawler
|
from scrapy.crawler import Crawler
|
||||||
|
|
||||||
|
|
||||||
class Command(crawl.Command):
|
class Command(crawl.Command):
|
||||||
def run(self, args: List[str], opts: optparse.Values) -> None:
|
def run(self, args: list[str], opts: optparse.Values) -> None:
|
||||||
crawlers = []
|
crawlers = []
|
||||||
real_create_crawler = self.crawler_process.create_crawler
|
real_create_crawler = self.crawler_process.create_crawler
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from .common.spiders import BaseDocumentationSpider
|
from .common.spiders import BaseDocumentationSpider
|
||||||
|
|
||||||
|
|
||||||
def get_start_url() -> List[str]:
|
def get_start_url() -> list[str]:
|
||||||
# Get index.html file as start URL and convert it to file URI
|
# Get index.html file as start URL and convert it to file URI
|
||||||
dir_path = os.path.dirname(os.path.realpath(__file__))
|
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
start_file = os.path.join(
|
start_file = os.path.join(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import os
|
import os
|
||||||
from posixpath import basename
|
from posixpath import basename
|
||||||
from typing import Any, List, Set
|
from typing import Any
|
||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
@ -20,7 +20,7 @@ class UnusedImagesLinterSpider(BaseDocumentationSpider):
|
||||||
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.static_images: Set[str] = set()
|
self.static_images: set[str] = set()
|
||||||
self.images_static_dir: str = get_images_dir(self.images_path)
|
self.images_static_dir: str = get_images_dir(self.images_path)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -45,7 +45,7 @@ class UnusedImagesLinterSpider(BaseDocumentationSpider):
|
||||||
class HelpDocumentationSpider(UnusedImagesLinterSpider):
|
class HelpDocumentationSpider(UnusedImagesLinterSpider):
|
||||||
name = "help_documentation_crawler"
|
name = "help_documentation_crawler"
|
||||||
start_urls = ["http://localhost:9981/help/"]
|
start_urls = ["http://localhost:9981/help/"]
|
||||||
deny_domains: List[str] = []
|
deny_domains: list[str] = []
|
||||||
deny = ["/policies/privacy"]
|
deny = ["/policies/privacy"]
|
||||||
images_path = "static/images/help"
|
images_path = "static/images/help"
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ class HelpDocumentationSpider(UnusedImagesLinterSpider):
|
||||||
class APIDocumentationSpider(UnusedImagesLinterSpider):
|
class APIDocumentationSpider(UnusedImagesLinterSpider):
|
||||||
name = "api_documentation_crawler"
|
name = "api_documentation_crawler"
|
||||||
start_urls = ["http://localhost:9981/api"]
|
start_urls = ["http://localhost:9981/api"]
|
||||||
deny_domains: List[str] = []
|
deny_domains: list[str] = []
|
||||||
images_path = "static/images/api"
|
images_path = "static/images/api"
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,4 +84,4 @@ class PorticoDocumentationSpider(BaseDocumentationSpider):
|
||||||
"http://localhost:9981/for/research/",
|
"http://localhost:9981/for/research/",
|
||||||
"http://localhost:9981/security/",
|
"http://localhost:9981/security/",
|
||||||
]
|
]
|
||||||
deny_domains: List[str] = []
|
deny_domains: list[str] = []
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from typing import Callable, Iterator, List, Optional, Union
|
from typing import Callable, Iterator, Optional, Union
|
||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
import scrapy
|
import scrapy
|
||||||
|
@ -60,10 +60,10 @@ ZULIP_SERVER_GITHUB_DIRECTORY_PATH_PREFIX = "/zulip/zulip/tree/main"
|
||||||
class BaseDocumentationSpider(scrapy.Spider):
|
class BaseDocumentationSpider(scrapy.Spider):
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
# Exclude domain address.
|
# Exclude domain address.
|
||||||
deny_domains: List[str] = []
|
deny_domains: list[str] = []
|
||||||
start_urls: List[str] = []
|
start_urls: list[str] = []
|
||||||
deny: List[str] = []
|
deny: list[str] = []
|
||||||
file_extensions: List[str] = ["." + ext for ext in IGNORED_EXTENSIONS]
|
file_extensions: list[str] = ["." + ext for ext in IGNORED_EXTENSIONS]
|
||||||
tags = ("a", "area", "img")
|
tags = ("a", "area", "img")
|
||||||
attrs = ("href", "src")
|
attrs = ("href", "src")
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ import re
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
@ -30,7 +29,7 @@ append_key = """\
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def get_mentor_keys(username: str) -> List[str]:
|
def get_mentor_keys(username: str) -> list[str]:
|
||||||
url = f"https://api.github.com/users/{username}/keys"
|
url = f"https://api.github.com/users/{username}/keys"
|
||||||
|
|
||||||
r = requests.get(url)
|
r = requests.get(url)
|
||||||
|
|
|
@ -20,7 +20,7 @@ import sys
|
||||||
import time
|
import time
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from typing import Any, Dict, List, Tuple
|
from typing import Any
|
||||||
|
|
||||||
import digitalocean
|
import digitalocean
|
||||||
import requests
|
import requests
|
||||||
|
@ -56,7 +56,7 @@ def assert_github_user_exists(github_username: str) -> bool:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def get_ssh_public_keys_from_github(github_username: str) -> List[Dict[str, Any]]:
|
def get_ssh_public_keys_from_github(github_username: str) -> list[dict[str, Any]]:
|
||||||
print("Checking to see that GitHub user has available public keys...")
|
print("Checking to see that GitHub user has available public keys...")
|
||||||
apiurl_keys = f"https://api.github.com/users/{github_username}/keys"
|
apiurl_keys = f"https://api.github.com/users/{github_username}/keys"
|
||||||
try:
|
try:
|
||||||
|
@ -108,12 +108,12 @@ def assert_droplet_does_not_exist(my_token: str, droplet_name: str, recreate: bo
|
||||||
print("...No droplet found...proceeding.")
|
print("...No droplet found...proceeding.")
|
||||||
|
|
||||||
|
|
||||||
def get_ssh_keys_string_from_github_ssh_key_dicts(userkey_dicts: List[Dict[str, Any]]) -> str:
|
def get_ssh_keys_string_from_github_ssh_key_dicts(userkey_dicts: list[dict[str, Any]]) -> str:
|
||||||
return "\n".join(userkey_dict["key"] for userkey_dict in userkey_dicts)
|
return "\n".join(userkey_dict["key"] for userkey_dict in userkey_dicts)
|
||||||
|
|
||||||
|
|
||||||
def generate_dev_droplet_user_data(
|
def generate_dev_droplet_user_data(
|
||||||
username: str, subdomain: str, userkey_dicts: List[Dict[str, Any]]
|
username: str, subdomain: str, userkey_dicts: list[dict[str, Any]]
|
||||||
) -> str:
|
) -> str:
|
||||||
ssh_keys_string = get_ssh_keys_string_from_github_ssh_key_dicts(userkey_dicts)
|
ssh_keys_string = get_ssh_keys_string_from_github_ssh_key_dicts(userkey_dicts)
|
||||||
setup_root_ssh_keys = f"printf '{ssh_keys_string}' > /root/.ssh/authorized_keys"
|
setup_root_ssh_keys = f"printf '{ssh_keys_string}' > /root/.ssh/authorized_keys"
|
||||||
|
@ -159,7 +159,7 @@ su -c 'git config --global pull.rebase true' zulipdev
|
||||||
return cloudconf
|
return cloudconf
|
||||||
|
|
||||||
|
|
||||||
def generate_prod_droplet_user_data(username: str, userkey_dicts: List[Dict[str, Any]]) -> str:
|
def generate_prod_droplet_user_data(username: str, userkey_dicts: list[dict[str, Any]]) -> str:
|
||||||
ssh_keys_string = get_ssh_keys_string_from_github_ssh_key_dicts(userkey_dicts)
|
ssh_keys_string = get_ssh_keys_string_from_github_ssh_key_dicts(userkey_dicts)
|
||||||
setup_root_ssh_keys = f"printf '{ssh_keys_string}' > /root/.ssh/authorized_keys"
|
setup_root_ssh_keys = f"printf '{ssh_keys_string}' > /root/.ssh/authorized_keys"
|
||||||
|
|
||||||
|
@ -179,10 +179,10 @@ def create_droplet(
|
||||||
my_token: str,
|
my_token: str,
|
||||||
template_id: str,
|
template_id: str,
|
||||||
name: str,
|
name: str,
|
||||||
tags: List[str],
|
tags: list[str],
|
||||||
user_data: str,
|
user_data: str,
|
||||||
region: str = "nyc3",
|
region: str = "nyc3",
|
||||||
) -> Tuple[str, str]:
|
) -> tuple[str, str]:
|
||||||
droplet = digitalocean.Droplet(
|
droplet = digitalocean.Droplet(
|
||||||
token=my_token,
|
token=my_token,
|
||||||
name=name,
|
name=name,
|
||||||
|
@ -215,7 +215,7 @@ def create_droplet(
|
||||||
return (droplet.ip_address, droplet.ip_v6_address)
|
return (droplet.ip_address, droplet.ip_v6_address)
|
||||||
|
|
||||||
|
|
||||||
def delete_existing_records(records: List[digitalocean.Record], record_name: str) -> None:
|
def delete_existing_records(records: list[digitalocean.Record], record_name: str) -> None:
|
||||||
count = 0
|
count = 0
|
||||||
for record in records:
|
for record in records:
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -11,7 +11,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Dict, List, Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||||
from scripts.lib.setup_path import setup_path
|
from scripts.lib.setup_path import setup_path
|
||||||
|
@ -42,7 +42,7 @@ args = parser.parse_args()
|
||||||
|
|
||||||
class ContributorsJSON(TypedDict):
|
class ContributorsJSON(TypedDict):
|
||||||
date: str
|
date: str
|
||||||
contributors: List[Dict[str, Union[int, str]]]
|
contributors: list[dict[str, Union[int, str]]]
|
||||||
|
|
||||||
|
|
||||||
class Contributor(TypedDict):
|
class Contributor(TypedDict):
|
||||||
|
@ -56,15 +56,15 @@ class Contributor(TypedDict):
|
||||||
logger = logging.getLogger("zulip.fetch_contributors_json")
|
logger = logging.getLogger("zulip.fetch_contributors_json")
|
||||||
|
|
||||||
|
|
||||||
def fetch_contributors(repo_name: str, max_retries: int) -> List[Contributor]:
|
def fetch_contributors(repo_name: str, max_retries: int) -> list[Contributor]:
|
||||||
contributors: List[Contributor] = []
|
contributors: list[Contributor] = []
|
||||||
page_index = 1
|
page_index = 1
|
||||||
|
|
||||||
api_link = f"https://api.github.com/repos/zulip/{repo_name}/contributors"
|
api_link = f"https://api.github.com/repos/zulip/{repo_name}/contributors"
|
||||||
api_data = {"anon": "1"}
|
api_data = {"anon": "1"}
|
||||||
certificates = os.environ.get("CUSTOM_CA_CERTIFICATES")
|
certificates = os.environ.get("CUSTOM_CA_CERTIFICATES")
|
||||||
|
|
||||||
headers: Dict[str, str] = {}
|
headers: dict[str, str] = {}
|
||||||
personal_access_token = get_secret("github_personal_access_token")
|
personal_access_token = get_secret("github_personal_access_token")
|
||||||
if personal_access_token is not None:
|
if personal_access_token is not None:
|
||||||
headers = {"Authorization": f"token {personal_access_token}"}
|
headers = {"Authorization": f"token {personal_access_token}"}
|
||||||
|
@ -134,7 +134,7 @@ def update_contributor_data_file() -> None:
|
||||||
]
|
]
|
||||||
|
|
||||||
data: ContributorsJSON = dict(date=str(datetime.now(tz=timezone.utc).date()), contributors=[])
|
data: ContributorsJSON = dict(date=str(datetime.now(tz=timezone.utc).date()), contributors=[])
|
||||||
contributor_username_to_data: Dict[str, Dict[str, Union[str, int]]] = {}
|
contributor_username_to_data: dict[str, dict[str, Union[str, int]]] = {}
|
||||||
|
|
||||||
for repo_name in repo_names:
|
for repo_name in repo_names:
|
||||||
contributors = fetch_contributors(repo_name, args.max_retries)
|
contributors = fetch_contributors(repo_name, args.max_retries)
|
||||||
|
|
|
@ -3,14 +3,13 @@ import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from subprocess import check_output
|
from subprocess import check_output
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
|
|
||||||
def get_json_filename(locale: str) -> str:
|
def get_json_filename(locale: str) -> str:
|
||||||
return f"locale/{locale}/mobile.json"
|
return f"locale/{locale}/mobile.json"
|
||||||
|
|
||||||
|
|
||||||
def get_locales() -> List[str]:
|
def get_locales() -> list[str]:
|
||||||
output = check_output(["git", "ls-files", "locale"], text=True)
|
output = check_output(["git", "ls-files", "locale"], text=True)
|
||||||
tracked_files = output.split()
|
tracked_files = output.split()
|
||||||
regex = re.compile(r"locale/(\w+)/LC_MESSAGES/django.po")
|
regex = re.compile(r"locale/(\w+)/LC_MESSAGES/django.po")
|
||||||
|
@ -23,7 +22,7 @@ def get_locales() -> List[str]:
|
||||||
return locales
|
return locales
|
||||||
|
|
||||||
|
|
||||||
def get_translation_stats(resource_path: str) -> Dict[str, int]:
|
def get_translation_stats(resource_path: str) -> dict[str, int]:
|
||||||
with open(resource_path) as raw_resource_file:
|
with open(resource_path) as raw_resource_file:
|
||||||
raw_info = json.load(raw_resource_file)
|
raw_info = json.load(raw_resource_file)
|
||||||
|
|
||||||
|
@ -32,7 +31,7 @@ def get_translation_stats(resource_path: str) -> Dict[str, int]:
|
||||||
return {"total": total, "not_translated": not_translated}
|
return {"total": total, "not_translated": not_translated}
|
||||||
|
|
||||||
|
|
||||||
translation_stats: Dict[str, Dict[str, int]] = {}
|
translation_stats: dict[str, dict[str, int]] = {}
|
||||||
locale_paths = [] # List[str]
|
locale_paths = [] # List[str]
|
||||||
for locale in get_locales():
|
for locale in get_locales():
|
||||||
path = get_json_filename(locale)
|
path = get_json_filename(locale)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import argparse
|
||||||
import html
|
import html
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
from typing import Dict, NamedTuple
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
|
||||||
class CLIArgs(NamedTuple):
|
class CLIArgs(NamedTuple):
|
||||||
|
@ -36,7 +36,7 @@ if __name__ == "__main__":
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
print(f"unescaping file {args.filename}", file=sys.stderr)
|
print(f"unescaping file {args.filename}", file=sys.stderr)
|
||||||
|
|
||||||
json_data: Dict[str, str] = {}
|
json_data: dict[str, str] = {}
|
||||||
|
|
||||||
with open(args.filename) as source:
|
with open(args.filename) as source:
|
||||||
json_data = json.load(source)
|
json_data = json.load(source)
|
||||||
|
|
|
@ -3,7 +3,6 @@ import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from subprocess import check_output
|
from subprocess import check_output
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
LEGACY_STRINGS_MAP = {
|
LEGACY_STRINGS_MAP = {
|
||||||
"<p>You are searching for messages that belong to more than one channel, which is not possible.</p>": "<p>You are searching for messages that belong to more than one stream, which is not possible.</p>",
|
"<p>You are searching for messages that belong to more than one channel, which is not possible.</p>": "<p>You are searching for messages that belong to more than one stream, which is not possible.</p>",
|
||||||
|
@ -187,7 +186,7 @@ def get_legacy_filename(locale: str) -> str:
|
||||||
return f"locale/{locale}/legacy_stream_translations.json"
|
return f"locale/{locale}/legacy_stream_translations.json"
|
||||||
|
|
||||||
|
|
||||||
def get_locales() -> List[str]:
|
def get_locales() -> list[str]:
|
||||||
output = check_output(["git", "ls-files", "locale"], text=True)
|
output = check_output(["git", "ls-files", "locale"], text=True)
|
||||||
tracked_files = output.split()
|
tracked_files = output.split()
|
||||||
regex = re.compile(r"locale/(\w+)/LC_MESSAGES/django.po")
|
regex = re.compile(r"locale/(\w+)/LC_MESSAGES/django.po")
|
||||||
|
@ -200,7 +199,7 @@ def get_locales() -> List[str]:
|
||||||
return locales
|
return locales
|
||||||
|
|
||||||
|
|
||||||
def get_translations(path: str) -> Dict[str, str]:
|
def get_translations(path: str) -> dict[str, str]:
|
||||||
with open(path) as raw_resource_file:
|
with open(path) as raw_resource_file:
|
||||||
translations = json.load(raw_resource_file)
|
translations = json.load(raw_resource_file)
|
||||||
|
|
||||||
|
@ -208,10 +207,10 @@ def get_translations(path: str) -> Dict[str, str]:
|
||||||
|
|
||||||
|
|
||||||
def update_for_legacy_stream_translations(
|
def update_for_legacy_stream_translations(
|
||||||
current: Dict[str, str], legacy: Dict[str, str], path: str
|
current: dict[str, str], legacy: dict[str, str], path: str
|
||||||
) -> None:
|
) -> None:
|
||||||
number_of_updates = 0
|
number_of_updates = 0
|
||||||
updated_translations: Dict[str, str] = {}
|
updated_translations: dict[str, str] = {}
|
||||||
for line in current:
|
for line in current:
|
||||||
# If the string has a legacy string mapped and see if it's
|
# If the string has a legacy string mapped and see if it's
|
||||||
# not currently translated (e.g. an empty string), then use
|
# not currently translated (e.g. an empty string), then use
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import re
|
import re
|
||||||
from typing import List, Match, Tuple
|
from typing import Match
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@ def is_capitalized(safe_text: str) -> bool:
|
||||||
return not any(DISALLOWED_REGEX.search(sentence.strip()) for sentence in sentences)
|
return not any(DISALLOWED_REGEX.search(sentence.strip()) for sentence in sentences)
|
||||||
|
|
||||||
|
|
||||||
def check_banned_words(text: str) -> List[str]:
|
def check_banned_words(text: str) -> list[str]:
|
||||||
lower_cased_text = text.lower()
|
lower_cased_text = text.lower()
|
||||||
errors = []
|
errors = []
|
||||||
for word, reason in BANNED_WORDS.items():
|
for word, reason in BANNED_WORDS.items():
|
||||||
|
@ -266,7 +266,7 @@ def check_banned_words(text: str) -> List[str]:
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
def check_capitalization(strings: List[str]) -> Tuple[List[str], List[str], List[str]]:
|
def check_capitalization(strings: list[str]) -> tuple[list[str], list[str], list[str]]:
|
||||||
errors = []
|
errors = []
|
||||||
ignored = []
|
ignored = []
|
||||||
banned_word_errors = []
|
banned_word_errors = []
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from gitlint.git import GitCommit
|
from gitlint.git import GitCommit
|
||||||
from gitlint.rules import CommitMessageTitle, LineRule, RuleViolation
|
from gitlint.rules import CommitMessageTitle, LineRule, RuleViolation
|
||||||
|
|
||||||
|
@ -87,7 +85,7 @@ class ImperativeMood(LineRule):
|
||||||
'("{word}" -> "{imperative}"): "{title}"'
|
'("{word}" -> "{imperative}"): "{title}"'
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate(self, line: str, commit: GitCommit) -> List[RuleViolation]:
|
def validate(self, line: str, commit: GitCommit) -> list[RuleViolation]:
|
||||||
violations = []
|
violations = []
|
||||||
|
|
||||||
# Ignore the section tag (ie `<section tag>: <message body>.`)
|
# Ignore the section tag (ie `<section tag>: <message body>.`)
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import re
|
import re
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
from .template_parser import FormattedError, Token, tokenize
|
from .template_parser import FormattedError, Token, tokenize
|
||||||
|
|
||||||
|
|
||||||
class TagInfo:
|
class TagInfo:
|
||||||
def __init__(self, tag: str, classes: List[str], ids: List[str], token: Token) -> None:
|
def __init__(self, tag: str, classes: list[str], ids: list[str], token: Token) -> None:
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
self.classes = classes
|
self.classes = classes
|
||||||
self.ids = ids
|
self.ids = ids
|
||||||
|
@ -29,8 +28,8 @@ class TagInfo:
|
||||||
def get_tag_info(token: Token) -> TagInfo:
|
def get_tag_info(token: Token) -> TagInfo:
|
||||||
s = token.s
|
s = token.s
|
||||||
tag = token.tag
|
tag = token.tag
|
||||||
classes: List[str] = []
|
classes: list[str] = []
|
||||||
ids: List[str] = []
|
ids: list[str] = []
|
||||||
|
|
||||||
searches = [
|
searches = [
|
||||||
(classes, ' class="(.*?)"'),
|
(classes, ' class="(.*?)"'),
|
||||||
|
@ -48,7 +47,7 @@ def get_tag_info(token: Token) -> TagInfo:
|
||||||
return TagInfo(tag=tag, classes=classes, ids=ids, token=token)
|
return TagInfo(tag=tag, classes=classes, ids=ids, token=token)
|
||||||
|
|
||||||
|
|
||||||
def split_for_id_and_class(element: str) -> List[str]:
|
def split_for_id_and_class(element: str) -> list[str]:
|
||||||
# Here we split a given string which is expected to contain id or class
|
# Here we split a given string which is expected to contain id or class
|
||||||
# attributes from HTML tags. This also takes care of template variables
|
# attributes from HTML tags. This also takes care of template variables
|
||||||
# in string during splitting process. For eg. 'red black {{ a|b|c }}'
|
# in string during splitting process. For eg. 'red black {{ a|b|c }}'
|
||||||
|
@ -74,8 +73,8 @@ def split_for_id_and_class(element: str) -> List[str]:
|
||||||
return lst
|
return lst
|
||||||
|
|
||||||
|
|
||||||
def build_id_dict(templates: List[str]) -> Dict[str, List[str]]:
|
def build_id_dict(templates: list[str]) -> dict[str, list[str]]:
|
||||||
template_id_dict: Dict[str, List[str]] = defaultdict(list)
|
template_id_dict: dict[str, list[str]] = defaultdict(list)
|
||||||
|
|
||||||
for fn in templates:
|
for fn in templates:
|
||||||
with open(fn) as f:
|
with open(fn) as f:
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import List, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from zulint.printer import BOLDRED, CYAN, ENDC, GREEN
|
from zulint.printer import BOLDRED, CYAN, ENDC, GREEN
|
||||||
|
|
||||||
from .template_parser import Token
|
from .template_parser import Token
|
||||||
|
|
||||||
|
|
||||||
def shift_indents_to_the_next_tokens(tokens: List[Token]) -> None:
|
def shift_indents_to_the_next_tokens(tokens: list[Token]) -> None:
|
||||||
"""
|
"""
|
||||||
During the parsing/validation phase, it's useful to have separate
|
During the parsing/validation phase, it's useful to have separate
|
||||||
tokens for "indent" chunks, but during pretty printing, we like
|
tokens for "indent" chunks, but during pretty printing, we like
|
||||||
|
@ -39,7 +39,7 @@ def token_allows_children_to_skip_indents(token: Token) -> bool:
|
||||||
return token.kind in ("django_start", "handlebars_start") or token.tag == "a"
|
return token.kind in ("django_start", "handlebars_start") or token.tag == "a"
|
||||||
|
|
||||||
|
|
||||||
def adjust_block_indentation(tokens: List[Token], fn: str) -> None:
|
def adjust_block_indentation(tokens: list[Token], fn: str) -> None:
|
||||||
start_token: Optional[Token] = None
|
start_token: Optional[Token] = None
|
||||||
|
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
|
@ -106,7 +106,7 @@ def adjust_block_indentation(tokens: List[Token], fn: str) -> None:
|
||||||
token.indent = start_token.child_indent
|
token.indent = start_token.child_indent
|
||||||
|
|
||||||
|
|
||||||
def fix_indents_for_multi_line_tags(tokens: List[Token]) -> None:
|
def fix_indents_for_multi_line_tags(tokens: list[Token]) -> None:
|
||||||
def fix(frag: str) -> str:
|
def fix(frag: str) -> str:
|
||||||
frag = frag.strip()
|
frag = frag.strip()
|
||||||
return continue_indent + frag if frag else ""
|
return continue_indent + frag if frag else ""
|
||||||
|
@ -128,13 +128,13 @@ def fix_indents_for_multi_line_tags(tokens: List[Token]) -> None:
|
||||||
token.new_s = frags[0] + "\n" + "\n".join(fix(frag) for frag in frags[1:])
|
token.new_s = frags[0] + "\n" + "\n".join(fix(frag) for frag in frags[1:])
|
||||||
|
|
||||||
|
|
||||||
def apply_token_indents(tokens: List[Token]) -> None:
|
def apply_token_indents(tokens: list[Token]) -> None:
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
if token.indent:
|
if token.indent:
|
||||||
token.new_s = token.indent + token.new_s
|
token.new_s = token.indent + token.new_s
|
||||||
|
|
||||||
|
|
||||||
def pretty_print_html(tokens: List[Token], fn: str) -> str:
|
def pretty_print_html(tokens: list[Token], fn: str) -> str:
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
token.new_s = token.s
|
token.new_s = token.s
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ def numbered_lines(s: str) -> str:
|
||||||
return "".join(f"{i + 1: >5} {line}\n" for i, line in enumerate(s.split("\n")))
|
return "".join(f"{i + 1: >5} {line}\n" for i, line in enumerate(s.split("\n")))
|
||||||
|
|
||||||
|
|
||||||
def validate_indent_html(fn: str, tokens: List[Token], fix: bool) -> bool:
|
def validate_indent_html(fn: str, tokens: list[Token], fix: bool) -> bool:
|
||||||
with open(fn) as f:
|
with open(fn) as f:
|
||||||
html = f.read()
|
html = f.read()
|
||||||
phtml = pretty_print_html(tokens, fn)
|
phtml = pretty_print_html(tokens, fn)
|
||||||
|
|
|
@ -6,7 +6,7 @@ import os
|
||||||
import platform
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import List, NoReturn
|
from typing import NoReturn
|
||||||
|
|
||||||
os.environ["PYTHONUNBUFFERED"] = "y"
|
os.environ["PYTHONUNBUFFERED"] = "y"
|
||||||
|
|
||||||
|
@ -232,7 +232,7 @@ def install_system_deps() -> None:
|
||||||
run_as_root(["./scripts/lib/build-pgroonga"])
|
run_as_root(["./scripts/lib/build-pgroonga"])
|
||||||
|
|
||||||
|
|
||||||
def install_apt_deps(deps_to_install: List[str]) -> None:
|
def install_apt_deps(deps_to_install: list[str]) -> None:
|
||||||
# setup-apt-repo does an `apt-get update` if the sources.list files changed.
|
# setup-apt-repo does an `apt-get update` if the sources.list files changed.
|
||||||
run_as_root(["./scripts/lib/setup-apt-repo"])
|
run_as_root(["./scripts/lib/setup-apt-repo"])
|
||||||
|
|
||||||
|
@ -255,7 +255,7 @@ def install_apt_deps(deps_to_install: List[str]) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def install_yum_deps(deps_to_install: List[str]) -> None:
|
def install_yum_deps(deps_to_install: list[str]) -> None:
|
||||||
print(WARNING + "RedHat support is still experimental." + ENDC)
|
print(WARNING + "RedHat support is still experimental." + ENDC)
|
||||||
run_as_root(["./scripts/lib/setup-yum-repo"])
|
run_as_root(["./scripts/lib/setup-yum-repo"])
|
||||||
|
|
||||||
|
@ -265,7 +265,7 @@ def install_yum_deps(deps_to_install: List[str]) -> None:
|
||||||
#
|
#
|
||||||
# Error: Package: moreutils-0.49-2.el7.x86_64 (epel)
|
# Error: Package: moreutils-0.49-2.el7.x86_64 (epel)
|
||||||
# Requires: perl(IPC::Run)
|
# Requires: perl(IPC::Run)
|
||||||
yum_extra_flags: List[str] = []
|
yum_extra_flags: list[str] = []
|
||||||
if vendor == "rhel":
|
if vendor == "rhel":
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
["sudo", "subscription-manager", "status"],
|
["sudo", "subscription-manager", "status"],
|
||||||
|
|
|
@ -11,7 +11,6 @@ import glob
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
|
||||||
|
|
||||||
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
@ -57,7 +56,7 @@ def create_var_directories() -> None:
|
||||||
os.makedirs(path, exist_ok=True)
|
os.makedirs(path, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
def build_pygments_data_paths() -> List[str]:
|
def build_pygments_data_paths() -> list[str]:
|
||||||
paths = [
|
paths = [
|
||||||
"tools/setup/build_pygments_data",
|
"tools/setup/build_pygments_data",
|
||||||
"tools/setup/lang.json",
|
"tools/setup/lang.json",
|
||||||
|
@ -65,27 +64,27 @@ def build_pygments_data_paths() -> List[str]:
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
|
|
||||||
def build_timezones_data_paths() -> List[str]:
|
def build_timezones_data_paths() -> list[str]:
|
||||||
paths = [
|
paths = [
|
||||||
"tools/setup/build_timezone_values",
|
"tools/setup/build_timezone_values",
|
||||||
]
|
]
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
|
|
||||||
def build_landing_page_images_paths() -> List[str]:
|
def build_landing_page_images_paths() -> list[str]:
|
||||||
paths = ["tools/setup/generate_landing_page_images.py"]
|
paths = ["tools/setup/generate_landing_page_images.py"]
|
||||||
paths += glob.glob("static/images/landing-page/hello/original/*")
|
paths += glob.glob("static/images/landing-page/hello/original/*")
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
|
|
||||||
def compilemessages_paths() -> List[str]:
|
def compilemessages_paths() -> list[str]:
|
||||||
paths = ["zerver/management/commands/compilemessages.py"]
|
paths = ["zerver/management/commands/compilemessages.py"]
|
||||||
paths += glob.glob("locale/*/LC_MESSAGES/*.po")
|
paths += glob.glob("locale/*/LC_MESSAGES/*.po")
|
||||||
paths += glob.glob("locale/*/translations.json")
|
paths += glob.glob("locale/*/translations.json")
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
|
|
||||||
def configure_rabbitmq_paths() -> List[str]:
|
def configure_rabbitmq_paths() -> list[str]:
|
||||||
paths = [
|
paths = [
|
||||||
"scripts/setup/configure-rabbitmq",
|
"scripts/setup/configure-rabbitmq",
|
||||||
]
|
]
|
||||||
|
@ -194,7 +193,7 @@ def need_to_run_compilemessages() -> bool:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def need_to_run_configure_rabbitmq(settings_list: List[str]) -> bool:
|
def need_to_run_configure_rabbitmq(settings_list: list[str]) -> bool:
|
||||||
obsolete = is_digest_obsolete(
|
obsolete = is_digest_obsolete(
|
||||||
"last_configure_rabbitmq_hash",
|
"last_configure_rabbitmq_hash",
|
||||||
configure_rabbitmq_paths(),
|
configure_rabbitmq_paths(),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Callable, List, Optional
|
from typing import Callable, Optional
|
||||||
|
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ class Token:
|
||||||
self.parent_token: Optional[Token] = None
|
self.parent_token: Optional[Token] = None
|
||||||
|
|
||||||
|
|
||||||
def tokenize(text: str, template_format: Optional[str] = None) -> List[Token]:
|
def tokenize(text: str, template_format: Optional[str] = None) -> list[Token]:
|
||||||
in_code_block = False
|
in_code_block = False
|
||||||
|
|
||||||
def advance(n: int) -> None:
|
def advance(n: int) -> None:
|
||||||
|
@ -124,7 +124,7 @@ def tokenize(text: str, template_format: Optional[str] = None) -> List[Token]:
|
||||||
return looking_at("\n") or looking_at(" ")
|
return looking_at("\n") or looking_at(" ")
|
||||||
|
|
||||||
state = TokenizerState()
|
state = TokenizerState()
|
||||||
tokens: List[Token] = []
|
tokens: list[Token] = []
|
||||||
|
|
||||||
while state.i < len(text):
|
while state.i < len(text):
|
||||||
try:
|
try:
|
||||||
|
@ -355,7 +355,7 @@ def validate(
|
||||||
fn: Optional[str] = None,
|
fn: Optional[str] = None,
|
||||||
text: Optional[str] = None,
|
text: Optional[str] = None,
|
||||||
template_format: Optional[str] = None,
|
template_format: Optional[str] = None,
|
||||||
) -> List[Token]:
|
) -> list[Token]:
|
||||||
assert fn or text
|
assert fn or text
|
||||||
|
|
||||||
if fn is None:
|
if fn is None:
|
||||||
|
@ -500,7 +500,7 @@ def validate(
|
||||||
return tokens
|
return tokens
|
||||||
|
|
||||||
|
|
||||||
def ensure_matching_indentation(fn: str, tokens: List[Token], lines: List[str]) -> None:
|
def ensure_matching_indentation(fn: str, tokens: list[Token], lines: list[str]) -> None:
|
||||||
def has_bad_indentation() -> bool:
|
def has_bad_indentation() -> bool:
|
||||||
is_inline_tag = start_tag in HTML_INLINE_TAGS and start_token.kind == "html_start"
|
is_inline_tag = start_tag in HTML_INLINE_TAGS and start_token.kind == "html_start"
|
||||||
|
|
||||||
|
@ -545,7 +545,7 @@ def ensure_matching_indentation(fn: str, tokens: List[Token], lines: List[str])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def prevent_extra_newlines(fn: str, tokens: List[Token]) -> None:
|
def prevent_extra_newlines(fn: str, tokens: list[Token]) -> None:
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
|
@ -560,7 +560,7 @@ def prevent_extra_newlines(fn: str, tokens: List[Token]) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def prevent_whitespace_violations(fn: str, tokens: List[Token]) -> None:
|
def prevent_whitespace_violations(fn: str, tokens: list[Token]) -> None:
|
||||||
if tokens[0].kind in ("indent", "whitespace"):
|
if tokens[0].kind in ("indent", "whitespace"):
|
||||||
raise TemplateParserError(f" Please remove the whitespace at the beginning of {fn}.")
|
raise TemplateParserError(f" Please remove the whitespace at the beginning of {fn}.")
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from typing import Iterable, List, Optional, Tuple
|
from typing import Iterable, Optional
|
||||||
|
|
||||||
from scripts.lib.zulip_tools import get_dev_uuid_var_path
|
from scripts.lib.zulip_tools import get_dev_uuid_var_path
|
||||||
from version import PROVISION_VERSION
|
from version import PROVISION_VERSION
|
||||||
|
@ -25,7 +25,7 @@ properly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def preamble(version: Tuple[int, ...]) -> str:
|
def preamble(version: tuple[int, ...]) -> str:
|
||||||
text = PREAMBLE.format(version, PROVISION_VERSION)
|
text = PREAMBLE.format(version, PROVISION_VERSION)
|
||||||
text += "\n"
|
text += "\n"
|
||||||
return text
|
return text
|
||||||
|
@ -49,7 +49,7 @@ Do this: `./tools/provision`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def get_provisioning_status() -> Tuple[bool, Optional[str]]:
|
def get_provisioning_status() -> tuple[bool, Optional[str]]:
|
||||||
version_file = get_version_file()
|
version_file = get_version_file()
|
||||||
if not os.path.exists(version_file):
|
if not os.path.exists(version_file):
|
||||||
# If the developer doesn't have a version_file written by
|
# If the developer doesn't have a version_file written by
|
||||||
|
@ -93,7 +93,7 @@ def add_provision_check_override_param(parser: ArgumentParser) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def find_js_test_files(test_dir: str, files: Iterable[str]) -> List[str]:
|
def find_js_test_files(test_dir: str, files: Iterable[str]) -> list[str]:
|
||||||
test_files = []
|
test_files = []
|
||||||
for file in files:
|
for file in files:
|
||||||
file = min(
|
file = min(
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from zulint.custom_rules import Rule, RuleList
|
from zulint.custom_rules import Rule, RuleList
|
||||||
|
|
||||||
# Rule help:
|
# Rule help:
|
||||||
|
@ -39,7 +37,7 @@ FILES_WITH_LEGACY_SUBJECT = {
|
||||||
"zerver/tests/test_message_fetch.py",
|
"zerver/tests/test_message_fetch.py",
|
||||||
}
|
}
|
||||||
|
|
||||||
shebang_rules: List["Rule"] = [
|
shebang_rules: list["Rule"] = [
|
||||||
{
|
{
|
||||||
"pattern": r"\A#!",
|
"pattern": r"\A#!",
|
||||||
"description": "zerver library code shouldn't have a shebang line.",
|
"description": "zerver library code shouldn't have a shebang line.",
|
||||||
|
@ -59,7 +57,7 @@ shebang_rules: List["Rule"] = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
base_whitespace_rules: List["Rule"] = [
|
base_whitespace_rules: list["Rule"] = [
|
||||||
{
|
{
|
||||||
"pattern": r"[\t ]+$",
|
"pattern": r"[\t ]+$",
|
||||||
"exclude": {"tools/ci/success-http-headers.template.txt"},
|
"exclude": {"tools/ci/success-http-headers.template.txt"},
|
||||||
|
@ -70,7 +68,7 @@ base_whitespace_rules: List["Rule"] = [
|
||||||
"description": "Missing newline at end of file",
|
"description": "Missing newline at end of file",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
whitespace_rules: List["Rule"] = [
|
whitespace_rules: list["Rule"] = [
|
||||||
*base_whitespace_rules,
|
*base_whitespace_rules,
|
||||||
{
|
{
|
||||||
"pattern": "http://zulip.readthedocs.io",
|
"pattern": "http://zulip.readthedocs.io",
|
||||||
|
@ -85,7 +83,7 @@ whitespace_rules: List["Rule"] = [
|
||||||
"description": "Web app should be two words",
|
"description": "Web app should be two words",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
comma_whitespace_rule: List["Rule"] = [
|
comma_whitespace_rule: list["Rule"] = [
|
||||||
{
|
{
|
||||||
"pattern": ", {2,}[^#/ ]",
|
"pattern": ", {2,}[^#/ ]",
|
||||||
"exclude": {"zerver/tests", "web/tests", "corporate/tests"},
|
"exclude": {"zerver/tests", "web/tests", "corporate/tests"},
|
||||||
|
@ -94,7 +92,7 @@ comma_whitespace_rule: List["Rule"] = [
|
||||||
"bad_lines": ["foo(1, 2, 3)", "foo(1, 2, 3)"],
|
"bad_lines": ["foo(1, 2, 3)", "foo(1, 2, 3)"],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
markdown_whitespace_rules: List["Rule"] = [
|
markdown_whitespace_rules: list["Rule"] = [
|
||||||
*(rule for rule in whitespace_rules if rule["pattern"] != r"[\t ]+$"),
|
*(rule for rule in whitespace_rules if rule["pattern"] != r"[\t ]+$"),
|
||||||
# Two spaces trailing a line with other content is okay--it's a Markdown line break.
|
# Two spaces trailing a line with other content is okay--it's a Markdown line break.
|
||||||
# This rule finds one space trailing a non-space, three or more trailing spaces, and
|
# This rule finds one space trailing a non-space, three or more trailing spaces, and
|
||||||
|
@ -508,7 +506,7 @@ css_rules = RuleList(
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
prose_style_rules: List["Rule"] = [
|
prose_style_rules: list["Rule"] = [
|
||||||
{
|
{
|
||||||
"pattern": r'^[\t ]*[^\n{].*?[^\n\/\#\-"]([jJ]avascript)', # exclude usage in hrefs/divs/custom-markdown
|
"pattern": r'^[\t ]*[^\n{].*?[^\n\/\#\-"]([jJ]avascript)', # exclude usage in hrefs/divs/custom-markdown
|
||||||
"exclude": {"docs/documentation/api.md", "templates/corporate/policies/privacy.md"},
|
"exclude": {"docs/documentation/api.md", "templates/corporate/policies/privacy.md"},
|
||||||
|
@ -531,7 +529,7 @@ prose_style_rules: List["Rule"] = [
|
||||||
},
|
},
|
||||||
*comma_whitespace_rule,
|
*comma_whitespace_rule,
|
||||||
]
|
]
|
||||||
html_rules: List["Rule"] = [
|
html_rules: list["Rule"] = [
|
||||||
*whitespace_rules,
|
*whitespace_rules,
|
||||||
*prose_style_rules,
|
*prose_style_rules,
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,6 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import digitalocean
|
import digitalocean
|
||||||
import zulip
|
import zulip
|
||||||
|
@ -63,7 +62,7 @@ def set_api_request_retry_limits(api_object: digitalocean.baseapi.BaseAPI) -> No
|
||||||
|
|
||||||
|
|
||||||
def create_droplet(
|
def create_droplet(
|
||||||
name: str, ssh_keys: List[str], image: str = "ubuntu-22-04-x64"
|
name: str, ssh_keys: list[str], image: str = "ubuntu-22-04-x64"
|
||||||
) -> digitalocean.Droplet:
|
) -> digitalocean.Droplet:
|
||||||
droplet = digitalocean.Droplet(
|
droplet = digitalocean.Droplet(
|
||||||
token=manager.token,
|
token=manager.token,
|
||||||
|
|
|
@ -4,10 +4,9 @@ import glob
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
def validate_order(order: List[int], length: int) -> None:
|
def validate_order(order: list[int], length: int) -> None:
|
||||||
if len(order) != length:
|
if len(order) != length:
|
||||||
print("Please enter the sequence of all the conflicting files at once")
|
print("Please enter the sequence of all the conflicting files at once")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
@ -18,8 +17,8 @@ def validate_order(order: List[int], length: int) -> None:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def renumber_migration(conflicts: List[str], order: List[int], last_correct_migration: str) -> None:
|
def renumber_migration(conflicts: list[str], order: list[int], last_correct_migration: str) -> None:
|
||||||
stack: List[str] = []
|
stack: list[str] = []
|
||||||
for i in order:
|
for i in order:
|
||||||
if conflicts[i - 1][0:4] not in stack:
|
if conflicts[i - 1][0:4] not in stack:
|
||||||
stack.append(conflicts[i - 1][0:4])
|
stack.append(conflicts[i - 1][0:4])
|
||||||
|
@ -38,7 +37,7 @@ def renumber_migration(conflicts: List[str], order: List[int], last_correct_migr
|
||||||
last_correct_migration = new_name.replace(".py", "")
|
last_correct_migration = new_name.replace(".py", "")
|
||||||
|
|
||||||
|
|
||||||
def resolve_conflicts(conflicts: List[str], files_list: List[str]) -> None:
|
def resolve_conflicts(conflicts: list[str], files_list: list[str]) -> None:
|
||||||
print("Conflicting migrations:")
|
print("Conflicting migrations:")
|
||||||
for i in range(len(conflicts)):
|
for i in range(len(conflicts)):
|
||||||
print(str(i + 1) + ". " + conflicts[i])
|
print(str(i + 1) + ". " + conflicts[i])
|
||||||
|
@ -56,8 +55,8 @@ def resolve_conflicts(conflicts: List[str], files_list: List[str]) -> None:
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
MIGRATIONS_TO_SKIP = {"0209", "0261", "0501"}
|
MIGRATIONS_TO_SKIP = {"0209", "0261", "0501"}
|
||||||
while True:
|
while True:
|
||||||
conflicts: List[str] = []
|
conflicts: list[str] = []
|
||||||
stack: List[str] = []
|
stack: list[str] = []
|
||||||
files_list = [os.path.basename(path) for path in glob.glob("zerver/migrations/????_*.py")]
|
files_list = [os.path.basename(path) for path in glob.glob("zerver/migrations/????_*.py")]
|
||||||
file_index = [file[0:4] for file in files_list]
|
file_index = [file[0:4] for file in files_list]
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
def exit(message: str) -> None:
|
def exit(message: str) -> None:
|
||||||
|
@ -11,12 +10,12 @@ def exit(message: str) -> None:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def run(command: List[str]) -> None:
|
def run(command: list[str]) -> None:
|
||||||
print(f"\n>>> {shlex.join(command)}")
|
print(f"\n>>> {shlex.join(command)}")
|
||||||
subprocess.check_call(command)
|
subprocess.check_call(command)
|
||||||
|
|
||||||
|
|
||||||
def check_output(command: List[str]) -> str:
|
def check_output(command: list[str]) -> str:
|
||||||
return subprocess.check_output(command, text=True)
|
return subprocess.check_output(command, text=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import pwd
|
||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
|
||||||
|
|
||||||
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
sys.path.insert(0, os.path.dirname(TOOLS_DIR))
|
sys.path.insert(0, os.path.dirname(TOOLS_DIR))
|
||||||
|
@ -81,7 +80,7 @@ if options.interface is None:
|
||||||
elif options.interface == "":
|
elif options.interface == "":
|
||||||
options.interface = None
|
options.interface = None
|
||||||
|
|
||||||
runserver_args: List[str] = []
|
runserver_args: list[str] = []
|
||||||
base_port = 9991
|
base_port = 9991
|
||||||
if options.test:
|
if options.test:
|
||||||
base_port = 9981
|
base_port = 9981
|
||||||
|
@ -139,7 +138,7 @@ with open(pid_file_path, "w+") as f:
|
||||||
f.write(str(os.getpgrp()) + "\n")
|
f.write(str(os.getpgrp()) + "\n")
|
||||||
|
|
||||||
|
|
||||||
def server_processes() -> List[List[str]]:
|
def server_processes() -> list[list[str]]:
|
||||||
main_cmds = [
|
main_cmds = [
|
||||||
[
|
[
|
||||||
"./manage.py",
|
"./manage.py",
|
||||||
|
@ -330,7 +329,7 @@ def https_log_filter(record: logging.LogRecord) -> bool:
|
||||||
logging.getLogger("aiohttp.server").addFilter(https_log_filter)
|
logging.getLogger("aiohttp.server").addFilter(https_log_filter)
|
||||||
|
|
||||||
runner: web.AppRunner
|
runner: web.AppRunner
|
||||||
children: List["subprocess.Popen[bytes]"] = []
|
children: list["subprocess.Popen[bytes]"] = []
|
||||||
|
|
||||||
|
|
||||||
async def serve() -> None:
|
async def serve() -> None:
|
||||||
|
|
|
@ -3,7 +3,6 @@ import argparse
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from zulint import lister
|
from zulint import lister
|
||||||
|
|
||||||
|
@ -72,7 +71,7 @@ if not python_files and not pyi_files:
|
||||||
print("There are no files to run mypy on.")
|
print("There are no files to run mypy on.")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
mypy_args: List[str] = []
|
mypy_args: list[str] = []
|
||||||
# --no-error-summary is a mypy flag that comes after all dmypy options
|
# --no-error-summary is a mypy flag that comes after all dmypy options
|
||||||
if args.use_daemon:
|
if args.use_daemon:
|
||||||
mypy_args += ["run", "--"]
|
mypy_args += ["run", "--"]
|
||||||
|
|
|
@ -6,7 +6,7 @@ import base64
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, Optional, Tuple
|
from typing import Any, Optional
|
||||||
from urllib.parse import parse_qsl, urlencode
|
from urllib.parse import parse_qsl, urlencode
|
||||||
|
|
||||||
SCREENSHOTS_DIR = os.path.abspath(os.path.dirname(__file__))
|
SCREENSHOTS_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
@ -94,7 +94,7 @@ def create_integration_stream(integration: Integration, bot: UserProfile) -> Non
|
||||||
bulk_add_subscriptions(realm, [stream], [bot, bot.bot_owner], acting_user=bot)
|
bulk_add_subscriptions(realm, [stream], [bot, bot.bot_owner], acting_user=bot)
|
||||||
|
|
||||||
|
|
||||||
def get_fixture_info(fixture_path: str) -> Tuple[Any, bool, str]:
|
def get_fixture_info(fixture_path: str) -> tuple[Any, bool, str]:
|
||||||
json_fixture = fixture_path.endswith(".json")
|
json_fixture = fixture_path.endswith(".json")
|
||||||
_, fixture_name = split_fixture_path(fixture_path)
|
_, fixture_name = split_fixture_path(fixture_path)
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ def get_integration(integration_name: str) -> Integration:
|
||||||
return integration
|
return integration
|
||||||
|
|
||||||
|
|
||||||
def get_requests_headers(integration_name: str, fixture_name: str) -> Dict[str, Any]:
|
def get_requests_headers(integration_name: str, fixture_name: str) -> dict[str, Any]:
|
||||||
headers = get_fixture_http_headers(integration_name, fixture_name)
|
headers = get_fixture_http_headers(integration_name, fixture_name)
|
||||||
|
|
||||||
def fix_name(header: str) -> str:
|
def fix_name(header: str) -> str:
|
||||||
|
@ -126,7 +126,7 @@ def get_requests_headers(integration_name: str, fixture_name: str) -> Dict[str,
|
||||||
return {fix_name(k): v for k, v in headers.items()}
|
return {fix_name(k): v for k, v in headers.items()}
|
||||||
|
|
||||||
|
|
||||||
def custom_headers(headers_json: str) -> Dict[str, str]:
|
def custom_headers(headers_json: str) -> dict[str, str]:
|
||||||
if not headers_json:
|
if not headers_json:
|
||||||
return {}
|
return {}
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -6,7 +6,7 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Dict, List, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
@ -59,7 +59,7 @@ realm.save()
|
||||||
|
|
||||||
DEFAULT_USER = get_user_by_delivery_email("iago@zulip.com", realm)
|
DEFAULT_USER = get_user_by_delivery_email("iago@zulip.com", realm)
|
||||||
NOTIFICATION_BOT = get_system_bot(settings.NOTIFICATION_BOT, realm.id)
|
NOTIFICATION_BOT = get_system_bot(settings.NOTIFICATION_BOT, realm.id)
|
||||||
message_thread_ids: List[int] = []
|
message_thread_ids: list[int] = []
|
||||||
|
|
||||||
USER_AVATARS_MAP = {
|
USER_AVATARS_MAP = {
|
||||||
"Ariella Drake": "tools/screenshots/user_avatars/AriellaDrake.png",
|
"Ariella Drake": "tools/screenshots/user_avatars/AriellaDrake.png",
|
||||||
|
@ -82,8 +82,8 @@ class MessageThread(BaseModel):
|
||||||
content: str
|
content: str
|
||||||
starred: bool
|
starred: bool
|
||||||
edited: bool
|
edited: bool
|
||||||
reactions: Dict[str, List[str]]
|
reactions: dict[str, list[str]]
|
||||||
date: Dict[str, int]
|
date: dict[str, int]
|
||||||
|
|
||||||
|
|
||||||
def create_user(full_name: str, avatar_filename: Optional[str]) -> None:
|
def create_user(full_name: str, avatar_filename: Optional[str]) -> None:
|
||||||
|
@ -104,7 +104,7 @@ def set_avatar(user: UserProfile, filename: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
def create_and_subscribe_stream(
|
def create_and_subscribe_stream(
|
||||||
stream_name: str, users: List[str], color: Optional[str] = None, invite_only: bool = False
|
stream_name: str, users: list[str], color: Optional[str] = None, invite_only: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
stream = ensure_stream(realm, stream_name, invite_only=invite_only, acting_user=DEFAULT_USER)
|
stream = ensure_stream(realm, stream_name, invite_only=invite_only, acting_user=DEFAULT_USER)
|
||||||
bulk_add_subscriptions(
|
bulk_add_subscriptions(
|
||||||
|
@ -123,22 +123,22 @@ def create_and_subscribe_stream(
|
||||||
|
|
||||||
|
|
||||||
def send_stream_messages(
|
def send_stream_messages(
|
||||||
stream_name: str, topic: str, staged_messages_data: List[MessageThread]
|
stream_name: str, topic: str, staged_messages_data: list[MessageThread]
|
||||||
) -> List[int]:
|
) -> list[int]:
|
||||||
staged_messages = [dict(staged_message) for staged_message in staged_messages_data]
|
staged_messages = [dict(staged_message) for staged_message in staged_messages_data]
|
||||||
stream = ensure_stream(realm, stream_name, acting_user=DEFAULT_USER)
|
stream = ensure_stream(realm, stream_name, acting_user=DEFAULT_USER)
|
||||||
subscribers_query = get_active_subscriptions_for_stream_id(
|
subscribers_query = get_active_subscriptions_for_stream_id(
|
||||||
stream.id, include_deactivated_users=False
|
stream.id, include_deactivated_users=False
|
||||||
).values_list("user_profile", flat=True)
|
).values_list("user_profile", flat=True)
|
||||||
|
|
||||||
subscribers: Dict[str, UserProfile] = {}
|
subscribers: dict[str, UserProfile] = {}
|
||||||
for subscriber_id in subscribers_query:
|
for subscriber_id in subscribers_query:
|
||||||
subscriber = UserProfile.objects.get(realm=realm, id=subscriber_id)
|
subscriber = UserProfile.objects.get(realm=realm, id=subscriber_id)
|
||||||
subscribers[subscriber.full_name] = subscriber
|
subscribers[subscriber.full_name] = subscriber
|
||||||
|
|
||||||
subscribers["Notification Bot"] = NOTIFICATION_BOT
|
subscribers["Notification Bot"] = NOTIFICATION_BOT
|
||||||
|
|
||||||
messages: List[Optional[SendMessageRequest]] = []
|
messages: list[Optional[SendMessageRequest]] = []
|
||||||
|
|
||||||
for message in staged_messages:
|
for message in staged_messages:
|
||||||
date_sent = message["date"]
|
date_sent = message["date"]
|
||||||
|
@ -186,7 +186,7 @@ def send_stream_messages(
|
||||||
return message_ids
|
return message_ids
|
||||||
|
|
||||||
|
|
||||||
def add_message_reactions(message_id: int, emoji: str, users: List[UserProfile]) -> None:
|
def add_message_reactions(message_id: int, emoji: str, users: list[UserProfile]) -> None:
|
||||||
preview_message = access_message(user_profile=DEFAULT_USER, message_id=message_id)
|
preview_message = access_message(user_profile=DEFAULT_USER, message_id=message_id)
|
||||||
emoji_data = get_emoji_data(realm.id, emoji)
|
emoji_data = get_emoji_data(realm.id, emoji)
|
||||||
for user in users:
|
for user in users:
|
||||||
|
@ -195,7 +195,7 @@ def add_message_reactions(message_id: int, emoji: str, users: List[UserProfile])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_user_group(group_name: str, members: List[str]) -> None:
|
def create_user_group(group_name: str, members: list[str]) -> None:
|
||||||
member_profiles = [
|
member_profiles = [
|
||||||
UserProfile.objects.get(realm=realm, full_name=member_name) for member_name in members
|
UserProfile.objects.get(realm=realm, full_name=member_name) for member_name in members
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, Iterator, List, Optional, Sequence
|
from typing import Any, Iterator, Optional, Sequence
|
||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
|
|
||||||
|
@ -127,13 +127,13 @@ def percent(f: float) -> str:
|
||||||
return f"{f * 100:0.3f}%"
|
return f"{f * 100:0.3f}%"
|
||||||
|
|
||||||
|
|
||||||
def get_square_size(emoji_data: Sequence[Dict[str, Any]]) -> int:
|
def get_square_size(emoji_data: Sequence[dict[str, Any]]) -> int:
|
||||||
"""
|
"""
|
||||||
Spritesheets are usually NxN squares, and we have to
|
Spritesheets are usually NxN squares, and we have to
|
||||||
infer N from the sheet_x/sheet_y values of emojis.
|
infer N from the sheet_x/sheet_y values of emojis.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_offsets(emoji_data: Sequence[Dict[str, Any]]) -> Iterator[int]:
|
def get_offsets(emoji_data: Sequence[dict[str, Any]]) -> Iterator[int]:
|
||||||
for emoji_dict in emoji_data:
|
for emoji_dict in emoji_data:
|
||||||
yield emoji_dict["sheet_x"]
|
yield emoji_dict["sheet_x"]
|
||||||
yield emoji_dict["sheet_y"]
|
yield emoji_dict["sheet_y"]
|
||||||
|
@ -148,10 +148,10 @@ def get_square_size(emoji_data: Sequence[Dict[str, Any]]) -> int:
|
||||||
|
|
||||||
def generate_sprite_css_files(
|
def generate_sprite_css_files(
|
||||||
cache_path: str,
|
cache_path: str,
|
||||||
emoji_data: List[Dict[str, Any]],
|
emoji_data: list[dict[str, Any]],
|
||||||
emojiset: str,
|
emojiset: str,
|
||||||
alt_name: str,
|
alt_name: str,
|
||||||
fallback_emoji_data: Sequence[Dict[str, Any]],
|
fallback_emoji_data: Sequence[dict[str, Any]],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Spritesheets are usually NxN squares.
|
Spritesheets are usually NxN squares.
|
||||||
|
@ -272,9 +272,9 @@ def generate_sprite_css_files(
|
||||||
f.write(extra_emoji_positions)
|
f.write(extra_emoji_positions)
|
||||||
|
|
||||||
|
|
||||||
def setup_emoji_farms(cache_path: str, emoji_data: List[Dict[str, Any]]) -> None:
|
def setup_emoji_farms(cache_path: str, emoji_data: list[dict[str, Any]]) -> None:
|
||||||
def ensure_emoji_image(
|
def ensure_emoji_image(
|
||||||
emoji_dict: Dict[str, Any], src_emoji_farm: str, target_emoji_farm: str
|
emoji_dict: dict[str, Any], src_emoji_farm: str, target_emoji_farm: str
|
||||||
) -> None:
|
) -> None:
|
||||||
# We use individual images from emoji farm for rendering emojis
|
# We use individual images from emoji farm for rendering emojis
|
||||||
# in notification messages. We have a custom emoji formatter in
|
# in notification messages. We have a custom emoji formatter in
|
||||||
|
@ -289,9 +289,9 @@ def setup_emoji_farms(cache_path: str, emoji_data: List[Dict[str, Any]]) -> None
|
||||||
|
|
||||||
def setup_emoji_farm(
|
def setup_emoji_farm(
|
||||||
emojiset: str,
|
emojiset: str,
|
||||||
emoji_data: List[Dict[str, Any]],
|
emoji_data: list[dict[str, Any]],
|
||||||
alt_name: Optional[str] = None,
|
alt_name: Optional[str] = None,
|
||||||
fallback_emoji_data: Sequence[Dict[str, Any]] = [],
|
fallback_emoji_data: Sequence[dict[str, Any]] = [],
|
||||||
) -> None:
|
) -> None:
|
||||||
# `alt_name` is an optional parameter that we use to avoid duplicating below
|
# `alt_name` is an optional parameter that we use to avoid duplicating below
|
||||||
# code. It is only used while setting up google-blob emoji set as it is just
|
# code. It is only used while setting up google-blob emoji set as it is just
|
||||||
|
@ -342,7 +342,7 @@ def setup_emoji_farms(cache_path: str, emoji_data: List[Dict[str, Any]]) -> None
|
||||||
|
|
||||||
|
|
||||||
def setup_old_emoji_farm(
|
def setup_old_emoji_farm(
|
||||||
cache_path: str, emoji_map: Dict[str, str], emoji_data: List[Dict[str, Any]]
|
cache_path: str, emoji_map: dict[str, str], emoji_data: list[dict[str, Any]]
|
||||||
) -> None:
|
) -> None:
|
||||||
# Code for setting up old emoji farm.
|
# Code for setting up old emoji farm.
|
||||||
os.chdir(cache_path)
|
os.chdir(cache_path)
|
||||||
|
@ -374,7 +374,7 @@ def setup_old_emoji_farm(
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def generate_map_files(cache_path: str, emoji_catalog: Dict[str, List[str]]) -> None:
|
def generate_map_files(cache_path: str, emoji_catalog: dict[str, list[str]]) -> None:
|
||||||
# This function generates the main data files about emoji that are
|
# This function generates the main data files about emoji that are
|
||||||
# consumed by the web app, mobile apps, Markdown processor, etc.
|
# consumed by the web app, mobile apps, Markdown processor, etc.
|
||||||
names = emoji_names_for_picker(EMOJI_NAME_MAPS)
|
names = emoji_names_for_picker(EMOJI_NAME_MAPS)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
CUSTOM_EMOJI_NAME_MAPS: Dict[str, Dict[str, Any]] = {
|
CUSTOM_EMOJI_NAME_MAPS: dict[str, dict[str, Any]] = {
|
||||||
# seems like best emoji for happy
|
# seems like best emoji for happy
|
||||||
"1f600": {"canonical_name": "grinning", "aliases": ["happy"]},
|
"1f600": {"canonical_name": "grinning", "aliases": ["happy"]},
|
||||||
"1f603": {"canonical_name": "smiley", "aliases": []},
|
"1f603": {"canonical_name": "smiley", "aliases": []},
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
# Generated with `generate_emoji_names`.
|
# Generated with `generate_emoji_names`.
|
||||||
|
|
||||||
EMOJI_NAME_MAPS: Dict[str, Dict[str, Any]] = {
|
EMOJI_NAME_MAPS: dict[str, dict[str, Any]] = {
|
||||||
"0023-20e3": {"canonical_name": "hash", "aliases": []},
|
"0023-20e3": {"canonical_name": "hash", "aliases": []},
|
||||||
"002a-20e3": {"canonical_name": "asterisk", "aliases": []},
|
"002a-20e3": {"canonical_name": "asterisk", "aliases": []},
|
||||||
"0030-20e3": {"canonical_name": "zero", "aliases": []},
|
"0030-20e3": {"canonical_name": "zero", "aliases": []},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# This file contains various helper functions used by `build_emoji` tool.
|
# This file contains various helper functions used by `build_emoji` tool.
|
||||||
# See docs/subsystems/emoji.md for details on how this system works.
|
# See docs/subsystems/emoji.md for details on how this system works.
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
from zerver.lib.emoji_utils import emoji_to_hex_codepoint, hex_codepoint_to_emoji, unqualify_emoji
|
from zerver.lib.emoji_utils import emoji_to_hex_codepoint, hex_codepoint_to_emoji, unqualify_emoji
|
||||||
|
|
||||||
|
@ -50,8 +50,8 @@ EMOTICON_CONVERSIONS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def emoji_names_for_picker(emoji_name_maps: Dict[str, Dict[str, Any]]) -> List[str]:
|
def emoji_names_for_picker(emoji_name_maps: dict[str, dict[str, Any]]) -> list[str]:
|
||||||
emoji_names: List[str] = []
|
emoji_names: list[str] = []
|
||||||
for name_info in emoji_name_maps.values():
|
for name_info in emoji_name_maps.values():
|
||||||
emoji_names.append(name_info["canonical_name"])
|
emoji_names.append(name_info["canonical_name"])
|
||||||
emoji_names.extend(name_info["aliases"])
|
emoji_names.extend(name_info["aliases"])
|
||||||
|
@ -59,7 +59,7 @@ def emoji_names_for_picker(emoji_name_maps: Dict[str, Dict[str, Any]]) -> List[s
|
||||||
return sorted(emoji_names)
|
return sorted(emoji_names)
|
||||||
|
|
||||||
|
|
||||||
def get_emoji_code(emoji_dict: Dict[str, Any]) -> str:
|
def get_emoji_code(emoji_dict: dict[str, Any]) -> str:
|
||||||
# There is a `non_qualified` field on `emoji_dict` but it's
|
# There is a `non_qualified` field on `emoji_dict` but it's
|
||||||
# inconsistently present, so we'll always use the unqualified
|
# inconsistently present, so we'll always use the unqualified
|
||||||
# emoji by unqualifying it ourselves. This gives us more consistent
|
# emoji by unqualifying it ourselves. This gives us more consistent
|
||||||
|
@ -72,10 +72,10 @@ def get_emoji_code(emoji_dict: Dict[str, Any]) -> str:
|
||||||
# codepoints are sorted according to the `sort_order` as defined in
|
# codepoints are sorted according to the `sort_order` as defined in
|
||||||
# `emoji_data`.
|
# `emoji_data`.
|
||||||
def generate_emoji_catalog(
|
def generate_emoji_catalog(
|
||||||
emoji_data: List[Dict[str, Any]], emoji_name_maps: Dict[str, Dict[str, Any]]
|
emoji_data: list[dict[str, Any]], emoji_name_maps: dict[str, dict[str, Any]]
|
||||||
) -> Dict[str, List[str]]:
|
) -> dict[str, list[str]]:
|
||||||
sort_order: Dict[str, int] = {}
|
sort_order: dict[str, int] = {}
|
||||||
emoji_catalog: Dict[str, List[str]] = defaultdict(list)
|
emoji_catalog: dict[str, list[str]] = defaultdict(list)
|
||||||
|
|
||||||
for emoji_dict in emoji_data:
|
for emoji_dict in emoji_data:
|
||||||
emoji_code = get_emoji_code(emoji_dict)
|
emoji_code = get_emoji_code(emoji_dict)
|
||||||
|
@ -93,8 +93,8 @@ def generate_emoji_catalog(
|
||||||
return dict(emoji_catalog)
|
return dict(emoji_catalog)
|
||||||
|
|
||||||
|
|
||||||
def generate_codepoint_to_name_map(emoji_name_maps: Dict[str, Dict[str, Any]]) -> Dict[str, str]:
|
def generate_codepoint_to_name_map(emoji_name_maps: dict[str, dict[str, Any]]) -> dict[str, str]:
|
||||||
codepoint_to_name: Dict[str, str] = {}
|
codepoint_to_name: dict[str, str] = {}
|
||||||
for emoji_code, name_info in emoji_name_maps.items():
|
for emoji_code, name_info in emoji_name_maps.items():
|
||||||
codepoint_to_name[emoji_code] = name_info["canonical_name"]
|
codepoint_to_name[emoji_code] = name_info["canonical_name"]
|
||||||
return codepoint_to_name
|
return codepoint_to_name
|
||||||
|
@ -102,13 +102,13 @@ def generate_codepoint_to_name_map(emoji_name_maps: Dict[str, Dict[str, Any]]) -
|
||||||
|
|
||||||
# We support Google Modern, and fall back to Google Modern when emoji
|
# We support Google Modern, and fall back to Google Modern when emoji
|
||||||
# aren't supported by the other styles we use.
|
# aren't supported by the other styles we use.
|
||||||
def emoji_is_supported(emoji_dict: Dict[str, Any]) -> bool:
|
def emoji_is_supported(emoji_dict: dict[str, Any]) -> bool:
|
||||||
return emoji_dict["has_img_google"]
|
return emoji_dict["has_img_google"]
|
||||||
|
|
||||||
|
|
||||||
def generate_codepoint_to_names_map(
|
def generate_codepoint_to_names_map(
|
||||||
emoji_name_maps: Dict[str, Dict[str, Any]],
|
emoji_name_maps: dict[str, dict[str, Any]],
|
||||||
) -> Dict[str, List[str]]:
|
) -> dict[str, list[str]]:
|
||||||
# The first element of the names list is always the canonical name.
|
# The first element of the names list is always the canonical name.
|
||||||
return {
|
return {
|
||||||
emoji_code: [name_info["canonical_name"], *name_info["aliases"]]
|
emoji_code: [name_info["canonical_name"], *name_info["aliases"]]
|
||||||
|
@ -116,7 +116,7 @@ def generate_codepoint_to_names_map(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def generate_name_to_codepoint_map(emoji_name_maps: Dict[str, Dict[str, Any]]) -> Dict[str, str]:
|
def generate_name_to_codepoint_map(emoji_name_maps: dict[str, dict[str, Any]]) -> dict[str, str]:
|
||||||
name_to_codepoint = {}
|
name_to_codepoint = {}
|
||||||
for emoji_code, name_info in emoji_name_maps.items():
|
for emoji_code, name_info in emoji_name_maps.items():
|
||||||
canonical_name = name_info["canonical_name"]
|
canonical_name = name_info["canonical_name"]
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
# codepoint.
|
# codepoint.
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
|
|
||||||
|
@ -124,15 +124,15 @@ SORTED_CATEGORIES = [
|
||||||
"Skin Tones",
|
"Skin Tones",
|
||||||
]
|
]
|
||||||
|
|
||||||
emoji_code_to_zulip_names: Dict[str, str] = {}
|
emoji_code_to_zulip_names: dict[str, str] = {}
|
||||||
emoji_code_to_iamcal_names: Dict[str, str] = {}
|
emoji_code_to_iamcal_names: dict[str, str] = {}
|
||||||
emoji_code_to_gemoji_names: Dict[str, str] = {}
|
emoji_code_to_gemoji_names: dict[str, str] = {}
|
||||||
emoji_collection: Dict[str, List[Dict[str, Any]]] = {category: [] for category in SORTED_CATEGORIES}
|
emoji_collection: dict[str, list[dict[str, Any]]] = {category: [] for category in SORTED_CATEGORIES}
|
||||||
|
|
||||||
|
|
||||||
def generate_emoji_code_to_emoji_names_maps() -> None:
|
def generate_emoji_code_to_emoji_names_maps() -> None:
|
||||||
# Prepare gemoji names map.
|
# Prepare gemoji names map.
|
||||||
reverse_unified_reactions_map: Dict[str, List[str]] = {}
|
reverse_unified_reactions_map: dict[str, list[str]] = {}
|
||||||
for name in UNIFIED_REACTIONS_MAP:
|
for name in UNIFIED_REACTIONS_MAP:
|
||||||
emoji_code = UNIFIED_REACTIONS_MAP[name]
|
emoji_code = UNIFIED_REACTIONS_MAP[name]
|
||||||
if emoji_code in reverse_unified_reactions_map:
|
if emoji_code in reverse_unified_reactions_map:
|
||||||
|
|
|
@ -3,7 +3,6 @@ import glob
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
|
||||||
|
|
||||||
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
if ZULIP_PATH not in sys.path:
|
if ZULIP_PATH not in sys.path:
|
||||||
|
@ -25,7 +24,7 @@ def generate_zulip_bots_static_files() -> None:
|
||||||
|
|
||||||
package_bots_dir = get_bots_directory_path()
|
package_bots_dir = get_bots_directory_path()
|
||||||
|
|
||||||
def copy_bots_data(bot_names: List[str]) -> None:
|
def copy_bots_data(bot_names: list[str]) -> None:
|
||||||
for name in bot_names:
|
for name in bot_names:
|
||||||
src_dir = os.path.join(package_bots_dir, name)
|
src_dir = os.path.join(package_bots_dir, name)
|
||||||
dst_dir = os.path.join(bots_dir, name)
|
dst_dir = os.path.join(bots_dir, name)
|
||||||
|
|
|
@ -13,7 +13,7 @@ os.environ["DJANGO_SETTINGS_MODULE"] = "zproject.settings"
|
||||||
import argparse
|
import argparse
|
||||||
import secrets
|
import secrets
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import Iterator, List, Tuple, TypedDict
|
from typing import Iterator, TypedDict
|
||||||
|
|
||||||
import boto3.session
|
import boto3.session
|
||||||
import orjson
|
import orjson
|
||||||
|
@ -86,7 +86,7 @@ def get_ses_arn(session: boto3.session.Session, args: argparse.Namespace) -> str
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def our_sqs_queue(session: boto3.session.Session, ses_topic_arn: str) -> Iterator[Tuple[str, str]]:
|
def our_sqs_queue(session: boto3.session.Session, ses_topic_arn: str) -> Iterator[tuple[str, str]]:
|
||||||
(_, _, _, region, account_id, topic_name) = ses_topic_arn.split(":")
|
(_, _, _, region, account_id, topic_name) = ses_topic_arn.split(":")
|
||||||
|
|
||||||
sqs: SQSClient = session.client("sqs")
|
sqs: SQSClient = session.client("sqs")
|
||||||
|
@ -153,7 +153,7 @@ def print_messages(session: boto3.session.Session, queue_url: str) -> None:
|
||||||
MessageAttributeNames=["All"],
|
MessageAttributeNames=["All"],
|
||||||
)
|
)
|
||||||
messages = resp.get("Messages", [])
|
messages = resp.get("Messages", [])
|
||||||
delete_list: List[DeleteMessageBatchRequestEntryTypeDef] = []
|
delete_list: list[DeleteMessageBatchRequestEntryTypeDef] = []
|
||||||
for m in messages:
|
for m in messages:
|
||||||
body = orjson.loads(m["Body"])
|
body = orjson.loads(m["Body"])
|
||||||
body_message = orjson.loads(body["Message"])
|
body_message = orjson.loads(body["Message"])
|
||||||
|
|
|
@ -8,7 +8,7 @@ import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import TYPE_CHECKING, Iterator, List, Type, cast
|
from typing import TYPE_CHECKING, Iterator, cast
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -154,7 +154,7 @@ enforce_fully_covered = sorted(
|
||||||
FAILED_TEST_PATH = "var/last_test_failure.json"
|
FAILED_TEST_PATH = "var/last_test_failure.json"
|
||||||
|
|
||||||
|
|
||||||
def get_failed_tests() -> List[str]:
|
def get_failed_tests() -> list[str]:
|
||||||
try:
|
try:
|
||||||
with open(FAILED_TEST_PATH, "rb") as f:
|
with open(FAILED_TEST_PATH, "rb") as f:
|
||||||
return orjson.loads(f.read())
|
return orjson.loads(f.read())
|
||||||
|
@ -418,7 +418,7 @@ def main() -> None:
|
||||||
|
|
||||||
# isinstance check cannot be used with types. This can potentially improved by supporting
|
# isinstance check cannot be used with types. This can potentially improved by supporting
|
||||||
# dynamic resolution of the test runner type with the django-stubs mypy plugin.
|
# dynamic resolution of the test runner type with the django-stubs mypy plugin.
|
||||||
TestRunner = cast("Type[Runner]", get_runner(settings))
|
TestRunner = cast("type[Runner]", get_runner(settings))
|
||||||
|
|
||||||
if options.processes:
|
if options.processes:
|
||||||
parallel = options.processes
|
parallel = options.processes
|
||||||
|
|
|
@ -5,7 +5,7 @@ import os
|
||||||
import pwd
|
import pwd
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, List, Set
|
from typing import Any
|
||||||
|
|
||||||
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
sys.path.insert(0, os.path.dirname(TOOLS_DIR))
|
sys.path.insert(0, os.path.dirname(TOOLS_DIR))
|
||||||
|
@ -34,7 +34,7 @@ USAGE = """
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def make_set(files: List[str]) -> Set[str]:
|
def make_set(files: list[str]) -> set[str]:
|
||||||
for i in range(1, len(files)):
|
for i in range(1, len(files)):
|
||||||
if files[i - 1] > files[i]:
|
if files[i - 1] > files[i]:
|
||||||
raise Exception(f"Please move {files[i]} so that names are sorted.")
|
raise Exception(f"Please move {files[i]} so that names are sorted.")
|
||||||
|
@ -376,7 +376,7 @@ def clean_file(orig_fn: str) -> str:
|
||||||
return fn
|
return fn
|
||||||
|
|
||||||
|
|
||||||
def clean_files(fns: List[str]) -> List[str]:
|
def clean_files(fns: list[str]) -> list[str]:
|
||||||
cleaned_files = [clean_file(fn) for fn in fns]
|
cleaned_files = [clean_file(fn) for fn in fns]
|
||||||
return cleaned_files
|
return cleaned_files
|
||||||
|
|
||||||
|
@ -442,7 +442,7 @@ def run_tests_via_node_js() -> int:
|
||||||
|
|
||||||
|
|
||||||
def check_line_coverage(
|
def check_line_coverage(
|
||||||
fn: str, line_coverage: Dict[Any, Any], line_mapping: Dict[Any, Any], log: bool = True
|
fn: str, line_coverage: dict[Any, Any], line_mapping: dict[Any, Any], log: bool = True
|
||||||
) -> bool:
|
) -> bool:
|
||||||
missing_lines = []
|
missing_lines = []
|
||||||
for line in line_coverage:
|
for line in line_coverage:
|
||||||
|
|
|
@ -39,7 +39,7 @@ from tools.lib import sanity_check
|
||||||
|
|
||||||
sanity_check.check_venv(__file__)
|
sanity_check.check_venv(__file__)
|
||||||
|
|
||||||
from typing import Iterable, Tuple
|
from typing import Iterable
|
||||||
|
|
||||||
from tools.lib.test_script import (
|
from tools.lib.test_script import (
|
||||||
add_provision_check_override_param,
|
add_provision_check_override_param,
|
||||||
|
@ -94,7 +94,7 @@ def run_tests(files: Iterable[str], external_host: str, loop: int = 1) -> None:
|
||||||
test_files = find_js_test_files(test_dir, files)
|
test_files = find_js_test_files(test_dir, files)
|
||||||
total_tests = len(test_files)
|
total_tests = len(test_files)
|
||||||
|
|
||||||
def run_tests(test_number: int = 0) -> Tuple[int, int]:
|
def run_tests(test_number: int = 0) -> tuple[int, int]:
|
||||||
current_test_num = test_number
|
current_test_num = test_number
|
||||||
for test_file in test_files[test_number:]:
|
for test_file in test_files[test_number:]:
|
||||||
return_code = run_single_test(test_file, current_test_num + 1, total_tests)
|
return_code = run_single_test(test_file, current_test_num + 1, total_tests)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
|
|
||||||
|
@ -74,13 +73,13 @@ def maybe_set_up_cache() -> None:
|
||||||
fp.write(orjson.dumps([]))
|
fp.write(orjson.dumps([]))
|
||||||
|
|
||||||
|
|
||||||
def load_cache() -> List[str]:
|
def load_cache() -> list[str]:
|
||||||
with open(CACHE_FILE, "rb") as fp:
|
with open(CACHE_FILE, "rb") as fp:
|
||||||
hash_list = orjson.loads(fp.read())
|
hash_list = orjson.loads(fp.read())
|
||||||
return hash_list
|
return hash_list
|
||||||
|
|
||||||
|
|
||||||
def update_cache(hash_list: List[str]) -> None:
|
def update_cache(hash_list: list[str]) -> None:
|
||||||
# We store last 100 hash entries. Aggressive caching is
|
# We store last 100 hash entries. Aggressive caching is
|
||||||
# not a problem as it is cheap to do.
|
# not a problem as it is cheap to do.
|
||||||
if len(hash_list) > 100:
|
if len(hash_list) > 100:
|
||||||
|
|
|
@ -5,7 +5,6 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||||
|
|
||||||
|
@ -16,7 +15,7 @@ sanity_check.check_venv(__file__)
|
||||||
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
def start_server(logfile_name: str) -> Tuple[bool, str]:
|
def start_server(logfile_name: str) -> tuple[bool, str]:
|
||||||
failure = True
|
failure = True
|
||||||
key = "Quit the server with CONTROL-C."
|
key = "Quit the server with CONTROL-C."
|
||||||
datalog = []
|
datalog = []
|
||||||
|
|
|
@ -5,7 +5,6 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
bot_commits = 0
|
bot_commits = 0
|
||||||
|
|
||||||
|
@ -13,7 +12,7 @@ ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
os.chdir(ZULIP_PATH)
|
os.chdir(ZULIP_PATH)
|
||||||
|
|
||||||
|
|
||||||
def add_log(committer_dict: Dict[str, int], input: List[str]) -> None:
|
def add_log(committer_dict: dict[str, int], input: list[str]) -> None:
|
||||||
for dataset in input:
|
for dataset in input:
|
||||||
committer_name = dataset.split("\t")[1]
|
committer_name = dataset.split("\t")[1]
|
||||||
commit_count = int(dataset.split("\t")[0])
|
commit_count = int(dataset.split("\t")[0])
|
||||||
|
@ -27,7 +26,7 @@ def add_log(committer_dict: Dict[str, int], input: List[str]) -> None:
|
||||||
committer_dict[committer_name] += commit_count
|
committer_dict[committer_name] += commit_count
|
||||||
|
|
||||||
|
|
||||||
def retrieve_log(repo: str, revisions: str) -> List[str]:
|
def retrieve_log(repo: str, revisions: str) -> list[str]:
|
||||||
return subprocess.check_output(
|
return subprocess.check_output(
|
||||||
["git", "shortlog", "-s", revisions],
|
["git", "shortlog", "-s", revisions],
|
||||||
cwd=find_path(repo),
|
cwd=find_path(repo),
|
||||||
|
@ -41,7 +40,7 @@ def find_path(repository: str) -> str:
|
||||||
|
|
||||||
def process_repo(
|
def process_repo(
|
||||||
*,
|
*,
|
||||||
out_dict: Dict[str, int],
|
out_dict: dict[str, int],
|
||||||
repo_short: str,
|
repo_short: str,
|
||||||
repo_full: str,
|
repo_full: str,
|
||||||
lower_version: str,
|
lower_version: str,
|
||||||
|
@ -164,8 +163,8 @@ print(
|
||||||
f"Commit range {lower_zulip_version}..{upper_zulip_version} corresponds to {lower_time} to {upper_time}"
|
f"Commit range {lower_zulip_version}..{upper_zulip_version} corresponds to {lower_time} to {upper_time}"
|
||||||
)
|
)
|
||||||
|
|
||||||
repository_dict: Dict[str, int] = defaultdict(int)
|
repository_dict: dict[str, int] = defaultdict(int)
|
||||||
out_dict: Dict[str, int] = defaultdict(int)
|
out_dict: dict[str, int] = defaultdict(int)
|
||||||
subprocess.check_call(["git", "fetch"], cwd=find_path("zulip"))
|
subprocess.check_call(["git", "fetch"], cwd=find_path("zulip"))
|
||||||
process_repo(
|
process_repo(
|
||||||
out_dict=out_dict,
|
out_dict=out_dict,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import IO, Dict, List, cast
|
from typing import IO, cast
|
||||||
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||||
from scripts.lib.setup_path import setup_path
|
from scripts.lib.setup_path import setup_path
|
||||||
|
@ -41,7 +41,7 @@ session = boto3.session.Session()
|
||||||
client: S3Client = session.client("s3")
|
client: S3Client = session.client("s3")
|
||||||
bucket = session.resource("s3", region_name="us-east-1").Bucket("zulip-download")
|
bucket = session.resource("s3", region_name="us-east-1").Bucket("zulip-download")
|
||||||
|
|
||||||
file_hashes: Dict[str, str] = {}
|
file_hashes: dict[str, str] = {}
|
||||||
with open(args.filename, "rb") as new_file:
|
with open(args.filename, "rb") as new_file:
|
||||||
print(f"Hashing {new_basename}..")
|
print(f"Hashing {new_basename}..")
|
||||||
file_hashes[new_basename] = sha256_contents(new_file)
|
file_hashes[new_basename] = sha256_contents(new_file)
|
||||||
|
@ -75,7 +75,7 @@ for obj_summary in bucket.objects.filter(Prefix="server/zulip-server-"):
|
||||||
|
|
||||||
file_hashes[filename] = metadata["sha256sum"]
|
file_hashes[filename] = metadata["sha256sum"]
|
||||||
|
|
||||||
ordered_filenames = cast(List[str], natsorted(file_hashes.keys(), reverse=True))
|
ordered_filenames = cast(list[str], natsorted(file_hashes.keys(), reverse=True))
|
||||||
assert ordered_filenames[0] == "zulip-server-latest.tar.gz"
|
assert ordered_filenames[0] == "zulip-server-latest.tar.gz"
|
||||||
|
|
||||||
print(f"Uploading {new_basename}..")
|
print(f"Uploading {new_basename}..")
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
@ -136,7 +136,7 @@ def set_realm_permissions_based_on_org_type(realm: Realm) -> None:
|
||||||
|
|
||||||
@transaction.atomic(savepoint=False)
|
@transaction.atomic(savepoint=False)
|
||||||
def set_default_for_realm_permission_group_settings(
|
def set_default_for_realm_permission_group_settings(
|
||||||
realm: Realm, group_settings_defaults_for_org_types: Optional[Dict[str, Dict[int, str]]] = None
|
realm: Realm, group_settings_defaults_for_org_types: Optional[dict[str, dict[int, str]]] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
system_groups_dict = get_role_based_system_groups_dict(realm)
|
system_groups_dict = get_role_based_system_groups_dict(realm)
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ def do_create_realm(
|
||||||
logging.info("Server not yet initialized. Creating the internal realm first.")
|
logging.info("Server not yet initialized. Creating the internal realm first.")
|
||||||
create_internal_realm()
|
create_internal_realm()
|
||||||
|
|
||||||
kwargs: Dict[str, Any] = {}
|
kwargs: dict[str, Any] = {}
|
||||||
if emails_restricted_to_domains is not None:
|
if emails_restricted_to_domains is not None:
|
||||||
kwargs["emails_restricted_to_domains"] = emails_restricted_to_domains
|
kwargs["emails_restricted_to_domains"] = emails_restricted_to_domains
|
||||||
if description is not None:
|
if description is not None:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Any, Dict, Iterable, List, Optional, Sequence, Set
|
from typing import Any, Iterable, Optional, Sequence
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
@ -133,7 +133,7 @@ def set_up_streams_for_new_human_user(
|
||||||
realm = user_profile.realm
|
realm = user_profile.realm
|
||||||
|
|
||||||
if prereg_user is not None:
|
if prereg_user is not None:
|
||||||
streams: List[Stream] = list(prereg_user.streams.all())
|
streams: list[Stream] = list(prereg_user.streams.all())
|
||||||
acting_user: Optional[UserProfile] = prereg_user.referred_by
|
acting_user: Optional[UserProfile] = prereg_user.referred_by
|
||||||
|
|
||||||
# A PregistrationUser should not be used for another UserProfile
|
# A PregistrationUser should not be used for another UserProfile
|
||||||
|
@ -351,10 +351,10 @@ def process_new_human_user(
|
||||||
OnboardingStep.objects.create(user=user_profile, onboarding_step="visibility_policy_banner")
|
OnboardingStep.objects.create(user=user_profile, onboarding_step="visibility_policy_banner")
|
||||||
|
|
||||||
|
|
||||||
def notify_created_user(user_profile: UserProfile, notify_user_ids: List[int]) -> None:
|
def notify_created_user(user_profile: UserProfile, notify_user_ids: list[int]) -> None:
|
||||||
user_row = user_profile_to_user_row(user_profile)
|
user_row = user_profile_to_user_row(user_profile)
|
||||||
|
|
||||||
format_user_row_kwargs: Dict[str, Any] = {
|
format_user_row_kwargs: dict[str, Any] = {
|
||||||
"realm_id": user_profile.realm_id,
|
"realm_id": user_profile.realm_id,
|
||||||
"row": user_row,
|
"row": user_row,
|
||||||
# Since we don't know what the client
|
# Since we don't know what the client
|
||||||
|
@ -370,8 +370,8 @@ def notify_created_user(user_profile: UserProfile, notify_user_ids: List[int]) -
|
||||||
"custom_profile_field_data": {},
|
"custom_profile_field_data": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
user_ids_without_access_to_created_user: List[int] = []
|
user_ids_without_access_to_created_user: list[int] = []
|
||||||
users_with_access_to_created_users: List[UserProfile] = []
|
users_with_access_to_created_users: list[UserProfile] = []
|
||||||
|
|
||||||
if notify_user_ids:
|
if notify_user_ids:
|
||||||
# This is currently used to send creation event when a guest
|
# This is currently used to send creation event when a guest
|
||||||
|
@ -427,7 +427,7 @@ def notify_created_user(user_profile: UserProfile, notify_user_ids: List[int]) -
|
||||||
|
|
||||||
if user_ids_with_real_email_access:
|
if user_ids_with_real_email_access:
|
||||||
assert person_for_real_email_access_users is not None
|
assert person_for_real_email_access_users is not None
|
||||||
event: Dict[str, Any] = dict(
|
event: dict[str, Any] = dict(
|
||||||
type="realm_user", op="add", person=person_for_real_email_access_users
|
type="realm_user", op="add", person=person_for_real_email_access_users
|
||||||
)
|
)
|
||||||
send_event_on_commit(user_profile.realm, event, user_ids_with_real_email_access)
|
send_event_on_commit(user_profile.realm, event, user_ids_with_real_email_access)
|
||||||
|
@ -447,7 +447,7 @@ def notify_created_user(user_profile: UserProfile, notify_user_ids: List[int]) -
|
||||||
send_event_on_commit(user_profile.realm, event, user_ids_without_access_to_created_user)
|
send_event_on_commit(user_profile.realm, event, user_ids_without_access_to_created_user)
|
||||||
|
|
||||||
|
|
||||||
def created_bot_event(user_profile: UserProfile) -> Dict[str, Any]:
|
def created_bot_event(user_profile: UserProfile) -> dict[str, Any]:
|
||||||
def stream_name(stream: Optional[Stream]) -> Optional[str]:
|
def stream_name(stream: Optional[Stream]) -> Optional[str]:
|
||||||
if not stream:
|
if not stream:
|
||||||
return None
|
return None
|
||||||
|
@ -749,7 +749,7 @@ def do_reactivate_user(user_profile: UserProfile, *, acting_user: Optional[UserP
|
||||||
streams=subscribed_streams,
|
streams=subscribed_streams,
|
||||||
)
|
)
|
||||||
|
|
||||||
altered_user_dict: Dict[int, Set[int]] = defaultdict(set)
|
altered_user_dict: dict[int, set[int]] = defaultdict(set)
|
||||||
for stream in subscribed_streams:
|
for stream in subscribed_streams:
|
||||||
altered_user_dict[stream.id] = {user_profile.id}
|
altered_user_dict[stream.id] = {user_profile.id}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Dict, Iterable, List, Optional, Union
|
from typing import Iterable, Optional, Union
|
||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
@ -146,7 +146,7 @@ def try_reorder_realm_custom_profile_fields(realm: Realm, order: Iterable[int])
|
||||||
|
|
||||||
|
|
||||||
def notify_user_update_custom_profile_data(
|
def notify_user_update_custom_profile_data(
|
||||||
user_profile: UserProfile, field: Dict[str, Union[int, str, List[int], None]]
|
user_profile: UserProfile, field: dict[str, Union[int, str, list[int], None]]
|
||||||
) -> None:
|
) -> None:
|
||||||
data = dict(id=field["id"], value=field["value"])
|
data = dict(id=field["id"], value=field["value"])
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ def notify_user_update_custom_profile_data(
|
||||||
|
|
||||||
def do_update_user_custom_profile_data_if_changed(
|
def do_update_user_custom_profile_data_if_changed(
|
||||||
user_profile: UserProfile,
|
user_profile: UserProfile,
|
||||||
data: List[ProfileDataElementUpdateDict],
|
data: list[ProfileDataElementUpdateDict],
|
||||||
) -> None:
|
) -> None:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
for custom_profile_field in data:
|
for custom_profile_field in data:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Any, Dict, Iterable, List
|
from typing import Any, Iterable
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
@ -37,8 +37,8 @@ def check_default_stream_group_name(group_name: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
def lookup_default_stream_groups(
|
def lookup_default_stream_groups(
|
||||||
default_stream_group_names: List[str], realm: Realm
|
default_stream_group_names: list[str], realm: Realm
|
||||||
) -> List[DefaultStreamGroup]:
|
) -> list[DefaultStreamGroup]:
|
||||||
default_stream_groups = []
|
default_stream_groups = []
|
||||||
for group_name in default_stream_group_names:
|
for group_name in default_stream_group_names:
|
||||||
try:
|
try:
|
||||||
|
@ -86,7 +86,7 @@ def do_remove_default_stream(stream: Stream) -> None:
|
||||||
|
|
||||||
|
|
||||||
def do_create_default_stream_group(
|
def do_create_default_stream_group(
|
||||||
realm: Realm, group_name: str, description: str, streams: List[Stream]
|
realm: Realm, group_name: str, description: str, streams: list[Stream]
|
||||||
) -> None:
|
) -> None:
|
||||||
default_stream_ids = get_default_stream_ids_for_realm(realm.id)
|
default_stream_ids = get_default_stream_ids_for_realm(realm.id)
|
||||||
for stream in streams:
|
for stream in streams:
|
||||||
|
@ -113,7 +113,7 @@ def do_create_default_stream_group(
|
||||||
|
|
||||||
|
|
||||||
def do_add_streams_to_default_stream_group(
|
def do_add_streams_to_default_stream_group(
|
||||||
realm: Realm, group: DefaultStreamGroup, streams: List[Stream]
|
realm: Realm, group: DefaultStreamGroup, streams: list[Stream]
|
||||||
) -> None:
|
) -> None:
|
||||||
default_stream_ids = get_default_stream_ids_for_realm(realm.id)
|
default_stream_ids = get_default_stream_ids_for_realm(realm.id)
|
||||||
for stream in streams:
|
for stream in streams:
|
||||||
|
@ -136,7 +136,7 @@ def do_add_streams_to_default_stream_group(
|
||||||
|
|
||||||
|
|
||||||
def do_remove_streams_from_default_stream_group(
|
def do_remove_streams_from_default_stream_group(
|
||||||
realm: Realm, group: DefaultStreamGroup, streams: List[Stream]
|
realm: Realm, group: DefaultStreamGroup, streams: list[Stream]
|
||||||
) -> None:
|
) -> None:
|
||||||
group_stream_ids = {stream.id for stream in group.streams.all()}
|
group_stream_ids = {stream.id for stream in group.streams.all()}
|
||||||
for stream in streams:
|
for stream in streams:
|
||||||
|
@ -190,5 +190,5 @@ def do_remove_default_stream_group(realm: Realm, group: DefaultStreamGroup) -> N
|
||||||
|
|
||||||
def default_stream_groups_to_dicts_sorted(
|
def default_stream_groups_to_dicts_sorted(
|
||||||
groups: Iterable[DefaultStreamGroup],
|
groups: Iterable[DefaultStreamGroup],
|
||||||
) -> List[Dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
return sorted((group.to_dict() for group in groups), key=lambda elt: elt["name"])
|
return sorted((group.to_dict() for group in groups), key=lambda elt: elt["name"])
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, Collection, Dict, List, Optional, Sequence, Set, Tuple
|
from typing import Any, Collection, Optional, Sequence
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
@ -179,7 +179,7 @@ def do_invite_users(
|
||||||
invite_expires_in_minutes: Optional[int],
|
invite_expires_in_minutes: Optional[int],
|
||||||
include_realm_default_subscriptions: bool,
|
include_realm_default_subscriptions: bool,
|
||||||
invite_as: int = PreregistrationUser.INVITE_AS["MEMBER"],
|
invite_as: int = PreregistrationUser.INVITE_AS["MEMBER"],
|
||||||
) -> List[Tuple[str, str, bool]]:
|
) -> list[tuple[str, str, bool]]:
|
||||||
num_invites = len(invitee_emails)
|
num_invites = len(invitee_emails)
|
||||||
|
|
||||||
# Lock the realm, since we need to not race with other invitations
|
# Lock the realm, since we need to not race with other invitations
|
||||||
|
@ -211,8 +211,8 @@ def do_invite_users(
|
||||||
sent_invitations=False,
|
sent_invitations=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
good_emails: Set[str] = set()
|
good_emails: set[str] = set()
|
||||||
errors: List[Tuple[str, str, bool]] = []
|
errors: list[tuple[str, str, bool]] = []
|
||||||
validate_email_allowed_in_realm = get_realm_email_validator(realm)
|
validate_email_allowed_in_realm = get_realm_email_validator(realm)
|
||||||
for email in invitee_emails:
|
for email in invitee_emails:
|
||||||
if email == "":
|
if email == "":
|
||||||
|
@ -234,7 +234,7 @@ def do_invite_users(
|
||||||
"""
|
"""
|
||||||
error_dict = get_existing_user_errors(realm, good_emails)
|
error_dict = get_existing_user_errors(realm, good_emails)
|
||||||
|
|
||||||
skipped: List[Tuple[str, str, bool]] = []
|
skipped: list[tuple[str, str, bool]] = []
|
||||||
for email in error_dict:
|
for email in error_dict:
|
||||||
msg, deactivated = error_dict[email]
|
msg, deactivated = error_dict[email]
|
||||||
skipped.append((email, msg, deactivated))
|
skipped.append((email, msg, deactivated))
|
||||||
|
@ -292,7 +292,7 @@ def get_invitation_expiry_date(confirmation_obj: Confirmation) -> Optional[int]:
|
||||||
return datetime_to_timestamp(expiry_date)
|
return datetime_to_timestamp(expiry_date)
|
||||||
|
|
||||||
|
|
||||||
def do_get_invites_controlled_by_user(user_profile: UserProfile) -> List[Dict[str, Any]]:
|
def do_get_invites_controlled_by_user(user_profile: UserProfile) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Returns a list of dicts representing invitations that can be controlled by user_profile.
|
Returns a list of dicts representing invitations that can be controlled by user_profile.
|
||||||
This isn't necessarily the same as all the invitations generated by the user, as administrators
|
This isn't necessarily the same as all the invitations generated by the user, as administrators
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Iterable, List, TypedDict
|
from typing import Iterable, TypedDict
|
||||||
|
|
||||||
from zerver.lib import retention
|
from zerver.lib import retention
|
||||||
from zerver.lib.retention import move_messages_to_archive
|
from zerver.lib.retention import move_messages_to_archive
|
||||||
|
@ -9,14 +9,14 @@ from zerver.tornado.django_api import send_event_on_commit
|
||||||
|
|
||||||
class DeleteMessagesEvent(TypedDict, total=False):
|
class DeleteMessagesEvent(TypedDict, total=False):
|
||||||
type: str
|
type: str
|
||||||
message_ids: List[int]
|
message_ids: list[int]
|
||||||
message_type: str
|
message_type: str
|
||||||
topic: str
|
topic: str
|
||||||
stream_id: int
|
stream_id: int
|
||||||
|
|
||||||
|
|
||||||
def check_update_first_message_id(
|
def check_update_first_message_id(
|
||||||
realm: Realm, stream: Stream, message_ids: List[int], users_to_notify: Iterable[int]
|
realm: Realm, stream: Stream, message_ids: list[int], users_to_notify: Iterable[int]
|
||||||
) -> None:
|
) -> None:
|
||||||
# This will not update the `first_message_id` of streams where the
|
# This will not update the `first_message_id` of streams where the
|
||||||
# first message was deleted prior to the implementation of this function.
|
# first message was deleted prior to the implementation of this function.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import itertools
|
import itertools
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import AbstractSet, Any, Dict, Iterable, List, Optional, Set, Tuple
|
from typing import AbstractSet, Any, Iterable, Optional
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
@ -84,7 +84,7 @@ from zerver.models.users import get_system_bot
|
||||||
from zerver.tornado.django_api import send_event_on_commit
|
from zerver.tornado.django_api import send_event_on_commit
|
||||||
|
|
||||||
|
|
||||||
def subscriber_info(user_id: int) -> Dict[str, Any]:
|
def subscriber_info(user_id: int) -> dict[str, Any]:
|
||||||
return {"id": user_id, "flags": ["read"]}
|
return {"id": user_id, "flags": ["read"]}
|
||||||
|
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ def maybe_send_resolve_topic_notifications(
|
||||||
new_topic_name: str,
|
new_topic_name: str,
|
||||||
changed_messages: QuerySet[Message],
|
changed_messages: QuerySet[Message],
|
||||||
pre_truncation_new_topic_name: str,
|
pre_truncation_new_topic_name: str,
|
||||||
) -> Tuple[Optional[int], bool]:
|
) -> tuple[Optional[int], bool]:
|
||||||
"""Returns resolved_topic_message_id if resolve topic notifications were in fact sent."""
|
"""Returns resolved_topic_message_id if resolve topic notifications were in fact sent."""
|
||||||
# Note that topics will have already been stripped in check_update_message.
|
# Note that topics will have already been stripped in check_update_message.
|
||||||
resolved_prefix_len = len(RESOLVED_TOPIC_PREFIX)
|
resolved_prefix_len = len(RESOLVED_TOPIC_PREFIX)
|
||||||
|
@ -299,7 +299,7 @@ def send_message_moved_breadcrumbs(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_mentions_for_message_updates(message_id: int) -> Set[int]:
|
def get_mentions_for_message_updates(message_id: int) -> set[int]:
|
||||||
# We exclude UserMessage.flags.historical rows since those
|
# We exclude UserMessage.flags.historical rows since those
|
||||||
# users did not receive the message originally, and thus
|
# users did not receive the message originally, and thus
|
||||||
# probably are not relevant for reprocessed alert_words,
|
# probably are not relevant for reprocessed alert_words,
|
||||||
|
@ -330,7 +330,7 @@ def update_user_message_flags(
|
||||||
) -> None:
|
) -> None:
|
||||||
mentioned_ids = rendering_result.mentions_user_ids
|
mentioned_ids = rendering_result.mentions_user_ids
|
||||||
ids_with_alert_words = rendering_result.user_ids_with_alert_words
|
ids_with_alert_words = rendering_result.user_ids_with_alert_words
|
||||||
changed_ums: Set[UserMessage] = set()
|
changed_ums: set[UserMessage] = set()
|
||||||
|
|
||||||
def update_flag(um: UserMessage, should_set: bool, flag: int) -> None:
|
def update_flag(um: UserMessage, should_set: bool, flag: int) -> None:
|
||||||
if should_set:
|
if should_set:
|
||||||
|
@ -366,7 +366,7 @@ def do_update_embedded_data(
|
||||||
rendering_result: MessageRenderingResult,
|
rendering_result: MessageRenderingResult,
|
||||||
) -> None:
|
) -> None:
|
||||||
timestamp = timezone_now()
|
timestamp = timezone_now()
|
||||||
event: Dict[str, Any] = {
|
event: dict[str, Any] = {
|
||||||
"type": "update_message",
|
"type": "update_message",
|
||||||
"user_id": None,
|
"user_id": None,
|
||||||
"edit_timestamp": datetime_to_timestamp(timestamp),
|
"edit_timestamp": datetime_to_timestamp(timestamp),
|
||||||
|
@ -390,7 +390,7 @@ def do_update_embedded_data(
|
||||||
|
|
||||||
event["message_ids"] = update_message_cache(changed_messages)
|
event["message_ids"] = update_message_cache(changed_messages)
|
||||||
|
|
||||||
def user_info(um: UserMessage) -> Dict[str, Any]:
|
def user_info(um: UserMessage) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"id": um.user_profile_id,
|
"id": um.user_profile_id,
|
||||||
"flags": um.flags_list(),
|
"flags": um.flags_list(),
|
||||||
|
@ -433,7 +433,7 @@ def do_update_message(
|
||||||
send_notification_to_new_thread: bool,
|
send_notification_to_new_thread: bool,
|
||||||
content: Optional[str],
|
content: Optional[str],
|
||||||
rendering_result: Optional[MessageRenderingResult],
|
rendering_result: Optional[MessageRenderingResult],
|
||||||
prior_mention_user_ids: Set[int],
|
prior_mention_user_ids: set[int],
|
||||||
mention_data: Optional[MentionData] = None,
|
mention_data: Optional[MentionData] = None,
|
||||||
) -> int:
|
) -> int:
|
||||||
"""
|
"""
|
||||||
|
@ -452,7 +452,7 @@ def do_update_message(
|
||||||
timestamp = timezone_now()
|
timestamp = timezone_now()
|
||||||
target_message.last_edit_time = timestamp
|
target_message.last_edit_time = timestamp
|
||||||
|
|
||||||
event: Dict[str, Any] = {
|
event: dict[str, Any] = {
|
||||||
"type": "update_message",
|
"type": "update_message",
|
||||||
"user_id": user_profile.id,
|
"user_id": user_profile.id,
|
||||||
"edit_timestamp": datetime_to_timestamp(timestamp),
|
"edit_timestamp": datetime_to_timestamp(timestamp),
|
||||||
|
@ -586,7 +586,7 @@ def do_update_message(
|
||||||
event["propagate_mode"] = propagate_mode
|
event["propagate_mode"] = propagate_mode
|
||||||
|
|
||||||
users_losing_access = UserProfile.objects.none()
|
users_losing_access = UserProfile.objects.none()
|
||||||
user_ids_gaining_usermessages: List[int] = []
|
user_ids_gaining_usermessages: list[int] = []
|
||||||
if new_stream is not None:
|
if new_stream is not None:
|
||||||
assert content is None
|
assert content is None
|
||||||
assert target_message.is_stream_message()
|
assert target_message.is_stream_message()
|
||||||
|
@ -798,7 +798,7 @@ def do_update_message(
|
||||||
|
|
||||||
event["message_ids"] = update_message_cache(changed_messages, realm_id)
|
event["message_ids"] = update_message_cache(changed_messages, realm_id)
|
||||||
|
|
||||||
def user_info(um: UserMessage) -> Dict[str, Any]:
|
def user_info(um: UserMessage) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"id": um.user_profile_id,
|
"id": um.user_profile_id,
|
||||||
"flags": um.flags_list(),
|
"flags": um.flags_list(),
|
||||||
|
@ -908,9 +908,9 @@ def do_update_message(
|
||||||
assert target_stream is not None
|
assert target_stream is not None
|
||||||
assert target_topic_name is not None
|
assert target_topic_name is not None
|
||||||
|
|
||||||
stream_inaccessible_to_user_profiles: List[UserProfile] = []
|
stream_inaccessible_to_user_profiles: list[UserProfile] = []
|
||||||
orig_topic_user_profile_to_visibility_policy: Dict[UserProfile, int] = {}
|
orig_topic_user_profile_to_visibility_policy: dict[UserProfile, int] = {}
|
||||||
target_topic_user_profile_to_visibility_policy: Dict[UserProfile, int] = {}
|
target_topic_user_profile_to_visibility_policy: dict[UserProfile, int] = {}
|
||||||
user_ids_losing_access = {user.id for user in users_losing_access}
|
user_ids_losing_access = {user.id for user in users_losing_access}
|
||||||
for user_topic in get_users_with_user_topic_visibility_policy(
|
for user_topic in get_users_with_user_topic_visibility_policy(
|
||||||
stream_being_edited.id, orig_topic_name
|
stream_being_edited.id, orig_topic_name
|
||||||
|
@ -930,14 +930,14 @@ def do_update_message(
|
||||||
)
|
)
|
||||||
|
|
||||||
# User profiles having any of the visibility policies set for either the original or target topic.
|
# User profiles having any of the visibility policies set for either the original or target topic.
|
||||||
user_profiles_having_visibility_policy: Set[UserProfile] = set(
|
user_profiles_having_visibility_policy: set[UserProfile] = set(
|
||||||
itertools.chain(
|
itertools.chain(
|
||||||
orig_topic_user_profile_to_visibility_policy.keys(),
|
orig_topic_user_profile_to_visibility_policy.keys(),
|
||||||
target_topic_user_profile_to_visibility_policy.keys(),
|
target_topic_user_profile_to_visibility_policy.keys(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
user_profiles_for_visibility_policy_pair: Dict[Tuple[int, int], List[UserProfile]] = (
|
user_profiles_for_visibility_policy_pair: dict[tuple[int, int], list[UserProfile]] = (
|
||||||
defaultdict(list)
|
defaultdict(list)
|
||||||
)
|
)
|
||||||
for user_profile_with_policy in user_profiles_having_visibility_policy:
|
for user_profile_with_policy in user_profiles_having_visibility_policy:
|
||||||
|
@ -1197,7 +1197,7 @@ def check_time_limit_for_change_all_propagate_mode(
|
||||||
message__recipient_id=message.recipient_id,
|
message__recipient_id=message.recipient_id,
|
||||||
message__subject__iexact=message.topic_name(),
|
message__subject__iexact=message.topic_name(),
|
||||||
).values_list("message_id", flat=True)
|
).values_list("message_id", flat=True)
|
||||||
messages_allowed_to_move: List[int] = list(
|
messages_allowed_to_move: list[int] = list(
|
||||||
Message.objects.filter(
|
Message.objects.filter(
|
||||||
# Uses index: zerver_message_pkey
|
# Uses index: zerver_message_pkey
|
||||||
id__in=accessible_messages_in_topic,
|
id__in=accessible_messages_in_topic,
|
||||||
|
@ -1291,8 +1291,8 @@ def check_update_message(
|
||||||
raise JsonableError(_("The time limit for editing this message's topic has passed."))
|
raise JsonableError(_("The time limit for editing this message's topic has passed."))
|
||||||
|
|
||||||
rendering_result = None
|
rendering_result = None
|
||||||
links_for_embed: Set[str] = set()
|
links_for_embed: set[str] = set()
|
||||||
prior_mention_user_ids: Set[int] = set()
|
prior_mention_user_ids: set[int] = set()
|
||||||
mention_data: Optional[MentionData] = None
|
mention_data: Optional[MentionData] = None
|
||||||
if content is not None:
|
if content is not None:
|
||||||
if content.rstrip() == "":
|
if content.rstrip() == "":
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import asdict, dataclass, field
|
from dataclasses import asdict, dataclass, field
|
||||||
from typing import List, Optional, Set
|
from typing import Optional
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
@ -26,7 +26,7 @@ from zerver.tornado.django_api import send_event
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ReadMessagesEvent:
|
class ReadMessagesEvent:
|
||||||
messages: List[int]
|
messages: list[int]
|
||||||
all: bool
|
all: bool
|
||||||
type: str = field(default="update_message_flags", init=False)
|
type: str = field(default="update_message_flags", init=False)
|
||||||
op: str = field(default="add", init=False)
|
op: str = field(default="add", init=False)
|
||||||
|
@ -203,9 +203,9 @@ def do_mark_muted_user_messages_as_read(
|
||||||
|
|
||||||
def do_update_mobile_push_notification(
|
def do_update_mobile_push_notification(
|
||||||
message: Message,
|
message: Message,
|
||||||
prior_mention_user_ids: Set[int],
|
prior_mention_user_ids: set[int],
|
||||||
mentions_user_ids: Set[int],
|
mentions_user_ids: set[int],
|
||||||
stream_push_user_ids: Set[int],
|
stream_push_user_ids: set[int],
|
||||||
) -> None:
|
) -> None:
|
||||||
# Called during the message edit code path to remove mobile push
|
# Called during the message edit code path to remove mobile push
|
||||||
# notifications for users who are no longer mentioned following
|
# notifications for users who are no longer mentioned following
|
||||||
|
@ -223,7 +223,7 @@ def do_update_mobile_push_notification(
|
||||||
|
|
||||||
|
|
||||||
def do_clear_mobile_push_notifications_for_ids(
|
def do_clear_mobile_push_notifications_for_ids(
|
||||||
user_profile_ids: List[int], message_ids: List[int]
|
user_profile_ids: list[int], message_ids: list[int]
|
||||||
) -> None:
|
) -> None:
|
||||||
if len(message_ids) == 0:
|
if len(message_ids) == 0:
|
||||||
return
|
return
|
||||||
|
@ -261,7 +261,7 @@ def do_clear_mobile_push_notifications_for_ids(
|
||||||
|
|
||||||
|
|
||||||
def do_update_message_flags(
|
def do_update_message_flags(
|
||||||
user_profile: UserProfile, operation: str, flag: str, messages: List[int]
|
user_profile: UserProfile, operation: str, flag: str, messages: list[int]
|
||||||
) -> int:
|
) -> int:
|
||||||
valid_flags = [item for item in UserMessage.flags if item not in UserMessage.NON_API_FLAGS]
|
valid_flags = [item for item in UserMessage.flags if item not in UserMessage.NON_API_FLAGS]
|
||||||
if flag not in valid_flags:
|
if flag not in valid_flags:
|
||||||
|
|
|
@ -3,20 +3,7 @@ from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from email.headerregistry import Address
|
from email.headerregistry import Address
|
||||||
from typing import (
|
from typing import AbstractSet, Any, Callable, Collection, Optional, Sequence, TypedDict, Union
|
||||||
AbstractSet,
|
|
||||||
Any,
|
|
||||||
Callable,
|
|
||||||
Collection,
|
|
||||||
Dict,
|
|
||||||
List,
|
|
||||||
Optional,
|
|
||||||
Sequence,
|
|
||||||
Set,
|
|
||||||
Tuple,
|
|
||||||
TypedDict,
|
|
||||||
Union,
|
|
||||||
)
|
|
||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -157,7 +144,7 @@ def render_incoming_message(
|
||||||
content: str,
|
content: str,
|
||||||
realm: Realm,
|
realm: Realm,
|
||||||
mention_data: Optional[MentionData] = None,
|
mention_data: Optional[MentionData] = None,
|
||||||
url_embed_data: Optional[Dict[str, Optional[UrlEmbedData]]] = None,
|
url_embed_data: Optional[dict[str, Optional[UrlEmbedData]]] = None,
|
||||||
email_gateway: bool = False,
|
email_gateway: bool = False,
|
||||||
) -> MessageRenderingResult:
|
) -> MessageRenderingResult:
|
||||||
realm_alert_words_automaton = get_alert_word_automaton(realm)
|
realm_alert_words_automaton = get_alert_word_automaton(realm)
|
||||||
|
@ -178,25 +165,25 @@ def render_incoming_message(
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RecipientInfoResult:
|
class RecipientInfoResult:
|
||||||
active_user_ids: Set[int]
|
active_user_ids: set[int]
|
||||||
online_push_user_ids: Set[int]
|
online_push_user_ids: set[int]
|
||||||
dm_mention_email_disabled_user_ids: Set[int]
|
dm_mention_email_disabled_user_ids: set[int]
|
||||||
dm_mention_push_disabled_user_ids: Set[int]
|
dm_mention_push_disabled_user_ids: set[int]
|
||||||
stream_email_user_ids: Set[int]
|
stream_email_user_ids: set[int]
|
||||||
stream_push_user_ids: Set[int]
|
stream_push_user_ids: set[int]
|
||||||
topic_wildcard_mention_user_ids: Set[int]
|
topic_wildcard_mention_user_ids: set[int]
|
||||||
stream_wildcard_mention_user_ids: Set[int]
|
stream_wildcard_mention_user_ids: set[int]
|
||||||
followed_topic_email_user_ids: Set[int]
|
followed_topic_email_user_ids: set[int]
|
||||||
followed_topic_push_user_ids: Set[int]
|
followed_topic_push_user_ids: set[int]
|
||||||
topic_wildcard_mention_in_followed_topic_user_ids: Set[int]
|
topic_wildcard_mention_in_followed_topic_user_ids: set[int]
|
||||||
stream_wildcard_mention_in_followed_topic_user_ids: Set[int]
|
stream_wildcard_mention_in_followed_topic_user_ids: set[int]
|
||||||
muted_sender_user_ids: Set[int]
|
muted_sender_user_ids: set[int]
|
||||||
um_eligible_user_ids: Set[int]
|
um_eligible_user_ids: set[int]
|
||||||
long_term_idle_user_ids: Set[int]
|
long_term_idle_user_ids: set[int]
|
||||||
default_bot_user_ids: Set[int]
|
default_bot_user_ids: set[int]
|
||||||
service_bot_tuples: List[Tuple[int, int]]
|
service_bot_tuples: list[tuple[int, int]]
|
||||||
all_bot_user_ids: Set[int]
|
all_bot_user_ids: set[int]
|
||||||
topic_participant_user_ids: Set[int]
|
topic_participant_user_ids: set[int]
|
||||||
sender_muted_stream: Optional[bool]
|
sender_muted_stream: Optional[bool]
|
||||||
|
|
||||||
|
|
||||||
|
@ -226,16 +213,16 @@ def get_recipient_info(
|
||||||
possible_topic_wildcard_mention: bool = True,
|
possible_topic_wildcard_mention: bool = True,
|
||||||
possible_stream_wildcard_mention: bool = True,
|
possible_stream_wildcard_mention: bool = True,
|
||||||
) -> RecipientInfoResult:
|
) -> RecipientInfoResult:
|
||||||
stream_push_user_ids: Set[int] = set()
|
stream_push_user_ids: set[int] = set()
|
||||||
stream_email_user_ids: Set[int] = set()
|
stream_email_user_ids: set[int] = set()
|
||||||
topic_wildcard_mention_user_ids: Set[int] = set()
|
topic_wildcard_mention_user_ids: set[int] = set()
|
||||||
stream_wildcard_mention_user_ids: Set[int] = set()
|
stream_wildcard_mention_user_ids: set[int] = set()
|
||||||
followed_topic_push_user_ids: Set[int] = set()
|
followed_topic_push_user_ids: set[int] = set()
|
||||||
followed_topic_email_user_ids: Set[int] = set()
|
followed_topic_email_user_ids: set[int] = set()
|
||||||
topic_wildcard_mention_in_followed_topic_user_ids: Set[int] = set()
|
topic_wildcard_mention_in_followed_topic_user_ids: set[int] = set()
|
||||||
stream_wildcard_mention_in_followed_topic_user_ids: Set[int] = set()
|
stream_wildcard_mention_in_followed_topic_user_ids: set[int] = set()
|
||||||
muted_sender_user_ids: Set[int] = get_muting_users(sender_id)
|
muted_sender_user_ids: set[int] = get_muting_users(sender_id)
|
||||||
topic_participant_user_ids: Set[int] = set()
|
topic_participant_user_ids: set[int] = set()
|
||||||
sender_muted_stream: Optional[bool] = None
|
sender_muted_stream: Optional[bool] = None
|
||||||
|
|
||||||
if recipient.type == Recipient.PERSONAL:
|
if recipient.type == Recipient.PERSONAL:
|
||||||
|
@ -315,7 +302,7 @@ def get_recipient_info(
|
||||||
|
|
||||||
user_id_to_visibility_policy = stream_topic.user_id_to_visibility_policy_dict()
|
user_id_to_visibility_policy = stream_topic.user_id_to_visibility_policy_dict()
|
||||||
|
|
||||||
def notification_recipients(setting: str) -> Set[int]:
|
def notification_recipients(setting: str) -> set[int]:
|
||||||
return {
|
return {
|
||||||
row["user_profile_id"]
|
row["user_profile_id"]
|
||||||
for row in subscription_rows
|
for row in subscription_rows
|
||||||
|
@ -332,7 +319,7 @@ def get_recipient_info(
|
||||||
stream_push_user_ids = notification_recipients("push_notifications")
|
stream_push_user_ids = notification_recipients("push_notifications")
|
||||||
stream_email_user_ids = notification_recipients("email_notifications")
|
stream_email_user_ids = notification_recipients("email_notifications")
|
||||||
|
|
||||||
def followed_topic_notification_recipients(setting: str) -> Set[int]:
|
def followed_topic_notification_recipients(setting: str) -> set[int]:
|
||||||
return {
|
return {
|
||||||
row["user_profile_id"]
|
row["user_profile_id"]
|
||||||
for row in subscription_rows
|
for row in subscription_rows
|
||||||
|
@ -427,7 +414,7 @@ def get_recipient_info(
|
||||||
# to-do.
|
# to-do.
|
||||||
rows = []
|
rows = []
|
||||||
|
|
||||||
def get_ids_for(f: Callable[[ActiveUserDict], bool]) -> Set[int]:
|
def get_ids_for(f: Callable[[ActiveUserDict], bool]) -> set[int]:
|
||||||
"""Only includes users on the explicit message to line"""
|
"""Only includes users on the explicit message to line"""
|
||||||
return {row["id"] for row in rows if f(row)} & message_to_user_id_set
|
return {row["id"] for row in rows if f(row)} & message_to_user_id_set
|
||||||
|
|
||||||
|
@ -506,12 +493,12 @@ def get_recipient_info(
|
||||||
|
|
||||||
def get_service_bot_events(
|
def get_service_bot_events(
|
||||||
sender: UserProfile,
|
sender: UserProfile,
|
||||||
service_bot_tuples: List[Tuple[int, int]],
|
service_bot_tuples: list[tuple[int, int]],
|
||||||
mentioned_user_ids: Set[int],
|
mentioned_user_ids: set[int],
|
||||||
active_user_ids: Set[int],
|
active_user_ids: set[int],
|
||||||
recipient_type: int,
|
recipient_type: int,
|
||||||
) -> Dict[str, List[Dict[str, Any]]]:
|
) -> dict[str, list[dict[str, Any]]]:
|
||||||
event_dict: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
|
event_dict: dict[str, list[dict[str, Any]]] = defaultdict(list)
|
||||||
|
|
||||||
# Avoid infinite loops by preventing messages sent by bots from generating
|
# Avoid infinite loops by preventing messages sent by bots from generating
|
||||||
# Service events.
|
# Service events.
|
||||||
|
@ -576,12 +563,12 @@ def build_message_send_dict(
|
||||||
stream: Optional[Stream] = None,
|
stream: Optional[Stream] = None,
|
||||||
local_id: Optional[str] = None,
|
local_id: Optional[str] = None,
|
||||||
sender_queue_id: Optional[str] = None,
|
sender_queue_id: Optional[str] = None,
|
||||||
widget_content_dict: Optional[Dict[str, Any]] = None,
|
widget_content_dict: Optional[dict[str, Any]] = None,
|
||||||
email_gateway: bool = False,
|
email_gateway: bool = False,
|
||||||
mention_backend: Optional[MentionBackend] = None,
|
mention_backend: Optional[MentionBackend] = None,
|
||||||
limit_unread_user_ids: Optional[Set[int]] = None,
|
limit_unread_user_ids: Optional[set[int]] = None,
|
||||||
disable_external_notifications: bool = False,
|
disable_external_notifications: bool = False,
|
||||||
recipients_for_user_creation_events: Optional[Dict[UserProfile, Set[int]]] = None,
|
recipients_for_user_creation_events: Optional[dict[UserProfile, set[int]]] = None,
|
||||||
) -> SendMessageRequest:
|
) -> SendMessageRequest:
|
||||||
"""Returns a dictionary that can be passed into do_send_messages. In
|
"""Returns a dictionary that can be passed into do_send_messages. In
|
||||||
production, this is always called by check_message, but some
|
production, this is always called by check_message, but some
|
||||||
|
@ -726,10 +713,10 @@ def create_user_messages(
|
||||||
mentioned_user_ids: AbstractSet[int],
|
mentioned_user_ids: AbstractSet[int],
|
||||||
followed_topic_push_user_ids: AbstractSet[int],
|
followed_topic_push_user_ids: AbstractSet[int],
|
||||||
followed_topic_email_user_ids: AbstractSet[int],
|
followed_topic_email_user_ids: AbstractSet[int],
|
||||||
mark_as_read_user_ids: Set[int],
|
mark_as_read_user_ids: set[int],
|
||||||
limit_unread_user_ids: Optional[Set[int]],
|
limit_unread_user_ids: Optional[set[int]],
|
||||||
topic_participant_user_ids: Set[int],
|
topic_participant_user_ids: set[int],
|
||||||
) -> List[UserMessageLite]:
|
) -> list[UserMessageLite]:
|
||||||
# These properties on the Message are set via
|
# These properties on the Message are set via
|
||||||
# render_message_markdown by code in the Markdown inline patterns
|
# render_message_markdown by code in the Markdown inline patterns
|
||||||
ids_with_alert_words = rendering_result.user_ids_with_alert_words
|
ids_with_alert_words = rendering_result.user_ids_with_alert_words
|
||||||
|
@ -798,7 +785,7 @@ def create_user_messages(
|
||||||
return user_messages
|
return user_messages
|
||||||
|
|
||||||
|
|
||||||
def filter_presence_idle_user_ids(user_ids: Set[int]) -> List[int]:
|
def filter_presence_idle_user_ids(user_ids: set[int]) -> list[int]:
|
||||||
# Given a set of user IDs (the recipients of a message), accesses
|
# Given a set of user IDs (the recipients of a message), accesses
|
||||||
# the UserPresence table to determine which of these users are
|
# the UserPresence table to determine which of these users are
|
||||||
# currently idle and should potentially get email notifications
|
# currently idle and should potentially get email notifications
|
||||||
|
@ -821,8 +808,8 @@ def filter_presence_idle_user_ids(user_ids: Set[int]) -> List[int]:
|
||||||
def get_active_presence_idle_user_ids(
|
def get_active_presence_idle_user_ids(
|
||||||
realm: Realm,
|
realm: Realm,
|
||||||
sender_id: int,
|
sender_id: int,
|
||||||
user_notifications_data_list: List[UserMessageNotificationsData],
|
user_notifications_data_list: list[UserMessageNotificationsData],
|
||||||
) -> List[int]:
|
) -> list[int]:
|
||||||
"""
|
"""
|
||||||
Given a list of active_user_ids, we build up a subset
|
Given a list of active_user_ids, we build up a subset
|
||||||
of those users who fit these criteria:
|
of those users who fit these criteria:
|
||||||
|
@ -851,7 +838,7 @@ def do_send_messages(
|
||||||
send_message_requests_maybe_none: Sequence[Optional[SendMessageRequest]],
|
send_message_requests_maybe_none: Sequence[Optional[SendMessageRequest]],
|
||||||
*,
|
*,
|
||||||
mark_as_read: Sequence[int] = [],
|
mark_as_read: Sequence[int] = [],
|
||||||
) -> List[SentMessageResult]:
|
) -> list[SentMessageResult]:
|
||||||
"""See
|
"""See
|
||||||
https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html
|
https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html
|
||||||
for high-level documentation on this subsystem.
|
for high-level documentation on this subsystem.
|
||||||
|
@ -865,7 +852,7 @@ def do_send_messages(
|
||||||
]
|
]
|
||||||
|
|
||||||
# Save the message receipts in the database
|
# Save the message receipts in the database
|
||||||
user_message_flags: Dict[int, Dict[int, List[str]]] = defaultdict(dict)
|
user_message_flags: dict[int, dict[int, list[str]]] = defaultdict(dict)
|
||||||
|
|
||||||
Message.objects.bulk_create(send_request.message for send_request in send_message_requests)
|
Message.objects.bulk_create(send_request.message for send_request in send_message_requests)
|
||||||
|
|
||||||
|
@ -877,7 +864,7 @@ def do_send_messages(
|
||||||
send_request.message.has_attachment = True
|
send_request.message.has_attachment = True
|
||||||
send_request.message.save(update_fields=["has_attachment"])
|
send_request.message.save(update_fields=["has_attachment"])
|
||||||
|
|
||||||
ums: List[UserMessageLite] = []
|
ums: list[UserMessageLite] = []
|
||||||
for send_request in send_message_requests:
|
for send_request in send_message_requests:
|
||||||
# Service bots (outgoing webhook bots and embedded bots) don't store UserMessage rows;
|
# Service bots (outgoing webhook bots and embedded bots) don't store UserMessage rows;
|
||||||
# they will be processed later.
|
# they will be processed later.
|
||||||
|
@ -978,7 +965,7 @@ def do_send_messages(
|
||||||
human_user_personal_mentions = send_request.rendering_result.mentions_user_ids & (
|
human_user_personal_mentions = send_request.rendering_result.mentions_user_ids & (
|
||||||
send_request.active_user_ids - send_request.all_bot_user_ids
|
send_request.active_user_ids - send_request.all_bot_user_ids
|
||||||
)
|
)
|
||||||
expect_follow_user_profiles: Set[UserProfile] = set()
|
expect_follow_user_profiles: set[UserProfile] = set()
|
||||||
|
|
||||||
if len(human_user_personal_mentions) > 0:
|
if len(human_user_personal_mentions) > 0:
|
||||||
expect_follow_user_profiles = set(
|
expect_follow_user_profiles = set(
|
||||||
|
@ -1045,10 +1032,10 @@ def do_send_messages(
|
||||||
|
|
||||||
class UserData(TypedDict):
|
class UserData(TypedDict):
|
||||||
id: int
|
id: int
|
||||||
flags: List[str]
|
flags: list[str]
|
||||||
mentioned_user_group_id: Optional[int]
|
mentioned_user_group_id: Optional[int]
|
||||||
|
|
||||||
users: List[UserData] = []
|
users: list[UserData] = []
|
||||||
for user_id in user_list:
|
for user_id in user_list:
|
||||||
flags = user_flags.get(user_id, [])
|
flags = user_flags.get(user_id, [])
|
||||||
# TODO/compatibility: The `wildcard_mentioned` flag was deprecated in favor of
|
# TODO/compatibility: The `wildcard_mentioned` flag was deprecated in favor of
|
||||||
|
@ -1278,7 +1265,7 @@ def extract_stream_indicator(s: str) -> Union[str, int]:
|
||||||
raise JsonableError(_("Invalid data type for channel"))
|
raise JsonableError(_("Invalid data type for channel"))
|
||||||
|
|
||||||
|
|
||||||
def extract_private_recipients(s: str) -> Union[List[str], List[int]]:
|
def extract_private_recipients(s: str) -> Union[list[str], list[int]]:
|
||||||
# We try to accept multiple incoming formats for recipients.
|
# We try to accept multiple incoming formats for recipients.
|
||||||
# See test_extract_recipients() for examples of what we allow.
|
# See test_extract_recipients() for examples of what we allow.
|
||||||
|
|
||||||
|
@ -1306,7 +1293,7 @@ def extract_private_recipients(s: str) -> Union[List[str], List[int]]:
|
||||||
return get_validated_user_ids(data)
|
return get_validated_user_ids(data)
|
||||||
|
|
||||||
|
|
||||||
def get_validated_user_ids(user_ids: Collection[int]) -> List[int]:
|
def get_validated_user_ids(user_ids: Collection[int]) -> list[int]:
|
||||||
for user_id in user_ids:
|
for user_id in user_ids:
|
||||||
if not isinstance(user_id, int):
|
if not isinstance(user_id, int):
|
||||||
raise JsonableError(_("Recipient lists may contain emails or user IDs, but not both."))
|
raise JsonableError(_("Recipient lists may contain emails or user IDs, but not both."))
|
||||||
|
@ -1314,7 +1301,7 @@ def get_validated_user_ids(user_ids: Collection[int]) -> List[int]:
|
||||||
return list(set(user_ids))
|
return list(set(user_ids))
|
||||||
|
|
||||||
|
|
||||||
def get_validated_emails(emails: Collection[str]) -> List[str]:
|
def get_validated_emails(emails: Collection[str]) -> list[str]:
|
||||||
for email in emails:
|
for email in emails:
|
||||||
if not isinstance(email, str):
|
if not isinstance(email, str):
|
||||||
raise JsonableError(_("Recipient lists may contain emails or user IDs, but not both."))
|
raise JsonableError(_("Recipient lists may contain emails or user IDs, but not both."))
|
||||||
|
@ -1457,7 +1444,7 @@ def send_pm_if_empty_stream(
|
||||||
|
|
||||||
if sender.bot_owner is not None:
|
if sender.bot_owner is not None:
|
||||||
with override_language(sender.bot_owner.default_language):
|
with override_language(sender.bot_owner.default_language):
|
||||||
arg_dict: Dict[str, Any] = {
|
arg_dict: dict[str, Any] = {
|
||||||
"bot_identity": f"`{sender.delivery_email}`",
|
"bot_identity": f"`{sender.delivery_email}`",
|
||||||
}
|
}
|
||||||
if stream is None:
|
if stream is None:
|
||||||
|
@ -1596,14 +1583,14 @@ def check_sender_can_access_recipients(
|
||||||
|
|
||||||
def get_recipients_for_user_creation_events(
|
def get_recipients_for_user_creation_events(
|
||||||
realm: Realm, sender: UserProfile, user_profiles: Sequence[UserProfile]
|
realm: Realm, sender: UserProfile, user_profiles: Sequence[UserProfile]
|
||||||
) -> Dict[UserProfile, Set[int]]:
|
) -> dict[UserProfile, set[int]]:
|
||||||
"""
|
"""
|
||||||
This function returns a dictionary with data about which users would
|
This function returns a dictionary with data about which users would
|
||||||
receive stream creation events due to gaining access to a user.
|
receive stream creation events due to gaining access to a user.
|
||||||
The key of the dictionary is a user object and the value is a set of
|
The key of the dictionary is a user object and the value is a set of
|
||||||
user_ids that would gain access to that user.
|
user_ids that would gain access to that user.
|
||||||
"""
|
"""
|
||||||
recipients_for_user_creation_events: Dict[UserProfile, Set[int]] = defaultdict(set)
|
recipients_for_user_creation_events: dict[UserProfile, set[int]] = defaultdict(set)
|
||||||
|
|
||||||
# If none of the users in the direct message conversation are
|
# If none of the users in the direct message conversation are
|
||||||
# guests, then there is no possible can_access_all_users_group
|
# guests, then there is no possible can_access_all_users_group
|
||||||
|
@ -1666,7 +1653,7 @@ def check_message(
|
||||||
skip_stream_access_check: bool = False,
|
skip_stream_access_check: bool = False,
|
||||||
message_type: int = Message.MessageType.NORMAL,
|
message_type: int = Message.MessageType.NORMAL,
|
||||||
mention_backend: Optional[MentionBackend] = None,
|
mention_backend: Optional[MentionBackend] = None,
|
||||||
limit_unread_user_ids: Optional[Set[int]] = None,
|
limit_unread_user_ids: Optional[set[int]] = None,
|
||||||
disable_external_notifications: bool = False,
|
disable_external_notifications: bool = False,
|
||||||
) -> SendMessageRequest:
|
) -> SendMessageRequest:
|
||||||
"""See
|
"""See
|
||||||
|
@ -1841,7 +1828,7 @@ def _internal_prep_message(
|
||||||
email_gateway: bool = False,
|
email_gateway: bool = False,
|
||||||
message_type: int = Message.MessageType.NORMAL,
|
message_type: int = Message.MessageType.NORMAL,
|
||||||
mention_backend: Optional[MentionBackend] = None,
|
mention_backend: Optional[MentionBackend] = None,
|
||||||
limit_unread_user_ids: Optional[Set[int]] = None,
|
limit_unread_user_ids: Optional[set[int]] = None,
|
||||||
disable_external_notifications: bool = False,
|
disable_external_notifications: bool = False,
|
||||||
forged: bool = False,
|
forged: bool = False,
|
||||||
forged_timestamp: Optional[float] = None,
|
forged_timestamp: Optional[float] = None,
|
||||||
|
@ -1896,7 +1883,7 @@ def internal_prep_stream_message(
|
||||||
*,
|
*,
|
||||||
email_gateway: bool = False,
|
email_gateway: bool = False,
|
||||||
message_type: int = Message.MessageType.NORMAL,
|
message_type: int = Message.MessageType.NORMAL,
|
||||||
limit_unread_user_ids: Optional[Set[int]] = None,
|
limit_unread_user_ids: Optional[set[int]] = None,
|
||||||
forged: bool = False,
|
forged: bool = False,
|
||||||
forged_timestamp: Optional[float] = None,
|
forged_timestamp: Optional[float] = None,
|
||||||
) -> Optional[SendMessageRequest]:
|
) -> Optional[SendMessageRequest]:
|
||||||
|
@ -1993,7 +1980,7 @@ def internal_send_stream_message(
|
||||||
*,
|
*,
|
||||||
email_gateway: bool = False,
|
email_gateway: bool = False,
|
||||||
message_type: int = Message.MessageType.NORMAL,
|
message_type: int = Message.MessageType.NORMAL,
|
||||||
limit_unread_user_ids: Optional[Set[int]] = None,
|
limit_unread_user_ids: Optional[set[int]] = None,
|
||||||
) -> Optional[int]:
|
) -> Optional[int]:
|
||||||
message = internal_prep_stream_message(
|
message = internal_prep_stream_message(
|
||||||
sender,
|
sender,
|
||||||
|
@ -2038,8 +2025,8 @@ def internal_prep_group_direct_message(
|
||||||
sender: UserProfile,
|
sender: UserProfile,
|
||||||
content: str,
|
content: str,
|
||||||
*,
|
*,
|
||||||
emails: Optional[List[str]] = None,
|
emails: Optional[list[str]] = None,
|
||||||
recipient_users: Optional[List[UserProfile]] = None,
|
recipient_users: Optional[list[UserProfile]] = None,
|
||||||
) -> Optional[SendMessageRequest]:
|
) -> Optional[SendMessageRequest]:
|
||||||
if recipient_users is not None:
|
if recipient_users is not None:
|
||||||
addressee = Addressee.for_user_profiles(recipient_users)
|
addressee = Addressee.for_user_profiles(recipient_users)
|
||||||
|
@ -2060,8 +2047,8 @@ def internal_send_group_direct_message(
|
||||||
sender: UserProfile,
|
sender: UserProfile,
|
||||||
content: str,
|
content: str,
|
||||||
*,
|
*,
|
||||||
emails: Optional[List[str]] = None,
|
emails: Optional[list[str]] = None,
|
||||||
recipient_users: Optional[List[UserProfile]] = None,
|
recipient_users: Optional[list[UserProfile]] = None,
|
||||||
) -> Optional[int]:
|
) -> Optional[int]:
|
||||||
message = internal_prep_group_direct_message(
|
message = internal_prep_group_direct_message(
|
||||||
realm, sender, content, emails=emails, recipient_users=recipient_users
|
realm, sender, content, emails=emails, recipient_users=recipient_users
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from zerver.actions.user_topics import do_set_user_topic_visibility_policy
|
from zerver.actions.user_topics import do_set_user_topic_visibility_policy
|
||||||
from zerver.lib.emoji import check_emoji_request, get_emoji_data
|
from zerver.lib.emoji import check_emoji_request, get_emoji_data
|
||||||
|
@ -26,7 +26,7 @@ def notify_reaction_update(
|
||||||
"full_name": user_profile.full_name,
|
"full_name": user_profile.full_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
event: Dict[str, Any] = {
|
event: dict[str, Any] = {
|
||||||
"type": "reaction",
|
"type": "reaction",
|
||||||
"op": op,
|
"op": op,
|
||||||
"user_id": user_profile.id,
|
"user_id": user_profile.id,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import IO, Dict, Optional
|
from typing import IO, Optional
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
@ -17,7 +17,7 @@ from zerver.models.users import active_user_ids
|
||||||
from zerver.tornado.django_api import send_event_on_commit
|
from zerver.tornado.django_api import send_event_on_commit
|
||||||
|
|
||||||
|
|
||||||
def notify_realm_emoji(realm: Realm, realm_emoji: Dict[str, EmojiInfo]) -> None:
|
def notify_realm_emoji(realm: Realm, realm_emoji: dict[str, EmojiInfo]) -> None:
|
||||||
event = dict(type="realm_emoji", op="update", realm_emoji=realm_emoji)
|
event = dict(type="realm_emoji", op="update", realm_emoji=realm_emoji)
|
||||||
send_event_on_commit(realm, event, active_user_ids(realm.id))
|
send_event_on_commit(realm, event, active_user_ids(realm.id))
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Dict, List, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Max
|
from django.db.models import Max
|
||||||
|
@ -13,8 +13,8 @@ from zerver.models.users import active_user_ids
|
||||||
from zerver.tornado.django_api import send_event_on_commit
|
from zerver.tornado.django_api import send_event_on_commit
|
||||||
|
|
||||||
|
|
||||||
def notify_linkifiers(realm: Realm, realm_linkifiers: List[LinkifierDict]) -> None:
|
def notify_linkifiers(realm: Realm, realm_linkifiers: list[LinkifierDict]) -> None:
|
||||||
event: Dict[str, object] = dict(type="realm_linkifiers", realm_linkifiers=realm_linkifiers)
|
event: dict[str, object] = dict(type="realm_linkifiers", realm_linkifiers=realm_linkifiers)
|
||||||
send_event_on_commit(realm, event, active_user_ids(realm.id))
|
send_event_on_commit(realm, event, active_user_ids(realm.id))
|
||||||
|
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ def do_update_linkifier(
|
||||||
|
|
||||||
@transaction.atomic(durable=True)
|
@transaction.atomic(durable=True)
|
||||||
def check_reorder_linkifiers(
|
def check_reorder_linkifiers(
|
||||||
realm: Realm, ordered_linkifier_ids: List[int], *, acting_user: Optional[UserProfile]
|
realm: Realm, ordered_linkifier_ids: list[int], *, acting_user: Optional[UserProfile]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""ordered_linkifier_ids should contain ids of all existing linkifiers.
|
"""ordered_linkifier_ids should contain ids of all existing linkifiers.
|
||||||
In the rare situation when any of the linkifier gets deleted that more ids
|
In the rare situation when any of the linkifier gets deleted that more ids
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue