diff --git a/src/posit/connect/permissions.py b/src/posit/connect/permissions.py index a20b6f3f..37f25ad2 100644 --- a/src/posit/connect/permissions.py +++ b/src/posit/connect/permissions.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, overload +from typing import TYPE_CHECKING, List, Optional, overload from requests.sessions import Session as Session @@ -67,36 +67,101 @@ def count(self) -> int: return len(self.find()) @overload - def create(self, *, principal_guid: str, principal_type: str, role: str) -> Permission: + def create(self, /, *, principal_guid: str, principal_type: str, role: str) -> Permission: ... + + @overload + def create(self, principal: User | Group, /, *, role: str) -> Permission: ... + + def create( + self, + principal: Optional[User | Group] = None, + /, + **kwargs, + ) -> Permission: """Create a permission. Parameters ---------- + principal : User | Group + The principal user or group to add. + role : str + The principal role. Currently only `"viewer"` and `"owner"` are supported. principal_guid : str + User guid or Group guid. principal_type : str + The principal type. Either `"user"` or `"group"`. role : str + The principal role. Currently only `"viewer"` and `"owner"` are supported Returns ------- Permission - """ + The created permission. - @overload - def create(self, **kwargs) -> Permission: - """Create a permission. + Examples + -------- + ```python + from posit import connect - Returns - ------- - Permission - """ + client = connect.Client() + content_item = client.content.get(content_guid) - def create(self, **kwargs) -> Permission: - """Create a permission. + # New permission role + role = "viewer" # Or "owner" + + # Example groups and users + groups = client.groups.find(prefix="GROUP_NAME_PREFIX_HERE") + group = groups[0] + user = client.users.get("USER_GUID_HERE") + users_and_groups = [user, *groups] + + # Add a group permission + content_item.permissions.create(group, role=role) + # Add a user permission + content_item.permissions.create(user, role=role) + + # Add many group and user permissions with the same role + for principal in users_and_groups: + content_item.permissions.create(principal, role=role) + + # Add a group permission manually + content_item.permissions.create( + principal_guid=group["guid"], + principal_type="group", + role=role, + ) + # Add a user permission manually + content_item.permissions.create( + principal_guid=user["guid"], + principal_type="user", + role=role, + ) - Returns - ------- - Permission + # Confirm new permissions + content_item.permissions.find() + ``` """ + if principal is not None: + # Avoid circular imports + from .groups import Group + from .users import User + + if isinstance(principal, User): + principal_type = "user" + elif isinstance(principal, Group): + principal_type = "group" + else: + raise TypeError(f"Invalid argument type: {type(principal).__name__}") + + if "principal_guid" in kwargs: + raise ValueError("'principal_guid' can not be defined with `principal` present.") + if "principal_type" in kwargs: + raise ValueError("'principal_guid' can not be defined with `principal` present.") + + # Set the corresponding kwargs + kwargs["principal_guid"] = principal["guid"] + kwargs["principal_type"] = principal_type + path = f"v1/content/{self.content_guid}/permissions" url = self.params.url + path response = self.params.session.post(url, json=kwargs) diff --git a/tests/posit/connect/test_permissions.py b/tests/posit/connect/test_permissions.py index dc0fa470..9260e575 100644 --- a/tests/posit/connect/test_permissions.py +++ b/tests/posit/connect/test_permissions.py @@ -195,6 +195,93 @@ def test(self): # assert assert permission == fake_permission + def test_assertions(self): + # setup + principal_guid = "principal_guid" + content_guid = "content_guid" + client = Client("https://connect.example/__api__", "12345") + permissions = Permissions(client.resource_params, content_guid=content_guid) + user = User(client._ctx, guid=principal_guid) + group = User(client._ctx, guid=principal_guid) + + # behavior + with pytest.raises(TypeError, match="str"): + permissions.create( # pyright: ignore[reportCallIssue] + "not a user or group", + ) + with pytest.raises(ValueError): + permissions.create( # pyright: ignore[reportCallIssue] + user, + principal_guid=principal_guid, + ) + with pytest.raises(ValueError): + permissions.create( # pyright: ignore[reportCallIssue] + user, + principal_type="viewer", + ) + + @responses.activate + def test_user_group(self): + # data + content_guid = "CONTENT_GUID" + user_guid = "USER_GUID" + group_guid = "GROUP_GUID" + + fake_user = { + "principal_guid": user_guid, + "principal_type": "user", + "role": "viewer", + } + fake_group = { + "principal_guid": group_guid, + "principal_type": "group", + "role": "viewer", + } + res_user = responses.post( + f"https://connect.example/__api__/v1/content/{content_guid}/permissions", + json=fake_user, + match=[matchers.json_params_matcher(fake_user)], + ) + res_group = responses.post( + f"https://connect.example/__api__/v1/content/{content_guid}/permissions", + json=fake_group, + match=[matchers.json_params_matcher(fake_group)], + ) + + # setup + client = Client("https://connect.example/__api__", "12345") + permissions = Permissions(client.resource_params, content_guid=content_guid) + user = User(client._ctx, guid=user_guid) + group = Group(client._ctx, guid=group_guid) + + # invoke + user_perm = permissions.create(user, role="viewer") + group_perm = permissions.create(group, role="viewer") + + created_permissions = [user_perm, group_perm] + + # assert + assert res_user.call_count == 1 + assert res_group.call_count == 1 + + for permission in created_permissions: + assert isinstance(permission, Permission) + + assert created_permissions == [ + Permission( + client.resource_params, + principal_guid=user_guid, + principal_type="user", + role="viewer", + ), + Permission( + client.resource_params, + principal_guid=group_guid, + principal_type="group", + role="viewer", + ), + ] + class TestPermissionsFind: @responses.activate