Skip to content

Commit a87899b

Browse files
committed
Introduce profile commands and improved profiles.
- Add new commands for explicit Galaxy profile management. - ``profile_create`` creates a new profile - and can now automatically setup and configure a Postgres database for the profile. - ``profile_list`` shows created profiles. - ``profile_delete`` deletes a created profile and cleans up the database created for the profile. xref #447
1 parent 89f032a commit a87899b

13 files changed

+273
-25
lines changed

planemo/commands/cmd_database_create.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,7 @@
99

1010

1111
@click.command('database_create')
12-
@click.argument(
13-
'identifier',
14-
metavar="IDENTIFIER",
15-
type=str,
16-
)
12+
@options.database_identifier_argument()
1713
@options.database_source_options()
1814
@command_function
1915
def cli(ctx, identifier, **kwds):

planemo/commands/cmd_database_delete.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@
88

99

1010
@click.command('database_delete')
11-
@click.argument(
12-
'identifier',
13-
metavar="IDENTIFIER",
14-
type=str,
15-
)
11+
@options.database_identifier_argument()
1612
@options.database_source_options()
1713
@command_function
1814
def cli(ctx, identifier, **kwds):
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Module describing the planemo ``profile_create`` command."""
2+
from __future__ import print_function
3+
4+
import click
5+
6+
from planemo.cli import command_function
7+
from planemo import options
8+
from planemo.galaxy import profiles
9+
10+
11+
@click.command('profile_create')
12+
@options.profile_name_argument()
13+
@options.profile_database_options()
14+
@command_function
15+
def cli(ctx, profile_name, **kwds):
16+
"""Create a profile."""
17+
profiles.create_profile(ctx, profile_name, **kwds)
18+
print("Profile created.")
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Module describing the planemo ``profile_delete`` command."""
2+
from __future__ import print_function
3+
4+
import click
5+
6+
from planemo.cli import command_function
7+
from planemo import options
8+
from planemo.galaxy import profiles
9+
10+
11+
@click.command('profile_delete')
12+
@options.profile_name_argument()
13+
@options.profile_database_options()
14+
@command_function
15+
def cli(ctx, profile_name, **kwds):
16+
"""Delete a profile."""
17+
profiles.delete_profile(ctx, profile_name, **kwds)
18+
print("Profile deleted.")

planemo/commands/cmd_profile_list.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""Module describing the planemo ``profile_list`` command."""
2+
from __future__ import print_function
3+
4+
import click
5+
6+
from planemo.cli import command_function
7+
from planemo.galaxy import profiles
8+
9+
10+
@click.command('profile_list')
11+
@command_function
12+
def cli(ctx, **kwds):
13+
"""List configured profile names."""
14+
profile_names = profiles.list_profiles(ctx, **kwds)
15+
print(profile_names)

planemo/database/factory.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@
44

55
def create_database_source(**kwds):
66
"""Return a :class:`planemo.database.DatabaseSource` for configuration."""
7-
return LocalPostgresDatabaseSource(**kwds)
7+
database_type = kwds.get("database_type", "postgres")
8+
if database_type == "postgres":
9+
return LocalPostgresDatabaseSource(**kwds)
10+
# TODO
11+
# from .sqlite import SqliteDatabaseSource
12+
# elif database_type == "sqlite":
13+
# return SqliteDatabaseSource(**kwds)
14+
else:
15+
raise Exception("Unknown database type [%s]." % database_type)
816

917

1018
__all__ = ['create_database_source']

planemo/database/postgres.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class LocalPostgresDatabaseSource(DatabaseSource):
1111
"""Local postgres database source managed through psql application."""
1212

1313
def __init__(self, **kwds):
14-
"""Construct a postgres datasource from planemo configuration."""
14+
"""Construct a postgres database source from planemo configuration."""
1515
self.psql_path = kwds.get("postgres_psql_path", None) or 'psql'
1616
self.database_user = kwds.get("postgres_database_user", None)
1717
self.database_host = kwds.get("postgres_database_host", None)

planemo/database/sqlite.py-wip

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Module describes an sqlite implementation of the :class:`DatabaseSource` interface."""
2+
import os
3+
4+
from .interface import DatabaseSource
5+
6+
from .config import (
7+
DATABASE_LOCATION_TEMPLATE,
8+
attempt_database_preseed,
9+
)
10+
11+
12+
class SqliteDatabaseSource(DatabaseSource):
13+
""":class:`DatabaseSource` implementation for creating sqlite databases."""
14+
15+
def __init__(self, **kwds):
16+
"""Construct a sqlite database source from planemo configuration."""
17+
self.sqlite_path = kwds.get("sqlite_path", None) or 'psql'
18+
self._kwds = kwds
19+
20+
def list_databases(self):
21+
"""List sqlite databases on a path."""
22+
return os.path.basename(self.sqlite_path)
23+
24+
def create_database(self, identifier):
25+
"""Create pre-populated Galxay sqlite database with specified path."""
26+
return os.path.basename(self.sqlite_path)
27+
28+
def delete_database(self, identifier):
29+
"""Use `psql -c "drop database"` to delete a database."""
30+
31+
def sqlalchemy_url(self, identifier):
32+
"""Return URL or form postgresql://username:password@localhost/mydatabase."""

planemo/galaxy/profiles.py

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,126 @@
33
This is a workspace with a specific default configuration and shed
44
tool setup. It is meant to be used with various serve commands.
55
"""
6+
import json
67
import os
8+
import shutil
79

810
from .config import (
911
DATABASE_LOCATION_TEMPLATE,
1012
attempt_database_preseed,
1113
)
14+
from planemo.database import create_database_source
1215

16+
PROFILE_OPTIONS_JSON_NAME = "planemo_profile_options.json"
17+
ALREADY_EXISTS_EXCEPTION = "Cannot create profile with name [%s], directory [%s] already exists."
1318

14-
def ensure_profile(ctx, profile_name, **kwds):
15-
"""Ensure a Galaxy profile exists and return profile defaults."""
19+
20+
def profile_exists(ctx, profile_name, **kwds):
21+
"""Return a truthy value iff the specified profile already exists."""
1622
profile_directory = _profile_directory(ctx, profile_name)
17-
database_location = os.path.join(profile_directory, "galaxy.sqlite")
23+
return os.path.exists(profile_directory)
24+
1825

19-
if not os.path.exists(profile_directory):
20-
os.makedirs(profile_directory)
26+
def list_profiles(ctx, **kwds):
27+
return os.listdir(ctx.galaxy_profiles_directory)
28+
29+
30+
def delete_profile(ctx, profile_name, **kwds):
31+
"""Delete profile with the specified name."""
32+
profile_directory = _profile_directory(ctx, profile_name)
33+
profile_options = _read_profile_options(profile_directory)
34+
database_type = profile_options.get("database_type")
35+
if database_type != "sqlite":
36+
database_source = create_database_source(**kwds)
37+
database_identifier = _profile_to_database_identifier(profile_name)
38+
database_source.delete_database(
39+
database_identifier,
40+
)
41+
shutil.rmtree(profile_directory)
42+
43+
44+
def create_profile(ctx, profile_name, **kwds):
45+
"""Create a profile with the specified name."""
46+
profile_directory = _profile_directory(ctx, profile_name)
47+
if profile_exists(ctx, profile_name, **kwds):
48+
message = ALREADY_EXISTS_EXCEPTION % (
49+
profile_name, profile_directory
50+
)
51+
raise Exception(message)
52+
53+
os.makedirs(profile_directory)
54+
database_type = kwds.get("database_type", "sqlite")
55+
if database_type != "sqlite":
56+
database_source = create_database_source(**kwds)
57+
database_identifier = _profile_to_database_identifier(profile_name)
58+
database_source.create_database(
59+
database_identifier,
60+
)
61+
database_connection = database_source.sqlalchemy_url(database_identifier)
62+
else:
63+
database_location = os.path.join(profile_directory, "galaxy.sqlite")
2164
attempt_database_preseed(None, database_location, **kwds)
65+
database_connection = DATABASE_LOCATION_TEMPLATE % database_location
66+
67+
stored_profile_options = {
68+
'database_type': database_type,
69+
'database_connection': database_connection,
70+
}
71+
profile_options_path = _stored_profile_options_path(profile_directory)
72+
with open(profile_options_path, "w") as f:
73+
json.dump(stored_profile_options, f)
74+
2275

23-
database_connection = DATABASE_LOCATION_TEMPLATE % database_location
76+
def ensure_profile(ctx, profile_name, **kwds):
77+
"""Ensure a Galaxy profile exists and return profile defaults."""
78+
if not profile_exists(ctx, profile_name, **kwds):
79+
create_profile(ctx, profile_name, **kwds)
80+
81+
return _profile_options(ctx, profile_name, **kwds)
82+
83+
84+
def _profile_options(ctx, profile_name, **kwds):
85+
profile_directory = _profile_directory(ctx, profile_name)
2486
file_path = os.path.join(profile_directory, "files")
2587
shed_tool_path = os.path.join(profile_directory, "shed_tools")
2688
shed_tool_conf = os.path.join(profile_directory, "shed_tool_conf.xml")
2789
tool_dependency_dir = os.path.join(profile_directory, "deps")
2890

29-
return dict(
91+
profile_options = _read_profile_options(profile_directory)
92+
profile_options.update(dict(
3093
file_path=file_path,
31-
database_connection=database_connection,
3294
tool_dependency_dir=tool_dependency_dir,
3395
shed_tool_conf=shed_tool_conf,
3496
shed_tool_path=shed_tool_path,
97+
))
98+
99+
return profile_options
100+
101+
102+
def _profile_to_database_identifier(profile_name):
103+
char_lst = [c if c.isalnum() else "_" for c in profile_name]
104+
return "plnmoprof_%s" % "".join(char_lst)
105+
106+
107+
def _read_profile_options(profile_directory):
108+
profile_options_path = _stored_profile_options_path(profile_directory)
109+
with open(profile_options_path, "r") as f:
110+
profile_options = json.load(f)
111+
return profile_options
112+
113+
114+
def _stored_profile_options_path(profile_directory):
115+
profile_options_path = os.path.join(
116+
profile_directory, PROFILE_OPTIONS_JSON_NAME
35117
)
118+
return profile_options_path
36119

37120

38121
def _profile_directory(ctx, profile_name):
39122
return os.path.join(ctx.galaxy_profiles_directory, profile_name)
40123

41124
__all__ = [
42125
"ensure_profile",
126+
"create_profile",
127+
"delete_profile",
43128
]

planemo/options.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,7 @@ def galaxy_config_options():
789789
conda_auto_init_option(),
790790
# Profile options...
791791
profile_option(),
792+
profile_database_options(),
792793
file_path_option(),
793794
database_connection_option(),
794795
shed_tools_conf_option(),
@@ -968,6 +969,45 @@ def test_report_options():
968969
)
969970

970971

972+
def profile_name_argument():
973+
return click.argument(
974+
'profile_name',
975+
metavar="PROFILE_NAME",
976+
type=str,
977+
)
978+
979+
980+
def database_identifier_argument():
981+
return click.argument(
982+
'identifier',
983+
metavar="IDENTIFIER",
984+
type=str,
985+
)
986+
987+
988+
def postgres_datatype_type_option():
989+
return planemo_option(
990+
"--postgres",
991+
"database_type",
992+
flag_value="postgres",
993+
help=("Use postgres database type."),
994+
)
995+
996+
997+
def database_type_option():
998+
return planemo_option(
999+
"--database_type",
1000+
default="postgres",
1001+
type=click.Choice([
1002+
"postgres",
1003+
"sqlite",
1004+
]),
1005+
use_global_config=True,
1006+
help=("Type of database to use for profile - "
1007+
"currently only 'postgres' is available."),
1008+
)
1009+
1010+
9711011
def database_source_options():
9721012
"""Database connection options for commands that utilize a database."""
9731013
return _compose(
@@ -998,6 +1038,14 @@ def database_source_options():
9981038
)
9991039

10001040

1041+
def profile_database_options():
1042+
return _compose(
1043+
postgres_datatype_type_option(),
1044+
database_type_option(),
1045+
database_source_options(),
1046+
)
1047+
1048+
10011049
def test_options():
10021050
return _compose(
10031051
planemo_option(

0 commit comments

Comments
 (0)