-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgithub_client.py
More file actions
86 lines (73 loc) · 2.54 KB
/
Copy pathgithub_client.py
File metadata and controls
86 lines (73 loc) · 2.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
"""
Thin PyGithub wrapper used by both the MCP tools and the FastAPI inspection
router. Kept intentionally small so students can extend it in challenges.
"""
import os
from typing import List
from github import Github, GithubException
from models import PRDiff, ReviewComment
def _client() -> Github:
token = os.getenv("GITHUB_TOKEN")
if not token:
raise RuntimeError(
"GITHUB_TOKEN is not set. Add it to .env to call the GitHub API."
)
return Github(token)
def get_pr_diff(owner: str, repo: str, number: int) -> PRDiff:
"""Fetch a PR's unified diff plus its list of changed files."""
gh = _client()
try:
repository = gh.get_repo(f"{owner}/{repo}")
pr = repository.get_pull(number)
except GithubException as exc:
raise RuntimeError(f"GitHub error fetching PR: {exc.data}") from exc
# PyGithub exposes the raw diff via the PR's patch URL.
import httpx
headers = {
"Authorization": f"Bearer {os.getenv('GITHUB_TOKEN')}",
"Accept": "application/vnd.github.v3.diff",
}
resp = httpx.get(pr.url, headers=headers, timeout=30.0)
resp.raise_for_status()
diff_text = resp.text
changed = [f.filename for f in pr.get_files()]
return PRDiff(
owner=owner,
repo=repo,
pr_number=number,
title=pr.title,
diff=diff_text,
changed_files=changed,
)
def post_review(
owner: str,
repo: str,
number: int,
comments: List[ReviewComment],
summary: str = "",
) -> dict:
"""Post a review back to the PR as a single review with inline comments."""
gh = _client()
repository = gh.get_repo(f"{owner}/{repo}")
pr = repository.get_pull(number)
# Map our severity into a prefix so reviewers immediately see criticality.
prefix = {"info": "[info]", "warning": "[warning]", "critical": "[critical]"}
# Use PyGithub's create_review with a list of comment payloads. We pin every
# comment to the PR's latest commit so line anchors resolve correctly.
commit = list(pr.get_commits())[-1]
payload = []
for c in comments:
payload.append(
{
"path": c.file,
"position": c.line,
"body": f"{prefix.get(c.severity, '')} {c.suggestion}".strip(),
}
)
review = pr.create_review(
commit=commit,
body=summary or "Automated review from mcp-pr-review-server.",
event="COMMENT",
comments=payload,
)
return {"review_id": review.id, "comment_count": len(payload)}