Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bf0fab0
Codemod return types for literal returns (#97988)
thetruecpaul Aug 18, 2025
374e30b
type middleware/integrations/ (#97992)
thetruecpaul Aug 18, 2025
d40df21
type two ezpz files (#98000)
thetruecpaul Aug 18, 2025
fe159b3
fix(typing): add typing to demo_mode module (#97987)
isabellaenriquez Aug 18, 2025
c58b00c
Type tests/sentry_plugins (#97998)
thetruecpaul Aug 18, 2025
3e989cc
add misc files (#98007)
thetruecpaul Aug 18, 2025
0e3d4ac
type tests/apidocs (#98012)
thetruecpaul Aug 18, 2025
9928780
type src/sentry/endpoints/release_thresholds/ (#98025)
shayna-ch Aug 19, 2025
dbc6065
type tests/acceptance (#98011)
thetruecpaul Aug 19, 2025
cb1da7d
Add types to some low-lift files (#98027)
thetruecpaul Aug 19, 2025
e596f88
added types to src/sentry/api/bases/ (#98034)
shayna-ch Aug 20, 2025
9062ef8
added types for src/sentry/dashboards (#98063)
shayna-ch Aug 20, 2025
1906583
codemod returns to `setup` and `teardown` (#98065)
thetruecpaul Aug 20, 2025
e7a58d2
fix(typing): dynamic sampling module (#98049)
isabellaenriquez Aug 20, 2025
51d831a
added types for src/sentry/api/endpoints/organization_members (#98055)
shayna-ch Aug 20, 2025
86d529b
improve plugin tests to not need explicit mocks (#98032)
thetruecpaul Aug 20, 2025
77afe6d
type 65 additional low-lift files (#98083)
thetruecpaul Aug 20, 2025
425c149
fix(typing): auth module (#98020)
isabellaenriquez Aug 21, 2025
9282aa3
fix(typing): mail module (#98093)
isabellaenriquez Aug 21, 2025
64b3959
type src/sentry/api/helpers (#98013)
thetruecpaul Aug 21, 2025
1d57b68
conftests (#98106)
thetruecpaul Aug 21, 2025
0143a25
type sentry.notifications.notifications.activity.base
Christinarlong Aug 21, 2025
606793e
fix(typing): Type pagerduty module (#98075)
Christinarlong Aug 21, 2025
cd36428
fix(typing): data_export module (#98096)
isabellaenriquez Aug 21, 2025
b57211d
parameterized types (#98115)
thetruecpaul Aug 21, 2025
6af05c5
type notifications module
Christinarlong Aug 21, 2025
56fbd2b
Merge branch 'hackweek/typing-25' into crl/type-ntoifications
Christinarlong Aug 21, 2025
e162579
fix request serializer
Christinarlong Aug 21, 2025
1c1af92
fix tests
Christinarlong Aug 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion fixtures/integrations/jira/stub_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def get_transitions(self, issue_key):
def transition_issue(self, issue_key, transition_id):
pass

def user_id_field(self):
def user_id_field(self) -> str:
return "accountId"

def get_user(self, user_id):
Expand Down
38 changes: 16 additions & 22 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -307,14 +307,12 @@ module = [
"sentry.api.endpoints.organization_releases",
"sentry.api.paginator",
"sentry.db.postgres.base",
"sentry.integrations.pagerduty.actions.form",
"sentry.integrations.slack.message_builder.notifications.issues",
"sentry.integrations.slack.webhooks.event",
"sentry.issues.search",
"sentry.middleware.auth",
"sentry.middleware.ratelimit",
"sentry.net.http",
"sentry.notifications.notifications.activity.base",
"sentry.plugins.config",
"sentry.release_health.metrics_sessions_v2",
"sentry.search.events.builder.errors",
Expand Down Expand Up @@ -349,32 +347,26 @@ module = [
"fixtures.safe_migrations_apps.*",
"fixtures.schema_validation",
"sentry.analytics.*",
"sentry.api.bases.*",
"sentry.api.decorators",
"sentry.api.endpoints.integrations.sentry_apps.installation.external_issue.*",
"sentry.api.endpoints.organization_events_spans_performance",
"sentry.api.endpoints.organization_member.*",
"sentry.api.endpoints.project_repo_path_parsing",
"sentry.api.endpoints.project_rules_configuration",
"sentry.api.endpoints.release_thresholds.health_checks.*",
"sentry.api.endpoints.release_thresholds.*",
"sentry.api.event_search",
"sentry.api.helpers.deprecation",
"sentry.api.helpers.environments",
"sentry.api.helpers.error_upsampling",
"sentry.api.helpers.group_index.delete",
"sentry.api.helpers.group_index.update",
"sentry.api.helpers.source_map_helper",
"sentry.api.helpers.*",
"sentry.api.permissions",
"sentry.api.serializers.models.organization_member.*",
"sentry.api.serializers.rest_framework.group_notes",
"sentry.audit_log.services.*",
"sentry.auth.access",
"sentry.auth.authenticators.recovery_code",
"sentry.auth.manager",
"sentry.auth.services.*",
"sentry.auth.view",
"sentry.auth.*",
"sentry.bgtasks.*",
"sentry.buffer.*",
"sentry.build.*",
"sentry.data_export.processors.issues_by_tag",
"sentry.dashboards.*",
"sentry.data_export.*",
"sentry.data_secrecy.models.*",
"sentry.data_secrecy.service.*",
"sentry.db.models.fields.citext",
Expand All @@ -386,12 +378,9 @@ module = [
"sentry.db.models.utils",
"sentry.db.pending_deletion",
"sentry.deletions.*",
"sentry.demo_mode.*",
"sentry.digests.*",
"sentry.dynamic_sampling.models.*",
"sentry.dynamic_sampling.rules.biases.*",
"sentry.dynamic_sampling.rules.combinators.*",
"sentry.dynamic_sampling.rules.helpers.*",
"sentry.dynamic_sampling.tasks.helpers.*",
"sentry.dynamic_sampling.*",
"sentry.eventstream.*",
"sentry.eventtypes.error",
"sentry.feedback.migrations.*",
Expand Down Expand Up @@ -427,6 +416,7 @@ module = [
"sentry.integrations.jira_server.actions.*",
"sentry.integrations.jira_server.utils.*",
"sentry.integrations.models.integration_feature",
"sentry.integrations.pagerduty.*",
"sentry.integrations.project_management.*",
"sentry.integrations.repository.*",
"sentry.integrations.services.*",
Expand Down Expand Up @@ -503,6 +493,8 @@ module = [
"sentry.issues.update_inbox",
"sentry.lang.java.processing",
"sentry.llm.*",
"sentry.mail.*",
"sentry.middleware.integrations.*",
"sentry.middleware.reporting_endpoint",
"sentry.migrations.*",
"sentry.models.activity",
Expand All @@ -516,8 +508,7 @@ module = [
"sentry.monitors.consumers.monitor_consumer",
"sentry.monkey.*",
"sentry.net.socket",
"sentry.notifications.platform.*",
"sentry.notifications.services.*",
"sentry.notifications.*",
"sentry.options.rollout",
"sentry.organizations.*",
"sentry.performance_issues.detectors.*",
Expand Down Expand Up @@ -655,6 +646,8 @@ module = [
"social_auth.admin",
"social_auth.migrations.*",
"sudo.*",
"tests.acceptance.*",
"tests.apidocs.*",
"tests.sentry.api.endpoints.issues.*",
"tests.sentry.api.endpoints.release_thresholds.utils.*",
"tests.sentry.api.endpoints.secret_scanning.*",
Expand Down Expand Up @@ -795,6 +788,7 @@ module = [
"tests.sentry.workflow_engine.endpoints.utils.*",
"tests.sentry.workflow_engine.handlers.action.*",
"tests.sentry.workflow_engine.models.*",
"tests.sentry_plugins.*",
"tools.*",
]
disallow_any_generics = true
Expand Down
19 changes: 12 additions & 7 deletions src/sentry/api/bases/avatar.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@

from sentry.api.fields import AvatarField
from sentry.api.serializers import serialize
from sentry.db.models.base import Model
from sentry.models.avatars.base import AvatarBase
from sentry.models.avatars.control_base import ControlAvatarBase

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


class AvatarSerializer(serializers.Serializer):
class AvatarSerializer(serializers.Serializer[dict[str, Any]]):
avatar_photo = AvatarField(required=False)
avatar_type = serializers.ChoiceField(
choices=(("upload", "upload"), ("gravatar", "gravatar"), ("letter_avatar", "letter_avatar"))
)

def validate(self, attrs):
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
attrs = super().validate(attrs)
if attrs.get("avatar_type") == "upload":
model_type = self.context["type"]
Expand All @@ -46,7 +47,7 @@ def validate(self, attrs):

class AvatarMixin(Generic[AvatarT]):
object_type: ClassVar[str]
serializer_cls: ClassVar[type[serializers.Serializer]] = AvatarSerializer
serializer_cls: ClassVar[type[serializers.Serializer[dict[str, Any]]]] = AvatarSerializer

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

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

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

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

serializer = self.serializer_cls(
data=request.data, context=self.get_serializer_context(obj)
)
return (obj, serializer)

def save_avatar(self, obj: Any, serializer: serializers.Serializer, **kwargs: Any) -> AvatarT:
def save_avatar(
self, obj: Model, serializer: serializers.Serializer[dict[str, Any]], **kwargs: Any
) -> AvatarT:
result = serializer.validated_data

return self.model.save_avatar(
Expand Down
27 changes: 19 additions & 8 deletions src/sentry/api/bases/group.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
from __future__ import annotations

import logging
from typing import Any

import sentry_sdk
from django.db.models import QuerySet
from rest_framework.permissions import SAFE_METHODS
from rest_framework.request import Request
from rest_framework.views import APIView

from sentry.api.api_owners import ApiOwner
from sentry.api.base import Endpoint
from sentry.api.bases.project import ProjectPermission
from sentry.api.exceptions import ResourceDoesNotExist
from sentry.demo_mode.utils import is_demo_mode_enabled, is_demo_user
from sentry.integrations.tasks import create_comment, update_comment
from sentry.models.activity import Activity
from sentry.models.group import Group, GroupStatus, get_group_with_redirect
from sentry.models.grouplink import GroupLink
from sentry.models.organization import Organization
Expand All @@ -34,7 +38,8 @@ class GroupPermission(ProjectPermission):
"DELETE": ["event:admin"],
}

def has_object_permission(self, request: Request, view, group):
def has_object_permission(self, request: Request, view: APIView, group: Any) -> bool:
assert isinstance(group, Group)
return super().has_object_permission(request, view, group.project)


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

def convert_args(
self, request: Request, issue_id, organization_id_or_slug=None, *args, **kwargs
):
self,
request: Request,
issue_id: str,
organization_id_or_slug: str | None = None,
*args: Any,
**kwargs: Any,
) -> tuple[tuple[Any, ...], dict[str, Any]]:
# TODO(tkaemming): Ideally, this would return a 302 response, rather
# than just returning the data that is bound to the new group. (It
# technically shouldn't be a 301, since the response could change again
Expand Down Expand Up @@ -96,12 +106,12 @@ def convert_args(

return (args, kwargs)

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

def create_external_comment(self, request: Request, group, group_note):
def create_external_comment(self, request: Request, group: Group, group_note: Activity) -> None:
for external_issue_id in self.get_external_issue_ids(group):
create_comment.apply_async(
kwargs={
Expand All @@ -111,7 +121,7 @@ def create_external_comment(self, request: Request, group, group_note):
}
)

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

def has_permission(self, request: Request, view) -> bool:
def has_permission(self, request: Request, view: APIView) -> bool:
if is_demo_user(request.user):
if not is_demo_mode_enabled() or request.method not in self.ALLOWED_METHODS:
return False

return True
return super().has_permission(request, view)

def has_object_permission(self, request: Request, view, group) -> bool:
def has_object_permission(self, request: Request, view: APIView, group: Any) -> bool:
assert isinstance(group, Group)
if is_demo_user(request.user):
if not is_demo_mode_enabled() or request.method not in self.ALLOWED_METHODS:
return False
Expand Down
10 changes: 9 additions & 1 deletion src/sentry/api/bases/incident.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any

from rest_framework.exceptions import PermissionDenied
from rest_framework.request import Request

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


class IncidentEndpoint(OrganizationEndpoint):
def convert_args(self, request: Request, incident_identifier, *args, **kwargs):
def convert_args(
self,
request: Request,
incident_identifier: str,
*args: Any,
**kwargs: Any,
) -> tuple[tuple[Any, ...], dict[str, Any]]:
args, kwargs = super().convert_args(request, *args, **kwargs)
organization = kwargs["organization"]

Expand Down
Loading
Loading