-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgithub.py
More file actions
145 lines (113 loc) · 4.44 KB
/
Copy pathgithub.py
File metadata and controls
145 lines (113 loc) · 4.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
from dataclasses import dataclass
from urllib.parse import quote
import requests
from utils import github_api_headers
REQUEST_TIMEOUT_SECONDS = 30
@dataclass
class Asset:
# リリースアセットのダウンロードURLとファイル名を保持
browser_download_url: str
name: str
@dataclass
class GithubRelease:
# GitHubリリースの基本情報とアセット一覧を保持
tag_name: str
html_url: str
assets: list[Asset]
@dataclass(frozen=True)
class GithubCommit:
# コミットのSHA、URL、件名(サブジェクト)を保持
sha: str
html_url: str
subject: str
def _to_github_release(release) -> GithubRelease:
# APIレスポンスの辞書からGithubReleaseオブジェクトに変換
assets = [
Asset(browser_download_url=asset["browser_download_url"], name=asset["name"])
for asset in release["assets"]
]
return GithubRelease(
tag_name=release["tag_name"], html_url=release["html_url"], assets=assets
)
def _fetch_release(url: str) -> GithubRelease | None:
# 指定URLからリリース情報を取得し、存在しない場合はNoneを返す
response = requests.get(
url,
headers=github_api_headers(),
timeout=REQUEST_TIMEOUT_SECONDS,
)
if response.status_code == 404:
return None
response.raise_for_status()
return _to_github_release(response.json())
def get_release_by_tag(repo_url: str, tag: str) -> GithubRelease | None:
# タグ名をエンコードしてGitHub Releases APIから特定リリースを取得
encoded_tag = quote(tag, safe="")
url = f"https://api.github.com/repos/{repo_url}/releases/tags/{encoded_tag}"
return _fetch_release(url)
def get_last_build_version(repo_url: str) -> GithubRelease | None:
# 最新リリースを取得する(/latestエンドポイントを使用)
url = f"https://api.github.com/repos/{repo_url}/releases/latest"
return _fetch_release(url)
def get_commits_between(
repo_url: str, base: str, head: str
) -> list[GithubCommit] | None:
# ベースコミットとヘッドコミットの間の全コミットを取得してリスト化
url = f"https://api.github.com/repos/{repo_url}/compare/{base}...{head}"
response = requests.get(
url,
headers=github_api_headers(),
timeout=REQUEST_TIMEOUT_SECONDS,
)
if response.status_code == 404:
return None
response.raise_for_status()
payload = response.json()
if not isinstance(payload, dict):
raise ValueError("GitHub compare response was not an object")
commits = payload.get("commits", [])
if not isinstance(commits, list):
raise ValueError("GitHub compare response did not contain a commit list")
result: list[GithubCommit] = []
for commit in commits:
# 各コミットから必要な情報だけを取り出し、件名は最初の行のみ使用
if not isinstance(commit, dict):
continue
commit_details = commit.get("commit", {})
if not isinstance(commit_details, dict):
continue
message = commit_details.get("message", "")
sha = commit.get("sha")
html_url = commit.get("html_url")
if not isinstance(message, str):
continue
if not isinstance(sha, str) or not isinstance(html_url, str):
continue
result.append(
GithubCommit(
sha=sha,
html_url=html_url,
subject=message.partition("\n")[0],
)
)
return result
def get_release_asset_json(
release: GithubRelease, asset_name: str
) -> list[str] | None:
# 指定リリースの指定アセットをダウンロードし、JSONから文字列リストを返す
asset = next((asset for asset in release.assets if asset.name == asset_name), None)
if asset is None:
return None
# ブラウザダウンロードURLのため、GitHubトークンは送らず、Acceptヘッダのみでリクエスト
response = requests.get(
asset.browser_download_url,
headers={"Accept": "application/octet-stream"},
timeout=REQUEST_TIMEOUT_SECONDS,
)
response.raise_for_status()
contents = response.json()
if not isinstance(contents, list) or not all(
isinstance(patch, str) for patch in contents
):
raise ValueError(f"Release asset {asset_name} must contain a list of strings")
return contents