diff options
| author | Nejc Habjan <hab.nejc@gmail.com> | 2021-04-17 17:07:55 +0200 |
|---|---|---|
| committer | John Villalovos <john@sodarock.com> | 2021-05-31 20:43:30 -0700 |
| commit | 79d88bde9e5e6c33029e4a9f26c97404e6a7a874 (patch) | |
| tree | c06b8347690c0f64d9dc9a113ceadd1fc7f4cd29 /gitlab | |
| parent | fbbc0d400015d7366952a66e4401215adff709f0 (diff) | |
| download | gitlab-79d88bde9e5e6c33029e4a9f26c97404e6a7a874.tar.gz | |
feat(objects): add support for generic packages API
Diffstat (limited to 'gitlab')
| -rw-r--r-- | gitlab/client.py | 70 | ||||
| -rw-r--r-- | gitlab/v4/objects/packages.py | 114 | ||||
| -rw-r--r-- | gitlab/v4/objects/projects.py | 3 |
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"), |
