From b68991c4caa44aefeaba752748ae62db98a446fd Mon Sep 17 00:00:00 2001 From: John Chilton Date: Mon, 21 Dec 2015 20:28:36 +0000 Subject: [PATCH] Allow loading tool confs written in YAML and JSON. Because lets say someone was writing a programatic way to manage elements in a tool conf, that hypothetical person probably shouldn't be writing XML. --- lib/galaxy/tools/toolbox/panel.py | 1 - lib/galaxy/tools/toolbox/parser.py | 39 ++++++++++++++++++-- test/unit/tools/test_toolbox.py | 58 +++++++++++++++++++++--------- 3 files changed, 78 insertions(+), 20 deletions(-) diff --git a/lib/galaxy/tools/toolbox/panel.py b/lib/galaxy/tools/toolbox/panel.py index bb1d6bbe3c5..5dd77e071c4 100644 --- a/lib/galaxy/tools/toolbox/panel.py +++ b/lib/galaxy/tools/toolbox/panel.py @@ -53,7 +53,6 @@ class ToolSection( Dictifiable, HasPanelItems, object ): def __init__( self, item=None ): """ Build a ToolSection from an ElementTree element or a dictionary. """ - item = ensure_tool_conf_item(item) f = lambda item, val: item is not None and item.get( val ) or '' self.name = f( item, 'name' ) self.id = f( item, 'id' ) diff --git a/lib/galaxy/tools/toolbox/parser.py b/lib/galaxy/tools/toolbox/parser.py index c6a5ad6dd15..6a075095972 100644 --- a/lib/galaxy/tools/toolbox/parser.py +++ b/lib/galaxy/tools/toolbox/parser.py @@ -2,6 +2,7 @@ from abc import ABCMeta from abc import abstractmethod from galaxy.util import parse_xml +import yaml class ToolConfSource(object): @@ -34,6 +35,20 @@ class XmlToolConfSource(ToolConfSource): return map(ensure_tool_conf_item, self.root.getchildren()) +class YamlToolConfSource(ToolConfSource): + + def __init__(self, config_filename): + with open(config_filename, "r") as f: + as_dict = yaml.load(f) + self.as_dict = as_dict + + def parse_tool_path(self): + return self.as_dict.get('tool_path') + + def parse_items(self): + return map(ToolConfItem.from_dict, self.as_dict.get('items')) + + class ToolConfItem(object): """ This interface represents an abstract source to parse tool information from. @@ -44,6 +59,20 @@ class ToolConfItem(object): self.attributes = attributes self._elem = elem + @classmethod + def from_dict(cls, _as_dict): + as_dict = _as_dict.copy() + type = as_dict.get('type') + del as_dict['type'] + attributes = as_dict + if type == 'section': + items = map(cls.from_dict, as_dict['items']) + del as_dict['items'] + item = ToolConfSection(attributes, items) + else: + item = ToolConfItem(type, attributes) + return item + def get(self, key, default=None): return self.attributes.get(key, default) @@ -73,7 +102,9 @@ class ToolConfSection(ToolConfItem): def ensure_tool_conf_item(xml_or_item): - if isinstance(xml_or_item, ToolConfItem): + if xml_or_item is None: + return None + elif isinstance(xml_or_item, ToolConfItem): return xml_or_item else: elem = xml_or_item @@ -87,4 +118,8 @@ def ensure_tool_conf_item(xml_or_item): def get_toolbox_parser(config_filename): - return XmlToolConfSource(config_filename) + is_yaml = any(map(lambda e: config_filename.endswith(e), [".yml", ".yaml", ".json"])) + if is_yaml: + return YamlToolConfSource(config_filename) + else: + return XmlToolConfSource(config_filename) diff --git a/test/unit/tools/test_toolbox.py b/test/unit/tools/test_toolbox.py index a29c36246b7..5e784a21172 100644 --- a/test/unit/tools/test_toolbox.py +++ b/test/unit/tools/test_toolbox.py @@ -1,3 +1,4 @@ +import json import os import string import unittest @@ -117,10 +118,14 @@ class BaseToolBoxTestCase( unittest.TestCase, tools_support.UsesApp, tools_supp """ self._add_config( template % (self.test_directory, CONFIG_TEST_TOOL_VERSION_1, CONFIG_TEST_TOOL_VERSION_2 ) ) - def _add_config( self, xml, name="tool_conf.xml" ): + def _add_config( self, content, name="tool_conf.xml" ): + is_json = name.endswith(".json") path = self._tool_conf_path( name=name ) with open( path, "w" ) as f: - f.write( xml ) + if not is_json or isinstance(content, basestring): + f.write( content ) + else: + json.dump(content, f) self.config_files.append( path ) def _tool_conf_path( self, name="tool_conf.xml" ): @@ -146,20 +151,22 @@ class ToolBoxTestCase( BaseToolBoxTestCase ): assert toolbox.get_tool( "not_a_test_tool" ) is None def test_to_dict_in_panel( self ): - self._init_tool_in_section() - mapper = routes.Mapper() - mapper.connect( "tool_runner", "/test/tool_runner" ) - as_dict = self.toolbox.to_dict( mock_trans() ) - test_section = self._find_section(as_dict, "t") - assert len(test_section["elems"]) == 1 - assert test_section["elems"][0]["id"] == "test_tool" + for json_conf in [True, False]: + self._init_tool_in_section(json=json_conf) + mapper = routes.Mapper() + mapper.connect( "tool_runner", "/test/tool_runner" ) + as_dict = self.toolbox.to_dict( mock_trans() ) + test_section = self._find_section(as_dict, "t") + assert len(test_section["elems"]) == 1 + assert test_section["elems"][0]["id"] == "test_tool" def test_to_dict_out_of_panel( self ): - self._init_tool_in_section() - mapper = routes.Mapper() - mapper.connect( "tool_runner", "/test/tool_runner" ) - as_dict = self.toolbox.to_dict( mock_trans(), in_panel=False ) - assert as_dict[0]["id"] == "test_tool" + for json_conf in [True, False]: + self._init_tool_in_section(json=json_conf) + mapper = routes.Mapper() + mapper.connect( "tool_runner", "/test/tool_runner" ) + as_dict = self.toolbox.to_dict( mock_trans(), in_panel=False ) + assert as_dict[0]["id"] == "test_tool" def test_out_of_panel_filtering( self ): self._init_tool_in_section() @@ -183,7 +190,7 @@ class ToolBoxTestCase( BaseToolBoxTestCase ): if elem.get("id") == section_id: assert elem["model_class"] == "ToolSection" return elem - assert False, "Failed to find section with id [%s]" % id + assert False, "Failed to find section with id [%s]" % section_id def test_tool_shed_properties( self ): self._init_tool() @@ -275,6 +282,13 @@ class ToolBoxTestCase( BaseToolBoxTestCase ): toolbox = self.toolbox assert toolbox.get_tool( "test_tool" ) is not None + def test_tool_dir_json( self ): + self._init_tool() + self._add_config({"items": [{"type": "tool_dir", "dir": self.test_directory}]}, name="tool_conf.json") + + toolbox = self.toolbox + assert toolbox.get_tool( "test_tool" ) is not None + def test_workflow_in_panel( self ): stored_workflow = self.__test_workflow() encoded_id = self.app.security.encode_id( stored_workflow.id ) @@ -305,9 +319,19 @@ class ToolBoxTestCase( BaseToolBoxTestCase ): section = self.toolbox._tool_panel[ 'tid' ] self.__check_test_labels( section.elems ) - def _init_tool_in_section( self ): + def _init_tool_in_section( self, json=False ): self._init_tool() - self._add_config( """
""" ) + if not json: + self._add_config( """
""" ) + else: + section = { + "type": "section", + "id": "t", + "name": "test", + "items": [{"type": "tool", + "file": "tool.xml"}], + } + self._add_config({"items": [section]}, name="tool_conf.json") def __check_test_labels( self, panel_dict ): assert panel_dict.keys() == ["label_lab1", "label_lab2"]