Skip to content

Commit

Permalink
Merge pull request galaxyproject#17051 from arash77/migrate_groups_fa…
Browse files Browse the repository at this point in the history
…stapi

Migrate groups API to fastAPI
  • Loading branch information
davelopez authored Nov 23, 2023
2 parents 01c4de5 + 361522d commit 1ed254f
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 101 deletions.
177 changes: 173 additions & 4 deletions client/src/api/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,9 +381,21 @@ export interface paths {
/** Return raw sequence data */
get: operations["sequences_api_genomes__id__sequences_get"];
};
"/api/groups": {
/** Displays a collection (list) of groups. */
get: operations["index_api_groups_get"];
/** Creates a new group. */
post: operations["create_api_groups_post"];
};
"/api/groups/{group_id}": {
/** Displays information about a group. */
get: operations["show_group_api_groups__group_id__get"];
/** Modifies a group. */
put: operations["update_api_groups__group_id__put"];
};
"/api/groups/{group_id}/roles": {
/** Displays a collection (list) of groups. */
get: operations["index_api_groups__group_id__roles_get"];
get: operations["group_roles_api_groups__group_id__roles_get"];
};
"/api/groups/{group_id}/roles/{role_id}": {
/** Displays information about a group role. */
Expand Down Expand Up @@ -418,7 +430,7 @@ export interface paths {
* @description GET /api/groups/{encoded_group_id}/users
* Displays a collection (list) of groups.
*/
get: operations["index_api_groups__group_id__users_get"];
get: operations["group_users_api_groups__group_id__users_get"];
};
"/api/groups/{group_id}/users/{user_id}": {
/**
Expand Down Expand Up @@ -4626,6 +4638,29 @@ export interface components {
/** Tags */
tags?: string[];
};
/**
* GroupCreatePayload
* @description Payload schema for creating a group.
*/
GroupCreatePayload: {
/** name of the group */
name: string;
/**
* role IDs
* @default []
*/
role_ids?: string[];
/**
* user IDs
* @default []
*/
user_ids?: string[];
};
/**
* GroupListResponse
* @description Response schema for listing groups.
*/
GroupListResponse: components["schemas"]["GroupResponse"][];
/**
* GroupModel
* @description User group model
Expand Down Expand Up @@ -4665,6 +4700,32 @@ export interface components {
*/
model_class: "GroupQuotaAssociation";
};
/**
* GroupResponse
* @description Response schema for a group.
*/
GroupResponse: {
/**
* group ID
* @example 0123456789ABCDEF
*/
id: string;
/**
* Model class
* @description The name of the database model class.
* @default Group
* @enum {string}
*/
model_class: "Group";
/** name of the group */
name: string;
/** URL for the roles of the group */
roles_url?: string;
/** URL for the group */
url: string;
/** URL for the users of the group */
users_url?: string;
};
/** GroupRoleListResponse */
GroupRoleListResponse: components["schemas"]["GroupRoleResponse"][];
/** GroupRoleResponse */
Expand Down Expand Up @@ -11538,7 +11599,115 @@ export interface operations {
};
};
};
index_api_groups__group_id__roles_get: {
index_api_groups_get: {
/** Displays a collection (list) of groups. */
parameters?: {
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
header?: {
"run-as"?: string;
};
};
responses: {
/** @description Successful Response */
200: {
content: {
"application/json": components["schemas"]["GroupListResponse"];
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
create_api_groups_post: {
/** Creates a new group. */
parameters?: {
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
header?: {
"run-as"?: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["GroupCreatePayload"];
};
};
responses: {
/** @description Successful Response */
200: {
content: {
"application/json": components["schemas"]["GroupListResponse"];
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
show_group_api_groups__group_id__get: {
/** Displays information about a group. */
parameters: {
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
header?: {
"run-as"?: string;
};
path: {
group_id: string;
};
};
responses: {
/** @description Successful Response */
200: {
content: {
"application/json": components["schemas"]["GroupResponse"];
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
update_api_groups__group_id__put: {
/** Modifies a group. */
parameters: {
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
header?: {
"run-as"?: string;
};
path: {
group_id: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["GroupCreatePayload"];
};
};
responses: {
/** @description Successful Response */
200: {
content: {
"application/json": components["schemas"]["GroupResponse"];
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
group_roles_api_groups__group_id__roles_get: {
/** Displays a collection (list) of groups. */
parameters: {
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
Expand Down Expand Up @@ -11750,7 +11919,7 @@ export interface operations {
};
};
};
index_api_groups__group_id__users_get: {
group_users_api_groups__group_id__users_get: {
/**
* Displays a collection (list) of groups.
* @description GET /api/groups/{encoded_group_id}/users
Expand Down
81 changes: 29 additions & 52 deletions lib/galaxy/managers/groups.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
from typing import (
Any,
Dict,
List,
)

from sqlalchemy import (
false,
select,
Expand All @@ -16,21 +10,15 @@
ObjectAttributeMissingException,
ObjectNotFound,
)
from galaxy.managers.base import decode_id
from galaxy.managers.context import ProvidesAppContext
from galaxy.managers.roles import get_roles_by_ids
from galaxy.managers.users import get_users_by_ids
from galaxy.model import (
Group,
Role,
)
from galaxy.model import Group
from galaxy.model.base import transaction
from galaxy.model.scoped_session import galaxy_scoped_session
from galaxy.schema.fields import (
DecodedDatabaseIdField,
EncodedDatabaseIdField,
)
from galaxy.schema.fields import DecodedDatabaseIdField
from galaxy.schema.groups import GroupCreatePayload
from galaxy.structured_app import MinimalManagerApp
from galaxy.web import url_for


class GroupsManager:
Expand All @@ -47,33 +35,33 @@ def index(self, trans: ProvidesAppContext):
for group in get_not_deleted_groups(trans.sa_session):
item = group.to_dict(value_mapper={"id": DecodedDatabaseIdField.encode})
encoded_id = DecodedDatabaseIdField.encode(group.id)
item["url"] = url_for("group", id=encoded_id)
item["url"] = self._url_for(trans, "show_group", group_id=encoded_id)
rval.append(item)
return rval

def create(self, trans: ProvidesAppContext, payload: Dict[str, Any]):
def create(self, trans: ProvidesAppContext, payload: GroupCreatePayload):
"""
Creates a new group.
"""
sa_session = trans.sa_session
name = payload.get("name", None)
name = payload.name
if name is None:
raise ObjectAttributeMissingException("Missing required name")
self._check_duplicated_group_name(sa_session, name)

group = model.Group(name=name)
sa_session.add(group)
encoded_user_ids = payload.get("user_ids", [])
users = self._get_users_by_encoded_ids(sa_session, encoded_user_ids)
encoded_role_ids = payload.get("role_ids", [])
roles = self._get_roles_by_encoded_ids(sa_session, encoded_role_ids)
user_ids = payload.user_ids
users = get_users_by_ids(sa_session, user_ids)
role_ids = payload.role_ids
roles = get_roles_by_ids(sa_session, role_ids)
trans.app.security_agent.set_entity_group_associations(groups=[group], roles=roles, users=users)
with transaction(sa_session):
sa_session.commit()

encoded_id = DecodedDatabaseIdField.encode(group.id)
item = group.to_dict(view="element", value_mapper={"id": DecodedDatabaseIdField.encode})
item["url"] = url_for("group", id=encoded_id)
item["url"] = self._url_for(trans, "show_group", group_id=encoded_id)
return [item]

def show(self, trans: ProvidesAppContext, group_id: int):
Expand All @@ -83,32 +71,40 @@ def show(self, trans: ProvidesAppContext, group_id: int):
encoded_id = DecodedDatabaseIdField.encode(group_id)
group = self._get_group(trans.sa_session, group_id)
item = group.to_dict(view="element", value_mapper={"id": DecodedDatabaseIdField.encode})
item["url"] = url_for("group", id=encoded_id)
item["users_url"] = url_for("group_users", group_id=encoded_id)
item["roles_url"] = url_for("group_roles", group_id=encoded_id)
item["url"] = self._url_for(trans, "show_group", group_id=encoded_id)
item["users_url"] = self._url_for(trans, "group_users", group_id=encoded_id)
item["roles_url"] = self._url_for(trans, "group_roles", group_id=encoded_id)
return item

def update(self, trans: ProvidesAppContext, group_id: int, payload: Dict[str, Any]):
def update(self, trans: ProvidesAppContext, group_id: int, payload: GroupCreatePayload):
"""
Modifies a group.
"""
sa_session = trans.sa_session
group = self._get_group(sa_session, group_id)
name = payload.get("name", None)
name = payload.name
if name:
self._check_duplicated_group_name(sa_session, name)
group.name = name
sa_session.add(group)
encoded_user_ids = payload.get("user_ids", [])
users = self._get_users_by_encoded_ids(sa_session, encoded_user_ids)
encoded_role_ids = payload.get("role_ids", [])
roles = self._get_roles_by_encoded_ids(sa_session, encoded_role_ids)
user_ids = payload.user_ids
users = get_users_by_ids(sa_session, user_ids)
role_ids = payload.role_ids
roles = get_roles_by_ids(sa_session, role_ids)
self._app.security_agent.set_entity_group_associations(
groups=[group], roles=roles, users=users, delete_existing_assocs=False
)
with transaction(sa_session):
sa_session.commit()

encoded_id = DecodedDatabaseIdField.encode(group.id)
item = group.to_dict(view="element", value_mapper={"id": DecodedDatabaseIdField.encode})
item["url"] = self._url_for(trans, "show_group", group_id=encoded_id)
return item

def _url_for(self, trans, name, **kwargs):
return trans.url_builder(name, **kwargs)

def _check_duplicated_group_name(self, sa_session: galaxy_scoped_session, group_name: str) -> None:
if get_group_by_name(sa_session, group_name):
raise Conflict(f"A group with name '{group_name}' already exists")
Expand All @@ -119,25 +115,6 @@ def _get_group(self, sa_session: galaxy_scoped_session, group_id: int) -> model.
raise ObjectNotFound("Group with the provided id was not found.")
return group

def _get_users_by_encoded_ids(
self, sa_session: galaxy_scoped_session, encoded_user_ids: List[EncodedDatabaseIdField]
) -> List[model.User]:
user_ids = self._decode_ids(encoded_user_ids)
return get_users_by_ids(sa_session, user_ids)

def _get_roles_by_encoded_ids(
self, sa_session: galaxy_scoped_session, encoded_role_ids: List[EncodedDatabaseIdField]
) -> List[model.Role]:
role_ids = self._decode_ids(encoded_role_ids)
stmt = select(Role).where(Role.id.in_(role_ids))
return sa_session.scalars(stmt).all()

def _decode_id(self, encoded_id: EncodedDatabaseIdField) -> int:
return decode_id(self._app, encoded_id)

def _decode_ids(self, encoded_ids: List[EncodedDatabaseIdField]) -> List[int]:
return [self._decode_id(encoded_id) for encoded_id in encoded_ids]


def get_group_by_name(session: Session, name: str):
stmt = select(Group).filter(Group.name == name).limit(1)
Expand Down
10 changes: 9 additions & 1 deletion lib/galaxy/managers/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
false,
select,
)
from sqlalchemy.orm import exc as sqlalchemy_exceptions
from sqlalchemy.orm import (
exc as sqlalchemy_exceptions,
Session,
)

import galaxy.exceptions
from galaxy import model
Expand Down Expand Up @@ -97,3 +100,8 @@ def create_role(self, trans: ProvidesUserContext, role_definition_model: RoleDef
with transaction(trans.sa_session):
trans.sa_session.commit()
return role


def get_roles_by_ids(session: Session, role_ids):
stmt = select(Role).where(Role.id.in_(role_ids))
return session.scalars(stmt).all()
Loading

0 comments on commit 1ed254f

Please sign in to comment.