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

add resource Visibility enum and operations #196

Merged
merged 4 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ For pre-1.0 releases, see [0.0.35 Changelog](https://github.com/noteable-io/orig

### [1.1.3] - 2023-10-23
### Added
- Programmatically share access to Spaces, Projects, and Notebooks/Files by email and access level. E.g. `await api_client.share_file(file_id, email, 'viewer')`
- Programmatically share access to Spaces, Projects, and Notebooks/Files by email and access level. E.g. `await api_client.share_file(file_id, email, 'viewer')`, as well as change Space/Project/File visibility (e.g. `private`, `open`, `public`)

### Changed
- Removed `RuntimeError` in RTUClient catastrophic failure, top level applications (e.g. PA, Origamist) should define that behavior
Expand Down
105 changes: 105 additions & 0 deletions origami/clients/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@ def from_str(cls, s: str):
raise ValueError(f"Invalid access level {s}")


class Visibility(enum.Enum):
"""Visibility levels associated with a specific Resource.

Private = only invited users can access
Open = any member can access
Public = anyone can access
"""

private = "private"
open = "open"
public = "public"

@classmethod
def from_str(cls, s: str):
for vis in cls:
if vis.name == s:
return vis
raise ValueError(f"Invalid visibility {s}")


class Resource(enum.Enum):
spaces = "spaces"
projects = "projects"
Expand Down Expand Up @@ -128,6 +148,43 @@ async def unshare_resource(self, resource: Resource, resource_id: uuid.UUID, ema
unshare_resp.raise_for_status()
return len(users)

async def change_resource_visibility(
self,
resource: Resource,
resource_id: uuid.UUID,
visibility: Visibility,
visibility_default_access_level: Optional[AccessLevel] = None,
):
"""
Change overall visibility of a Resource.

visibility_default_access_level is only required when visibility is not private.
"""
if isinstance(visibility, str):
visibility = Visibility.from_str(visibility)

if visibility is not Visibility.private and visibility_default_access_level is None:
raise ValueError(
"visibility_default_access_level must be set when visibility is not private"
)

patch_body = {"visibility": visibility.value}
if isinstance(visibility_default_access_level, str):
visibility_default_access_level = AccessLevel.from_str(
visibility_default_access_level
).value

# always set this as either None or a valid (string) value
patch_body["visibility_default_access_level"] = visibility_default_access_level

endpoint = f"/{resource.value}/{resource_id}"
resp = await self.client.patch(
endpoint,
json=patch_body,
)
resp.raise_for_status()
return resp.json()

# Spaces are collections of Projects. Some "scoped" resources such as Secrets and Datasources
# can also be attached to a Space and made available to all users of that Space.
async def create_space(self, name: str, description: Optional[str] = None) -> Space:
Expand Down Expand Up @@ -176,6 +233,22 @@ async def unshare_space(self, space_id: uuid.UUID, email: str) -> int:
"""
return await self.unshare_resource(Resource.spaces, space_id, email)

async def change_space_visibility(
self,
space_id: uuid.UUID,
visibility: Visibility,
visibility_default_access_level: Optional[AccessLevel] = None,
) -> Visibility:
"""
Change overall visibility of a Space
"""
return await self.change_resource_visibility(
Resource.spaces,
space_id,
visibility,
visibility_default_access_level,
)

# Projects are collections of Files, including Notebooks. When a Kernel is launched for a
# Notebook, all Files in the Project are volume mounted into the Kernel container at startup.
async def create_project(
Expand Down Expand Up @@ -228,6 +301,22 @@ async def unshare_project(self, project_id: uuid.UUID, email: str) -> int:
"""
return await self.unshare_resource(Resource.projects, project_id, email)

async def change_project_visibility(
self,
project_id: uuid.UUID,
visibility: Visibility,
visibility_default_access_level: Optional[AccessLevel] = None,
) -> Visibility:
"""
Change overall visibility of a Project
"""
return await self.change_resource_visibility(
Resource.projects,
project_id,
visibility,
visibility_default_access_level,
)

async def list_project_files(self, project_id: uuid.UUID) -> List[File]:
"""List all Files in a Project. Files do not have presigned download urls included here."""
self.add_tags_and_contextvars(project_id=str(project_id))
Expand Down Expand Up @@ -369,6 +458,22 @@ async def unshare_file(self, file_id: uuid.UUID, email: str) -> int:
"""
return await self.unshare_resource(Resource.files, file_id, email)

async def change_file_visibility(
self,
file_id: uuid.UUID,
visibility: Visibility,
visibility_default_access_level: Optional[AccessLevel] = None,
) -> Visibility:
"""
Change overall visibility of a Notebook or File
"""
return await self.change_resource_visibility(
Resource.files,
file_id,
visibility,
visibility_default_access_level,
)

async def get_datasources_for_notebook(self, file_id: uuid.UUID) -> List[DataSource]:
"""Return a list of Datasources that can be used in SQL cells within a Notebook"""
self.add_tags_and_contextvars(file_id=str(file_id))
Expand Down