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.general

"""This module contains linting functions for general aspects of the tool."""

import re
from typing import (
    Tuple,
    TYPE_CHECKING,
)

from packaging.version import Version

from galaxy.tool_util.lint import Linter
from galaxy.tool_util.version import (
    LegacyVersion,
    parse_version,
)

if TYPE_CHECKING:
    from galaxy.tool_util.lint import LintContext
    from galaxy.tool_util.parser.interface import ToolSource
    from galaxy.util.etree import (
        Element,
        ElementTree,
    )

PROFILE_PATTERN = re.compile(r"^[12]\d\.\d{1,2}$")


lint_tool_types = ["*"]


def _tool_xml_and_root(tool_source: "ToolSource") -> Tuple["ElementTree", "Element"]:
    tool_xml = getattr(tool_source, "xml_tree", None)
    if tool_xml:
        tool_node = tool_xml.getroot()
    else:
        tool_node = None
    return tool_xml, tool_node


[docs]class ToolVersionMissing(Linter): """ Tools must have a version """
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): tool_xml, tool_node = _tool_xml_and_root(tool_source) version = tool_source.parse_version() or "" if not version: lint_ctx.error("Tool version is missing or empty.", linter=cls.name(), node=tool_node)
[docs]class ToolVersionPEP404(Linter): """ Tools should have a PEP404 compliant version. """
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): tool_xml, tool_node = _tool_xml_and_root(tool_source) version = tool_source.parse_version() or "" parsed_version = parse_version(version) if version and isinstance(parsed_version, LegacyVersion): lint_ctx.warn(f"Tool version [{version}] is not compliant with PEP 440.", linter=cls.name(), node=tool_node)
[docs]class ToolVersionWhitespace(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): tool_xml, tool_node = _tool_xml_and_root(tool_source) version = tool_source.parse_version() or "" if version != version.strip(): lint_ctx.warn( f"Tool version is pre/suffixed by whitespace, this may cause errors: [{version}].", linter=cls.name(), node=tool_node, )
[docs]class ToolVersionValid(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): tool_xml, tool_node = _tool_xml_and_root(tool_source) version = tool_source.parse_version() or "" parsed_version = parse_version(version) if version and not isinstance(parsed_version, LegacyVersion) and version == version.strip(): lint_ctx.valid(f"Tool defines a version [{version}].", linter=cls.name(), node=tool_node)
[docs]class ToolNameMissing(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): _, tool_node = _tool_xml_and_root(tool_source) name = tool_source.parse_name() if not name: lint_ctx.error("Tool name is missing or empty.", linter=cls.name(), node=tool_node)
[docs]class ToolNameWhitespace(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): _, tool_node = _tool_xml_and_root(tool_source) name = tool_source.parse_name() if name and name != name.strip(): lint_ctx.warn( f"Tool name is pre/suffixed by whitespace, this may cause errors: [{name}].", linter=cls.name(), node=tool_node, )
[docs]class ToolNameValid(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): _, tool_node = _tool_xml_and_root(tool_source) name = tool_source.parse_name() if name and name == name.strip(): lint_ctx.valid(f"Tool defines a name [{name}].", linter=cls.name(), node=tool_node)
[docs]class ToolIDMissing(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): _, tool_node = _tool_xml_and_root(tool_source) tool_id = tool_source.parse_id() if not tool_id: lint_ctx.error("Tool does not define an id attribute.", linter=cls.name(), node=tool_node)
[docs]class ToolIDWhitespace(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): _, tool_node = _tool_xml_and_root(tool_source) tool_id = tool_source.parse_id() if tool_id and re.search(r"\s", tool_id): lint_ctx.warn( f"Tool ID contains whitespace - this is discouraged: [{tool_id}].", linter=cls.name(), node=tool_node )
[docs]class ToolIDValid(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): _, tool_node = _tool_xml_and_root(tool_source) tool_id = tool_source.parse_id() if tool_id and not re.search(r"\s", tool_id): lint_ctx.valid(f"Tool defines an id [{tool_id}].", linter=cls.name(), node=tool_node)
[docs]class ToolProfileInvalid(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): _, tool_node = _tool_xml_and_root(tool_source) profile = tool_source.parse_profile() profile_valid = PROFILE_PATTERN.match(profile) is not None if not profile_valid: lint_ctx.error(f"Tool specifies an invalid profile version [{profile}].", linter=cls.name(), node=tool_node)
[docs]class ToolProfileLegacy(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): _, tool_node = _tool_xml_and_root(tool_source) profile = tool_source.parse_profile() profile_valid = PROFILE_PATTERN.match(profile) is not None if profile_valid and Version(profile) == Version("16.01"): lint_ctx.valid("Tool targets 16.01 Galaxy profile.", linter=cls.name(), node=tool_node)
[docs]class ToolProfileValid(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): _, tool_node = _tool_xml_and_root(tool_source) profile = tool_source.parse_profile() profile_valid = PROFILE_PATTERN.match(profile) is not None if profile_valid and Version(profile) != Version("16.01"): lint_ctx.valid(f"Tool specifies profile version [{profile}].", linter=cls.name(), node=tool_node)
[docs]class RequirementNameMissing(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): _, tool_node = _tool_xml_and_root(tool_source) requirements, containers, resource_requirements = tool_source.parse_requirements_and_containers() for r in requirements: if r.type != "package": continue if not r.name: lint_ctx.error("Requirement without name found", linter=cls.name(), node=tool_node)
[docs]class RequirementVersionMissing(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): _, tool_node = _tool_xml_and_root(tool_source) requirements, containers, resource_requirements = tool_source.parse_requirements_and_containers() for r in requirements: if r.type != "package": continue if not r.version: lint_ctx.warn(f"Requirement {r.name} defines no version", linter=cls.name(), node=tool_node)
[docs]class RequirementVersionWhitespace(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): _, tool_node = _tool_xml_and_root(tool_source) requirements, containers, resource_requirements = tool_source.parse_requirements_and_containers() for r in requirements: if r.type != "package": continue if r.version and r.version != r.version.strip(): lint_ctx.warn( f"Requirement version contains whitespace, this may cause errors: [{r.version}].", linter=cls.name(), node=tool_node, )
[docs]class ResourceRequirementExpression(Linter):
[docs] @classmethod def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): _, tool_node = _tool_xml_and_root(tool_source) requirements, containers, resource_requirements = tool_source.parse_requirements_and_containers() for rr in resource_requirements: if rr.runtime_required: lint_ctx.warn( "Expressions in resource requirement not supported yet", linter=cls.name(), node=tool_node )