Warning
This document is for an old release of Galaxy. You can alternatively view this page in the latest release if it exists or view the top of the latest release's documentation.
Source code for galaxy.tool_util.linters.outputs
"""This module contains a linting functions for tool outputs."""
from typing import TYPE_CHECKING
from packaging.version import Version
from galaxy.util import (
etree,
string_as_bool,
)
from ._util import is_valid_cheetah_placeholder
from ..parser.output_collection_def import NAMED_PATTERNS
if TYPE_CHECKING:
from galaxy.tool_util.lint import LintContext
from galaxy.tool_util.parser import ToolSource
[docs]def lint_output(tool_source: "ToolSource", lint_ctx: "LintContext"):
"""Check output elements, ensure there is at least one and check attributes."""
tool_xml = getattr(tool_source, "xml_tree", None)
if tool_xml is None:
return
profile = tool_source.parse_profile()
outputs = tool_xml.findall("./outputs")
# determine node to report for general problems with outputs
tool_node = tool_xml.find("./outputs")
if tool_node is None:
tool_node = tool_xml.getroot()
if len(outputs) == 0:
lint_ctx.warn("Tool contains no outputs section, most tools should produce outputs.", node=tool_node)
return
if len(outputs) > 1:
lint_ctx.warn("Tool contains multiple output sections, behavior undefined.", node=outputs[1])
num_outputs = 0
labels = set()
names = set()
for output in list(outputs[0]):
if output.tag is etree.Comment:
continue
if output.tag not in ["data", "collection"]:
lint_ctx.warn(f"Unknown element found in outputs [{output.tag}]", node=output)
continue
num_outputs += 1
if "name" not in output.attrib:
lint_ctx.warn("Tool output doesn't define a name - this is likely a problem.", node=output)
# TODO make this an error if there is no discover_datasets / from_work_dir (is this then still a problem)
elif not is_valid_cheetah_placeholder(output.attrib["name"]):
lint_ctx.warn(
f'Tool output name [{output.attrib["name"]}] is not a valid Cheetah placeholder.', node=output
)
name = output.attrib.get("name")
if name is not None:
if name in names:
lint_ctx.error(f"Tool output [{name}] has duplicated name", node=output)
names.add(name)
label = output.attrib.get("label", "${tool.name} on ${on_string}")
if label in labels:
filter_node = output.find(".//filter")
if filter_node is not None:
lint_ctx.warn(
f"Tool output [{name}] uses duplicated label '{label}', double check if filters imply disjoint cases",
node=output,
)
else:
lint_ctx.warn(f"Tool output [{name}] uses duplicated label '{label}'", node=output)
labels.add(label)
format_set = False
if __check_format(output, lint_ctx, profile):
format_set = True
if output.tag == "data":
if "auto_format" in output.attrib and output.attrib["auto_format"]:
format_set = True
elif output.tag == "collection":
if "type" not in output.attrib:
lint_ctx.warn("Collection output with undefined 'type' found.", node=output)
if "structured_like" in output.attrib and "inherit_format" in output.attrib:
format_set = True
for sub in output:
if __check_pattern(sub):
format_set = True
elif __check_format(sub, lint_ctx, profile, allow_ext=True):
format_set = True
if not format_set:
lint_ctx.warn(
f"Tool {output.tag} output {output.attrib.get('name', 'with missing name')} doesn't define an output format.",
node=output,
)
# TODO: check for different labels in case of multiple outputs
lint_ctx.info(f"{num_outputs} outputs found.", node=outputs[0])
def __check_format(node, lint_ctx, profile: str, allow_ext=False):
"""
check if format/ext/format_source attribute is set in a given node
issue a warning if the value is input
return true (node defines format/ext) / false (else)
"""
if "format_source" in node.attrib and ("ext" in node.attrib or "format" in node.attrib):
lint_ctx.warn(
f"Tool {node.tag} output '{node.attrib.get('name', 'with missing name')}' should use either format_source or format/ext",
node=node,
)
if "format_source" in node.attrib:
return True
if node.find(".//action[@type='format']") is not None:
return True
# if allowed (e.g. for discover_datasets), ext takes precedence over format
fmt = None
if allow_ext:
fmt = node.attrib.get("ext")
if fmt is None:
fmt = node.attrib.get("format")
if fmt == "input":
message = f"Using format='input' on {node.tag} is deprecated. Use the format_source attribute."
if Version(str(profile)) <= Version("16.01"):
lint_ctx.warn(message, node=node)
else:
lint_ctx.error(message, node=node)
return fmt is not None
def __check_pattern(node):
"""
check if
- pattern attribute is set and defines the extension or
- from_tool_provided_metadata is true
"""
if node.tag != "discover_datasets":
return False
if "from_tool_provided_metadata" in node.attrib and string_as_bool(
node.attrib.get("from_tool_provided_metadata", "false")
):
return True
if "pattern" not in node.attrib:
return False
pattern = node.attrib["pattern"]
regex_pattern = NAMED_PATTERNS.get(pattern, pattern)
# TODO error on wrong pattern or non-regexp
if "(?P<ext>" in regex_pattern:
return True