From b284e6f820567e6936a29bcc67bb0c664d95a906 Mon Sep 17 00:00:00 2001 From: Bruce Martin Date: Thu, 14 Nov 2019 13:02:40 -0800 Subject: [PATCH] Improve CLI help (#1025) * launch option changes * more CLI help improvements * change plot help * additional changes requested * change metavars for options and subcommand --- server/cli/cli.py | 13 +++++- server/cli/launch.py | 104 ++++++++++++++++++++++++++++-------------- server/cli/prepare.py | 53 +++++++++++++-------- server/utils/utils.py | 9 ++++ 4 files changed, 123 insertions(+), 56 deletions(-) diff --git a/server/cli/cli.py b/server/cli/cli.py index 969ec83c..e7f644ec 100644 --- a/server/cli/cli.py +++ b/server/cli/cli.py @@ -4,8 +4,17 @@ from .launch import launch from .prepare import prepare -@click.group(name="cellxgene", context_settings=dict(max_content_width=85)) -@click.version_option(version="0.12.0", prog_name="cellxgene", message="[%(prog)s] Version %(version)s") +@click.group(name="cellxgene", + subcommand_metavar="COMMAND ", + options_metavar="", + context_settings=dict(max_content_width=85, + help_option_names=['-h', '--help'])) +@click.help_option("--help", "-h", help="Show this message and exit.") +@click.version_option( + version="0.12.0", + prog_name="cellxgene", + message="[%(prog)s] Version %(version)s", + help="Show the software version and exit.") def cli(): pass diff --git a/server/cli/launch.py b/server/cli/launch.py index f5b13d9f..1d9b5691 100644 --- a/server/cli/launch.py +++ b/server/cli/launch.py @@ -13,7 +13,7 @@ import click from server.app.app import Server from server.app.util.errors import ScanpyFileError from server.app.util.utils import custom_format_warning -from server.utils.utils import find_available_port, is_port_available +from server.utils.utils import find_available_port, is_port_available, sort_options from server.app.util.data_locator import DataLocator # anything bigger than this will generate a special message @@ -25,55 +25,70 @@ def common_args(func): Decorator to contain CLI args that will be common to both CLI and GUI: title and engine args. """ - @click.option("--title", "-t", help="Title to display (if omitted will use file name).") - @click.option("--about", - help="A URL to more information about the dataset." - "(This must be an absolute URL including HTTP(S) protocol)") + @click.option( + "--title", + "-t", + metavar="", + help="Title to display. If omitted will use file name.") + @click.option( + "--about", + metavar="", + help="URL providing more information about the dataset " + "(hint: must be a fully specified absolute URL).") @click.option( "--embedding", "-e", default=[], multiple=True, show_default=False, + metavar="", help="Embedding name, eg, 'umap'. Repeat option for multiple embeddings. Defaults to all." ) - @click.option("--obs-names", default=None, metavar="", help="Name of annotation field to use for observations.") - @click.option("--var-names", default=None, metavar="", help="Name of annotation to use for variables.") + @click.option( + "--obs-names", + "-obs", + default=None, + metavar="", + help="Name of annotation field to use for observations. If not specified cellxgene will use the the obs index.") + @click.option( + "--var-names", + "-var", + default=None, + metavar="", + help="Name of annotation to use for variables. If not specified cellxgene will use the the var index.") @click.option( "--max-category-items", default=1000, - metavar="", + metavar="", show_default=True, - help="Categories with more distinct values than this will not be displayed.", - ) + help="Will not display categories with more distinct values than specified.",) @click.option( "--diffexp-lfc-cutoff", + "-de", default=0.01, show_default=True, - help="Relative expression cutoff used when selecting top N differentially expressed genes", - ) + metavar="", + help="Minimum log fold change threshold for differential expression.",) @click.option( "--experimental-label-file", default=None, show_default=True, multiple=False, - metavar="", - help="CSV file containing user annotations; will be overwritten. Created if does not exist.", - ) + metavar="", + help="CSV file containing user annotations; will be overwritten. Created if does not exist.",) @click.option( "--backed", + "-b", is_flag=True, default=False, show_default=False, - help="Load data in file-backed mode, which may save memory, but result in slower overall performance." - ) + help="Load data in file-backed mode. This may save memory, but may result in slower overall performance.") @click.option( "--disable-diffexp", is_flag=True, default=False, show_default=False, - help="Disable on-demand differential expression." - ) + help="Disable on-demand differential expression.") @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) @@ -96,17 +111,26 @@ def parse_engine_args(embedding, obs_names, var_names, max_category_items, } -@click.command() -@click.argument("data", nargs=1, metavar="", required=True) +@sort_options +@click.command(short_help="Launch the cellxgene data viewer. " + "Run `cellxgene launch --help` for more information.", + options_metavar="",) +@click.argument("data", nargs=1, metavar="", required=True) @click.option( "--verbose", "-v", is_flag=True, default=False, show_default=True, - help="Provide verbose output, including warnings and all server requests.", -) -@click.option("--debug", is_flag=True, default=False, show_default=True, help="Run in debug mode.") + help="Provide verbose output, including warnings and all server requests.",) +@click.option( + "--debug", + "-d", + is_flag=True, + default=False, + show_default=True, + help="Run in debug mode. This is helpful for cellxgene developers, " + "or when you want more information about an error condition.",) @click.option( "--open", "-o", @@ -114,18 +138,29 @@ def parse_engine_args(embedding, obs_names, var_names, max_category_items, is_flag=True, default=False, show_default=True, - help="Open the web browser after launch.", -) -@click.option("--port", "-p", help="Port to run server on, if not specified cellxgene will find an available port.", - metavar="", show_default=True) -@click.option("--host", default="127.0.0.1", help="Host IP address") + help="Open web browser after launch.",) +@click.option( + "--port", + "-p", + metavar="", + show_default=True, + help="Port to run server on. If not specified cellxgene will find an available port.",) +@click.option( + "--host", + metavar="", + default="127.0.0.1", + show_default=False, + help="Host IP address. By default cellxgene will use localhost (e.g. 127.0.0.1).") @click.option( "--scripts", + "-s", default=[], multiple=True, - help="Additional script files to include in html page", - show_default=True, -) + metavar="", + help="Additional script files to include in HTML page. If not specified, " + "no additional script files will be included.", + show_default=False,) +@click.help_option("--help", "-h", help="Show this message and exit.") @common_args def launch( data, @@ -148,8 +183,9 @@ def launch( ): """Launch the cellxgene data viewer. This web app lets you explore single-cell expression data. - Data must be in a format that cellxgene expects, read the - "getting started" guide. + Data must be in a format that cellxgene expects. + Read the "getting started" guide to learn more: + https://chanzuckerberg.github.io/cellxgene/getting-started.html Examples: diff --git a/server/cli/prepare.py b/server/cli/prepare.py index 730e4f86..224fa17c 100644 --- a/server/cli/prepare.py +++ b/server/cli/prepare.py @@ -4,16 +4,21 @@ import click from numpy import ndarray, unique from scipy.sparse.csc import csc_matrix +from server.utils.utils import sort_options -@click.command() -@click.argument("data", nargs=1, metavar="", required=True) + +@sort_options +@click.command(short_help="Preprocess data for use with cellxgene. " + "Run `cellxgene prepare --help` for more information.", + options_metavar="",) +@click.argument("data", nargs=1, metavar="", required=True) @click.option( "--embedding", "-e", default=["umap", "tsne"], multiple=True, type=click.Choice(["umap", "tsne"]), - help="Embedding algorithm", + help="Embedding algorithm(s). Repeat option for multiple embeddings.", show_default=True, ) @click.option( @@ -25,21 +30,29 @@ from scipy.sparse.csc import csc_matrix show_default=True, ) @click.option("--output", "-o", default="", help="Save a new file to filename.", metavar="") -@click.option("--plotting", "-p", default=False, is_flag=True, help="Whether to generate plots.", show_default=True) -@click.option("--sparse", default=False, is_flag=True, help="Whether to force sparsity.", show_default=True) +@click.option("--plotting", "-p", default=False, is_flag=True, help="Generate plots.", show_default=True) +@click.option("--sparse", default=False, is_flag=True, help="Force sparsity.", show_default=True) @click.option("--overwrite", default=False, is_flag=True, help="Allow file overwriting.", show_default=True) @click.option("--set-obs-names", default="", help="Named field to set as index for obs.", metavar="") @click.option("--set-var-names", default="", help="Named field to set as index for var.", metavar="") +@click.option("--skip-qc", default=False, is_flag=True, + help="Do not run quality control metrics. By default cellxgene runs them " + "(saved to adata.obs and adata.var; see scanpy.pp.calculate_qc_metrics for details).") @click.option( - "--run-qc/--skip-qc", default=True, is_flag=True, - help="Whether to calculate QC metrics (saved to adata.obs and adata.var). \ - See scanpy.pp.calculate_qc_metrics for details.", show_default=True) -@click.option( - "--make-obs-names-unique", default=True, is_flag=True, help="Ensure obs index is unique.", show_default=True + "--make-obs-names-unique", + default=True, + is_flag=True, + help="Ensure obs index is unique.", + show_default=True ) @click.option( - "--make-var-names-unique", default=True, is_flag=True, help="Ensure var index is unique.", show_default=True + "--make-var-names-unique", + default=True, + is_flag=True, + help="Ensure var index is unique.", + show_default=True ) +@click.help_option("--help", "-h", help="Show this message and exit.") def prepare( data, embedding, @@ -50,18 +63,18 @@ def prepare( overwrite, set_obs_names, set_var_names, - run_qc, + skip_qc, make_obs_names_unique, make_var_names_unique, ): - """Preprocesses data for use with cellxgene. - - This tool runs a series of scanpy routines for preparing a dataset - for use with cellxgene. It loads data from different formats + """ + Preprocess data for use with cellxgene. + This tool runs a series of scanpy routines for preparing a dataset for use + with cellxgene. It loads data from different formats (h5ad, loom, or a 10x directory), runs dimensionality reduction, computes nearest neighbors, computes an embedding, performs clustering, - and saves the results. Includes additional options for naming - annotations, ensuring sparsity, and plotting results.""" + and saves the results. Includes additional options for naming annotations, + ensuring sparsity, and plotting results.""" # collect slow imports here to make CLI startup more responsive click.echo("[cellxgene] Starting CLI...") @@ -129,7 +142,7 @@ def prepare( return adata def calculate_qc_metrics(adata): - if run_qc: + if not skip_qc: sc.pp.calculate_qc_metrics(adata, inplace=True) return adata @@ -179,7 +192,7 @@ def prepare( sc.pl.tsne(adata, color="louvain", palette=palette, save="_louvain") def show_step(item): - if run_qc: + if not skip_qc: qc_name = "Calculating QC metrics" else: qc_name = "Skipping QC" diff --git a/server/utils/utils.py b/server/utils/utils.py index 239f45bd..417aeb7b 100644 --- a/server/utils/utils.py +++ b/server/utils/utils.py @@ -24,3 +24,12 @@ def is_port_available(host, port): except socket.error: pass return is_available + + +def sort_options(command): + """ + Helper for the click options - will sort options in a command, and can + be used as a decorator. + """ + command.params.sort(key=lambda p: p.name) + return command