Coverage for src/gitlabracadabra/packages/gitlab.py: 92%

45 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-14 23:10 +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/>. 

16 

17from __future__ import annotations 

18 

19from logging import getLogger 

20from typing import TYPE_CHECKING 

21from urllib.parse import quote 

22 

23from gitlabracadabra.packages.destination import Destination 

24 

25if TYPE_CHECKING: 25 ↛ 26line 25 didn't jump to line 26 because the condition on line 25 was never true

26 from gitlabracadabra.gitlab.connection import GitlabConnection 

27 from gitlabracadabra.packages.package_file import PackageFile 

28 

29 

30HELM = "helm" 

31PYPI = "pypi" 

32 

33logger = getLogger(__name__) 

34 

35 

36class Gitlab(Destination): 

37 """Gitlab repository.""" 

38 

39 def __init__( 

40 self, 

41 *, 

42 connection: GitlabConnection, 

43 full_path: str, 

44 project_id: int, 

45 ) -> None: 

46 """Initialize Gitlab repository. 

47 

48 Args: 

49 connection: A Gitlab connection. 

50 full_path: Project full path. 

51 project_id: Project ID. 

52 """ 

53 super().__init__(log_prefix=f"[{full_path}] ") 

54 self._connection = connection 

55 self._full_path = full_path 

56 self._project_id = project_id 

57 self._connection.session_callback(self.session) 

58 

59 def upload_method(self, package_file: PackageFile) -> str: 

60 """Get upload HTTP method. 

61 

62 Args: 

63 package_file: Source package file. 

64 

65 Returns: 

66 The upload method. 

67 """ 

68 if package_file.package_type in {HELM, PYPI}: 

69 return "POST" 

70 

71 return super().upload_method(package_file) 

72 

73 def head_url(self, package_file: PackageFile) -> str: 

74 """Get URL to test existence of destination package file with a HEAD request. 

75 

76 Args: 

77 package_file: Source package file. 

78 

79 Returns: 

80 An URL. 

81 

82 Raises: 

83 NotImplementedError: For unsupported package types. 

84 """ 

85 if package_file.package_type == "raw": 

86 return "{}/projects/{}/packages/generic/{}/{}/{}".format( 

87 self._connection.api_url, 

88 quote(self._full_path, safe=""), 

89 quote(package_file.package_name, safe=""), # [A-Za-z0-9\.\_\-\+]+ 

90 quote(package_file.package_version, safe=""), # (\.?[\w\+-]+\.?)+ 

91 quote(package_file.file_name, safe=""), # [A-Za-z0-9\.\_\-\+]+ 

92 ) 

93 if package_file.package_type == HELM: 

94 channel = package_file.metadata.get("channel") or "stable" 

95 file_name = f"{package_file.package_name}-{package_file.package_version}.tgz" 

96 return "{}/projects/{}/packages/helm/{}/charts/{}".format( 

97 self._connection.api_url, 

98 quote(self._full_path, safe=""), 

99 quote(channel, safe=""), 

100 quote(file_name, safe=""), 

101 ) 

102 if package_file.package_type == PYPI: 102 ↛ 110line 102 didn't jump to line 110 because the condition on line 102 was always true

103 return "{}/projects/{}/packages/pypi/files/{}/{}".format( 

104 self._connection.api_url, 

105 self._project_id, 

106 quote(package_file.metadata.get("sha256", ""), safe=""), 

107 quote(package_file.file_name, safe=""), 

108 ) 

109 

110 raise NotImplementedError 

111 

112 def upload_url(self, package_file: PackageFile) -> str: 

113 """Get URL to upload to. 

114 

115 Args: 

116 package_file: Source package file. 

117 

118 Returns: 

119 The upload URL. 

120 """ 

121 if package_file.package_type == HELM: 

122 channel = package_file.metadata.get("channel") or "stable" 

123 return "{}/projects/{}/packages/helm/api/{}/charts".format( 

124 self._connection.api_url, 

125 quote(self._full_path, safe=""), 

126 quote(channel, safe=""), 

127 ) 

128 if package_file.package_type == PYPI: 

129 return ( 

130 "{}/projects/{}/packages/pypi?" 

131 "requires_python={}&" 

132 "name={}&" 

133 "version={}&" 

134 "md5_digest={}&" 

135 "sha256_digest={}" 

136 ).format( 

137 self._connection.api_url, 

138 self._project_id, 

139 quote(package_file.metadata.get("requires-python", ""), safe=""), 

140 quote(package_file.package_name, safe=""), 

141 quote(package_file.package_version, safe=""), 

142 quote(package_file.metadata.get("md5", ""), safe=""), 

143 quote(package_file.metadata.get("sha256", ""), safe=""), 

144 ) 

145 

146 return super().upload_url(package_file) 

147 

148 def files_key(self, package_file: PackageFile) -> str | None: 

149 """Get files key, to upload to. If None, uploaded as body. 

150 

151 Args: 

152 package_file: Source package file. 

153 

154 Returns: 

155 The files key, or None. 

156 """ 

157 if package_file.package_type == HELM: 

158 return "chart" 

159 if package_file.package_type == PYPI: 

160 return "content" 

161 

162 return super().files_key(package_file)