From 68a8608e161858612fe4c45a8192f67bcbb2d40c Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 6 Dec 2023 12:54:29 +0100 Subject: [PATCH 1/4] Allow parsing lists of simple test assertions --- lib/galaxy/tool_util/parser/yaml.py | 11 ++++++- test/unit/tool_util/test_test_parsing.py | 40 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 test/unit/tool_util/test_test_parsing.py diff --git a/lib/galaxy/tool_util/parser/yaml.py b/lib/galaxy/tool_util/parser/yaml.py index 7ebc574c9ad0..c39599849744 100644 --- a/lib/galaxy/tool_util/parser/yaml.py +++ b/lib/galaxy/tool_util/parser/yaml.py @@ -267,7 +267,7 @@ def _parse_test(i, test_dict) -> ToolSourceTest: return test_dict -def __to_test_assert_list(assertions) -> AssertionList: +def to_test_assert_list(assertions) -> AssertionList: def expand_dict_form(item): key, value = item new_value = value.copy() @@ -281,6 +281,12 @@ def expand_dict_form(item): for assertion in assertions: # TODO: not handling nested assertions correctly, # not sure these are used though. + if "that" not in assertion: + new_assertion = {} + for assertion_key, assertion_value in assertion.items(): + new_assertion["that"] = assertion_key + new_assertion.update(assertion_value) + assertion = new_assertion children = [] if "children" in assertion: children = assertion["children"] @@ -295,6 +301,9 @@ def expand_dict_form(item): return assert_list or None # XML variant is None if no assertions made +__to_test_assert_list = to_test_assert_list + + class YamlPageSource(PageSource): def __init__(self, inputs_list): self.inputs_list = inputs_list diff --git a/test/unit/tool_util/test_test_parsing.py b/test/unit/tool_util/test_test_parsing.py new file mode 100644 index 000000000000..c489d9658cae --- /dev/null +++ b/test/unit/tool_util/test_test_parsing.py @@ -0,0 +1,40 @@ +import yaml + +from galaxy.tool_util.parser.yaml import to_test_assert_list + +# Legacy style +ASSERT_THAT_LIST = yaml.safe_load( + """ +- that: "has_text" + text: "Number of input reads |\t1051466" +- that: "has_text" + text: "Uniquely mapped reads number |\t871202" +""" +) +# New list of assertion style +ASSERT_LIST = yaml.safe_load( + """ +- has_text: + text: "Number of input reads |\t1051466" +- has_text: + text: "Uniquely mapped reads number |\t871202" +""" +) +# Singleton assertion +SIMPLE_ASSERT = {"has_text": {"text": "Number of input reads |\t1051466"}} + + +def test_assert_that_list_to_test_assert_list(): + to_test_assert_list(ASSERT_THAT_LIST) + + +def test_assert_list_to_test_assert_list(): + to_test_assert_list(ASSERT_LIST) + + +def test_simple_assert_to_test_assert_list(): + to_test_assert_list(SIMPLE_ASSERT) + + +def test_assert_legacy_same_as_new_list_style(): + assert to_test_assert_list(ASSERT_THAT_LIST) == to_test_assert_list(ASSERT_THAT_LIST) From 0f76e6ada76ca04e1b178bc32d7184875f2e0506 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 4 Dec 2023 13:30:15 +0100 Subject: [PATCH 2/4] Add int to Bytes, bool to PermissiveBoolean schema --- lib/galaxy/tool_util/xsd/galaxy.xsd | 41 ++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/lib/galaxy/tool_util/xsd/galaxy.xsd b/lib/galaxy/tool_util/xsd/galaxy.xsd index 4994e5e7b7ee..02d67e1c35b3 100644 --- a/lib/galaxy/tool_util/xsd/galaxy.xsd +++ b/lib/galaxy/tool_util/xsd/galaxy.xsd @@ -7322,24 +7322,39 @@ and ``contains``. In addition there is ``sim_size`` which is discouraged in favo Documentation for PermissiveBoolean - - - - - - - - - - + + + + + + + + + + + + + + + + + Number of bytes allowing for suffix (k|K|M|G|P|E)i? - - - + + + + + + + + + + + From 295934b125a0bada5caac4c152daeb454b621e52 Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Thu, 7 Dec 2023 12:25:58 +0000 Subject: [PATCH 3/4] Refactor item tag API tests to create only necessary items. --- lib/galaxy_test/api/test_item_tags.py | 52 ++++++++++++++------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/lib/galaxy_test/api/test_item_tags.py b/lib/galaxy_test/api/test_item_tags.py index 319e91a2ce22..57c4c53bd9a9 100644 --- a/lib/galaxy_test/api/test_item_tags.py +++ b/lib/galaxy_test/api/test_item_tags.py @@ -6,7 +6,7 @@ from galaxy_test.driver import integration_util -class TestHistoriesApi(integration_util.IntegrationTestCase): +class TestItemTagsApi(integration_util.IntegrationTestCase): dataset_populator: DatasetPopulator @classmethod @@ -20,75 +20,77 @@ def setUp(self): self.dataset_collection_populator = DatasetCollectionPopulator(self.galaxy_interactor) def test_get_tags_workflows(self): - response = self._test_get_tags(self._create_prefixes()["workflows"]) + response = self._test_get_tags(self._create_prefix("workflows")) self._assert_status_code_is(response, 200) def test_create_tag_workflows(self): - response = self._test_create_tag(self._create_prefixes()["workflows"]) + response = self._test_create_tag(self._create_prefix("workflows")) self._assert_status_code_is(response, 200) def test_update_tag_workflows(self): - response = self._test_update_tag(self._create_prefixes()["workflows"]) + response = self._test_update_tag(self._create_prefix("workflows")) self._assert_status_code_is(response, 200) def test_get_tag_workflows(self): - response = self._test_get_tag(self._create_prefixes()["workflows"]) + response = self._test_get_tag(self._create_prefix("workflows")) self._assert_status_code_is(response, 200) def test_delete_tag_workflows(self): - response = self._test_delete_tag(self._create_prefixes()["workflows"]) + response = self._test_delete_tag(self._create_prefix("workflows")) self._assert_status_code_is(response, 200) def test_get_tags_histories(self): - response = self._test_get_tags(self._create_prefixes()["histories"]) + response = self._test_get_tags(self._create_prefix("histories")) self._assert_status_code_is(response, 200) def test_create_tag_histories(self): - response = self._test_create_tag(self._create_prefixes()["histories"]) + response = self._test_create_tag(self._create_prefix("histories")) self._assert_status_code_is(response, 200) def test_update_tag_histories(self): - response = self._test_update_tag(self._create_prefixes()["histories"]) + response = self._test_update_tag(self._create_prefix("histories")) self._assert_status_code_is(response, 200) def test_get_tag_histories(self): - response = self._test_get_tag(self._create_prefixes()["histories"]) + response = self._test_get_tag(self._create_prefix("histories")) self._assert_status_code_is(response, 200) def test_delete_tag_histories(self): - response = self._test_delete_tag(self._create_prefixes()["histories"]) + response = self._test_delete_tag(self._create_prefix("histories")) self._assert_status_code_is(response, 200) def test_get_tags_histories_content(self): - response = self._test_get_tags(self._create_prefixes()["histories_content"]) + response = self._test_get_tags(self._create_prefix("histories_content")) self._assert_status_code_is(response, 200) def test_create_tag_histories_content(self): - response = self._test_create_tag(self._create_prefixes()["histories_content"]) + response = self._test_create_tag(self._create_prefix("histories_content")) self._assert_status_code_is(response, 200) def test_update_tag_histories_content(self): - response = self._test_update_tag(self._create_prefixes()["histories_content"]) + response = self._test_update_tag(self._create_prefix("histories_content")) self._assert_status_code_is(response, 200) def test_get_tag_histories_content(self): - response = self._test_get_tag(self._create_prefixes()["histories_content"]) + response = self._test_get_tag(self._create_prefix("histories_content")) self._assert_status_code_is(response, 200) def test_delete_tag_histories_content(self): - response = self._test_delete_tag(self._create_prefixes()["histories_content"]) + response = self._test_delete_tag(self._create_prefix("histories_content")) self._assert_status_code_is(response, 200) - def _create_prefixes(self): - workflow_id = self._create_workflow() + def _create_prefix(self, type_: str) -> str: + if type_ == "workflows": + workflow_id = self._create_workflow() + return f"workflows/{workflow_id}" history_id = self._create_history() - history_content_id = self._create_history_contents(history_id) - prefixs = { - "workflows": f"workflows/{workflow_id}", - "histories": f"histories/{history_id}", - "histories_content": f"histories/{history_id}/contents/{history_content_id}", - } - return prefixs + if type_ == "histories": + return f"histories/{history_id}" + elif type_ == "histories_content": + history_content_id = self._create_history_contents(history_id) + return f"histories/{history_id}/contents/{history_content_id}" + else: + raise ValueError(f"Unrecognized type_ {type_}") def _test_get_tags(self, prefix): url = f"{prefix}/tags" From 71be4875e3a8d2f3d99e0bb0d85fc6bf88b34feb Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Thu, 7 Dec 2023 12:28:02 +0000 Subject: [PATCH 4/4] Make payload optional again for create tag API Before https://github.com/galaxyproject/galaxy/pull/17064 it was possible to create a tag by simply making a POST request like `/api/histories/911dde3ddb677bcd/tags/tag1` , this restores the previous behaviour. Fix BioBlend test failure: https://github.com/galaxyproject/bioblend/actions/runs/7094150509/job/19308900670#step:10:3029 --- client/src/api/schema/schema.ts | 8 ++++---- lib/galaxy/schema/item_tags.py | 4 ++-- lib/galaxy/webapps/galaxy/api/item_tags.py | 4 +++- lib/galaxy_test/api/test_item_tags.py | 7 ++++++- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index d4c9e9238542..678b34d63a66 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -6392,7 +6392,7 @@ export interface components { */ ItemTagsCreatePayload: { /** value of the item tag */ - value: string; + value?: string; }; /** * ItemTagsListResponse @@ -13796,7 +13796,7 @@ export interface operations { history_id: string; }; }; - requestBody: { + requestBody?: { content: { "application/json": components["schemas"]["ItemTagsCreatePayload"]; }; @@ -15103,7 +15103,7 @@ export interface operations { tag_name: string; }; }; - requestBody: { + requestBody?: { content: { "application/json": components["schemas"]["ItemTagsCreatePayload"]; }; @@ -19862,7 +19862,7 @@ export interface operations { tag_name: string; }; }; - requestBody: { + requestBody?: { content: { "application/json": components["schemas"]["ItemTagsCreatePayload"]; }; diff --git a/lib/galaxy/schema/item_tags.py b/lib/galaxy/schema/item_tags.py index 6458241717c9..1237e42f01dc 100644 --- a/lib/galaxy/schema/item_tags.py +++ b/lib/galaxy/schema/item_tags.py @@ -42,7 +42,7 @@ class ItemTagsListResponse(Model): class ItemTagsCreatePayload(Model): """Payload schema for creating an item tag.""" - value: str = Field( - Required, + value: Optional[str] = Field( + None, title="value of the item tag", ) diff --git a/lib/galaxy/webapps/galaxy/api/item_tags.py b/lib/galaxy/webapps/galaxy/api/item_tags.py index 7b7f7fb65389..a32805ac4d49 100644 --- a/lib/galaxy/webapps/galaxy/api/item_tags.py +++ b/lib/galaxy/webapps/galaxy/api/item_tags.py @@ -72,8 +72,10 @@ def create( trans: ProvidesAppContext = DependsOnTrans, item_id: DecodedDatabaseIdField = Path(..., title="Item ID", alias=tagged_item_id), tag_name: str = Path(..., title="Tag Name"), - payload: ItemTagsCreatePayload = Body(...), + payload: ItemTagsCreatePayload = Body(None), ) -> ItemTagsResponse: + if payload is None: + payload = ItemTagsCreatePayload() return self.manager.create(trans, tagged_item_class, item_id, tag_name, payload) @router.put( diff --git a/lib/galaxy_test/api/test_item_tags.py b/lib/galaxy_test/api/test_item_tags.py index 57c4c53bd9a9..3bf856126c8d 100644 --- a/lib/galaxy_test/api/test_item_tags.py +++ b/lib/galaxy_test/api/test_item_tags.py @@ -1,3 +1,8 @@ +from typing import ( + Any, + Dict, +) + from galaxy_test.base.populators import ( DatasetCollectionPopulator, DatasetPopulator, @@ -125,7 +130,7 @@ def _test_delete_tag(self, prefix): def _create_valid_tag(self, prefix: str): url = f"{prefix}/tags/awesometagname" - tag_data = dict(value="awesometagvalue") + tag_data: Dict[str, Any] = {} # Can also be dict(value="awesometagvalue") response = self._post(url, data=tag_data, json=True) return response