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 82% rename from libs/labelbox/src/labelbox/schema/user_group_upload.py rename to libs/labelbox/src/labelbox/schema/user_group_v2.py index 8d6d2bc25..a734eb397 100644 --- a/libs/labelbox/src/labelbox/schema/user_group_upload.py +++ b/libs/labelbox/src/labelbox/schema/user_group_v2.py @@ -54,7 +54,14 @@ class UploadReport: lines: List[UploadReportLine] -class UserGroupUpload: +@dataclass +class Member: + """A member of a user group.""" + + email: str + + +class UserGroupV2: """Upload members to a user group.""" def __init__(self, client: Client): @@ -80,7 +87,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: @@ -109,7 +116,7 @@ def upload_members( "text/csv", ) } - query = """mutation ImportMembersToGroup( + query = """mutation ImportMembersToGroupPyPi( $roleId: ID! $file: Upload! $where: WhereUniqueIdInput! @@ -183,6 +190,47 @@ 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 export_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"] + + 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:]: + if email.strip(): # Skip empty lines + 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 { 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"