From 442ad50957737e2ecb69b6ec0104ca5385426b22 Mon Sep 17 00:00:00 2001 From: mkdev11 Date: Wed, 29 Apr 2026 22:42:34 +0200 Subject: [PATCH] perf(cache): extend shallow-copy cache helpers to mirror PRs Wire ScoredMirrorPR handling into _build_cache_entry (drop files, copy pr/review_summary/labels/linked_issues) and _isolate_for_downstream (shallow-copy each ScoredMirrorPR plus mutable sub-objects). Add TestMirrorCacheIsolation covering file-blob stripping, store-to-get isolation, and get-to-get isolation for mirror PRs. Closes #696 --- gittensor/classes.py | 34 +++++ .../test_validator_cache_fallback.py | 125 ++++++++++++++++++ 2 files changed, 159 insertions(+) diff --git a/gittensor/classes.py b/gittensor/classes.py index 93bed5e1d..469b058b8 100644 --- a/gittensor/classes.py +++ b/gittensor/classes.py @@ -12,6 +12,7 @@ # Forward-reference only — avoids importing the mirror subpackage at runtime # and prevents accidental coupling. The mirror_* lists below are typed as # strings to defer resolution. + from gittensor.utils.mirror.models import MirrorLinkedIssue, MirrorPullRequest from gittensor.validator.oss_contributions.mirror.scored_pr import ScoredMirrorPR from gittensor.constants import ( @@ -708,6 +709,9 @@ def _build_cache_entry(evaluation: 'MinerEvaluation') -> 'MinerEvaluation': cached.merged_pull_requests = [_pr_for_cache(pr) for pr in evaluation.merged_pull_requests] cached.open_pull_requests = [_pr_for_cache(pr) for pr in evaluation.open_pull_requests] cached.closed_pull_requests = [_pr_for_cache(pr) for pr in evaluation.closed_pull_requests] + cached.mirror_merged_prs = [_scored_mirror_pr_for_cache(s) for s in evaluation.mirror_merged_prs] + cached.mirror_open_prs = [_scored_mirror_pr_for_cache(s) for s in evaluation.mirror_open_prs] + cached.mirror_closed_prs = [_scored_mirror_pr_for_cache(s) for s in evaluation.mirror_closed_prs] return cached @staticmethod @@ -720,6 +724,9 @@ def _isolate_for_downstream(cached_eval: 'MinerEvaluation') -> 'MinerEvaluation' copy_eval.merged_pull_requests = [_pr_with_fresh_issues(pr) for pr in cached_eval.merged_pull_requests] copy_eval.open_pull_requests = [_pr_with_fresh_issues(pr) for pr in cached_eval.open_pull_requests] copy_eval.closed_pull_requests = [_pr_with_fresh_issues(pr) for pr in cached_eval.closed_pull_requests] + copy_eval.mirror_merged_prs = [_scored_mirror_pr_isolated(s) for s in cached_eval.mirror_merged_prs] + copy_eval.mirror_open_prs = [_scored_mirror_pr_isolated(s) for s in cached_eval.mirror_open_prs] + copy_eval.mirror_closed_prs = [_scored_mirror_pr_isolated(s) for s in cached_eval.mirror_closed_prs] return copy_eval @@ -735,3 +742,30 @@ def _pr_with_fresh_issues(pr: 'PullRequest') -> 'PullRequest': if pr.issues is not None: pr_copy.issues = [copy.copy(issue) for issue in pr.issues] return pr_copy + + +def _mirror_linked_issue_copy(issue: 'MirrorLinkedIssue') -> 'MirrorLinkedIssue': + issue_copy = copy.copy(issue) + issue_copy.labels = [copy.copy(label) for label in issue.labels] + return issue_copy + + +def _mirror_pr_copy(pr: 'MirrorPullRequest') -> 'MirrorPullRequest': + pr_copy = copy.copy(pr) + pr_copy.review_summary = copy.copy(pr.review_summary) + pr_copy.labels = [copy.copy(label) for label in pr.labels] + pr_copy.linked_issues = [_mirror_linked_issue_copy(li) for li in pr.linked_issues] + return pr_copy + + +def _scored_mirror_pr_for_cache(scored: 'ScoredMirrorPR') -> 'ScoredMirrorPR': + scored_copy = copy.copy(scored) + scored_copy.pr = _mirror_pr_copy(scored.pr) + scored_copy.files = None + return scored_copy + + +def _scored_mirror_pr_isolated(scored: 'ScoredMirrorPR') -> 'ScoredMirrorPR': + scored_copy = copy.copy(scored) + scored_copy.pr = _mirror_pr_copy(scored.pr) + return scored_copy diff --git a/tests/validator/test_validator_cache_fallback.py b/tests/validator/test_validator_cache_fallback.py index 89667d195..bda5a193c 100644 --- a/tests/validator/test_validator_cache_fallback.py +++ b/tests/validator/test_validator_cache_fallback.py @@ -4,6 +4,8 @@ from typing import cast from gittensor.classes import FileChange, Issue, MinerEvaluation, MinerEvaluationCache, PRState, PullRequest +from gittensor.utils.mirror.models import MirrorFile, MirrorLinkedIssue, MirrorPullRequest, MirrorReviewSummary +from gittensor.validator.oss_contributions.mirror.scored_pr import ScoredMirrorPR from neurons.validator import Validator @@ -175,3 +177,126 @@ def test_store_isolates_cache_from_source_issue_mutations(self): cached_issues = cached.merged_pull_requests[0].issues assert cached_issues is not None assert cached_issues[0].discovery_earned_score == 0.0 + + +def _make_scored_mirror_pr(pr_number: int = 100, blob: str = 'heavy file content') -> ScoredMirrorPR: + now = datetime.now(timezone.utc) + linked_issue = MirrorLinkedIssue( + number=77, + title='mirror linked issue', + state='CLOSED', + state_reason='COMPLETED', + author_github_id='99', + author_association='CONTRIBUTOR', + created_at=now, + closed_at=now, + updated_at=now, + is_transferred=False, + solved_by_pr=pr_number, + ) + pr = MirrorPullRequest( + repo_full_name='owner/repo', + pr_number=pr_number, + title='mirror pr', + body=None, + state='MERGED', + author_github_id='12345', + author_login='miner', + author_association='CONTRIBUTOR', + created_at=now, + closed_at=None, + merged_at=now, + last_edited_at=None, + edited_after_merge=False, + hours_since_merge=0.0, + merged_by_login='maintainer', + base_ref='main', + head_ref='feature', + head_repo_full_name='owner/repo', + default_branch='main', + head_sha='head', + base_sha='base', + merge_base_sha='merge-base', + additions=3, + deletions=1, + commits_count=1, + scoring_data_stored=True, + review_summary=MirrorReviewSummary(maintainer_changes_requested_count=1), + linked_issues=[linked_issue], + ) + scored = ScoredMirrorPR(pr=pr, base_score=8.0, token_score=12.0) + scored.files = [ + MirrorFile( + filename='src/lib.py', + previous_filename=None, + status='modified', + additions=3, + deletions=1, + changes=4, + is_binary=False, + head_content=blob, + base_content=blob, + ) + ] + return scored + + +def _build_mirror_eval() -> MinerEvaluation: + ev = MinerEvaluation(uid=1, hotkey='hotkey_1', github_id='12345') + ev.mirror_merged_prs = [_make_scored_mirror_pr()] + return ev + + +class TestMirrorCacheIsolation: + """Mirror-PR counterpart to TestCacheIsolation: validate that + ScoredMirrorPR.files are dropped, and mutable mirror sub-objects + (pr, review_summary, labels, linked_issues) are isolated.""" + + def test_cache_drops_mirror_files(self): + cache = MinerEvaluationCache() + source = _build_mirror_eval() + + cache.store(source) + + # Source files must remain intact. + assert source.mirror_merged_prs[0].files is not None + assert source.mirror_merged_prs[0].files[0].head_content == 'heavy file content' + + cached = cache.get(uid=1, hotkey='hotkey_1', github_id='12345') + assert cached is not None + assert cached.mirror_merged_prs[0].files is None + + def test_store_isolates_mirror_fields_from_source_mutations(self): + cache = MinerEvaluationCache() + source = _build_mirror_eval() + cache.store(source) + + source.mirror_merged_prs[0].base_score = 999.0 + source.mirror_merged_prs[0].pr.title = 'mutated' + source.mirror_merged_prs[0].pr.review_summary.maintainer_changes_requested_count = 99 + source.mirror_merged_prs[0].pr.linked_issues[0].title = 'mutated issue' + + cached = cache.get(uid=1, hotkey='hotkey_1', github_id='12345') + assert cached is not None + assert cached.mirror_merged_prs[0].base_score == 8.0 + assert cached.mirror_merged_prs[0].pr.title == 'mirror pr' + assert cached.mirror_merged_prs[0].pr.review_summary.maintainer_changes_requested_count == 1 + assert cached.mirror_merged_prs[0].pr.linked_issues[0].title == 'mirror linked issue' + + def test_get_returns_isolated_mirror_copies(self): + cache = MinerEvaluationCache() + cache.store(_build_mirror_eval()) + + first = cache.get(uid=1, hotkey='hotkey_1', github_id='12345') + assert first is not None + first.mirror_merged_prs[0].base_score = 999.0 + first.mirror_merged_prs[0].pr.title = 'mutated' + first.mirror_merged_prs[0].pr.review_summary.maintainer_changes_requested_count = 99 + first.mirror_merged_prs[0].pr.linked_issues[0].title = 'mutated issue' + + second = cache.get(uid=1, hotkey='hotkey_1', github_id='12345') + assert second is not None + assert second.mirror_merged_prs[0].base_score == 8.0 + assert second.mirror_merged_prs[0].pr.title == 'mirror pr' + assert second.mirror_merged_prs[0].pr.review_summary.maintainer_changes_requested_count == 1 + assert second.mirror_merged_prs[0].pr.linked_issues[0].title == 'mirror linked issue'