In addition to common functionality that is implemented in the library itself, there are countless patterns that can be implemented by extending Click. This page should give some insight into what can be accomplished.
Many tools support aliases for commands. For instance, you can configure git to accept git ci as alias for git commit. Other tools also support auto-discovery for aliases by automatically shortening them.
Click does not support this out of the box, but it’s very easy to customize the Group or any other MultiCommand to provide this functionality.
As explained in Custom Multi Commands, a multi command can provide two methods: list_commands() and get_command(). In this particular case, you only need to override the latter as you generally don’t want to enumerate the aliases on the help page in order to avoid confusion.
This following example implements a subclass of Group that accepts a prefix for a command. If there were a command called push, it would accept pus as an alias (so long as it was unique):
class AliasedGroup(click.Group):
def get_command(self, ctx, cmd_name):
rv = click.Group.get_command(self, ctx, cmd_name)
if rv is not None:
return rv
matches = [x for x in self.list_commands(ctx)
if x.startswith(cmd_name)]
if not matches:
return None
elif len(matches) == 1:
return click.Group.get_command(self, ctx, matches[0])
ctx.fail('Too many matches: %s' % ', '.join(sorted(matches)))
And it can then be used like this:
@click.command(cls=AliasedGroup)
def cli():
pass
@cli.command()
def push():
pass
@cli.command()
def pop():
pass
Parameters (options and arguments) are forwarded to the command callbacks as you have seen. One common way to prevent a parameter from being passed to the callback is the expose_value argument to a parameter which hides the parameter entirely. The way this works is that the Context object has a params attribute which is a dictionary of all parameters. Whatever is in that dictionary is being passed to the callbacks.
This can be used to make up addition parameters. Generally this pattern is not recommended but in some cases it can be useful. At the very least it’s good to know that the system works this way.
import urllib
def open_url(ctx, param, value):
if value is not None:
ctx.params['fp'] = urllib.urlopen(value)
return value
@click.command()
@click.option('--url', callback=open_url)
def cli(url, fp=None):
if fp is not None:
click.echo('%s: %s' % (url, fp.code))
In this case the callback returns the URL unchanged but also passes a second fp value to the callback. What’s more recommended is to pass the information in a wrapper however:
import urllib
class URL(object):
def __init__(self, url, fp):
self.url = url
self.fp = fp
def open_url(ctx, param, value):
if value is not None:
return URL(value, urllib.urlopen(value))
@click.command()
@click.option('--url', callback=open_url)
def cli(url):
if url is not None:
click.echo('%s: %s' % (url.url, url.fp.code))
New in version 2.0.
Starting with Click 2.0, it’s possible to provide a function that is used for normalizing tokens. Tokens are option names, choice values, or command values. This can be used to implement case insensitive options, for instance.
In order to use this feature, the context needs to be passed a function that performs the normalization of the token. For instance, you could have a function that converts the token to lowercase:
CONTEXT_SETTINGS = dict(token_normalize_func=lambda x: x.lower())
@click.command(context_settings=CONTEXT_SETTINGS)
@click.option('--name', default='Pete')
def cli(name):
click.echo('Name: %s' % name)
And how it works on the command line:
$ cli --NAME=Pete
Name: Pete
Sometimes, it might be interesting to invoke one command from another command. This is a pattern that is generally discouraged with Click, but possible nonetheless. For this, you can use the Context.invoke() or Context.forward() methods.
They work similarly, but the difference is that Context.invoke() merely invokes another command with the arguments you provide as a caller, whereas Context.forward() fills in the arguments from the current command. Both accept the command as the first argument and everything else is passed onwards as you would expect.
Example:
cli = click.Group()
@cli.command()
@click.option('--count', default=1)
def test(count):
click.echo('Count: %d' % count)
@cli.command()
@click.option('--count', default=1)
@click.pass_context
def dist(ctx, count):
ctx.forward(test)
ctx.invoke(test, count=42)
And what it looks like:
$ cli dist
Count: 1
Count: 42
Click works a bit differently than some other command line parsers in that it attempts to reconcile the order of arguments as defined by the programmer with the order of arguments as defined by the user before invoking any callbacks.
This is an important concept to understand when porting complex patterns to Click from optparse or other systems. A parameter callback invocation in optparse happens as part of the parsing step, whereas a callback invocation in Click happens after the parsing.
The main difference is that in optparse, callbacks are invoked with the raw value as it happens, whereas a callback in Click is invoked after the value has been fully converted.
Generally, the order of invocation is driven by the order in which the user provides the arguments to the script; if there is an option called --foo and an option called --bar and the user calls it as --bar --foo, then the callback for bar will fire before the one for foo.
There are three exceptions to this rule which are important to know:
An option can be set to be “eager”. All eager parameters are evaluated before all non-eager parameters, but again in the order as they were provided on the command line by the user.
This is important for parameters that execute and exit like --help and --version. Both are eager parameters, but whatever parameter comes first on the command line will win and exit the program.
If an option or argument is split up on the command line into multiple places because it is repeated – for instance, --exclude foo --include baz --exclude bar – the callback will fire based on the position of the first option. In this case, the callback will fire for exclude and it will be passed both options (foo and bar), then the callback for include will fire with baz only.
Note that even if a parameter does not allow multiple versions, Click will still accept the position of the first, but it will ignore every value except the last. The reason for this is to allow composability through shell aliases that set defaults.
Most of the time you do not need to be concerned about any of this, but it is important to know how it works for some advanced cases.
In some situations it is interesting to be able to accept all unknown options for further manual processing. Click can generally do that as of Click 4.0, but it has some limitations that lie in the nature of the problem. The support for this is provided through a parser flag called ignore_unknown_options which will instruct the parser to collect all unknown options and to put them to the leftover argument instead of triggering a parsing error.
This can generally be activated in two different ways:
For most situations the easiest solution is the second. Once the behavior is changed something needs to pick up those leftover options (which at this point are considered arguments). For this again you have two options:
In the end you end up with something like this:
import sys
from subprocess import call
@click.command(context_settings=dict(
ignore_unknown_options=True,
))
@click.option('-v', '--verbose', is_flag=True, help='Enables verbose mode')
@click.argument('timeit_args', nargs=-1, type=click.UNPROCESSED)
def cli(verbose, timeit_args):
"""A wrapper around Python's timeit."""
cmdline = ['python', '-mtimeit'] + list(timeit_args)
if verbose:
click.echo('Invoking: %s' % ' '.join(cmdline))
call(cmdline)
And what it looks like:
$ cli --help
Usage: cli [OPTIONS] [TIMEIT_ARGS]...
A wrapper around Python's timeit.
Options:
-v, --verbose Enables verbose mode
--help Show this message and exit.
$ cli -n 100 "a = 1; b = 2; a * b"
100 loops, best of 3: 0.1 usec per loop
$ cli -v "a = 1; b = 2; a * b"
Invoking: python -mtimeit a = 1; b = 2; a * b
10000000 loops, best of 3: 0.0643 usec per loop
As you can see the verbosity flag is handled by Click, everything else ends up in the timeit_args variable for further processing which then for instance, allows invoking a subprocess. There are a few things that are important to know about how this ignoring of unhandled flag happens:
Generally though the combinated handling of options and arguments from your own commands and commands from another application are discouraged and if you can avoid it, you should. It’s a much better idea to have everything below a subcommand be forwarded to another application than to handle some arguments yourself.