From daaf2c8cf0cc89358cc0e8e1361ab906ee6058f6 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Mon, 4 Aug 2014 12:18:00 -0400 Subject: [PATCH] Restructure tool external file logic in load_tool_from_tmp_config in ToolValidator... ... so internals of tool XML description are only utilized in the galaxy.tools module (and submodules). Add some unit tests for this new method for finding externally referenced files. --- lib/galaxy/tools/__init__.py | 17 +++++++++++++- lib/galaxy/tools/loader.py | 10 +++++++- lib/tool_shed/tools/tool_validator.py | 14 +++++------ test/unit/tools/test_tool_external_files.py | 26 +++++++++++++++++++++ 4 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 test/unit/tools/test_tool_external_files.py diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 347f107f172..e36a0523fb4 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -69,7 +69,7 @@ from galaxy.model.item_attrs import Dictifiable from galaxy.model import Workflow from tool_shed.util import common_util from tool_shed.util import shed_util_common as suc -from .loader import load_tool, template_macro_params +from .loader import load_tool, template_macro_params, raw_tool_xml_tree from .execute import execute as execute_job from .wrappers import ( ToolParameterValueWrapper, @@ -2989,6 +2989,21 @@ class Tool( object, Dictifiable ): def get_default_history_by_trans( self, trans, create=False ): return trans.get_history( create=create ) + @classmethod + def get_externally_referenced_paths( self, path ): + """ Return relative paths to externally referenced files by the tool + described by file at `path`. External components should not assume things + about the structure of tool xml files (this is the tool's responsibility). + """ + tree = raw_tool_xml_tree(path) + root = tree.getroot() + external_paths = [] + for code_elem in root.findall( 'code' ): + external_path = code_elem.get( 'file' ) + if external_path: + external_paths.append( external_path ) + return external_paths + class OutputParameterJSONTool( Tool ): """ diff --git a/lib/galaxy/tools/loader.py b/lib/galaxy/tools/loader.py index d696ce680e1..85082647997 100644 --- a/lib/galaxy/tools/loader.py +++ b/lib/galaxy/tools/loader.py @@ -10,7 +10,7 @@ def load_tool(path): """ Loads tool from file system and preprocesses tool macros. """ - tree = parse_xml(path) + tree = raw_tool_xml_tree(path) root = tree.getroot() _import_macros(root, path) @@ -38,6 +38,14 @@ def template_macro_params(root): return param_dict +def raw_tool_xml_tree(path): + """ Load raw (no macro expansion) tree representation of tool represented + at the specified path. + """ + tree = parse_xml(path) + return tree + + def _import_macros(root, path): tool_dir = os.path.dirname(path) macros_el = root.find('macros') diff --git a/lib/tool_shed/tools/tool_validator.py b/lib/tool_shed/tools/tool_validator.py index 09afe10c971..dc3910c1695 100644 --- a/lib/tool_shed/tools/tool_validator.py +++ b/lib/tool_shed/tools/tool_validator.py @@ -3,6 +3,7 @@ import logging import os import tempfile +from galaxy.tools import Tool from galaxy.tools import parameters from galaxy.tools.parameters import dynamic_options @@ -311,15 +312,14 @@ class ToolValidator( object ): message = '' tmp_tool_config = hg_util.get_named_tmpfile_from_ctx( ctx, ctx_file, work_dir ) if tmp_tool_config: - element_tree, error_message = xml_util.parse_xml( tmp_tool_config ) - if element_tree is None: + tool_element, error_message = xml_util.parse_xml( tmp_tool_config ) + if tool_element is None: return tool, message - element_tree_root = element_tree.getroot() - # Look for code files required by the tool config. + # Look for external files required by the tool config. tmp_code_files = [] - for code_elem in element_tree_root.findall( 'code' ): - code_file_name = code_elem.get( 'file' ) - tmp_code_file_name = hg_util.copy_file_from_manifest( repo, ctx, code_file_name, work_dir ) + external_paths = Tool.get_externally_referenced_paths( tmp_tool_config ) + for path in external_paths: + tmp_code_file_name = hg_util.copy_file_from_manifest( repo, ctx, path, work_dir ) if tmp_code_file_name: tmp_code_files.append( tmp_code_file_name ) tool, valid, message = self.load_tool_from_config( repository_id, tmp_tool_config ) diff --git a/test/unit/tools/test_tool_external_files.py b/test/unit/tools/test_tool_external_files.py new file mode 100644 index 00000000000..369bd8d22a6 --- /dev/null +++ b/test/unit/tools/test_tool_external_files.py @@ -0,0 +1,26 @@ +""" Unit test logic related to finding externally referenced files in tool +descriptions. +""" +import tempfile +import os +import shutil +from galaxy.tools import Tool + + +def test_finds_external_code_file(): + assert __external_files("""""") == ["foo.py"] + + +def test_finds_skips_empty_code_file_attribute(): + assert __external_files("""""") == [] + + +def __external_files(contents): + base_path = tempfile.mkdtemp() + try: + tool_path = os.path.join(base_path, "tool.xml") + with open(tool_path, "w") as f: + f.write(contents) + return Tool.get_externally_referenced_paths(tool_path) + finally: + shutil.rmtree(base_path)