Installation ------------ Climax is installed with pip:: $ pip install climax Getting Started --------------- The following example should give you a pretty good idea of how climax works:: import climax @climax.command() @climax.argument('name', help='the name to repeat') @climax.argument('--count', type=int, help='how many times to repeat') def repeat(count, name): """This silly program repeats a name the given number of times.""" for i in range(count): print(name) if __name__ == '__main__': repeat() In the script above, the arguments to the ``@climax.argument`` decorator are anything you would send to the ``ArgumentParser.add_argument`` method. In fact, climax passes these arguments to it untouched. The ``@climax.command`` decorator takes optional arguments, which are passed to the ``ArgumentParser`` constructor. For example, to set a custom program name, you can pass ``prog='my_command_name'``. When you run the above script, you get a functional command line parser:: $ python repeat.py usage: repeat.py [-h] [--count COUNT] name The ``--help`` option is automatically generated by argparse, from the information passed on the decorators:: $ python repeat.py --help usage: repeat.py [-h] [--count COUNT] name This silly program repeats a name the given number of times. positional arguments: name the name to repeat optional arguments: -h, --help show this help message and exit --count COUNT how many times to repeat If you provide valid arguments, then the command function runs:: $ python repeat.py --count 3 foo foo foo foo And if anything in the command line is incorrect, you get an error:: $ python repeat.py --count not-a-number foo usage: repeat.py [-h] [--count COUNT] name repeat.py: error: argument --count: invalid int value: 'not-a-number' Building Command Groups ----------------------- One of the nicest features of argparse is the ability to build complex command lines by grouping multiple commands under a single top-level parser. Climax support these easily:: import climax @climax.group() def main(): pass @main.command() @climax.argument('values', type=int, nargs='+', help='sequence of numbers to add') def add(values): """add numbers""" print(sum(values)) @main.command() @climax.argument('values', type=int, nargs='+', help='sequence of numbers to average') def avg(values): """average numbers""" print(sum(values) / len(values)) if __name__ == '__main__': main() Note that to link a command to its parent group, the ``command`` decorator is obtained from the group function (i.e. ``@main.command`` instead of ``@climax.command``). Without any arguments, this script generates the following output:: $ python sumavg.py usage: sumavg.py [-h] {add,avg} ... sumavg.py: error: too few arguments The ``--help`` now generates information about the group of commands:: $ python sumavg.py --help usage: sumavg.py [-h] {add,avg} ... positional arguments: {add,avg} add add numbers avg average numbers optional arguments: -h, --help show this help message and exit And each command generates its own help messages as well:: $ python sumavg.py add usage: sumavg.py add [-h] values [values ...] sumavg.py add: error: the following arguments are required: values $ python sumavg.py add --help usage: sumavg.py add [-h] values [values ...] positional arguments: values sequence of numbers to add optional arguments: -h, --help show this help message and exit Other Useful Features --------------------- Options vs. Arguments ~~~~~~~~~~~~~~~~~~~~~ Argparse does not make a distinction between options and arguments, positional and optional arguments are considered arguments. In climax, the ``@climax.argument`` and ``@climax.option`` decorators are equivalent, so they can be used according to your preference. Password Prompts ~~~~~~~~~~~~~~~~ Argparse does not provide any facility to enter passwords securely. Since this is a common need of scripts, climax includes a custom argparse action that adds this functionality:: import climax @climax.command() @climax.argument('--username', '-u', help='your username') @climax.argument('--password', '-p', action=climax.PasswordPrompt, help='prompt for your password') def login(username, password): """Login example.""" print(username, password) if __name__ == '__main__': login() When you run this script, the ``getpass`` function from the Python standard library is invoked to prompt for the password without echoing what you type:: $ python login.py -u john -p Password: Contexts ~~~~~~~~ In the command group example above, there is a function associated with the group, called ``main``. If arguments are defined at this level, they will apply to all the commands in the group. When a command is invoked, climax first calls the group function with its arguments, and then calls the appropriate command function. Consider, for example, a ``--verbose`` option, which applies to all commands in a group:: @climax.group() @climax.argument('--verbose', action='store_true') def main(verbose): return {'verbose': verbose} After the group function processes its arguments, it may need to communicate some state to the command function that will run after it. For this purpose, the group function can return a dictionary with values that will be sent as arguments into the command function, in addition to the arguments generated by argparse. To support verbosity, the sum function can then be coded as follows:: @main.command() @climax.argument('values', type=int, nargs='+', help='sequence of numbers to add') def add(values, verbose): """add numbers""" if verbose: print('The input values are: ', str(values)) print(sum(values)) Return Values ~~~~~~~~~~~~~ You have seen in the previous section that the return value from a group function is the context that is passed as arguments to the command function. A command function can also return a value, which is returned to the caller:: import climax @climax.command() @climax.argument('--count', type=int, help='how many times to repeat') @climax.argument('name', help='the name to repeat') def repeat(count, name): """This silly program repeats a name the given number of times.""" for i in range(count): print(name) return count if __name__ == '__main__': result = main() # result now has the return value from the command Parents ~~~~~~~ Argparse supports the concept of "parents", which allows a command to inherit arguments from another command, which is designated as a parent. The purpose of this feature is to avoid duplication when a common set of arguments needs to be applied to several commands. With climax, the parent feature is available through a ``parent`` decorator:: import climax @climax.parent() @climax.argument('--count', type=int, help='how many times to repeat') def repeat() pass @climax.group() def main(): pass @main.command(parents=[repeat]) def foo(count): for i in range(count): print('foo') @main.command(parents=[repeat]) @climax.argument('name', help='the name to repeat') def bar(name, count): for i in range(count): print(name) if __name__ == '__main__': main() In this example, both the ``foo`` and ``bar`` commands accept ``--count`` as argument. The function that handles a command that has parents will receive its own arguments combined with those of the parents. Optional Commands ~~~~~~~~~~~~~~~~~ In Python 3.2 and older, argparse requires that a command name is specified when using groups. But argparse versions that ship with Python 3.3 and newer lift that requirement due to a bug that inadvertently slipped. This makes it possible to specify a command line in which no command from the group is selected, something that can sometimes be useful. If you are using one of the newer Python versions, climax makes group commands required by default, like in the older Python releases. This gives a consistent behavior. To make commands in a group optional, the group can be given the ``required=False`` argument:: import climax @climax.group(required=False) def main(): print('this is main') @main.command() def cmd() print('this is cmd') With this example, the following two commands are both valid:: $ python main.py cmd this is main this is cmd $ python main.py this is main Without setting ``required=False``, the second command would return an error. Note that optional commands do not work in Python releases before 3.3. Recursive Groups ~~~~~~~~~~~~~~~~ Argparse supports multiple levels of commands and sub-commands. In climax, multiple layers of commands can be built using the familiar decorator syntax. Consider the following example:: @climax.group() def main(): pass @main.group() def sub1(): pass @sub1.command() def sub1a(): pass @sub1.command() def sub1b(): pass @main.command() def sub2(): pass Integration with native argparse parsers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Climax's use of argparse is not magical. In fact, it is possible to attach a regular argparse parser as a command in a climax group, by passing a ``parser`` argument to the ``@command`` decorator:: @climax.group() def grp(): pass parser = argparse.ArgumentParser('cmd1.py') parser.add_argument('--repeat', type=int) parser.add_argument('name') @grp.command(parser=parser) def cmd1(repeat, name): pass Or directly to the main command:: import climax parser = argparse.ArgumentParser('cmd.py') parser.add_argument('--repeat', type=int) parser.add_argument('name') @climax.command(parser=parser) def cmd(repeat, name): pass The reverse is also possible. If you need to obtain the argparse parser generated by climax to integrate it with another parser or to make custom modifications to it, you can simply obtain it by invoking the ``parser`` attribute on the corresponding function. In the above example, ``grp.parser`` returns a fully built and ready to use parser.