Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove galaxy tool util dependency #2195

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

### Linting

### General

- Remove the dependency on `galaxy-tool-util` by copying code directly into `nf_core.modules.mulled`.

### Modules

- Add an `--empty-template` option to create a module without TODO statements or examples ([#2175](https://github.com/nf-core/tools/pull/2175) & [#2177](https://github.com/nf-core/tools/pull/2177))
Expand Down
126 changes: 120 additions & 6 deletions nf_core/modules/mulled.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,40 @@
"""Generate the name of a BioContainers mulled image version 2."""


from __future__ import annotations

import hashlib
import logging
import re
from typing import Iterable, List, Tuple
from typing import Iterable, List, NamedTuple, Optional, Tuple

import requests
from galaxy.tool_util.deps.mulled.util import build_target, v2_image_name
from packaging.version import InvalidVersion, Version

log = logging.getLogger(__name__)


# Copied from galaxy.tool_util.deps.mulled.util
class Target(NamedTuple):
package_name: str
version: Optional[str]
build: Optional[str]
package: Optional[str]

@classmethod
def create(
cls, package_name: str, version: Optional[str] = None, build: Optional[str] = None, tag: Optional[str] = None
) -> Target:
"""Use supplied arguments to build a :class:`Target` object."""
if tag is not None:
assert version is None
assert build is None
version, build = tag.rsplit("--", 1)

# conda package and quay image names are lowercase
return Target(package_name=package_name.lower(), version=version, build=build, package=package_name)


class MulledImageNameGenerator:
"""
Define a service class for generating BioContainers version 2 mulled image names.
Expand All @@ -31,7 +54,7 @@ def parse_targets(cls, specifications: Iterable[str]) -> List[Tuple[str, str]]:
specifications: An iterable of strings that contain tools and their versions.

"""
result = []
result: List[Tuple[str, str]] = []
for spec in specifications:
try:
tool, version = cls._split_pattern.split(spec, maxsplit=1)
Expand All @@ -47,16 +70,18 @@ def parse_targets(cls, specifications: Iterable[str]) -> List[Tuple[str, str]]:
return result

@classmethod
def generate_image_name(cls, targets: Iterable[Tuple[str, str]], build_number: int = 0) -> str:
def generate_image_name(cls, targets: List[Tuple[str, str]], build_number: int = 0) -> str:
"""
Generate the name of a BioContainers mulled image version 2.

Args:
targets: One or more tool, version pairs of the multi-tool container image.
targets: One or more target packages of the multi-tool container image.
build_number: The build number for this image. This is an incremental value that starts at zero.

"""
return v2_image_name([build_target(name, version) for name, version in targets], image_build=str(build_number))
return cls._v2_image_name(
[Target.create(name, version) for name, version in targets], image_build=str(build_number)
)

@classmethod
def image_exists(cls, image_name: str) -> bool:
Expand All @@ -70,3 +95,92 @@ def image_exists(cls, image_name: str) -> bool:
else:
log.error(f"Was not able to find [link={quay_url}]docker image[/link] on quay.io")
return False

# Copied from galaxy.tool_util.deps.mulled.util
@classmethod
def _v2_image_name(
cls, targets: List[Target], image_build: Optional[str] = None, name_override: Optional[str] = None
) -> str:
"""
Generate mulled hash version 2 container identifier for supplied arguments.

If a single target is specified, simply use the supplied name and version as
the repository name and tag respectively. If multiple targets are supplied,
hash the package names as the repository name and hash the package versions (if set)
as the tag.

>>> single_targets = [build_target("samtools", version="1.3.1")]
>>> v2_image_name(single_targets)
'samtools:1.3.1'
>>> single_targets = [build_target("samtools", version="1.3.1", build="py_1")]
>>> v2_image_name(single_targets)
'samtools:1.3.1--py_1'
>>> single_targets = [build_target("samtools", version="1.3.1")]
>>> v2_image_name(single_targets, image_build="0")
'samtools:1.3.1'
>>> single_targets = [build_target("samtools", version="1.3.1", build="py_1")]
>>> v2_image_name(single_targets, image_build="0")
'samtools:1.3.1--py_1'
>>> multi_targets = [build_target("samtools", version="1.3.1"), build_target("bwa", version="0.7.13")]
>>> v2_image_name(multi_targets)
'mulled-v2-fe8faa35dbf6dc65a0f7f5d4ea12e31a79f73e40:4d0535c94ef45be8459f429561f0894c3fe0ebcf'
>>> multi_targets_on_versionless = [build_target("samtools", version="1.3.1"), build_target("bwa")]
>>> v2_image_name(multi_targets_on_versionless)
'mulled-v2-fe8faa35dbf6dc65a0f7f5d4ea12e31a79f73e40:b0c847e4fb89c343b04036e33b2daa19c4152cf5'
>>> multi_targets_versionless = [build_target("samtools"), build_target("bwa")]
>>> v2_image_name(multi_targets_versionless)
'mulled-v2-fe8faa35dbf6dc65a0f7f5d4ea12e31a79f73e40'
"""
if name_override is not None:
print(
"WARNING: Overriding mulled image name, auto-detection of 'mulled' package attributes will fail to detect result."
)
return name_override

targets = list(targets)
if len(targets) == 1:
return cls._simple_image_name(targets, image_build=image_build)
else:
targets_order = sorted(targets, key=lambda t: t.package_name)
package_name_buffer = "\n".join(map(lambda t: t.package_name, targets_order))
package_hash = hashlib.sha1()
package_hash.update(package_name_buffer.encode())

versions = map(lambda t: t.version, targets_order)
if any(versions):
# Only hash versions if at least one package has versions...
version_name_buffer = "\n".join(map(lambda t: t.version or "null", targets_order))
version_hash = hashlib.sha1()
version_hash.update(version_name_buffer.encode())
version_hash_str = version_hash.hexdigest()
else:
version_hash_str = ""

if not image_build:
build_suffix = ""
elif version_hash_str:
# tagged verson is <version_hash>-<build>
build_suffix = f"-{image_build}"
else:
# tagged version is simply the build
build_suffix = image_build
suffix = ""
if version_hash_str or build_suffix:
suffix = f":{version_hash_str}{build_suffix}"
return f"mulled-v2-{package_hash.hexdigest()}{suffix}"

# Copied from galaxy.tool_util.deps.mulled.util
@classmethod
def _simple_image_name(cls, targets: List[Target], image_build: Optional[str] = None) -> str:
target = targets[0]
suffix = ""
if target.version is not None:
build = target.build
if build is None and image_build is not None and image_build != "0":
# Special case image_build == "0", which has been built without a suffix
print("WARNING: Hard-coding image build instead of using Conda build - this is not recommended.")
build = image_build
suffix += f":{target.version}"
if build is not None:
suffix += f"--{build}"
return f"{target.package_name}{suffix}"
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
click
filetype
galaxy-tool-util
GitPython
jinja2
jsonschema>=3.0
Expand Down