Skip to content

Commit 5d08b67

Browse files
committed
Implement Galaxy profiles.
Specify ``--profile <name>`` with serve, test, or their shed counterparts to maintain a Galaxy database and set of shed installs across invocations.
1 parent 2052db0 commit 5d08b67

File tree

7 files changed

+121
-16
lines changed

7 files changed

+121
-16
lines changed

planemo/cli.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
import click
77

88
from .io import error
9-
from .config import read_global_config
9+
from .config import (
10+
read_global_config,
11+
OptionSource,
12+
)
13+
from planemo.galaxy import profiles
1014
from planemo import __version__
1115

1216
PYTHON_2_7_COMMANDS = ["cwl_run", "cwl_script"]
@@ -39,14 +43,15 @@ def __init__(self):
3943
self.planemo_directory = None
4044
self.option_source = {}
4145

42-
def set_option_source(self, param_name, option_source):
46+
def set_option_source(self, param_name, option_source, force=False):
4347
"""Specify how an option was set."""
44-
assert param_name not in self.option_source
48+
if not force:
49+
assert param_name not in self.option_source
4550
self.option_source[param_name] = option_source
4651

4752
def get_option_source(self, param_name):
4853
"""Return OptionSource value indicating how the option was set."""
49-
assert param_name not in self.option_source
54+
assert param_name in self.option_source
5055
return self.option_source[param_name]
5156

5257
@property
@@ -143,11 +148,18 @@ def get_command(self, ctx, name):
143148

144149
def command_function(f):
145150
"""Extension point for processing kwds after click callbacks."""
146-
def outer(*args, **kwargs):
147-
# arg_spec = inspect.getargspec(f)
148-
return f(*args, **kwargs)
149-
outer.__doc__ = f.__doc__
150-
return pass_context(outer)
151+
def handle_profile_options(*args, **kwds):
152+
profile = kwds.get("profile", None)
153+
if profile:
154+
ctx = args[0]
155+
profile_defaults = profiles.ensure_profile(ctx, profile, **kwds)
156+
for key, value in profile_defaults.items():
157+
if ctx.get_option_source(key) != OptionSource.cli:
158+
kwds[key] = value
159+
ctx.set_option_source(key, OptionSource.profile, force=True)
160+
return f(*args, **kwds)
161+
handle_profile_options.__doc__ = f.__doc__
162+
return pass_context(handle_profile_options)
151163

152164

153165
@click.command(cls=PlanemoCLI, context_settings=CONTEXT_SETTINGS)

planemo/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414

1515
OptionSource = aenum.Enum(
16-
"OptionSource", 'cli global_config default'
16+
"OptionSource", 'cli profile global_config default'
1717
)
1818

1919

planemo/galaxy/config.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,13 +155,21 @@ def config_join(*args):
155155
_handle_dependency_resolution(config_directory, kwds)
156156
_handle_job_metrics(config_directory, kwds)
157157
file_path = kwds.get("file_path") or config_join("files")
158+
_ensure_directory(file_path)
159+
160+
tool_dependency_dir = kwds.get("tool_dependency_dir") or config_join("deps")
161+
_ensure_directory(tool_dependency_dir)
162+
158163
shed_tool_conf = kwds.get("shed_tool_conf") or config_join("shed_tools_conf.xml")
159164
tool_definition = _tool_conf_entry_for(tool_paths)
160165
empty_tool_conf = config_join("empty_tool_conf.xml")
161166

162167
tool_conf = config_join("tool_conf.xml")
163168
database_location = config_join("galaxy.sqlite")
169+
164170
shed_tool_path = kwds.get("shed_tool_path") or config_join("shed_tools")
171+
_ensure_directory(shed_tool_path)
172+
165173
sheds_config_path = _configure_sheds_config_file(
166174
ctx, config_directory, **kwds
167175
)
@@ -173,7 +181,7 @@ def config_join(*args):
173181
latest_galaxy=latest_galaxy,
174182
**kwds
175183
)
176-
os.makedirs(shed_tool_path)
184+
_ensure_directory(shed_tool_path)
177185
server_name = "planemo%d" % random.randint(0, 100000)
178186
port = int(kwds.get("port", 9090))
179187
template_args = dict(
@@ -257,7 +265,8 @@ def config_join(*args):
257265
write_file(empty_tool_conf, EMPTY_TOOL_CONF_TEMPLATE)
258266

259267
shed_tool_conf_contents = _sub(SHED_TOOL_CONF_TEMPLATE, template_args)
260-
write_file(shed_tool_conf, shed_tool_conf_contents)
268+
# Write a new shed_tool_conf.xml if needed.
269+
write_file(shed_tool_conf, shed_tool_conf_contents, force=False)
261270

262271
pid_file = kwds.get("pid_file") or config_join("galaxy.pid")
263272

@@ -411,7 +420,7 @@ def _download_database_template(
411420
shutil.copyfile(galaxy_sqlite_database, database_location)
412421
return True
413422

414-
if latest:
423+
if latest or not galaxy_root:
415424
template_url = DOWNLOADS_URL + urlopen(LATEST_URL).read()
416425
urlretrieve(template_url, database_location)
417426
return True
@@ -748,7 +757,6 @@ def _handle_kwd_overrides(properties, kwds):
748757
'job_config_file',
749758
'job_metrics_config_file',
750759
'dependency_resolvers_config_file',
751-
'tool_dependency_dir',
752760
]
753761
for prop in kwds_gx_properties:
754762
val = kwds.get(prop, None)
@@ -761,7 +769,14 @@ def _sub(template, args):
761769
return ''
762770
return Template(template).safe_substitute(args)
763771

772+
773+
def _ensure_directory(path):
774+
if path is not None and not os.path.exists(path):
775+
os.makedirs(path)
776+
777+
764778
__all__ = [
765779
"attempt_database_preseed",
780+
"DATABASE_LOCATION_TEMPLATE",
766781
"galaxy_config",
767782
]

planemo/galaxy/profiles.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""This modules describes the abstraction of a Galaxy profile.
2+
3+
This is a workspace with a specific default configuration and shed
4+
tool setup. It is meant to be used with various serve commands.
5+
"""
6+
import os
7+
8+
from .config import (
9+
DATABASE_LOCATION_TEMPLATE,
10+
attempt_database_preseed,
11+
)
12+
13+
14+
def ensure_profile(ctx, profile_name, **kwds):
15+
"""Ensure a Galaxy profile exists and return profile defaults."""
16+
profile_directory = _profile_directory(ctx, profile_name)
17+
database_location = os.path.join(profile_directory, "galaxy.sqlite")
18+
19+
if not os.path.exists(profile_directory):
20+
os.makedirs(profile_directory)
21+
attempt_database_preseed(None, database_location, **kwds)
22+
23+
database_connection = DATABASE_LOCATION_TEMPLATE % database_location
24+
file_path = os.path.join(profile_directory, "files")
25+
shed_tool_path = os.path.join(profile_directory, "shed_tools")
26+
shed_tool_conf = os.path.join(profile_directory, "shed_tool_conf.xml")
27+
tool_dependency_dir = os.path.join(profile_directory, "deps")
28+
29+
return dict(
30+
file_path=file_path,
31+
database_connection=database_connection,
32+
tool_dependency_dir=tool_dependency_dir,
33+
shed_tool_conf=shed_tool_conf,
34+
shed_tool_path=shed_tool_path,
35+
)
36+
37+
38+
def _profile_directory(ctx, profile_name):
39+
return os.path.join(ctx.galaxy_profiles_directory, profile_name)
40+
41+
__all__ = [
42+
"ensure_profile",
43+
]

planemo/io.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ def shell_join(*args):
7575
return "; ".join([c for c in args if c])
7676

7777

78-
def write_file(path, content):
78+
def write_file(path, content, force=True):
79+
if os.path.exists(path) and not force:
80+
return
81+
7982
with open(path, "w") as f:
8083
f.write(content)
8184

planemo/options.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ def tool_dependency_dir_option():
211211
dir_okay=True,
212212
resolve_path=True
213213
),
214+
default=None,
214215
use_global_config=True,
215216
help="Tool dependency dir for Galaxy to target.",
216217
)
@@ -701,6 +702,7 @@ def galaxy_config_options():
701702
conda_auto_install_option(),
702703
conda_auto_init_option(),
703704
# Profile options...
705+
profile_option(),
704706
file_path_option(),
705707
database_connection_option(),
706708
shed_tools_conf_option(),
@@ -740,6 +742,15 @@ def daemon_option():
740742
)
741743

742744

745+
def profile_option():
746+
return planemo_option(
747+
"--profile",
748+
type=str,
749+
default=None,
750+
help="Location of pid file is executed with --daemon."
751+
)
752+
753+
743754
def galaxy_serve_options():
744755
return _compose(
745756
galaxy_run_options(),

tests/test_cmd_serve.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from planemo.galaxy import api
77
from planemo import network_util
8+
from planemo.io import kill_pid_file
89

910
from .test_utils import (
1011
CliTestCase,
@@ -30,20 +31,40 @@ def test_serve_daemon(self):
3031
extra_args = ["--daemon", "--pid_file", pid_file]
3132
serve = functools.partial(self._run, port, extra_args)
3233
self._launch_thread_and_wait(serve, port)
33-
time.sleep(.1)
34+
admin_gi = api.gi(port)
35+
user_api_key = api.user_api_key(admin_gi)
36+
user_gi = api.gi(port, user_api_key)
37+
assert len(user_gi.histories.get_histories(name=TEST_HISTORY_NAME)) == 0
38+
user_gi.histories.create_history(TEST_HISTORY_NAME)
39+
40+
@skip_if_environ("PLANEMO_SKIP_GALAXY_TESTS")
41+
def test_serve_profile(self):
42+
port = network_util.get_free_port()
43+
pid_file = os.path.join(self._home, "test.pid")
44+
extra_args = [
45+
"--daemon", "--pid_file", pid_file, "--profile", "moo",
46+
]
47+
serve = functools.partial(self._run, port, extra_args)
48+
self._launch_thread_and_wait(serve, port)
3449
assert network_util.wait_net_service("127.0.0.1", port)
3550
admin_gi = api.gi(port)
3651
user_api_key = api.user_api_key(admin_gi)
3752
user_gi = api.gi(port, user_api_key)
3853
assert len(user_gi.histories.get_histories(name=TEST_HISTORY_NAME)) == 0
3954
user_gi.histories.create_history(TEST_HISTORY_NAME)
55+
kill_pid_file(pid_file)
56+
57+
self._launch_thread_and_wait(serve, port)
58+
assert len(user_gi.histories.get_histories(name=TEST_HISTORY_NAME)) == 1
4059

4160
def _launch_thread_and_wait(self, func, port):
4261
t = threading.Thread(target=func)
4362
t.daemon = True
4463
t.start()
4564
time.sleep(15)
4665
assert network_util.wait_net_service("127.0.0.1", port)
66+
time.sleep(.1)
67+
assert network_util.wait_net_service("127.0.0.1", port)
4768

4869
def _run(self, port, serve_args=[]):
4970
cat_path = os.path.join(TEST_REPOS_DIR, "single_tool", "cat.xml")

0 commit comments

Comments
 (0)