Skip to content

Commit 7a604c7

Browse files
authored
Use mock github when in dry run mode (#355)
1 parent 7e04bc4 commit 7a604c7

File tree

7 files changed

+112
-43
lines changed

7 files changed

+112
-43
lines changed

jupyter_releaser/actions/common.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import os
2+
import tempfile
23
from contextlib import contextmanager
34

5+
from jupyter_releaser.util import ensure_mock_github
46
from jupyter_releaser.util import run as _run
57

68

@@ -30,6 +32,12 @@ def setup():
3032
print(f"Using GITHUB_REF: {ref}")
3133
os.environ["RH_BRANCH"] = "/".join(ref.split("/")[2:])
3234

35+
if os.environ.get("RH_DRY_RUN", "").lower() == "true":
36+
static_dir = os.path.join(tempfile.gettempdir(), "gh_static")
37+
os.makedirs(static_dir, exist_ok=True)
38+
os.environ["RH_GITHUB_STATIC_DIR"] = static_dir
39+
ensure_mock_github()
40+
3341

3442
def run_action(target, *args, **kwargs):
3543
with make_group(target):

jupyter_releaser/changelog.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import re
44
from pathlib import Path
55

6-
from ghapi.core import GhApi
76
from github_activity import generate_activity_md
87

98
from jupyter_releaser import util
@@ -13,7 +12,7 @@
1312
PR_PREFIX = "Automated Changelog Entry"
1413

1514

16-
def format_pr_entry(target, number, auth=None):
15+
def format_pr_entry(target, number, auth=None, dry_run=False):
1716
"""Format a PR entry in the style used by our changelogs.
1817
1918
Parameters
@@ -24,14 +23,16 @@ def format_pr_entry(target, number, auth=None):
2423
The PR number to resolve
2524
auth : str, optional
2625
The GitHub authorization token
26+
dry_run: bool, optional
27+
Whether this is a dry run.
2728
2829
Returns
2930
-------
3031
str
3132
A formatted PR entry
3233
"""
3334
owner, repo = target.split("/")
34-
gh = GhApi(owner=owner, repo=repo, token=auth)
35+
gh = util.get_gh_object(dry_run=dry_run, owner=owner, repo=repo, token=auth)
3536
pull = gh.pulls.get(number)
3637
title = pull.title
3738
url = pull.html_url
@@ -51,6 +52,7 @@ def get_version_entry(
5152
until=None,
5253
auth=None,
5354
resolve_backports=False,
55+
dry_run=False,
5456
):
5557
"""Get a changelog for the changes since the last tag on the given branch.
5658
@@ -74,6 +76,8 @@ def get_version_entry(
7476
The GitHub authorization token
7577
resolve_backports: bool, optional
7678
Whether to resolve backports to the original PR
79+
dry_run: bool, optional
80+
Whether this is a dry run.
7781
7882
Returns
7983
-------
@@ -120,7 +124,7 @@ def get_version_entry(
120124
# Look for a backport, either manual or automatic.
121125
match = re.search(r"Backport PR #(\d+) on branch", line)
122126
if match:
123-
entry[ind] = format_pr_entry(repo, match.groups()[0])
127+
entry[ind] = format_pr_entry(repo, match.groups()[0], dry_run=dry_run)
124128

125129
# Remove github actions PRs
126130
gh_actions = "[@github-actions](https://github.com/github-actions)"

jupyter_releaser/cli.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -578,11 +578,12 @@ def draft_release(
578578

579579
@main.command()
580580
@add_options(auth_options)
581+
@add_options(dry_run_options)
581582
@click.argument("release-url", nargs=1)
582583
@use_checkout_dir()
583-
def delete_release(auth, release_url):
584+
def delete_release(auth, dry_run, release_url):
584585
"""Delete a draft GitHub release by url to the release page"""
585-
lib.delete_release(auth, release_url)
586+
lib.delete_release(auth, release_url, dry_run)
586587

587588

588589
@main.command()
@@ -675,11 +676,12 @@ def publish_assets(
675676

676677
@main.command()
677678
@add_options(auth_options)
679+
@add_options(dry_run_options)
678680
@click.argument("release-url", nargs=1)
679681
@use_checkout_dir()
680-
def publish_release(auth, release_url):
682+
def publish_release(auth, dry_run, release_url):
681683
"""Publish GitHub release"""
682-
lib.publish_release(auth, release_url)
684+
lib.publish_release(auth, dry_run, release_url)
683685

684686

685687
@main.command()

jupyter_releaser/lib.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import requests
1616
import toml
17-
from ghapi.core import GhApi
1817
from packaging.version import parse as parse_version
1918
from pkginfo import SDist, Wheel
2019

@@ -183,17 +182,14 @@ def make_changelog_pr(auth, branch, repo, title, commit_message, body, dry_run=F
183182

184183
# Create the pull
185184
owner, repo_name = repo.split("/")
186-
gh = GhApi(owner=owner, repo=repo_name, token=auth)
185+
gh = util.get_gh_object(dry_run=dry_run, owner=owner, repo=repo_name, token=auth)
187186

188187
base = branch
189188
head = pr_branch
190189
maintainer_can_modify = True
191190

192-
if dry_run:
193-
util.log("Skipping pull request due to dry run")
194-
return
195-
196-
util.run(f"git push origin {pr_branch}")
191+
if not dry_run:
192+
util.run(f"git push origin {pr_branch}")
197193

198194
# title, head, base, body, maintainer_can_modify, draft, issue
199195
pull = gh.pulls.create(title, head, base, body, maintainer_can_modify, False, None)
@@ -255,11 +251,8 @@ def draft_release(
255251
util.log(post_version_message.format(post_version=post_version))
256252
util.run(f'git commit -a -m "Bump to {post_version}"')
257253

258-
if dry_run:
259-
return
260-
261254
owner, repo_name = repo.split("/")
262-
gh = GhApi(owner=owner, repo=repo_name, token=auth)
255+
gh = util.get_gh_object(dry_run=dry_run, owner=owner, repo=repo_name, token=auth)
263256

264257
# Remove draft releases over a day old
265258
if bool(os.environ.get("GITHUB_ACTIONS")):
@@ -273,7 +266,7 @@ def draft_release(
273266
gh.repos.delete_release(release.id)
274267

275268
remote_url = util.run("git config --get remote.origin.url")
276-
if not os.path.exists(remote_url):
269+
if not dry_run and not os.path.exists(remote_url):
277270
util.run(f"git push origin HEAD:{branch} --follow-tags --tags")
278271

279272
util.log(f"Creating release for {version}")
@@ -292,14 +285,14 @@ def draft_release(
292285
util.actions_output("release_url", release.html_url)
293286

294287

295-
def delete_release(auth, release_url):
288+
def delete_release(auth, release_url, dry_run=False):
296289
"""Delete a draft GitHub release by url to the release page"""
297290
match = re.match(util.RELEASE_HTML_PATTERN, release_url)
298291
match = match or re.match(util.RELEASE_API_PATTERN, release_url)
299292
if not match:
300293
raise ValueError(f"Release url is not valid: {release_url}")
301294

302-
gh = GhApi(owner=match["owner"], repo=match["repo"], token=auth)
295+
gh = util.get_gh_object(dry_run=dry_run, owner=match["owner"], repo=match["repo"], token=auth)
303296
release = util.release_for_url(gh, release_url)
304297
for asset in release.assets:
305298
gh.repos.delete_release_asset(asset.id)
@@ -320,7 +313,8 @@ def extract_release(
320313
"""Download and verify assets from a draft GitHub release"""
321314
match = parse_release_url(release_url)
322315
owner, repo = match["owner"], match["repo"]
323-
gh = GhApi(owner=owner, repo=repo, token=auth)
316+
317+
gh = util.get_gh_object(dry_run=dry_run, owner=owner, repo=repo, token=auth)
324318
release = util.release_for_url(gh, release_url)
325319
branch = release.target_commitish
326320
assets = release.assets
@@ -494,14 +488,14 @@ def publish_assets(
494488
util.log("No files to upload")
495489

496490

497-
def publish_release(auth, release_url):
491+
def publish_release(auth, dry_run, release_url):
498492
"""Publish GitHub release"""
499493
util.log(f"Publishing {release_url}")
500494

501495
match = parse_release_url(release_url)
502496

503497
# Take the release out of draft
504-
gh = GhApi(owner=match["owner"], repo=match["repo"], token=auth)
498+
gh = util.get_gh_object(dry_run=dry_run, owner=match["owner"], repo=match["repo"], token=auth)
505499
release = util.release_for_url(gh, release_url)
506500

507501
release = gh.repos.update_release(
@@ -625,7 +619,8 @@ def forwardport_changelog(auth, ref, branch, repo, username, changelog_path, dry
625619
"""Forwardport Changelog Entries to the Default Branch"""
626620
# Set up the git repo with the branch
627621
match = parse_release_url(release_url)
628-
gh = GhApi(owner=match["owner"], repo=match["repo"], token=auth)
622+
623+
gh = util.get_gh_object(dry_run=dry_run, owner=match["owner"], repo=match["repo"], token=auth)
629624
release = util.release_for_url(gh, release_url)
630625
tag = release.tag_name
631626
source_branch = release.target_commitish

jupyter_releaser/mock_github.py

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import atexit
22
import datetime
33
import os
4+
import pickle
45
import tempfile
56
import uuid
67
from typing import Dict, List
@@ -13,14 +14,34 @@
1314

1415
app = FastAPI()
1516

16-
static_dir = tempfile.TemporaryDirectory()
17-
atexit.register(static_dir.cleanup)
18-
app.mount("/static", StaticFiles(directory=static_dir.name), name="static")
17+
if "RH_GITHUB_STATIC_DIR" in os.environ:
18+
static_dir = os.environ["RH_GITHUB_STATIC_DIR"]
19+
else:
20+
static_dir_obj = tempfile.TemporaryDirectory()
21+
atexit.register(static_dir_obj.cleanup)
22+
static_dir = static_dir_obj.name
1923

20-
releases: Dict[int, "Release"] = {}
21-
pulls: Dict[int, "PullRequest"] = {}
22-
release_ids_for_asset: Dict[int, int] = {}
23-
tag_refs: Dict[str, "Tag"] = {}
24+
app.mount("/static", StaticFiles(directory=static_dir), name="static")
25+
26+
27+
def load_from_pickle(name):
28+
source_file = os.path.join(static_dir, name + ".pkl")
29+
if not os.path.exists(source_file):
30+
return {}
31+
with open(source_file, "rb") as fid:
32+
return pickle.load(fid)
33+
34+
35+
def write_to_pickle(name, data):
36+
source_file = os.path.join(static_dir, name + ".pkl")
37+
with open(source_file, "wb") as fid:
38+
pickle.dump(data, fid)
39+
40+
41+
releases: Dict[int, "Release"] = load_from_pickle("releases")
42+
pulls: Dict[int, "PullRequest"] = load_from_pickle("pulls")
43+
release_ids_for_asset: Dict[int, int] = load_from_pickle("release_ids_for_asset")
44+
tag_refs: Dict[str, "Tag"] = load_from_pickle("tag_refs")
2445

2546

2647
class Asset(BaseModel):
@@ -112,6 +133,7 @@ async def create_a_release(owner: str, repo: str, request: Request) -> Release:
112133
**data,
113134
)
114135
releases[model.id] = model
136+
write_to_pickle("releases", releases)
115137
return model
116138

117139

@@ -122,6 +144,7 @@ async def update_a_release(owner: str, repo: str, release_id: int, request: Requ
122144
model = releases[release_id]
123145
for name, value in data.items():
124146
setattr(model, name, value)
147+
write_to_pickle("releases", releases)
125148
return model
126149

127150

@@ -131,7 +154,7 @@ async def upload_a_release_asset(owner: str, repo: str, release_id: int, request
131154
model = releases[release_id]
132155
asset_id = uuid.uuid4().int
133156
name = request.query_params["name"]
134-
with open(f"{static_dir.name}/{asset_id}", "wb") as fid:
157+
with open(f"{static_dir}/{asset_id}", "wb") as fid:
135158
async for chunk in request.stream():
136159
fid.write(chunk)
137160
headers = request.headers
@@ -145,27 +168,34 @@ async def upload_a_release_asset(owner: str, repo: str, release_id: int, request
145168
)
146169
release_ids_for_asset[asset_id] = release_id
147170
model.assets.append(asset)
171+
write_to_pickle("releases", releases)
172+
write_to_pickle("release_ids_for_asset", release_ids_for_asset)
148173

149174

150175
@app.delete("/repos/{owner}/{repo}/releases/assets/{asset_id}")
151176
async def delete_a_release_asset(owner: str, repo: str, asset_id: int) -> None:
152177
"""https://docs.github.com/en/rest/releases/assets#delete-a-release-asset"""
153178
release = releases[release_ids_for_asset[asset_id]]
154-
os.remove(f"{static_dir.name}/{asset_id}")
179+
os.remove(f"{static_dir}/{asset_id}")
155180
release.assets = [a for a in release.assets if a.id != asset_id]
181+
del release_ids_for_asset[asset_id]
182+
write_to_pickle("releases", releases)
183+
write_to_pickle("release_ids_for_asset", release_ids_for_asset)
156184

157185

158186
@app.delete("/repos/{owner}/{repo}/releases/{release_id}")
159187
def delete_a_release(owner: str, repo: str, release_id: int) -> None:
160188
"""https://docs.github.com/en/rest/releases/releases#delete-a-release"""
161189
del releases[release_id]
190+
write_to_pickle("releases", releases)
162191

163192

164193
@app.get("/repos/{owner}/{repo}/pulls/{pull_number}")
165194
def get_a_pull_request(owner: str, repo: str, pull_number: int) -> PullRequest:
166195
"""https://docs.github.com/en/rest/pulls/pulls#get-a-pull-request"""
167196
if pull_number not in pulls:
168197
pulls[pull_number] = PullRequest()
198+
write_to_pickle("pulls", pulls)
169199
return pulls[pull_number]
170200

171201

@@ -174,6 +204,7 @@ def create_a_pull_request(owner: str, repo: str) -> PullRequest:
174204
"""https://docs.github.com/en/rest/pulls/pulls#create-a-pull-request"""
175205
pull = PullRequest()
176206
pulls[pull.number] = pull
207+
write_to_pickle("releases", releases)
177208
return pull
178209

179210

@@ -188,6 +219,7 @@ def create_tag_ref(tag_ref: str, sha: str) -> None:
188219
"""Create a remote tag ref object for testing"""
189220
tag = Tag(ref=f"refs/tags/{tag_ref}", object=TagObject(sha=sha))
190221
tag_refs[tag_ref] = tag
222+
write_to_pickle("tag_refs", tag_refs)
191223

192224

193225
@app.get("/repos/{owner}/{repo}/git/matching-refs/tags/{tag_ref}")

jupyter_releaser/tests/conftest.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66
from pathlib import Path
77

88
from click.testing import CliRunner
9-
from ghapi import core
109
from pytest import fixture
1110

1211
from jupyter_releaser import cli, util
1312
from jupyter_releaser.tests import util as testutil
14-
from jupyter_releaser.util import MOCK_GITHUB_URL, run, start_mock_github
13+
from jupyter_releaser.util import ensure_mock_github, run
1514

1615

1716
@fixture(autouse=True)
@@ -26,7 +25,6 @@ def mock_env(mocker):
2625
del env[key]
2726

2827
mocker.patch.dict(os.environ, env, clear=True)
29-
core.GH_HOST = MOCK_GITHUB_URL
3028

3129
try:
3230
run("git config --global user.name")
@@ -191,8 +189,9 @@ def wrapped(cmd, **kwargs):
191189

192190
@fixture
193191
def mock_github():
194-
proc = start_mock_github()
192+
proc = ensure_mock_github()
195193
yield proc
196194

197-
proc.kill()
198-
proc.wait()
195+
if proc:
196+
proc.kill()
197+
proc.wait()

0 commit comments

Comments
 (0)