Skip to content

Commit 9d0cfdb

Browse files
committed
Avoid redundant release asset list requests
1 parent 6cd0ab3 commit 9d0cfdb

3 files changed

Lines changed: 103 additions & 1 deletion

File tree

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Unreleased
1414
checkpoints (#62).
1515
- Stop paginating pull requests during incremental backups once the sorted
1616
results are older than the active checkpoint.
17+
- Avoid extra release asset list requests by using asset metadata already
18+
included in GitHub's releases response.
1719
- Add ``--token-from-gh`` to read authentication from ``gh auth token``.
1820

1921

github_backup/github_backup.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2919,7 +2919,12 @@ def backup_releases(args, repo_cwd, repository, repos_template, include_assets=F
29192919
written_count += 1
29202920

29212921
if include_assets and not skip_assets:
2922-
assets = retrieve_data(args, release["assets_url"])
2922+
# The releases list API already includes release asset metadata. Use
2923+
# it to avoid an extra /releases/{id}/assets request per release.
2924+
# Keep a fallback for older/enterprise responses that might omit it.
2925+
assets = release.get("assets")
2926+
if assets is None:
2927+
assets = retrieve_data(args, release["assets_url"])
29232928
if len(assets) > 0:
29242929
# give release asset files somewhere to live & download them (not including source archives)
29252930
release_assets_cwd = os.path.join(release_cwd, release_name_safe)

tests/test_releases.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""Tests for release backup behavior."""
2+
3+
from github_backup import github_backup
4+
5+
6+
def test_backup_releases_uses_embedded_assets_without_extra_asset_list_request(
7+
create_args, tmp_path, monkeypatch
8+
):
9+
args = create_args(include_releases=True, include_assets=True)
10+
repository = {"full_name": "owner/repo", "name": "repo"}
11+
calls = []
12+
downloads = []
13+
14+
def fake_retrieve_data(passed_args, template, query_args=None, paginated=True, **kwargs):
15+
calls.append(template)
16+
if template == "https://api.github.com/repos/owner/repo/releases":
17+
return [
18+
{
19+
"tag_name": "v1.0.0",
20+
"created_at": "2026-01-01T00:00:00Z",
21+
"updated_at": "2026-01-01T00:00:00Z",
22+
"prerelease": False,
23+
"draft": False,
24+
"assets_url": "https://api.github.com/repos/owner/repo/releases/1/assets",
25+
"assets": [
26+
{
27+
"name": "artifact.zip",
28+
"url": "https://api.github.com/repos/owner/repo/releases/assets/1",
29+
}
30+
],
31+
}
32+
]
33+
raise AssertionError("Unexpected API request: {0}".format(template))
34+
35+
def fake_download_file(url, path, auth, as_app=False, fine=False):
36+
downloads.append((url, path))
37+
38+
monkeypatch.setattr(github_backup, "retrieve_data", fake_retrieve_data)
39+
monkeypatch.setattr(github_backup, "download_file", fake_download_file)
40+
41+
github_backup.backup_releases(
42+
args,
43+
tmp_path,
44+
repository,
45+
"https://api.github.com/repos",
46+
include_assets=True,
47+
)
48+
49+
assert calls == ["https://api.github.com/repos/owner/repo/releases"]
50+
assert downloads == [
51+
(
52+
"https://api.github.com/repos/owner/repo/releases/assets/1",
53+
str(tmp_path / "releases" / "v1.0.0" / "artifact.zip"),
54+
)
55+
]
56+
57+
58+
def test_backup_releases_falls_back_to_assets_url_when_assets_missing(
59+
create_args, tmp_path, monkeypatch
60+
):
61+
args = create_args(include_releases=True, include_assets=True)
62+
repository = {"full_name": "owner/repo", "name": "repo"}
63+
calls = []
64+
65+
def fake_retrieve_data(passed_args, template, query_args=None, paginated=True, **kwargs):
66+
calls.append(template)
67+
if template == "https://api.github.com/repos/owner/repo/releases":
68+
return [
69+
{
70+
"tag_name": "v1.0.0",
71+
"created_at": "2026-01-01T00:00:00Z",
72+
"updated_at": "2026-01-01T00:00:00Z",
73+
"prerelease": False,
74+
"draft": False,
75+
"assets_url": "https://api.github.com/repos/owner/repo/releases/1/assets",
76+
}
77+
]
78+
if template == "https://api.github.com/repos/owner/repo/releases/1/assets":
79+
return []
80+
raise AssertionError("Unexpected API request: {0}".format(template))
81+
82+
monkeypatch.setattr(github_backup, "retrieve_data", fake_retrieve_data)
83+
84+
github_backup.backup_releases(
85+
args,
86+
tmp_path,
87+
repository,
88+
"https://api.github.com/repos",
89+
include_assets=True,
90+
)
91+
92+
assert calls == [
93+
"https://api.github.com/repos/owner/repo/releases",
94+
"https://api.github.com/repos/owner/repo/releases/1/assets",
95+
]

0 commit comments

Comments
 (0)