Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Permission.create(principal:User | Group) support #343

Merged
merged 8 commits into from
Dec 5, 2024
95 changes: 80 additions & 15 deletions src/posit/connect/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
87 changes: 87 additions & 0 deletions tests/posit/connect/test_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading