Coverage for src/gitlabracadabra/packages/github.py: 85%
79 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-23 06:44 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-23 06:44 +0200
1#
2# Copyright (C) 2019-2025 Mathieu Parent <math.parent@gmail.com>
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Lesser General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
17from __future__ import annotations
19from logging import getLogger
20from os import getenv
21from typing import TYPE_CHECKING
23from github import Github as PyGithub
24from github.GithubException import UnknownObjectException
26from gitlabracadabra.matchers import Matcher
27from gitlabracadabra.packages.package_file import PackageFile
28from gitlabracadabra.packages.source import Source
30if TYPE_CHECKING: 30 ↛ 31line 30 didn't jump to line 31 because the condition on line 30 was never true
31 from github.GitRelease import GitRelease
33 from gitlabracadabra.packages.destination import Destination
35logger = getLogger(__name__)
38class Github(Source):
39 """Github source."""
41 def __init__(
42 self,
43 *,
44 log_prefix: str = "",
45 full_name: str,
46 package_name: str | None = None,
47 tags: list[str] | None = None,
48 semver: str | None = None,
49 latest_release: bool | None = None,
50 tarball: bool | None = None,
51 zipball: bool | None = None,
52 assets: list[str] | None = None,
53 ) -> None:
54 """Initialize a Github source.
56 Args:
57 log_prefix: Log prefix.
58 full_name: Repository full name. Mandatory.
59 package_name: Destination package name (defaults to repository name).
60 tags: List of tags.
61 semver: Semantic version applied on tags.
62 latest_release: Get latest release.
63 tarball: Get repository tarball (defaults to False).
64 zipball: Get repository zip (defaults to False).
65 assets: List of assets (None by default).
66 """
67 super().__init__()
68 self._log_prefix = log_prefix
69 self._full_name = full_name
70 self._package_name = package_name or full_name.split("/").pop()
71 self._tags = tags or []
72 self._semver = semver
73 self._latest_release = latest_release or False
74 self._tarball = tarball or False
75 self._zipball = zipball or False
76 self._assets = assets or []
78 self._repo = PyGithub(
79 login_or_token=getenv("GITHUB_TOKEN"),
80 ).get_repo(self._full_name, lazy=True)
81 self._all_releases: dict[str, GitRelease] | None = None
82 self._matching_releases: dict[str, GitRelease] | None = None
84 def __str__(self) -> str:
85 """Return string representation.
87 Returns:
88 A string.
89 """
90 return f"Github repository (full_name={self._full_name})"
92 def package_files(
93 self,
94 destination: Destination, # noqa: ARG002
95 ) -> list[PackageFile]:
96 """Return list of package files.
98 Returns:
99 List of package files.
100 """
101 package_files: list[PackageFile] = []
102 for release in self._get_matching_releases().values():
103 self._append_package_file_from_release(package_files, release)
104 return package_files
106 def _get_matching_releases(self) -> dict[str, GitRelease]:
107 if self._matching_releases is None: 107 ↛ 129line 107 didn't jump to line 129 because the condition on line 107 was always true
108 matches = Matcher(
109 self._tags,
110 self._semver,
111 log_prefix=self._log_prefix,
112 ).match(
113 self._get_all_tag_names,
114 )
115 self._matching_releases = {}
116 for match in matches:
117 self._append_matching_release(match[0])
118 if self._latest_release: 118 ↛ 129line 118 didn't jump to line 129 because the condition on line 118 was always true
119 try:
120 latest_release = self._repo.get_latest_release()
121 self._matching_releases[latest_release.tag_name] = latest_release
122 except UnknownObjectException as err2:
123 logger.warning(
124 "%sError getting package files from repository %s, latest release: %s",
125 self._log_prefix,
126 self._full_name,
127 str(err2),
128 )
129 return self._matching_releases
131 def _get_all_tag_names(self) -> list[str]:
132 return list(self._get_all_releases().keys())
134 def _get_all_releases(self) -> dict[str, GitRelease]:
135 if self._all_releases is None: 135 ↛ 139line 135 didn't jump to line 139 because the condition on line 135 was always true
136 self._all_releases = {}
137 for release in self._repo.get_releases():
138 self._all_releases[release.tag_name] = release
139 return self._all_releases
141 def _append_matching_release(self, tag_name: str) -> None:
142 if self._all_releases is None: 142 ↛ 143line 142 didn't jump to line 143 because the condition on line 142 was never true
143 try:
144 self._matching_releases[tag_name] = self._repo.get_release(tag_name) # type: ignore
145 except UnknownObjectException as err:
146 logger.warning(
147 "%sError getting package files from repository %s, release with tag %s: %s",
148 self._log_prefix,
149 self._full_name,
150 tag_name,
151 str(err),
152 )
153 else:
154 self._matching_releases[tag_name] = self._all_releases[tag_name] # type: ignore
156 def _append_package_file_from_release(self, package_files: list[PackageFile], release: GitRelease) -> None:
157 if self._tarball:
158 package_files.append(
159 PackageFile(
160 release.tarball_url,
161 "generic",
162 self._package_name,
163 release.tag_name,
164 "{}-{}.tar.gz".format(self._full_name.split("/").pop(), release.tag_name),
165 )
166 )
167 if self._zipball:
168 package_files.append(
169 PackageFile(
170 release.zipball_url,
171 "generic",
172 self._package_name,
173 release.tag_name,
174 "{}-{}.zip".format(self._full_name.split("/").pop(), release.tag_name),
175 )
176 )
177 if self._assets:
178 try:
179 # https://github.com/PyGithub/PyGithub/pull/1899
180 assets = release.assets
181 except AttributeError:
182 assets = list(release.get_assets())
183 assets_map = {asset.name: asset.browser_download_url for asset in assets}
184 for asset_name in self._assets:
185 try:
186 package_files.append(
187 PackageFile(
188 assets_map[asset_name],
189 "generic",
190 self._package_name,
191 release.tag_name,
192 asset_name,
193 )
194 )
195 except KeyError:
196 logger.warning(
197 '%sAsset "%s" not found from repository %s in release with tag %s',
198 self._log_prefix,
199 asset_name,
200 self._full_name,
201 release.tag_name,
202 )