Repository: python-gitlab/python-gitlab

Issue:
Add user membership
https://docs.gitlab.com/ee/api/users.html#user-memberships-admin-only


Discussion:

Resolving patch:
diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py
--- a/gitlab/v4/objects.py
+++ b/gitlab/v4/objects.py
@@ -229,6 +229,17 @@ class UserImpersonationTokenManager(NoUpdateMixin, RESTManager):
     _list_filters = ("state",)
 
 
+class UserMembership(RESTObject):
+    _id_attr = "source_id"
+
+
+class UserMembershipManager(RetrieveMixin, RESTManager):
+    _path = "/users/%(user_id)s/memberships"
+    _obj_cls = UserMembership
+    _from_parent_attrs = {"user_id": "id"}
+    _list_filters = ("type",)
+
+
 class UserProject(RESTObject):
     pass
 
@@ -311,6 +322,7 @@ class User(SaveMixin, ObjectDeleteMixin, RESTObject):
         ("gpgkeys", "UserGPGKeyManager"),
         ("impersonationtokens", "UserImpersonationTokenManager"),
         ("keys", "UserKeyManager"),
+        ("memberships", "UserMembershipManager"),
         ("projects", "UserProjectManager"),
         ("status", "UserStatusManager"),
     )


Test patch:
diff --git a/gitlab/tests/test_gitlab.py b/gitlab/tests/test_gitlab.py
--- a/gitlab/tests/test_gitlab.py
+++ b/gitlab/tests/test_gitlab.py
@@ -658,6 +658,38 @@ def test_users(self):
             self.assertEqual(user.name, "name")
             self.assertEqual(user.id, 1)
 
+    def test_user_memberships(self):
+        @urlmatch(
+            scheme="http",
+            netloc="localhost",
+            path="/api/v4/users/1/memberships",
+            method="get",
+        )
+        def resp_get_user_memberships(url, request):
+            headers = {"content-type": "application/json"}
+            content = """[
+                          {
+                            "source_id": 1,
+                            "source_name": "Project one",
+                            "source_type": "Project",
+                            "access_level": "20"
+                          },
+                          {
+                            "source_id": 3,
+                            "source_name": "Group three",
+                            "source_type": "Namespace",
+                            "access_level": "20"
+                          }
+                        ]"""
+            content = content.encode("utf-8")
+            return response(200, content, headers, None, 5, request)
+
+        with HTTMock(resp_get_user_memberships):
+            user = self.gl.users.get(1, lazy=True)
+            memberships = user.memberships.list()
+            self.assertIsInstance(memberships[0], UserMembership)
+            self.assertEqual(memberships[0].source_type, "Project")
+
     def test_user_status(self):
         @urlmatch(
             scheme="http",
diff --git a/tools/cli_test_v4.sh b/tools/cli_test_v4.sh
--- a/tools/cli_test_v4.sh
+++ b/tools/cli_test_v4.sh
@@ -61,6 +61,10 @@ testcase "adding member to a project" '
         --user-id "$USER_ID" --access-level 40 >/dev/null 2>&1
 '
 
+testcase "listing user memberships" '
+    GITLAB user-membership list --user-id "$USER_ID" >/dev/null 2>&1
+'
+
 testcase "file creation" '
     GITLAB project-file create --project-id "$PROJECT_ID" \
         --file-path README --branch master --content "CONTENT" \
diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py
--- a/tools/python_test_v4.py
+++ b/tools/python_test_v4.py
@@ -266,6 +266,35 @@
 
 group2.members.create({"access_level": gitlab.const.OWNER_ACCESS, "user_id": user2.id})
 
+# User memberships (admin only)
+memberships1 = user1.memberships.list()
+assert len(memberships1) == 1
+
+memberships2 = user2.memberships.list()
+assert len(memberships2) == 2
+
+membership = memberships1[0]
+assert membership.source_type == "Namespace"
+assert membership.access_level == gitlab.const.OWNER_ACCESS
+
+project_memberships = user1.memberships.list(type="Project")
+assert len(project_memberships) == 0
+
+group_memberships = user1.memberships.list(type="Namespace")
+assert len(group_memberships) == 1
+
+try:
+    membership = user1.memberships.list(type="Invalid")
+except gitlab.GitlabListError as e:
+    error_message = e.error_message
+assert error_message == "type does not have a valid value"
+
+try:
+    user1.memberships.list(sudo=user1.name)
+except gitlab.GitlabListError as e:
+    error_message = e.error_message
+assert error_message == "403 Forbidden"
+
 # Administrator belongs to the groups
 assert len(group1.members.list()) == 3
 assert len(group2.members.list()) == 2