From ce1fc58c1b399111931a71b24b4d913567702ad0 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Mon, 25 Jan 2021 19:12:09 +0100 Subject: [PATCH 01/14] Refactor legacy controller logic to manager class --- lib/galaxy/managers/tags.py | 64 +++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 lib/galaxy/managers/tags.py diff --git a/lib/galaxy/managers/tags.py b/lib/galaxy/managers/tags.py new file mode 100644 index 00000000000..541daa58e2c --- /dev/null +++ b/lib/galaxy/managers/tags.py @@ -0,0 +1,64 @@ +from typing import List, Optional + +from pydantic import ( + BaseModel, + Field, +) + +from galaxy.app import StructuredApp +from galaxy.exceptions import MessageException +from galaxy.webapps.base.controller import UsesTagsMixin +from galaxy.webapps.base.webapp import GalaxyWebTransaction + + +class ItemTagsPayload(BaseModel): + item_id: str = Field( + ..., # This field is required + title="Item ID", + description="The identifier of the item whose tags will be updated", + ) + item_class: str = Field( + ..., # This field is required + title="Item class", + description="The name of the class of the item", + ) + item_tags: Optional[List[str]] = Field( + default=None, + title="Item tags", + description="The list of tags that will replace the current tags associated with the item", + ) + + +class TagsManager(UsesTagsMixin): + """Interface/service object shared by controllers for interacting with tags.""" + + def __init__(self, app: StructuredApp): + self._app = app + + def update( + self, + trans: GalaxyWebTransaction, + payload: ItemTagsPayload, + ) -> None: + """Apply a new set of tags to an item; previous tags are deleted.""" + if payload.item_id is None: + raise MessageException("Please provide the item id (item_id).") + if payload.item_class is None: + raise MessageException("Please provide the item class (item_class).") + + new_tags: Optional[str] = None + if payload.item_tags and len(payload.item_tags) > 0: + new_tags = ",".join(payload.item_tags) + item = self._get_item(trans, payload.item_class, trans.security.decode_id(payload.item_id)) + user = trans.user + self.get_tag_handler(trans).delete_item_tags(user, item) + self.get_tag_handler(trans).apply_item_tags(user, item, new_tags) + trans.sa_session.flush() + + def _get_item(self, trans: GalaxyWebTransaction, item_class_name: str, id: int): + """ + Get an item based on type and id. + """ + item_class = self.get_tag_handler(trans).item_tag_assoc_info[item_class_name].item_class + item = trans.sa_session.query(item_class).filter(item_class.id == id).first() + return item From bd8a5e7f5792702f489282d1fbc4c88d112cbd1e Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Mon, 25 Jan 2021 19:54:28 +0100 Subject: [PATCH 02/14] Add FastAPI controller --- lib/galaxy/managers/tags.py | 6 +-- lib/galaxy/webapps/base/controller.py | 2 +- lib/galaxy/webapps/galaxy/api/tags.py | 70 +++++++++++++++++---------- 3 files changed, 49 insertions(+), 29 deletions(-) diff --git a/lib/galaxy/managers/tags.py b/lib/galaxy/managers/tags.py index 541daa58e2c..f86cf44e82c 100644 --- a/lib/galaxy/managers/tags.py +++ b/lib/galaxy/managers/tags.py @@ -15,17 +15,17 @@ class ItemTagsPayload(BaseModel): item_id: str = Field( ..., # This field is required title="Item ID", - description="The identifier of the item whose tags will be updated", + description="The `encoded identifier` of the item whose tags will be updated.", ) item_class: str = Field( ..., # This field is required title="Item class", - description="The name of the class of the item", + description="The name of the class of the item.", ) item_tags: Optional[List[str]] = Field( default=None, title="Item tags", - description="The list of tags that will replace the current tags associated with the item", + description="The list of tags that will replace the current tags associated with the item.", ) diff --git a/lib/galaxy/webapps/base/controller.py b/lib/galaxy/webapps/base/controller.py index eac3e994319..2be1a97d171 100644 --- a/lib/galaxy/webapps/base/controller.py +++ b/lib/galaxy/webapps/base/controller.py @@ -1515,7 +1515,7 @@ class UsesQuotaMixin: class UsesTagsMixin(SharableItemSecurityMixin): - def get_tag_handler(self, trans): + def get_tag_handler(self, trans) -> tags.GalaxyTagHandler: return trans.app.tag_handler def _get_user_tags(self, trans, item_class_name, id): diff --git a/lib/galaxy/webapps/galaxy/api/tags.py b/lib/galaxy/webapps/galaxy/api/tags.py index 9f0639f486c..87b6e27a3dc 100644 --- a/lib/galaxy/webapps/galaxy/api/tags.py +++ b/lib/galaxy/webapps/galaxy/api/tags.py @@ -3,15 +3,56 @@ API Controller providing Galaxy Tags """ import logging -from galaxy.exceptions import MessageException +from fastapi import Body +# TODO: replace with Router after merging #11219 +from fastapi_utils.cbv import cbv +from fastapi_utils.inferring_router import InferringRouter as APIRouter + +from galaxy.managers.tags import ( + ItemTagsPayload, + TagsManager, +) +from galaxy.structured_app import StructuredApp from galaxy.web import expose_api -from galaxy.webapps.base.controller import BaseAPIController, UsesTagsMixin +from galaxy.webapps.base.controller import BaseAPIController from galaxy.webapps.base.webapp import GalaxyWebTransaction +from . import Depends, get_app, get_trans log = logging.getLogger(__name__) +router = APIRouter(tags=['tags']) -class TagsController(BaseAPIController, UsesTagsMixin): + +def get_tags_manager(app: StructuredApp = Depends(get_app)) -> TagsManager: + return TagsManager(app) # TODO: remove/refactor after merging #11180 + + +@cbv(router) +class FastAPITags: + manager: TagsManager = Depends(get_tags_manager) + + @router.put( + '/api/tags', + summary="Apply a new set of tags to an item; previous tags are deleted.", + ) + def update( + self, + payload: ItemTagsPayload = Body( + ..., # Required + title="Payload", + description="Request body containing the item and the tags to be assigned.", + ), + trans: GalaxyWebTransaction = Depends(get_trans), + ): + """Replaces the tags associated with an item with the new ones specified in the payload.""" + self.manager.update(trans, payload) + + +class TagsController(BaseAPIController): + + def __init__(self, app): + super().__init__(app) + self.manager = TagsManager(app) # Retag an item. All previous tags are deleted and new tags are applied. @expose_api @@ -21,25 +62,4 @@ class TagsController(BaseAPIController, UsesTagsMixin): Apply a new set of tags to an item; previous tags are deleted. """ - item_id = payload.get("item_id") - item_class = payload.get("item_class") - item_tags = payload.get("item_tags") - if item_id is None: - raise MessageException("Please provide the item id (item_id).") - if item_class is None: - raise MessageException("Please provide the item class (item_class).") - if item_tags and len(item_tags) > 0: - item_tags = ",".join(item_tags) - item = self._get_item(trans, item_class, trans.security.decode_id(item_id)) - user = trans.user - self.get_tag_handler(trans).delete_item_tags(user, item) - self.get_tag_handler(trans).apply_item_tags(user, item, item_tags) - trans.sa_session.flush() - - def _get_item(self, trans: GalaxyWebTransaction, item_class_name, id): - """ - Get an item based on type and id. - """ - item_class = self.get_tag_handler(trans).item_tag_assoc_info[item_class_name].item_class - item = trans.sa_session.query(item_class).filter(item_class.id == id).first() - return item + self.manager.update(trans, ItemTagsPayload(**payload)) From 7778a9f4e562919ed4c23c1fe1c516577d7cc8bb Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Tue, 26 Jan 2021 11:08:12 +0100 Subject: [PATCH 03/14] Return 204 status and assert OK in test --- lib/galaxy/webapps/galaxy/api/tags.py | 6 +++++- lib/galaxy_test/api/test_datasets.py | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/tags.py b/lib/galaxy/webapps/galaxy/api/tags.py index 87b6e27a3dc..1a42bd19fc4 100644 --- a/lib/galaxy/webapps/galaxy/api/tags.py +++ b/lib/galaxy/webapps/galaxy/api/tags.py @@ -3,7 +3,10 @@ API Controller providing Galaxy Tags """ import logging -from fastapi import Body +from fastapi import ( + Body, + status, +) # TODO: replace with Router after merging #11219 from fastapi_utils.cbv import cbv from fastapi_utils.inferring_router import InferringRouter as APIRouter @@ -34,6 +37,7 @@ class FastAPITags: @router.put( '/api/tags', summary="Apply a new set of tags to an item; previous tags are deleted.", + status_code=status.HTTP_204_NO_CONTENT, ) def update( self, diff --git a/lib/galaxy_test/api/test_datasets.py b/lib/galaxy_test/api/test_datasets.py index 5e4a98768f7..ce56a2dde01 100644 --- a/lib/galaxy_test/api/test_datasets.py +++ b/lib/galaxy_test/api/test_datasets.py @@ -127,7 +127,8 @@ class DatasetsApiTestCase(ApiTestCase): 'item_class': 'HistoryDatasetAssociation', 'item_tags': ['cool:tag_a', 'cool:tag_b', 'tag_c', 'name:tag_d', '#tag_e'], } - self._put("tags", payload).json() + put_response = self._put("tags", payload) + self._assert_status_code_is_ok(put_response) updated_hda = self._get( f"histories/{self.history_id}/contents/{hda_id}").json() assert 'cool:tag_a' in updated_hda['tags'] From 33ee18758ba80090f487871d155a4dadc6f1214c Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Tue, 26 Jan 2021 11:19:04 +0100 Subject: [PATCH 04/14] Remove redundant checks These checks are done by the pydantic model now --- lib/galaxy/managers/tags.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/galaxy/managers/tags.py b/lib/galaxy/managers/tags.py index f86cf44e82c..9eedaced928 100644 --- a/lib/galaxy/managers/tags.py +++ b/lib/galaxy/managers/tags.py @@ -6,7 +6,6 @@ from pydantic import ( ) from galaxy.app import StructuredApp -from galaxy.exceptions import MessageException from galaxy.webapps.base.controller import UsesTagsMixin from galaxy.webapps.base.webapp import GalaxyWebTransaction @@ -41,11 +40,6 @@ class TagsManager(UsesTagsMixin): payload: ItemTagsPayload, ) -> None: """Apply a new set of tags to an item; previous tags are deleted.""" - if payload.item_id is None: - raise MessageException("Please provide the item id (item_id).") - if payload.item_class is None: - raise MessageException("Please provide the item class (item_class).") - new_tags: Optional[str] = None if payload.item_tags and len(payload.item_tags) > 0: new_tags = ",".join(payload.item_tags) From a90510eee49c4f6f8243da369fc440dad9d9a724 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Tue, 26 Jan 2021 13:15:50 +0100 Subject: [PATCH 05/14] Fix Bad Request error --- lib/galaxy_test/api/test_datasets.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/galaxy_test/api/test_datasets.py b/lib/galaxy_test/api/test_datasets.py index ce56a2dde01..38755e66ba3 100644 --- a/lib/galaxy_test/api/test_datasets.py +++ b/lib/galaxy_test/api/test_datasets.py @@ -1,3 +1,4 @@ +import json import textwrap from galaxy_test.base.populators import ( @@ -122,11 +123,11 @@ class DatasetsApiTestCase(ApiTestCase): def test_tag_change(self): hda_id = self.dataset_populator.new_dataset(self.history_id)['id'] - payload = { + payload = json.dumps({ 'item_id': hda_id, 'item_class': 'HistoryDatasetAssociation', 'item_tags': ['cool:tag_a', 'cool:tag_b', 'tag_c', 'name:tag_d', '#tag_e'], - } + }) put_response = self._put("tags", payload) self._assert_status_code_is_ok(put_response) updated_hda = self._get( From 01d7d57549a30454a368ec97fc23f304a03add1b Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Tue, 26 Jan 2021 13:16:30 +0100 Subject: [PATCH 06/14] Minor docstring changes --- lib/galaxy/webapps/galaxy/api/tags.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/tags.py b/lib/galaxy/webapps/galaxy/api/tags.py index 1a42bd19fc4..8f5613a5957 100644 --- a/lib/galaxy/webapps/galaxy/api/tags.py +++ b/lib/galaxy/webapps/galaxy/api/tags.py @@ -36,19 +36,23 @@ class FastAPITags: @router.put( '/api/tags', - summary="Apply a new set of tags to an item; previous tags are deleted.", + summary="Apply a new set of tags to an item.", status_code=status.HTTP_204_NO_CONTENT, ) def update( self, + trans: GalaxyWebTransaction = Depends(get_trans), payload: ItemTagsPayload = Body( ..., # Required title="Payload", description="Request body containing the item and the tags to be assigned.", ), - trans: GalaxyWebTransaction = Depends(get_trans), ): - """Replaces the tags associated with an item with the new ones specified in the payload.""" + """Replaces the tags associated with an item with the new ones specified in the payload. + + - The previous tags will be __deleted__. + - If no tags are provided in the request body, the currently associated tags will also be __deleted__. + """ self.manager.update(trans, payload) From 202e62de18ce6b5f56baee4d887c5da5c6c6aa25 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Tue, 26 Jan 2021 13:45:20 +0100 Subject: [PATCH 07/14] Add tags metadata --- lib/galaxy/webapps/galaxy/fast_app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/galaxy/webapps/galaxy/fast_app.py b/lib/galaxy/webapps/galaxy/fast_app.py index 82ab083e740..d5bd24f7130 100644 --- a/lib/galaxy/webapps/galaxy/fast_app.py +++ b/lib/galaxy/webapps/galaxy/fast_app.py @@ -28,6 +28,10 @@ api_tags_metadata = [ "name": "licenses", "description": "Operations with [SPDX licenses](https://spdx.org/licenses/).", }, + { + "name": "tags", + "description": "Operations with tags.", + }, { "name": "tool data tables", "description": "Operations with tool [Data Tables](https://galaxyproject.org/admin/tools/data-tables/).", From 18b3297f5981691efcea11d4b3c3fcfa8d250d70 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Tue, 26 Jan 2021 17:44:03 +0100 Subject: [PATCH 08/14] Refactor controllers to avoid UsesTagsMixin in manager --- lib/galaxy/managers/tags.py | 25 +++++++++++++++---------- lib/galaxy/webapps/galaxy/api/tags.py | 16 ++++++++-------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/lib/galaxy/managers/tags.py b/lib/galaxy/managers/tags.py index 9eedaced928..c5ca93c574b 100644 --- a/lib/galaxy/managers/tags.py +++ b/lib/galaxy/managers/tags.py @@ -1,4 +1,7 @@ -from typing import List, Optional +from typing import ( + List, + Optional, +) from pydantic import ( BaseModel, @@ -6,8 +9,8 @@ from pydantic import ( ) from galaxy.app import StructuredApp -from galaxy.webapps.base.controller import UsesTagsMixin -from galaxy.webapps.base.webapp import GalaxyWebTransaction +from galaxy.managers.context import ProvidesUserContext +from galaxy.model.tags import GalaxyTagHandler class ItemTagsPayload(BaseModel): @@ -28,7 +31,7 @@ class ItemTagsPayload(BaseModel): ) -class TagsManager(UsesTagsMixin): +class TagsManager: """Interface/service object shared by controllers for interacting with tags.""" def __init__(self, app: StructuredApp): @@ -36,23 +39,25 @@ class TagsManager(UsesTagsMixin): def update( self, - trans: GalaxyWebTransaction, + trans: ProvidesUserContext, payload: ItemTagsPayload, + tag_handler: GalaxyTagHandler ) -> None: """Apply a new set of tags to an item; previous tags are deleted.""" new_tags: Optional[str] = None if payload.item_tags and len(payload.item_tags) > 0: new_tags = ",".join(payload.item_tags) - item = self._get_item(trans, payload.item_class, trans.security.decode_id(payload.item_id)) + item = self._get_item(trans, tag_handler, payload) user = trans.user - self.get_tag_handler(trans).delete_item_tags(user, item) - self.get_tag_handler(trans).apply_item_tags(user, item, new_tags) + tag_handler.delete_item_tags(user, item) + tag_handler.apply_item_tags(user, item, new_tags) trans.sa_session.flush() - def _get_item(self, trans: GalaxyWebTransaction, item_class_name: str, id: int): + def _get_item(self, trans: ProvidesUserContext, tag_handler: GalaxyTagHandler, payload: ItemTagsPayload): """ Get an item based on type and id. """ - item_class = self.get_tag_handler(trans).item_tag_assoc_info[item_class_name].item_class + id = trans.security.decode_id(payload.item_id) + item_class = tag_handler.item_tag_assoc_info[payload.item_class].item_class item = trans.sa_session.query(item_class).filter(item_class.id == id).first() return item diff --git a/lib/galaxy/webapps/galaxy/api/tags.py b/lib/galaxy/webapps/galaxy/api/tags.py index 8f5613a5957..69ef31dc301 100644 --- a/lib/galaxy/webapps/galaxy/api/tags.py +++ b/lib/galaxy/webapps/galaxy/api/tags.py @@ -11,14 +11,14 @@ from fastapi import ( from fastapi_utils.cbv import cbv from fastapi_utils.inferring_router import InferringRouter as APIRouter +from galaxy.managers.context import ProvidesUserContext from galaxy.managers.tags import ( ItemTagsPayload, TagsManager, ) from galaxy.structured_app import StructuredApp from galaxy.web import expose_api -from galaxy.webapps.base.controller import BaseAPIController -from galaxy.webapps.base.webapp import GalaxyWebTransaction +from galaxy.webapps.base.controller import BaseAPIController, UsesTagsMixin from . import Depends, get_app, get_trans log = logging.getLogger(__name__) @@ -31,7 +31,7 @@ def get_tags_manager(app: StructuredApp = Depends(get_app)) -> TagsManager: @cbv(router) -class FastAPITags: +class FastAPITags(UsesTagsMixin): manager: TagsManager = Depends(get_tags_manager) @router.put( @@ -41,7 +41,7 @@ class FastAPITags: ) def update( self, - trans: GalaxyWebTransaction = Depends(get_trans), + trans: ProvidesUserContext = Depends(get_trans), payload: ItemTagsPayload = Body( ..., # Required title="Payload", @@ -53,10 +53,10 @@ class FastAPITags: - The previous tags will be __deleted__. - If no tags are provided in the request body, the currently associated tags will also be __deleted__. """ - self.manager.update(trans, payload) + self.manager.update(trans, payload, self.get_tag_handler(trans)) -class TagsController(BaseAPIController): +class TagsController(BaseAPIController, UsesTagsMixin): def __init__(self, app): super().__init__(app) @@ -64,10 +64,10 @@ class TagsController(BaseAPIController): # Retag an item. All previous tags are deleted and new tags are applied. @expose_api - def update(self, trans: GalaxyWebTransaction, payload: dict, **kwd): + def update(self, trans: ProvidesUserContext, payload: dict, **kwd): """ PUT /api/tags/ Apply a new set of tags to an item; previous tags are deleted. """ - self.manager.update(trans, ItemTagsPayload(**payload)) + self.manager.update(trans, ItemTagsPayload(**payload), self.get_tag_handler(trans)) From a7a0eefb000b6bb148beb140405d5c63a77f670e Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Tue, 26 Jan 2021 18:17:01 +0100 Subject: [PATCH 09/14] Remove UsesTagsMixin from controllers --- lib/galaxy/managers/tags.py | 8 ++++---- lib/galaxy/webapps/galaxy/api/tags.py | 16 ++++++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/galaxy/managers/tags.py b/lib/galaxy/managers/tags.py index c5ca93c574b..73f7b60660a 100644 --- a/lib/galaxy/managers/tags.py +++ b/lib/galaxy/managers/tags.py @@ -10,7 +10,6 @@ from pydantic import ( from galaxy.app import StructuredApp from galaxy.managers.context import ProvidesUserContext -from galaxy.model.tags import GalaxyTagHandler class ItemTagsPayload(BaseModel): @@ -41,22 +40,23 @@ class TagsManager: self, trans: ProvidesUserContext, payload: ItemTagsPayload, - tag_handler: GalaxyTagHandler ) -> None: """Apply a new set of tags to an item; previous tags are deleted.""" + tag_handler = trans.app.tag_handler new_tags: Optional[str] = None if payload.item_tags and len(payload.item_tags) > 0: new_tags = ",".join(payload.item_tags) - item = self._get_item(trans, tag_handler, payload) + item = self._get_item(trans, payload) user = trans.user tag_handler.delete_item_tags(user, item) tag_handler.apply_item_tags(user, item, new_tags) trans.sa_session.flush() - def _get_item(self, trans: ProvidesUserContext, tag_handler: GalaxyTagHandler, payload: ItemTagsPayload): + def _get_item(self, trans: ProvidesUserContext, payload: ItemTagsPayload): """ Get an item based on type and id. """ + tag_handler = trans.app.tag_handler id = trans.security.decode_id(payload.item_id) item_class = tag_handler.item_tag_assoc_info[payload.item_class].item_class item = trans.sa_session.query(item_class).filter(item_class.id == id).first() diff --git a/lib/galaxy/webapps/galaxy/api/tags.py b/lib/galaxy/webapps/galaxy/api/tags.py index 69ef31dc301..9cb4b02f4c4 100644 --- a/lib/galaxy/webapps/galaxy/api/tags.py +++ b/lib/galaxy/webapps/galaxy/api/tags.py @@ -18,8 +18,12 @@ from galaxy.managers.tags import ( ) from galaxy.structured_app import StructuredApp from galaxy.web import expose_api -from galaxy.webapps.base.controller import BaseAPIController, UsesTagsMixin -from . import Depends, get_app, get_trans +from galaxy.webapps.base.controller import BaseAPIController +from . import ( + Depends, + get_app, + get_trans, +) log = logging.getLogger(__name__) @@ -31,7 +35,7 @@ def get_tags_manager(app: StructuredApp = Depends(get_app)) -> TagsManager: @cbv(router) -class FastAPITags(UsesTagsMixin): +class FastAPITags: manager: TagsManager = Depends(get_tags_manager) @router.put( @@ -53,10 +57,10 @@ class FastAPITags(UsesTagsMixin): - The previous tags will be __deleted__. - If no tags are provided in the request body, the currently associated tags will also be __deleted__. """ - self.manager.update(trans, payload, self.get_tag_handler(trans)) + self.manager.update(trans, payload) -class TagsController(BaseAPIController, UsesTagsMixin): +class TagsController(BaseAPIController): def __init__(self, app): super().__init__(app) @@ -70,4 +74,4 @@ class TagsController(BaseAPIController, UsesTagsMixin): Apply a new set of tags to an item; previous tags are deleted. """ - self.manager.update(trans, ItemTagsPayload(**payload), self.get_tag_handler(trans)) + self.manager.update(trans, ItemTagsPayload(**payload)) From 72a9936e217b1f1418c1b1d2880e94961b6b40c7 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Wed, 27 Jan 2021 09:51:18 +0100 Subject: [PATCH 10/14] Remove unnecessary app injection --- lib/galaxy/managers/tags.py | 10 +--------- lib/galaxy/webapps/galaxy/api/tags.py | 8 +++----- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/lib/galaxy/managers/tags.py b/lib/galaxy/managers/tags.py index 73f7b60660a..0028a24ab77 100644 --- a/lib/galaxy/managers/tags.py +++ b/lib/galaxy/managers/tags.py @@ -8,7 +8,6 @@ from pydantic import ( Field, ) -from galaxy.app import StructuredApp from galaxy.managers.context import ProvidesUserContext @@ -33,14 +32,7 @@ class ItemTagsPayload(BaseModel): class TagsManager: """Interface/service object shared by controllers for interacting with tags.""" - def __init__(self, app: StructuredApp): - self._app = app - - def update( - self, - trans: ProvidesUserContext, - payload: ItemTagsPayload, - ) -> None: + def update(self, trans: ProvidesUserContext, payload: ItemTagsPayload) -> None: """Apply a new set of tags to an item; previous tags are deleted.""" tag_handler = trans.app.tag_handler new_tags: Optional[str] = None diff --git a/lib/galaxy/webapps/galaxy/api/tags.py b/lib/galaxy/webapps/galaxy/api/tags.py index 9cb4b02f4c4..9af7182d565 100644 --- a/lib/galaxy/webapps/galaxy/api/tags.py +++ b/lib/galaxy/webapps/galaxy/api/tags.py @@ -16,12 +16,10 @@ from galaxy.managers.tags import ( ItemTagsPayload, TagsManager, ) -from galaxy.structured_app import StructuredApp from galaxy.web import expose_api from galaxy.webapps.base.controller import BaseAPIController from . import ( Depends, - get_app, get_trans, ) @@ -30,8 +28,8 @@ log = logging.getLogger(__name__) router = APIRouter(tags=['tags']) -def get_tags_manager(app: StructuredApp = Depends(get_app)) -> TagsManager: - return TagsManager(app) # TODO: remove/refactor after merging #11180 +def get_tags_manager() -> TagsManager: + return TagsManager() # TODO: remove/refactor after merging #11180 @cbv(router) @@ -64,7 +62,7 @@ class TagsController(BaseAPIController): def __init__(self, app): super().__init__(app) - self.manager = TagsManager(app) + self.manager = TagsManager() # Retag an item. All previous tags are deleted and new tags are applied. @expose_api From 7ebc7c9ad60db7886c7b00ebf0ad30a760c46072 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Mon, 1 Feb 2021 11:00:34 +0100 Subject: [PATCH 11/14] Disable FastAPI router --- lib/galaxy/webapps/galaxy/api/tags.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/tags.py b/lib/galaxy/webapps/galaxy/api/tags.py index 9af7182d565..8664d036625 100644 --- a/lib/galaxy/webapps/galaxy/api/tags.py +++ b/lib/galaxy/webapps/galaxy/api/tags.py @@ -7,7 +7,7 @@ from fastapi import ( Body, status, ) -# TODO: replace with Router after merging #11219 +# TODO: replace with _router after merging #11219 from fastapi_utils.cbv import cbv from fastapi_utils.inferring_router import InferringRouter as APIRouter @@ -25,18 +25,19 @@ from . import ( log = logging.getLogger(__name__) -router = APIRouter(tags=['tags']) +# TODO: This FastAPI router is disabled. Please rename it to `router` when the database session issues are fixed. +_router = APIRouter(tags=['tags']) def get_tags_manager() -> TagsManager: return TagsManager() # TODO: remove/refactor after merging #11180 -@cbv(router) +@cbv(_router) class FastAPITags: manager: TagsManager = Depends(get_tags_manager) - @router.put( + @_router.put( '/api/tags', summary="Apply a new set of tags to an item.", status_code=status.HTTP_204_NO_CONTENT, From 29ce1e675864847835f4b53c7afa72b40a4e36cd Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Mon, 1 Feb 2021 11:25:15 +0100 Subject: [PATCH 12/14] Add TaggableItemClass enum and fix item_id field typing --- lib/galaxy/managers/tags.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/managers/tags.py b/lib/galaxy/managers/tags.py index 0028a24ab77..019d868f391 100644 --- a/lib/galaxy/managers/tags.py +++ b/lib/galaxy/managers/tags.py @@ -1,3 +1,4 @@ +from enum import Enum from typing import ( List, Optional, @@ -9,18 +10,29 @@ from pydantic import ( ) from galaxy.managers.context import ProvidesUserContext +from galaxy.schema.fields import EncodedDatabaseIdField + + +class TaggableItemClass(str, Enum): + History = "History" + HistoryDatasetAssociation = "HistoryDatasetAssociation" + HistoryDatasetCollectionAssociation = "HistoryDatasetCollectionAssociation" + LibraryDatasetDatasetAssociation = "LibraryDatasetDatasetAssociation" + Page = "Page" + StoredWorkflow = "StoredWorkflow" + Visualization = "Visualization" class ItemTagsPayload(BaseModel): - item_id: str = Field( + item_id: EncodedDatabaseIdField = Field( ..., # This field is required title="Item ID", description="The `encoded identifier` of the item whose tags will be updated.", ) - item_class: str = Field( + item_class: TaggableItemClass = Field( ..., # This field is required title="Item class", - description="The name of the class of the item.", + description="The name of the class of the item that will be tagged.", ) item_tags: Optional[List[str]] = Field( default=None, From 49d2b5f640ef8e9d344a1240c0537d87da5772bd Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Mon, 1 Feb 2021 13:38:46 +0100 Subject: [PATCH 13/14] Dynamically generate enum for TaggableItemClass --- lib/galaxy/managers/tags.py | 14 +++++--------- lib/galaxy/model/__init__.py | 7 ++++++- lib/galaxy/webapps/galaxy/api/tags.py | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/galaxy/managers/tags.py b/lib/galaxy/managers/tags.py index 019d868f391..c3d797ef8d1 100644 --- a/lib/galaxy/managers/tags.py +++ b/lib/galaxy/managers/tags.py @@ -10,17 +10,13 @@ from pydantic import ( ) from galaxy.managers.context import ProvidesUserContext +from galaxy.model import ItemTagAssociation from galaxy.schema.fields import EncodedDatabaseIdField - -class TaggableItemClass(str, Enum): - History = "History" - HistoryDatasetAssociation = "HistoryDatasetAssociation" - HistoryDatasetCollectionAssociation = "HistoryDatasetCollectionAssociation" - LibraryDatasetDatasetAssociation = "LibraryDatasetDatasetAssociation" - Page = "Page" - StoredWorkflow = "StoredWorkflow" - Visualization = "Visualization" +taggable_item_names = {item: item for item in ItemTagAssociation.associated_item_names} +# This Enum is generated dynamically and mypy can not statically infer it's real type +# so it should be ignored. See:https://github.com/python/mypy/issues/4865#issuecomment-592560696 +TaggableItemClass = Enum('TaggableItemClass', taggable_item_names) # type: ignore class ItemTagsPayload(BaseModel): diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 643aa8628c3..1692884bef5 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -19,7 +19,7 @@ from collections import defaultdict from datetime import datetime, timedelta from enum import Enum from string import Template -from typing import Optional, TYPE_CHECKING +from typing import List, Optional, TYPE_CHECKING from uuid import UUID, uuid4 from boltons.iterutils import remap @@ -6350,6 +6350,11 @@ class Tag(RepresentById): class ItemTagAssociation(Dictifiable): dict_collection_visible_keys = ['id', 'user_tname', 'user_value'] dict_element_visible_keys = dict_collection_visible_keys + associated_item_names: List[str] = [] + + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + cls.associated_item_names.append(cls.__name__.replace("TagAssociation", "")) def __init__(self, id=None, user=None, item_id=None, tag_id=None, user_tname=None, value=None): self.id = id diff --git a/lib/galaxy/webapps/galaxy/api/tags.py b/lib/galaxy/webapps/galaxy/api/tags.py index 8664d036625..aa36d091389 100644 --- a/lib/galaxy/webapps/galaxy/api/tags.py +++ b/lib/galaxy/webapps/galaxy/api/tags.py @@ -7,7 +7,7 @@ from fastapi import ( Body, status, ) -# TODO: replace with _router after merging #11219 +# TODO: replace with Router after merging #11219 from fastapi_utils.cbv import cbv from fastapi_utils.inferring_router import InferringRouter as APIRouter From ebd6a8db18eaa2db01441c90d7dbc8c96287ce11 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Mon, 1 Feb 2021 13:48:05 +0100 Subject: [PATCH 14/14] Fix enum serializarion --- lib/galaxy/managers/tags.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/galaxy/managers/tags.py b/lib/galaxy/managers/tags.py index c3d797ef8d1..a166d2578ba 100644 --- a/lib/galaxy/managers/tags.py +++ b/lib/galaxy/managers/tags.py @@ -36,6 +36,9 @@ class ItemTagsPayload(BaseModel): description="The list of tags that will replace the current tags associated with the item.", ) + class Config: + use_enum_values = True + class TagsManager: """Interface/service object shared by controllers for interacting with tags."""