Click is Flask's team pallets development of excellent open source projects, it for the development of command line tools encapsulate a large number of methods, so that developers only need to focus on the implementation of the function. It just so happens that I recently developed a small tool needs to operate in the command line environment, write a learning note.
As usual, we'll start with a "Hello World" program (assuming the Click package is installed).
# import click @() @('--count', default=1, help='Number of greetings.') @('--name', prompt='Your name', help='The person to greet.') def hello(count, name): """Simple program that greets NAME for a total of COUNT times.""" for x in range(count): ('Hello %s!' % name) if __name__ == '__main__': hello()
Execute python --count=3 and it's not hard to guess what the console output will be. On top of that, Click quietly does other things, such as help options:
$ python --help Usage: [OPTIONS] Simple program that greets NAME for a total of COUNT times. Options: --count INTEGER Number of greetings. --name TEXT The person to greet. --help Show this message and exit.
Functions into CLIs in seconds
As you can see from the "Hello World" demo above, Click decorates a function method into a command-line interface via a decorator, which is `@()`.
import click @() def hello(): ('Hello World!')
The `@() decorator turns the hello() method into a Command object that, when called, performs the behavior within that instance. And the -help argument is the Command` object's built-in argument.
Different Command instances can be associated with a group, and the commands bound to a group become its subcommands, as shown in the following code:
@() def cli(): pass @() def initdb(): ('Initialized the database') @() def dropdb(): ('Dropped the database') cli.add_command(initdb) cli.add_command(dropdb)
The `@ decorator decorates the method as a Group object that can have multiple subcommands. The Command object is associated to the Group object by the Group.add_command() method. It is also possible to decorate the method directly with the @decorator, which will automatically associate the method under that Group` object.
@() def cli(): pass @() def initdb(): ('Initialized the database') @() def dropdb(): ('Dropped the database')
Command line arguments are indispensable, and Click supports adding custom arguments to command methods, implemented by the option() and argument() decorators.
@() @('--count', default=1, help='number of greetings') @('name') def hello(count, name): for x in range(count): ('Hello %s!' % name)
Packaging cross-platform executable programs
After you have written a simple command-line method with Click, you need to convert the .py file into a command-line program that you can run in the console. The easiest way to do this is to add the following code to the end of the file:
if __name__ == '__main__': command()
Click supports the use of setuptools to better implement command line program packaging, packaging source code files into executable programs on the system, and regardless of platform. Generally, we will create a script in the root directory of the source code, let's look at a simple packaging code:
from setuptools import setup setup( name='hello', version='0.1', py_modules=['hello'], install_requires=[ 'Click', ], entry_points=''' [console_scripts] hello=hello:cli ''', )
Note the entry_points field. Under console_scripts, each line is a console script, with the name of the script to the left of the equal sign, and the import path of the Click command to the right.
Command Line Parameters Explained
Two decorators for customizing command line arguments were mentioned above: `@() and @()`, which are slightly different and have different usage scenarios.
Overall, the argument() decorator is a bit simpler than option(), which supports the following features:
- Automatically prompts for missing inputs;
- The option parameter can be retrieved from an environment variable, the argument parameter cannot;
- The option argument is fully documented in the help output, the argument is not;
Whereas the argument parameter can accept a variable number of values, the option parameter can only accept a fixed number of values (1 by default).
Click can set different parameter types, simple types such as , , , , .
Command line parameter names are declared by "-short_name" and "-long_name", if the parameter name does not start with either "- " nor "-", then this side of the variable name becomes an internal variable of the decorated method, not a method parameter.
Option Parameters
The most basic use of option is a simple value variable, option receives a variable value, the following is a sample code:
@() @('--n', default=1) def dots(n): ('.' * n)
If you follow the command line with the argument --n=2 it will output two points, if you pass the argument, it will output one point by default. In the above code, the parameter type is not shown as given, but the interpreter will assume that it is of type INT, since the default value of 1 is an int value.
There are times when you need to pass in more than one value, which can be interpreted as a list. option only supports fixed-length parameter values, i.e., it must be passed in after setting, and the number of values is determined by nargs.
@() @('--pos', nargs=2, type=float) def findme(pos): ('%s / %s' % pos) findme --pos 2.0 3.0 The output is 2.0 / 3.0
Since you can pass in a list, what about a tuple, which is also supported by Click?
@() @('--item', type=(unicode, int)) def putitem(item): ('name=%s id=%d' % item)
This passes in a tuple variable, putitem --item peter 1338 The output you get is name=peter id=1338
There are no nargs set above, because nargs automatically takes the value of the length of the tuple. So the above code is effectively equivalent:
@() @('--item', nargs=2, type=([unicode, int])) def putitem(item): ('name=%s id=%d' % item)
option also supports multiple uses of the same argument, as in git commit -m aa -m bb where the -m argument is passed twice. option supports this with the multiple identifier bit:
@() @('--message', '-m', multiple=True) def commit(message): ('\n'.join(message))
Sometimes command line arguments are fixed values, and types can be used to qualify the potential values of passed parameters:
# choice @() @('--hash-type', type=(['md5', 'sha1'])) def digest(hash_type): (hash_type)
When the above command-line program argument --hash-type is not md5 or sha1, an error is output and the choice option is also shown in the --help prompt.
If you want the command line program to be able to prompt the user in case we make a mistake or miss an input, you need to use Click's prompt function, look at the code:
# prompt @() @('--name', prompt=True) def hello(name): ('Hello %s!' % name)
If hello is executed without the -name argument, the console will prompt the user for it. You can also customize the console prompt output by changing prompt to something you want.
For inputting parameters such as account passwords, it is necessary to hide them. option's hide_input and confirmation_promt flags are used to control the input of password parameters:
# password @() @('--password', prompt=True, hide_input=True, confirmation_prompt=True) def encrypt(password): ('Encrypting password to %s' % ('rot13'))
Click further encapsulates the above operation into the decorator click.password_option(), so the above code can also be simplified:
# password @() @click.password_option() def encrypt(password): ('Encrypting password to %s' % ('rot13'))
Some arguments change the execution of the command-line program, such as node to enter the Node console and node --verion to output the version number of the node. click provides the eager identifier to mark the argument name, intercepting the established command-line execution flow, and instead calling a callback method that exits directly after execution. The following is a simulation of click.version_option(), which outputs the version number of the --version parameter:
# eager def print_version(ctx, param, value): if not value or ctx.resilient_parsing: return ('Version 1.0') () @() @('--version', is_flag=True, callback=print_version, expose_value=False, is_eager=True) def hello(): ('Hello World!')
For dangerous operations like deleting a database table, Click supports a confirmation popup, which will ask the user to reconfirm if the -yes flag is in the True position:
# yes parameters def abort_if_false(ctx, param, value): if not value: () @() @('--yes', is_flag=True, callback=abort_if_false, expose_value=False, prompt='Are you sure you want to drop the db?') def dropdb(): ('Dropped all tables!')
Test run down:
$ dropdb Are you sure you want to drop the db? [y/N]: n Aborted! $ dropdb --yes Dropped all tables!
Similarly, Click encapsulates sub, click.confirmation_option() decorator implements the above functionality:
@() @click.confirmation_option(prompt='Are you sure you want to drop the db?') def dropdb(): ('Dropped all tables!')
The previous section only talked about the default parameter prefixes -- and -, Click allows developers to customize the parameter prefixes (although it is seriously not recommended).
# other prefix @() @('+w/-w') def chmod(w): ('writable=%s' % w) if __name__ == '__main__': chmod()
If you want to use / as a prefix, and you want to use Boolean identifiers like above, it will create a conflict because Boolean identifiers also use /. In this case, you can use ; instead of / for Boolean identifiers:
@() @('/debug;/no-debug') def log(debug): ('debug=%s' % debug) if __name__ == '__main__': log()
Since Choice is supported, it's not hard to associate it with Range, so let's look at the code first:
# range @() @('--count', type=(0, 20, clamp=True)) @('--digit', type=(0, 10)) def repeat(count, digit): (str(digit) * count) if __name__ == '__main__': repeat()
Argument Parameters
Argument is similar to Option, but not as comprehensive as Option.
Like Option, the most basic application of Argument is to pass a simple variable value:
@() @('filename') def touch(filename): (filename)
The value of the parameter following the command line is assigned to the parameter name filename.
Another widely used option is variable arguments, where the number of arguments is determined by nargs, and the value of the variable is passed into the function in the form of a tuple:
@() @('src', nargs=-1) @('dst', nargs=1) def copy(src, dst): for fn in src: ('move %s to folder %s' % (fn, dst))
Run the program:
$ copy my_folder move to folder my_folder move to folder my_folder
Click supports operations on files via the filename parameter, which is handled by the () decorator, especially on Unix-like systems, which supports the - symbol as standard input/output.
# File @() @('input', type=('rb')) @('output', type=('wb')) def inout(input, output): while True: chunk = (1024) if not chunk: break (chunk)
Run the program to write the text to the file and then read the
$ inout - hello ^D $ inout - hello
What if the value of the parameter is just a filename? It's easy, just specify type as ():
@() @('f', type=(exists=True)) def touch(f): (click.format_filename(f)) $ touch $ touch Usage: touch [OPTIONS] F Error: Invalid value for "f": Path "" does not exist.
For more information on how to use the Python3 Click module check out the related links below