Browse Source

ref(hc): Force transaction.atomic calls to specify using= (#53027)

In anticipation of enforcing a runtime check that `using=` must be
specified for all transaction.atomic and transaction.get_connection
calls, we add a value to all sentry callsites.
Zach Collins 1 year ago
parent
commit
5b7cc05d3e

+ 2 - 2
src/sentry/api/endpoints/group_hashes_split.py

@@ -2,7 +2,7 @@ import datetime
 from typing import Any, Dict, List, Optional, Sequence
 
 import sentry_sdk
-from django.db import transaction
+from django.db import router, transaction
 from rest_framework.request import Request
 from rest_framework.response import Response
 from snuba_sdk import Request as SnubaRequest
@@ -204,7 +204,7 @@ def _unsplit_group(group: Group, hash: str, hierarchical_hashes: Optional[Sequen
         if grouphash.group_id == group.id:
             grouphash_to_delete = grouphash
 
-    with transaction.atomic():
+    with transaction.atomic(router.db_for_write(GroupHash)):
         if grouphash_to_unsplit is not None:
             grouphash_to_unsplit.state = GroupHash.State.UNLOCKED
             grouphash_to_unsplit.save()

+ 4 - 4
src/sentry/api/endpoints/group_integration_details.py

@@ -1,6 +1,6 @@
 from typing import Any, Mapping, MutableMapping
 
-from django.db import IntegrityError, transaction
+from django.db import IntegrityError, router, transaction
 from rest_framework.request import Request
 from rest_framework.response import Response
 
@@ -192,7 +192,7 @@ class GroupIntegrationDetailsEndpoint(GroupEndpoint):
             return Response({"non_field_errors": [str(e)]}, status=400)
 
         try:
-            with transaction.atomic():
+            with transaction.atomic(router.db_for_write(GroupLink)):
                 GroupLink.objects.create(
                     group_id=group.id,
                     project_id=group.project_id,
@@ -256,7 +256,7 @@ class GroupIntegrationDetailsEndpoint(GroupEndpoint):
         )
 
         try:
-            with transaction.atomic():
+            with transaction.atomic(router.db_for_write(GroupLink)):
                 GroupLink.objects.create(
                     group_id=group.id,
                     project_id=group.project_id,
@@ -318,7 +318,7 @@ class GroupIntegrationDetailsEndpoint(GroupEndpoint):
         except ExternalIssue.DoesNotExist:
             return Response(status=404)
 
-        with transaction.atomic():
+        with transaction.atomic(router.db_for_write(GroupLink)):
             GroupLink.objects.get_group_issues(group, external_issue_id).delete()
 
             # check if other groups reference this external issue

+ 2 - 2
src/sentry/api/endpoints/organization_access_request_details.py

@@ -1,4 +1,4 @@
-from django.db import IntegrityError, transaction
+from django.db import IntegrityError, router, transaction
 from rest_framework import serializers
 from rest_framework.request import Request
 from rest_framework.response import Response
@@ -118,7 +118,7 @@ class OrganizationAccessRequestDetailsEndpoint(OrganizationEndpoint):
 
         if is_approved:
             try:
-                with transaction.atomic():
+                with transaction.atomic(router.db_for_write(OrganizationMemberTeam)):
                     omt = OrganizationMemberTeam.objects.create(
                         organizationmember=access_request.member, team=access_request.team
                     )

+ 2 - 2
src/sentry/api/endpoints/organization_dashboard_details.py

@@ -1,5 +1,5 @@
 import sentry_sdk
-from django.db import IntegrityError, transaction
+from django.db import IntegrityError, router, transaction
 from django.db.models import F
 from django.utils import timezone
 from rest_framework.request import Request
@@ -125,7 +125,7 @@ class OrganizationDashboardDetailsEndpoint(OrganizationDashboardBase):
         if not serializer.is_valid():
             return Response(serializer.errors, status=400)
         try:
-            with transaction.atomic():
+            with transaction.atomic(router.db_for_write(DashboardTombstone)):
                 serializer.save()
                 if tombstone:
                     DashboardTombstone.objects.get_or_create(

+ 2 - 2
src/sentry/api/endpoints/organization_dashboards.py

@@ -1,6 +1,6 @@
 import re
 
-from django.db import IntegrityError, transaction
+from django.db import IntegrityError, router, transaction
 from django.db.models import Case, IntegerField, When
 from rest_framework.request import Request
 from rest_framework.response import Response
@@ -154,7 +154,7 @@ class OrganizationDashboardsEndpoint(OrganizationEndpoint):
             return Response(serializer.errors, status=400)
 
         try:
-            with transaction.atomic():
+            with transaction.atomic(router.db_for_write(Dashboard)):
                 dashboard = serializer.save()
             return Response(serialize(dashboard, request.user), status=201)
         except IntegrityError:

+ 3 - 3
src/sentry/api/endpoints/organization_details.py

@@ -2,7 +2,7 @@ import logging
 from copy import copy
 from datetime import datetime
 
-from django.db import IntegrityError, models, transaction
+from django.db import IntegrityError, models, router, transaction
 from django.db.models.query_utils import DeferredAttribute
 from pytz import UTC
 from rest_framework import serializers, status
@@ -529,7 +529,7 @@ class OrganizationDetailsEndpoint(OrganizationEndpoint):
         if serializer.is_valid():
             changed_data = {}
             try:
-                with transaction.atomic():
+                with transaction.atomic(router.db_for_write(Organization)):
                     organization, changed_data = serializer.save()
             except IntegrityError:
                 return self.respond(
@@ -580,7 +580,7 @@ class OrganizationDetailsEndpoint(OrganizationEndpoint):
         ).exists():
             return self.respond({"detail": ERR_3RD_PARTY_PUBLISHED_APP}, status=400)
 
-        with transaction.atomic():
+        with transaction.atomic(router.db_for_write(ScheduledDeletion)):
             updated_organization = mark_organization_as_pending_deletion_with_outbox_message(
                 org_id=organization.id
             )

+ 2 - 2
src/sentry/api/endpoints/organization_index.py

@@ -1,5 +1,5 @@
 from django.conf import settings
-from django.db import IntegrityError, transaction
+from django.db import IntegrityError, router, transaction
 from django.db.models import Count, Q, Sum
 from rest_framework import serializers, status
 from rest_framework.request import Request
@@ -216,7 +216,7 @@ class OrganizationIndexEndpoint(Endpoint):
 
             try:
 
-                with transaction.atomic():
+                with transaction.atomic(router.db_for_write(Organization)):
                     org = create_organization_with_outbox_message(
                         create_options={"name": result["name"], "slug": result.get("slug")}
                     )

+ 4 - 4
src/sentry/api/endpoints/organization_member/details.py

@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from django.db import transaction
+from django.db import router, transaction
 from drf_spectacular.utils import OpenApiParameter, extend_schema
 from rest_framework.request import Request
 from rest_framework.response import Response
@@ -181,7 +181,7 @@ class OrganizationMemberDetailsEndpoint(OrganizationMemberEndpoint):
 
                 if result.get("regenerate"):
                     if request.access.has_scope("member:admin"):
-                        with transaction.atomic():
+                        with transaction.atomic(router.db_for_write(OrganizationMember)):
                             member.regenerate_token()
                             member.save()
                     else:
@@ -274,7 +274,7 @@ class OrganizationMemberDetailsEndpoint(OrganizationMemberEndpoint):
             r.id for r in team_roles.get_all() if r.priority <= new_minimum_team_role.priority
         ]
 
-        with transaction.atomic():
+        with transaction.atomic(router.db_for_write(OrganizationMemberTeam)):
             # If the member has any existing team roles that are less than or equal
             # to their new minimum role, overwrite the redundant team roles with
             # null. We do this because such a team role would be effectively
@@ -352,7 +352,7 @@ class OrganizationMemberDetailsEndpoint(OrganizationMemberEndpoint):
 
         audit_data = member.get_audit_log_data()
 
-        with transaction.atomic():
+        with transaction.atomic(router.db_for_write(Project)):
             # Delete instances of `UserOption` that are scoped to the projects within the
             # organization when corresponding member is removed from org
             proj_list = list(

+ 2 - 2
src/sentry/api/endpoints/organization_member/index.py

@@ -1,7 +1,7 @@
 from typing import List, Tuple
 
 from django.conf import settings
-from django.db import transaction
+from django.db import router, transaction
 from django.db.models import F, Q
 from rest_framework import serializers
 from rest_framework.request import Request
@@ -267,7 +267,7 @@ class OrganizationMemberIndexEndpoint(OrganizationEndpoint):
             )
             return Response({"detail": ERR_RATE_LIMITED}, status=429)
 
-        with transaction.atomic():
+        with transaction.atomic(router.db_for_write(OrganizationMember)):
             # remove any invitation requests for this email before inviting
             existing_invite = OrganizationMember.objects.filter(
                 Q(invite_status=InviteStatus.REQUESTED_TO_BE_INVITED.value)

+ 4 - 2
src/sentry/api/endpoints/organization_member/requests/invite/index.py

@@ -1,4 +1,4 @@
-from django.db import transaction
+from django.db import router, transaction
 from django.db.models import Q
 from rest_framework.request import Request
 from rest_framework.response import Response
@@ -74,7 +74,9 @@ class OrganizationInviteRequestIndexEndpoint(OrganizationEndpoint):
 
         result = serializer.validated_data
 
-        with outbox_context(transaction.atomic(), flush=False):
+        with outbox_context(
+            transaction.atomic(router.db_for_write(OrganizationMember)), flush=False
+        ):
             om = OrganizationMember.objects.create(
                 organization_id=organization.id,
                 role=result["role"] or organization.default_role,

Some files were not shown because too many files changed in this diff