Skip to content

Commit 6c056f1

Browse files
committed
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 #592. Implements #508.
1 parent a91ca2c commit 6c056f1

11 files changed

Lines changed: 294 additions & 101 deletions

File tree

planemo/commands/cmd_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ def cli(ctx, paths, **kwds):
6565
"""
6666
runnables = for_paths(paths)
6767
enable_beta_test = any([r.type not in [RunnableType.galaxy_tool, RunnableType.directory] for r in runnables])
68-
enable_beta_test = enable_beta_test or not is_galaxy_engine(**kwds)
68+
enable_beta_test = enable_beta_test or kwds.get("engine", "galaxy") != "engine"
6969
if enable_beta_test:
70-
info("Enable beta testing mode to test artifact that isn't a Galaxy tool.")
70+
info("Enable beta testing mode for testing.")
7171
with engine_context(ctx, **kwds) as engine:
7272
test_data = engine.test(runnables)
7373
return_value = handle_reports_and_summary(ctx, test_data.structured_data, kwds=kwds)

planemo/commands/cmd_test_reports.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ def cli(ctx, path, **kwds):
2424
return 1
2525

2626
test_data = StructuredData(path)
27+
test_data.calculate_summary_data_if_needed()
2728
handle_reports(ctx, test_data.structured_data, kwds)

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: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
"""Module contianing the :class:`GalaxyEngine` implementation of :class:`Engine`."""
2+
from __future__ import absolute_import
23

4+
import abc
35
import contextlib
46

7+
from galaxy.tools.verify import interactor
8+
59
from planemo.galaxy.activity import execute
10+
from planemo.galaxy.config import external_galaxy_config
611
from planemo.galaxy.serve import serve_daemon
712
from planemo.runnable import RunnableType
813
from .interface import BaseEngine
914

1015

1116
class GalaxyEngine(BaseEngine):
12-
"""An :class:`Engine` implementation backed by Galaxy.
17+
"""An :class:`Engine` implementation backed by a managed Galaxy.
1318
1419
More information on Galaxy can be found at http://galaxyproject.org/.
1520
"""
1621

22+
__metaclass__ = abc.ABCMeta
23+
1724
handled_runnable_types = [
1825
RunnableType.cwl_tool,
1926
RunnableType.cwl_workflow,
@@ -24,14 +31,71 @@ class GalaxyEngine(BaseEngine):
2431
def _run(self, runnable, job_path):
2532
"""Run CWL job in Galaxy."""
2633
self._ctx.vlog("Serving artifact [%s] with Galaxy." % (runnable,))
27-
with self.serve_runnables([runnable]) as config:
34+
with self.ensure_runnables_served([runnable]) as config:
2835
self._ctx.vlog("Running job path [%s]" % job_path)
2936
run_response = execute(self._ctx, config, runnable, job_path, **self._kwds)
3037

3138
return run_response
3239

40+
@abc.abstractmethod
41+
def ensure_runnables_served(self, runnables):
42+
"""Use a context manager and describe Galaxy instance with runnables being served."""
43+
44+
def _run_test_case(self, test_case):
45+
if hasattr(test_case, "job_path"):
46+
# Simple file-based job path.
47+
super(GalaxyEngine, self)._run_test_case(test_case)
48+
else:
49+
with self.ensure_runnables_served([test_case.runnable]) as config:
50+
galaxy_interactor_kwds = {
51+
"galaxy_url": config.galaxy_url,
52+
"master_api_key": config.master_api_key,
53+
"api_key": config.user_api_key,
54+
"keep_outputs_dir": "", # TODO: this...
55+
}
56+
tool_id = test_case.tool_id
57+
test_index = test_case.test_index
58+
tool_version = test_case.tool_version
59+
galaxy_interactor = interactor.GalaxyInteractorApi(**galaxy_interactor_kwds)
60+
61+
test_results = []
62+
63+
def _register_job_data(job_data):
64+
test_results.append({
65+
'id': tool_id + "-" + str(test_index),
66+
'has_data': True,
67+
'data': job_data,
68+
})
69+
70+
verbose = self._ctx.verbose
71+
try:
72+
if verbose:
73+
# TODO: this is pretty hacky, it'd be better to send a stream
74+
# and capture the output information somehow.
75+
interactor.VERBOSE_GALAXY_ERRORS = True
76+
77+
interactor.verify_tool(
78+
tool_id,
79+
galaxy_interactor,
80+
test_index=test_index,
81+
tool_version=tool_version,
82+
register_job_data=_register_job_data,
83+
quiet=not verbose,
84+
)
85+
except Exception:
86+
pass
87+
88+
return test_results[0]
89+
90+
91+
class LocalManagedGalaxyEngine(BaseEngine):
92+
"""An :class:`Engine` implementation backed by a managed Galaxy.
93+
94+
More information on Galaxy can be found at http://galaxyproject.org/.
95+
"""
96+
3397
@contextlib.contextmanager
34-
def serve_runnables(self, runnables):
98+
def ensure_runnables_served(self, runnables):
3599
# TODO: define an interface for this - not everything in config would make sense for a
36100
# pre-existing Galaxy interface.
37101
with serve_daemon(self._ctx, runnables, **self._serve_kwds()) as config:
@@ -41,7 +105,7 @@ def _serve_kwds(self):
41105
return self._kwds.copy()
42106

43107

44-
class DockerizedGalaxyEngine(GalaxyEngine):
108+
class DockerizedManagedGalaxyEngine(LocalManagedGalaxyEngine):
45109
"""An :class:`Engine` implementation backed by Galaxy running in Docker.
46110
47111
More information on Galaxy can be found at http://galaxyproject.org/.
@@ -53,7 +117,20 @@ def _serve_kwds(self):
53117
return serve_kwds
54118

55119

120+
class ExternalGalaxyEngine(GalaxyEngine):
121+
"""An :class:`Engine` implementation backed by an external Galaxy instance.
122+
"""
123+
124+
@contextlib.contextmanager
125+
def ensure_runnables_served(self, runnables):
126+
# TODO: ensure tools are available
127+
with external_galaxy_config(self._ctx, runnables, **self._kwds) as config:
128+
config.install_workflows()
129+
yield config
130+
131+
56132
__all__ = (
57-
"GalaxyEngine",
58-
"DockerizedGalaxyEngine",
133+
"DockerizedManagedGalaxyEngine",
134+
"ExternalGalaxyEngine",
135+
"LocalManagedGalaxyEngine",
59136
)

planemo/engine/interface.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -95,27 +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-
mode="w+",
109-
)
110-
tmp_path = f.name
111-
job_path = tmp_path
112-
json.dump(job, f)
113-
f.close()
114-
try:
115-
run_response = self._run(runnable, job_path)
116-
finally:
117-
if tmp_path:
118-
os.remove(tmp_path)
98+
run_response = self._run_test_case(test_case)
11999
self._ctx.vlog(
120100
"Test case [%s] resulted in run response [%s]",
121101
test_case,
@@ -124,6 +104,30 @@ def _collect_test_results(self, test_cases):
124104
test_results.append((test_case, run_response))
125105
return test_results
126106

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+
mode="w+",
119+
)
120+
tmp_path = f.name
121+
job_path = tmp_path
122+
json.dump(job, f)
123+
f.close()
124+
try:
125+
run_response = self._run(runnable, job_path)
126+
finally:
127+
if tmp_path:
128+
os.remove(tmp_path)
129+
return run_response
130+
127131
def _process_test_results(self, test_results):
128132
for (test_case, run_response) in test_results:
129133
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

0 commit comments

Comments
 (0)