diff options
author | John L. Villalovos <john@sodarock.com> | 2022-07-27 16:08:25 -0700 |
---|---|---|
committer | John L. Villalovos <john@sodarock.com> | 2022-07-27 16:08:25 -0700 |
commit | 1af44ce8761e6ee8a9467a3e192f6c4d19e5cefe (patch) | |
tree | da3116aa356818cb2a865b047de9ef160bbfffa7 /gitlab | |
parent | 194ee0100c2868c1a9afb161c15f3145efb01c7c (diff) | |
download | gitlab-1af44ce8761e6ee8a9467a3e192f6c4d19e5cefe.tar.gz |
fix: use the [] after key names for array variables in `params`
1. If a value is of type ArrayAttribute then append '[]' to the name
of the value for query parameters (`params`).
This is step 3 in a series of steps of our goal to add full
support for the GitLab API data types[1]:
* array
* hash
* array of hashes
Step one was: commit 5127b1594c00c7364e9af15e42d2e2f2d909449b
Step two was: commit a57334f1930752c70ea15847a39324fa94042460
Fixes: #1698
[1] https://docs.gitlab.com/ee/api/#encoding-api-parameters-of-array-and-hash-types
Diffstat (limited to 'gitlab')
-rw-r--r-- | gitlab/mixins.py | 15 | ||||
-rw-r--r-- | gitlab/types.py | 22 | ||||
-rw-r--r-- | gitlab/utils.py | 39 |
3 files changed, 57 insertions, 19 deletions
diff --git a/gitlab/mixins.py b/gitlab/mixins.py index f33a1fc..a48c032 100644 --- a/gitlab/mixins.py +++ b/gitlab/mixins.py @@ -238,7 +238,12 @@ class ListMixin(HeadMixin, _RestManagerBase): GitlabListError: If the server cannot perform the request """ - data, _ = utils._transform_types(kwargs, self._types, transform_files=False) + data, _ = utils._transform_types( + data=kwargs, + custom_types=self._types, + transform_data=True, + transform_files=False, + ) if self.gitlab.per_page: data.setdefault("per_page", self.gitlab.per_page) @@ -303,7 +308,9 @@ class CreateMixin(_RestManagerBase): data = {} self._create_attrs.validate_attrs(data=data) - data, files = utils._transform_types(data, self._types) + data, files = utils._transform_types( + data=data, custom_types=self._types, transform_data=False + ) # Handle specific URL for creation path = kwargs.pop("path", self.path) @@ -370,7 +377,9 @@ class UpdateMixin(_RestManagerBase): if self._obj_cls is not None and self._obj_cls._id_attr is not None: excludes = [self._obj_cls._id_attr] self._update_attrs.validate_attrs(data=new_data, excludes=excludes) - new_data, files = utils._transform_types(new_data, self._types) + new_data, files = utils._transform_types( + data=new_data, custom_types=self._types, transform_data=False + ) http_method = self._get_update_method() result = http_method(path, post_data=new_data, files=files, **kwargs) diff --git a/gitlab/types.py b/gitlab/types.py index f811a6f..d683b70 100644 --- a/gitlab/types.py +++ b/gitlab/types.py @@ -63,8 +63,8 @@ class GitlabAttribute: def set_from_cli(self, cli_value: Any) -> None: self._value = cli_value - def get_for_api(self) -> Any: - return self._value + def get_for_api(self, *, key: str) -> Tuple[str, Any]: + return (key, self._value) class _ListArrayAttribute(GitlabAttribute): @@ -76,20 +76,28 @@ class _ListArrayAttribute(GitlabAttribute): else: self._value = [item.strip() for item in cli_value.split(",")] - def get_for_api(self) -> str: + def get_for_api(self, *, key: str) -> Tuple[str, str]: # Do not comma-split single value passed as string if isinstance(self._value, str): - return self._value + return (key, self._value) if TYPE_CHECKING: assert isinstance(self._value, list) - return ",".join([str(x) for x in self._value]) + return (key, ",".join([str(x) for x in self._value])) class ArrayAttribute(_ListArrayAttribute): """To support `array` types as documented in https://docs.gitlab.com/ee/api/#array""" + def get_for_api(self, *, key: str) -> Tuple[str, Any]: + if isinstance(self._value, str): + return (f"{key}[]", self._value) + + if TYPE_CHECKING: + assert isinstance(self._value, list) + return (f"{key}[]", self._value) + class CommaSeparatedListAttribute(_ListArrayAttribute): """For values which are sent to the server as a Comma Separated Values @@ -98,8 +106,8 @@ class CommaSeparatedListAttribute(_ListArrayAttribute): class LowercaseStringAttribute(GitlabAttribute): - def get_for_api(self) -> str: - return str(self._value).lower() + def get_for_api(self, *, key: str) -> Tuple[str, str]: + return (key, str(self._value).lower()) class FileAttribute(GitlabAttribute): diff --git a/gitlab/utils.py b/gitlab/utils.py index 4d2ec8d..f3d97f7 100644 --- a/gitlab/utils.py +++ b/gitlab/utils.py @@ -55,34 +55,53 @@ def response_content( def _transform_types( - data: Dict[str, Any], custom_types: dict, *, transform_files: Optional[bool] = True + data: Dict[str, Any], + custom_types: dict, + *, + transform_data: bool, + transform_files: Optional[bool] = True, ) -> Tuple[dict, dict]: """Copy the data dict with attributes that have custom types and transform them before being sent to the server. - If ``transform_files`` is ``True`` (default), also populates the ``files`` dict for + ``transform_files``: If ``True`` (default), also populates the ``files`` dict for FileAttribute types with tuples to prepare fields for requests' MultipartEncoder: https://toolbelt.readthedocs.io/en/latest/user.html#multipart-form-data-encoder + ``transform_data``: If ``True`` transforms the ``data`` dict with fields + suitable for encoding as query parameters for GitLab's API: + https://docs.gitlab.com/ee/api/#encoding-api-parameters-of-array-and-hash-types + Returns: A tuple of the transformed data dict and files dict""" # Duplicate data to avoid messing with what the user sent us data = data.copy() + if not transform_files and not transform_data: + return data, {} + files = {} - for attr_name, type_cls in custom_types.items(): + for attr_name, attr_class in custom_types.items(): if attr_name not in data: continue - type_obj = type_cls(data[attr_name]) + gitlab_attribute = attr_class(data[attr_name]) - # if the type if FileAttribute we need to pass the data as file - if transform_files and isinstance(type_obj, types.FileAttribute): - key = type_obj.get_file_name(attr_name) + # if the type is FileAttribute we need to pass the data as file + if isinstance(gitlab_attribute, types.FileAttribute) and transform_files: + key = gitlab_attribute.get_file_name(attr_name) files[attr_name] = (key, data.pop(attr_name)) - else: - data[attr_name] = type_obj.get_for_api() + continue + + if not transform_data: + continue + + if isinstance(gitlab_attribute, types.GitlabAttribute): + key, value = gitlab_attribute.get_for_api(key=attr_name) + if key != attr_name: + del data[attr_name] + data[key] = value return data, files @@ -94,6 +113,8 @@ def copy_dict( ) -> None: for k, v in src.items(): if isinstance(v, dict): + # NOTE(jlvillal): This provides some support for the `hash` type + # https://docs.gitlab.com/ee/api/#hash # Transform dict values to new attributes. For example: # custom_attributes: {'foo', 'bar'} => # "custom_attributes['foo']": "bar" |