"""Ensure requirements are matched in best-practice Conda channels."""
from typing import (
Any,
List,
NamedTuple,
Optional,
TYPE_CHECKING,
)
from galaxy.tool_util.lint import Linter
from planemo.conda import (
BEST_PRACTICE_CHANNELS,
best_practice_search,
tool_source_conda_targets,
)
from planemo.linters.util import (
cached_lint_result,
xml_node_from_toolsource,
)
if TYPE_CHECKING:
from galaxy.tool_util.deps.conda_util import CondaTarget
from galaxy.tool_util.lint import LintContext
from galaxy.tool_util.parser.interface import ToolSource
MESSAGE_WARN_NO_REQUIREMENTS = "No valid package requirement tags found to check against Conda."
lint_tool_types = ["*"]
[docs]
class CondaCheck(NamedTuple):
target: "CondaTarget"
best_hit: Optional[Any]
exact: Optional[bool]
def _conda_checks(tool_source: "ToolSource") -> List[CondaCheck]:
def calculate() -> List[CondaCheck]:
checks = []
for conda_target in tool_source_conda_targets(tool_source):
best_hit, exact = best_practice_search(conda_target)
checks.append(CondaCheck(conda_target, best_hit, exact))
return checks
return cached_lint_result(tool_source, "conda_requirements", calculate)
def _target_string(check: CondaCheck) -> str:
target = check.target.package
if check.target.version:
target += f"@{check.target.version}"
return target
[docs]
class CondaRequirementInexact(Linter):
[docs]
@classmethod
def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"):
requirements_node = xml_node_from_toolsource(tool_source, "requirements")
for check in _conda_checks(tool_source):
if check.best_hit and not check.exact:
message = (
f"Requirement [{_target_string(check)}] doesn't exactly match available version "
f"[{check.best_hit['version']}] in best practice Conda channel "
f"[{check.best_hit.get('channel')}]."
)
lint_ctx.warn(message, linter=cls.name(), node=requirements_node)
[docs]
class CondaRequirementMissing(Linter):
[docs]
@classmethod
def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"):
requirements_node = xml_node_from_toolsource(tool_source, "requirements")
for check in _conda_checks(tool_source):
if not check.best_hit:
message = (
f"Requirement [{_target_string(check)}] doesn't match any recipe in a best practice "
f"Conda channel [{BEST_PRACTICE_CHANNELS}]."
)
lint_ctx.warn(message, linter=cls.name(), node=requirements_node)
[docs]
class CondaRequirementValid(Linter):
[docs]
@classmethod
def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"):
requirements_node = xml_node_from_toolsource(tool_source, "requirements")
for check in _conda_checks(tool_source):
if check.best_hit and check.exact:
message = (
f"Requirement [{_target_string(check)}] matches target in best practice "
f"Conda channel [{check.best_hit.get('channel')}]."
)
lint_ctx.info(message, linter=cls.name(), node=requirements_node)
[docs]
class CondaRequirementsMissing(Linter):
[docs]
@classmethod
def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"):
if not _conda_checks(tool_source):
requirements_node = xml_node_from_toolsource(tool_source, "requirements")
lint_ctx.warn(MESSAGE_WARN_NO_REQUIREMENTS, linter=cls.name(), node=requirements_node)