I have code like this:
import click
@click.group()
def entry_point():
pass
entry_point.add_command(lidtk.data.download_documents.main)
entry_point.add_command(lidtk.data.create_ml_dataset.main)
entry_point.add_command(lidtk.classifiers.text_cat.textcat_ngram.cli)
which gives the help text:
lidtk --help
Usage: lidtk [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
create-dataset Create sharable dataset from downloaded...
download Download 1000 documents of each language.
textcat
which is over all pretty close to what I want. But I would like to change the order to:
Commands:
download Download 1000 documents of each language.
create-dataset Create sharable dataset from downloaded...
textcat
How can this be done using click?
Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary. It's the “Command Line Interface Creation Kit”.
This is a special attribute where commands are supposed to remember what they need to pass on to their children. In order for this to work, we need to mark our function with pass_context() , because otherwise, the context object would be entirely hidden from us.
Click actually implements its own parsing of arguments and does not use optparse or argparse following the optparse parsing behavior. The reason it's not based on argparse is that argparse does not allow proper nesting of commands by design and has some deficiencies when it comes to POSIX compliant argument handling.
The order of the commands listed by help is set by the list_commands()
method of the click.Group
class. So, one way to approach the desire to change the help listing order is to inherit for click.Group
and override list_commands
to give the desired order.
This class overrides the click.Group.command()
method which is used to decorate command functions. It adds the ability to specify a help_priority
, which allows the sort order to be modified as desired:
class SpecialHelpOrder(click.Group):
def __init__(self, *args, **kwargs):
self.help_priorities = {}
super(SpecialHelpOrder, self).__init__(*args, **kwargs)
def get_help(self, ctx):
self.list_commands = self.list_commands_for_help
return super(SpecialHelpOrder, self).get_help(ctx)
def list_commands_for_help(self, ctx):
"""reorder the list of commands when listing the help"""
commands = super(SpecialHelpOrder, self).list_commands(ctx)
return (c[1] for c in sorted(
(self.help_priorities.get(command, 1), command)
for command in commands))
def command(self, *args, **kwargs):
"""Behaves the same as `click.Group.command()` except capture
a priority for listing command names in help.
"""
help_priority = kwargs.pop('help_priority', 1)
help_priorities = self.help_priorities
def decorator(f):
cmd = super(SpecialHelpOrder, self).command(*args, **kwargs)(f)
help_priorities[cmd.name] = help_priority
return cmd
return decorator
By passing the cls
parameter to the click.group()
decorator, any commands added to the group via the the group.command()
can be passed a help_priority
. The priorities default to 1, and lower numbers are printed first.
@click.group(cls=SpecialHelpOrder)
def cli():
"""My Excellent CLI"""
@cli.command(help_priority=5)
def my_command():
....
This works because click is a well designed OO framework. The @click.group()
decorator usually instantiates a click.Group
object but allows this behavior to be over ridden with the cls
parameter. So it is a relatively easy matter to inherit from click.Group
in our own class and over ride the desired methods.
Steps here:
Group.command()
so that decorated commands can be passed a help_priority
. In the over ridden decorator, capture the desired priority for laterGroup.get_help()
. In the over ridden method, substitute Group.list_commands
with a list_commands
which will order the commands as desired.import click
@click.group(cls=SpecialHelpOrder)
def cli():
pass
@cli.command()
def command1():
'''Command #1'''
@cli.command(help_priority=5)
def command2():
'''Command #2'''
@cli.command()
def command3():
'''Command #3'''
if __name__ == '__main__':
cli('--help'.split())
Usage: test.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
command1 Command #1
command3 Command #3
command2 Command #2
It's a nice description at the top, but we can achieve it more easily
import click
import collections
from .aws import aws_group
from .db import db_group
from .front import front_group
from .celery import celery_group
from .i18n import i18n_group
from .deprecated import add_deprecated
class OrderedGroup(click.Group):
def __init__(self, name=None, commands=None, **attrs):
super(OrderedGroup, self).__init__(name, commands, **attrs)
#: the registered subcommands by their exported names.
self.commands = commands or collections.OrderedDict()
def list_commands(self, ctx):
return self.commands
@click.group(cls=OrderedGroup)
def entire_group():
"""Entire Group"""
entire_group.add_command(aws_group)
entire_group.add_command(db_group)
entire_group.add_command(front_group)
entire_group.add_command(celery_group)
entire_group.add_command(i18n_group)
add_deprecated(entire_group)
Just change self.commands
from Dict
to OrderedDict
. As a result, my deprecated commands at a bottom of list.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With