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:
Kenneth Rodrigues
2024-07-30 13:50:01 +05:30
committed by Tim Abbott
parent 4e63daf2ce
commit 97f15d8811
6 changed files with 148 additions and 138 deletions

View File

@@ -1,29 +1,21 @@
import time
from collections.abc import Callable, Mapping, Sequence
from typing import Any, TypeVar
from collections.abc import Callable
from typing import Annotated, Any, TypeVar
from asgiref.sync import async_to_sync
from django.conf import settings
from django.http import HttpRequest, HttpResponse
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 zerver.decorator import internal_api_view, process_client
from zerver.lib.exceptions import JsonableError
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.typed_endpoint import typed_endpoint
from zerver.lib.validator import (
check_bool,
check_dict,
check_int,
check_list,
check_string,
to_non_negative_int,
)
from zerver.models import Client, UserProfile
from zerver.lib.typed_endpoint import ApiParamConfig, DocumentationStatus, typed_endpoint
from zerver.models import UserProfile
from zerver.models.clients import get_client
from zerver.models.users import get_user_profile_by_id
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)
@has_request_variables
def notify(
request: HttpRequest, data: Mapping[str, Any] = REQ(json_validator=check_dict([]))
) -> HttpResponse:
@typed_endpoint
def notify(request: HttpRequest, *, data: Json[dict[str, Any]]) -> HttpResponse:
# Only the puppeteer full-stack tests use this endpoint; it
# injects an event, as if read from RabbitMQ.
in_tornado_thread(process_notification)(data)
@@ -77,9 +67,9 @@ def web_reload_clients(
)
@has_request_variables
@typed_endpoint
def cleanup_event_queue(
request: HttpRequest, user_profile: UserProfile, queue_id: str = REQ()
request: HttpRequest, user_profile: UserProfile, *, queue_id: str
) -> HttpResponse:
log_data = RequestNotes.get_notes(request).log_data
assert log_data is not None
@@ -108,10 +98,8 @@ def cleanup_event_queue(
@internal_api_view(True)
@has_request_variables
def get_events_internal(
request: HttpRequest, user_profile_id: int = REQ(json_validator=check_int)
) -> HttpResponse:
@typed_endpoint
def get_events_internal(request: HttpRequest, *, user_profile_id: Json[int]) -> HttpResponse:
user_profile = get_user_profile_by_id(user_profile_id)
RequestNotes.get_notes(request).requester_for_logs = user_profile.format_requester_for_logs()
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)
@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(
request: HttpRequest,
user_profile: UserProfile,
*,
# user_client is intended only for internal Django=>Tornado requests
# and thus shouldn't be documented for external use.
user_client: Client | None = REQ(
converter=lambda var_name, s: get_client(s), default=None, intentionally_undocumented=True
),
last_event_id: int | None = REQ(json_validator=check_int, default=None),
queue_id: str | None = REQ(default=None),
user_client: Annotated[
UserClient | None,
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
] = None,
last_event_id: Json[int] | None = None,
queue_id: str | None = None,
# apply_markdown, client_gravatar, all_public_streams, and various
# other parameters are only used when registering a new queue via this
# endpoint. This is a feature used primarily by get_events_internal
# and not expected to be used by third-party clients.
apply_markdown: bool = REQ(
default=False, json_validator=check_bool, intentionally_undocumented=True
),
client_gravatar: bool = REQ(
default=False, json_validator=check_bool, intentionally_undocumented=True
),
slim_presence: bool = REQ(
default=False, json_validator=check_bool, intentionally_undocumented=True
),
all_public_streams: bool = REQ(
default=False, json_validator=check_bool, intentionally_undocumented=True
),
event_types: Sequence[str] | None = REQ(
default=None, json_validator=check_list(check_string), intentionally_undocumented=True
),
dont_block: bool = REQ(default=False, json_validator=check_bool),
narrow: Sequence[Sequence[str]] = REQ(
default=[],
json_validator=check_list(check_list(check_string)),
intentionally_undocumented=True,
),
lifespan_secs: int = REQ(
default=0, converter=to_non_negative_int, intentionally_undocumented=True
),
bulk_message_deletion: bool = REQ(
default=False, json_validator=check_bool, intentionally_undocumented=True
),
stream_typing_notifications: bool = REQ(
default=False, json_validator=check_bool, intentionally_undocumented=True
),
user_settings_object: bool = REQ(
default=False, json_validator=check_bool, intentionally_undocumented=True
),
pronouns_field_type_supported: bool = REQ(
default=True, json_validator=check_bool, intentionally_undocumented=True
),
linkifier_url_template: bool = REQ(
default=False, json_validator=check_bool, intentionally_undocumented=True
),
user_list_incomplete: bool = REQ(
default=False, json_validator=check_bool, intentionally_undocumented=True
),
apply_markdown: Annotated[
Json[bool],
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
] = False,
client_gravatar: Annotated[
Json[bool],
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
] = False,
slim_presence: Annotated[
Json[bool],
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
] = False,
all_public_streams: Annotated[
Json[bool],
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
] = False,
event_types: Annotated[
Json[list[str]] | None,
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
] = None,
dont_block: Json[bool] = False,
narrow: Annotated[
Json[list[list[str]]] | None,
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
] = None,
lifespan_secs: Annotated[
Json[NonNegativeInt],
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
] = 0,
bulk_message_deletion: Annotated[
Json[bool],
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
] = False,
stream_typing_notifications: Annotated[
Json[bool],
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
] = False,
user_settings_object: Annotated[
Json[bool],
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
] = 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:
if narrow is None:
narrow = []
if all_public_streams and not user_profile.can_access_public_streams():
raise JsonableError(_("User not authorized for this query"))
@@ -205,8 +219,9 @@ def get_events_backend(
if user_client is None:
valid_user_client = RequestNotes.get_notes(request).client
assert valid_user_client is not None
valid_user_client_name = valid_user_client.name
else:
valid_user_client = user_client
valid_user_client_name = user_client.name
new_queue_data = None
if queue_id is None:
@@ -214,7 +229,7 @@ def get_events_backend(
user_profile_id=user_profile.id,
realm_id=user_profile.realm_id,
event_types=event_types,
client_type_name=valid_user_client.name,
client_type_name=valid_user_client_name,
apply_markdown=apply_markdown,
client_gravatar=client_gravatar,
slim_presence=slim_presence,
@@ -234,7 +249,7 @@ def get_events_backend(
user_profile_id=user_profile.id,
queue_id=queue_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,
handler_id=handler_id,
new_queue_data=new_queue_data,