From 64ef7ce3b0cb36c873584563de11a534ea5cdd68 Mon Sep 17 00:00:00 2001 From: Dan Fuller Date: Tue, 12 May 2026 16:39:54 -0700 Subject: [PATCH 1/2] chore(repositories): Switch over `SeerProjectRepository` attribute uses of `Project` and `Repository` to use `ProjectRepository` As per the title, follow up to https://github.com/getsentry/sentry/pull/115456 --- src/sentry/seer/autofix/utils.py | 25 +++++++++++++++-------- src/sentry/tasks/seer/night_shift/cron.py | 4 +++- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/sentry/seer/autofix/utils.py b/src/sentry/seer/autofix/utils.py index 2bb5a4c57b23ff..1bea780db6b8e4 100644 --- a/src/sentry/seer/autofix/utils.py +++ b/src/sentry/seer/autofix/utils.py @@ -543,9 +543,13 @@ def _write_preferences_to_sentry_db( # Create branch overrides using the created project repos. overrides_to_create: list[SeerProjectRepositoryBranchOverride] = [] for seer_project_repo in created_project_repos: - for override in overrides_by_key.get( - (seer_project_repo.project_id, seer_project_repo.repository_id), [] - ): + pr = seer_project_repo.project_repository + key = ( + (pr.project_id, pr.repository_id) + if pr is not None + else (seer_project_repo.project_id, seer_project_repo.repository_id) + ) + for override in overrides_by_key.get(key, []): overrides_to_create.append( SeerProjectRepositoryBranchOverride( seer_project_repository=seer_project_repo, @@ -608,7 +612,8 @@ def build_repo_definition_from_project_repo( """Build a SeerRepoDefinition from a SeerProjectRepository with its joined Repository. Returns None if Repository name is invalid.""" - repo = seer_project_repo.repository + pr = seer_project_repo.project_repository + repo = pr.repository if pr is not None else seer_project_repo.repository repo_name_sections = repo.name.split("/") if len(repo_name_sections) < 2: sentry_sdk.capture_exception(ValueError(f"Invalid repository name format: {repo.name}")) @@ -670,7 +675,7 @@ def read_preference_from_sentry_db(project: Project) -> SeerProjectPreference: SeerProjectRepository.objects.filter( project=project, repository__status=ObjectStatus.ACTIVE ) - .select_related("repository") + .select_related("repository", "project_repository", "project_repository__repository") .prefetch_related("branch_overrides") ) repo_definitions = [ @@ -708,11 +713,13 @@ def bulk_read_preferences_from_sentry_db( else: seer_repo_qs = SeerProjectRepository.objects.filter( project_id__in=project_ids, repository__status=ObjectStatus.ACTIVE - ).select_related("repository") - for project_repo in seer_repo_qs.prefetch_related("branch_overrides"): - repo_def = build_repo_definition_from_project_repo(project_repo) + ).select_related("repository", "project_repository", "project_repository__repository") + for seer_repo in seer_repo_qs.prefetch_related("branch_overrides"): + repo_def = build_repo_definition_from_project_repo(seer_repo) if repo_def is not None: - repo_definitions_by_project[project_repo.project_id].append(repo_def) + pr = seer_repo.project_repository + pid = pr.project_id if pr is not None else seer_repo.project_id + repo_definitions_by_project[pid].append(repo_def) # get_value_bulk_id returns None for missing options, unlike project.get_option # which automatically falls back to the registered well-known key default. diff --git a/src/sentry/tasks/seer/night_shift/cron.py b/src/sentry/tasks/seer/night_shift/cron.py index c52c31715d069d..8c62f3dfd062a1 100644 --- a/src/sentry/tasks/seer/night_shift/cron.py +++ b/src/sentry/tasks/seer/night_shift/cron.py @@ -123,7 +123,9 @@ def schedule_night_shift( ), step=1000, ): - seer_org_ids.add(spr.project.organization_id) + pr = spr.project_repository + project = pr.project if pr is not None else spr.project + seer_org_ids.add(project.organization_id) logger.info( "night_shift.schedule_org_ids_collected", From b65e1fa5b4a8cdf1f7646359ad8220cf6396b6fd Mon Sep 17 00:00:00 2001 From: Dan Fuller Date: Tue, 12 May 2026 16:49:02 -0700 Subject: [PATCH 2/2] guard better --- src/sentry/seer/autofix/utils.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/sentry/seer/autofix/utils.py b/src/sentry/seer/autofix/utils.py index 1bea780db6b8e4..4d55e72ec34c05 100644 --- a/src/sentry/seer/autofix/utils.py +++ b/src/sentry/seer/autofix/utils.py @@ -608,12 +608,16 @@ def clear_preference_automation_handoff(project: Project) -> None: def build_repo_definition_from_project_repo( seer_project_repo: SeerProjectRepository, + use_project_repository_fk: bool = False, ) -> SeerRepoDefinition | None: """Build a SeerRepoDefinition from a SeerProjectRepository with its joined Repository. Returns None if Repository name is invalid.""" - pr = seer_project_repo.project_repository - repo = pr.repository if pr is not None else seer_project_repo.repository + if use_project_repository_fk: + pr = seer_project_repo.project_repository + repo = pr.repository if pr is not None else seer_project_repo.repository + else: + repo = seer_project_repo.repository repo_name_sections = repo.name.split("/") if len(repo_name_sections) < 2: sentry_sdk.capture_exception(ValueError(f"Invalid repository name format: {repo.name}")) @@ -661,7 +665,8 @@ def build_automation_handoff( def read_preference_from_sentry_db(project: Project) -> SeerProjectPreference: """Read a single project's Seer preferences from Sentry DB.""" - if features.has("organizations:project-repository-fk-reads", project.organization): + use_fk = features.has("organizations:project-repository-fk-reads", project.organization) + if use_fk: seer_project_repo_qs = ( SeerProjectRepository.objects.filter( project_repository__project=project, @@ -681,7 +686,12 @@ def read_preference_from_sentry_db(project: Project) -> SeerProjectPreference: repo_definitions = [ repo_def for project_repo in seer_project_repo_qs - if (repo_def := build_repo_definition_from_project_repo(project_repo)) is not None + if ( + repo_def := build_repo_definition_from_project_repo( + project_repo, use_project_repository_fk=use_fk + ) + ) + is not None ] return SeerProjectPreference( @@ -705,7 +715,8 @@ def bulk_read_preferences_from_sentry_db( org = Organization.objects.get(id=organization_id) repo_definitions_by_project: defaultdict[int, list[SeerRepoDefinition]] = defaultdict(list) - if features.has("organizations:project-repository-fk-reads", org): + use_fk = features.has("organizations:project-repository-fk-reads", org) + if use_fk: seer_repo_qs = SeerProjectRepository.objects.filter( project_repository__project_id__in=project_ids, project_repository__repository__status=ObjectStatus.ACTIVE, @@ -715,10 +726,15 @@ def bulk_read_preferences_from_sentry_db( project_id__in=project_ids, repository__status=ObjectStatus.ACTIVE ).select_related("repository", "project_repository", "project_repository__repository") for seer_repo in seer_repo_qs.prefetch_related("branch_overrides"): - repo_def = build_repo_definition_from_project_repo(seer_repo) + repo_def = build_repo_definition_from_project_repo( + seer_repo, use_project_repository_fk=use_fk + ) if repo_def is not None: - pr = seer_repo.project_repository - pid = pr.project_id if pr is not None else seer_repo.project_id + if use_fk: + pr = seer_repo.project_repository + pid = pr.project_id if pr is not None else seer_repo.project_id + else: + pid = seer_repo.project_id repo_definitions_by_project[pid].append(repo_def) # get_value_bulk_id returns None for missing options, unlike project.get_option