Commands and Groups
The capability of arbitrarily nesting command line utilities is the most important feature
of Click.This is implemented through the Command and Group (actually MultiCommand).
Command Vs MultiCommand(Group):
For a regular command, the callback is executed whenever the command runs. If the script
is the only command, it will always fire (unless a parameter callback prevents it. This
for instance happens if someone passes --help to the script).For groups and multi commands,
the callback fires whenever a subcommand fires (unless this behaviour is changed). What
this means in practice is that an outer command runs when an inner command runs. To
create a group of commands(cli and sync) ,we write the following script and name it
tool.py:
import click
@click.group()
@click.option('--debug/--no-debug', default=False)
def cli(debug):
click.echo('Debug mode is %s' % ('on' if debug else 'off'))
@cli.command() # @cli, not @click!
def sync():
click.echo('Syncing')
You will need to edit the setup.py file and then test the script:
$mytool sync
Debug mode is off
Syncing
Passing Parameters:
Click strictly separates parameters between commands and subcommands. What this means is
that options and arguments for a specific command have to be specified after the command
name itself, but before any other command names.This behavior is already observable with
the predefined --help option. Suppose we have a program called tool.py, containing a
subcommand called sub.
mytool --help will return the help for the whole program (listing subcommands).
mytool sync --help will return the help for the sub subcommand.
But mytool --help sync will treat --help as an argument for the main program. Click then
invokes the callback for --help, which prints the help and aborts the program before click
can process the subcommand.
Nested Commands:
In the example above, the basic command group accepts a debug argument which is passed to
its callback, but not to the sync command itself. The sync command only accepts its own
arguments.This allows tools.py to act completely independent of each other, but one
command talks to a nested one through an object called Context.Each time a command is
invoked, a new context is created and linked with the parent context. Normally, you can%u2019t
see these contexts, but they are there. Contexts are passed to parameter callbacks
together with the value automatically. Commands can also ask for the context to be passed
by marking themselves with the pass_context() decorator. In that case, the context is
passed as first argument.
The context can also carry a program specified object that can be used for the program%u2019s
purposes. What this means is ,like in the script below, we use the context object to modify
or use the options passed to the command in parameters like debug and name:
import click
@click.group()
@click.option('--debug/--no-debug', default=False)
@click.option('--name', default=None)
@click.pass_context
def cli(ctx, debug,name):
# ensure that ctx.obj exists and is a dict (in case `cli()` is called
# by means other than the `if` block below
ctx.ensure_object(dict)
ctx.obj['DEBUG'] = debug
ctx.obj['NAME'] = name
click.echo('Inside cli Debug is %s' % (ctx.obj['DEBUG'] and 'on' or 'off'))
click.echo('Inside cli Name is %s' % (ctx.obj['NAME']))
@cli.command()
@click.pass_context
def sync(ctx):
ctx.obj['NAME']='Ranjit'
ctx.obj['DEBUG']='on'
click.echo('Inside sync with Debug is %s' % (ctx.obj['DEBUG'] and 'on' or 'off'))
click.echo('Inside sync Name is %s' % (ctx.obj['NAME']))
if __name__ == '__main__':
cli(obj={})
To test this :
$ mytool --name=Tony sync
Inside cli Debug is off
Inside cli Name is Tony
Inside sync with Debug is on
Inside sync Name is Ranjit
Group Invocation Without Command
By default, a group or multi command is not invoked unless a subcommand is passed. In fact,
not providing a command automatically passes --help by default. This behaviour can be
changed by passing invoke_without_command=True to a group. In that case, the callback is
always invoked instead of showing the help page. The context object also includes
information about whether or not the invocation would go to a subcommand.
@click.group(invoke_without_command=True)
@click.pass_context
def cli(ctx):
if ctx.invoked_subcommand is None:
click.echo('I was invoked without subcommand')
else:
click.echo('I am about to invoke %s' % ctx.invoked_subcommand)
@cli.command()
def sync():
click.echo('The subcommand')
The command:
$ tool
I was invoked without subcommand
$ tool sync
I am about to invoke sync
The subcommand
Custom Multi Commands:
In addition to using click.group(), you can also build your own custom multi commands.
This is useful when you want to support commands being loaded lazily from plugins.A custom
multi command just needs to implement a list and load method like in the example below.We
have a folder named commands that holds various scripts for commands that are called when
the following script is called with that argument:
import click
import os
plugin_folder = os.path.join(os.path.dirname(__file__), 'commands')
class MyCLI(click.MultiCommand):
def list_commands(self, ctx):
rv = []
for filename in os.listdir(plugin_folder):
if filename.endswith('.py'):
rv.append(filename[:-3])
rv.sort()
return rv
def get_command(self, ctx, name):
ns = {}
fn = os.path.join(plugin_folder, name + '.py')
with open(fn) as f:
code = compile(f.read(), fn, 'exec')
eval(code, ns, ns)
return ns['cli']
cli = MyCLI(help='This tool\'s subcommands are loaded from a '
'plugin folder dynamically.')
if __name__ == '__main__':
cli()
To test the above script, first we call the help to see the command scripts available:
$ mytool
Usage: mytool [OPTIONS] COMMAND [ARGS]...
This tool's subcommands are loaded from a plugin folder dynamically.
Options:
--help Show this message and exit.
Commands:
hello
Then we call the command script ('hello' in the case above):
$ mytool hello
Usage: mytool hello [OPTIONS] COMMAND [ARGS]...
Options:
--debug / --no-debug
--help Show this message and exit.
Commands:
sync
Then we call the sub-command('sync' in the case above) :
$ mytool hello sync
Debug mode is off
Syncing
Multi Command Pipelines
A very common usecase of multi command chaining is to have one command process the result
of the previous command. There are various ways in which this can be facilitated. The most
obvious way is to store a value on the context object and process it from function to
function. This works by decorating a function with pass_context() after which the context
object is provided and a subcommand can store its data there.
Another way to accomplish this is to setup pipelines by returning processing functions.
Think of it like this: when a subcommand gets invoked it processes all of its parameters
and comes up with a plan of how to do its processing. At that point it then returns a
function and returns.
Where do the returned functions go? The chained multicommand can register a callback with
MultiCommand.resultcallback() that goes over all these functions and then invoke them.
Example:
@click.group(chain=True, invoke_without_command=True)
@click.option('-i', '--input', type=click.File('r'))
def cli(input):
pass
@cli.resultcallback()
def process_pipeline(processors, input):
iterator = (x.rstrip('\r\n') for x in input)
for processor in processors:
iterator = processor(iterator)
for item in iterator:
click.echo(item)
@cli.command('uppercase')
def make_uppercase():
def processor(iterator):
for line in iterator:
yield line.upper()
return processor
@cli.command('lowercase')
def make_lowercase():
def processor(iterator):
for line in iterator:
yield line.lower()
return processor
@cli.command('strip')
def make_strip():
def processor(iterator):
for line in iterator:
yield line.strip()
return processor
The code above has these features:
1.First we create a group that can be chainable and we set Click to invoke without any
subcommands. If this would not be done, then invoking an empty pipeline would produce
the help page instead of running the result callbacks.
2.Then we register a result callback on our group. This callback will be invoked with an
argument which is the list of all return values of all subcommands and then the same
keyword parameters as our group itself. This means we can access the input file easily
there without having to use the context object.
3.In this result callback we create an iterator of all the lines in the input file and then
pass this iterator through all the returned callbacks from all subcommands and finally
we print all lines to stdout.
4.After that point we can register as many subcommands as we want and each subcommand can
return a processor function to modify the stream of lines.
To test it, you can pass the commands like this ,the order doesn't matter:
$ mytool -i input.txt lowercase uppercase strip
THIS IS INPUT
HELLO THERE
One important thing of note is that Click shuts down the context after each callback has
been run. This means that for instance file types cannot be accessed in the processor
functions as the files will already be closed there. This limitation is unlikely to change
because it would make resource handling much more complicated. For such it%u2019s recommended
to not use the file type and manually open the file through open_file().
Overriding Defaults
By default, the default value for a parameter is pulled from the default flag that is
provided when it%u2019s defined, but that%u2019s not the only place defaults can be loaded from.
The other place is the Context.default_map (a dictionary) on the context. This allows
defaults to be loaded from a configuration file to override the regular defaults.This is
useful if you plug in some commands from another package but you%u2019re not satisfied with the
defaults.
Context Defaults
Starting with Click 2.0 you can override defaults for contexts not just when calling your
script, but also in the decorator that declares a command. For instance given the previous
example which defines a custom default_map this can also be accomplished in the decorator
now.
Example:
CONTEXT_SETTINGS = dict(
default_map={'runserver': {'port': 5000}}
In the case above, the runserver command can be used to modify the default port number
passed as an option.
Command Return Values
In essence any command callback can now return a value. This return value is bubbled to
certain receivers.
Things to know about return values:
--The return value of a command callback is generally returned from the BaseCommand.invoke()
method. The exception to this rule has to do with Groups:
In a group the return value is generally the return value of the subcommand invoked.
The only exception to this rule is that the return value is the return value of the
group callback if it%u2019s invoked without arguments and invoke_without_command is enabled.
If a group is set up for chaining then the return value is a list of all subcommands%u2019
results.
Return values of groups can be processed through a MultiCommand.result_callback. This is
invoked with the list of all return values in chain mode, or the single return value in
case of non chained commands.
--The return value is bubbled through from the Context.invoke() and Context.forward()
methods. This is useful in situations where you internally want to call into another
command.
--Click does not have any hard requirements for the return values and does not use them
itself. This allows return values to be used for custom decorators or workflows (like in
the multi command chaining example).
--When a Click script is invoked as command line application (through BaseCommand.main())
the return value is ignored unless the standalone_mode is disabled in which case it%u2019s
bubbled through.
Comments