mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	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:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						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
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user