Skip to content

Commit 00a5409

Browse files
kennethnrktimabbott
authored andcommitted
analytics: Migrate to @typed_endpoint.
1 parent 3ea3f48 commit 00a5409

File tree

2 files changed

+120
-40
lines changed

2 files changed

+120
-40
lines changed

analytics/views/stats.py

+119-39
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
from collections import defaultdict
33
from datetime import datetime, timedelta, timezone
4-
from typing import Any, Optional, TypeAlias, TypeVar, cast
4+
from typing import Annotated, Any, Optional, TypeAlias, TypeVar, cast
55

66
from django.conf import settings
77
from django.db.models import QuerySet
@@ -10,6 +10,7 @@
1010
from django.utils import translation
1111
from django.utils.timezone import now as timezone_now
1212
from django.utils.translation import gettext as _
13+
from pydantic import BeforeValidator, Json, NonNegativeInt
1314

1415
from analytics.lib.counts import COUNT_STATS, CountStat
1516
from analytics.lib.time_utils import time_range
@@ -30,11 +31,10 @@
3031
)
3132
from zerver.lib.exceptions import JsonableError
3233
from zerver.lib.i18n import get_and_set_request_language, get_language_translation_data
33-
from zerver.lib.request import REQ, has_request_variables
3434
from zerver.lib.response import json_success
3535
from zerver.lib.streams import access_stream_by_id
3636
from zerver.lib.timestamp import convert_to_UTC
37-
from zerver.lib.validator import to_non_negative_int
37+
from zerver.lib.typed_endpoint import PathOnly, typed_endpoint
3838
from zerver.models import Client, Realm, Stream, UserProfile
3939
from zerver.models.realms import get_realm
4040

@@ -111,8 +111,8 @@ def stats(request: HttpRequest) -> HttpResponse:
111111

112112

113113
@require_server_admin
114-
@has_request_variables
115-
def stats_for_realm(request: HttpRequest, realm_str: str) -> HttpResponse:
114+
@typed_endpoint
115+
def stats_for_realm(request: HttpRequest, *, realm_str: PathOnly[str]) -> HttpResponse:
116116
try:
117117
realm = get_realm(realm_str)
118118
except Realm.DoesNotExist:
@@ -127,9 +127,9 @@ def stats_for_realm(request: HttpRequest, realm_str: str) -> HttpResponse:
127127

128128

129129
@require_server_admin
130-
@has_request_variables
130+
@typed_endpoint
131131
def stats_for_remote_realm(
132-
request: HttpRequest, remote_server_id: int, remote_realm_id: int
132+
request: HttpRequest, *, remote_server_id: PathOnly[int], remote_realm_id: PathOnly[int]
133133
) -> HttpResponse:
134134
assert settings.ZILENCER_ENABLED
135135
server = RemoteZulipServer.objects.get(id=remote_server_id)
@@ -142,22 +142,45 @@ def stats_for_remote_realm(
142142

143143

144144
@require_server_admin_api
145-
@has_request_variables
145+
@typed_endpoint
146146
def get_chart_data_for_realm(
147-
request: HttpRequest, /, user_profile: UserProfile, realm_str: str, **kwargs: Any
147+
request: HttpRequest,
148+
user_profile: UserProfile,
149+
/,
150+
*,
151+
realm_str: PathOnly[str],
152+
chart_name: str,
153+
min_length: Json[NonNegativeInt] | None = None,
154+
start: Annotated[datetime | None, BeforeValidator(to_utc_datetime)] = None,
155+
end: Annotated[datetime | None, BeforeValidator(to_utc_datetime)] = None,
148156
) -> HttpResponse:
149157
try:
150158
realm = get_realm(realm_str)
151159
except Realm.DoesNotExist:
152160
raise JsonableError(_("Invalid organization"))
153161

154-
return get_chart_data(request, user_profile, realm=realm, **kwargs)
162+
return do_get_chart_data(
163+
request,
164+
user_profile,
165+
realm=realm,
166+
chart_name=chart_name,
167+
min_length=min_length,
168+
start=start,
169+
end=end,
170+
)
155171

156172

157173
@require_non_guest_user
158-
@has_request_variables
174+
@typed_endpoint
159175
def get_chart_data_for_stream(
160-
request: HttpRequest, /, user_profile: UserProfile, stream_id: int
176+
request: HttpRequest,
177+
user_profile: UserProfile,
178+
*,
179+
stream_id: PathOnly[int],
180+
chart_name: str,
181+
min_length: Json[NonNegativeInt] | None = None,
182+
start: Annotated[datetime | None, BeforeValidator(to_utc_datetime)] = None,
183+
end: Annotated[datetime | None, BeforeValidator(to_utc_datetime)] = None,
161184
) -> HttpResponse:
162185
stream, ignored_sub = access_stream_by_id(
163186
user_profile,
@@ -166,28 +189,43 @@ def get_chart_data_for_stream(
166189
allow_realm_admin=True,
167190
)
168191

169-
return get_chart_data(request, user_profile, stream=stream)
192+
return do_get_chart_data(
193+
request,
194+
user_profile,
195+
stream=stream,
196+
chart_name=chart_name,
197+
min_length=min_length,
198+
start=start,
199+
end=end,
200+
)
170201

171202

172203
@require_server_admin_api
173-
@has_request_variables
204+
@typed_endpoint
174205
def get_chart_data_for_remote_realm(
175206
request: HttpRequest,
176-
/,
177207
user_profile: UserProfile,
178-
remote_server_id: int,
179-
remote_realm_id: int,
180-
**kwargs: Any,
208+
/,
209+
*,
210+
remote_server_id: PathOnly[int],
211+
remote_realm_id: PathOnly[int],
212+
chart_name: str,
213+
min_length: Json[NonNegativeInt] | None = None,
214+
start: Annotated[datetime | None, BeforeValidator(to_utc_datetime)] = None,
215+
end: Annotated[datetime | None, BeforeValidator(to_utc_datetime)] = None,
181216
) -> HttpResponse:
182217
assert settings.ZILENCER_ENABLED
183218
server = RemoteZulipServer.objects.get(id=remote_server_id)
184-
return get_chart_data(
219+
return do_get_chart_data(
185220
request,
186221
user_profile,
187222
server=server,
188223
remote=True,
189-
remote_realm_id=int(remote_realm_id),
190-
**kwargs,
224+
remote_realm_id=remote_realm_id,
225+
chart_name=chart_name,
226+
min_length=min_length,
227+
start=start,
228+
end=end,
191229
)
192230

193231

@@ -210,47 +248,89 @@ def stats_for_remote_installation(request: HttpRequest, remote_server_id: int) -
210248

211249

212250
@require_server_admin_api
213-
@has_request_variables
251+
@typed_endpoint
214252
def get_chart_data_for_installation(
215-
request: HttpRequest, /, user_profile: UserProfile, chart_name: str = REQ(), **kwargs: Any
253+
request: HttpRequest,
254+
user_profile: UserProfile,
255+
/,
256+
*,
257+
chart_name: str,
258+
min_length: Json[NonNegativeInt] | None = None,
259+
start: Annotated[datetime | None, BeforeValidator(to_utc_datetime)] = None,
260+
end: Annotated[datetime | None, BeforeValidator(to_utc_datetime)] = None,
216261
) -> HttpResponse:
217-
return get_chart_data(request, user_profile, for_installation=True, **kwargs)
262+
return do_get_chart_data(
263+
request,
264+
user_profile,
265+
for_installation=True,
266+
chart_name=chart_name,
267+
min_length=min_length,
268+
start=start,
269+
end=end,
270+
)
218271

219272

220273
@require_server_admin_api
221-
@has_request_variables
274+
@typed_endpoint
222275
def get_chart_data_for_remote_installation(
223276
request: HttpRequest,
224-
/,
225277
user_profile: UserProfile,
226-
remote_server_id: int,
227-
chart_name: str = REQ(),
228-
**kwargs: Any,
278+
/,
279+
*,
280+
remote_server_id: PathOnly[int],
281+
chart_name: str,
282+
min_length: Json[NonNegativeInt] | None = None,
283+
start: Annotated[datetime | None, BeforeValidator(to_utc_datetime)] = None,
284+
end: Annotated[datetime | None, BeforeValidator(to_utc_datetime)] = None,
229285
) -> HttpResponse:
230286
assert settings.ZILENCER_ENABLED
231287
server = RemoteZulipServer.objects.get(id=remote_server_id)
232-
return get_chart_data(
288+
return do_get_chart_data(
233289
request,
234290
user_profile,
235291
for_installation=True,
236292
remote=True,
237293
server=server,
238-
**kwargs,
294+
chart_name=chart_name,
295+
min_length=min_length,
296+
start=start,
297+
end=end,
239298
)
240299

241300

242301
@require_non_guest_user
243-
@has_request_variables
302+
@typed_endpoint
244303
def get_chart_data(
245304
request: HttpRequest,
246305
user_profile: UserProfile,
247-
chart_name: str = REQ(),
248-
min_length: int | None = REQ(converter=to_non_negative_int, default=None),
249-
start: datetime | None = REQ(converter=to_utc_datetime, default=None),
250-
end: datetime | None = REQ(converter=to_utc_datetime, default=None),
251-
# These last several parameters are only used by functions
252-
# wrapping get_chart_data; the callers are responsible for
253-
# parsing/validation/authorization for them.
306+
*,
307+
chart_name: str,
308+
min_length: Json[NonNegativeInt] | None = None,
309+
start: Annotated[datetime | None, BeforeValidator(to_utc_datetime)] = None,
310+
end: Annotated[datetime | None, BeforeValidator(to_utc_datetime)] = None,
311+
) -> HttpResponse:
312+
return do_get_chart_data(
313+
request,
314+
user_profile,
315+
chart_name=chart_name,
316+
min_length=min_length,
317+
start=start,
318+
end=end,
319+
)
320+
321+
322+
@require_non_guest_user
323+
def do_get_chart_data(
324+
request: HttpRequest,
325+
user_profile: UserProfile,
326+
*,
327+
# Common parameters supported by all stats endpoints.
328+
chart_name: str,
329+
min_length: NonNegativeInt | None = None,
330+
start: datetime | None = None,
331+
end: datetime | None = None,
332+
# The following parameters are only used by wrapping functions for
333+
# various contexts; the callers are responsible for validating them.
254334
realm: Realm | None = None,
255335
for_installation: bool = False,
256336
remote: bool = False,

zerver/decorator.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,7 @@ def _wrapped_func_arguments(
955955
return _wrapped_view_func
956956

957957

958-
def to_utc_datetime(var_name: str, timestamp: str) -> datetime:
958+
def to_utc_datetime(timestamp: str) -> datetime:
959959
return timestamp_to_datetime(float(timestamp))
960960

961961

0 commit comments

Comments
 (0)