From c2c96eb0cf3b9d9ba0452eb74ab71248f461b1cf Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Wed, 2 Aug 2023 14:53:10 -0700 Subject: [PATCH] python: Annotate type aliases with TypeAlias. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is not strictly necessary but it’s clearer and improves mypy’s error messages. https://docs.python.org/3/library/typing.html#typing.TypeAlias https://mypy.readthedocs.io/en/stable/kinds_of_types.html#type-aliases Signed-off-by: Anders Kaseorg --- analytics/lib/counts.py | 3 ++- .../commands/populate_analytics_db.py | 3 ++- analytics/views/stats.py | 3 ++- confirmation/models.py | 3 ++- zerver/actions/streams.py | 7 +++-- zerver/data_import/gitter.py | 3 ++- zerver/data_import/import_util.py | 3 ++- zerver/data_import/slack.py | 11 ++++---- .../data_import/slack_message_conversion.py | 8 +++--- zerver/lib/db.py | 5 ++-- zerver/lib/digest.py | 3 ++- zerver/lib/export.py | 15 ++++++----- zerver/lib/integrations.py | 3 ++- zerver/lib/markdown/__init__.py | 3 ++- zerver/lib/narrow.py | 5 ++-- zerver/lib/push_notifications.py | 3 ++- zerver/lib/queue.py | 3 ++- zerver/lib/test_runner.py | 5 ++-- zerver/lib/types.py | 26 +++++++++++-------- zerver/tests/test_queue_worker.py | 3 ++- zerver/views/auth.py | 4 +-- zerver/views/events_register.py | 3 ++- zerver/webhooks/pagerduty/view.py | 3 ++- zerver/webhooks/taiga/view.py | 5 ++-- 24 files changed, 81 insertions(+), 52 deletions(-) diff --git a/analytics/lib/counts.py b/analytics/lib/counts.py index f4b28668e3..1fdd4bb47d 100644 --- a/analytics/lib/counts.py +++ b/analytics/lib/counts.py @@ -8,6 +8,7 @@ from django.conf import settings from django.db import connection, models from django.db.models import F from psycopg2.sql import SQL, Composable, Identifier, Literal +from typing_extensions import TypeAlias from analytics.models import ( BaseCount, @@ -346,7 +347,7 @@ def do_drop_single_stat(property: str) -> None: ## DataCollector-level operations ## -QueryFn = Callable[[Dict[str, Composable]], Composable] +QueryFn: TypeAlias = Callable[[Dict[str, Composable]], Composable] def do_pull_by_sql_query( diff --git a/analytics/management/commands/populate_analytics_db.py b/analytics/management/commands/populate_analytics_db.py index 3abc148e07..ea3d00e211 100644 --- a/analytics/management/commands/populate_analytics_db.py +++ b/analytics/management/commands/populate_analytics_db.py @@ -5,6 +5,7 @@ from typing import Any, Dict, List, Mapping, Type, Union from django.core.files.uploadedfile import UploadedFile from django.core.management.base import BaseCommand from django.utils.timezone import now as timezone_now +from typing_extensions import TypeAlias from analytics.lib.counts import COUNT_STATS, CountStat, do_drop_all_analytics_tables from analytics.lib.fixtures import generate_time_series_data @@ -147,7 +148,7 @@ class Command(BaseCommand): with open(IMAGE_FILE_PATH, "rb") as fp: upload_message_attachment_from_request(UploadedFile(fp), shylock, file_size) - FixtureData = Mapping[Union[str, int, None], List[int]] + FixtureData: TypeAlias = Mapping[Union[str, int, None], List[int]] def insert_fixture_data( stat: CountStat, diff --git a/analytics/views/stats.py b/analytics/views/stats.py index 963358e262..a2679bed67 100644 --- a/analytics/views/stats.py +++ b/analytics/views/stats.py @@ -10,6 +10,7 @@ from django.shortcuts import render from django.utils import translation from django.utils.timezone import now as timezone_now from django.utils.translation import gettext as _ +from typing_extensions import TypeAlias from analytics.lib.counts import COUNT_STATS, CountStat from analytics.lib.time_utils import time_range @@ -242,7 +243,7 @@ def get_chart_data( remote_realm_id: Optional[int] = None, server: Optional["RemoteZulipServer"] = None, ) -> HttpResponse: - TableType = Union[ + TableType: TypeAlias = Union[ Type["RemoteInstallationCount"], Type[InstallationCount], Type["RemoteRealmCount"], diff --git a/confirmation/models.py b/confirmation/models.py index 46d4332b1e..57347086f7 100644 --- a/confirmation/models.py +++ b/confirmation/models.py @@ -16,6 +16,7 @@ from django.http import HttpRequest, HttpResponse from django.template.response import TemplateResponse from django.urls import reverse from django.utils.timezone import now as timezone_now +from typing_extensions import TypeAlias from confirmation import settings as confirmation_settings from zerver.lib.types import UnspecifiedValue @@ -55,7 +56,7 @@ def generate_key() -> str: return b32encode(secrets.token_bytes(15)).decode().lower() -ConfirmationObjT = Union[ +ConfirmationObjT: TypeAlias = Union[ MultiuseInvite, PreregistrationRealm, PreregistrationUser, diff --git a/zerver/actions/streams.py b/zerver/actions/streams.py index 55ddc63fa2..9f6e126e68 100644 --- a/zerver/actions/streams.py +++ b/zerver/actions/streams.py @@ -11,6 +11,7 @@ from django.utils.timezone import now as timezone_now from django.utils.translation import gettext as _ from django.utils.translation import override as override_language from django_stubs_ext import ValuesQuerySet +from typing_extensions import TypeAlias from zerver.actions.default_streams import ( do_remove_default_stream, @@ -560,7 +561,7 @@ def send_peer_subscriber_events( send_event_on_commit(realm, event, peer_user_ids) -SubT = Tuple[List[SubInfo], List[SubInfo]] +SubT: TypeAlias = Tuple[List[SubInfo], List[SubInfo]] def bulk_add_subscriptions( @@ -729,7 +730,9 @@ def notify_subscriptions_removed( send_event(realm, event, [user_profile.id]) -SubAndRemovedT = Tuple[List[Tuple[UserProfile, Stream]], List[Tuple[UserProfile, Stream]]] +SubAndRemovedT: TypeAlias = Tuple[ + List[Tuple[UserProfile, Stream]], List[Tuple[UserProfile, Stream]] +] def send_subscription_remove_events( diff --git a/zerver/data_import/gitter.py b/zerver/data_import/gitter.py index c73f8e08b5..f827a894e9 100644 --- a/zerver/data_import/gitter.py +++ b/zerver/data_import/gitter.py @@ -7,6 +7,7 @@ import orjson from django.conf import settings from django.forms.models import model_to_dict from django.utils.timezone import now as timezone_now +from typing_extensions import TypeAlias from zerver.data_import.import_util import ( ZerverFieldsT, @@ -29,7 +30,7 @@ from zerver.models import Recipient, UserProfile from zproject.backends import GitHubAuthBackend # stubs -GitterDataT = List[Dict[str, Any]] +GitterDataT: TypeAlias = List[Dict[str, Any]] realm_id = 0 diff --git a/zerver/data_import/import_util.py b/zerver/data_import/import_util.py index a673a4fd22..e64cbeff75 100644 --- a/zerver/data_import/import_util.py +++ b/zerver/data_import/import_util.py @@ -25,6 +25,7 @@ import orjson import requests from django.forms.models import model_to_dict from django.utils.timezone import now as timezone_now +from typing_extensions import TypeAlias from zerver.data_import.sequencer import NEXT_ID from zerver.lib.avatar_hash import user_avatar_path_from_ids @@ -43,7 +44,7 @@ from zerver.models import ( from zproject.backends import all_implemented_backend_names # stubs -ZerverFieldsT = Dict[str, Any] +ZerverFieldsT: TypeAlias = Dict[str, Any] class SubscriberHandler: diff --git a/zerver/data_import/slack.py b/zerver/data_import/slack.py index e22d2ca21d..ba80420558 100644 --- a/zerver/data_import/slack.py +++ b/zerver/data_import/slack.py @@ -16,6 +16,7 @@ import requests from django.conf import settings from django.forms.models import model_to_dict from django.utils.timezone import now as timezone_now +from typing_extensions import TypeAlias from zerver.data_import.import_util import ( ZerverFieldsT, @@ -56,11 +57,11 @@ from zerver.models import ( UserProfile, ) -SlackToZulipUserIDT = Dict[str, int] -AddedChannelsT = Dict[str, Tuple[str, int]] -AddedMPIMsT = Dict[str, Tuple[str, int]] -DMMembersT = Dict[str, Tuple[str, str]] -SlackToZulipRecipientT = Dict[str, int] +SlackToZulipUserIDT: TypeAlias = Dict[str, int] +AddedChannelsT: TypeAlias = Dict[str, Tuple[str, int]] +AddedMPIMsT: TypeAlias = Dict[str, Tuple[str, int]] +DMMembersT: TypeAlias = Dict[str, Tuple[str, str]] +SlackToZulipRecipientT: TypeAlias = Dict[str, int] # Generic type for SlackBotEmail class SlackBotEmailT = TypeVar("SlackBotEmailT", bound="SlackBotEmail") diff --git a/zerver/data_import/slack_message_conversion.py b/zerver/data_import/slack_message_conversion.py index 5727d6eb3e..e1c27a8e15 100644 --- a/zerver/data_import/slack_message_conversion.py +++ b/zerver/data_import/slack_message_conversion.py @@ -1,10 +1,12 @@ import re from typing import Any, Dict, List, Optional, Tuple +from typing_extensions import TypeAlias + # stubs -ZerverFieldsT = Dict[str, Any] -SlackToZulipUserIDT = Dict[str, int] -AddedChannelsT = Dict[str, Tuple[str, int]] +ZerverFieldsT: TypeAlias = Dict[str, Any] +SlackToZulipUserIDT: TypeAlias = Dict[str, int] +AddedChannelsT: TypeAlias = Dict[str, Tuple[str, int]] # Slack link can be in the format and LINK_REGEX = r""" diff --git a/zerver/lib/db.py b/zerver/lib/db.py index 10231aa9f5..b325bb8c37 100644 --- a/zerver/lib/db.py +++ b/zerver/lib/db.py @@ -3,10 +3,11 @@ from typing import Any, Callable, Dict, Iterable, List, Mapping, Sequence, TypeV from psycopg2.extensions import connection, cursor from psycopg2.sql import Composable +from typing_extensions import TypeAlias CursorObj = TypeVar("CursorObj", bound=cursor) -Query = Union[str, bytes, Composable] -Params = Union[Sequence[object], Mapping[str, object], None] +Query: TypeAlias = Union[str, bytes, Composable] +Params: TypeAlias = Union[Sequence[object], Mapping[str, object], None] ParamsT = TypeVar("ParamsT") diff --git a/zerver/lib/digest.py b/zerver/lib/digest.py index f5a6e06684..0c6a824c1c 100644 --- a/zerver/lib/digest.py +++ b/zerver/lib/digest.py @@ -7,6 +7,7 @@ from typing import Any, Collection, Dict, List, Set, Tuple from django.conf import settings from django.db import transaction from django.utils.timezone import now as timezone_now +from typing_extensions import TypeAlias from confirmation.models import one_click_unsubscribe_link from zerver.context_processors import common_context @@ -34,7 +35,7 @@ log_to_file(logger, settings.DIGEST_LOG_PATH) DIGEST_CUTOFF = 5 MAX_HOT_TOPICS_TO_BE_INCLUDED_IN_DIGEST = 4 -TopicKey = Tuple[int, str] +TopicKey: TypeAlias = Tuple[int, str] class DigestTopic: diff --git a/zerver/lib/export.py b/zerver/lib/export.py index 84e918f590..f688b28d2c 100644 --- a/zerver/lib/export.py +++ b/zerver/lib/export.py @@ -24,6 +24,7 @@ from django.db.models import Exists, OuterRef, Q from django.forms.models import model_to_dict from django.utils.timezone import is_naive as timezone_is_naive from mypy_boto3_s3.service_resource import Object +from typing_extensions import TypeAlias import zerver.lib.upload from analytics.models import RealmCount, StreamCount, UserCount @@ -74,17 +75,17 @@ from zerver.models import ( ) # Custom mypy types follow: -Record = Dict[str, Any] +Record: TypeAlias = Dict[str, Any] TableName = str -TableData = Dict[TableName, List[Record]] +TableData: TypeAlias = Dict[TableName, List[Record]] Field = str Path = str -Context = Dict[str, Any] -FilterArgs = Dict[str, Any] -IdSource = Tuple[TableName, Field] -SourceFilter = Callable[[Record], bool] +Context: TypeAlias = Dict[str, Any] +FilterArgs: TypeAlias = Dict[str, Any] +IdSource: TypeAlias = Tuple[TableName, Field] +SourceFilter: TypeAlias = Callable[[Record], bool] -CustomFetch = Callable[[TableData, Context], None] +CustomFetch: TypeAlias = Callable[[TableData, Context], None] class MessagePartial(TypedDict): diff --git a/zerver/lib/integrations.py b/zerver/lib/integrations.py index 14c9c304ca..ed66986f19 100644 --- a/zerver/lib/integrations.py +++ b/zerver/lib/integrations.py @@ -7,6 +7,7 @@ from django.urls import URLResolver, path from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy from django_stubs_ext import StrPromise +from typing_extensions import TypeAlias from zerver.lib.storage import static_path @@ -31,7 +32,7 @@ Over time, we expect this registry to grow additional convenience features for writing and configuring integrations efficiently. """ -OptionValidator = Callable[[str, str], Optional[str]] +OptionValidator: TypeAlias = Callable[[str, str], Optional[str]] META_CATEGORY: Dict[str, StrPromise] = { "meta-integration": gettext_lazy("Integration frameworks"), diff --git a/zerver/lib/markdown/__init__.py b/zerver/lib/markdown/__init__.py index 69b475e9b9..2795f33848 100644 --- a/zerver/lib/markdown/__init__.py +++ b/zerver/lib/markdown/__init__.py @@ -47,6 +47,7 @@ from markdown.blockparser import BlockParser from markdown.extensions import codehilite, nl2br, sane_lists, tables from soupsieve import escape as css_escape from tlds import tld_set +from typing_extensions import TypeAlias from zerver.lib import mention from zerver.lib.cache import cache_with_key @@ -148,7 +149,7 @@ class DbData: version = 1 _T = TypeVar("_T") -ElementStringNone = Union[Element, Optional[str]] +ElementStringNone: TypeAlias = Union[Element, Optional[str]] EMOJI_REGEX = r"(?P:[\w\-\+]+:)" diff --git a/zerver/lib/narrow.py b/zerver/lib/narrow.py index c1b74e5046..24e9624aac 100644 --- a/zerver/lib/narrow.py +++ b/zerver/lib/narrow.py @@ -43,6 +43,7 @@ from sqlalchemy.sql import ( ) from sqlalchemy.sql.selectable import SelectBase from sqlalchemy.types import ARRAY, Boolean, Integer, Text +from typing_extensions import TypeAlias from zerver.lib.addressee import get_user_profiles, get_user_profiles_by_ids from zerver.lib.exceptions import ErrorCode, JsonableError @@ -207,9 +208,9 @@ class BadNarrowOperatorError(JsonableError): return _("Invalid narrow operator: {desc}") -ConditionTransform = Callable[[ClauseElement], ClauseElement] +ConditionTransform: TypeAlias = Callable[[ClauseElement], ClauseElement] -OptionalNarrowListT = Optional[List[Dict[str, Any]]] +OptionalNarrowListT: TypeAlias = Optional[List[Dict[str, Any]]] # These delimiters will not appear in rendered messages or HTML-escaped topics. TS_START = "" diff --git a/zerver/lib/push_notifications.py b/zerver/lib/push_notifications.py index 604b3d3ed5..4f0c3589b4 100644 --- a/zerver/lib/push_notifications.py +++ b/zerver/lib/push_notifications.py @@ -17,6 +17,7 @@ from django.db.models import F, Q from django.utils.timezone import now as timezone_now from django.utils.translation import gettext as _ from django.utils.translation import override as override_language +from typing_extensions import TypeAlias from zerver.lib.avatar import absolute_avatar_url from zerver.lib.exceptions import JsonableError @@ -47,7 +48,7 @@ logger = logging.getLogger(__name__) if settings.ZILENCER_ENABLED: from zilencer.models import RemotePushDeviceToken, RemoteZulipServer -DeviceToken = Union[PushDeviceToken, "RemotePushDeviceToken"] +DeviceToken: TypeAlias = Union[PushDeviceToken, "RemotePushDeviceToken"] # We store the token as b64, but apns-client wants hex strings diff --git a/zerver/lib/queue.py b/zerver/lib/queue.py index 716b056353..aa6ac5b35c 100644 --- a/zerver/lib/queue.py +++ b/zerver/lib/queue.py @@ -17,12 +17,13 @@ from pika.adapters.blocking_connection import BlockingChannel from pika.channel import Channel from pika.spec import Basic from tornado import ioloop +from typing_extensions import TypeAlias from zerver.lib.utils import assert_is_not_none MAX_REQUEST_RETRIES = 3 ChannelT = TypeVar("ChannelT", Channel, BlockingChannel) -Consumer = Callable[[ChannelT, Basic.Deliver, pika.BasicProperties, bytes], None] +Consumer: TypeAlias = Callable[[ChannelT, Basic.Deliver, pika.BasicProperties, bytes], None] # This simple queuing library doesn't expose much of the power of diff --git a/zerver/lib/test_runner.py b/zerver/lib/test_runner.py index 3de9100658..64e43a5b22 100644 --- a/zerver/lib/test_runner.py +++ b/zerver/lib/test_runner.py @@ -14,6 +14,7 @@ from django.db import ProgrammingError, connections from django.test import runner as django_runner from django.test.runner import DiscoverRunner from django.test.signals import template_rendered +from typing_extensions import TypeAlias from scripts.lib.zulip_tools import ( TEMPLATE_DATABASE_DIR, @@ -103,8 +104,8 @@ def process_instrumented_calls(func: Callable[[Dict[str, Any]], None]) -> None: func(call) -SerializedSubsuite = Tuple[Type[TestSuite], List[str]] -SubsuiteArgs = Tuple[Type["RemoteTestRunner"], int, SerializedSubsuite, bool, bool] +SerializedSubsuite: TypeAlias = Tuple[Type[TestSuite], List[str]] +SubsuiteArgs: TypeAlias = Tuple[Type["RemoteTestRunner"], int, SerializedSubsuite, bool, bool] def run_subsuite(args: SubsuiteArgs) -> Tuple[int, Any]: diff --git a/zerver/lib/types.py b/zerver/lib/types.py index 5dd2d0c1d1..b1f804b3ea 100644 --- a/zerver/lib/types.py +++ b/zerver/lib/types.py @@ -3,17 +3,17 @@ from dataclasses import dataclass from typing import Any, Callable, Dict, List, Optional, Tuple, TypedDict, TypeVar, Union from django_stubs_ext import StrPromise -from typing_extensions import NotRequired +from typing_extensions import NotRequired, TypeAlias # See zerver/lib/validator.py for more details of Validators, # including many examples ResultT = TypeVar("ResultT") -Validator = Callable[[str, object], ResultT] -ExtendedValidator = Callable[[str, str, object], str] -RealmUserValidator = Callable[[int, object, bool], List[int]] +Validator: TypeAlias = Callable[[str, object], ResultT] +ExtendedValidator: TypeAlias = Callable[[str, str, object], str] +RealmUserValidator: TypeAlias = Callable[[int, object, bool], List[int]] -ProfileDataElementValue = Union[str, List[int]] +ProfileDataElementValue: TypeAlias = Union[str, List[int]] class ProfileDataElementBase(TypedDict, total=False): @@ -36,13 +36,17 @@ class ProfileDataElementUpdateDict(TypedDict): value: ProfileDataElementValue -ProfileData = List[ProfileDataElement] +ProfileData: TypeAlias = List[ProfileDataElement] -FieldElement = Tuple[int, StrPromise, Validator[ProfileDataElementValue], Callable[[Any], Any], str] -ExtendedFieldElement = Tuple[int, StrPromise, ExtendedValidator, Callable[[Any], Any], str] -UserFieldElement = Tuple[int, StrPromise, RealmUserValidator, Callable[[Any], Any], str] +FieldElement: TypeAlias = Tuple[ + int, StrPromise, Validator[ProfileDataElementValue], Callable[[Any], Any], str +] +ExtendedFieldElement: TypeAlias = Tuple[ + int, StrPromise, ExtendedValidator, Callable[[Any], Any], str +] +UserFieldElement: TypeAlias = Tuple[int, StrPromise, RealmUserValidator, Callable[[Any], Any], str] -ProfileFieldData = Dict[str, Union[Dict[str, str], str]] +ProfileFieldData: TypeAlias = Dict[str, Union[Dict[str, str], str]] class UserDisplayRecipient(TypedDict): @@ -52,7 +56,7 @@ class UserDisplayRecipient(TypedDict): is_mirror_dummy: bool -DisplayRecipientT = Union[str, List[UserDisplayRecipient]] +DisplayRecipientT: TypeAlias = Union[str, List[UserDisplayRecipient]] class LinkifierDict(TypedDict): diff --git a/zerver/tests/test_queue_worker.py b/zerver/tests/test_queue_worker.py index 8e0932b303..11783ee49a 100644 --- a/zerver/tests/test_queue_worker.py +++ b/zerver/tests/test_queue_worker.py @@ -15,6 +15,7 @@ import time_machine from django.conf import settings from django.db.utils import IntegrityError from django.test import override_settings +from typing_extensions import TypeAlias from zerver.lib.email_mirror import RateLimitedRealmMirror from zerver.lib.email_mirror_helpers import encode_email_address @@ -43,7 +44,7 @@ from zerver.worker.queue_processors import ( get_active_worker_queues, ) -Event = Dict[str, Any] +Event: TypeAlias = Dict[str, Any] class FakeClient: diff --git a/zerver/views/auth.py b/zerver/views/auth.py index 91cb3f74c7..40a4132012 100644 --- a/zerver/views/auth.py +++ b/zerver/views/auth.py @@ -27,7 +27,7 @@ from django.views.decorators.http import require_safe from social_django.utils import load_backend, load_strategy from two_factor.forms import BackupTokenForm from two_factor.views import LoginView as BaseTwoFactorLoginView -from typing_extensions import Concatenate, ParamSpec +from typing_extensions import Concatenate, ParamSpec, TypeAlias from confirmation.models import ( Confirmation, @@ -103,7 +103,7 @@ if TYPE_CHECKING: from django.http.request import _ImmutableQueryDict ParamT = ParamSpec("ParamT") -ExtraContext = Optional[Dict[str, Any]] +ExtraContext: TypeAlias = Optional[Dict[str, Any]] EXPIRABLE_SESSION_VAR_DEFAULT_EXPIRY_SECS = 3600 diff --git a/zerver/views/events_register.py b/zerver/views/events_register.py index 4f703bf4f3..bf0d66911b 100644 --- a/zerver/views/events_register.py +++ b/zerver/views/events_register.py @@ -4,6 +4,7 @@ from django.conf import settings from django.contrib.auth.models import AnonymousUser from django.http import HttpRequest, HttpResponse from django.utils.translation import gettext as _ +from typing_extensions import TypeAlias from zerver.context_processors import get_valid_realm_from_request from zerver.lib.compatibility import is_pronouns_field_type_supported @@ -34,7 +35,7 @@ def _default_narrow( return narrow -NarrowT = Sequence[Sequence[str]] +NarrowT: TypeAlias = Sequence[Sequence[str]] @has_request_variables diff --git a/zerver/webhooks/pagerduty/view.py b/zerver/webhooks/pagerduty/view.py index cdc522736d..1a54a444c1 100644 --- a/zerver/webhooks/pagerduty/view.py +++ b/zerver/webhooks/pagerduty/view.py @@ -2,6 +2,7 @@ from email.headerregistry import Address from typing import Dict, Union from django.http import HttpRequest, HttpResponse +from typing_extensions import TypeAlias from zerver.decorator import webhook_view from zerver.lib.exceptions import UnsupportedWebhookEventTypeError @@ -11,7 +12,7 @@ from zerver.lib.validator import WildValue, check_int, check_none_or, check_stri from zerver.lib.webhooks.common import check_send_webhook_message from zerver.models import UserProfile -FormatDictType = Dict[str, Union[str, int]] +FormatDictType: TypeAlias = Dict[str, Union[str, int]] PAGER_DUTY_EVENT_NAMES = { "incident.trigger": "triggered", diff --git a/zerver/webhooks/taiga/view.py b/zerver/webhooks/taiga/view.py index f1a05d0e49..ca3c337a7d 100644 --- a/zerver/webhooks/taiga/view.py +++ b/zerver/webhooks/taiga/view.py @@ -9,6 +9,7 @@ should be in bold. from typing import Dict, List, Optional, Tuple, Union from django.http import HttpRequest, HttpResponse +from typing_extensions import TypeAlias from zerver.decorator import webhook_view from zerver.lib.request import REQ, has_request_variables @@ -17,8 +18,8 @@ from zerver.lib.validator import WildValue, check_bool, check_none_or, check_str from zerver.lib.webhooks.common import check_send_webhook_message from zerver.models import UserProfile -EventType = Dict[str, Union[str, Dict[str, Optional[Union[str, bool]]]]] -ReturnType = Tuple[WildValue, WildValue] +EventType: TypeAlias = Dict[str, Union[str, Dict[str, Optional[Union[str, bool]]]]] +ReturnType: TypeAlias = Tuple[WildValue, WildValue] @webhook_view("Taiga")