| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 194ee01 commit 1af44ce
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -238,7 +238,12 @@ def list(self, **kwargs: Any) -> Union[base.RESTObjectList, List[base.RESTObject | |||
| 238 | 238 | GitlabListError: If the server cannot perform the request | |
| 239 | 239 | """ | |
| 240 | 240 | ||
| 241 | - data, _ = utils._transform_types(kwargs, self._types, transform_files=False) | ||
| 241 | + data, _ = utils._transform_types( | ||
| 242 | + data=kwargs, | ||
| 243 | + custom_types=self._types, | ||
| 244 | + transform_data=True, | ||
| 245 | + transform_files=False, | ||
| 246 | + ) | ||
| 242 | 247 | ||
| 243 | 248 | if self.gitlab.per_page: | |
| 244 | 249 | data.setdefault("per_page", self.gitlab.per_page) | |
@@ -303,7 +308,9 @@ def create( | |||
| 303 | 308 | data = {} | |
| 304 | 309 | ||
| 305 | 310 | self._create_attrs.validate_attrs(data=data) | |
| 306 | - data, files = utils._transform_types(data, self._types) | ||
| 311 | + data, files = utils._transform_types( | ||
| 312 | + data=data, custom_types=self._types, transform_data=False | ||
| 313 | + ) | ||
| 307 | 314 | ||
| 308 | 315 | # Handle specific URL for creation | |
| 309 | 316 | path = kwargs.pop("path", self.path) | |
@@ -370,7 +377,9 @@ def update( | |||
| 370 | 377 | if self._obj_cls is not None and self._obj_cls._id_attr is not None: | |
| 371 | 378 | excludes = [self._obj_cls._id_attr] | |
| 372 | 379 | self._update_attrs.validate_attrs(data=new_data, excludes=excludes) | |
| 373 | - new_data, files = utils._transform_types(new_data, self._types) | ||
| 380 | + new_data, files = utils._transform_types( | ||
| 381 | + data=new_data, custom_types=self._types, transform_data=False | ||
| 382 | + ) | ||
| 374 | 383 | ||
| 375 | 384 | http_method = self._get_update_method() | |
| 376 | 385 | result = http_method(path, post_data=new_data, files=files, **kwargs) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -63,8 +63,8 @@ def get(self) -> Any: | |||
| 63 | 63 | def set_from_cli(self, cli_value: Any) -> None: | |
| 64 | 64 | self._value = cli_value | |
| 65 | 65 | ||
| 66 | - def get_for_api(self) -> Any: | ||
| 67 | - return self._value | ||
| 66 | + def get_for_api(self, *, key: str) -> Tuple[str, Any]: | ||
| 67 | + return (key, self._value) | ||
| 68 | 68 | ||
| 69 | 69 | ||
| 70 | 70 | class _ListArrayAttribute(GitlabAttribute): | |
@@ -76,20 +76,28 @@ def set_from_cli(self, cli_value: str) -> None: | |||
| 76 | 76 | else: | |
| 77 | 77 | self._value = [item.strip() for item in cli_value.split(",")] | |
| 78 | 78 | ||
| 79 | - def get_for_api(self) -> str: | ||
| 79 | + def get_for_api(self, *, key: str) -> Tuple[str, str]: | ||
| 80 | 80 | # Do not comma-split single value passed as string | |
| 81 | 81 | if isinstance(self._value, str): | |
| 82 | - return self._value | ||
| 82 | + return (key, self._value) | ||
| 83 | 83 | ||
| 84 | 84 | if TYPE_CHECKING: | |
| 85 | 85 | assert isinstance(self._value, list) | |
| 86 | - return ",".join([str(x) for x in self._value]) | ||
| 86 | + return (key, ",".join([str(x) for x in self._value])) | ||
| 87 | 87 | ||
| 88 | 88 | ||
| 89 | 89 | class ArrayAttribute(_ListArrayAttribute): | |
| 90 | 90 | """To support `array` types as documented in | |
| 91 | 91 | https://docs.gitlab.com/ee/api/#array""" | |
| 92 | 92 | ||
| 93 | + def get_for_api(self, *, key: str) -> Tuple[str, Any]: | ||
| 94 | + if isinstance(self._value, str): | ||
| 95 | + return (f"{key}[]", self._value) | ||
| 96 | + | ||
| 97 | + if TYPE_CHECKING: | ||
| 98 | + assert isinstance(self._value, list) | ||
| 99 | + return (f"{key}[]", self._value) | ||
| 100 | + | ||
| 93 | 101 | ||
| 94 | 102 | class CommaSeparatedListAttribute(_ListArrayAttribute): | |
| 95 | 103 | """For values which are sent to the server as a Comma Separated Values | |
@@ -98,8 +106,8 @@ class CommaSeparatedListAttribute(_ListArrayAttribute): | |||
| 98 | 106 | ||
| 99 | 107 | ||
| 100 | 108 | class LowercaseStringAttribute(GitlabAttribute): | |
| 101 | - def get_for_api(self) -> str: | ||
| 102 | - return str(self._value).lower() | ||
| 109 | + def get_for_api(self, *, key: str) -> Tuple[str, str]: | ||
| 110 | + return (key, str(self._value).lower()) | ||
| 103 | 111 | ||
| 104 | 112 | ||
| 105 | 113 | class FileAttribute(GitlabAttribute): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -55,34 +55,53 @@ def response_content( | |||
| 55 | 55 | ||
| 56 | 56 | ||
| 57 | 57 | def _transform_types( | |
| 58 | - data: Dict[str, Any], custom_types: dict, *, transform_files: Optional[bool] = True | ||
| 58 | + data: Dict[str, Any], | ||
| 59 | + custom_types: dict, | ||
| 60 | + *, | ||
| 61 | + transform_data: bool, | ||
| 62 | + transform_files: Optional[bool] = True, | ||
| 59 | 63 | ) -> Tuple[dict, dict]: | |
| 60 | 64 | """Copy the data dict with attributes that have custom types and transform them | |
| 61 | 65 | before being sent to the server. | |
| 62 | 66 | ||
| 63 | - If ``transform_files`` is ``True`` (default), also populates the ``files`` dict for | ||
| 67 | + ``transform_files``: If ``True`` (default), also populates the ``files`` dict for | ||
| 64 | 68 | FileAttribute types with tuples to prepare fields for requests' MultipartEncoder: | |
| 65 | 69 | https://toolbelt.readthedocs.io/en/latest/user.html#multipart-form-data-encoder | |
| 66 | 70 | ||
| 71 | + ``transform_data``: If ``True`` transforms the ``data`` dict with fields | ||
| 72 | + suitable for encoding as query parameters for GitLab's API: | ||
| 73 | + https://docs.gitlab.com/ee/api/#encoding-api-parameters-of-array-and-hash-types | ||
| 74 | + | ||
| 67 | 75 | Returns: | |
| 68 | 76 | A tuple of the transformed data dict and files dict""" | |
| 69 | 77 | ||
| 70 | 78 | # Duplicate data to avoid messing with what the user sent us | |
| 71 | 79 | data = data.copy() | |
| 80 | + if not transform_files and not transform_data: | ||
| 81 | + return data, {} | ||
| 82 | + | ||
| 72 | 83 | files = {} | |
| 73 | 84 | ||
| 74 | - for attr_name, type_cls in custom_types.items(): | ||
| 85 | + for attr_name, attr_class in custom_types.items(): | ||
| 75 | 86 | if attr_name not in data: | |
| 76 | 87 | continue | |
| 77 | 88 | ||
| 78 | - type_obj = type_cls(data[attr_name]) | ||
| 89 | + gitlab_attribute = attr_class(data[attr_name]) | ||
| 79 | 90 | ||
| 80 | - # if the type if FileAttribute we need to pass the data as file | ||
| 81 | - if transform_files and isinstance(type_obj, types.FileAttribute): | ||
| 82 | - key = type_obj.get_file_name(attr_name) | ||
| 91 | + # if the type is FileAttribute we need to pass the data as file | ||
| 92 | + if isinstance(gitlab_attribute, types.FileAttribute) and transform_files: | ||
| 93 | + key = gitlab_attribute.get_file_name(attr_name) | ||
| 83 | 94 | files[attr_name] = (key, data.pop(attr_name)) | |
| 84 | - else: | ||
| 85 | - data[attr_name] = type_obj.get_for_api() | ||
| 95 | + continue | ||
| 96 | + | ||
| 97 | + if not transform_data: | ||
| 98 | + continue | ||
| 99 | + | ||
| 100 | + if isinstance(gitlab_attribute, types.GitlabAttribute): | ||
| 101 | + key, value = gitlab_attribute.get_for_api(key=attr_name) | ||
| 102 | + if key != attr_name: | ||
| 103 | + del data[attr_name] | ||
| 104 | + data[key] = value | ||
| 86 | 105 | ||
| 87 | 106 | return data, files | |
| 88 | 107 | ||
@@ -94,6 +113,8 @@ def copy_dict( | |||
| 94 | 113 | ) -> None: | |
| 95 | 114 | for k, v in src.items(): | |
| 96 | 115 | if isinstance(v, dict): | |
| 116 | + # NOTE(jlvillal): This provides some support for the `hash` type | ||
| 117 | + # https://docs.gitlab.com/ee/api/#hash | ||
| 97 | 118 | # Transform dict values to new attributes. For example: | |
| 98 | 119 | # custom_attributes: {'foo', 'bar'} => | |
| 99 | 120 | # "custom_attributes['foo']": "bar" | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -99,6 +99,11 @@ def test_groups(gl): | |||
| 99 | 99 | assert len(group1.members.list()) == 3 | |
| 100 | 100 | assert len(group2.members.list()) == 2 | |
| 101 | 101 | ||
| 102 | + # Test `user_ids` array | ||
| 103 | + result = group1.members.list(user_ids=[user.id, 99999]) | ||
| 104 | + assert len(result) == 1 | ||
| 105 | + assert result[0].id == user.id | ||
| 106 | + | ||
| 102 | 107 | group1.members.delete(user.id) | |
| 103 | 108 | assert user not in group1.members.list() | |
| 104 | 109 | assert group1.members_all.list() | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -205,6 +205,25 @@ class M(ListMixin, FakeManager): | |||
| 205 | 205 | assert responses.assert_call_count(url, 2) is True | |
| 206 | 206 | ||
| 207 | 207 | ||
| 208 | + @responses.activate | ||
| 209 | + def test_list_mixin_with_attributes(gl): | ||
| 210 | + class M(ListMixin, FakeManager): | ||
| 211 | + _types = {"my_array": gl_types.ArrayAttribute} | ||
| 212 | + | ||
| 213 | + url = "http://localhost/api/v4/tests" | ||
| 214 | + responses.add( | ||
| 215 | + method=responses.GET, | ||
| 216 | + headers={}, | ||
| 217 | + url=url, | ||
| 218 | + json=[], | ||
| 219 | + status=200, | ||
| 220 | + match=[responses.matchers.query_param_matcher({"my_array[]": ["1", "2", "3"]})], | ||
| 221 | + ) | ||
| 222 | + | ||
| 223 | + mgr = M(gl) | ||
| 224 | + mgr.list(iterator=True, my_array=[1, 2, 3]) | ||
| 225 | + | ||
| 226 | + | ||
| 208 | 227 | @responses.activate | |
| 209 | 228 | def test_list_other_url(gl): | |
| 210 | 229 | class M(ListMixin, FakeManager): | |
@@ -295,6 +314,25 @@ class M(CreateMixin, FakeManager): | |||
| 295 | 314 | assert responses.assert_call_count(url, 1) is True | |
| 296 | 315 | ||
| 297 | 316 | ||
| 317 | + @responses.activate | ||
| 318 | + def test_create_mixin_with_attributes(gl): | ||
| 319 | + class M(CreateMixin, FakeManager): | ||
| 320 | + _types = {"my_array": gl_types.ArrayAttribute} | ||
| 321 | + | ||
| 322 | + url = "http://localhost/api/v4/tests" | ||
| 323 | + responses.add( | ||
| 324 | + method=responses.POST, | ||
| 325 | + headers={}, | ||
| 326 | + url=url, | ||
| 327 | + json={}, | ||
| 328 | + status=200, | ||
| 329 | + match=[responses.matchers.json_params_matcher({"my_array": [1, 2, 3]})], | ||
| 330 | + ) | ||
| 331 | + | ||
| 332 | + mgr = M(gl) | ||
| 333 | + mgr.create({"my_array": [1, 2, 3]}) | ||
| 334 | + | ||
| 335 | + | ||
| 298 | 336 | def test_update_mixin_missing_attrs(gl): | |
| 299 | 337 | class M(UpdateMixin, FakeManager): | |
| 300 | 338 | _update_attrs = gl_types.RequiredOptional( | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -73,7 +73,7 @@ def test_gitlab_attribute_get(): | |||
| 73 | 73 | ||
| 74 | 74 | o.set_from_cli("whatever2") | |
| 75 | 75 | assert o.get() == "whatever2" | |
| 76 | - assert o.get_for_api() == "whatever2" | ||
| 76 | + assert o.get_for_api(key="spam") == ("spam", "whatever2") | ||
| 77 | 77 | ||
| 78 | 78 | o = types.GitlabAttribute() | |
| 79 | 79 | assert o._value is None | |
@@ -100,42 +100,42 @@ def test_array_attribute_empty_input(): | |||
| 100 | 100 | def test_array_attribute_get_for_api_from_cli(): | |
| 101 | 101 | o = types.ArrayAttribute() | |
| 102 | 102 | o.set_from_cli("foo,bar,baz") | |
| 103 | - assert o.get_for_api() == "foo,bar,baz" | ||
| 103 | + assert o.get_for_api(key="spam") == ("spam[]", ["foo", "bar", "baz"]) | ||
| 104 | 104 | ||
| 105 | 105 | ||
| 106 | 106 | def test_array_attribute_get_for_api_from_list(): | |
| 107 | 107 | o = types.ArrayAttribute(["foo", "bar", "baz"]) | |
| 108 | - assert o.get_for_api() == "foo,bar,baz" | ||
| 108 | + assert o.get_for_api(key="spam") == ("spam[]", ["foo", "bar", "baz"]) | ||
| 109 | 109 | ||
| 110 | 110 | ||
| 111 | 111 | def test_array_attribute_get_for_api_from_int_list(): | |
| 112 | 112 | o = types.ArrayAttribute([1, 9, 7]) | |
| 113 | - assert o.get_for_api() == "1,9,7" | ||
| 113 | + assert o.get_for_api(key="spam") == ("spam[]", [1, 9, 7]) | ||
| 114 | 114 | ||
| 115 | 115 | ||
| 116 | 116 | def test_array_attribute_does_not_split_string(): | |
| 117 | 117 | o = types.ArrayAttribute("foo") | |
| 118 | - assert o.get_for_api() == "foo" | ||
| 118 | + assert o.get_for_api(key="spam") == ("spam[]", "foo") | ||
| 119 | 119 | ||
| 120 | 120 | ||
| 121 | 121 | # CommaSeparatedListAttribute tests | |
| 122 | 122 | def test_csv_string_attribute_get_for_api_from_cli(): | |
| 123 | 123 | o = types.CommaSeparatedListAttribute() | |
| 124 | 124 | o.set_from_cli("foo,bar,baz") | |
| 125 | - assert o.get_for_api() == "foo,bar,baz" | ||
| 125 | + assert o.get_for_api(key="spam") == ("spam", "foo,bar,baz") | ||
| 126 | 126 | ||
| 127 | 127 | ||
| 128 | 128 | def test_csv_string_attribute_get_for_api_from_list(): | |
| 129 | 129 | o = types.CommaSeparatedListAttribute(["foo", "bar", "baz"]) | |
| 130 | - assert o.get_for_api() == "foo,bar,baz" | ||
| 130 | + assert o.get_for_api(key="spam") == ("spam", "foo,bar,baz") | ||
| 131 | 131 | ||
| 132 | 132 | ||
| 133 | 133 | def test_csv_string_attribute_get_for_api_from_int_list(): | |
| 134 | 134 | o = types.CommaSeparatedListAttribute([1, 9, 7]) | |
| 135 | - assert o.get_for_api() == "1,9,7" | ||
| 135 | + assert o.get_for_api(key="spam") == ("spam", "1,9,7") | ||
| 136 | 136 | ||
| 137 | 137 | ||
| 138 | 138 | # LowercaseStringAttribute tests | |
| 139 | 139 | def test_lowercase_string_attribute_get_for_api(): | |
| 140 | 140 | o = types.LowercaseStringAttribute("FOO") | |
| 141 | - assert o.get_for_api() == "foo" | ||
| 141 | + assert o.get_for_api(key="spam") == ("spam", "foo") | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -155,7 +155,7 @@ def test_remove_none_from_dict(dictionary, expected): | |||
| 155 | 155 | ||
| 156 | 156 | def test_transform_types_copies_data_with_empty_files(): | |
| 157 | 157 | data = {"attr": "spam"} | |
| 158 | - new_data, files = utils._transform_types(data, {}) | ||
| 158 | + new_data, files = utils._transform_types(data, {}, transform_data=True) | ||
| 159 | 159 | ||
| 160 | 160 | assert new_data is not data | |
| 161 | 161 | assert new_data == data | |
@@ -165,7 +165,7 @@ def test_transform_types_copies_data_with_empty_files(): | |||
| 165 | 165 | def test_transform_types_with_transform_files_populates_files(): | |
| 166 | 166 | custom_types = {"attr": types.FileAttribute} | |
| 167 | 167 | data = {"attr": "spam"} | |
| 168 | - new_data, files = utils._transform_types(data, custom_types) | ||
| 168 | + new_data, files = utils._transform_types(data, custom_types, transform_data=True) | ||
| 169 | 169 | ||
| 170 | 170 | assert new_data == {} | |
| 171 | 171 | assert files["attr"] == ("attr", "spam") | |
@@ -174,7 +174,29 @@ def test_transform_types_with_transform_files_populates_files(): | |||
| 174 | 174 | def test_transform_types_without_transform_files_populates_data_with_empty_files(): | |
| 175 | 175 | custom_types = {"attr": types.FileAttribute} | |
| 176 | 176 | data = {"attr": "spam"} | |
| 177 | - new_data, files = utils._transform_types(data, custom_types, transform_files=False) | ||
| 177 | + new_data, files = utils._transform_types( | ||
| 178 | + data, custom_types, transform_files=False, transform_data=True | ||
| 179 | + ) | ||
| 178 | 180 | ||
| 179 | 181 | assert new_data == {"attr": "spam"} | |
| 180 | 182 | assert files == {} | |
| 183 | + | ||
| 184 | + | ||
| 185 | + def test_transform_types_params_array(): | ||
| 186 | + data = {"attr": [1, 2, 3]} | ||
| 187 | + custom_types = {"attr": types.ArrayAttribute} | ||
| 188 | + new_data, files = utils._transform_types(data, custom_types, transform_data=True) | ||
| 189 | + | ||
| 190 | + assert new_data is not data | ||
| 191 | + assert new_data == {"attr[]": [1, 2, 3]} | ||
| 192 | + assert files == {} | ||
| 193 | + | ||
| 194 | + | ||
| 195 | + def test_transform_types_not_params_array(): | ||
| 196 | + data = {"attr": [1, 2, 3]} | ||
| 197 | + custom_types = {"attr": types.ArrayAttribute} | ||
| 198 | + new_data, files = utils._transform_types(data, custom_types, transform_data=False) | ||
| 199 | + | ||
| 200 | + assert new_data is not data | ||
| 201 | + assert new_data == data | ||
| 202 | + assert files == {} | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments