Browse Source

chore(actor): Move ActorTuple to its own file so that it's not tied to models during import (#69005)

We want to use `ActorTuple` in the issue platform, but it causes a lot
of import loops currently. Moving this to a separate place so it can
more easily be imported
Dan Fuller 11 months ago
parent
commit
ef66c637e6

+ 1 - 1
src/sentry/api/endpoints/event_owners.py

@@ -8,9 +8,9 @@ from sentry.api.base import region_silo_endpoint
 from sentry.api.bases.project import ProjectEndpoint
 from sentry.api.serializers import serialize
 from sentry.api.serializers.models.actor import ActorSerializer
-from sentry.models.actor import ActorTuple
 from sentry.models.projectownership import ProjectOwnership
 from sentry.models.team import Team
+from sentry.utils.actor import ActorTuple
 
 
 @region_silo_endpoint

+ 1 - 1
src/sentry/api/fields/actor.py

@@ -4,10 +4,10 @@ from drf_spectacular.types import OpenApiTypes
 from drf_spectacular.utils import extend_schema_field
 from rest_framework import serializers
 
-from sentry.models.actor import ActorTuple
 from sentry.models.organizationmember import OrganizationMember
 from sentry.models.team import Team
 from sentry.models.user import User
+from sentry.utils.actor import ActorTuple
 
 
 @extend_schema_field(field=OpenApiTypes.STR)

+ 2 - 1
src/sentry/api/helpers/group_index/update.py

@@ -27,7 +27,7 @@ from sentry.issues.priority import update_priority
 from sentry.issues.status_change import handle_status_update
 from sentry.issues.update_inbox import update_inbox
 from sentry.models.activity import Activity, ActivityIntegration
-from sentry.models.actor import Actor, ActorTuple
+from sentry.models.actor import Actor
 from sentry.models.group import STATUS_UPDATE_CHOICES, Group, GroupStatus
 from sentry.models.groupassignee import GroupAssignee
 from sentry.models.groupbookmark import GroupBookmark
@@ -56,6 +56,7 @@ from sentry.tasks.integrations import kick_off_status_syncs
 from sentry.types.activity import ActivityType
 from sentry.types.group import SUBSTATUS_UPDATE_CHOICES, GroupSubStatus, PriorityLevel
 from sentry.utils import metrics
+from sentry.utils.actor import ActorTuple
 
 from . import ACTIVITIES_COUNT, BULK_MUTATION_LIMIT, SearchFunction, delete_group_list
 from .validators import GroupValidator, ValidationError

+ 1 - 1
src/sentry/api/serializers/rest_framework/mentions.py

@@ -4,13 +4,13 @@ from collections.abc import Sequence
 
 from rest_framework import serializers
 
-from sentry.models.actor import ActorTuple
 from sentry.models.organizationmember import OrganizationMember
 from sentry.models.organizationmemberteam import OrganizationMemberTeam
 from sentry.models.team import Team
 from sentry.models.user import User
 from sentry.services.hybrid_cloud.user import RpcUser
 from sentry.services.hybrid_cloud.util import region_silo_function
+from sentry.utils.actor import ActorTuple
 
 
 @region_silo_function

+ 1 - 1
src/sentry/integrations/slack/message_builder/issues.py

@@ -38,7 +38,6 @@ from sentry.issues.grouptype import (
     PerformanceP95EndpointRegressionGroupType,
     ProfileFunctionRegressionType,
 )
-from sentry.models.actor import ActorTuple
 from sentry.models.commit import Commit
 from sentry.models.group import Group, GroupStatus
 from sentry.models.project import Project
@@ -60,6 +59,7 @@ from sentry.services.hybrid_cloud.user.model import RpcUser
 from sentry.types.group import SUBSTATUS_TO_STR
 from sentry.types.integrations import ExternalProviders
 from sentry.utils import json
+from sentry.utils.actor import ActorTuple
 
 STATUSES = {"resolved": "resolved", "ignored": "ignored", "unresolved": "re-opened"}
 SUPPORTED_COMMIT_PROVIDERS = (

+ 1 - 157
src/sentry/models/actor.py

@@ -1,7 +1,5 @@
 from __future__ import annotations
 
-from collections import defaultdict, namedtuple
-from collections.abc import Sequence
 from typing import TYPE_CHECKING, overload
 
 import sentry_sdk
@@ -9,7 +7,6 @@ from django.conf import settings
 from django.db import IntegrityError, models, router, transaction
 from django.db.models.signals import post_save
 from django.forms import model_to_dict
-from rest_framework import serializers
 
 from sentry.backup.dependencies import ImportKind, PrimaryKeyMap
 from sentry.backup.helpers import ImportFlags
@@ -20,6 +17,7 @@ from sentry.db.models.fields.hybrid_cloud_foreign_key import HybridCloudForeignK
 from sentry.models.outbox import OutboxCategory, OutboxScope, RegionOutbox, outbox_context
 from sentry.services.hybrid_cloud.user import RpcUser
 from sentry.services.hybrid_cloud.user.service import user_service
+from sentry.utils.actor import ActorTuple
 
 if TYPE_CHECKING:
     from sentry.models.team import Team
@@ -78,32 +76,6 @@ def fetch_actors_by_actor_ids(
         raise ValueError(f"Cls {cls} is not a valid actor type.")
 
 
-@overload
-def fetch_actor_by_id(cls: type[User], id: int) -> RpcUser:
-    ...
-
-
-@overload
-def fetch_actor_by_id(cls: type[Team], id: int) -> Team:
-    ...
-
-
-def fetch_actor_by_id(cls: type[User] | type[Team], id: int) -> Team | RpcUser:
-    from sentry.models.team import Team
-    from sentry.models.user import User
-
-    if cls is Team:
-        return Team.objects.get(id=id)
-
-    elif cls is User:
-        user = user_service.get_user(id)
-        if user is None:
-            raise User.DoesNotExist()
-        return user
-    else:
-        raise ValueError(f"Cls {cls} is not a valid actor type.")
-
-
 def actor_type_to_string(type: int) -> str | None:
     # `type` will be 0 or 1 and we want to get "team" or "user"
     for k, v in ACTOR_TYPES.items():
@@ -233,134 +205,6 @@ def get_actor_for_team(team: int | Team) -> Actor:
     return actor
 
 
-class ActorTuple(namedtuple("Actor", "id type")):
-    """
-    This is an artifact from before we had the Actor model.
-    We want to eventually drop this model and merge functionality with Actor
-    This should happen more easily if we move GroupAssignee, GroupOwner, etc. to use the Actor model.
-    """
-
-    @property
-    def identifier(self):
-        return f"{self.type.__name__.lower()}:{self.id}"
-
-    @overload
-    @classmethod
-    def from_actor_identifier(cls, actor_identifier: None) -> None:
-        ...
-
-    @overload
-    @classmethod
-    def from_actor_identifier(cls, actor_identifier: int | str) -> ActorTuple:
-        ...
-
-    @classmethod
-    def from_actor_identifier(cls, actor_identifier: int | str | None) -> ActorTuple | None:
-        from sentry.models.team import Team
-        from sentry.models.user import User
-
-        """
-        Returns an Actor tuple corresponding to a User or Team associated with
-        the given identifier.
-
-        Forms `actor_identifier` can take:
-            1231 -> look up User by id
-            "1231" -> look up User by id
-            "user:1231" -> look up User by id
-            "team:1231" -> look up Team by id
-            "maiseythedog" -> look up User by username
-            "maisey@dogsrule.com" -> look up User by primary email
-        """
-
-        if actor_identifier is None:
-            return None
-
-        # If we have an integer, fall back to assuming it's a User
-        if isinstance(actor_identifier, int):
-            return cls(actor_identifier, User)
-
-        # If the actor_identifier is a simple integer as a string,
-        # we're also a User
-        if actor_identifier.isdigit():
-            return cls(int(actor_identifier), User)
-
-        if actor_identifier.startswith("user:"):
-            return cls(int(actor_identifier[5:]), User)
-
-        if actor_identifier.startswith("team:"):
-            return cls(int(actor_identifier[5:]), Team)
-
-        try:
-            user = user_service.get_by_username(username=actor_identifier)[0]
-            return cls(user.id, User)
-        except IndexError as e:
-            raise serializers.ValidationError(f"Unable to resolve actor identifier: {e}")
-
-    @classmethod
-    def from_id(cls, user_id: int | None, team_id: int | None) -> ActorTuple | None:
-        from sentry.models.team import Team
-        from sentry.models.user import User
-
-        if user_id and team_id:
-            raise ValueError("user_id and team_id may not both be specified")
-        if user_id and not team_id:
-            return cls(user_id, User)
-        if team_id and not user_id:
-            return cls(team_id, Team)
-
-        return None
-
-    @classmethod
-    def from_ids(cls, user_ids: Sequence[int], team_ids: Sequence[int]) -> Sequence[ActorTuple]:
-        from sentry.models.team import Team
-        from sentry.models.user import User
-
-        return [
-            *[cls(user_id, User) for user_id in user_ids],
-            *[cls(team_id, Team) for team_id in team_ids],
-        ]
-
-    def resolve(self) -> Team | RpcUser:
-        return fetch_actor_by_id(self.type, self.id)
-
-    def resolve_to_actor(self) -> Actor:
-        from sentry.models.user import User
-
-        obj = self.resolve()
-        if isinstance(obj, (User, RpcUser)):
-            return get_actor_for_user(obj)
-        # Team case. Teams have actors generated as a post_save signal
-        return Actor.objects.get(id=obj.actor_id)
-
-    @classmethod
-    def resolve_many(cls, actors: Sequence[ActorTuple]) -> Sequence[Team | RpcUser]:
-        """
-        Resolve multiple actors at the same time. Returns the result in the same order
-        as the input, minus any actors we couldn't resolve.
-        :param actors:
-        :return:
-        """
-        from sentry.models.user import User
-
-        if not actors:
-            return []
-
-        actors_by_type = defaultdict(list)
-        for actor in actors:
-            actors_by_type[actor.type].append(actor)
-
-        results = {}
-        for model_class, _actors in actors_by_type.items():
-            if model_class == User:
-                for instance in user_service.get_many(filter={"user_ids": [a.id for a in _actors]}):
-                    results[(model_class, instance.id)] = instance
-            else:
-                for instance in model_class.objects.filter(id__in=[a.id for a in _actors]):
-                    results[(model_class, instance.id)] = instance
-
-        return list(filter(None, [results.get((actor.type, actor.id)) for actor in actors]))
-
-
 def handle_team_post_save(instance, **kwargs):
     # we want to create an actor if we don't have one
     if not instance.actor_id:

+ 1 - 1
src/sentry/models/groupowner.py

@@ -96,7 +96,7 @@ class GroupOwner(Model):
         raise NotImplementedError("Unknown Owner")
 
     def owner(self):
-        from sentry.models.actor import ActorTuple
+        from sentry.utils.actor import ActorTuple
 
         if not self.owner_id():
             return None

+ 1 - 1
src/sentry/models/projectownership.py

@@ -14,12 +14,12 @@ from sentry.db.models import Model, region_silo_only_model, sane_repr
 from sentry.db.models.fields import FlexibleForeignKey, JSONField
 from sentry.eventstore.models import Event, GroupEvent
 from sentry.models.activity import Activity
-from sentry.models.actor import ActorTuple
 from sentry.models.group import Group
 from sentry.models.groupowner import OwnerRuleType
 from sentry.ownership.grammar import Rule, load_schema, resolve_actors
 from sentry.types.activity import ActivityType
 from sentry.utils import metrics
+from sentry.utils.actor import ActorTuple
 from sentry.utils.cache import cache
 
 if TYPE_CHECKING:

+ 1 - 1
src/sentry/monitors/models.py

@@ -35,11 +35,11 @@ from sentry.db.models.fields.hybrid_cloud_foreign_key import HybridCloudForeignK
 from sentry.db.models.fields.slug import SentrySlugField
 from sentry.db.models.utils import slugify_instance
 from sentry.locks import locks
-from sentry.models.actor import ActorTuple
 from sentry.models.environment import Environment
 from sentry.models.rule import Rule, RuleSource
 from sentry.monitors.constants import MAX_SLUG_LENGTH
 from sentry.monitors.types import CrontabSchedule, IntervalSchedule
+from sentry.utils.actor import ActorTuple
 from sentry.utils.retries import TimedRetryPolicy
 
 logger = logging.getLogger(__name__)

+ 1 - 1
src/sentry/monitors/serializers.py

@@ -7,12 +7,12 @@ from django.db.models import prefetch_related_objects
 
 from sentry.api.serializers import ProjectSerializerResponse, Serializer, register, serialize
 from sentry.api.serializers.models.actor import ActorSerializer, ActorSerializerResponse
-from sentry.models.actor import ActorTuple
 from sentry.models.project import Project
 from sentry.monitors.utils import fetch_associated_groups
 from sentry.monitors.validators import IntervalNames
 
 from ..models import Environment
+from ..utils.actor import ActorTuple
 from .models import (
     Monitor,
     MonitorCheckIn,

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