Skip to content

Commit

Permalink
Update to latest galaxy-lib.
Browse files Browse the repository at this point in the history
An assortment of tweaks and fixes to tool parsing and cwltool handling used by planemo and down stream CWL fork of Galaxy.
  • Loading branch information
jmchilton committed May 12, 2016
1 parent bf79960 commit 7ab4065
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 64 deletions.
27 changes: 23 additions & 4 deletions lib/galaxy/tools/cwl/cwltool_deps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
""" This module contains logic for dealing with cwltool as an optional
dependency for Galaxy and/or applications which use Galaxy as a library.
"""Logic for dealing with cwltool as an optional dependency.
Use this as the import interface for cwltool and just call
:func:`ensure_cwltool_available` before using any of the imported
functionality at runtime.
"""

try:
Expand All @@ -14,20 +17,27 @@
job,
process,
)
except ImportError:
except (ImportError, SyntaxError):
# Drop SyntaxError once cwltool supports Python 3
main = None
workflow = None
job = None
process = None

try:
from cwltool import load_tool
except (ImportError, SyntaxError):
load_tool = None

try:
import shellescape
except ImportError:
shellescape = None

try:
import schema_salad
except ImportError:
except (ImportError, SyntaxError):
# Drop SyntaxError once schema_salad supports Python 3
schema_salad = None

import re
Expand All @@ -36,8 +46,16 @@


def ensure_cwltool_available():
"""Assert optional dependencies proxied via this module are available at runtime.
Throw an ImportError with a description of the problem if they do not exist.
"""
if main is None or workflow is None or shellescape is None:
message = "This feature requires cwltool and dependencies to be available, they are not."
if main is None:
message += " cwltool is not unavailable."
elif load_tool is None:
message += " cwltool.load_tool is unavailabe - cwltool version is too old."
if requests is None:
message += " Library 'requests' unavailable."
if shellescape is None:
Expand All @@ -49,6 +67,7 @@ def ensure_cwltool_available():

__all__ = [
'main',
'load_tool',
'workflow',
'process',
'ensure_cwltool_available',
Expand Down
14 changes: 7 additions & 7 deletions lib/galaxy/tools/cwl/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ def load_job_proxy(job_directory):

def to_cwl_tool_object(tool_path):
proxy_class = None
cwl_tool = None
cwl_tool = schema_loader.tool(path=tool_path)
if isinstance(cwl_tool, int):
raise Exception("Failed to load tool.")
Expand All @@ -88,10 +87,6 @@ def to_cwl_tool_object(tool_path):
if "cwlVersion" not in raw_tool:
raise Exception("File does not declare a CWL version, pre-draft 3 CWL tools are not supported.")

cwl_version = raw_tool["cwlVersion"]
if cwl_version != "https://w3id.org/cwl/cwl#draft-3":
raise Exception("Only draft 3 CWL tools are supported by Galaxy.")

proxy = proxy_class(cwl_tool, tool_path)
return proxy

Expand Down Expand Up @@ -154,14 +149,19 @@ def docker_identifier(self):
def description(self):
""" Return description to tool. """

@abstractmethod
def label(self):
""" Return label for tool. """


class CommandLineToolProxy(ToolProxy):

def description(self):
# Feels like I should be getting some abstract namespaced thing
# not actually description, is this correct?
return self._tool.tool.get('description')

def label(self):
return self._tool.tool.get('label')

def input_instances(self):
return self._find_inputs(self._tool.inputs_record_schema)

Expand Down
82 changes: 38 additions & 44 deletions lib/galaxy/tools/cwl/schema.py
Original file line number Diff line number Diff line change
@@ -1,78 +1,72 @@
"""Abstraction around cwltool and related libraries for loading a CWL artifact."""
from collections import namedtuple
import os

from six.moves.urllib.parse import urldefrag

from .cwltool_deps import (
process,
ensure_cwltool_available,
schema_salad,
workflow,
load_tool,
)

ProcessDefinition = namedtuple("ProcessObject", ["process_object", "metadata"])
RawProcessReference = namedtuple("RawProcessReference", ["process_object", "uri"])
ProcessDefinition = namedtuple("ProcessDefinition", ["process_object", "metadata", "document_loader", "avsc_names", "raw_process_reference"])


class SchemaLoader(object):

def __init__(self, strict=True):
self._strict = strict
self._document_loader = None
self._avsc_names = None
self._raw_document_loader = None

def _lazy_init(self):
if self._document_loader is not None:
return
@property
def raw_document_loader(self):
if self._raw_document_loader is None:
ensure_cwltool_available()
self._raw_document_loader = schema_salad.ref_resolver.Loader({"cwl": "https://w3id.org/cwl/cwl#", "id": "@id"})

document_loader, avsc_names, _ = process.get_schema()
self._document_loader = document_loader
self._avsc_names = avsc_names
return self._raw_document_loader

if isinstance(avsc_names, Exception):
raise avsc_names

def raw_object(self, path):
self._lazy_init()
self._document_loader.idx.clear()
def raw_process_reference(self, path):
uri = "file://" + os.path.abspath(path)
fileuri, _ = urldefrag(uri)
return self._document_loader.fetch(fileuri)
return RawProcessReference(self.raw_document_loader.fetch(fileuri), uri)

def process_definition(self, raw_object):
self._lazy_init()
process_object, metadata = schema_salad.schema.load_and_validate(
self._document_loader,
self._avsc_names,
raw_object,
self._strict,
def process_definition(self, raw_reference):
document_loader, avsc_names, process_object, metadata, uri = load_tool.validate_document(
self.raw_document_loader,
raw_reference.process_object,
raw_reference.uri,
)
process_def = ProcessDefinition(
process_object,
metadata,
document_loader,
avsc_names,
raw_reference,
)
return ProcessDefinition(process_object, metadata)
return process_def

def tool(self, **kwds):
self._lazy_init()
process_definition = kwds.get("process_definition", None)
if process_definition is None:
raw_object = kwds.get("raw_object", None)
if raw_object is None:
raw_object = self.raw_object(kwds["path"])
process_definition = self.process_definition(raw_object)
raw_process_reference = kwds.get("raw_process_reference", None)
if raw_process_reference is None:
raw_process_reference = self.raw_process_reference(kwds["path"])
process_definition = self.process_definition(raw_process_reference)

make_tool = kwds.get("make_tool", workflow.defaultMakeTool)
tool = make_tool(
tool = load_tool.make_tool(
process_definition.document_loader,
process_definition.avsc_names,
process_definition.process_object,
strict=self._strict,
makeTool=make_tool,
loader=self._document_loader,
avsc_names=self._avsc_names,
process_definition.metadata,
process_definition.raw_process_reference.uri,
make_tool,
{"strict": self._strict},
)
if process_definition.metadata:
metadata = process_definition.metadata
else:
metadata = {
"$namespaces": tool.tool.get("$namespaces", {}),
"$schemas": tool.tool.get("$schemas", [])
}

tool.metadata = metadata
return tool

schema_loader = SchemaLoader()
5 changes: 5 additions & 0 deletions lib/galaxy/tools/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ def lint_tool_source_with(lint_context, tool_source, extra_modules=[]):
linter_modules = submodules.submodules(galaxy.tools.linters)
linter_modules.extend(extra_modules)
for module in linter_modules:
tool_type = tool_source.parse_tool_type() or "default"
lint_tool_types = getattr(module, "lint_tool_types", ["default"])
if not ("*" in lint_tool_types or tool_type in lint_tool_types):
continue

for (name, value) in inspect.getmembers(module):
if callable(value) and name.startswith("lint_"):
# Look at the first argument to the linter to decide
Expand Down
2 changes: 2 additions & 0 deletions lib/galaxy/tools/linters/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
ERROR_ID_MSG = "Tool does not define an id attribute."
VALID_ID_MSG = "Tool defines an id [%s]."

lint_tool_types = ["*"]


def lint_general(tool_source, lint_ctx):
"""Check tool version, name, and id."""
Expand Down
2 changes: 1 addition & 1 deletion lib/galaxy/tools/loader_directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def looks_like_a_cwl_artifact(path, classes=None):

def looks_like_a_tool_cwl(path):
"""Quick check to see if a file looks like it may be a CWL tool."""
return looks_like_a_cwl_artifact(path, classes=["CommandLineTool"])
return looks_like_a_cwl_artifact(path, classes=["CommandLineTool", "ExpressionTool"])


def _find_tool_files(path, recursive, enable_beta_formats):
Expand Down
17 changes: 9 additions & 8 deletions lib/galaxy/tools/parser/cwl.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,23 @@ class CwlToolSource(ToolSource):
def __init__(self, tool_file):
self._cwl_tool_file = tool_file
self._id, _ = os.path.splitext(os.path.basename(tool_file))
self._tool_proxy = tool_proxy(tool_file)
self._tool_proxy = None
self._source_path = tool_file

@property
def tool_proxy(self):
if self._tool_proxy is None:
self._tool_proxy = tool_proxy(self._source_path)
return self._tool_proxy

def parse_tool_type(self):
return 'cwl'

def parse_id(self):
log.warn("TOOL ID is %s" % self._id)
return self._id

def parse_name(self):
return self._id
return self.tool_proxy.label() or self.parse_id()

def parse_command(self):
return "$__cwl_command"
Expand All @@ -60,7 +61,7 @@ def parse_environment_variables(self):
return environment_variables

def parse_help(self):
return ""
return self.tool_proxy.description() or ""

def parse_sanitize(self):
return False
Expand Down Expand Up @@ -90,14 +91,14 @@ def parse_version(self):
return "0.0.1"

def parse_description(self):
return self._tool_proxy.description() or ""
return ""

def parse_input_pages(self):
page_source = CwlPageSource(self._tool_proxy)
page_source = CwlPageSource(self.tool_proxy)
return PagesSource([page_source])

def parse_outputs(self, tool):
output_instances = self._tool_proxy.output_instances()
output_instances = self.tool_proxy.output_instances()
outputs = odict()
output_defs = []
for output_instance in output_instances:
Expand Down Expand Up @@ -130,7 +131,7 @@ def _parse_output(self, tool, output_instance):

def parse_requirements_and_containers(self):
containers = []
docker_identifier = self._tool_proxy.docker_identifier()
docker_identifier = self.tool_proxy.docker_identifier()
if docker_identifier:
containers.append({"type": "docker",
"identifier": docker_identifier})
Expand Down

0 comments on commit 7ab4065

Please sign in to comment.