Skip to content

Commit 9b607e8

Browse files
shtromzzzeid
authored andcommitted
github: add GitHub class for basic URL and token manipulation
1 parent 9d31c94 commit 9b607e8

3 files changed

Lines changed: 55 additions & 34 deletions

File tree

src/lando/main/scm/git.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
)
2323
from lando.main.scm.helpers import GitPatchHelper, PatchHelper
2424
from lando.settings import LANDO_USER_EMAIL, LANDO_USER_NAME
25-
from lando.utils.github import GitHubAPI
25+
from lando.utils.github import GitHub
2626

2727
from .abstract_scm import AbstractSCM
2828

@@ -113,8 +113,8 @@ def push(
113113
if force_push:
114114
push_command += ["--force"]
115115

116-
if GitHubAPI.is_github_url(push_path):
117-
push_path = GitHubAPI(push_path).authenticated_url
116+
if GitHub.is_supported_url(push_path):
117+
push_path = GitHub(push_path).authenticated_url
118118

119119
push_command += [push_path]
120120

src/lando/main/tests/test_git.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from pathlib import Path
99
from textwrap import dedent
1010
from unittest import mock
11-
from unittest.mock import MagicMock
11+
from unittest.mock import MagicMock, PropertyMock
1212

1313
import pytest
1414

@@ -679,21 +679,35 @@ def mock_github_api_get_token(monkeypatch: pytest.MonkeyPatch):
679679
return mock_get_token
680680

681681

682-
def test_GitSCM_push_get_github_token(
683-
git_repo: Path, mock_github_api_get_token: mock.Mock
682+
def mock_github_authenticated_url(monkeypatch: pytest.MonkeyPatch):
683+
mock_authenticated_url = PropertyMock()
684+
685+
mock_authenticated_url.return_value = (
686+
"ssh+git:ghs_yolo@github.com/some-org/some-repo"
687+
)
688+
689+
monkeypatch.setattr(
690+
"lando.utils.github.GitHub.authenticated_url", mock_authenticated_url
691+
)
692+
693+
return mock_authenticated_url
694+
695+
696+
def test_GitSCM_push_github_authenticated_url(
697+
git_repo: Path, mock_github_authenticated_url: mock.Mock
684698
):
685699
scm = GitSCM(str(git_repo))
686700
scm._git_run = MagicMock()
687701

688702
scm.push("https://github.com/some/repo")
689703

690-
assert scm._git_run.call_count == 1, "_git_run wasn't called when pushing"
704+
assert scm._git_run.call_count >= 1, "_git_run wasn't called when pushing"
691705
assert (
692-
mock_github_api_get_token.call_count == 1
693-
), "_get_github_token wasn't called when pushing to a github-like URL"
706+
mock_github_authenticated_url.call_count == 1
707+
), "GitHub.authenticated_url wasn't accessed when pushing to a github-like URL"
694708
assert (
695709
"git:ghs_yolo@github.com" in scm._git_run.call_args[0][1]
696-
), "github token not found in rewritten push_path"
710+
), "GitHub authenticated_url was not found in rewritten push_path"
697711

698712

699713
@pytest.mark.parametrize(

src/lando/utils/github.py

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,23 @@
1616
)
1717

1818

19-
class GitHubAPI:
20-
"""A simple wrapper that authenticates with and communicates with the GitHub API."""
19+
class GitHub:
20+
"""Work with authentication to GitHub repositories."""
2121

22-
GITHUB_BASE_URL = "https://api.github.com"
22+
# NOTE: This RE takes care of removing the '.git' suffix, to provide normalised URLs.
23+
GITHUB_URL_RE = re.compile(
24+
f"https://{URL_USERINFO_RE.pattern}?github.com/(?P<owner>[-A-Za-z0-9]+)/(?P<repo>[^/]+)(.git)?"
25+
)
2326

2427
repo_url: str
2528
repo_owner: str
2629
repo_name: str
2730
userinfo: str
28-
session: requests.Session
29-
30-
GITHUB_BASE_URL = "https://api.github.com"
31-
# NOTE: This RE takes care of removing the '.git' suffix, to provide normalised URLs.
32-
GITHUB_URL_RE = re.compile(
33-
f"https://{URL_USERINFO_RE.pattern}?github.com/(?P<owner>[-A-Za-z0-9]+)/(?P<repo>[^/]+)(.git)?"
34-
)
3531

3632
def __init__(self, repo_url: str):
3733
self.repo_url = repo_url
3834

39-
parsed_url = self.parse_github_url(repo_url)
35+
parsed_url = self.parse_url(repo_url)
4036

4137
if parsed_url is None:
4238
raise ValueError(f"Cannot parse URL as GitHub repo: {repo_url}")
@@ -45,22 +41,12 @@ def __init__(self, repo_url: str):
4541
self.repo_name = parsed_url["repo"]
4642
self.userinfo = parsed_url["userinfo"]
4743

48-
self.session = requests.Session()
49-
self.session.headers.update(
50-
{
51-
"Authorization": f"Bearer {self._fetch_token()}",
52-
"User-Agent": settings.HTTP_USER_AGENT,
53-
"Accept": "application/vnd.github+json",
54-
"X-GitHub-Api-Version": "2022-11-28",
55-
}
56-
)
57-
5844
@classmethod
59-
def is_github_url(cls, url: str) -> bool:
60-
return cls.parse_github_url(url) is not None
45+
def is_supported_url(cls, url: str) -> bool:
46+
return cls.parse_url(url) is not None
6147

6248
@classmethod
63-
def parse_github_url(cls, url: str) -> re.Match[str] | None:
49+
def parse_url(cls, url: str) -> re.Match[str] | None:
6450
"""Parse GitHub data from URL, or return None if not Github."""
6551
return re.match(cls.GITHUB_URL_RE, url)
6652

@@ -112,6 +98,27 @@ def _fetch_token(self) -> str | None:
11298
)
11399
return asyncio.run(session.get_token())
114100

101+
102+
class GitHubAPI(GitHub):
103+
"""A simple wrapper that authenticates with and communicates with the GitHub API."""
104+
105+
session: requests.Session
106+
107+
GITHUB_BASE_URL = "https://api.github.com"
108+
109+
def __init__(self, repo_url: str):
110+
super().__init__(repo_url)
111+
112+
self.session = requests.Session()
113+
self.session.headers.update(
114+
{
115+
"Authorization": f"Bearer {self._fetch_token()}",
116+
"User-Agent": settings.HTTP_USER_AGENT,
117+
"Accept": "application/vnd.github+json",
118+
"X-GitHub-Api-Version": "2022-11-28",
119+
}
120+
)
121+
115122
def get(self, path: str, *args, **kwargs) -> dict:
116123
"""Send a GET request to the GitHub API with given args and kwargs."""
117124
url = f"{self.GITHUB_BASE_URL}/{path}"

0 commit comments

Comments
 (0)