diff --git a/synapseclient/client.py b/synapseclient/client.py index 56a90ca6d..af25435f0 100644 --- a/synapseclient/client.py +++ b/synapseclient/client.py @@ -621,6 +621,73 @@ def invalidateAPIKey(self): if self._is_logged_in(): self.restDELETE("/secretKey", endpoint=self.authEndpoint) + @functools.lru_cache() + def get_user_profile_by_username( + self, + username: str = None, + sessionToken: str = None, + ) -> UserProfile: + """ + Get the details about a Synapse user. + Retrieves information on the current user if 'id' is omitted or is empty string. + :param username: The userName of a user + :param sessionToken: The session token to use to find the user profile + :returns: The user profile for the user of interest. + + Example:: + my_profile = syn.get_user_profile_by_username() + freds_profile = syn.get_user_profile_by_username('fredcommo') + """ + is_none = username is None + is_str = isinstance(username, str) + if not is_str and not is_none: + raise TypeError("username must be string or None") + if is_str: + principals = self._findPrincipals(username) + for principal in principals: + if principal.get("userName", None).lower() == username.lower(): + id = principal["ownerId"] + break + else: + raise ValueError(f"Can't find user '{username}'") + else: + id = "" + uri = f"/userProfile/{id}" + return UserProfile( + **self.restGET( + uri, headers={"sessionToken": sessionToken} if sessionToken else None + ) + ) + + @functools.lru_cache() + def get_user_profile_by_id( + self, + id: int = None, + sessionToken: str = None, + ) -> UserProfile: + """ + Get the details about a Synapse user. + Retrieves information on the current user if 'id' is omitted. + :param id: The ownerId of a user + :param sessionToken: The session token to use to find the user profile + :returns: The user profile for the user of interest. + + Example:: + my_profile = syn.get_user_profile_by_id() + freds_profile = syn.get_user_profile_by_id(1234567) + """ + if id: + if not isinstance(id, int): + raise TypeError("id must be an 'ownerId' integer") + else: + id = "" + uri = f"/userProfile/{id}" + return UserProfile( + **self.restGET( + uri, headers={"sessionToken": sessionToken} if sessionToken else None + ) + ) + @functools.lru_cache() def getUserProfile( self, diff --git a/tests/unit/synapseclient/unit_test_get_user_profile.py b/tests/unit/synapseclient/unit_test_get_user_profile.py new file mode 100644 index 000000000..f83d9681e --- /dev/null +++ b/tests/unit/synapseclient/unit_test_get_user_profile.py @@ -0,0 +1,100 @@ +import pytest +from unittest.mock import patch + + +test_user_profile = { + "ownerId": "1234567", + "etag": "test-etag", + "firstName": "Test", + "lastName": "User", + "emails": ["test.user@example.com"], + "openIds": [], + "userName": "test_user", + "displayName": "Test User", + "summary": "This is a test user", + "position": "Test Position", + "location": "Test Location", + "industry": "Test Industry", + "company": "Test Company", + "profilePicureFileHandleId": "test-file-handle", + "url": "https://example.com", + "notificationSettings": {"sendEmailNotifications": False}, + "createdOn": "2022-01-01T00:00:00.000Z", +} + + +class TestGetUserProfileByUserName: + principals = [ + { + "ownerId": "7654321", + "firstName": "test", + "lastName": "person", + "userName": "abc", + "isIndividual": True, + }, + { + "ownerId": "1234567", + "firstName": "test_2", + "lastName": "person_2", + "userName": "test_user", + "isIndividual": True, + }, + ] + + @pytest.fixture(autouse=True, scope="function") + def setup_method(self, syn): + self.syn = syn + self.syn.restGET = patch.object( + self.syn, "restGET", return_value=test_user_profile + ).start() + self.syn._findPrincipals = patch.object( + self.syn, "_findPrincipals", return_value=self.principals + ).start() + + def teardown_method(self): + self.syn.restGET.stop() + self.syn._findPrincipals.stop() + + def test_that_get_user_profile_returns_expected_with_no_id(self): + result = self.syn.get_user_profile_by_username() + self.syn.restGET.assert_called_once_with("/userProfile/", headers=None) + assert result == test_user_profile + + def test_that_get_user_profile_returns_expected_with_username(self): + result = self.syn.get_user_profile_by_username("test_user") + self.syn.restGET.assert_called_once_with("/userProfile/1234567", headers=None) + assert result == test_user_profile + + def test_that_get_user_profile_raises_value_error_when_user_does_not_exist(self): + with pytest.raises(ValueError, match="Can't find user *"): + self.syn.get_user_profile_by_username("not_a_user") + + def test_that_get_user_profile_raises_type_error_when_id_is_not_allowed_type(self): + with pytest.raises(TypeError, match="username must be string or None"): + self.syn.get_user_profile_by_username(1234567) + + +class TestGetUserProfileByID: + @pytest.fixture(autouse=True, scope="function") + def setup_method(self, syn): + self.syn = syn + self.syn.restGET = patch.object( + self.syn, "restGET", return_value=test_user_profile + ).start() + + def teardown_method(self): + self.syn.restGET.stop() + + def test_that_get_user_profile_by_id_returns_expected_when_id_is_defined(self): + result = self.syn.get_user_profile_by_id(1234567) + self.syn.restGET.assert_called_once_with("/userProfile/1234567", headers=None) + assert result == test_user_profile + + def test_that_get_user_profile_by_id_returns_expected_when_id_is_not_defined(self): + result = self.syn.get_user_profile_by_id() + self.syn.restGET.assert_called_once_with("/userProfile/", headers=None) + assert result == test_user_profile + + def test_that_get_user_profile_by_id_raises_type_error_when_id_is_not_int(self): + with pytest.raises(TypeError, match="id must be an 'ownerId' integer"): + self.syn.get_user_profile_by_id("1234567")