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

Allow unambiguous specification of requirement names #3117

Closed
wants to merge 1 commit 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
8 changes: 8 additions & 0 deletions lib/galaxy/tools/deps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def requirements_to_dependencies(self, requirements, **kwds):
dependency = self.find_dep( name=requirement.name,
version=requirement.version,
type=requirement.type,
specs=requirement.specs,
**kwds )
log.debug(dependency.resolver_msg)
if dependency.dependency_type:
Expand All @@ -118,7 +119,14 @@ def find_dep( self, name, version=None, type='package', **kwds ):
log.debug('Find dependency %s version %s' % (name, version))
index = kwds.get('index', None)
require_exact = kwds.get('exact', False)
specs = kwds.get("specs", [])
for i, resolver in enumerate(self.dependency_resolvers):
if hasattr(resolver, "find_specification"):
spec = resolver.find_specification(specs)
if spec is not None:
name = spec.short_name
version = spec.version or version

if index is not None and i != index:
continue
dependency = resolver.resolve( name, version, type, **kwds )
Expand Down
63 changes: 58 additions & 5 deletions lib/galaxy/tools/deps/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,48 @@ class ToolRequirement( object ):
run (for example, a program, package, or library). Requirements can
optionally assert a specific version.
"""
def __init__( self, name=None, type=None, version=None ):
def __init__( self, name=None, type=None, version=None, specs=[] ):
self.name = name
self.type = type
self.version = version
self.specs = specs

def to_dict( self ):
return dict(name=self.name, type=self.type, version=self.version)
specs = [s.to_dict() for s in self.specs]
return dict(name=self.name, type=self.type, version=self.version, specs=specs)

@staticmethod
def from_dict( dict ):
version = dict.get( "version", None )
name = dict.get("name", None)
type = dict.get("type", None)
return ToolRequirement( name=name, type=type, version=version )
specs = [RequirementSpecification.from_dict(s) for s in dict.get("specs", [])]
return ToolRequirement( name=name, type=type, version=version, specs=specs )


class RequirementSpecification(object):
"""Refine a requirement using a URI."""

def __init__(self, uri, version=None):
self.uri = uri
self.version = version

@property
def specifies_version(self):
return self.version is not None

@property
def short_name(self):
return self.uri.split("/")[-1]

def to_dict(self):
return dict(uri=self.uri, version=self.version)

@staticmethod
def from_dict(dict):
uri = dict.get["uri"]
version = dict.get("version", None)
return RequirementSpecification(uri=uri, version=version)


DEFAULT_CONTAINER_TYPE = "docker"
Expand Down Expand Up @@ -104,10 +132,29 @@ def parse_requirements_from_xml( xml_root ):

requirements = []
for requirement_elem in requirement_elems:
name = xml_text( requirement_elem )
if "name" in requirement_elem.attrib:
name = requirement_elem.get( "name" )
spec_elems = requirement_elem.findall("specification")
specs = map(specification_from_element, spec_elems)
else:
name = xml_text( requirement_elem )
spec_uris_raw = requirement_elem.attrib.get("specification_uris", "")
specs = []
for spec_uri in spec_uris_raw.split(","):
if not spec_uri:
continue
version = None
if "@" in spec_uri:
uri, version = spec_uri.split("@", 1)
else:
uri = spec_uri
uri = uri.strip()
if version:
version = version.strip()
specs.append(RequirementSpecification(uri, version))
type = requirement_elem.get( "type", DEFAULT_REQUIREMENT_TYPE )
version = requirement_elem.get( "version", DEFAULT_REQUIREMENT_VERSION )
requirement = ToolRequirement( name=name, type=type, version=version )
requirement = ToolRequirement( name=name, type=type, version=version, specs=specs )
requirements.append( requirement )

container_elems = []
Expand All @@ -119,6 +166,12 @@ def parse_requirements_from_xml( xml_root ):
return requirements, containers


def specification_from_element(specification_elem):
uri = specification_elem.get("uri", None)
version = specification_elem.get("version", None)
return RequirementSpecification(uri, version)


def container_from_element(container_elem):
identifier = xml_text(container_elem)
type = container_elem.get("type", DEFAULT_CONTAINER_TYPE)
Expand Down
28 changes: 28 additions & 0 deletions lib/galaxy/tools/deps/resolvers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,34 @@ def _to_requirement(self, name, version=None):
return ToolRequirement(name=name, type="package", version=version)


class SpecificationAwareDependencyResolver:
"""Mix this into a :class:`DependencyResolver` to implement URI specification matching.

Allows adapting generic requirements to more specific URIs - to tailor name
or version to specified resolution system.
"""
__metaclass__ = ABCMeta

@abstractmethod
def find_specification(self, specs):
"""Find closest matching specification for discovered resolver."""


class SpecificationPatternDependencyResolver:
"""Implement the :class:`SpecificationAwareDependencyResolver` with a regex pattern."""

@abstractproperty
def _specification_pattern(self):
"""Pattern of URI to match against."""

def find_specification(self, specs):
pattern = self._specification_pattern
for spec in specs:
if pattern.match(spec.uri):
return spec
return None


class InstallableDependencyResolver:
""" Mix this into a ``DependencyResolver`` and implement to indicate
the dependency resolver can attempt to install new dependencies.
Expand Down
5 changes: 4 additions & 1 deletion lib/galaxy/tools/deps/resolvers/conda.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import logging
import os
import re

import galaxy.tools.deps.installable

Expand All @@ -25,6 +26,7 @@
InstallableDependencyResolver,
ListableDependencyResolver,
NullDependency,
SpecificationPatternDependencyResolver,
)


Expand All @@ -35,9 +37,10 @@
log = logging.getLogger(__name__)


class CondaDependencyResolver(DependencyResolver, ListableDependencyResolver, InstallableDependencyResolver):
class CondaDependencyResolver(DependencyResolver, ListableDependencyResolver, InstallableDependencyResolver, SpecificationPatternDependencyResolver):
dict_collection_visible_keys = DependencyResolver.dict_collection_visible_keys + ['conda_prefix', 'versionless', 'ensure_channels', 'auto_install']
resolver_type = "conda"
_specification_pattern = re.compile(r"https\:\/\/anaconda.org\/\w+\/\w+")

def __init__(self, dependency_manager, **kwds):
self.versionless = _string_as_bool(kwds.get('versionless', 'false'))
Expand Down
39 changes: 24 additions & 15 deletions lib/galaxy/tools/xsd/galaxy.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ complete descriptions of the runtime of a tool.
</xs:sequence>
</xs:complexType>

<xs:complexType name="Requirement">
<xs:complexType name="Requirement" mixed="true">
<xs:annotation>
<xs:documentation xml:lang="en"><![CDATA[

Expand Down Expand Up @@ -274,20 +274,29 @@ resolver.

]]></xs:documentation>
</xs:annotation>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="type" type="RequirementType" use="required">
<xs:annotation>
<xs:documentation xml:lang="en"> This value defines the which type of the 3rd party module required by this tool. </xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="version" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en"> For package type requirements this value defines a specific version of the tool dependency. </xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:simpleContent>
<xs:sequence>
<xs:element name="specification" minOccurs="0" maxOccurs="unbounded" type="xs:anyType" />
</xs:sequence>
<xs:attribute name="type" type="RequirementType" use="required">
<xs:annotation>
<xs:documentation xml:lang="en"> This value defines the which type of the 3rd party module required by this tool. </xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="version" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en"> For package type requirements this value defines a specific version of the tool dependency. </xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="name" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">Name of requirement (if body of ``requirement`` element contains specification URIs).</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="specification_uris" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">URIs and versions of requirement specification.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="Container">
<xs:annotation>
Expand Down
18 changes: 18 additions & 0 deletions test/functional/tools/requirement_specification_1.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<tool id="requirement_specification_1" name="requirement_specification_1" version="0.1.0" profile="16.10">
<command><![CDATA[
blastn -help > $out_file1 ;
echo "Moo" >> $out_file1 ;
]]></command>
<requirements>
<requirement type="package" version="2.2.31" name="blast+">
<specification uri="https://anaconda.org/bioconda/blast" />
<specification uri="https://packages.debian.org/sid/ncbi-blast+" version="2.2.31-3" />
</requirement>
</requirements>
<inputs>
<param name="input1" type="data" optional="true" />
</inputs>
<outputs>
<data name="out_file1" format="txt" />
</outputs>
</tool>
16 changes: 16 additions & 0 deletions test/functional/tools/requirement_specification_2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<tool id="requirement_specification_2" name="requirement_specification_2" version="0.1.0" profile="16.01">
<command><![CDATA[
blastn -help > $out_file1 ;
echo "Moo" >> $out_file1 ;
]]></command>
<requirements>
<!-- Demonstrate backward-compatible-ish specification_uri syntax. -->
<requirement type="package" version="2.2" specification_uris="https://anaconda.org/bioconda/[email protected],https://packages.debian.org/jessie/[email protected]">blast+</requirement>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So theoretically this wouldn't lint in older Galaxies, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Galaxy doesn't lint stuff - planemo does. Older versions of Galaxy would load and use this requirement I think but to pass linting would require a Planemo upgrade post merge (as with all XSD changes now).

Copy link
Member

@mvdbeek mvdbeek Nov 3, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does miss the name attribute though, right?
nevermind, had the wrong syntax in my head

Copy link
Member

@martenson martenson Nov 3, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which is fine with me. IOW specification_uris param is unknown and ignored by older Galaxies.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

</requirements>
<inputs>
<param name="input1" type="data" optional="true" />
</inputs>
<outputs>
<data name="out_file1" format="txt" />
</outputs>
</tool>
2 changes: 2 additions & 0 deletions test/functional/tools/samples_tool_conf.xml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@
<tool file="for_workflows/create_input_collection.xml" />

<tool file="mulled_example_multi_1.xml" />
<tool file="requirement_specification_1.xml" />
<tool file="requirement_specification_2.xml" />

<tool file="simple_constructs.yml" />

Expand Down