mirror of https://github.com/zulip/zulip.git
events: Migrate to typed_endpoint.
Migrate `event_register.py` and `tornado` to typed_endpoint. Modified the tests to work with the migrated endpoints.
This commit is contained in:
parent
4e63daf2ce
commit
97f15d8811
|
@ -3,11 +3,12 @@
|
||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from collections.abc import Callable, Collection, Iterable, Mapping, Sequence
|
from collections.abc import Callable, Collection, Iterable, Sequence
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
from typing_extensions import NotRequired, TypedDict
|
||||||
|
|
||||||
from version import API_FEATURE_LEVEL, ZULIP_MERGE_BASE, ZULIP_VERSION
|
from version import API_FEATURE_LEVEL, ZULIP_MERGE_BASE, ZULIP_VERSION
|
||||||
from zerver.actions.default_streams import default_stream_groups_to_dicts_sorted
|
from zerver.actions.default_streams import default_stream_groups_to_dicts_sorted
|
||||||
|
@ -1639,6 +1640,20 @@ def apply_event(
|
||||||
raise AssertionError("Unexpected event type {}".format(event["type"]))
|
raise AssertionError("Unexpected event type {}".format(event["type"]))
|
||||||
|
|
||||||
|
|
||||||
|
class ClientCapabilities(TypedDict):
|
||||||
|
# This field was accidentally made required when it was added in v2.0.0-781;
|
||||||
|
# this was not realized until after the release of Zulip 2.1.2. (It remains
|
||||||
|
# required to help ensure backwards compatibility of client code.)
|
||||||
|
notification_settings_null: bool
|
||||||
|
# Any new fields of `client_capabilities` should be optional. Add them here.
|
||||||
|
bulk_message_deletion: NotRequired[bool]
|
||||||
|
user_avatar_url_field_optional: NotRequired[bool]
|
||||||
|
stream_typing_notifications: NotRequired[bool]
|
||||||
|
user_settings_object: NotRequired[bool]
|
||||||
|
linkifier_url_template: NotRequired[bool]
|
||||||
|
user_list_incomplete: NotRequired[bool]
|
||||||
|
|
||||||
|
|
||||||
def do_events_register(
|
def do_events_register(
|
||||||
user_profile: UserProfile | None,
|
user_profile: UserProfile | None,
|
||||||
realm: Realm,
|
realm: Realm,
|
||||||
|
@ -1652,7 +1667,7 @@ def do_events_register(
|
||||||
all_public_streams: bool = False,
|
all_public_streams: bool = False,
|
||||||
include_subscribers: bool = True,
|
include_subscribers: bool = True,
|
||||||
include_streams: bool = True,
|
include_streams: bool = True,
|
||||||
client_capabilities: Mapping[str, bool] = {},
|
client_capabilities: ClientCapabilities = ClientCapabilities(notification_settings_null=False),
|
||||||
narrow: Collection[NarrowTerm] = [],
|
narrow: Collection[NarrowTerm] = [],
|
||||||
fetch_event_types: Collection[str] | None = None,
|
fetch_event_types: Collection[str] | None = None,
|
||||||
spectator_requested_language: str | None = None,
|
spectator_requested_language: str | None = None,
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.utils import translation
|
||||||
from two_factor.utils import default_device
|
from two_factor.utils import default_device
|
||||||
|
|
||||||
from zerver.context_processors import get_apps_page_url
|
from zerver.context_processors import get_apps_page_url
|
||||||
from zerver.lib.events import do_events_register
|
from zerver.lib.events import ClientCapabilities, do_events_register
|
||||||
from zerver.lib.i18n import (
|
from zerver.lib.i18n import (
|
||||||
get_and_set_request_language,
|
get_and_set_request_language,
|
||||||
get_language_list,
|
get_language_list,
|
||||||
|
@ -147,15 +147,16 @@ def build_page_params_for_home_page_load(
|
||||||
|
|
||||||
The page_params data structure gets sent to the client.
|
The page_params data structure gets sent to the client.
|
||||||
"""
|
"""
|
||||||
client_capabilities = {
|
|
||||||
"notification_settings_null": True,
|
client_capabilities = ClientCapabilities(
|
||||||
"bulk_message_deletion": True,
|
notification_settings_null=True,
|
||||||
"user_avatar_url_field_optional": True,
|
bulk_message_deletion=True,
|
||||||
"stream_typing_notifications": True,
|
user_avatar_url_field_optional=True,
|
||||||
"user_settings_object": True,
|
stream_typing_notifications=True,
|
||||||
"linkifier_url_template": True,
|
user_settings_object=True,
|
||||||
"user_list_incomplete": True,
|
linkifier_url_template=True,
|
||||||
}
|
user_list_incomplete=True,
|
||||||
|
)
|
||||||
|
|
||||||
if user_profile is not None:
|
if user_profile is not None:
|
||||||
client = RequestNotes.get_notes(request).client
|
client = RequestNotes.get_notes(request).client
|
||||||
|
|
|
@ -155,7 +155,9 @@ class MissedMessageHookTest(ZulipTestCase):
|
||||||
return access_client_descriptor(user.id, queue_id)
|
return access_client_descriptor(user.id, queue_id)
|
||||||
|
|
||||||
def destroy_event_queue(self, user: UserProfile, queue_id: str) -> None:
|
def destroy_event_queue(self, user: UserProfile, queue_id: str) -> None:
|
||||||
result = self.tornado_call(cleanup_event_queue, user, {"queue_id": queue_id})
|
# Ignore this when running mypy, since mypy expects queue_id to be specified when calling the function
|
||||||
|
# but it is actually extracted from the request body using the typed_endpoint decorator.
|
||||||
|
result = self.tornado_call(cleanup_event_queue, user, {"queue_id": queue_id}) # type: ignore[arg-type]
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
|
||||||
def assert_maybe_enqueue_notifications_call_args(
|
def assert_maybe_enqueue_notifications_call_args(
|
||||||
|
|
|
@ -318,7 +318,8 @@ so maybe we shouldn't mark it as intentionally undocumented in the URLs.
|
||||||
val = 6
|
val = 6
|
||||||
for t in types:
|
for t in types:
|
||||||
if isinstance(t, tuple):
|
if isinstance(t, tuple):
|
||||||
return t # e.g. (list, dict) or (list, str)
|
# e.g. (list, dict) or (list, str)
|
||||||
|
return t # nocoverage
|
||||||
v = priority.get(t, 6)
|
v = priority.get(t, 6)
|
||||||
if v < val:
|
if v < val:
|
||||||
val = v
|
val = v
|
||||||
|
@ -343,8 +344,6 @@ so maybe we shouldn't mark it as intentionally undocumented in the URLs.
|
||||||
elif origin in [list, abc.Sequence]:
|
elif origin in [list, abc.Sequence]:
|
||||||
[st] = get_args(t)
|
[st] = get_args(t)
|
||||||
return (list, self.get_standardized_argument_type(st))
|
return (list, self.get_standardized_argument_type(st))
|
||||||
elif origin in [dict, abc.Mapping]:
|
|
||||||
return dict
|
|
||||||
raise AssertionError(f"Unknown origin {origin}")
|
raise AssertionError(f"Unknown origin {origin}")
|
||||||
|
|
||||||
def render_openapi_type_exception(
|
def render_openapi_type_exception(
|
||||||
|
|
|
@ -1,29 +1,21 @@
|
||||||
import time
|
import time
|
||||||
from collections.abc import Callable, Mapping, Sequence
|
from collections.abc import Callable
|
||||||
from typing import Any, TypeVar
|
from typing import Annotated, Any, TypeVar
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from pydantic import Json
|
from pydantic import BaseModel, Json, NonNegativeInt, StringConstraints, model_validator
|
||||||
from typing_extensions import ParamSpec
|
from typing_extensions import ParamSpec
|
||||||
|
|
||||||
from zerver.decorator import internal_api_view, process_client
|
from zerver.decorator import internal_api_view, process_client
|
||||||
from zerver.lib.exceptions import JsonableError
|
from zerver.lib.exceptions import JsonableError
|
||||||
from zerver.lib.queue import get_queue_client
|
from zerver.lib.queue import get_queue_client
|
||||||
from zerver.lib.request import REQ, RequestNotes, has_request_variables
|
from zerver.lib.request import RequestNotes
|
||||||
from zerver.lib.response import AsynchronousResponse, json_success
|
from zerver.lib.response import AsynchronousResponse, json_success
|
||||||
from zerver.lib.typed_endpoint import typed_endpoint
|
from zerver.lib.typed_endpoint import ApiParamConfig, DocumentationStatus, typed_endpoint
|
||||||
from zerver.lib.validator import (
|
from zerver.models import UserProfile
|
||||||
check_bool,
|
|
||||||
check_dict,
|
|
||||||
check_int,
|
|
||||||
check_list,
|
|
||||||
check_string,
|
|
||||||
to_non_negative_int,
|
|
||||||
)
|
|
||||||
from zerver.models import Client, UserProfile
|
|
||||||
from zerver.models.clients import get_client
|
from zerver.models.clients import get_client
|
||||||
from zerver.models.users import get_user_profile_by_id
|
from zerver.models.users import get_user_profile_by_id
|
||||||
from zerver.tornado.descriptors import is_current_port
|
from zerver.tornado.descriptors import is_current_port
|
||||||
|
@ -47,10 +39,8 @@ def in_tornado_thread(f: Callable[P, T]) -> Callable[P, T]:
|
||||||
|
|
||||||
|
|
||||||
@internal_api_view(True)
|
@internal_api_view(True)
|
||||||
@has_request_variables
|
@typed_endpoint
|
||||||
def notify(
|
def notify(request: HttpRequest, *, data: Json[dict[str, Any]]) -> HttpResponse:
|
||||||
request: HttpRequest, data: Mapping[str, Any] = REQ(json_validator=check_dict([]))
|
|
||||||
) -> HttpResponse:
|
|
||||||
# Only the puppeteer full-stack tests use this endpoint; it
|
# Only the puppeteer full-stack tests use this endpoint; it
|
||||||
# injects an event, as if read from RabbitMQ.
|
# injects an event, as if read from RabbitMQ.
|
||||||
in_tornado_thread(process_notification)(data)
|
in_tornado_thread(process_notification)(data)
|
||||||
|
@ -77,9 +67,9 @@ def web_reload_clients(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@has_request_variables
|
@typed_endpoint
|
||||||
def cleanup_event_queue(
|
def cleanup_event_queue(
|
||||||
request: HttpRequest, user_profile: UserProfile, queue_id: str = REQ()
|
request: HttpRequest, user_profile: UserProfile, *, queue_id: str
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
log_data = RequestNotes.get_notes(request).log_data
|
log_data = RequestNotes.get_notes(request).log_data
|
||||||
assert log_data is not None
|
assert log_data is not None
|
||||||
|
@ -108,10 +98,8 @@ def cleanup_event_queue(
|
||||||
|
|
||||||
|
|
||||||
@internal_api_view(True)
|
@internal_api_view(True)
|
||||||
@has_request_variables
|
@typed_endpoint
|
||||||
def get_events_internal(
|
def get_events_internal(request: HttpRequest, *, user_profile_id: Json[int]) -> HttpResponse:
|
||||||
request: HttpRequest, user_profile_id: int = REQ(json_validator=check_int)
|
|
||||||
) -> HttpResponse:
|
|
||||||
user_profile = get_user_profile_by_id(user_profile_id)
|
user_profile = get_user_profile_by_id(user_profile_id)
|
||||||
RequestNotes.get_notes(request).requester_for_logs = user_profile.format_requester_for_logs()
|
RequestNotes.get_notes(request).requester_for_logs = user_profile.format_requester_for_logs()
|
||||||
assert is_current_port(get_user_tornado_port(user_profile))
|
assert is_current_port(get_user_tornado_port(user_profile))
|
||||||
|
@ -137,64 +125,90 @@ def get_events(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
||||||
return get_events_backend(request, user_profile)
|
return get_events_backend(request, user_profile)
|
||||||
|
|
||||||
|
|
||||||
@has_request_variables
|
class UserClient(BaseModel):
|
||||||
|
id: int
|
||||||
|
name: Annotated[str, StringConstraints(max_length=30)]
|
||||||
|
|
||||||
|
@model_validator(mode="before")
|
||||||
|
@classmethod
|
||||||
|
def convert_term(cls, elem: str) -> dict[str, Any]:
|
||||||
|
client = get_client(elem)
|
||||||
|
return {"id": client.id, "name": client.name}
|
||||||
|
|
||||||
|
|
||||||
|
@typed_endpoint
|
||||||
def get_events_backend(
|
def get_events_backend(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
user_profile: UserProfile,
|
user_profile: UserProfile,
|
||||||
|
*,
|
||||||
# user_client is intended only for internal Django=>Tornado requests
|
# user_client is intended only for internal Django=>Tornado requests
|
||||||
# and thus shouldn't be documented for external use.
|
# and thus shouldn't be documented for external use.
|
||||||
user_client: Client | None = REQ(
|
user_client: Annotated[
|
||||||
converter=lambda var_name, s: get_client(s), default=None, intentionally_undocumented=True
|
UserClient | None,
|
||||||
),
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
last_event_id: int | None = REQ(json_validator=check_int, default=None),
|
] = None,
|
||||||
queue_id: str | None = REQ(default=None),
|
last_event_id: Json[int] | None = None,
|
||||||
|
queue_id: str | None = None,
|
||||||
# apply_markdown, client_gravatar, all_public_streams, and various
|
# apply_markdown, client_gravatar, all_public_streams, and various
|
||||||
# other parameters are only used when registering a new queue via this
|
# other parameters are only used when registering a new queue via this
|
||||||
# endpoint. This is a feature used primarily by get_events_internal
|
# endpoint. This is a feature used primarily by get_events_internal
|
||||||
# and not expected to be used by third-party clients.
|
# and not expected to be used by third-party clients.
|
||||||
apply_markdown: bool = REQ(
|
apply_markdown: Annotated[
|
||||||
default=False, json_validator=check_bool, intentionally_undocumented=True
|
Json[bool],
|
||||||
),
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
client_gravatar: bool = REQ(
|
] = False,
|
||||||
default=False, json_validator=check_bool, intentionally_undocumented=True
|
client_gravatar: Annotated[
|
||||||
),
|
Json[bool],
|
||||||
slim_presence: bool = REQ(
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
default=False, json_validator=check_bool, intentionally_undocumented=True
|
] = False,
|
||||||
),
|
slim_presence: Annotated[
|
||||||
all_public_streams: bool = REQ(
|
Json[bool],
|
||||||
default=False, json_validator=check_bool, intentionally_undocumented=True
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
),
|
] = False,
|
||||||
event_types: Sequence[str] | None = REQ(
|
all_public_streams: Annotated[
|
||||||
default=None, json_validator=check_list(check_string), intentionally_undocumented=True
|
Json[bool],
|
||||||
),
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
dont_block: bool = REQ(default=False, json_validator=check_bool),
|
] = False,
|
||||||
narrow: Sequence[Sequence[str]] = REQ(
|
event_types: Annotated[
|
||||||
default=[],
|
Json[list[str]] | None,
|
||||||
json_validator=check_list(check_list(check_string)),
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
intentionally_undocumented=True,
|
] = None,
|
||||||
),
|
dont_block: Json[bool] = False,
|
||||||
lifespan_secs: int = REQ(
|
narrow: Annotated[
|
||||||
default=0, converter=to_non_negative_int, intentionally_undocumented=True
|
Json[list[list[str]]] | None,
|
||||||
),
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
bulk_message_deletion: bool = REQ(
|
] = None,
|
||||||
default=False, json_validator=check_bool, intentionally_undocumented=True
|
lifespan_secs: Annotated[
|
||||||
),
|
Json[NonNegativeInt],
|
||||||
stream_typing_notifications: bool = REQ(
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
default=False, json_validator=check_bool, intentionally_undocumented=True
|
] = 0,
|
||||||
),
|
bulk_message_deletion: Annotated[
|
||||||
user_settings_object: bool = REQ(
|
Json[bool],
|
||||||
default=False, json_validator=check_bool, intentionally_undocumented=True
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
),
|
] = False,
|
||||||
pronouns_field_type_supported: bool = REQ(
|
stream_typing_notifications: Annotated[
|
||||||
default=True, json_validator=check_bool, intentionally_undocumented=True
|
Json[bool],
|
||||||
),
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
linkifier_url_template: bool = REQ(
|
] = False,
|
||||||
default=False, json_validator=check_bool, intentionally_undocumented=True
|
user_settings_object: Annotated[
|
||||||
),
|
Json[bool],
|
||||||
user_list_incomplete: bool = REQ(
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
default=False, json_validator=check_bool, intentionally_undocumented=True
|
] = False,
|
||||||
),
|
pronouns_field_type_supported: Annotated[
|
||||||
|
Json[bool],
|
||||||
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
|
] = True,
|
||||||
|
linkifier_url_template: Annotated[
|
||||||
|
Json[bool],
|
||||||
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
|
] = False,
|
||||||
|
user_list_incomplete: Annotated[
|
||||||
|
Json[bool],
|
||||||
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
|
] = False,
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
|
if narrow is None:
|
||||||
|
narrow = []
|
||||||
if all_public_streams and not user_profile.can_access_public_streams():
|
if all_public_streams and not user_profile.can_access_public_streams():
|
||||||
raise JsonableError(_("User not authorized for this query"))
|
raise JsonableError(_("User not authorized for this query"))
|
||||||
|
|
||||||
|
@ -205,8 +219,9 @@ def get_events_backend(
|
||||||
if user_client is None:
|
if user_client is None:
|
||||||
valid_user_client = RequestNotes.get_notes(request).client
|
valid_user_client = RequestNotes.get_notes(request).client
|
||||||
assert valid_user_client is not None
|
assert valid_user_client is not None
|
||||||
|
valid_user_client_name = valid_user_client.name
|
||||||
else:
|
else:
|
||||||
valid_user_client = user_client
|
valid_user_client_name = user_client.name
|
||||||
|
|
||||||
new_queue_data = None
|
new_queue_data = None
|
||||||
if queue_id is None:
|
if queue_id is None:
|
||||||
|
@ -214,7 +229,7 @@ def get_events_backend(
|
||||||
user_profile_id=user_profile.id,
|
user_profile_id=user_profile.id,
|
||||||
realm_id=user_profile.realm_id,
|
realm_id=user_profile.realm_id,
|
||||||
event_types=event_types,
|
event_types=event_types,
|
||||||
client_type_name=valid_user_client.name,
|
client_type_name=valid_user_client_name,
|
||||||
apply_markdown=apply_markdown,
|
apply_markdown=apply_markdown,
|
||||||
client_gravatar=client_gravatar,
|
client_gravatar=client_gravatar,
|
||||||
slim_presence=slim_presence,
|
slim_presence=slim_presence,
|
||||||
|
@ -234,7 +249,7 @@ def get_events_backend(
|
||||||
user_profile_id=user_profile.id,
|
user_profile_id=user_profile.id,
|
||||||
queue_id=queue_id,
|
queue_id=queue_id,
|
||||||
last_event_id=last_event_id,
|
last_event_id=last_event_id,
|
||||||
client_type_name=valid_user_client.name,
|
client_type_name=valid_user_client_name,
|
||||||
dont_block=dont_block,
|
dont_block=dont_block,
|
||||||
handler_id=handler_id,
|
handler_id=handler_id,
|
||||||
new_queue_data=new_queue_data,
|
new_queue_data=new_queue_data,
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
from collections.abc import Sequence
|
from typing import Annotated, TypeAlias
|
||||||
from typing import TypeAlias
|
|
||||||
|
|
||||||
|
from annotated_types import Len
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
from pydantic import Json
|
||||||
|
|
||||||
from zerver.context_processors import get_valid_realm_from_request
|
from zerver.context_processors import get_valid_realm_from_request
|
||||||
from zerver.lib.compatibility import is_pronouns_field_type_supported
|
from zerver.lib.compatibility import is_pronouns_field_type_supported
|
||||||
from zerver.lib.events import do_events_register
|
from zerver.lib.events import ClientCapabilities, do_events_register
|
||||||
from zerver.lib.exceptions import JsonableError, MissingAuthenticationError
|
from zerver.lib.exceptions import JsonableError, MissingAuthenticationError
|
||||||
from zerver.lib.narrow_helpers import narrow_dataclasses_from_tuples
|
from zerver.lib.narrow_helpers import narrow_dataclasses_from_tuples
|
||||||
from zerver.lib.request import REQ, RequestNotes, has_request_variables
|
from zerver.lib.request import RequestNotes
|
||||||
from zerver.lib.response import json_success
|
from zerver.lib.response import json_success
|
||||||
from zerver.lib.validator import check_bool, check_dict, check_int, check_list, check_string
|
from zerver.lib.typed_endpoint import ApiParamConfig, DocumentationStatus, typed_endpoint
|
||||||
from zerver.models import Stream, UserProfile
|
from zerver.models import Stream, UserProfile
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,59 +25,36 @@ def _default_all_public_streams(user_profile: UserProfile, all_public_streams: b
|
||||||
return user_profile.default_all_public_streams
|
return user_profile.default_all_public_streams
|
||||||
|
|
||||||
|
|
||||||
def _default_narrow(
|
def _default_narrow(user_profile: UserProfile, narrow: list[list[str]]) -> list[list[str]]:
|
||||||
user_profile: UserProfile, narrow: Sequence[Sequence[str]]
|
|
||||||
) -> Sequence[Sequence[str]]:
|
|
||||||
default_stream: Stream | None = user_profile.default_events_register_stream
|
default_stream: Stream | None = user_profile.default_events_register_stream
|
||||||
if not narrow and default_stream is not None:
|
if not narrow and default_stream is not None:
|
||||||
narrow = [["stream", default_stream.name]]
|
narrow = [["stream", default_stream.name]]
|
||||||
return narrow
|
return narrow
|
||||||
|
|
||||||
|
|
||||||
NarrowT: TypeAlias = Sequence[Sequence[str]]
|
NarrowT: TypeAlias = list[Annotated[list[str], Len(min_length=2, max_length=2)]]
|
||||||
|
|
||||||
|
|
||||||
@has_request_variables
|
@typed_endpoint
|
||||||
def events_register_backend(
|
def events_register_backend(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
maybe_user_profile: UserProfile | AnonymousUser,
|
maybe_user_profile: UserProfile | AnonymousUser,
|
||||||
apply_markdown: bool = REQ(default=False, json_validator=check_bool),
|
*,
|
||||||
client_gravatar_raw: bool | None = REQ(
|
apply_markdown: Json[bool] = False,
|
||||||
"client_gravatar", default=None, json_validator=check_bool
|
client_gravatar_raw: Annotated[Json[bool | None], ApiParamConfig("client_gravatar")] = None,
|
||||||
),
|
slim_presence: Json[bool] = False,
|
||||||
slim_presence: bool = REQ(default=False, json_validator=check_bool),
|
all_public_streams: Json[bool] | None = None,
|
||||||
all_public_streams: bool | None = REQ(default=None, json_validator=check_bool),
|
include_subscribers: Json[bool] = False,
|
||||||
include_subscribers: bool = REQ(default=False, json_validator=check_bool),
|
client_capabilities: Json[ClientCapabilities] | None = None,
|
||||||
client_capabilities: dict[str, bool] | None = REQ(
|
event_types: Json[list[str]] | None = None,
|
||||||
json_validator=check_dict(
|
fetch_event_types: Json[list[str]] | None = None,
|
||||||
[
|
narrow: Json[NarrowT] | None = None,
|
||||||
# This field was accidentally made required when it was added in v2.0.0-781;
|
queue_lifespan_secs: Annotated[
|
||||||
# this was not realized until after the release of Zulip 2.1.2. (It remains
|
Json[int], ApiParamConfig(documentation_status=DocumentationStatus.DOCUMENTATION_PENDING)
|
||||||
# required to help ensure backwards compatibility of client code.)
|
] = 0,
|
||||||
("notification_settings_null", check_bool),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
# Any new fields of `client_capabilities` should be optional. Add them here.
|
|
||||||
("bulk_message_deletion", check_bool),
|
|
||||||
("user_avatar_url_field_optional", check_bool),
|
|
||||||
("stream_typing_notifications", check_bool),
|
|
||||||
("user_settings_object", check_bool),
|
|
||||||
("linkifier_url_template", check_bool),
|
|
||||||
("user_list_incomplete", check_bool),
|
|
||||||
],
|
|
||||||
value_validator=check_bool,
|
|
||||||
),
|
|
||||||
default=None,
|
|
||||||
),
|
|
||||||
event_types: Sequence[str] | None = REQ(json_validator=check_list(check_string), default=None),
|
|
||||||
fetch_event_types: Sequence[str] | None = REQ(
|
|
||||||
json_validator=check_list(check_string), default=None
|
|
||||||
),
|
|
||||||
narrow: NarrowT = REQ(
|
|
||||||
json_validator=check_list(check_list(check_string, length=2)), default=[]
|
|
||||||
),
|
|
||||||
queue_lifespan_secs: int = REQ(json_validator=check_int, default=0, documentation_pending=True),
|
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
|
if narrow is None:
|
||||||
|
narrow = []
|
||||||
if client_gravatar_raw is None:
|
if client_gravatar_raw is None:
|
||||||
client_gravatar = maybe_user_profile.is_authenticated
|
client_gravatar = maybe_user_profile.is_authenticated
|
||||||
else:
|
else:
|
||||||
|
@ -121,7 +99,7 @@ def events_register_backend(
|
||||||
include_streams = False
|
include_streams = False
|
||||||
|
|
||||||
if client_capabilities is None:
|
if client_capabilities is None:
|
||||||
client_capabilities = {}
|
client_capabilities = ClientCapabilities(notification_settings_null=False)
|
||||||
|
|
||||||
client = RequestNotes.get_notes(request).client
|
client = RequestNotes.get_notes(request).client
|
||||||
assert client is not None
|
assert client is not None
|
||||||
|
|
Loading…
Reference in New Issue