Skip to content

Commit a888a91

Browse files
thetruecpaulisabellaenriquezshayna-chgetsantry[bot]Christinarlong
authored
fix(typing): Fix typing in 561 files (#98197)
Hackweek! This branch contains 1,349 type-error fixes and fully types 561 additional files. (That's 12% of all errors & 23% of untyped files.) This differs from #98132 because it: * Updates `notifications.py` to allow for literal-`'None'` string identifiers * Adds a new test case to `test_notifications.py` to ensure literal string `'None'`s are handled gracefully These come in response to [an issue](https://sentry.sentry.io/issues/6835281181/?project=1) we saw where we were trying to cast `'None'` to int. Turns out that doesn't work so hot! This PR contains a new commit with fixes. We saw no other problems coming from this commit. --------- Co-authored-by: Isabella Enriquez <isabella.enriquez@sentry.io> Co-authored-by: Shayna Chambless <shayna.chambless@sentry.io> Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com> Co-authored-by: Christinarlong <60594860+Christinarlong@users.noreply.github.com>
1 parent c5655be commit a888a91

File tree

794 files changed

+2819
-1904
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

794 files changed

+2819
-1904
lines changed

fixtures/integrations/jira/stub_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def get_transitions(self, issue_key):
4444
def transition_issue(self, issue_key, transition_id):
4545
pass
4646

47-
def user_id_field(self):
47+
def user_id_field(self) -> str:
4848
return "accountId"
4949

5050
def get_user(self, user_id):

pyproject.toml

Lines changed: 124 additions & 37 deletions
Large diffs are not rendered by default.

src/sentry/api/bases/avatar.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@
88

99
from sentry.api.fields import AvatarField
1010
from sentry.api.serializers import serialize
11+
from sentry.db.models.base import Model
1112
from sentry.models.avatars.base import AvatarBase
1213
from sentry.models.avatars.control_base import ControlAvatarBase
1314

1415
AvatarT = TypeVar("AvatarT", bound=AvatarBase)
1516

1617

17-
class AvatarSerializer(serializers.Serializer):
18+
class AvatarSerializer(serializers.Serializer[dict[str, Any]]):
1819
avatar_photo = AvatarField(required=False)
1920
avatar_type = serializers.ChoiceField(
2021
choices=(("upload", "upload"), ("gravatar", "gravatar"), ("letter_avatar", "letter_avatar"))
2122
)
2223

23-
def validate(self, attrs):
24+
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
2425
attrs = super().validate(attrs)
2526
if attrs.get("avatar_type") == "upload":
2627
model_type = self.context["type"]
@@ -46,7 +47,7 @@ def validate(self, attrs):
4647

4748
class AvatarMixin(Generic[AvatarT]):
4849
object_type: ClassVar[str]
49-
serializer_cls: ClassVar[type[serializers.Serializer]] = AvatarSerializer
50+
serializer_cls: ClassVar[type[serializers.Serializer[dict[str, Any]]]] = AvatarSerializer
5051

5152
@property
5253
def model(self) -> type[AvatarT]:
@@ -56,21 +57,25 @@ def get(self, request: Request, **kwargs: Any) -> Response:
5657
obj = kwargs.pop(self.object_type, None)
5758
return Response(serialize(obj, request.user, **kwargs))
5859

59-
def get_serializer_context(self, obj, **kwargs: Any):
60+
def get_serializer_context(self, obj: Model, **kwargs: Any) -> dict[str, Any]:
6061
return {"type": self.model, "kwargs": {self.object_type: obj}}
6162

62-
def get_avatar_filename(self, obj):
63+
def get_avatar_filename(self, obj: Model) -> str:
6364
return f"{obj.id}.png"
6465

65-
def parse(self, request: Request, **kwargs: Any) -> tuple[Any, serializers.Serializer]:
66+
def parse(
67+
self, request: Request, **kwargs: Any
68+
) -> tuple[Model, serializers.Serializer[dict[str, Any]]]:
6669
obj = kwargs.pop(self.object_type, None)
6770

6871
serializer = self.serializer_cls(
6972
data=request.data, context=self.get_serializer_context(obj)
7073
)
7174
return (obj, serializer)
7275

73-
def save_avatar(self, obj: Any, serializer: serializers.Serializer, **kwargs: Any) -> AvatarT:
76+
def save_avatar(
77+
self, obj: Model, serializer: serializers.Serializer[dict[str, Any]], **kwargs: Any
78+
) -> AvatarT:
7479
result = serializer.validated_data
7580

7681
return self.model.save_avatar(

src/sentry/api/bases/group.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
from __future__ import annotations
22

33
import logging
4+
from typing import Any
45

56
import sentry_sdk
7+
from django.db.models import QuerySet
68
from rest_framework.permissions import SAFE_METHODS
79
from rest_framework.request import Request
10+
from rest_framework.views import APIView
811

912
from sentry.api.api_owners import ApiOwner
1013
from sentry.api.base import Endpoint
1114
from sentry.api.bases.project import ProjectPermission
1215
from sentry.api.exceptions import ResourceDoesNotExist
1316
from sentry.demo_mode.utils import is_demo_mode_enabled, is_demo_user
1417
from sentry.integrations.tasks import create_comment, update_comment
18+
from sentry.models.activity import Activity
1519
from sentry.models.group import Group, GroupStatus, get_group_with_redirect
1620
from sentry.models.grouplink import GroupLink
1721
from sentry.models.organization import Organization
@@ -34,7 +38,8 @@ class GroupPermission(ProjectPermission):
3438
"DELETE": ["event:admin"],
3539
}
3640

37-
def has_object_permission(self, request: Request, view, group):
41+
def has_object_permission(self, request: Request, view: APIView, group: Any) -> bool:
42+
assert isinstance(group, Group)
3843
return super().has_object_permission(request, view, group.project)
3944

4045

@@ -43,8 +48,13 @@ class GroupEndpoint(Endpoint):
4348
permission_classes = (GroupPermission,)
4449

4550
def convert_args(
46-
self, request: Request, issue_id, organization_id_or_slug=None, *args, **kwargs
47-
):
51+
self,
52+
request: Request,
53+
issue_id: str,
54+
organization_id_or_slug: str | None = None,
55+
*args: Any,
56+
**kwargs: Any,
57+
) -> tuple[tuple[Any, ...], dict[str, Any]]:
4858
# TODO(tkaemming): Ideally, this would return a 302 response, rather
4959
# than just returning the data that is bound to the new group. (It
5060
# technically shouldn't be a 301, since the response could change again
@@ -96,12 +106,12 @@ def convert_args(
96106

97107
return (args, kwargs)
98108

99-
def get_external_issue_ids(self, group):
109+
def get_external_issue_ids(self, group: Group) -> QuerySet[Any]:
100110
return GroupLink.objects.filter(
101111
project_id=group.project_id, group_id=group.id, linked_type=GroupLink.LinkedType.issue
102112
).values_list("linked_id", flat=True)
103113

104-
def create_external_comment(self, request: Request, group, group_note):
114+
def create_external_comment(self, request: Request, group: Group, group_note: Activity) -> None:
105115
for external_issue_id in self.get_external_issue_ids(group):
106116
create_comment.apply_async(
107117
kwargs={
@@ -111,7 +121,7 @@ def create_external_comment(self, request: Request, group, group_note):
111121
}
112122
)
113123

114-
def update_external_comment(self, request: Request, group, group_note):
124+
def update_external_comment(self, request: Request, group: Group, group_note: Activity) -> None:
115125
for external_issue_id in self.get_external_issue_ids(group):
116126
update_comment.apply_async(
117127
kwargs={
@@ -133,15 +143,16 @@ class GroupAiPermission(GroupPermission):
133143
# We want to allow POST requests in order to showcase AI features in demo mode
134144
ALLOWED_METHODS = tuple(list(SAFE_METHODS) + ["POST"])
135145

136-
def has_permission(self, request: Request, view) -> bool:
146+
def has_permission(self, request: Request, view: APIView) -> bool:
137147
if is_demo_user(request.user):
138148
if not is_demo_mode_enabled() or request.method not in self.ALLOWED_METHODS:
139149
return False
140150

141151
return True
142152
return super().has_permission(request, view)
143153

144-
def has_object_permission(self, request: Request, view, group) -> bool:
154+
def has_object_permission(self, request: Request, view: APIView, group: Any) -> bool:
155+
assert isinstance(group, Group)
145156
if is_demo_user(request.user):
146157
if not is_demo_mode_enabled() or request.method not in self.ALLOWED_METHODS:
147158
return False

src/sentry/api/bases/incident.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Any
2+
13
from rest_framework.exceptions import PermissionDenied
24
from rest_framework.request import Request
35

@@ -24,7 +26,13 @@ class IncidentPermission(OrganizationPermission):
2426

2527

2628
class IncidentEndpoint(OrganizationEndpoint):
27-
def convert_args(self, request: Request, incident_identifier, *args, **kwargs):
29+
def convert_args(
30+
self,
31+
request: Request,
32+
incident_identifier: str,
33+
*args: Any,
34+
**kwargs: Any,
35+
) -> tuple[tuple[Any, ...], dict[str, Any]]:
2836
args, kwargs = super().convert_args(request, *args, **kwargs)
2937
organization = kwargs["organization"]
3038

src/sentry/api/bases/organization_events.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from __future__ import annotations
22

33
import itertools
4-
from collections.abc import Callable, Sequence
4+
from collections.abc import Callable, Iterable, Sequence
55
from datetime import timedelta
66
from typing import Any, cast
77
from urllib.parse import quote as urlquote
88

99
import sentry_sdk
10+
from django.contrib.auth.models import AnonymousUser
1011
from django.http.request import HttpRequest
1112
from django.utils import timezone
1213
from rest_framework.exceptions import ParseError, ValidationError
@@ -27,9 +28,9 @@
2728
from sentry.api.serializers.snuba import SnubaTSResultSerializer
2829
from sentry.api.utils import handle_query_errors
2930
from sentry.discover.arithmetic import is_equation, strip_equation
30-
from sentry.discover.models import DatasetSourcesTypes, DiscoverSavedQueryTypes
31+
from sentry.discover.models import DatasetSourcesTypes, DiscoverSavedQuery, DiscoverSavedQueryTypes
3132
from sentry.exceptions import InvalidSearchQuery
32-
from sentry.models.dashboard_widget import DashboardWidgetTypes
33+
from sentry.models.dashboard_widget import DashboardWidget, DashboardWidgetTypes
3334
from sentry.models.dashboard_widget import DatasetSourcesTypes as DashboardDatasetSourcesTypes
3435
from sentry.models.group import Group
3536
from sentry.models.organization import Organization
@@ -43,6 +44,7 @@
4344
from sentry.snuba.dataset import Dataset
4445
from sentry.snuba.metrics.extraction import MetricSpecType
4546
from sentry.snuba.utils import DATASET_LABELS, DATASET_OPTIONS, get_dataset
47+
from sentry.users.models.user import User
4648
from sentry.users.services.user.serial import serialize_generic_user
4749
from sentry.utils import snuba
4850
from sentry.utils.cursors import Cursor
@@ -51,7 +53,7 @@
5153
from sentry.utils.snuba import MAX_FIELDS, SnubaTSResult
5254

5355

54-
def get_query_columns(columns, rollup):
56+
def get_query_columns(columns: list[str], rollup: int) -> list[str]:
5557
"""
5658
Backwards compatibility for incidents which uses the old
5759
column aliases as it straddles both versions of events/discover.
@@ -113,7 +115,7 @@ def get_teams(self, request: Request, organization: Organization) -> list[Team]:
113115
if not request.user:
114116
return []
115117

116-
teams = get_teams(request, organization)
118+
teams: Iterable[Team] = get_teams(request, organization)
117119
if not teams:
118120
teams = Team.objects.get_for_user(organization, request.user)
119121

@@ -249,7 +251,14 @@ def handle_on_demand(self, request: Request) -> tuple[bool, MetricSpecType]:
249251

250252
return use_on_demand_metrics, on_demand_metric_type
251253

252-
def save_split_decision(self, widget, has_errors, has_transactions_data, organization, user):
254+
def save_split_decision(
255+
self,
256+
widget: DashboardWidget,
257+
has_errors: bool,
258+
has_transactions_data: bool,
259+
organization: Organization,
260+
user: User | AnonymousUser,
261+
) -> int | None:
253262
"""This can be removed once the discover dataset has been fully split"""
254263
source = DashboardDatasetSourcesTypes.INFERRED.value
255264
if has_errors and not has_transactions_data:
@@ -273,15 +282,19 @@ def save_split_decision(self, widget, has_errors, has_transactions_data, organiz
273282
return decision
274283

275284
def save_discover_saved_query_split_decision(
276-
self, query, dataset_inferred_from_query, has_errors, has_transactions_data
277-
):
285+
self,
286+
query: DiscoverSavedQuery,
287+
dataset_inferred_from_query: int | None,
288+
has_errors: bool,
289+
has_transactions_data: bool,
290+
) -> int | None:
278291
"""
279292
This can be removed once the discover dataset has been fully split.
280293
If dataset is ambiguous (i.e., could be either transactions or errors),
281294
default to errors.
282295
"""
283296
dataset_source = DatasetSourcesTypes.INFERRED.value
284-
if dataset_inferred_from_query:
297+
if dataset_inferred_from_query is not None:
285298
decision = dataset_inferred_from_query
286299
sentry_sdk.set_tag("discover.split_reason", "inferred_from_query")
287300
elif has_errors and not has_transactions_data:
@@ -314,7 +327,7 @@ def handle_unit_meta(
314327
units[key], meta[key] = self.get_unit_and_type(key, value)
315328
return meta, units
316329

317-
def get_unit_and_type(self, field, field_type):
330+
def get_unit_and_type(self, field: str, field_type: str) -> tuple[str | None, str]:
318331
if field_type in SIZE_UNITS:
319332
return field_type, "size"
320333
elif field_type in DURATION_UNITS:
@@ -427,7 +440,7 @@ def handle_data(
427440

428441
return results
429442

430-
def handle_error_upsampling(self, project_ids: Sequence[int], results: dict[str, Any]):
443+
def handle_error_upsampling(self, project_ids: Sequence[int], results: dict[str, Any]) -> None:
431444
"""
432445
If the query is for error upsampled projects, we convert various functions under the hood.
433446
We need to rename these fields before returning the results to the client, to hide the conversion.
@@ -704,7 +717,9 @@ def serialize_multiple_axis(
704717

705718
return result
706719

707-
def update_meta_with_accuracy(self, meta, event_result, query_column) -> None:
720+
def update_meta_with_accuracy(
721+
self, meta: dict[str, Any], event_result: SnubaTSResult, query_column: str
722+
) -> None:
708723
if "processed_timeseries" in event_result.data:
709724
processed_timeseries = event_result.data["processed_timeseries"]
710725
meta["accuracy"] = {
@@ -724,7 +739,7 @@ def serialize_accuracy_data(
724739
data: Any,
725740
column: str,
726741
null_zero: bool = False,
727-
):
742+
) -> list[dict[str, Any]]:
728743
serialized_values = []
729744
for timestamp, group in itertools.groupby(data, key=lambda r: r["time"]):
730745
for row in group:

src/sentry/api/bases/organization_flag.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
from typing import Any
4+
35
from rest_framework.request import Request
46

57
from sentry import features
@@ -22,7 +24,9 @@ def feature_flags(self) -> list[str]:
2224
"Requires set 'feature_flags' property to restrict this endpoint."
2325
)
2426

25-
def convert_args(self, request: Request, *args, **kwargs):
27+
def convert_args(
28+
self, request: Request, *args: Any, **kwargs: Any
29+
) -> tuple[tuple[Any, ...], dict[str, Any]]:
2630
parsed_args, parsed_kwargs = super().convert_args(request, *args, **kwargs)
2731
organization = parsed_kwargs.get("organization")
2832
feature_gate = [

src/sentry/api/bases/organizationmember.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,18 @@ class MemberIdField(serializers.IntegerField):
6060
Allow "me" in addition to integers
6161
"""
6262

63-
def to_internal_value(self, data):
63+
def to_internal_value(self, data: float | int | str) -> Any:
6464
if data == "me":
6565
return data
6666
return super().to_internal_value(data)
6767

68-
def run_validation(self, data=empty):
68+
def run_validation(self, data: object | None = empty) -> object | None:
6969
if data == "me":
7070
return data
7171
return super().run_validation(data)
7272

7373

74-
class MemberSerializer(serializers.Serializer):
74+
class MemberSerializer(serializers.Serializer[dict[str, int | Literal["me"]]]):
7575
id = MemberIdField(min_value=0, max_value=BoundedAutoField.MAX_VALUE, required=True)
7676

7777

src/sentry/api/bases/project.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,9 @@ class ProjectEndpoint(Endpoint):
121121
def convert_args(
122122
self,
123123
request: Request,
124-
*args,
125-
**kwargs,
126-
):
124+
*args: Any,
125+
**kwargs: Any,
126+
) -> tuple[tuple[Any, ...], dict[str, Any]]:
127127
if args and args[0] is not None:
128128
organization_id_or_slug: int | str = args[0]
129129
# Required so it behaves like the original convert_args, where organization_id_or_slug was another parameter
@@ -193,7 +193,9 @@ def convert_args(
193193
kwargs["project"] = project
194194
return (args, kwargs)
195195

196-
def get_filter_params(self, request: Request, project, date_filter_optional=False):
196+
def get_filter_params(
197+
self, request: Request, project: Project, date_filter_optional: bool = False
198+
) -> dict[str, Any]:
197199
"""Similar to the version on the organization just for a single project."""
198200
# get the top level params -- projects, time range, and environment
199201
# from the request
@@ -203,7 +205,7 @@ def get_filter_params(self, request: Request, project, date_filter_optional=Fals
203205
raise ProjectEventsError(str(e))
204206

205207
environments = [env.name for env in get_environments(request, project.organization)]
206-
params = {"start": start, "end": end, "project_id": [project.id]}
208+
params: dict[str, Any] = {"start": start, "end": end, "project_id": [project.id]}
207209
if environments:
208210
params["environment"] = environments
209211

0 commit comments

Comments
 (0)