From 0c6d7fda7c44ebb7566253ec6f84b080d2eeb75d Mon Sep 17 00:00:00 2001 From: Val Brodsky Date: Mon, 9 Dec 2024 15:31:42 -0800 Subject: [PATCH 1/3] Rename UserGroupUpload to UserGroupV2 --- docs/labelbox/index.rst | 2 +- docs/labelbox/user-group-v2.rst | 6 ++++++ .../schema/{user_group_upload.py => user_group_v2.py} | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 docs/labelbox/user-group-v2.rst rename libs/labelbox/src/labelbox/schema/{user_group_upload.py => user_group_v2.py} (97%) diff --git a/docs/labelbox/index.rst b/docs/labelbox/index.rst index fd9941c1f..347abf6b4 100644 --- a/docs/labelbox/index.rst +++ b/docs/labelbox/index.rst @@ -52,5 +52,5 @@ Labelbox Python SDK Documentation task task-queue user - user-group-upload + user-group-v2 webhook diff --git a/docs/labelbox/user-group-v2.rst b/docs/labelbox/user-group-v2.rst new file mode 100644 index 000000000..68f1b4bcb --- /dev/null +++ b/docs/labelbox/user-group-v2.rst @@ -0,0 +1,6 @@ +User Group +=============================================================================================== + +.. automodule:: labelbox.schema.user_group_v2 + :members: + :show-inheritance: \ No newline at end of file diff --git a/libs/labelbox/src/labelbox/schema/user_group_upload.py b/libs/labelbox/src/labelbox/schema/user_group_v2.py similarity index 97% rename from libs/labelbox/src/labelbox/schema/user_group_upload.py rename to libs/labelbox/src/labelbox/schema/user_group_v2.py index 8d6d2bc25..41a3e4861 100644 --- a/libs/labelbox/src/labelbox/schema/user_group_upload.py +++ b/libs/labelbox/src/labelbox/schema/user_group_v2.py @@ -54,7 +54,7 @@ class UploadReport: lines: List[UploadReportLine] -class UserGroupUpload: +class UserGroupV2: """Upload members to a user group.""" def __init__(self, client: Client): @@ -80,7 +80,7 @@ def upload_members( For indicvidual email errors, the error message is available in the UploadReport. """ warnings.warn( - "The upload_members for UserGroupUpload is in beta. The method name and signature may change in the future.”", + "The upload_members for UserGroupV2 is in beta. The method name and signature may change in the future.”", ) if len(emails) == 0: From a6738d7956e4526a2fc230e8cece30647d43c808 Mon Sep 17 00:00:00 2001 From: Val Brodsky Date: Mon, 9 Dec 2024 16:00:54 -0800 Subject: [PATCH 2/3] Add export_members --- .../src/labelbox/schema/user_group_v2.py | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/libs/labelbox/src/labelbox/schema/user_group_v2.py b/libs/labelbox/src/labelbox/schema/user_group_v2.py index 41a3e4861..b82060b1c 100644 --- a/libs/labelbox/src/labelbox/schema/user_group_v2.py +++ b/libs/labelbox/src/labelbox/schema/user_group_v2.py @@ -54,6 +54,13 @@ class UploadReport: lines: List[UploadReportLine] +@dataclass +class Member: + """A member of a user group.""" + + email: str + + class UserGroupV2: """Upload members to a user group.""" @@ -109,7 +116,7 @@ def upload_members( "text/csv", ) } - query = """mutation ImportMembersToGroup( + query = """mutation ImportMembersToGroupPyPi( $roleId: ID! $file: Upload! $where: WhereUniqueIdInput! @@ -183,6 +190,46 @@ def upload_members( csv_report = file_data["importUsersAsCsvToGroup"]["csvReport"] return self._parse_csv_report(csv_report) + def export_members(self, group_id: str) -> Optional[List[Member]]: + warnings.warn( + "The upload_members for UserGroupV2 is in beta. The method name and signature may change in the future.”", + ) + + if not group_id: + raise ValueError("Group id is required") + + query = """query GetExportMembersAsCSVPyPi( + $id: ID! + ) { + userGroupV2(where: { id: $id }) { + id + membersAsCSV + } + } + """ + params = { + "id": group_id, + } + + result = self.client.execute(query, params) + if result["userGroupV2"] is None: + raise ResourceNotFoundError(message="The user group is not found.") + data = result["userGroupV2"] + + # Parse CSV string into list of members + csv_lines = data["membersAsCSV"].strip().split("\n") + members_list = [] + + # Skip header row + for email in csv_lines[1:]: + members_list.append( + Member( + email=email.strip(), + ) + ) + + return members_list + def _get_role_id(self, role_name: str) -> Optional[str]: role_id = None query = """query GetAvailableUserRolesPyPi { From 9a6505919f4165e7a63501c424272a3ede174313 Mon Sep 17 00:00:00 2001 From: Val Brodsky Date: Mon, 9 Dec 2024 20:56:05 -0800 Subject: [PATCH 3/3] Add unit tests --- .../src/labelbox/schema/user_group_v2.py | 19 +++---- .../tests/unit/schema/test_user_group_v2.py | 50 +++++++++++++++++++ 2 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 libs/labelbox/tests/unit/schema/test_user_group_v2.py diff --git a/libs/labelbox/src/labelbox/schema/user_group_v2.py b/libs/labelbox/src/labelbox/schema/user_group_v2.py index b82060b1c..a734eb397 100644 --- a/libs/labelbox/src/labelbox/schema/user_group_v2.py +++ b/libs/labelbox/src/labelbox/schema/user_group_v2.py @@ -192,7 +192,7 @@ def upload_members( def export_members(self, group_id: str) -> Optional[List[Member]]: warnings.warn( - "The upload_members for UserGroupV2 is in beta. The method name and signature may change in the future.”", + "The export_members for UserGroupV2 is in beta. The method name and signature may change in the future.", ) if not group_id: @@ -216,17 +216,18 @@ def export_members(self, group_id: str) -> Optional[List[Member]]: raise ResourceNotFoundError(message="The user group is not found.") data = result["userGroupV2"] - # Parse CSV string into list of members - csv_lines = data["membersAsCSV"].strip().split("\n") - members_list = [] + return self._parse_members_csv(data["membersAsCSV"]) + + def _parse_members_csv(self, csv_data: str) -> List[Member]: + csv_lines = csv_data.strip().split("\n") + if not csv_lines: + return [] + members_list = [] # Skip header row for email in csv_lines[1:]: - members_list.append( - Member( - email=email.strip(), - ) - ) + if email.strip(): # Skip empty lines + members_list.append(Member(email=email.strip())) return members_list diff --git a/libs/labelbox/tests/unit/schema/test_user_group_v2.py b/libs/labelbox/tests/unit/schema/test_user_group_v2.py new file mode 100644 index 000000000..4c79d6d52 --- /dev/null +++ b/libs/labelbox/tests/unit/schema/test_user_group_v2.py @@ -0,0 +1,50 @@ +from unittest.mock import MagicMock + +import pytest + +from labelbox.schema.user_group_v2 import Member, UserGroupV2 + + +@pytest.fixture +def client(): + return MagicMock() + + +def test_parse_members_csv_empty(client): + group = UserGroupV2(client) + assert group._parse_members_csv("") == [] + assert group._parse_members_csv("\n") == [] + + +def test_parse_members_csv_header_only(client): + group = UserGroupV2(client) + assert group._parse_members_csv("email\n") == [] + + +def test_parse_members_csv_single_member(client): + group = UserGroupV2(client) + result = group._parse_members_csv("email\ntest@example.com") + assert len(result) == 1 + assert isinstance(result[0], Member) + assert result[0].email == "test@example.com" + + +def test_parse_members_csv_multiple_members(client): + group = UserGroupV2(client) + csv_data = "email\ntest1@example.com\ntest2@example.com\ntest3@example.com" + result = group._parse_members_csv(csv_data) + assert len(result) == 3 + assert [m.email for m in result] == [ + "test1@example.com", + "test2@example.com", + "test3@example.com", + ] + + +def test_parse_members_csv_handles_whitespace(client): + group = UserGroupV2(client) + csv_data = "email\n test@example.com \n\nother@example.com\n" + result = group._parse_members_csv(csv_data) + assert len(result) == 2 + assert result[0].email == "test@example.com" + assert result[1].email == "other@example.com"