Skip to content

Commit de9e951

Browse files
authored
Merge pull request #80 from smkent/github-api
Use PyGithub for Github operations in manage-cookie
2 parents faaf379 + 3343a19 commit de9e951

6 files changed

Lines changed: 369 additions & 118 deletions

File tree

cookie_python/manage/github.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import contextlib
2+
import os
3+
from functools import cached_property, lru_cache
4+
from typing import Optional
5+
6+
import github
7+
from github.PullRequest import PullRequest
8+
from github.Repository import Repository
9+
10+
11+
class GithubRepo:
12+
def __init__(self) -> None:
13+
self._gh = github.Github(os.environ["GITHUB_API_TOKEN"])
14+
15+
@cached_property
16+
def username(self) -> str:
17+
return self._gh.get_user().login
18+
19+
@lru_cache # noqa: B019
20+
def find_repo(self, search: str) -> Repository:
21+
if "/" not in search:
22+
search = f"{self.username}/{search}"
23+
if "github.com" in search:
24+
with contextlib.suppress(IndexError):
25+
search = os.path.splitext(search.split(":")[1])[0]
26+
return self._gh.get_repo(search)
27+
28+
def find_pr(
29+
self, repo: Repository, head: str, base: str = "main"
30+
) -> Optional[PullRequest]:
31+
pulls = [
32+
pr
33+
for pr in repo.get_pulls(head=f"{self.username}:{head}", base=base)
34+
]
35+
if not pulls:
36+
return None
37+
if len(pulls) > 1:
38+
raise Exception(f"Multiple PRs found matching {head}")
39+
return pulls[0]

cookie_python/manage/release.py

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,10 @@
66

77

88
def release_patch_version(repo: RepoSandbox) -> None:
9-
releases = (
10-
repo.run(
11-
["gh", "release", "list"],
12-
capture_output=True,
13-
check=True,
14-
)
15-
.stdout.decode("utf-8")
16-
.splitlines()
17-
)
18-
latest_tag = None
19-
for line in releases:
20-
tag, status, *_ = line.split()
21-
if status.lower() == "latest":
22-
latest_tag = tag
23-
break
24-
if not latest_tag:
25-
repo.logger.warning("Unable to find latest version")
26-
return None
27-
check_refs = ["origin/main", latest_tag]
9+
if not repo.latest_release:
10+
repo.logger.warning("Unable to find latest release version")
11+
return
12+
check_refs = ["origin/main", repo.latest_release]
2813
refs = []
2914
for ref in check_refs:
3015
refs.append(
@@ -33,17 +18,18 @@ def release_patch_version(repo: RepoSandbox) -> None:
3318
.strip()
3419
)
3520
if len(refs) == len(check_refs) and len(set(refs)) == 1:
36-
repo.logger.info(f"No new changes since latest release {latest_tag}")
37-
return None
38-
sv = semver.VersionInfo.parse(latest_tag.lstrip("v"))
21+
repo.logger.info(
22+
f"No new changes since latest release {repo.latest_release}"
23+
)
24+
return
25+
sv = semver.VersionInfo.parse(repo.latest_release.lstrip("v"))
3926
next_patch_ver = sv.bump_patch()
4027
new_tag = f"v{next_patch_ver}"
4128
if repo.dry_run:
4229
repo.logger.success(f"Would release new version {new_tag}")
43-
return None
44-
repo.run(["gh", "release", "create", new_tag, "--generate-notes"])
30+
return
31+
repo.create_release(new_tag)
4532
repo.logger.success(f"Released new version {new_tag}")
46-
return None
4733

4834

4935
def release_action(args: Namespace) -> None:

cookie_python/manage/repo.py

Lines changed: 40 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313

1414
import loguru
1515

16+
from .github import GithubRepo
17+
1618

1719
class RepoSandbox:
1820
def __init__(self, repo: str, dry_run: bool = False) -> None:
1921
self._stack = contextlib.ExitStack()
20-
self.repo = repo
22+
self.repo = self.gh.find_repo(repo)
2123
self.branch = "update-cookie"
2224
self.dry_run = dry_run
2325

@@ -32,9 +34,30 @@ def __exit__(
3234
) -> None:
3335
self._stack.close()
3436

37+
@cached_property
38+
def gh(self) -> GithubRepo:
39+
return GithubRepo()
40+
41+
@cached_property
42+
def latest_release(self) -> str:
43+
return self.repo.get_latest_release().title
44+
45+
def create_release(self, tag: str) -> None:
46+
print(f"Should create release {tag}")
47+
# PyGithub's repository create_tag_and_release() doesn't support
48+
# generate_release_notes
49+
self.repo._requester.requestJsonAndCheck( # type: ignore
50+
"POST",
51+
f"/repos/{self.repo.full_name}/releases",
52+
input={
53+
"tag_name": tag,
54+
"generate_release_notes": True,
55+
},
56+
)
57+
3558
@cached_property
3659
def logger(self) -> "loguru.Logger":
37-
return loguru.logger.bind(repo=self.repo)
60+
return loguru.logger.bind(repo=self.repo.full_name)
3861

3962
@cached_property
4063
def tempdir(self) -> Path:
@@ -47,7 +70,9 @@ def tempdir(self) -> Path:
4770
@cached_property
4871
def clone_path(self) -> Path:
4972
subprocess.run(
50-
["git", "clone", self.repo, "repo"], cwd=self.tempdir, check=True
73+
["git", "clone", self.repo.ssh_url, "repo"],
74+
cwd=self.tempdir,
75+
check=True,
5176
)
5277
clone_path = self.tempdir / "repo"
5378
for cmd in (
@@ -107,41 +132,15 @@ def lint_test(self) -> None:
107132
self.logger.error("Resolve errors and exit shell to continue")
108133
self.shell()
109134

110-
def find_existing_pr(self) -> Optional[str]:
111-
with contextlib.suppress(
112-
subprocess.CalledProcessError, json.JSONDecodeError, TypeError
113-
):
114-
for pr in json.loads(
115-
self.run(
116-
[
117-
"gh",
118-
"pr",
119-
"list",
120-
"-H",
121-
self.branch,
122-
"-B",
123-
"main",
124-
"--json",
125-
",".join(("url", "headRefName", "baseRefName")),
126-
],
127-
capture_output=True,
128-
check=True,
129-
).stdout.decode()
130-
):
131-
pr_url = str(pr.pop("url"))
132-
if pr == {"headRefName": self.branch, "baseRefName": "main"}:
133-
return pr_url
134-
return None
135-
136135
def close_existing_pr(self) -> None:
137136
# Locate existing PR
138-
pr_url = self.find_existing_pr()
139-
if pr_url:
137+
pr = self.gh.find_pr(self.repo, self.branch)
138+
if pr:
140139
if self.dry_run:
141-
self.logger.info(f"Would close existing PR {pr_url}")
140+
self.logger.info(f"Would close existing PR {pr.url}")
142141
else:
143-
self.run(["gh", "pr", "close", pr_url])
144-
self.logger.info(f"Closed existing PR {pr_url}")
142+
pr.edit(state="closed")
143+
self.logger.info(f"Closed existing PR {pr.url}")
145144
if self.dry_run:
146145
return
147146
# Delete existing branch
@@ -160,21 +159,10 @@ def open_pr(self, message: str) -> None:
160159
return
161160
self.run(["git", "push", "origin", self.branch])
162161
commit_title, _, *commit_body = message.splitlines()
163-
pr_url = self.run(
164-
[
165-
"gh",
166-
"pr",
167-
"create",
168-
"--title",
169-
commit_title.strip(),
170-
"--body-file",
171-
"-",
172-
"--base",
173-
"main",
174-
"--head",
175-
self.branch,
176-
],
177-
input=os.linesep.join(commit_body).encode("utf-8"),
178-
capture_output=True,
179-
).stdout.decode()
180-
self.logger.success(f"Opened PR {pr_url}")
162+
pr = self.repo.create_pull(
163+
base="main",
164+
head=self.branch,
165+
title=commit_title.strip(),
166+
body=os.linesep.join(commit_body),
167+
)
168+
self.logger.success(f"Opened PR {pr.url}")

0 commit comments

Comments
 (0)