Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit b44cef7

Browse filesBrowse files
fix: use the [] after key names for array variables
1. Create a new CsvStringAttribute class. This is to indicate types which are sent to the GitLab server as comma-separated-strings (CSV) but we have been allowing users to use a list-of-strings. These values are NOT array values, so adding [] to the key name breaks them. 2. Rename ListAttribute to ArrayAttribute. 3. If a value is of type ArrayAttribute then append '[]' to the name of the value. 4. Move processing of most GitlabAttributes into the client.py:http_request() method. Now we convert our params into a list of tuples so that we can have multiple identical keys but with different values. Fixes: #1698
1 parent 09a973e commit b44cef7
Copy full SHA for b44cef7

File tree

Expand file treeCollapse file tree

15 files changed

+177
-55
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

15 files changed

+177
-55
lines changed
Open diff view settings
Collapse file

‎gitlab/client.py‎

Copy file name to clipboardExpand all lines: gitlab/client.py
+31-1Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
"""Wrapper for the GitLab API."""
1818

19+
import copy
1920
import time
2021
from typing import Any, cast, Dict, List, Optional, Tuple, TYPE_CHECKING, Union
2122

@@ -26,6 +27,7 @@
2627
import gitlab.config
2728
import gitlab.const
2829
import gitlab.exceptions
30+
from gitlab import types as gl_types
2931
from gitlab import utils
3032

3133
REDIRECT_MSG = (
@@ -524,6 +526,28 @@ def _prepare_send_data(
524526

525527
return (post_data, None, "application/json")
526528

529+
@staticmethod
530+
def _prepare_dict_for_api(*, in_dict: Dict[str, Any]) -> Dict[str, Any]:
531+
result: Dict[str, Any] = {}
532+
for key, value in in_dict.items():
533+
if isinstance(value, gl_types.GitlabAttribute):
534+
result[key] = value.get_for_api()
535+
else:
536+
result[key] = copy.deepcopy(in_dict[key])
537+
return result
538+
539+
@staticmethod
540+
def _param_dict_to_param_tuples(*, params: Dict[str, Any]) -> List[Tuple[str, Any]]:
541+
"""Convert a dict to a list of key/values. This will be used to pass
542+
values to requests"""
543+
result: List[Tuple[str, Any]] = []
544+
for key, value in params.items():
545+
if isinstance(value, gl_types.GitlabAttribute):
546+
result.extend(value.get_as_tuple_list(key=key))
547+
else:
548+
result.append((key, value))
549+
return result
550+
527551
def http_request(
528552
self,
529553
verb: str,
@@ -584,6 +608,10 @@ def http_request(
584608
else:
585609
utils.copy_dict(params, kwargs)
586610

611+
tuple_params = self._param_dict_to_param_tuples(params=params)
612+
if isinstance(post_data, dict):
613+
post_data = self._prepare_dict_for_api(in_dict=post_data)
614+
587615
opts = self._get_session_opts()
588616

589617
verify = opts.pop("verify")
@@ -602,7 +630,9 @@ def http_request(
602630
# The Requests behavior is right but it seems that web servers don't
603631
# always agree with this decision (this is the case with a default
604632
# gitlab installation)
605-
req = requests.Request(verb, url, json=json, data=data, params=params, **opts)
633+
req = requests.Request(
634+
verb, url, json=json, data=data, params=tuple_params, **opts
635+
)
606636
prepped = self.session.prepare_request(req)
607637
if TYPE_CHECKING:
608638
assert prepped.url is not None
Collapse file

‎gitlab/mixins.py‎

Copy file name to clipboardExpand all lines: gitlab/mixins.py
+3-5Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,7 @@ def list(self, **kwargs: Any) -> Union[base.RESTObjectList, List[base.RESTObject
230230
if self._types:
231231
for attr_name, type_cls in self._types.items():
232232
if attr_name in data.keys():
233-
type_obj = type_cls(data[attr_name])
234-
data[attr_name] = type_obj.get_for_api()
233+
data[attr_name] = type_cls(data[attr_name])
235234

236235
# Allow to overwrite the path, handy for custom listings
237236
path = data.pop("path", self.path)
@@ -307,14 +306,13 @@ def create(
307306
for attr_name, type_cls in self._types.items():
308307
if attr_name in data.keys():
309308
type_obj = type_cls(data[attr_name])
310-
311309
# if the type if FileAttribute we need to pass the data as
312310
# file
313311
if isinstance(type_obj, g_types.FileAttribute):
314312
k = type_obj.get_file_name(attr_name)
315313
files[attr_name] = (k, data.pop(attr_name))
316314
else:
317-
data[attr_name] = type_obj.get_for_api()
315+
data[attr_name] = type_obj
318316

319317
# Handle specific URL for creation
320318
path = kwargs.pop("path", self.path)
@@ -410,7 +408,7 @@ def update(
410408
k = type_obj.get_file_name(attr_name)
411409
files[attr_name] = (k, new_data.pop(attr_name))
412410
else:
413-
new_data[attr_name] = type_obj.get_for_api()
411+
new_data[attr_name] = type_obj
414412

415413
http_method = self._get_update_method()
416414
result = http_method(path, post_data=new_data, files=files, **kwargs)
Collapse file

‎gitlab/types.py‎

Copy file name to clipboardExpand all lines: gitlab/types.py
+43-2Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# You should have received a copy of the GNU Lesser General Public License
1616
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1717

18-
from typing import Any, Optional, TYPE_CHECKING
18+
from typing import Any, List, Optional, Tuple, TYPE_CHECKING
1919

2020

2121
class GitlabAttribute(object):
@@ -31,8 +31,43 @@ def set_from_cli(self, cli_value: Any) -> None:
3131
def get_for_api(self) -> Any:
3232
return self._value
3333

34+
def get_as_tuple_list(self, *, key: str) -> List[Tuple[str, Any]]:
35+
return [(key, self._value)]
36+
37+
38+
class ArrayAttribute(GitlabAttribute):
39+
"""To support `array` types as documented in
40+
https://docs.gitlab.com/ee/api/#array"""
41+
42+
def set_from_cli(self, cli_value: str) -> None:
43+
if not cli_value.strip():
44+
self._value = []
45+
else:
46+
self._value = [item.strip() for item in cli_value.split(",")]
47+
48+
def get_for_api(self) -> str:
49+
# Do not comma-split single value passed as string
50+
if isinstance(self._value, str):
51+
return self._value
52+
53+
if TYPE_CHECKING:
54+
assert isinstance(self._value, list)
55+
return ",".join([str(x) for x in self._value])
56+
57+
def get_as_tuple_list(self, *, key: str) -> List[Tuple[str, str]]:
58+
if isinstance(self._value, str):
59+
return [(f"{key}[]", self._value)]
60+
61+
if TYPE_CHECKING:
62+
assert isinstance(self._value, list)
63+
return [(f"{key}[]", str(value)) for value in self._value]
64+
65+
66+
class CsvStringAttribute(GitlabAttribute):
67+
"""For values which are sent to the server as a Comma Separated Values
68+
(CSV) string. We allow them to be specified as a list and we convert it
69+
into a CSV"""
3470

35-
class ListAttribute(GitlabAttribute):
3671
def set_from_cli(self, cli_value: str) -> None:
3772
if not cli_value.strip():
3873
self._value = []
@@ -48,11 +83,17 @@ def get_for_api(self) -> str:
4883
assert isinstance(self._value, list)
4984
return ",".join([str(x) for x in self._value])
5085

86+
def get_as_tuple_list(self, *, key: str) -> List[Tuple[str, str]]:
87+
return [(key, self.get_for_api())]
88+
5189

5290
class LowercaseStringAttribute(GitlabAttribute):
5391
def get_for_api(self) -> str:
5492
return str(self._value).lower()
5593

94+
def get_as_tuple_list(self, *, key: str) -> List[Tuple[str, str]]:
95+
return [(key, self.get_for_api())]
96+
5697

5798
class FileAttribute(GitlabAttribute):
5899
def get_file_name(self, attr_name: Optional[str] = None) -> Optional[str]:
Collapse file

‎gitlab/v4/objects/deploy_tokens.py‎

Copy file name to clipboardExpand all lines: gitlab/v4/objects/deploy_tokens.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class GroupDeployTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
3939
"username",
4040
),
4141
)
42-
_types = {"scopes": types.ListAttribute}
42+
_types = {"scopes": types.CsvStringAttribute}
4343

4444

4545
class ProjectDeployToken(ObjectDeleteMixin, RESTObject):
@@ -60,4 +60,4 @@ class ProjectDeployTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager
6060
"username",
6161
),
6262
)
63-
_types = {"scopes": types.ListAttribute}
63+
_types = {"scopes": types.CsvStringAttribute}
Collapse file

‎gitlab/v4/objects/epics.py‎

Copy file name to clipboardExpand all lines: gitlab/v4/objects/epics.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class GroupEpicManager(CRUDMixin, RESTManager):
4242
_update_attrs = RequiredOptional(
4343
optional=("title", "labels", "description", "start_date", "end_date"),
4444
)
45-
_types = {"labels": types.ListAttribute}
45+
_types = {"labels": types.CsvStringAttribute}
4646

4747
def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> GroupEpic:
4848
return cast(GroupEpic, super().get(id=id, lazy=lazy, **kwargs))
Collapse file

‎gitlab/v4/objects/groups.py‎

Copy file name to clipboardExpand all lines: gitlab/v4/objects/groups.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ class GroupManager(CRUDMixin, RESTManager):
290290
"shared_runners_setting",
291291
),
292292
)
293-
_types = {"avatar": types.ImageAttribute, "skip_groups": types.ListAttribute}
293+
_types = {"avatar": types.ImageAttribute, "skip_groups": types.ArrayAttribute}
294294

295295
def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> Group:
296296
return cast(Group, super().get(id=id, lazy=lazy, **kwargs))
@@ -350,7 +350,7 @@ class GroupSubgroupManager(ListMixin, RESTManager):
350350
"with_custom_attributes",
351351
"min_access_level",
352352
)
353-
_types = {"skip_groups": types.ListAttribute}
353+
_types = {"skip_groups": types.ArrayAttribute}
354354

355355

356356
class GroupDescendantGroup(RESTObject):
Collapse file

‎gitlab/v4/objects/issues.py‎

Copy file name to clipboardExpand all lines: gitlab/v4/objects/issues.py
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class IssueManager(RetrieveMixin, RESTManager):
6565
"updated_after",
6666
"updated_before",
6767
)
68-
_types = {"iids": types.ListAttribute, "labels": types.ListAttribute}
68+
_types = {"iids": types.ArrayAttribute, "labels": types.CsvStringAttribute}
6969

7070
def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> Issue:
7171
return cast(Issue, super().get(id=id, lazy=lazy, **kwargs))
@@ -95,7 +95,7 @@ class GroupIssueManager(ListMixin, RESTManager):
9595
"updated_after",
9696
"updated_before",
9797
)
98-
_types = {"iids": types.ListAttribute, "labels": types.ListAttribute}
98+
_types = {"iids": types.ArrayAttribute, "labels": types.CsvStringAttribute}
9999

100100

101101
class ProjectIssue(
@@ -233,7 +233,7 @@ class ProjectIssueManager(CRUDMixin, RESTManager):
233233
"discussion_locked",
234234
),
235235
)
236-
_types = {"iids": types.ListAttribute, "labels": types.ListAttribute}
236+
_types = {"iids": types.ArrayAttribute, "labels": types.CsvStringAttribute}
237237

238238
def get(
239239
self, id: Union[str, int], lazy: bool = False, **kwargs: Any
Collapse file

‎gitlab/v4/objects/members.py‎

Copy file name to clipboardExpand all lines: gitlab/v4/objects/members.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class GroupMemberManager(CRUDMixin, RESTManager):
3939
_update_attrs = RequiredOptional(
4040
required=("access_level",), optional=("expires_at",)
4141
)
42-
_types = {"user_ids": types.ListAttribute}
42+
_types = {"user_ids": types.ArrayAttribute}
4343

4444
def get(
4545
self, id: Union[str, int], lazy: bool = False, **kwargs: Any
@@ -95,7 +95,7 @@ class ProjectMemberManager(CRUDMixin, RESTManager):
9595
_update_attrs = RequiredOptional(
9696
required=("access_level",), optional=("expires_at",)
9797
)
98-
_types = {"user_ids": types.ListAttribute}
98+
_types = {"user_ids": types.ArrayAttribute}
9999

100100
def get(
101101
self, id: Union[str, int], lazy: bool = False, **kwargs: Any
Collapse file

‎gitlab/v4/objects/merge_requests.py‎

Copy file name to clipboardExpand all lines: gitlab/v4/objects/merge_requests.py
+11-11Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,10 @@ class MergeRequestManager(ListMixin, RESTManager):
9595
"deployed_after",
9696
)
9797
_types = {
98-
"approver_ids": types.ListAttribute,
99-
"approved_by_ids": types.ListAttribute,
100-
"in": types.ListAttribute,
101-
"labels": types.ListAttribute,
98+
"approver_ids": types.ArrayAttribute,
99+
"approved_by_ids": types.ArrayAttribute,
100+
"in": types.CsvStringAttribute,
101+
"labels": types.CsvStringAttribute,
102102
}
103103

104104

@@ -133,9 +133,9 @@ class GroupMergeRequestManager(ListMixin, RESTManager):
133133
"wip",
134134
)
135135
_types = {
136-
"approver_ids": types.ListAttribute,
137-
"approved_by_ids": types.ListAttribute,
138-
"labels": types.ListAttribute,
136+
"approver_ids": types.ArrayAttribute,
137+
"approved_by_ids": types.ArrayAttribute,
138+
"labels": types.CsvStringAttribute,
139139
}
140140

141141

@@ -450,10 +450,10 @@ class ProjectMergeRequestManager(CRUDMixin, RESTManager):
450450
"wip",
451451
)
452452
_types = {
453-
"approver_ids": types.ListAttribute,
454-
"approved_by_ids": types.ListAttribute,
455-
"iids": types.ListAttribute,
456-
"labels": types.ListAttribute,
453+
"approver_ids": types.ArrayAttribute,
454+
"approved_by_ids": types.ArrayAttribute,
455+
"iids": types.ArrayAttribute,
456+
"labels": types.CsvStringAttribute,
457457
}
458458

459459
def get(
Collapse file

‎gitlab/v4/objects/milestones.py‎

Copy file name to clipboardExpand all lines: gitlab/v4/objects/milestones.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class GroupMilestoneManager(CRUDMixin, RESTManager):
9393
optional=("title", "description", "due_date", "start_date", "state_event"),
9494
)
9595
_list_filters = ("iids", "state", "search")
96-
_types = {"iids": types.ListAttribute}
96+
_types = {"iids": types.ArrayAttribute}
9797

9898
def get(
9999
self, id: Union[str, int], lazy: bool = False, **kwargs: Any
@@ -177,7 +177,7 @@ class ProjectMilestoneManager(CRUDMixin, RESTManager):
177177
optional=("title", "description", "due_date", "start_date", "state_event"),
178178
)
179179
_list_filters = ("iids", "state", "search")
180-
_types = {"iids": types.ListAttribute}
180+
_types = {"iids": types.ArrayAttribute}
181181

182182
def get(
183183
self, id: Union[str, int], lazy: bool = False, **kwargs: Any

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.