Skip to content

Commit

Permalink
Tool shed publishing support.
Browse files Browse the repository at this point in the history
 - New shed_upload command that can tar up a repository and publish it to various tool sheds.
 - Introduce global config file ~/.planemo.yml with optional tool sheds API keys, tool shed username, etc...
 - New config_init command to setup a template for this global config file.
 - Allow each repo to contain an optional .shed.yml describing name of repository (will fill this out with more options).

Have to disable Python 3.4 support because bioblend doesn't support Python 3.4.

Warning: This depends on very new tool shed features that have not even been merged into galaxy-central yet.
  • Loading branch information
jmchilton committed Oct 14, 2014
1 parent 26d1bab commit e41892b
Show file tree
Hide file tree
Showing 14 changed files with 430 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
language: python

python:
- "3.4"
# - "3.4"
- "2.7"
- "2.6"
- "pypy"
Expand Down
2 changes: 2 additions & 0 deletions docs/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ documentation describes these commands.
.. include:: commands/brew.rst
.. include:: commands/brew_env.rst
.. include:: commands/brew_init.rst
.. include:: commands/config_init.rst
.. include:: commands/docker_build.rst
.. include:: commands/docker_shell.rst
.. include:: commands/lint.rst
.. include:: commands/project_init.rst
.. include:: commands/serve.rst
.. include:: commands/shed_upload.rst
.. include:: commands/syntax.rst
.. include:: commands/test.rst
.. include:: commands/tool_init.rst
Expand Down
22 changes: 22 additions & 0 deletions docs/commands/config_init.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

``config_init`` command
===============================

This section is auto-generated from the help text for the planemo command
``config_init``. This help message can be generated with ``planemo config_init
--help``.

**Usage**::

planemo config_init [OPTIONS] PROJECT

**Help**

Help initialize global configuration (in home directory) for Planemo.

**Options**::


--template TEXT
--help Show this message and exit.
31 changes: 31 additions & 0 deletions docs/commands/shed_upload.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

``shed_upload`` command
===============================

This section is auto-generated from the help text for the planemo command
``shed_upload``. This help message can be generated with ``planemo shed_upload
--help``.

**Usage**::

planemo shed_upload [OPTIONS] PROJECT

**Help**

Upload a tool directory as a tarball to a tool shed.

**Options**::


--message TEXT Commit message for tool shed upload.
--owner TEXT Tool shed repository owner (username).
--name TEXT Repository name (default to tool directory name).
--shed_target TEXT Tool shed to target (toolshed/testtoolshed/local/url).
--shed_key TEXT API key for shed access (required unless e-mail/pass
specified).
--shed_email TEXT E-mail for shed auth (required unless shed_key is
specified).
--shed_password TEXT Password for shed auth (required unless shed_key is
specified).
--help Show this message and exit.
16 changes: 16 additions & 0 deletions docs/planemo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ planemo.cli module
:undoc-members:
:show-inheritance:

planemo.config module
---------------------

.. automodule:: planemo.config
:members:
:undoc-members:
:show-inheritance:

planemo.galaxy_config module
----------------------------

Expand Down Expand Up @@ -51,6 +59,14 @@ planemo.options module
:undoc-members:
:show-inheritance:

planemo.shed module
-------------------

.. automodule:: planemo.shed
:members:
:undoc-members:
:show-inheritance:


Module contents
---------------
Expand Down
9 changes: 9 additions & 0 deletions planemo/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import sys
import click
from .io import error
from .config import read_global_config


CONTEXT_SETTINGS = dict(auto_envvar_prefix='PLANEMO')
COMMAND_ALIASES = {
Expand All @@ -16,6 +18,13 @@ class Context(object):
def __init__(self):
self.verbose = False
self.home = os.getcwd()
self._global_config = None

@property
def global_config(self):
if self._global_config is None:
self._global_config = read_global_config()
return self._global_config

def log(self, msg, *args):
"""Logs a message to stderr."""
Expand Down
59 changes: 59 additions & 0 deletions planemo/commands/cmd_config_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
"""
import os

import click

from planemo.cli import pass_context
from planemo import options
from planemo import config
from planemo.io import warn, info

CONFIG_TEMPLATE = """## Planemo Global Configuration File.
## Everything in this file is completely optional - these values can all be
## configured via command line options for the corresponding commands.
## Specify a default galaxy_root for test and server commands here.
#galaxy_root: /path/to/galaxy_root
## Username used with toolshed(s).
#shed_username: "<TODO>"
sheds:
# For each tool shed you wish to target, uncomment key or both email and
# password.
toolshed:
#key: "<TODO>"
#email: "<TODO>"
#password: "<TODO>"
testtoolshed
#key: "<TODO>"
#email: "<TODO>"
#password: "<TODO>"
local
#key: "<TODO>"
#email: "<TODO>"
#password: "<TODO>"
"""
SUCCESS_MESSAGE = (
"Wrote configuration template to %s, "
"please open with editor and fill out."
)


@click.command("config_init")
@options.optional_project_arg(exists=None)
@click.option(
'--template',
default=None
)
@pass_context
def cli(ctx, path, template=None, **kwds):
"""Help initialize global configuration (in home directory) for Planemo.
"""
# TODO: prompt for values someday.
config_path = config.global_config_path()
if os.path.exists(config_path):
warn("File %s already exists, exiting." % config_path)
return -1
with open(config_path, "w") as f:
f.write(CONFIG_TEMPLATE)
info(SUCCESS_MESSAGE % config_path)
70 changes: 70 additions & 0 deletions planemo/commands/cmd_shed_upload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""
"""
import click

from planemo.cli import pass_context
from planemo import options
from planemo import shed
from planemo.io import error
from planemo.io import info
from planemo.io import shell


# TODO: Implement alternative tool per repo upload strategy.
# TODO: Use git commit hash and origin to generated commit message.
@click.command("shed_upload")
@options.optional_project_arg(exists=True)
@click.option(
'--message',
help="Commit message for tool shed upload."
)
@click.option(
'--owner',
help="Tool shed repository owner (username)."
)
@click.option(
'--name',
help="Repository name (default to tool directory name)."
)
@click.option(
'--shed_target',
help="Tool shed to target (toolshed/testtoolshed/local/url).",
default="toolshed",
)
@click.option(
'--shed_key',
help="API key for shed access (required unless e-mail/pass specified)."
)
@click.option(
'--shed_email',
help="E-mail for shed auth (required unless shed_key is specified)."
)
@click.option(
'--shed_password',
help="Password for shed auth (required unless shed_key is specified)."
)
@click.option(
'--tar_only',
is_flag=True,
help="Produce tar file for upload but do not publish to a tool shed.",
)
@pass_context
def cli(ctx, path, template=None, **kwds):
"""Upload a tool directory as a tarball to a tool shed.
"""
tar_path = shed.build_tarball(path)
if kwds["tar_only"]:
shell("cp %s shed_upload.tar.gz" % tar_path)
return 0
tsi = shed.tool_shed_client(ctx, **kwds)
update_kwds = {}
message = kwds.get("message", None)
if message:
update_kwds["commit_message"] = message
repo_id = shed.find_repository_id(ctx, tsi, path, **kwds)
try:
tsi.repositories.update_repository(repo_id, tar_path, **update_kwds)
except Exception as e:
error(e.read())
return -1
info("Repository updated successfully.")
23 changes: 23 additions & 0 deletions planemo/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import os
import yaml

DEFAULT_CONFIG = {
}


def global_config_path():
config_path = os.environ.get(
"PLANEMO_GLOBAL_CONFIG_PATH",
"~/.planemo.yml"
)
config_path = os.path.expanduser(config_path)
return config_path


def read_global_config():
config_path = global_config_path()
if not os.path.exists(config_path):
return DEFAULT_CONFIG

with open(config_path) as f:
return yaml.load(f)
109 changes: 109 additions & 0 deletions planemo/shed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import os
from tempfile import mkstemp
import tarfile
import yaml
try:
from bioblend import toolshed
except ImportError:
toolshed = None


# Planemo generated or consumed files that do not need to be uploaded to the
# tool shed.
PLANEMO_FILES = [
"tool_test_output.html",
".travis",
".travis.yml",
".shed.yml"
]
SHED_SHORT_NAMES = {
"toolshed": "https://toolshed.g2.bx.psu.edu/",
"testtoolshed": "https://testtoolshed.g2.bx.psu.edu/",
"local": "http://localhost:9009/"
}


def shed_repo_config(path):
shed_yaml_path = os.path.join(path, ".shed.yml")
if os.path.exists(shed_yaml_path):
with open(shed_yaml_path, "r") as f:
return yaml.load(f)
else:
return {}


def tool_shed_client(ctx, **kwds):
shed_target = kwds.get("shed_target")
global_config = ctx.global_config
if global_config and "sheds" in global_config:
sheds_config = global_config["sheds"]
shed_config = sheds_config.get(shed_target, {})
else:
shed_config = {}

def prop(key):
return kwds.get("shed_%s" % key, None) or shed_config.get(key, None)

url = _tool_shed_url(kwds)
key = prop("key")
email = prop("email")
password = prop("password")
tsi = toolshed.ToolShedInstance(
url=url,
key=key,
email=email,
password=password
)
return tsi


def find_repository_id(ctx, tsi, path, **kwds):
repo_config = shed_repo_config(path)
owner = kwds.get("owner", None) or repo_config.get("owner", None)
name = kwds.get("name", None) or repo_config.get("name", None)
if owner is None:
owner = ctx.global_config.get("shed_username", None)
if name is None:
name = os.path.basename(os.path.abspath(path))
repos = tsi.repositories.get_repositories()

def matches(r):
return r["owner"] == owner and r["name"] == name

matching_repos = filter(matches, repos)
if not matching_repos:
message = "Failed to find repository for owner/name %s/%s"
raise Exception(message % (owner, name))
repo_id = matching_repos[0]["id"]
return repo_id


def build_tarball(tool_path):
"""Build a tool-shed tar ball for the specified path, caller is
responsible for deleting this file.
"""
fd, temp_path = mkstemp()
try:
with tarfile.open(temp_path, "w:gz") as tar:
for name in os.listdir(tool_path):
path = os.path.join(tool_path, name)
tar.add(path, name, recursive=True, exclude=_tar_excludes)
finally:
os.close(fd)
return temp_path


def _tool_shed_url(kwds):
url = kwds.get("shed_target")
if url in SHED_SHORT_NAMES:
url = SHED_SHORT_NAMES[url]
return url


def _tar_excludes(path):
name = os.path.basename(path)
if name.startswith(".git"):
return True
elif name in PLANEMO_FILES:
return True
return False
Loading

0 comments on commit e41892b

Please sign in to comment.