From a57334f1930752c70ea15847a39324fa94042460 Mon Sep 17 00:00:00 2001 From: "John L. Villalovos" Date: Sun, 30 Jan 2022 10:44:08 -0800 Subject: [PATCH] chore: create new ArrayAttribute class Create a new ArrayAttribute class. This is to indicate types which are sent to the GitLab server as arrays https://docs.gitlab.com/ee/api/#array At this stage it is identical to the CommaSeparatedListAttribute class but will be used later to support the array types sent to GitLab. This is the second step 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 [1] https://docs.gitlab.com/ee/api/#encoding-api-parameters-of-array-and-hash-types Related: #1698 --- gitlab/types.py | 15 ++++++++++- gitlab/v4/objects/groups.py | 7 ++--- gitlab/v4/objects/issues.py | 15 +++-------- gitlab/v4/objects/members.py | 4 +-- gitlab/v4/objects/merge_requests.py | 14 +++++----- gitlab/v4/objects/milestones.py | 4 +-- gitlab/v4/objects/projects.py | 2 +- gitlab/v4/objects/settings.py | 12 ++++----- gitlab/v4/objects/users.py | 2 +- tests/unit/test_types.py | 42 ++++++++++++++++++++--------- 10 files changed, 68 insertions(+), 49 deletions(-) diff --git a/gitlab/types.py b/gitlab/types.py index 2dc812114..bf74f2e8a 100644 --- a/gitlab/types.py +++ b/gitlab/types.py @@ -32,7 +32,9 @@ def get_for_api(self) -> Any: return self._value -class CommaSeparatedListAttribute(GitlabAttribute): +class _ListArrayAttribute(GitlabAttribute): + """Helper class to support `list` / `array` types.""" + def set_from_cli(self, cli_value: str) -> None: if not cli_value.strip(): self._value = [] @@ -49,6 +51,17 @@ def get_for_api(self) -> str: return ",".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""" + + +class CommaSeparatedListAttribute(_ListArrayAttribute): + """For values which are sent to the server as a Comma Separated Values + (CSV) string. We allow them to be specified as a list and we convert it + into a CSV""" + + class LowercaseStringAttribute(GitlabAttribute): def get_for_api(self) -> str: return str(self._value).lower() diff --git a/gitlab/v4/objects/groups.py b/gitlab/v4/objects/groups.py index 5e2ac00b9..a3a1051b0 100644 --- a/gitlab/v4/objects/groups.py +++ b/gitlab/v4/objects/groups.py @@ -314,10 +314,7 @@ class GroupManager(CRUDMixin, RESTManager): "shared_runners_setting", ), ) - _types = { - "avatar": types.ImageAttribute, - "skip_groups": types.CommaSeparatedListAttribute, - } + _types = {"avatar": types.ImageAttribute, "skip_groups": types.ArrayAttribute} def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> Group: return cast(Group, super().get(id=id, lazy=lazy, **kwargs)) @@ -377,7 +374,7 @@ class GroupSubgroupManager(ListMixin, RESTManager): "with_custom_attributes", "min_access_level", ) - _types = {"skip_groups": types.CommaSeparatedListAttribute} + _types = {"skip_groups": types.ArrayAttribute} class GroupDescendantGroup(RESTObject): diff --git a/gitlab/v4/objects/issues.py b/gitlab/v4/objects/issues.py index 3452daf91..f20252bd1 100644 --- a/gitlab/v4/objects/issues.py +++ b/gitlab/v4/objects/issues.py @@ -65,10 +65,7 @@ class IssueManager(RetrieveMixin, RESTManager): "updated_after", "updated_before", ) - _types = { - "iids": types.CommaSeparatedListAttribute, - "labels": types.CommaSeparatedListAttribute, - } + _types = {"iids": types.ArrayAttribute, "labels": types.CommaSeparatedListAttribute} def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> Issue: return cast(Issue, super().get(id=id, lazy=lazy, **kwargs)) @@ -98,10 +95,7 @@ class GroupIssueManager(ListMixin, RESTManager): "updated_after", "updated_before", ) - _types = { - "iids": types.CommaSeparatedListAttribute, - "labels": types.CommaSeparatedListAttribute, - } + _types = {"iids": types.ArrayAttribute, "labels": types.CommaSeparatedListAttribute} class ProjectIssue( @@ -239,10 +233,7 @@ class ProjectIssueManager(CRUDMixin, RESTManager): "discussion_locked", ), ) - _types = { - "iids": types.CommaSeparatedListAttribute, - "labels": types.CommaSeparatedListAttribute, - } + _types = {"iids": types.ArrayAttribute, "labels": types.CommaSeparatedListAttribute} def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any diff --git a/gitlab/v4/objects/members.py b/gitlab/v4/objects/members.py index 16fb92521..5ee0b0e4e 100644 --- a/gitlab/v4/objects/members.py +++ b/gitlab/v4/objects/members.py @@ -41,7 +41,7 @@ class GroupMemberManager(CRUDMixin, RESTManager): _update_attrs = RequiredOptional( required=("access_level",), optional=("expires_at",) ) - _types = {"user_ids": types.CommaSeparatedListAttribute} + _types = {"user_ids": types.ArrayAttribute} def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any @@ -101,7 +101,7 @@ class ProjectMemberManager(CRUDMixin, RESTManager): _update_attrs = RequiredOptional( required=("access_level",), optional=("expires_at",) ) - _types = {"user_ids": types.CommaSeparatedListAttribute} + _types = {"user_ids": types.ArrayAttribute} def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any diff --git a/gitlab/v4/objects/merge_requests.py b/gitlab/v4/objects/merge_requests.py index 7f0be4bc1..edd7d0195 100644 --- a/gitlab/v4/objects/merge_requests.py +++ b/gitlab/v4/objects/merge_requests.py @@ -95,8 +95,8 @@ class MergeRequestManager(ListMixin, RESTManager): "deployed_after", ) _types = { - "approver_ids": types.CommaSeparatedListAttribute, - "approved_by_ids": types.CommaSeparatedListAttribute, + "approver_ids": types.ArrayAttribute, + "approved_by_ids": types.ArrayAttribute, "in": types.CommaSeparatedListAttribute, "labels": types.CommaSeparatedListAttribute, } @@ -133,8 +133,8 @@ class GroupMergeRequestManager(ListMixin, RESTManager): "wip", ) _types = { - "approver_ids": types.CommaSeparatedListAttribute, - "approved_by_ids": types.CommaSeparatedListAttribute, + "approver_ids": types.ArrayAttribute, + "approved_by_ids": types.ArrayAttribute, "labels": types.CommaSeparatedListAttribute, } @@ -455,9 +455,9 @@ class ProjectMergeRequestManager(CRUDMixin, RESTManager): "wip", ) _types = { - "approver_ids": types.CommaSeparatedListAttribute, - "approved_by_ids": types.CommaSeparatedListAttribute, - "iids": types.CommaSeparatedListAttribute, + "approver_ids": types.ArrayAttribute, + "approved_by_ids": types.ArrayAttribute, + "iids": types.ArrayAttribute, "labels": types.CommaSeparatedListAttribute, } diff --git a/gitlab/v4/objects/milestones.py b/gitlab/v4/objects/milestones.py index dc6266ada..da75826db 100644 --- a/gitlab/v4/objects/milestones.py +++ b/gitlab/v4/objects/milestones.py @@ -93,7 +93,7 @@ class GroupMilestoneManager(CRUDMixin, RESTManager): optional=("title", "description", "due_date", "start_date", "state_event"), ) _list_filters = ("iids", "state", "search") - _types = {"iids": types.CommaSeparatedListAttribute} + _types = {"iids": types.ArrayAttribute} def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any @@ -177,7 +177,7 @@ class ProjectMilestoneManager(CRUDMixin, RESTManager): optional=("title", "description", "due_date", "start_date", "state_event"), ) _list_filters = ("iids", "state", "search") - _types = {"iids": types.CommaSeparatedListAttribute} + _types = {"iids": types.ArrayAttribute} def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py index 354e56efa..23f3d3c87 100644 --- a/gitlab/v4/objects/projects.py +++ b/gitlab/v4/objects/projects.py @@ -125,7 +125,7 @@ class ProjectGroupManager(ListMixin, RESTManager): "shared_min_access_level", "shared_visible_only", ) - _types = {"skip_groups": types.CommaSeparatedListAttribute} + _types = {"skip_groups": types.ArrayAttribute} class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTObject): diff --git a/gitlab/v4/objects/settings.py b/gitlab/v4/objects/settings.py index 3075d9ce2..9be545c12 100644 --- a/gitlab/v4/objects/settings.py +++ b/gitlab/v4/objects/settings.py @@ -80,12 +80,12 @@ class ApplicationSettingsManager(GetWithoutIdMixin, UpdateMixin, RESTManager): ), ) _types = { - "asset_proxy_allowlist": types.CommaSeparatedListAttribute, - "disabled_oauth_sign_in_sources": types.CommaSeparatedListAttribute, - "domain_allowlist": types.CommaSeparatedListAttribute, - "domain_denylist": types.CommaSeparatedListAttribute, - "import_sources": types.CommaSeparatedListAttribute, - "restricted_visibility_levels": types.CommaSeparatedListAttribute, + "asset_proxy_allowlist": types.ArrayAttribute, + "disabled_oauth_sign_in_sources": types.ArrayAttribute, + "domain_allowlist": types.ArrayAttribute, + "domain_denylist": types.ArrayAttribute, + "import_sources": types.ArrayAttribute, + "restricted_visibility_levels": types.ArrayAttribute, } @exc.on_http_error(exc.GitlabUpdateError) diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py index e3553b0e5..b2de33733 100644 --- a/gitlab/v4/objects/users.py +++ b/gitlab/v4/objects/users.py @@ -369,7 +369,7 @@ class ProjectUserManager(ListMixin, RESTManager): _obj_cls = ProjectUser _from_parent_attrs = {"project_id": "id"} _list_filters = ("search", "skip_users") - _types = {"skip_users": types.CommaSeparatedListAttribute} + _types = {"skip_users": types.ArrayAttribute} class UserEmail(ObjectDeleteMixin, RESTObject): diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index b3249d1b0..ae192b4cb 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -30,8 +30,8 @@ def test_gitlab_attribute_get(): assert o._value is None -def test_csv_list_attribute_input(): - o = types.CommaSeparatedListAttribute() +def test_array_attribute_input(): + o = types.ArrayAttribute() o.set_from_cli("foo,bar,baz") assert o.get() == ["foo", "bar", "baz"] @@ -39,8 +39,8 @@ def test_csv_list_attribute_input(): assert o.get() == ["foo"] -def test_csv_list_attribute_empty_input(): - o = types.CommaSeparatedListAttribute() +def test_array_attribute_empty_input(): + o = types.ArrayAttribute() o.set_from_cli("") assert o.get() == [] @@ -48,27 +48,45 @@ def test_csv_list_attribute_empty_input(): assert o.get() == [] -def test_csv_list_attribute_get_for_api_from_cli(): - o = types.CommaSeparatedListAttribute() +def test_array_attribute_get_for_api_from_cli(): + o = types.ArrayAttribute() o.set_from_cli("foo,bar,baz") assert o.get_for_api() == "foo,bar,baz" -def test_csv_list_attribute_get_for_api_from_list(): - o = types.CommaSeparatedListAttribute(["foo", "bar", "baz"]) +def test_array_attribute_get_for_api_from_list(): + o = types.ArrayAttribute(["foo", "bar", "baz"]) assert o.get_for_api() == "foo,bar,baz" -def test_csv_list_attribute_get_for_api_from_int_list(): - o = types.CommaSeparatedListAttribute([1, 9, 7]) +def test_array_attribute_get_for_api_from_int_list(): + o = types.ArrayAttribute([1, 9, 7]) assert o.get_for_api() == "1,9,7" -def test_csv_list_attribute_does_not_split_string(): - o = types.CommaSeparatedListAttribute("foo") +def test_array_attribute_does_not_split_string(): + o = types.ArrayAttribute("foo") assert o.get_for_api() == "foo" +# CommaSeparatedListAttribute tests +def test_csv_string_attribute_get_for_api_from_cli(): + o = types.CommaSeparatedListAttribute() + o.set_from_cli("foo,bar,baz") + assert o.get_for_api() == "foo,bar,baz" + + +def test_csv_string_attribute_get_for_api_from_list(): + o = types.CommaSeparatedListAttribute(["foo", "bar", "baz"]) + assert o.get_for_api() == "foo,bar,baz" + + +def test_csv_string_attribute_get_for_api_from_int_list(): + o = types.CommaSeparatedListAttribute([1, 9, 7]) + assert o.get_for_api() == "1,9,7" + + +# LowercaseStringAttribute tests def test_lowercase_string_attribute_get_for_api(): o = types.LowercaseStringAttribute("FOO") assert o.get_for_api() == "foo"