summaryrefslogtreecommitdiff
path: root/gitlab
diff options
context:
space:
mode:
authorNejc Habjan <hab.nejc@gmail.com>2021-04-17 17:07:55 +0200
committerJohn Villalovos <john@sodarock.com>2021-05-31 20:43:30 -0700
commit79d88bde9e5e6c33029e4a9f26c97404e6a7a874 (patch)
treec06b8347690c0f64d9dc9a113ceadd1fc7f4cd29 /gitlab
parentfbbc0d400015d7366952a66e4401215adff709f0 (diff)
downloadgitlab-79d88bde9e5e6c33029e4a9f26c97404e6a7a874.tar.gz
feat(objects): add support for generic packages API
Diffstat (limited to 'gitlab')
-rw-r--r--gitlab/client.py70
-rw-r--r--gitlab/v4/objects/packages.py114
-rw-r--r--gitlab/v4/objects/projects.py3
3 files changed, 157 insertions, 30 deletions
diff --git a/gitlab/client.py b/gitlab/client.py
index d6233db..1825505 100644
--- a/gitlab/client.py
+++ b/gitlab/client.py
@@ -394,15 +394,9 @@ class Gitlab(object):
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
- def _create_headers(self, content_type: Optional[str] = None) -> Dict[str, Any]:
- request_headers = self.headers.copy()
- if content_type is not None:
- request_headers["Content-type"] = content_type
- return request_headers
-
- def _get_session_opts(self, content_type: str) -> Dict[str, Any]:
+ def _get_session_opts(self) -> Dict[str, Any]:
return {
- "headers": self._create_headers(content_type),
+ "headers": self.headers.copy(),
"auth": self._http_auth,
"timeout": self.timeout,
"verify": self.ssl_verify,
@@ -442,12 +436,39 @@ class Gitlab(object):
if location and location.startswith("https://"):
raise gitlab.exceptions.RedirectError(REDIRECT_MSG)
+ def _prepare_send_data(
+ self,
+ files: Dict[str, Any] = None,
+ post_data: Dict[str, Any] = None,
+ raw: Optional[bool] = False,
+ ) -> Tuple:
+ if files:
+ if post_data is None:
+ post_data = {}
+ else:
+ # booleans does not exists for data (neither for MultipartEncoder):
+ # cast to string int to avoid: 'bool' object has no attribute 'encode'
+ for k, v in post_data.items():
+ if isinstance(v, bool):
+ post_data[k] = str(int(v))
+ post_data["file"] = files.get("file")
+ post_data["avatar"] = files.get("avatar")
+
+ data = MultipartEncoder(post_data)
+ return (None, data, data.content_type)
+
+ if raw and post_data:
+ return (None, post_data, "application/octet-stream")
+
+ return (post_data, None, "application/json")
+
def http_request(
self,
verb: str,
path: str,
query_data: Optional[Dict[str, Any]] = None,
post_data: Optional[Dict[str, Any]] = None,
+ raw: Optional[bool] = False,
streamed: bool = False,
files: Optional[Dict[str, Any]] = None,
timeout: Optional[float] = None,
@@ -465,7 +486,8 @@ class Gitlab(object):
'http://whatever/v4/api/projecs')
query_data (dict): Data to send as query parameters
post_data (dict): Data to send in the body (will be converted to
- json)
+ json by default)
+ raw (bool): If True, do not convert post_data to json
streamed (bool): Whether the data should be streamed
files (dict): The files to send to the server
timeout (float): The timeout, in seconds, for the request
@@ -504,7 +526,7 @@ class Gitlab(object):
else:
utils.copy_dict(params, kwargs)
- opts = self._get_session_opts(content_type="application/json")
+ opts = self._get_session_opts()
verify = opts.pop("verify")
opts_timeout = opts.pop("timeout")
@@ -513,23 +535,8 @@ class Gitlab(object):
timeout = opts_timeout
# We need to deal with json vs. data when uploading files
- if files:
- json = None
- if post_data is None:
- post_data = {}
- else:
- # booleans does not exists for data (neither for MultipartEncoder):
- # cast to string int to avoid: 'bool' object has no attribute 'encode'
- for k, v in post_data.items():
- if isinstance(v, bool):
- post_data[k] = str(int(v))
- post_data["file"] = files.get("file")
- post_data["avatar"] = files.get("avatar")
- data = MultipartEncoder(post_data)
- opts["headers"]["Content-type"] = data.content_type
- else:
- json = post_data
- data = None
+ json, data, content_type = self._prepare_send_data(files, post_data, raw)
+ opts["headers"]["Content-type"] = content_type
# Requests assumes that `.` should not be encoded as %2E and will make
# changes to urls using this encoding. Using a prepped request we can
@@ -684,6 +691,7 @@ class Gitlab(object):
path: str,
query_data: Optional[Dict[str, Any]] = None,
post_data: Optional[Dict[str, Any]] = None,
+ raw: Optional[bool] = False,
files: Optional[Dict[str, Any]] = None,
**kwargs: Any,
) -> Union[Dict[str, Any], requests.Response]:
@@ -694,7 +702,8 @@ class Gitlab(object):
'http://whatever/v4/api/projecs')
query_data (dict): Data to send as query parameters
post_data (dict): Data to send in the body (will be converted to
- json)
+ json by default)
+ raw (bool): If True, do not convert post_data to json
files (dict): The files to send to the server
**kwargs: Extra options to send to the server (e.g. sudo)
@@ -731,6 +740,7 @@ class Gitlab(object):
path: str,
query_data: Optional[Dict[str, Any]] = None,
post_data: Optional[Dict[str, Any]] = None,
+ raw: Optional[bool] = False,
files: Optional[Dict[str, Any]] = None,
**kwargs: Any,
) -> Union[Dict[str, Any], requests.Response]:
@@ -741,7 +751,8 @@ class Gitlab(object):
'http://whatever/v4/api/projecs')
query_data (dict): Data to send as query parameters
post_data (dict): Data to send in the body (will be converted to
- json)
+ json by default)
+ raw (bool): If True, do not convert post_data to json
files (dict): The files to send to the server
**kwargs: Extra options to send to the server (e.g. sudo)
@@ -761,6 +772,7 @@ class Gitlab(object):
query_data=query_data,
post_data=post_data,
files=files,
+ raw=raw,
**kwargs,
)
try:
diff --git a/gitlab/v4/objects/packages.py b/gitlab/v4/objects/packages.py
index f5ca081..a470a94 100644
--- a/gitlab/v4/objects/packages.py
+++ b/gitlab/v4/objects/packages.py
@@ -1,7 +1,17 @@
+from pathlib import Path
+from typing import Any, Callable, Optional, TYPE_CHECKING, Union
+
+import requests
+
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab import utils
from gitlab.base import RESTManager, RESTObject
from gitlab.mixins import DeleteMixin, GetMixin, ListMixin, ObjectDeleteMixin
__all__ = [
+ "GenericPackage",
+ "GenericPackageManager",
"GroupPackage",
"GroupPackageManager",
"ProjectPackage",
@@ -11,6 +21,110 @@ __all__ = [
]
+class GenericPackage(RESTObject):
+ _id_attr = "package_name"
+
+
+class GenericPackageManager(RESTManager):
+ _path = "/projects/%(project_id)s/packages/generic"
+ _obj_cls = GenericPackage
+ _from_parent_attrs = {"project_id": "id"}
+
+ @cli.register_custom_action(
+ "GenericPackageManager",
+ ("package_name", "package_version", "file_name", "path"),
+ )
+ @exc.on_http_error(exc.GitlabUploadError)
+ def upload(
+ self,
+ package_name: str,
+ package_version: str,
+ file_name: str,
+ path: Union[str, Path],
+ **kwargs,
+ ) -> GenericPackage:
+ """Upload a file as a generic package.
+
+ Args:
+ package_name (str): The package name. Must follow generic package
+ name regex rules
+ package_version (str): The package version. Must follow semantic
+ version regex rules
+ file_name (str): The name of the file as uploaded in the registry
+ path (str): The path to a local file to upload
+
+ Raises:
+ GitlabConnectionError: If the server cannot be reached
+ GitlabUploadError: If the file upload fails
+ GitlabUploadError: If ``filepath`` cannot be read
+
+ Returns:
+ GenericPackage: An object storing the metadata of the uploaded package.
+ """
+
+ try:
+ with open(path, "rb") as f:
+ file_data = f.read()
+ except OSError:
+ raise exc.GitlabUploadError(f"Failed to read package file {path}")
+
+ url = f"{self._computed_path}/{package_name}/{package_version}/{file_name}"
+ server_data = self.gitlab.http_put(url, post_data=file_data, raw=True, **kwargs)
+
+ return self._obj_cls(
+ self,
+ {
+ "package_name": package_name,
+ "package_version": package_version,
+ "file_name": file_name,
+ "path": path,
+ "message": server_data["message"],
+ },
+ )
+
+ @cli.register_custom_action(
+ "GenericPackageManager",
+ ("package_name", "package_version", "file_name"),
+ )
+ @exc.on_http_error(exc.GitlabGetError)
+ def download(
+ self,
+ package_name: str,
+ package_version: str,
+ file_name: str,
+ streamed: bool = False,
+ action: Optional[Callable] = None,
+ chunk_size: int = 1024,
+ **kwargs: Any,
+ ) -> Optional[bytes]:
+ """Download a generic package.
+
+ Args:
+ package_name (str): The package name.
+ package_version (str): The package version.
+ file_name (str): The name of the file in the registry
+ streamed (bool): If True the data will be processed by chunks of
+ `chunk_size` and each chunk is passed to `action` for
+ reatment
+ action (callable): Callable responsible of dealing with chunk of
+ data
+ chunk_size (int): Size of each chunk
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabGetError: If the server failed to perform the request
+
+ Returns:
+ str: The package content if streamed is False, None otherwise
+ """
+ path = f"{self._computed_path}/{package_name}/{package_version}/{file_name}"
+ result = self.gitlab.http_get(path, streamed=streamed, raw=True, **kwargs)
+ if TYPE_CHECKING:
+ assert isinstance(result, requests.Response)
+ return utils.response_content(result, streamed, action, chunk_size)
+
+
class GroupPackage(RESTObject):
pass
diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py
index b9951a7..b1cae49 100644
--- a/gitlab/v4/objects/projects.py
+++ b/gitlab/v4/objects/projects.py
@@ -41,7 +41,7 @@ from .merge_requests import ProjectMergeRequestManager # noqa: F401
from .milestones import ProjectMilestoneManager # noqa: F401
from .notes import ProjectNoteManager # noqa: F401
from .notification_settings import ProjectNotificationSettingsManager # noqa: F401
-from .packages import ProjectPackageManager # noqa: F401
+from .packages import GenericPackageManager, ProjectPackageManager # noqa: F401
from .pages import ProjectPagesDomainManager # noqa: F401
from .pipelines import ( # noqa: F401
ProjectPipeline,
@@ -124,6 +124,7 @@ class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTO
("exports", "ProjectExportManager"),
("files", "ProjectFileManager"),
("forks", "ProjectForkManager"),
+ ("generic_packages", "GenericPackageManager"),
("hooks", "ProjectHookManager"),
("keys", "ProjectKeyManager"),
("imports", "ProjectImportManager"),