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 API functions for team roles and user interactions. #1196

Merged
merged 8 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions webknossos/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ For upgrade instructions, please check the respective _Breaking Changes_ section
### Breaking Changes

### Added
- Webknossos API functions were added: `Team.get_list()`, `Team.add("new_name")`, `User.assign_team_roles("teamName", isTeamManager: True)` and `RemoteDataset.explore_and_add_remote()` are available now. [#1196](https://github.com/scalableminds/webknossos-libs/pull/1196)

### Changed

Expand Down
14 changes: 14 additions & 0 deletions webknossos/examples/explore_and_add_remote.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import webknossos as wk


def main() -> None:
# Explore a zarr dataset with webknossos by adding it as a remote dataset
wk.RemoteDataset.explore_and_add_remote(
"https://data-humerus.webknossos.org/data/zarr/b2275d664e4c2a96/HuaLab-CBA_Ca-mouse-unexposed-M1/color",
"Ca-mouse-unexposed-M1",
"/Datasets",
)


if __name__ == "__main__":
main()
31 changes: 31 additions & 0 deletions webknossos/examples/teams_and_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import webknossos as wk


def main() -> None:
# Get the current user
current_user = wk.User.get_current_user()
print(
f"You are currently logged in as: {current_user.first_name} {current_user.last_name} ({current_user.email})."
)

# Get all users managed by the current user
all_my_users = wk.User.get_all_managed_users()
print("Managed users:")
for user in all_my_users:
print(f"\t{user.first_name} {user.last_name} ({user.email})")

# Get teams of current user
all_my_teams = wk.Team.get_list()
print("Teams:")
for team in all_my_teams:
print(f"\t{team.name} ({team.organization_id})")

# Add a new team
wk.Team.add("My new team")

# Set current user as team manager
current_user.assign_team_roles("My new team", is_team_manager=True)


if __name__ == "__main__":
main()
42 changes: 41 additions & 1 deletion webknossos/webknossos/administration/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

import attr

from ..client.api_client.models import ApiLoggedTimeGroupedByMonth, ApiUser
from ..client.api_client.models import (
ApiLoggedTimeGroupedByMonth,
ApiTeamAdd,
ApiTeamMembership,
ApiUser,
)
from ..client.context import _get_api_client


Expand Down Expand Up @@ -82,6 +87,25 @@ def get_all_managed_users(cls) -> List["User"]:
api_users = client.user_list()
return [cls._from_api_user(i) for i in api_users]

def assign_team_roles(self, team_name: str, is_team_manager: bool) -> None:
"""Assigns the specified roles to the user for the specified team."""
client = _get_api_client(enforce_auth=True)
api_user = client.user_by_id(self.user_id)
if team_name in [team.name for team in api_user.teams]:
api_user.teams = [
team
if team.name != team_name
else ApiTeamMembership(team.id, team.name, is_team_manager)
for team in api_user.teams
]
else:
api_user.teams.append(
ApiTeamMembership(
Team.get_by_name(team_name).id, team_name, is_team_manager
)
)
client.user_update(api_user)


@attr.frozen
class Team:
Expand All @@ -99,6 +123,22 @@ def get_by_name(cls, name: str) -> "Team":
return cls(api_team.id, api_team.name, api_team.organization)
raise KeyError(f"Could not find team {name}.")

@classmethod
def get_list(cls) -> List["Team"]:
"""Returns all teams of the current user."""
client = _get_api_client(enforce_auth=True)
api_teams = client.team_list()
return [
cls(api_team.id, api_team.name, api_team.organization)
for api_team in api_teams
]

@classmethod
def add(cls, team_name: str) -> None:
"""Adds a new team with the specified name."""
client = _get_api_client(enforce_auth=True)
client.team_add(ApiTeamAdd(team_name))


@attr.frozen
class LoggedTime:
Expand Down
14 changes: 14 additions & 0 deletions webknossos/webknossos/client/api_client/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ class ApiTeam:
organization: str


@attr.s(auto_attribs=True)
class ApiTeamAdd:
name: str


@attr.s(auto_attribs=True)
class ApiBoundingBox:
top_left: Tuple[int, int, int]
Expand Down Expand Up @@ -102,6 +107,14 @@ class ApiDataset:
description: Optional[str] = None


@attr.s(auto_attribs=True)
class ApiDatasetExploreAndAddRemote:
remote_uri: str
dataset_name: str
folder_path: Optional[str] = None
data_store_name: Optional[str] = None


@attr.s(auto_attribs=True)
class ApiDatasetAnnounceUpload:
dataset_name: str
Expand Down Expand Up @@ -215,6 +228,7 @@ class ApiTaskCreationResult:
class ApiTeamMembership:
id: str
name: str
is_team_manager: bool


@attr.s(auto_attribs=True)
Expand Down
19 changes: 19 additions & 0 deletions webknossos/webknossos/client/api_client/wk_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
ApiAnnotation,
ApiAnnotationUploadResult,
ApiDataset,
ApiDatasetExploreAndAddRemote,
ApiDatasetIsValidNewNameResponse,
ApiDataStore,
ApiDataStoreToken,
Expand All @@ -20,6 +21,7 @@
ApiTaskCreationResult,
ApiTaskParameters,
ApiTeam,
ApiTeamAdd,
ApiUser,
ApiWkBuildInfo,
)
Expand Down Expand Up @@ -104,6 +106,15 @@ def dataset_is_valid_new_name(
route = f"/datasets/{organization_name}/{dataset_name}/isValidNewName"
return self._get_json(route, ApiDatasetIsValidNewNameResponse)

def dataset_explore_and_add_remote(
self, dataset: ApiDatasetExploreAndAddRemote
) -> None:
route = "/datasets/exploreAndAddRemote"
self._post_json(
route,
dataset,
)

def datastore_list(self) -> List[ApiDataStore]:
route = "/datastores"
return self._get_json(route, List[ApiDataStore])
Expand Down Expand Up @@ -176,10 +187,18 @@ def user_logged_time(self, user_id: str) -> ApiLoggedTimeGroupedByMonth:
route = f"/users/{user_id}/loggedTime"
return self._get_json(route, ApiLoggedTimeGroupedByMonth)

def user_update(self, user: ApiUser) -> None:
route = f"/users/{user.id}"
self._patch_json(route, user)

def team_list(self) -> List[ApiTeam]:
route = "/teams"
return self._get_json(route, List[ApiTeam])

def team_add(self, team: ApiTeamAdd) -> None:
route = "/teams"
self._post_json(route, team)

def token_generate_for_data_store(self) -> ApiDataStoreToken:
route = "/userToken/generate"
return self._post_with_json_response(route, ApiDataStoreToken)
Expand Down
25 changes: 24 additions & 1 deletion webknossos/webknossos/dataset/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@
from webknossos.dataset._metadata import DatasetMetadata
from webknossos.geometry.vec_int import VecIntLike

from ..client.api_client.models import ApiDataset, ApiMetadata
from ..client.api_client.models import (
ApiDataset,
ApiDatasetExploreAndAddRemote,
ApiMetadata,
)
from ..geometry.vec3_int import Vec3Int, Vec3IntLike
from ._array import ArrayException, ArrayInfo, BaseArray
from ._utils import pims_images
Expand Down Expand Up @@ -2202,6 +2206,25 @@ def allowed_teams(self, allowed_teams: Sequence[Union[str, "Team"]]) -> None:
self._organization_id, self._dataset_name, team_ids
)

@classmethod
def explore_and_add_remote(
markbader marked this conversation as resolved.
Show resolved Hide resolved
cls, dataset_uri: PathLike, dataset_name: str, folder_path: str
) -> "RemoteDataset":
from ..client.context import _get_api_client

(context, dataset_name, organisation_id, sharing_token) = cls._parse_remote(
dataset_name
)

with context:
client = _get_api_client()
dataset = ApiDatasetExploreAndAddRemote(
UPath(dataset_uri).resolve().as_uri(), dataset_name, folder_path
)
client.dataset_explore_and_add_remote(dataset)

return cls.open_remote(dataset_name, organisation_id, sharing_token)

@property
def folder(self) -> RemoteFolder:
return RemoteFolder.get_by_id(self._get_dataset_info().folder_id)
Expand Down
Loading