clize is a Python module that consists of a function decorator which Python programmers can use to quickly turn Python functions into usable command-line applications. It is compatible with Python 2.6, 2.7, 3.1 and forwards.
With sufficient privileges:
./setup.py install
Write your program as a function with the appropriate parameters:
def echo(text, reverse=False): if reverse: text = text[::-1] print(text)
There we have a simple printing function that allows you via an optional parameter to reverse the output. We can play around with it in an interactive session:
>>> echo("Hello world!") Hello world! >>> echo("Hello world!", reverse=True) !dlrow olleH
To CLIze your function, import the clize decorator from the clize module and apply it to your function:
#!/usr/bin/env python from clize import clize @clize def echo(text, reverse=False): if reverse: text = text[::-1] print(text)
Then, add the usual code to run your function with command-line arguments:
if __name__ == '__main__': import sys echo(*sys.argv)
Make sure your script is executable, and run its help:
$ ./echo.py --help Usage: ./echo.py [OPTIONS] text Positional arguments: text Options: --reverse -h, --help Show this help
Well there's something! clize already auto-generated --help
for you! It has
blanks to be filled, but that looks pretty much like what we wanted.
$ ./echo.py 'Hello world!' Hello world! $ ./echo.py --reverse 'Hello world!' !dlrow olleH $ ./echo.py 'Hello world!' --reverse !dlrow olleH
Why the quotes, you might ask. Good question. Let's try without.
$ ./echo.py Hello world! Traceback (most recent call last): File "./echo.py", line 13, in <module> echo(*sys.argv) File "/usr/lib/python3.2/site-packages/clize.py", line 381, in _getopts raise ArgumentError(_("Too many arguments."), command, name) clize.ArgumentError: Too many arguments. Usage: ./echo.py [OPTIONS] text
Uff! What happened here? The shell split "Hello" and "world!" into two different parameters, and therefore were interpreted to be two different python arguments. Before we fix this however, let's make sure a potential user of the program doesn't see this traceback, but just the error message.
Change the last part to catch ArgumentError exceptions:
from clize import clize, ArgumentError ... if __name__ == '__main__': import sys import os.path try: program(*sys.argv) except ArgumentError as e: print(os.path.basename(sys.argv[0]) + ': ' + str(e), file=sys.stderr)
Much better:
$ ./echo.py Hello world! echo.py: Too many arguments. Usage: ./echo.py [OPTIONS] text
That's mostly what the run
function does, so do use that instead:
from clize import clize, run ... if __name__ == '__main__': run(program)
Back to our little problem. We essentially want text
to recuperate all
arguments. Python functions have a syntax for that, but you'll have to shift
text
to the end of the parameter list:
@clize def echo(reverse=False, *text): ...
It is still a list of arguments, just put in one tuple. You simply have to join it:
@clize def echo(reverse=False, *text): text = ' '.join(text) if reverse: text = text[::-1] print(text)
In the shell:
$ ./echo.py Hello world! Hello world!
It will change the documentation to show [text...]
instead of just text
.
But... doesn't that mean text
is optional? Yes, and most programs want
excess arguments to be optional. But we don't. It's pointless to run this
without text! The decorator has a parameter for this:
@clize(require_excess=True) def echo(reverse=False, *text): text = ' '.join(text) if reverse: text = text[::-1] print(text)
And now text is mandatory.
Now, let's document it proper, with a docstring.
@clize(require_excess=True) def echo(reverse=False, *text): """ Echoes text back. """ text = ' '.join(text) if reverse: text = text[::-1] print(text)
If you look at the help output, you will see that you added a description for your command.
Document each parameter as it appears in your function like this:
@clize(require_excess=True) def echo(reverse=False, *text): """ Echoes text back. text: The text to be echoed reverse: Reverse text before echoing """ text = ' '.join(text) if reverse: text = text[::-1] print(text)
Should you want to add additional info after the arguments, just do so in the docstring:
@clize(require_excess=True) def echo(reverse=False, *text): """ Echoes text back. text: The text to be echoed reverse: Reverse text before echoing Beware! There is no warranty this program will not reverse your internets! """ text = ' '.join(text) if reverse: text = text[::-1] print(text)
This gives us this help string:
$ ./echo.py --help Usage: examples/echo.py [OPTIONS] text... Echoes text back Positional arguments: text... The text to be echoed Options: --reverse Reverse text before echoing -h, --help Show this help Beware! There is no warranty this program will not reverse your internets!
Finally, you might want to have a shorter name for --reverse
. This can be
achieved with the alias
keyword argument of clize, which is a mapping from
source names to a list of additional aliases:
@clize(require_excess=True, alias={ 'reverse': ('r',), }, ) def echo(reverse=False, *text): ...
You can now use -r
instead of --reverse
. This will be reflected in the
help text too.
Let's add a --version switch, for good measure.
You can add extra flags with the extra
keyword argument. It takes a sequence
of Option objects, but we'll just use the make_flag
helper function here,
since it is sufficient.
make_flag
takes at least two parameters: source
and names
.
source
is usually the name of the argument from the function assigned to
the option,
names
is a sequence of names the option will take.help
is optional
and is the help text assigned to the flag.
When source
is callable, it is called with four keyword parameters, most of
which you can ignore:
name
corresponds tosys.argv[0]
when called withsys.argv
.command
is the command object used internally to represent the command
subject to clize-ation.
val
is the value passed to the option.params
is the mapping of keyword arguments that will be passed to the
function subject to clize-ation.
If this function returns something true, the command will stop being processed.
In our case we want the command name and we want the command to stop once we printed the version:
def show_version(name, **kwargs): print("{0} version 1.0".format(os.path.basename(name))) return True @clize( require_excess=True, alias={ 'reverse': ('r',), }, extra=( make_flag( source=show_version, names=('version', 'v'), help="Show the version", ), ) ) def echo(reverse=False, *text): ...
This gives:
$ examples/echo.py --version echo.py version 1.0
And this concludes this guide of sorts. You can find the full example in examples/echo.py
Keyword arguments to the clize decorator: help_names
The different names the help function should take. Set it to an empty tuple to disable the help screen.
- force_positional
- A list/tuple of keyword arguments that should be forced into being optional positional arguments.
- coerce
- A mapping from argument name to type coercion functions.
Clize can also run as a subcommand dispatcher. Pass a tuple of functions to
run
and it will accept a function name as first argument. Here's an example
where two subcommands, echo
and shout
are put together:
from clize import clize, run @clize(require_excess=True) def echo(reverse=False, *text): text = ' '.join(text) if reverse: text = text[::-1] print(text) @clize(require_excess=True) def shout(*text): print(' '.join(text).upper()) if __name__ == '__main__': run((echo, shout))
This will let you do the following:
script.py shout 'Hello' script.py echo 'Hello' script.py echo 'Hello' --reverse
A help message is generated when the script is called with no subcommands and a
--help
argument, listing each subcommand. You can use run's description
and footnotes
arguments to specify the respective sections of the help
message.
If you do not worry about Python 2 compability, you can use annotations and keyword-only arguments:
If keyword-only arguments are found, all of them become options, and all other arguments become positional:
@clize def func(one, two=2, *, three=3, four=4): ...
Here would be the corresponding help message:
Usage: test.py [OPTIONS] one [two] Positional arguments: one two=INT Options: --three=INT --four=INT -h, --help Show this help
You can force this behaviour without using keyword-only arguments by using
@clize.kwo
instead of @clize
. You can technically make required options
with this, but I would recommend against it as it is counter-intuitive as far as
CLIs go.
With annotations you can specify aliases and type coercion functions. For instance if you want the parameter abc to be a float and be aliased to A, you can use:
@clize def func( *, abc: ('A', float) ): ...
Of course, don't do this. It would be a required option :-) You can omit the tuple if you only have one thing to specify.
Here's a full example of annotations and keyword-only arguments:
from clize import clize, run @clize def connect( host, port=400, *, number: 'n' = 1.2, negative: 'm' = False ): print( "I would connect to {0}:{1} and send {2} but I'm just an example!" .format(host, port, -number if negative else number) ) if __name__ == '__main__': run(connect)
You can find all the examples from here in the examples/
folder.