Skip to content

Commit

Permalink
Implement shed_init command.
Browse files Browse the repository at this point in the history
Currently just creates .shed.yml files from command-line arguments. Arguably the tool author could just open this file and start editing but at least --help provides some sort guidance of what fields are available and validates categories for instance. The real reason to add this however is as a stepping stone toward #118.
  • Loading branch information
jmchilton committed Apr 23, 2015
1 parent 9061d16 commit cc1a447
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 14 deletions.
47 changes: 47 additions & 0 deletions planemo/commands/cmd_shed_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import click
import sys

from planemo.cli import pass_context

from planemo import options
from planemo import shed


@click.command("shed_init")
@options.optional_project_arg()
# TODO:
# @click.option(
# "--from_workflow",
# type=click.Path(exists=True, file_okay=True, resolve_path=True),
# help=('Attempt to generate repository dependencies from specified '
# 'workflow.')
# )
@click.option(
"--description",
help='Specify repository description for .shed.yml.'
)
@click.option(
"--long_description",
help='Specify repository long_description for .shed.yml.'
)
@click.option(
"--remote_repository_url",
help='Specify repository remote_repository_url for .shed.yml.'
)
@click.option(
"--homepage_url",
help='Specify repository homepage_url for .shed.yml.'
)
@click.option(
"--category",
multiple=True,
help='Specify repository category for .shed.yml (may specify multiple).',
type=click.Choice(shed.CURRENT_CATEGORIES)
)
@options.shed_owner_option()
@options.shed_name_option()
@options.force_option()
@pass_context
def cli(ctx, path, **kwds):
exit_code = shed.shed_init(ctx, path, **kwds)
sys.exit(exit_code)
16 changes: 7 additions & 9 deletions planemo/commands/cmd_tool_init.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import os
import sys

import click

from planemo.cli import pass_context
from planemo import options
from planemo import io
from planemo import tool_builder

Expand All @@ -18,12 +22,7 @@
prompt=True,
help="Short identifier for new tool (no whitespace)",
)
@click.option(
"-f",
"--force",
is_flag=True,
help="Overwrite existing tool if present.",
)
@options.force_option(what="tool")
@click.option(
"-t",
"--tool",
Expand Down Expand Up @@ -190,9 +189,8 @@ def cli(ctx, **kwds):
output = kwds.get("tool")
if not output:
output = "%s.xml" % kwds.get("id")
if not kwds["force"] and os.path.exists(output):
io.error("%s already exists, exiting." % output)
return 1
if not io.can_write_to_path(output, **kwds):
sys.exit(1)
tool_description = tool_builder.build(**kwds)
open(output, "w").write(tool_description.contents)
io.info("Tool written to %s" % output)
Expand Down
7 changes: 7 additions & 0 deletions planemo/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ def info(message, *args):
_echo(click.style(message, bold=True, fg='green'))


def can_write_to_path(path, **kwds):
if not kwds["force"] and os.path.exists(path):
error("%s already exists, exiting." % path)
return False
return True


def error(message, *args):
if args:
message = message % args
Expand Down
9 changes: 9 additions & 0 deletions planemo/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@
from galaxy.tools.deps import docker_util


def force_option(what="files"):
return click.option(
"-f",
"--force",
is_flag=True,
help="Overwrite existing %s if present." % what,
)


def test_data_option():
return click.option(
"--test_data",
Expand Down
83 changes: 81 additions & 2 deletions planemo/shed.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
except ImportError:
toolshed = None

from planemo.io import error
from planemo.io import untar_to
from planemo.io import (
error,
untar_to,
can_write_to_path,
)

SHED_CONFIG_NAME = '.shed.yml'
NO_REPOSITORIES_MESSAGE = ("Could not find any .shed.yml files or a --name to "
Expand Down Expand Up @@ -57,6 +60,54 @@
REPO_TYPE_TOOL_DEP = "tool_dependency_definition"
REPO_TYPE_SUITE = "repository_suite_definition"

# Generate with python scripts/categories.py
CURRENT_CATEGORIES = [

This comment has been minimized.

Copy link
@bgruening

bgruening Apr 24, 2015

Member

Better option would be to ask the TS which categories are currently available. But I guess the API is not public.

This comment has been minimized.

This comment has been minimized.

Copy link
@bgruening

bgruening Apr 24, 2015

Member

You guys are great!

This comment has been minimized.

Copy link
@jmchilton

jmchilton Apr 24, 2015

Author Member

I intentionally didn't use the API - it should work without a network connection IMO - I am using these categories in a click annotation for instance (so you see them when you do planemo shed_init --help). That shouldn't require a network connection. Also planemo is updated more often then we add categories :). But if you think this is important I could work on this some more.

This comment has been minimized.

Copy link
@bgruening

bgruening Apr 24, 2015

Member

You could default to this list if there is no connection. I see your point, just wanted to save time and make this a little bit more automatic.
One thing we can think about is how this relates to different Tool Sheds.

This comment has been minimized.

Copy link
@jmchilton

jmchilton Apr 24, 2015

Author Member

Created an issue for this - see #137.

"Assembly",
"ChIP-seq",
"Combinatorial Selections",
"Computational chemistry",
"Convert Formats",
"Data Managers",
"Data Source",
"Fasta Manipulation",
"Fastq Manipulation",
"Genome-Wide Association Study",
"Genomic Interval Operations",
"Graphics",
"Imaging",
"Metabolomics",
"Metagenomics",
"Micro-array Analysis",
"Next Gen Mappers",
"Ontology Manipulation",
"Phylogenetics",
"Proteomics",
"RNA",
"SAM",
"Sequence Analysis",
"Statistics",
"Systems Biology",
"Text Manipulation",
"Tool Dependency Packages",
"Tool Generators",
"Transcriptomics",
"Variant Analysis",
"Visualization",
"Web Services",
]


def shed_init(ctx, path, **kwds):
if not os.path.exists(path):
os.makedirs(path)
shed_config_path = os.path.join(path, SHED_CONFIG_NAME)
if not can_write_to_path(shed_config_path, **kwds):
# .shed.yml exists and no --force sent.
return 1

_create_shed_config(ctx, shed_config_path, **kwds)
return 0


def shed_repo_config(path):
shed_yaml_path = os.path.join(path, SHED_CONFIG_NAME)
Expand Down Expand Up @@ -290,6 +341,34 @@ def realize_effective_repositories(path, **kwds):
shutil.rmtree(temp_directory)


def _create_shed_config(ctx, path, **kwds):
name = kwds.get("name", None) or path_to_repo_name(os.path.dirname(path))
owner = kwds.get("owner", None)
if owner is None:
owner = ctx.global_config.get("shed_username", None)
description = kwds.get("description", None) or name
long_description = kwds.get("long_description", None)
remote_repository_url = kwds.get("remote_repository_url", None)
homepage_url = kwds.get("homepage_url", None)
categories = kwds.get("category", [])
config = dict(
name=name,
owner=owner,
description=description,
long_description=long_description,
remote_repository_url=remote_repository_url,
homepage_url=homepage_url,
categories=categories,
)
# Remove empty entries...
for k in list(config.keys()):
if config[k] is None:
del config[k]

with open(path, "w") as f:
yaml.dump(config, f)


def _find_raw_repositories(path, **kwds):
name = kwds.get("name", None)
recursive = kwds.get("recursive", False)
Expand Down
17 changes: 17 additions & 0 deletions planemo/shed_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
REPO_TYPE_UNRESTRICTED,
REPO_TYPE_TOOL_DEP,
REPO_TYPE_SUITE,
CURRENT_CATEGORIES,
)
from planemo.tool_lint import (
build_lint_args,
Expand Down Expand Up @@ -111,6 +112,7 @@ def _lint_if_present(key, func, *args):
_lint_if_present("owner", _validate_repo_owner)
_lint_if_present("name", _validate_repo_name)
_lint_if_present("type", _validate_repo_type, effective_name)
_lint_if_present("categories", _validate_categories)


def _validate_repo_type(repo_type, name):
Expand Down Expand Up @@ -152,3 +154,18 @@ def _validate_repo_owner(owner):
if not(VALID_PUBLICNAME_RE.match(owner)):
msg = "Owner must contain only lower-case letters, numbers and '-'"
return msg


def _validate_categories(categories):
msg = None
if len(categories) == 0:
msg = "Repository should specify one or more categories."
else:
for category in categories:
unknown_categories = []
if category not in CURRENT_CATEGORIES:
unknown_categories.append(category)
if unknown_categories:
msg = "Categories [%s] unknown." % unknown_categories

return msg
11 changes: 11 additions & 0 deletions scripts/categories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from __future__ import print_function
import requests


categories = requests.get("https://testtoolshed.g2.bx.psu.edu/api/categories").json()
print("CURRENT_CATEGORIES = [")
for c in map(lambda c: c["name"], categories):
if c["
print(' "%s",' % c)
print("]")

2 changes: 1 addition & 1 deletion tests/data/repos/single_tool/.shed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ type: "unrestricted"
remote_repository_url: "https://github.com/galaxyproject/planemo/tree/master/tests/data/repos/single_tool"
homepage_url: "http://planemo.readthedocs.org/en/latest/"
categories:
- "Text Utils"
- "Text Manipulation"
2 changes: 1 addition & 1 deletion tests/shed_app_test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def mock_model(directory):
return InMemoryShedDataModel(
directory
).add_category(
"c1", "Text Utils"
"c1", "Text Manipulation"
).add_category(
"c2", "Sequence Analysis"
).add_repository(
Expand Down
52 changes: 52 additions & 0 deletions tests/test_shed_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import os

import yaml

from .test_utils import CliShedTestCase


class ShedInitTestCase(CliShedTestCase):

def test_minimal(self):
with self._isolate() as f:
name = os.path.basename(os.path.abspath(f))
self._check_exit_code(["shed_init", "--owner", "iuc"])
shed_config_path = os.path.join(f, ".shed.yml")
assert os.path.exists(shed_config_path)
shed_config = yaml.load(open(shed_config_path, "r"))
assert shed_config["name"] == name
assert shed_config["owner"] == "iuc"
assert shed_config["description"] == name
assert len(shed_config["categories"]) == 0

def test_more_options(self):
with self._isolate() as f:
repo_url = "https://github.com/galaxyproject/tools-devteam"
init_command = [
"shed_init",
"--owner", "devteam",
"--name", "samtools-filter",
"--description", "A samtools repo",
"--long_description", "A longer description.",
"--remote_repository_url",
repo_url,
"--homepage_url", "https://example.com/",
"--category", "SAM",
"--category", "Sequence Analysis",
"--category", "Statistics",
]
self._check_exit_code(init_command)
shed_config_path = os.path.join(f, ".shed.yml")
assert os.path.exists(shed_config_path)
shed_config = yaml.load(open(shed_config_path, "r"))
assert shed_config["name"] == "samtools-filter"
assert shed_config["owner"] == "devteam"
assert shed_config["description"] == "A samtools repo"
assert shed_config["long_description"] == "A longer description."
assert shed_config["remote_repository_url"] == repo_url
assert shed_config["homepage_url"] == "https://example.com/"

categories = shed_config["categories"]
assert len(categories) == 3
assert "SAM" in categories
assert "Statistics" in categories
2 changes: 1 addition & 1 deletion tests/test_shed_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def test_find_category_ids():
with mock_shed_client() as tsi:
category_ids = shed.find_category_ids(
tsi,
["Text Utils"]
["Text Manipulation"]
)
assert category_ids == ["c1"]

Expand Down

0 comments on commit cc1a447

Please sign in to comment.