Skip to content

Commit 5a5f989

Browse files
authored
Merge pull request #1235 from Sage-Bionetworks/synpy-1633
[SYNPY-1633] Add membership_status to Team model and Deprecate Old Method
2 parents a5bcba7 + 63b1e43 commit 5a5f989

File tree

9 files changed

+347
-4
lines changed

9 files changed

+347
-4
lines changed

docs/reference/experimental/async/team.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ at your own risk.
1717
- members_async
1818
- invite_async
1919
- open_invitations_async
20+
- get_user_membership_status_async
2021
---
2122

2223
::: synapseclient.models.TeamMember
24+
::: synapseclient.models.TeamMembershipStatus

docs/reference/experimental/sync/team.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ at your own risk.
2828
- members
2929
- invite
3030
- open_invitations
31+
- get_user_membership_status
3132
---
3233

3334
::: synapseclient.models.TeamMember
35+
::: synapseclient.models.TeamMembershipStatus

synapseclient/client.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6020,7 +6020,12 @@ def get_team_open_invitations(
60206020
open_requests = self._GET_paginated(request)
60216021
return open_requests
60226022

6023-
# TODO: Deprecate method in https://sagebionetworks.jira.com/browse/SYNPY-1633
6023+
@deprecated(
6024+
version="4.9.0",
6025+
reason="To be removed in 5.0.0. "
6026+
"Use the `get_user_membership_status` method on the `Team` class from `synapseclient.models` instead. "
6027+
"Check the docstring for the replacement function example.",
6028+
)
60246029
def get_membership_status(self, userid, team):
60256030
"""Retrieve a user's Team Membership Status bundle.
60266031
<https://rest-docs.synapse.org/rest/GET/team/id/member/principalId/membershipStatus.html>
@@ -6031,6 +6036,41 @@ def get_membership_status(self, userid, team):
60316036
60326037
Returns:
60336038
dict of TeamMembershipStatus
6039+
6040+
Example: Using this function (DEPRECATED)
6041+
&nbsp;
6042+
Getting a user's membership status for a team
6043+
6044+
```python
6045+
from synapseclient import Synapse
6046+
6047+
# Login to Synapse
6048+
syn = Synapse()
6049+
syn.login()
6050+
6051+
status = syn.get_membership_status(userid="12345", team="67890")
6052+
print(status)
6053+
```
6054+
6055+
6056+
Example: Migration to new method
6057+
&nbsp;
6058+
6059+
```python
6060+
from synapseclient import Synapse
6061+
from synapseclient.models import Team
6062+
6063+
# Login to Synapse
6064+
syn = Synapse()
6065+
syn.login()
6066+
6067+
# Use synchronous version (recommended for most use cases)
6068+
team = Team.from_id(id="67890")
6069+
status = team.get_user_membership_status(
6070+
user_id="12345",
6071+
)
6072+
print(status)
6073+
```
60346074
"""
60356075
teamid = id_of(team)
60366076
request = "/team/{team}/member/{user}/membershipStatus".format(

synapseclient/models/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
TableUpdateTransaction,
3535
UploadToTableRequest,
3636
)
37-
from synapseclient.models.team import Team, TeamMember
37+
from synapseclient.models.team import Team, TeamMember, TeamMembershipStatus
3838
from synapseclient.models.user import UserGroupHeader, UserPreference, UserProfile
3939
from synapseclient.models.virtualtable import VirtualTable
4040

@@ -50,6 +50,7 @@
5050
"Annotations",
5151
"Team",
5252
"TeamMember",
53+
"TeamMembershipStatus",
5354
"UserProfile",
5455
"UserPreference",
5556
"UserGroupHeader",

synapseclient/models/protocols/team_protocol.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from synapseclient import Synapse
77

88
if TYPE_CHECKING:
9-
from synapseclient.models import Team, TeamMember
9+
from synapseclient.models import Team, TeamMember, TeamMembershipStatus
1010

1111

1212
class TeamSynchronousProtocol(Protocol):
@@ -159,3 +159,53 @@ def open_invitations(
159159
List[dict]: A list of invitations.
160160
"""
161161
return list({})
162+
163+
def get_user_membership_status(
164+
self,
165+
user_id: str,
166+
*,
167+
synapse_client: Optional[Synapse] = None,
168+
) -> "TeamMembershipStatus":
169+
"""Retrieve a user's Team Membership Status bundle.
170+
171+
<https://rest-docs.synapse.org/rest/GET/team/id/member/principalId/membershipStatus.html>
172+
173+
Arguments:
174+
user_id: The ID of the user whose membership status is being queried.
175+
synapse_client: If not passed in and caching was not disabled by
176+
`Synapse.allow_client_caching(False)` this will use the last created
177+
instance from the Synapse class constructor.
178+
179+
Returns:
180+
TeamMembershipStatus object
181+
182+
Example:
183+
Check if a user is a member of a team
184+
This example shows how to check a user's membership status in a team.
185+
&nbsp;
186+
187+
```python
188+
from synapseclient import Synapse
189+
from synapseclient.models import Team
190+
191+
syn = Synapse()
192+
syn.login()
193+
194+
# Get a team by ID
195+
team = Team.from_id(123456)
196+
197+
# Check membership status for a specific user
198+
user_id = "3350396" # Replace with actual user ID
199+
status = team.get_user_membership_status(user_id)
200+
201+
print(f"User ID: {status.user_id}")
202+
print(f"Is member: {status.is_member}")
203+
print(f"Can join: {status.can_join}")
204+
print(f"Has open invitation: {status.has_open_invitation}")
205+
print(f"Has open request: {status.has_open_request}")
206+
print(f"Membership approval required: {status.membership_approval_required}")
207+
```
208+
"""
209+
from synapseclient.models.team import TeamMembershipStatus
210+
211+
return TeamMembershipStatus().fill_from_dict({})

synapseclient/models/team.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from synapseclient.api import (
88
create_team,
99
delete_team,
10+
get_membership_status,
1011
get_team,
1112
get_team_members,
1213
get_team_open_invitations,
@@ -56,6 +57,80 @@ def fill_from_dict(
5657
return self
5758

5859

60+
@dataclass
61+
class TeamMembershipStatus:
62+
"""
63+
Contains information about a user's membership status in a Team.
64+
Represents a [Synapse TeamMembershipStatus](<https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/TeamMembershipStatus.html>).
65+
User definable fields are:
66+
67+
Attributes:
68+
team_id: The id of the Team.
69+
user_id: The principal id of the user.
70+
is_member: true if and only if the user is a member of the team
71+
has_open_invitation: true if and only if the user has an open invitation to join the team
72+
has_open_request: true if and only if the user has an open request to join the team
73+
can_join: true if and only if the user requesting this status information can join the user to the team
74+
membership_approval_required: true if and only if team admin approval is required for the user to join the team
75+
has_unmet_access_requirement: true if and only if there is at least one unmet access requirement for the user on the team
76+
can_send_email: true if and only if the user can send an email to the team
77+
"""
78+
79+
team_id: Optional[str] = None
80+
"""The ID of the team"""
81+
82+
user_id: Optional[str] = None
83+
"""The ID of the user"""
84+
85+
is_member: Optional[bool] = None
86+
"""Whether the user is a member of the team"""
87+
88+
has_open_invitation: Optional[bool] = None
89+
"""Whether the user has an open invitation to join the team"""
90+
91+
has_open_request: Optional[bool] = None
92+
"""Whether the user has an open request to join the team"""
93+
94+
can_join: Optional[bool] = None
95+
"""Whether the user can join the team"""
96+
97+
membership_approval_required: Optional[bool] = None
98+
"""Whether membership approval is required for the team"""
99+
100+
has_unmet_access_requirement: Optional[bool] = None
101+
"""Whether the user has unmet access requirements"""
102+
103+
can_send_email: Optional[bool] = None
104+
"""Whether the user can send email to the team"""
105+
106+
def fill_from_dict(
107+
self, membership_status_dict: Dict[str, Union[str, bool]]
108+
) -> "TeamMembershipStatus":
109+
"""
110+
Converts a response from the REST API into this dataclass.
111+
112+
Arguments:
113+
membership_status_dict: The response from the REST API.
114+
115+
Returns:
116+
The TeamMembershipStatus object.
117+
"""
118+
self.team_id = membership_status_dict.get("teamId", None)
119+
self.user_id = membership_status_dict.get("userId", None)
120+
self.is_member = membership_status_dict.get("isMember", None)
121+
self.has_open_invitation = membership_status_dict.get("hasOpenInvitation", None)
122+
self.has_open_request = membership_status_dict.get("hasOpenRequest", None)
123+
self.can_join = membership_status_dict.get("canJoin", None)
124+
self.membership_approval_required = membership_status_dict.get(
125+
"membershipApprovalRequired", None
126+
)
127+
self.has_unmet_access_requirement = membership_status_dict.get(
128+
"hasUnmetAccessRequirement", None
129+
)
130+
self.can_send_email = membership_status_dict.get("canSendEmail", None)
131+
return self
132+
133+
59134
@dataclass
60135
@async_to_sync
61136
class Team(TeamSynchronousProtocol):
@@ -356,3 +431,57 @@ async def open_invitations_async(
356431
team=self.id, synapse_client=synapse_client
357432
)
358433
return list(invitations)
434+
435+
async def get_user_membership_status_async(
436+
self,
437+
user_id: str,
438+
*,
439+
synapse_client: Optional[Synapse] = None,
440+
) -> TeamMembershipStatus:
441+
"""Retrieve a user's Team Membership Status bundle for this team.
442+
443+
Arguments:
444+
user_id: Synapse user ID
445+
synapse_client: If not passed in and caching was not disabled by
446+
`Synapse.allow_client_caching(False)` this will use the last created
447+
instance from the Synapse class constructor.
448+
449+
Returns:
450+
TeamMembershipStatus object
451+
452+
Example: Check if a user is a member of a team
453+
This example shows how to check a user's membership status in a team.
454+
455+
```python
456+
import asyncio
457+
from synapseclient import Synapse
458+
from synapseclient.models import Team
459+
460+
syn = Synapse()
461+
syn.login()
462+
463+
async def check_membership():
464+
# Get a team by ID
465+
team = await Team.from_id_async(123456)
466+
467+
# Check membership status for a specific user
468+
user_id = "3350396" # Replace with actual user ID
469+
status = await team.get_user_membership_status_async(user_id)
470+
471+
print(f"User ID: {status.user_id}")
472+
print(f"Is member: {status.is_member}")
473+
print(f"Can join: {status.can_join}")
474+
print(f"Has open invitation: {status.has_open_invitation}")
475+
print(f"Has open request: {status.has_open_request}")
476+
print(f"Membership approval required: {status.membership_approval_required}")
477+
478+
asyncio.run(check_membership())
479+
```
480+
"""
481+
from synapseclient import Synapse
482+
483+
client = Synapse.get_client(synapse_client=synapse_client)
484+
status = await get_membership_status(
485+
user_id=user_id, team=self.id, synapse_client=client
486+
)
487+
return TeamMembershipStatus().fill_from_dict(status)

tests/integration/synapseclient/models/async/test_team_async.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,47 @@ async def test_team_membership_and_invitations(self) -> None:
143143

144144
# Clean up
145145
await test_team.delete_async()
146+
147+
async def test_get_user_membership_status(self) -> None:
148+
"""Test getting user membership status for a team"""
149+
# WHEN I create the team on Synapse
150+
test_team = await self.team.create_async()
151+
152+
try:
153+
# AND I get the membership status for the creator (who should be a member)
154+
creator_status = await test_team.get_user_membership_status_async(
155+
user_id=self.syn.getUserProfile().ownerId
156+
)
157+
158+
# THEN the creator should have membership status indicating they are a member
159+
assert creator_status.is_member is True
160+
assert creator_status.team_id == str(test_team.id)
161+
assert creator_status.has_open_invitation is False
162+
assert creator_status.has_open_request is False
163+
assert creator_status.can_join is True
164+
assert creator_status.membership_approval_required is False
165+
assert creator_status.has_unmet_access_requirement is False
166+
assert creator_status.can_send_email is True
167+
168+
# WHEN I invite a test user to the team
169+
invite = await test_team.invite_async(
170+
user=self.TEST_USER,
171+
message=self.TEST_MESSAGE,
172+
)
173+
174+
# Check the invited user's status
175+
invited_status = await test_team.get_user_membership_status_async(
176+
user_id=self.syn.getUserProfile(self.TEST_USER).ownerId
177+
)
178+
179+
# THEN the invited user should show they have an open invitation
180+
assert invited_status is not None
181+
assert invited_status.team_id == str(test_team.id)
182+
assert invited_status.has_open_invitation is True
183+
assert invited_status.membership_approval_required is True
184+
assert invited_status.can_send_email is True
185+
assert invited_status.is_member is False
186+
187+
finally:
188+
# Clean up
189+
await test_team.delete_async()

tests/integration/synapseclient/models/synchronous/test_team.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,46 @@ async def test_team_membership_and_invitations(self) -> None:
143143

144144
# Clean up
145145
test_team.delete()
146+
147+
async def test_team_membership_status(self) -> None:
148+
"""Test getting user membership status in a team"""
149+
# GIVEN a team object
150+
151+
# WHEN I create the team on Synapse
152+
test_team = self.team.create()
153+
154+
# AND I get the membership status for the creator (who should be a member)
155+
creator_status = test_team.get_user_membership_status(
156+
user_id=self.syn.getUserProfile().ownerId
157+
)
158+
159+
# THEN the creator should have membership status indicating they are a member
160+
assert creator_status.is_member is True
161+
assert creator_status.team_id == str(test_team.id)
162+
assert creator_status.has_open_invitation is False
163+
assert creator_status.has_open_request is False
164+
assert creator_status.can_join is True
165+
assert creator_status.membership_approval_required is False
166+
assert creator_status.has_unmet_access_requirement is False
167+
assert creator_status.can_send_email is True
168+
169+
# WHEN I invite a test user to the team
170+
invite = await test_team.invite_async(
171+
user=self.TEST_USER,
172+
message=self.TEST_MESSAGE,
173+
)
174+
# Check the invited user's status
175+
invited_status = await test_team.get_user_membership_status_async(
176+
user_id=self.syn.getUserProfile(self.TEST_USER).ownerId
177+
)
178+
179+
# THEN the invited user should show they have an open invitation
180+
assert invited_status is not None
181+
assert invited_status.team_id == str(test_team.id)
182+
assert invited_status.has_open_invitation is True
183+
assert invited_status.membership_approval_required is True
184+
assert invited_status.can_send_email is True
185+
assert invited_status.is_member is False
186+
187+
# Clean up
188+
test_team.delete()

0 commit comments

Comments
 (0)