Skip to content
Commits on Source (9)
q2cli/_version.py export-subst
q2templates/_version.py export-subst
......@@ -63,3 +63,5 @@ target/
# vi
.*.swp
.DS_Store
......@@ -14,12 +14,10 @@ install:
- wget -q https://raw.githubusercontent.com/qiime2/environment-files/master/latest/staging/qiime2-latest-py36-linux-conda.yml
- conda env create -q -n test-env --file qiime2-latest-py36-linux-conda.yml
- source activate test-env
- conda install -q nose
- conda install -q pytest
- pip install -q flake8
- pip install -q https://github.com/qiime2/q2lint/archive/master.zip
- make install
script:
- make lint
- make test
- QIIMETEST= source tab-qiime
- QIIMETEST= qiime info
include versioneer.py
include q2cli/_version.py
include q2templates/_version.py
.PHONY: all lint test install dev clean distclean
PYTHON ?= python
PREFIX ?= $(CONDA_PREFIX)
all: ;
......@@ -10,17 +9,13 @@ lint:
flake8
test: all
QIIMETEST= nosetests
py.test
install: all
$(PYTHON) setup.py install && \
mkdir -p $(PREFIX)/etc/conda/activate.d && \
cp hooks/50_activate_q2cli_tab_completion.sh $(PREFIX)/etc/conda/activate.d/
$(PYTHON) setup.py install
dev: all
pip install -e . && \
mkdir -p $(PREFIX)/etc/conda/activate.d && \
cp hooks/50_activate_q2cli_tab_completion.sh $(PREFIX)/etc/conda/activate.d/
pip install -e .
clean: distclean
......
# q2cli
A [click-based](http://click.pocoo.org/) command line interface for [QIIME 2](https://github.com/qiime2/qiime2).
## Installation and getting help
Visit https://qiime2.org to learn more about q2cli and the QIIME 2 project.
## Enabling tab completion
### Bash
To enable tab completion in Bash, run the following command or add it to your `.bashrc`/`.bash_profile`:
```bash
source tab-qiime
```
### ZSH
To enable tab completion in ZSH, run the following commands or add them to your `.zshrc`:
```bash
autoload bashcompinit && bashcompinit && source tab-qiime
```
# q2templates
Design template package for QIIME 2 Plugins
#!/usr/bin/env bash
# Bash completion script that defers to a cached completion script representing
# the state of the current QIIME 2 deployment.
#
# This script is intended to be executed on the command-line or in
# .bashrc/.bash_profile:
#
# source tab-qiime
#
_qiime_completion()
{
# Attempt to find the cached completion script. If q2cli isn't installed, or
# is an incompatible version, don't attempt completion.
local completion_path="$(python -c "import q2cli.util; print(q2cli.util.get_completion_path())" 2> /dev/null)"
if [[ $? != 0 ]]; then
unset COMPREPLY
return 0
fi
# If the completion script exists, attempt completion by invoking the script
# in a subshell, supplying COMP_WORDS and COMP_CWORD. Capture the output as
# the completion reply. If the completion script failed, don't attempt
# completion.
if [[ -f "$completion_path" ]] ; then
COMPREPLY=( $(COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD="${COMP_CWORD}" "$completion_path" 2> /dev/null) )
if [[ $? != 0 ]]; then
unset COMPREPLY
return 0
fi
else
unset COMPREPLY
return 0
fi
return 0
}
# Enable default readline and bash completion behavior when `_qiime_completion`
# doesn't have a reply.
complete -F _qiime_completion -o default -o bashdefault qiime
# Execute a `qiime` command (any command will do) so that tab-completion will
# work out-of-the-box (e.g. with a fresh installation of q2cli). Running a
# command will create or refresh the cache if necessary, which contains the
# actual completion script.
#
# Ignore stdout to avoid displaying help text to users enabling tab-completion.
# stderr displays the note about cache refreshing, as that can take a few
# moments to complete.
qiime > /dev/null
{% set data = load_setup_py_data() %}
{% set version = data.get('version') or 'placehold' %}
{% set release = '.'.join(version.split('.')[:2]) %}
{% set version = data.get('version') %}
# TODO review meta.yml spec and fill in missing fields as necessary (e.g. build number).
package:
name: q2cli
name: q2templates
version: {{ version }}
source:
......@@ -11,8 +11,6 @@ source:
build:
script: make install
entry_points:
- qiime=q2cli.__main__:qiime
requirements:
host:
......@@ -21,16 +19,13 @@ requirements:
run:
- python {{ python }}
- pip
- click
- qiime2 {{ release }}.*
- setuptools
- jinja2
- pandas
test:
imports:
- q2cli
commands:
- QIIMETEST= qiime --help
- q2templates
about:
home: https://qiime2.org
......
q2cli (2019.10.0-1) UNRELEASED; urgency=medium
* Team upload.
* New upstream version
* Standards-Version: 4.4.1
* Drop unneeded patches
* (Build-)Depends: python3-jinja2
* Upstream seems to have dropped zsh command line completion
TODO: The dir q2tamplates is pretty much a copy of package q2templates -
specifically its also carrying the third party JS + fonts stuff
we just excluded in q2templates. It might be worth investigating
whether this can be removed in q2cli completely
-- Andreas Tille <tille@debian.org> Tue, 12 Nov 2019 09:39:41 +0100
q2cli (2019.7.0-1) unstable; urgency=medium
[ Andreas Tille ]
......
......@@ -9,8 +9,9 @@ Build-Depends: debhelper-compat (= 12),
python3-setuptools,
qiime (>= 2019.7.0),
python3-click,
python3-nose
Standards-Version: 4.4.0
python3-jinja2,
python3-nose,
Standards-Version: 4.4.1
Vcs-Browser: https://salsa.debian.org/med-team/q2cli
Vcs-Git: https://salsa.debian.org/med-team/q2cli.git
Homepage: https://qiime2.org/
......@@ -22,6 +23,7 @@ Depends: ${shlibs:Depends},
${python3:Depends},
qiime,
python3-setuptools,
python3-jinja2,
python3-click
Description: Click-based command line interface for QIIME 2
QIIME 2 is a powerful, extensible, and decentralized microbiome analysis
......
Description: call python3 in the tab-qiime script, as the package is python3 only
From: Alex Mestiashvili <amestia@rsh2.donotuse.de>
--- q2cli.orig/bin/tab-qiime
+++ q2cli/bin/tab-qiime
@@ -13,7 +13,7 @@
{
# Attempt to find the cached completion script. If q2cli isn't installed, or
# is an incompatible version, don't attempt completion.
- local completion_path="$(python -c "import q2cli.util; print(q2cli.util.get_completion_path())" 2> /dev/null)"
+ local completion_path="$(python3 -c "import q2cli.util; print(q2cli.util.get_completion_path())" 2> /dev/null)"
if [[ $? != 0 ]]; then
unset COMPREPLY
Author: Liubov Chuprikova <chuprikovalv@gmail.com>
Last-Update: Sat, 23 Mar 2019 14:43:01 +0100
Description: Bash users will have a working completion, but we also need to
inform zsh users how they can enable completion.
--- a/q2cli/__main__.py
+++ b/q2cli/__main__.py
@@ -17,15 +17,10 @@
To get help with QIIME 2, visit https://qiime2.org.
-To enable tab completion in Bash, run the following command or add it to your \
-.bashrc/.bash_profile:
-
- source tab-qiime
-
To enable tab completion in ZSH, run the following commands or add them to \
your .zshrc:
- autoload bashcompinit && bashcompinit && source tab-qiime
+ autoload bashcompinit && bashcompinit && source /usr/share/bash-completion/completions/qiime
"""
call_py3.patch
inform_zsh_users_howto_set_autocompletion.patch
......@@ -11,11 +11,6 @@ QIIME 2 command\-line interface (q2cli)
\fB\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\fR
.IP
To get help with QIIME 2, visit https://qiime2.org.
.IP
To enable tab completion in ZSH, run the following commands or add them to
your .zshrc:
.IP
autoload bashcompinit && bashcompinit && source \fI\,/usr/share/bashcompletion/completions/qiime\/\fP
.SH OPTIONS
.TP
\fB\-\-version\fR
......
#!/usr/bin/make -f
# DH_VERBOSE := 1
export LC_ALL=C.UTF-8
include /usr/share/dpkg/default.mk
# this provides:
# DEB_SOURCE: the source package name
# DEB_VERSION: the full version of the package (epoch + upstream vers. + revision)
# DEB_VERSION_EPOCH_UPSTREAM: the package's version without the Debian revision
# DEB_VERSION_UPSTREAM_REVISION: the package's version without the Debian epoch
# DEB_VERSION_UPSTREAM: the package's upstream version
# DEB_DISTRIBUTION: the distribution(s) listed in the current entry of debian/changelog
# SOURCE_DATE_EPOCH: the source release date as seconds since the epoch, as
# specified by <https://reproducible-builds.org/specs/source-date-epoch/>
# for hardening you might like to uncomment this:
# export DEB_BUILD_MAINT_OPTIONS=hardening=+all
%:
dh $@ --with python3 --buildsystem=pybuild
......@@ -24,8 +9,3 @@ override_dh_auto_test:
ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
LC_ALL=C.UTF-8 dh_auto_test -- -s custom --test-args='cd {build_dir}; HOME=${CURDIR} QIIMETEST= nosetests3'
endif
override_dh_install:
dh_install
mkdir -p debian/$(DEB_SOURCE)/usr/share/bash-completion/completions
mv debian/$(DEB_SOURCE)/usr/bin/tab-qiime debian/$(DEB_SOURCE)/usr/share/bash-completion/completions/qiime
if [ -n "${ZSH_VERSION-}" ]; then
autoload bashcompinit && bashcompinit && source tab-qiime
elif [ -n "${BASH_VERSION-}" ]; then
source tab-qiime
fi
# ----------------------------------------------------------------------------
# Copyright (c) 2016-2019, QIIME 2 development team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# ----------------------------------------------------------------------------
import click
import q2cli.commands
ROOT_COMMAND_HELP = """\
QIIME 2 command-line interface (q2cli)
--------------------------------------
To get help with QIIME 2, visit https://qiime2.org.
To enable tab completion in Bash, run the following command or add it to your \
.bashrc/.bash_profile:
source tab-qiime
To enable tab completion in ZSH, run the following commands or add them to \
your .zshrc:
autoload bashcompinit && bashcompinit && source tab-qiime
"""
# Entry point for CLI
@click.command(cls=q2cli.commands.RootCommand, invoke_without_command=True,
no_args_is_help=True, help=ROOT_COMMAND_HELP)
@click.version_option(prog_name='q2cli',
message='%(prog)s version %(version)s\nRun `qiime info` '
'for more version details.')
def qiime():
pass
if __name__ == '__main__':
qiime()
# ----------------------------------------------------------------------------
# Copyright (c) 2016-2019, QIIME 2 development team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# ----------------------------------------------------------------------------
import click
from q2cli.click.command import ToolCommand, ToolGroupCommand
@click.group(help='Utilities for developers and advanced users.',
cls=ToolGroupCommand)
def dev():
pass
@dev.command(name='refresh-cache',
short_help='Refresh CLI cache.',
help="Refresh the CLI cache. Use this command if you are "
"developing a plugin, or q2cli itself, and want your "
"changes to take effect in the CLI. A refresh of the cache "
"is necessary because package versions do not typically "
"change each time an update is made to a package's code. "
"Setting the environment variable Q2CLIDEV to any value "
"will always refresh the cache when a command is run.",
cls=ToolCommand)
def refresh_cache():
import q2cli.core.cache
q2cli.core.cache.CACHE.refresh()
import_theme_help = \
("Allows for customization of q2cli's command line styling based on an "
"imported .theme (INI formatted) file. If you are unfamiliar with .ini "
"formatted files look here https://en.wikipedia.org/wiki/INI_file."
"\n"
"\n"
"The .theme file allows you to customize text on the basis of what that "
"text represents with the following supported text types: command, "
"option, type, default_arg, required, emphasis, problem, warning, error, "
"and success. These will be your headers in the '[]' brackets. "
"\n"
"\n"
"`command` refers to the name of the command you issued. `option` refers "
"to the arguments you give to the command when running it. `type` refers "
"to the QIIME 2 semantic typing of these arguments (where applicable). "
"`default_arg` refers to the label next to the argument indicating its "
"default value (where applicable), and if it is required (where "
"applicable). `required` refers to any arguments that must be passed to "
"the command for it to work and gives them special formatting on top of "
"your normal `option` formatting. `emphasis` refers to any emphasized "
"pieces of text within help text. `problem` refers to the text informing "
"you there were issues with the command. `warning` refers to the text "
"for non-fatal issues while `error` refers to the text for fatal issues."
"`success` refers to text indicating a process completed as expected."
"\n"
"\n"
"Depending on what your terminal supports, some or all of the following "
"pieces of the text's formatting may be customized: bold, dim (if true "
"the text's brightness is reduced), underline, blink, reverse (if true "
"foreground and background colors are reversed), and finally fg "
"(foreground color) and bg (background color). The first five may each "
"be either true or false, while the colors may be set to any of the "
"following: black, red, green, yellow, blue, magenta, cyan, white, "
"bright_black, bright_red, bright_green, bright_yellow, bright_blue, "
"bright_magenta, bright_cyan, or bright_white.")
@dev.command(name='import-theme',
short_help='Install new command line theme.',
help=import_theme_help,
cls=ToolCommand)
@click.option('--theme', required=True,
type=click.Path(exists=True, file_okay=True,
dir_okay=False, readable=True),
help='Path to file containing new theme info')
def import_theme(theme):
import os
import shutil
from configparser import Error
import q2cli.util
from q2cli.core.config import CONFIG
try:
CONFIG.parse_file(theme)
except Error as e:
# If they tried to change [error] in a valid manner before we hit our
# parsing error, we don't want to use their imported error settings
CONFIG.styles = CONFIG.get_default_styles()
header = 'Something went wrong while parsing your theme: '
q2cli.util.exit_with_error(e, header=header, traceback=None)
shutil.copy(theme, os.path.join(q2cli.util.get_app_dir(),
'cli-colors.theme'))
@dev.command(name='export-default-theme',
short_help='Export the default settings.',
help='Create a .theme (INI formatted) file from the default '
'settings at the specified filepath.',
cls=ToolCommand)
@click.option('--output-path', required=True,
type=click.Path(exists=False, file_okay=True,
dir_okay=False, readable=True),
help='Path to output the config to')
def export_default_theme(output_path):
import configparser
from q2cli.core.config import CONFIG
parser = configparser.ConfigParser()
parser.read_dict(CONFIG.get_default_styles())
with open(output_path, 'w') as fh:
parser.write(fh)
def abort_if_false(ctx, param, value):
if not value:
ctx.abort()
@dev.command(name='reset-theme',
short_help='Reset command line theme to default.',
help="Reset command line theme to default. Requres the '--yes' "
"parameter to be passed asserting you do want to reset.",
cls=ToolCommand)
@click.option('--yes', is_flag=True, callback=abort_if_false,
expose_value=False,
prompt='Are you sure you want to reset your theme?')
def reset_theme():
import os
import q2cli.util
path = os.path.join(q2cli.util.get_app_dir(), 'cli-colors.theme')
if os.path.exists(path):
os.unlink(path)
click.echo('Theme reset.')
else:
click.echo('Theme was already default.')
# ----------------------------------------------------------------------------
# Copyright (c) 2016-2019, QIIME 2 development team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# ----------------------------------------------------------------------------
import click
from q2cli.click.command import ToolCommand
def _echo_version():
import sys
import qiime2
import q2cli
pyver = sys.version_info
click.echo('Python version: %d.%d.%d' %
(pyver.major, pyver.minor, pyver.micro))
click.echo('QIIME 2 release: %s' % qiime2.__release__)
click.echo('QIIME 2 version: %s' % qiime2.__version__)
click.echo('q2cli version: %s' % q2cli.__version__)
def _echo_plugins():
import q2cli.core.cache
plugins = q2cli.core.cache.CACHE.plugins
if plugins:
for name, plugin in sorted(plugins.items()):
click.echo('%s: %s' % (name, plugin['version']))
else:
click.secho('No plugins are currently installed.\nYou can browse '
'the official QIIME 2 plugins at https://qiime2.org')
@click.command(help='Display information about current deployment.',
cls=ToolCommand)
def info():
import q2cli.util
# This import improves performance for repeated _echo_plugins
import q2cli.core.cache
click.secho('System versions', fg='green')
_echo_version()
click.secho('\nInstalled plugins', fg='green')
_echo_plugins()
click.secho('\nApplication config directory', fg='green')
click.secho(q2cli.util.get_app_dir())
click.secho('\nGetting help', fg='green')
click.secho('To get help with QIIME 2, visit https://qiime2.org')
# ----------------------------------------------------------------------------
# Copyright (c) 2016-2019, QIIME 2 development team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# ----------------------------------------------------------------------------
import os
import click
import q2cli.util
from q2cli.click.command import ToolCommand, ToolGroupCommand
_COMBO_METAVAR = 'ARTIFACT/VISUALIZATION'
@click.group(help='Tools for working with QIIME 2 files.',
cls=ToolGroupCommand)
def tools():
pass
@tools.command(name='export',
short_help='Export data from a QIIME 2 Artifact '
'or a Visualization',
help='Exporting extracts (and optionally transforms) data '
'stored inside an Artifact or Visualization. Note that '
'Visualizations cannot be transformed with --output-format',
cls=ToolCommand)
@click.option('--input-path', required=True, metavar=_COMBO_METAVAR,
type=click.Path(exists=True, file_okay=True,
dir_okay=False, readable=True),
help='Path to file that should be exported')
@click.option('--output-path', required=True,
type=click.Path(exists=False, file_okay=True, dir_okay=True,
writable=True),
help='Path to file or directory where '
'data should be exported to')
@click.option('--output-format', required=False,
help='Format which the data should be exported as. '
'This option cannot be used with Visualizations')
def export_data(input_path, output_path, output_format):
import qiime2.util
import qiime2.sdk
import distutils
from q2cli.core.config import CONFIG
result = qiime2.sdk.Result.load(input_path)
if output_format is None:
if isinstance(result, qiime2.sdk.Artifact):
output_format = result.format.__name__
else:
output_format = 'Visualization'
result.export_data(output_path)
else:
if isinstance(result, qiime2.sdk.Visualization):
error = '--output-format cannot be used with visualizations'
click.echo(CONFIG.cfg_style('error', error), err=True)
click.get_current_context().exit(1)
else:
source = result.view(qiime2.sdk.parse_format(output_format))
if os.path.isfile(str(source)):
if os.path.isfile(output_path):
os.remove(output_path)
else:
# create directory (recursively) if it doesn't exist yet
os.makedirs(os.path.dirname(output_path), exist_ok=True)
qiime2.util.duplicate(str(source), output_path)
else:
distutils.dir_util.copy_tree(str(source), output_path)
output_type = 'file' if os.path.isfile(output_path) else 'directory'
success = 'Exported %s as %s to %s %s' % (input_path, output_format,
output_type, output_path)
click.echo(CONFIG.cfg_style('success', success))
def show_importable_types(ctx, param, value):
if not value or ctx.resilient_parsing:
return
import qiime2.sdk
importable_types = sorted(qiime2.sdk.PluginManager().importable_types,
key=repr)
if importable_types:
for name in importable_types:
click.echo(name)
else:
click.echo('There are no importable types in the current deployment.')
ctx.exit()
def show_importable_formats(ctx, param, value):
if not value or ctx.resilient_parsing:
return
import qiime2.sdk
importable_formats = sorted(qiime2.sdk.PluginManager().importable_formats)
if importable_formats:
for format in importable_formats:
click.echo(format)
else:
click.echo('There are no importable formats '
'in the current deployment.')
ctx.exit()
@tools.command(name='import',
short_help='Import data into a new QIIME 2 Artifact.',
help="Import data to create a new QIIME 2 Artifact. See "
"https://docs.qiime2.org/ for usage examples and details "
"on the file types and associated semantic types that can "
"be imported.",
cls=ToolCommand)
@click.option('--type', required=True,
help='The semantic type of the artifact that will be created '
'upon importing. Use --show-importable-types to see what '
'importable semantic types are available in the current '
'deployment.')
@click.option('--input-path', required=True,
type=click.Path(exists=True, file_okay=True, dir_okay=True,
readable=True),
help='Path to file or directory that should be imported.')
@click.option('--output-path', required=True, metavar='ARTIFACT',
type=click.Path(exists=False, file_okay=True, dir_okay=False,
writable=True),
help='Path where output artifact should be written.')
@click.option('--input-format', required=False,
help='The format of the data to be imported. If not provided, '
'data must be in the format expected by the semantic type '
'provided via --type.')
@click.option('--show-importable-types', is_flag=True, is_eager=True,
callback=show_importable_types, expose_value=False,
help='Show the semantic types that can be supplied to --type '
'to import data into an artifact.')
@click.option('--show-importable-formats', is_flag=True, is_eager=True,
callback=show_importable_formats, expose_value=False,
help='Show formats that can be supplied to --input-format to '
'import data into an artifact.')
def import_data(type, input_path, output_path, input_format):
import qiime2.sdk
import qiime2.plugin
from q2cli.core.config import CONFIG
try:
artifact = qiime2.sdk.Artifact.import_data(type, input_path,
view_type=input_format)
except qiime2.plugin.ValidationError as e:
header = 'There was a problem importing %s:' % input_path
q2cli.util.exit_with_error(e, header=header, traceback=None)
except Exception as e:
header = 'An unexpected error has occurred:'
q2cli.util.exit_with_error(e, header=header)
artifact.save(output_path)
if input_format is None:
input_format = artifact.format.__name__
success = 'Imported %s as %s to %s' % (input_path,
input_format,
output_path)
click.echo(CONFIG.cfg_style('success', success))
@tools.command(short_help='Take a peek at a QIIME 2 Artifact or '
'Visualization.',
help="Display basic information about a QIIME 2 Artifact or "
"Visualization, including its UUID and type.",
cls=ToolCommand)
@click.argument('path', type=click.Path(exists=True, file_okay=True,
dir_okay=False, readable=True),
metavar=_COMBO_METAVAR)
def peek(path):
import qiime2.sdk
from q2cli.core.config import CONFIG
metadata = qiime2.sdk.Result.peek(path)
click.echo(CONFIG.cfg_style('type', "UUID")+": ", nl=False)
click.echo(metadata.uuid)
click.echo(CONFIG.cfg_style('type', "Type")+": ", nl=False)
click.echo(metadata.type)
if metadata.format is not None:
click.echo(CONFIG.cfg_style('type', "Data format")+": ", nl=False)
click.echo(metadata.format)
@tools.command('inspect-metadata',
short_help='Inspect columns available in metadata.',
help='Inspect metadata files or artifacts viewable as metadata.'
' Providing multiple file paths to this command will merge'
' the metadata.',
cls=ToolCommand)
@click.option('--tsv/--no-tsv', default=False,
help='Print as machine-readable TSV instead of text.')
@click.argument('paths', nargs=-1, required=True, metavar='METADATA...',
type=click.Path(exists=True, file_okay=True, dir_okay=False,
readable=True))
@q2cli.util.pretty_failure(traceback=None)
def inspect_metadata(paths, tsv, failure):
m = [_load_metadata(p) for p in paths]
metadata = m[0]
if m[1:]:
metadata = metadata.merge(*m[1:])
# we aren't expecting errors below this point, so set traceback to default
failure.traceback = 'stderr'
failure.header = "An unexpected error has occurred:"
COLUMN_NAME = "COLUMN NAME"
COLUMN_TYPE = "TYPE"
max_name_len = max([len(n) for n in metadata.columns]
+ [len(COLUMN_NAME)])
max_type_len = max([len(p.type) for p in metadata.columns.values()]
+ [len(COLUMN_TYPE)])
if tsv:
import csv
import io
def formatter(*row):
# This is gross, but less gross than robust TSV writing.
with io.StringIO() as fh:
writer = csv.writer(fh, dialect='excel-tab', lineterminator='')
writer.writerow(row)
return fh.getvalue()
else:
formatter = ("{0:>%d} {1:%d}" % (max_name_len, max_type_len)).format
click.secho(formatter(COLUMN_NAME, COLUMN_TYPE), bold=True)
if not tsv:
click.secho(formatter("=" * max_name_len, "=" * max_type_len),
bold=True)
for name, props in metadata.columns.items():
click.echo(formatter(name, props.type))
if not tsv:
click.secho(formatter("=" * max_name_len, "=" * max_type_len),
bold=True)
click.secho(("{0:>%d} " % max_name_len).format("IDS:"),
bold=True, nl=False)
click.echo(metadata.id_count)
click.secho(("{0:>%d} " % max_name_len).format("COLUMNS:"),
bold=True, nl=False)
click.echo(metadata.column_count)
def _load_metadata(path):
import qiime2
import qiime2.sdk
# TODO: clean up duplication between this and the metadata handlers.
try:
artifact = qiime2.sdk.Result.load(path)
except Exception:
metadata = qiime2.Metadata.load(path)
else:
if isinstance(artifact, qiime2.Visualization):
raise Exception("Visualizations cannot be viewed as QIIME 2"
" metadata:\n%r" % path)
elif artifact.has_metadata():
metadata = artifact.view(qiime2.Metadata)
else:
raise Exception("Artifacts with type %r cannot be viewed as"
" QIIME 2 metadata:\n%r" % (artifact.type, path))
return metadata
@tools.command(short_help='View a QIIME 2 Visualization.',
help="Displays a QIIME 2 Visualization until the command "
"exits. To open a QIIME 2 Visualization so it can be "
"used after the command exits, use 'qiime tools extract'.",
cls=ToolCommand)
@click.argument('visualization-path', metavar='VISUALIZATION',
type=click.Path(exists=True, file_okay=True, dir_okay=False,
readable=True))
@click.option('--index-extension', required=False, default='html',
help='The extension of the index file that should be opened. '
'[default: html]')
def view(visualization_path, index_extension):
# Guard headless envs from having to import anything large
import sys
from q2cli.core.config import CONFIG
if not os.getenv("DISPLAY") and sys.platform != "darwin":
raise click.UsageError(
'Visualization viewing is currently not supported in headless '
'environments. You can view Visualizations (and Artifacts) at '
'https://view.qiime2.org, or move the Visualization to an '
'environment with a display and view it with `qiime tools view`.')
import zipfile
import qiime2.sdk
if index_extension.startswith('.'):
index_extension = index_extension[1:]
try:
visualization = qiime2.sdk.Visualization.load(visualization_path)
# TODO: currently a KeyError is raised if a zipped file that is not a
# QIIME 2 result is passed. This should be handled better by the framework.
except (zipfile.BadZipFile, KeyError, TypeError):
raise click.BadParameter(
'%s is not a QIIME 2 Visualization. Only QIIME 2 Visualizations '
'can be viewed.' % visualization_path)
index_paths = visualization.get_index_paths(relative=False)
if index_extension not in index_paths:
raise click.BadParameter(
'No index %s file is present in the archive. Available index '
'extensions are: %s' % (index_extension,
', '.join(index_paths.keys())))
else:
index_path = index_paths[index_extension]
launch_status = click.launch(index_path)
if launch_status != 0:
click.echo(CONFIG.cfg_style('error', 'Viewing visualization '
'failed while attempting to open '
f'{index_path}'), err=True)
else:
while True:
click.echo(
"Press the 'q' key, Control-C, or Control-D to quit. This "
"view may no longer be accessible or work correctly after "
"quitting.", nl=False)
# There is currently a bug in click.getchar where translation
# of Control-C and Control-D into KeyboardInterrupt and
# EOFError (respectively) does not work on Python 3. The code
# here should continue to work as expected when the bug is
# fixed in Click.
#
# https://github.com/pallets/click/issues/583
try:
char = click.getchar()
click.echo()
if char in {'q', '\x03', '\x04'}:
break
except (KeyboardInterrupt, EOFError):
break
@tools.command(short_help="Extract a QIIME 2 Artifact or Visualization "
"archive.",
help="Extract all contents of a QIIME 2 Artifact or "
"Visualization's archive, including provenance, metadata, "
"and actual data. Use 'qiime tools export' to export only "
"the data stored in an Artifact or Visualization, with "
"the choice of exporting to different formats.",
cls=ToolCommand)
@click.option('--input-path', required=True, metavar=_COMBO_METAVAR,
type=click.Path(exists=True, file_okay=True, dir_okay=False,
readable=True),
help='Path to file that should be extracted')
@click.option('--output-path', required=False,
type=click.Path(exists=False, file_okay=False, dir_okay=True,
writable=True),
help='Directory where archive should be extracted to '
'[default: current working directory]',
default=os.getcwd())
def extract(input_path, output_path):
import zipfile
import qiime2.sdk
from q2cli.core.config import CONFIG
try:
extracted_dir = qiime2.sdk.Result.extract(input_path, output_path)
except (zipfile.BadZipFile, ValueError):
raise click.BadParameter(
'%s is not a valid QIIME 2 Result. Only QIIME 2 Artifacts and '
'Visualizations can be extracted.' % input_path)
else:
success = 'Extracted %s to directory %s' % (input_path, extracted_dir)
click.echo(CONFIG.cfg_style('success', success))
@tools.command(short_help='Validate data in a QIIME 2 Artifact.',
help='Validate data in a QIIME 2 Artifact. QIIME 2 '
'automatically performs some basic validation when '
'managing your data; use this command to perform explicit '
'and/or more thorough validation of your data (e.g. when '
'debugging issues with your data or analyses).\n\nNote: '
'validation can take some time to complete, depending on '
'the size and type of your data.',
cls=ToolCommand)
@click.argument('path', type=click.Path(exists=True, file_okay=True,
dir_okay=False, readable=True),
metavar=_COMBO_METAVAR)
@click.option('--level', required=False, type=click.Choice(['min', 'max']),
help='Desired level of validation. "min" will perform minimal '
'validation, and "max" will perform maximal validation (at '
'the potential cost of runtime).',
default='max', show_default=True)
def validate(path, level):
import qiime2.sdk
from q2cli.core.config import CONFIG
try:
result = qiime2.sdk.Result.load(path)
except Exception as e:
header = 'There was a problem loading %s as a QIIME 2 Result:' % path
q2cli.util.exit_with_error(e, header=header)
try:
result.validate(level)
except qiime2.plugin.ValidationError as e:
header = 'Result %s does not appear to be valid at level=%s:' % (
path, level)
q2cli.util.exit_with_error(e, header=header, traceback=None)
except Exception as e:
header = ('An unexpected error has occurred while attempting to '
'validate result %s:' % path)
q2cli.util.exit_with_error(e, header=header)
else:
click.echo(CONFIG.cfg_style('success', f'Result {path} appears to be '
f'valid at level={level}.'))
@tools.command(short_help='Print citations for a QIIME 2 result.',
help='Print citations as a BibTex file (.bib) for a QIIME 2'
' result.',
cls=ToolCommand)
@click.argument('path', type=click.Path(exists=True, file_okay=True,
dir_okay=False, readable=True),
metavar=_COMBO_METAVAR)
def citations(path):
import qiime2.sdk
import io
from q2cli.core.config import CONFIG
ctx = click.get_current_context()
try:
result = qiime2.sdk.Result.load(path)
except Exception as e:
header = 'There was a problem loading %s as a QIIME 2 result:' % path
q2cli.util.exit_with_error(e, header=header)
if result.citations:
with io.StringIO() as fh:
result.citations.save(fh)
click.echo(fh.getvalue(), nl=False)
ctx.exit(0)
else:
click.echo(CONFIG.cfg_style('problem', 'No citations found.'),
err=True)
ctx.exit(1)