Coverage for src/gitlabracadabra/packages/gitlab.py: 92%
45 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-10 17:02 +0100
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-10 17:02 +0100
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 typing import TYPE_CHECKING
21from urllib.parse import quote
23from gitlabracadabra.packages.destination import Destination
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
30HELM = "helm"
31PYPI = "pypi"
33logger = getLogger(__name__)
36class Gitlab(Destination):
37 """Gitlab repository."""
39 def __init__(
40 self,
41 *,
42 connection: GitlabConnection,
43 full_path: str,
44 project_id: int,
45 ) -> None:
46 """Initialize Gitlab repository.
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)
59 def upload_method(self, package_file: PackageFile) -> str:
60 """Get upload HTTP method.
62 Args:
63 package_file: Source package file.
65 Returns:
66 The upload method.
67 """
68 if package_file.package_type in {HELM, PYPI}:
69 return "POST"
71 return super().upload_method(package_file)
73 def head_url(self, package_file: PackageFile) -> str:
74 """Get URL to test existence of destination package file with a HEAD request.
76 Args:
77 package_file: Source package file.
79 Returns:
80 An URL.
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 )
110 raise NotImplementedError
112 def upload_url(self, package_file: PackageFile) -> str:
113 """Get URL to upload to.
115 Args:
116 package_file: Source package file.
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 )
146 return super().upload_url(package_file)
148 def files_key(self, package_file: PackageFile) -> str | None:
149 """Get files key, to upload to. If None, uploaded as body.
151 Args:
152 package_file: Source package file.
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"
162 return super().files_key(package_file)