Skip to content

Commit

Permalink
Merge pull request #5119 from mvdbeek/dev_toolshed_install_improvemen…
Browse files Browse the repository at this point in the history
…ts_2

Improve tool validation and metadata generation
  • Loading branch information
jmchilton committed Dec 7, 2017
2 parents a901264 + 4f162f2 commit 30e2736
Show file tree
Hide file tree
Showing 19 changed files with 478 additions and 257 deletions.
2 changes: 1 addition & 1 deletion lib/galaxy/model/tool_shed_install/__init__.py
Expand Up @@ -143,7 +143,7 @@ def guess_shed_config(self, app, default=None):
if tool_id in tool_ids:
self.shed_config_filename = name
return shed_tool_conf_dict
if self.includes_datatypes:
if self.includes_datatypes or self.includes_data_managers:
# We need to search by file paths here, which is less desirable.
tool_shed = common_util.remove_protocol_and_port_from_tool_shed_url(self.tool_shed)
for shed_tool_conf_dict in app.toolbox.dynamic_confs(include_migrated_tool_conf=True):
Expand Down
9 changes: 5 additions & 4 deletions lib/galaxy/tools/__init__.py
Expand Up @@ -940,9 +940,10 @@ def _parse_citations(self, tool_source):
for citation_elem in citations_elem:
if citation_elem.tag != "citation":
pass
citation = self.app.citations_manager.parse_citation(citation_elem, self.tool_dir)
if citation:
citations.append(citation)
if hasattr(self.app, 'citations_manager'):
citation = self.app.citations_manager.parse_citation(citation_elem, self.tool_dir)
if citation:
citations.append(citation)
return citations

def parse_input_elem(self, page_source, enctypes, context=None):
Expand Down Expand Up @@ -1378,7 +1379,7 @@ def params_with_missing_index_file(self):
for input_param in self.input_params:
if isinstance(input_param, SelectToolParameter) and input_param.is_dynamic:
options = input_param.options
if options and options.missing_index_file and input_param not in params:
if options and options.tool_data_table and options.tool_data_table.missing_index_file and input_param not in params:
params.append(input_param)
return params

Expand Down
3 changes: 2 additions & 1 deletion lib/galaxy/tools/data/__init__.py
Expand Up @@ -21,6 +21,7 @@
from galaxy import util
from galaxy.util.dictifiable import Dictifiable
from galaxy.util.odict import odict
from galaxy.util.renamed_temporary_file import RenamedTemporaryFile

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -186,7 +187,7 @@ def to_xml_file(self, shed_tool_data_table_config, new_elems=None, remove_elems=
# add new elems
out_elems.extend(new_elems)
out_path_is_new = not os.path.exists(full_path)
with open(full_path, 'wb') as out:
with RenamedTemporaryFile(full_path) as out:
out.write('<?xml version="1.0"?>\n<tables>\n')
for elem in out_elems:
out.write(util.xml_to_string(elem, pretty=True))
Expand Down
33 changes: 19 additions & 14 deletions lib/galaxy/tools/parameters/basic.py
Expand Up @@ -34,6 +34,7 @@
history_query
)
from ..parser import get_input_source as ensure_input_source
from ..repositories import ValidationContext

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -1435,7 +1436,7 @@ def __init__(self, tool, input_source, trans):
super(BaseDataToolParameter, self).__init__(tool, input_source)
self.refresh_on_change = True

def _datatypes_registery(self, trans, tool):
def _datatypes_registry(self, trans, tool):
# Find datatypes_registry
if tool is None:
if trans:
Expand All @@ -1448,22 +1449,26 @@ def _datatypes_registery(self, trans, tool):
datatypes_registry = galaxy.datatypes.registry.Registry()
datatypes_registry.load_datatypes()
else:
datatypes_registry = tool.app.datatypes_registry
if isinstance(tool.app, ValidationContext):
datatypes_registry = {}
else:
datatypes_registry = tool.app.datatypes_registry
return datatypes_registry

def _parse_formats(self, trans, tool, input_source):
datatypes_registry = self._datatypes_registery(trans, tool)

datatypes_registry = self._datatypes_registry(trans, tool)
formats = []
# Build list of classes for supported data formats
self.extensions = input_source.get('format', 'data').split(",")
normalized_extensions = [extension.strip().lower() for extension in self.extensions]
formats = []
for extension in normalized_extensions:
datatype = datatypes_registry.get_datatype_by_extension(extension)
if datatype is not None:
formats.append(datatype)
else:
log.warning("Datatype class not found for extension '%s', which is used in the 'format' attribute of parameter '%s'" % (extension, self.name))
if datatypes_registry:
# Skip during validation since no datatypes are loaded
for extension in normalized_extensions:
datatype = datatypes_registry.get_datatype_by_extension(extension)
if datatype is not None:
formats.append(datatype)
else:
log.warning("Datatype class not found for extension '%s', which is used in the 'format' attribute of parameter '%s'" % (extension, self.name))
self.formats = formats

def _parse_options(self, input_source):
Expand Down Expand Up @@ -1777,9 +1782,9 @@ def to_dict(self, trans, other_values={}):
# create dictionary and fill default parameters
d = super(DataToolParameter, self).to_dict(trans)
extensions = self.extensions
datatypes_registery = self._datatypes_registery(trans, self.tool)
all_edam_formats = datatypes_registery.edam_formats if hasattr(datatypes_registery, 'edam_formats') else {}
all_edam_data = datatypes_registery.edam_data if hasattr(datatypes_registery, 'edam_formats') else {}
datatypes_registry = self._datatypes_registry(trans, self.tool)
all_edam_formats = datatypes_registry.edam_formats if hasattr(datatypes_registry, 'edam_formats') else {}
all_edam_data = datatypes_registry.edam_data if hasattr(datatypes_registry, 'edam_formats') else {}
edam_formats = [all_edam_formats.get(ext, None) for ext in extensions]
edam_data = [all_edam_data.get(ext, None) for ext in extensions]

Expand Down
71 changes: 71 additions & 0 deletions lib/galaxy/tools/repositories.py
@@ -0,0 +1,71 @@
"""Provides a subset of app for verifying tools."""
import os
import shutil
import tempfile
from contextlib import contextmanager

from galaxy.datatypes.registry import Registry
from galaxy.tools.data import ToolDataTableManager
from galaxy.util.bunch import Bunch
from galaxy.util.dbkeys import GenomeBuilds


class ValidationContext(object):
"""Minimal App object for tool validation."""

def __init__(self, app_name,
security,
model,
tool_data_path,
shed_tool_data_path,
tool_data_tables=None,
registry=None,
hgweb_config_manager=None):
self.name = app_name
self.security = security
self.model = model
self.config = Bunch()
self.config.tool_data_path = tool_data_path
self.config.shed_tool_data_path = shed_tool_data_path
_, self.config.tool_data_table_config = tempfile.mkstemp()
_, self.config.shed_tool_data_table_config = tempfile.mkstemp()
self.tool_data_tables = tool_data_tables
self.datatypes_registry = registry or Registry()
self.hgweb_config_manager = hgweb_config_manager
_, self.config.len_file_path = tempfile.mkstemp()
_, self.config.builds_file_path = tempfile.mkstemp()
self.genome_builds = GenomeBuilds(self)

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
cleanup_paths = {self.config.builds_file_path,
self.config.len_file_path,
self.config.tool_data_table_config,
self.config.shed_tool_data_table_config}
for path in cleanup_paths:
try:
os.remove(path)
except Exception:
pass

@staticmethod
@contextmanager
def from_app(app, work_dir=None):
cleanup = False
if not work_dir:
work_dir = tempfile.mkdtemp()
cleanup = True
tool_data_tables = ToolDataTableManager(work_dir)
with ValidationContext(app_name=app.name,
security=app.security,
model=app.model,
tool_data_path=work_dir,
shed_tool_data_path=work_dir,
tool_data_tables=tool_data_tables,
hgweb_config_manager=getattr(app, 'hgweb_config_manager', None)
) as app:
yield app
if cleanup:
shutil.rmtree(work_dir, ignore_errors=True)
42 changes: 42 additions & 0 deletions lib/galaxy/util/renamed_temporary_file.py
@@ -0,0 +1,42 @@
"""Safely write file to temporary file and then move file into place."""
# Copied from https://stackoverflow.com/a/12007885.
import os
import tempfile


class RenamedTemporaryFile(object):
"""
A temporary file object which will be renamed to the specified
path on exit.
"""
def __init__(self, final_path, **kwargs):
tmpfile_dir = kwargs.pop('dir', None)

# Put temporary file in the same directory as the location for the
# final file so that an atomic move into place can occur.

if tmpfile_dir is None:
tmpfile_dir = os.path.dirname(final_path)

self.tmpfile = tempfile.NamedTemporaryFile(dir=tmpfile_dir, **kwargs)
self.final_path = final_path

def __getattr__(self, attr):
"""
Delegate attribute access to the underlying temporary file object.
"""
return getattr(self.tmpfile, attr)

def __enter__(self):
self.tmpfile.__enter__()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.tmpfile.delete = False
result = self.tmpfile.__exit__(exc_type, exc_val, exc_tb)
os.rename(self.tmpfile.name, self.final_path)
else:
result = self.tmpfile.__exit__(exc_type, exc_val, exc_tb)

return result
10 changes: 6 additions & 4 deletions lib/galaxy/webapps/tool_shed/api/tools.py
Expand Up @@ -12,6 +12,7 @@
RequestParameterInvalidException
)
from galaxy.tools.parameters import params_to_strings
from galaxy.tools.repositories import ValidationContext
from galaxy.web import _future_expose_api_raw_anonymous_and_sessionless as expose_api_raw_anonymous_and_sessionless
from galaxy.web.base.controller import BaseAPIController
from galaxy.webapps.tool_shed.search.tool_search import ToolSearch
Expand Down Expand Up @@ -163,10 +164,11 @@ def json(self, trans, **kwd):
trans.response.status = 404
return {'status': 'error', 'message': message}

tv = tool_validator.ToolValidator(trans.app)
repository, tool, message = tv.load_tool_from_changeset_revision(tsr_id,
changeset,
found_tool.tool_config)
with ValidationContext.from_app(trans.app) as validation_context:
tv = tool_validator.ToolValidator(validation_context)
repository, tool, message = tv.load_tool_from_changeset_revision(tsr_id,
changeset,
found_tool.tool_config)
if message:
status = 'error'
return dict(message=message, status=status)
Expand Down

0 comments on commit 30e2736

Please sign in to comment.