Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/sentry/api/endpoints/event_owners.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
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
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/api/fields/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
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)
Expand Down
3 changes: 2 additions & 1 deletion src/sentry/api/helpers/group_index/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
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
Expand Down Expand Up @@ -56,6 +56,7 @@
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
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/api/serializers/rest_framework/mentions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

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
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/integrations/slack/message_builder/issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
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
Expand All @@ -60,6 +59,7 @@
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 = (
Expand Down
158 changes: 1 addition & 157 deletions src/sentry/models/actor.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
from __future__ import annotations

from collections import defaultdict, namedtuple
from collections.abc import Sequence
from typing import TYPE_CHECKING, overload

import sentry_sdk
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
Expand All @@ -20,6 +17,7 @@
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
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/models/groupowner.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def owner_id(self):
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
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/models/projectownership.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
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:
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/monitors/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@
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__)
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/monitors/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

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,
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/notifications/utils/participants.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from django.db.models import Q

from sentry import features
from sentry.models.actor import ActorTuple
from sentry.models.commit import Commit
from sentry.models.group import Group
from sentry.models.groupassignee import GroupAssignee
Expand Down Expand Up @@ -37,6 +36,7 @@
from sentry.services.hybrid_cloud.user_option import get_option_from_list, user_option_service
from sentry.types.integrations import ExternalProviders, get_provider_enum_from_string
from sentry.utils import json, metrics
from sentry.utils.actor import ActorTuple
from sentry.utils.committers import AuthorCommitsSerialized, get_serialized_event_file_committers

if TYPE_CHECKING:
Expand Down
4 changes: 2 additions & 2 deletions src/sentry/ownership/grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
from rest_framework.serializers import ValidationError

from sentry.eventstore.models import EventSubjectTemplateData
from sentry.models.actor import ActorTuple
from sentry.models.integrations.repository_project_path_config import RepositoryProjectPathConfig
from sentry.models.organizationmember import OrganizationMember
from sentry.services.hybrid_cloud.user.service import user_service
from sentry.utils.actor import ActorTuple
from sentry.utils.codeowners import codeowners_match
from sentry.utils.event_frames import find_stack_frames, get_sdk_name, munged_filename_and_frames
from sentry.utils.glob import glob_match
Expand Down Expand Up @@ -416,9 +416,9 @@ def resolve_actors(owners: Iterable[Owner], project_id: int) -> Mapping[Owner, A
"""Convert a list of Owner objects into a dictionary
of {Owner: Actor} pairs. Actors not identified are returned
as None."""
from sentry.models.actor import ActorTuple
from sentry.models.team import Team
from sentry.models.user import User
from sentry.utils.actor import ActorTuple

if not owners:
return {}
Expand Down
Loading
Loading