Skip to content

Commit 5012549

Browse files
committed
[WIP] Implement external Galaxy engine.
This work is focused on "test" and "run" commands for Galaxy tools and workflows, though it should enable other combinations of artifacts and commands such as serving workflows and various CWL artifact operations against that fork of Galaxy. The tool piece of this requires an unreleased version of galaxy-lib (galaxyproject/galaxy-lib#91) and unmerged Galaxy modifications to expose a APIs for external tool testing (galaxyproject/galaxy#5545). Workflows are oddly enough likely closer to working with this WIP. Implements galaxyproject#592. Implements galaxyproject#508.
1 parent 99d5847 commit 5012549

7 files changed

Lines changed: 252 additions & 89 deletions

File tree

planemo/engine/factory.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,31 @@
33
import contextlib
44

55
from .cwltool import CwlToolEngine
6-
from .galaxy import DockerizedGalaxyEngine
7-
from .galaxy import GalaxyEngine
6+
from .galaxy import (
7+
DockerizedManagedGalaxyEngine,
8+
ExternalGalaxyEngine,
9+
LocalManagedGalaxyEngine,
10+
)
11+
812

913
UNKNOWN_ENGINE_TYPE_MESSAGE = "Unknown engine type specified [%s]."
1014

1115

1216
def is_galaxy_engine(**kwds):
1317
"""Return True iff the engine configured is :class:`GalaxyEngine`."""
1418
engine_type_str = kwds.get("engine", "galaxy")
15-
return engine_type_str in ["galaxy", "docker_galaxy"]
19+
return engine_type_str in ["galaxy", "docker_galaxy", "external_galaxy"]
1620

1721

1822
def build_engine(ctx, **kwds):
1923
"""Build an engine from the supplied planemo configuration."""
2024
engine_type_str = kwds.get("engine", "galaxy")
2125
if engine_type_str == "galaxy":
22-
engine_type = GalaxyEngine
26+
engine_type = LocalManagedGalaxyEngine
2327
elif engine_type_str == "docker_galaxy":
24-
engine_type = DockerizedGalaxyEngine
28+
engine_type = DockerizedManagedGalaxyEngine
29+
elif engine_type_str == "external_galaxy":
30+
engine_type = ExternalGalaxyEngine
2531
elif engine_type_str == "cwltool":
2632
engine_type = CwlToolEngine
2733
else:

planemo/engine/galaxy.py

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
"""Module contianing the :class:`GalaxyEngine` implementation of :class:`Engine`."""
22

3+
import abc
34
import contextlib
45

6+
from galaxy.tools.verify.interactor import GalaxyInteractorApi, verify_tool
7+
58
from planemo.galaxy.activity import execute
9+
from planemo.galaxy.config import external_galaxy_config
610
from planemo.galaxy.serve import serve_daemon
711
from planemo.runnable import RunnableType
812
from .interface import BaseEngine
913

1014

1115
class GalaxyEngine(BaseEngine):
12-
"""An :class:`Engine` implementation backed by Galaxy.
16+
"""An :class:`Engine` implementation backed by a managed Galaxy.
1317
1418
More information on Galaxy can be found at http://galaxyproject.org/.
1519
"""
1620

21+
__metaclass__ = abc.ABCMeta
22+
1723
handled_runnable_types = [
1824
RunnableType.cwl_tool,
1925
RunnableType.cwl_workflow,
@@ -24,14 +30,63 @@ class GalaxyEngine(BaseEngine):
2430
def _run(self, runnable, job_path):
2531
"""Run CWL job in Galaxy."""
2632
self._ctx.vlog("Serving artifact [%s] with Galaxy." % (runnable,))
27-
with self.serve_runnables([runnable]) as config:
33+
with self.ensure_runnables_served([runnable]) as config:
2834
self._ctx.vlog("Running job path [%s]" % job_path)
2935
run_response = execute(self._ctx, config, runnable, job_path, **self._kwds)
3036

3137
return run_response
3238

39+
@abc.abstractmethod
40+
def ensure_runnables_served(self, runnables):
41+
"""Use a context manager and describe Galaxy instance with runnables being served."""
42+
43+
def _run_test_case(self, test_case):
44+
if hasattr(test_case, "job_path"):
45+
# Simple file-based job path.
46+
super(GalaxyEngine, self)._run_test_case(test_case)
47+
else:
48+
with self.ensure_runnables_served([test_case.runnable]) as config:
49+
galaxy_interactor_kwds = {
50+
"galaxy_url": config.galaxy_url,
51+
"master_api_key": config.master_api_key,
52+
"api_key": config.user_api_key,
53+
"keep_outputs_dir": "", # TODO: this...
54+
}
55+
tool_id = test_case.tool_id
56+
test_index = test_case.test_index
57+
tool_version = test_case.tool_version
58+
galaxy_interactor = GalaxyInteractorApi(**galaxy_interactor_kwds)
59+
60+
test_results = []
61+
62+
def _register_job_data(job_data):
63+
test_results.append({
64+
'id': tool_id + "-" + str(test_index),
65+
'has_data': True,
66+
'data': job_data,
67+
})
68+
69+
try:
70+
verify_tool(
71+
tool_id, galaxy_interactor, test_index=test_index, tool_version=tool_version, register_job_data=_register_job_data
72+
)
73+
finally:
74+
pass
75+
76+
# TODO: record pass vs fail
77+
# TODO: record timing
78+
# TODO: return run_response compatible interface maybe?
79+
return test_results[0]
80+
81+
82+
class LocalManagedGalaxyEngine(BaseEngine):
83+
"""An :class:`Engine` implementation backed by a managed Galaxy.
84+
85+
More information on Galaxy can be found at http://galaxyproject.org/.
86+
"""
87+
3388
@contextlib.contextmanager
34-
def serve_runnables(self, runnables):
89+
def ensure_runnables_served(self, runnables):
3590
# TODO: define an interface for this - not everything in config would make sense for a
3691
# pre-existing Galaxy interface.
3792
with serve_daemon(self._ctx, runnables, **self._serve_kwds()) as config:
@@ -41,7 +96,7 @@ def _serve_kwds(self):
4196
return self._kwds.copy()
4297

4398

44-
class DockerizedGalaxyEngine(GalaxyEngine):
99+
class DockerizedManagedGalaxyEngine(LocalManagedGalaxyEngine):
45100
"""An :class:`Engine` implementation backed by Galaxy running in Docker.
46101
47102
More information on Galaxy can be found at http://galaxyproject.org/.
@@ -53,7 +108,20 @@ def _serve_kwds(self):
53108
return serve_kwds
54109

55110

111+
class ExternalGalaxyEngine(GalaxyEngine):
112+
"""An :class:`Engine` implementation backed by an external Galaxy instance.
113+
"""
114+
115+
@contextlib.contextmanager
116+
def ensure_runnables_served(self, runnables):
117+
# TODO: ensure tools are available
118+
with external_galaxy_config(self._ctx, runnables, self._kwds()) as config:
119+
config.install_workflows()
120+
yield config
121+
122+
56123
__all__ = (
57-
"GalaxyEngine",
58-
"DockerizedGalaxyEngine",
124+
"DockerizedManagedGalaxyEngine",
125+
"ExternalGalaxyEngine",
126+
"LocalManagedGalaxyEngine",
59127
)

planemo/engine/interface.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -95,26 +95,7 @@ def _collect_test_results(self, test_cases):
9595
self._ctx.vlog(
9696
"Running tests %s" % test_case
9797
)
98-
runnable = test_case.runnable
99-
job_path = test_case.job_path
100-
tmp_path = None
101-
if job_path is None:
102-
job = test_case.job
103-
f = tempfile.NamedTemporaryFile(
104-
dir=test_case.tests_directory,
105-
suffix=".json",
106-
prefix="plnmotmptestjob",
107-
delete=False,
108-
)
109-
tmp_path = f.name
110-
job_path = tmp_path
111-
json.dump(job, f)
112-
f.close()
113-
try:
114-
run_response = self._run(runnable, job_path)
115-
finally:
116-
if tmp_path:
117-
os.remove(tmp_path)
98+
run_response = self._run_test_case(test_case)
11899
self._ctx.vlog(
119100
"Test case [%s] resulted in run response [%s]",
120101
test_case,
@@ -123,6 +104,29 @@ def _collect_test_results(self, test_cases):
123104
test_results.append((test_case, run_response))
124105
return test_results
125106

107+
def _run_test_case(self, test_case):
108+
runnable = test_case.runnable
109+
job_path = test_case.job_path
110+
tmp_path = None
111+
if job_path is None:
112+
job = test_case.job
113+
f = tempfile.NamedTemporaryFile(
114+
dir=test_case.tests_directory,
115+
suffix=".json",
116+
prefix="plnmotmptestjob",
117+
delete=False,
118+
)
119+
tmp_path = f.name
120+
job_path = tmp_path
121+
json.dump(job, f)
122+
f.close()
123+
try:
124+
run_response = self._run(runnable, job_path)
125+
finally:
126+
if tmp_path:
127+
os.remove(tmp_path)
128+
return run_response
129+
126130
def _process_test_results(self, test_results):
127131
for (test_case, run_response) in test_results:
128132
pass

planemo/galaxy/api.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@
77
DEFAULT_MASTER_API_KEY = "test_key"
88

99

10-
def gi(port, key=None):
10+
def gi(port=None, url=None, key=None):
1111
"""Return a bioblend ``GalaxyInstance`` for Galaxy on this port."""
1212
ensure_module()
1313
if key is None:
1414
key = DEFAULT_MASTER_API_KEY
15+
if port is None:
16+
url = url
17+
else:
18+
url = "http://localhost:%d" % int(port)
19+
1520
return galaxy.GalaxyInstance(
16-
url="http://localhost:%d" % int(port),
21+
url=url,
1722
key=key
1823
)
1924

planemo/galaxy/config.py

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,12 @@ class = StreamHandler
246246
@contextlib.contextmanager
247247
def galaxy_config(ctx, runnables, **kwds):
248248
"""Set up a ``GalaxyConfig`` in an auto-cleaned context."""
249-
dockerize = kwds.get("dockerize", False)
250-
c = docker_galaxy_config if dockerize else local_galaxy_config
249+
c = local_galaxy_config
250+
if kwds.get("dockerize", False):
251+
c = docker_galaxy_config
252+
elif kwds.get("external", False):
253+
c = external_galaxy_config
254+
251255
with c(ctx, runnables, **kwds) as config:
252256
yield config
253257

@@ -553,6 +557,16 @@ def _shared_galaxy_properties(config_directory, kwds, for_tests):
553557
return properties
554558

555559

560+
@contextlib.contextmanager
561+
def external_galaxy_config(ctx, runnables, for_tests=False, **kwds):
562+
yield BaseGalaxyConfig(
563+
galaxy_url=kwds.get("external_galaxy_url", None),
564+
master_api_key=_get_master_api_key(kwds),
565+
user_api_key=kwds.get("user_api_key", None),
566+
runnables=runnables,
567+
)
568+
569+
556570
def _get_master_api_key(kwds):
557571
master_api_key = kwds.get("master_api_key", DEFAULT_MASTER_API_KEY)
558572
return master_api_key
@@ -647,59 +661,24 @@ def startup_command(self, ctx, **kwds):
647661
def log_contents(self):
648662
"""Retrieve text of log for running Galaxy instance."""
649663

650-
@abc.abstractproperty
651-
def gi(self):
652-
"""Return an admin bioblend Galaxy instance for API interactions."""
653-
654-
@abc.abstractproperty
655-
def user_gi(self):
656-
"""Return a user-backed bioblend Galaxy instance for API interactions."""
657-
658-
@abc.abstractmethod
659-
def install_repo(self, *args, **kwds):
660-
"""Install specified tool shed repository."""
661-
662-
@abc.abstractproperty
663-
def tool_shed_client(self):
664-
"""Return a admin bioblend tool shed client."""
665-
666-
@abc.abstractmethod
667-
def wait_for_all_installed(self):
668-
"""Wait for all queued up repositories installs to complete."""
669-
670-
@abc.abstractmethod
671-
def install_workflows(self):
672-
"""Install all workflows configured with these planemo arguments."""
673-
674-
@abc.abstractmethod
675-
def workflow_id(self, path):
676-
"""Get installed workflow API ID for input path."""
677-
678664
@abc.abstractmethod
679665
def cleanup(self):
680666
"""Cleanup allocated resources to run this instance."""
681667

682668

683-
class BaseGalaxyConfig(GalaxyConfig):
669+
class BaseGalaxyConfig(GalaxyInterface):
684670

685671
def __init__(
686672
self,
687-
config_directory,
688-
env,
689-
test_data_dir,
690-
port,
691-
server_name,
673+
galaxy_url,
692674
master_api_key,
675+
user_api_key,
693676
runnables,
694677
):
695-
self.config_directory = config_directory
696-
self.env = env
697-
self.test_data_dir = test_data_dir
698-
self.port = port
699-
self.server_name = server_name
678+
self.galaxy_url = galaxy_url
700679
self.master_api_key = master_api_key
680+
self._user_api_key = user_api_key
701681
self.runnables = runnables
702-
self._user_api_key = None
703682
self._workflow_ids = {}
704683

705684
@property
@@ -710,6 +689,7 @@ def gi(self):
710689
def user_gi(self):
711690
# TODO: thread-safe
712691
if self._user_api_key is None:
692+
# TODO: respect --galaxy_email - seems like a real bug
713693
self._user_api_key = user_api_key(self.gi)
714694

715695
return self._gi_for_key(self._user_api_key)
@@ -761,6 +741,31 @@ def workflow_id(self, path):
761741
return self._workflow_ids[path]
762742

763743

744+
class BaseManagedGalaxyConfig(GalaxyConfig):
745+
746+
def __init__(
747+
self,
748+
config_directory,
749+
env,
750+
test_data_dir,
751+
port,
752+
server_name,
753+
master_api_key,
754+
runnables,
755+
):
756+
galaxy_url = "http://localhost:%d" % port
757+
super(BaseManagedGalaxyConfig, self).__(
758+
galaxy_url=galaxy_url,
759+
master_api_key=master_api_key,
760+
user_api_key=None,
761+
)
762+
self.config_directory = config_directory
763+
self.env = env
764+
self.test_data_dir = test_data_dir
765+
self.port = port
766+
self.server_name = server_name
767+
768+
764769
class DockerGalaxyConfig(BaseGalaxyConfig):
765770
"""A :class:`GalaxyConfig` description of a Dockerized Galaxy instance."""
766771

0 commit comments

Comments
 (0)