From 8f1c5117dd0b5f2bc28c4e7247b7657c7b2399ef Mon Sep 17 00:00:00 2001 From: Andrew Pollack-Gray Date: Tue, 17 Mar 2026 00:35:44 -0700 Subject: [PATCH] [release] Add GitHubClient: pull request support (#61767) Add GitHubPullUser, GitHubPull dataclasses and GitHubRepo.get_pull(). Tests cover successful fetch, 404 error, and auth header verification. Signed-off-by: andrew --- release/ray_release/github_client.py | 28 ++++++++++ .../ray_release/tests/test_github_client.py | 55 +++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/release/ray_release/github_client.py b/release/ray_release/github_client.py index db82e7d4518b..75d2a92d4469 100644 --- a/release/ray_release/github_client.py +++ b/release/ray_release/github_client.py @@ -20,6 +20,29 @@ def __init__(self, status: int, data: Any = None, headers: Any = None) -> None: super().__init__(f"GitHub API error {status}: {data}") +@dataclass +class GitHubPullUser: + """The author of a pull request.""" + + login: str + + @classmethod + def from_dict(cls, data: dict) -> "GitHubPullUser": + return cls(login=data["login"]) + + +@dataclass +class GitHubPull: + """A GitHub pull request.""" + + number: int + user: GitHubPullUser + + @classmethod + def from_dict(cls, data: dict) -> "GitHubPull": + return cls(number=data["number"], user=GitHubPullUser.from_dict(data["user"])) + + @dataclass class GitHubRepo: """A GitHub repository.""" @@ -27,6 +50,11 @@ class GitHubRepo: full_name: str _client: "GitHubClient" + def get_pull(self, number: int) -> GitHubPull: + """Return a single pull request by number.""" + data = self._client._get(f"/repos/{self.full_name}/pulls/{number}") + return GitHubPull.from_dict(data) + class GitHubClient: """Authenticated client for the GitHub REST API.""" diff --git a/release/ray_release/tests/test_github_client.py b/release/ray_release/tests/test_github_client.py index 180390e4e1ac..6c2dc840b105 100644 --- a/release/ray_release/tests/test_github_client.py +++ b/release/ray_release/tests/test_github_client.py @@ -8,10 +8,12 @@ import sys import pytest +import responses from ray_release.github_client import ( GitHubClient, GitHubException, + GitHubPull, GitHubRepo, ) @@ -27,6 +29,59 @@ def _repo(client: GitHubClient = None) -> GitHubRepo: return (_client() if client is None else client).get_repo(REPO) +# --------------------------------------------------------------------------- +# GitHubClient — auth headers +# --------------------------------------------------------------------------- + + +@responses.activate +def test_auth_header_is_sent(): + responses.add( + responses.GET, + f"{BASE}/repos/{REPO}/pulls/1", + json={"number": 1, "user": {"login": "octocat"}}, + ) + client = GitHubClient("my-secret-token") + client.get_repo(REPO).get_pull(1) + + sent_headers = responses.calls[0].request.headers + assert sent_headers["Authorization"] == "Bearer my-secret-token" + assert sent_headers["Accept"] == "application/vnd.github+json" + assert "X-GitHub-Api-Version" in sent_headers + + +# --------------------------------------------------------------------------- +# GitHubRepo.get_pull +# --------------------------------------------------------------------------- + + +@responses.activate +def test_get_pull_returns_number_and_user_login(): + responses.add( + responses.GET, + f"{BASE}/repos/{REPO}/pulls/99", + json={"number": 99, "user": {"login": "octocat"}}, + ) + pull = _repo().get_pull(99) + + assert isinstance(pull, GitHubPull) + assert pull.number == 99 + assert pull.user.login == "octocat" + + +@responses.activate +def test_get_pull_raises_on_error(): + responses.add( + responses.GET, + f"{BASE}/repos/{REPO}/pulls/9999", + json={"message": "Not Found"}, + status=404, + ) + with pytest.raises(GitHubException) as exc_info: + _repo().get_pull(9999) + assert exc_info.value.status == 404 + + # --------------------------------------------------------------------------- # GitHubException # ---------------------------------------------------------------------------