Getting started¶
Motivation¶
Suppose we want a simple script that adds or multiplies two numbers. This code should then be
callable inside python (i.e. we create a function)
executable from the command line
So let’s setup the function in a file called 'add_or_multiply.py'
like this
In [1]: def do_something(a, b, multiply=False):
...: """
...: Multiply or add one number to the others
...:
...: Parameters
...: ----------
...: a: int
...: Number 1
...: b: list of int
...: A list of numbers to add `a` to
...: multiply: bool
...: If True, the numbers are multiplied, not added
...: """
...: if multiply:
...: result = [n * a for n in b]
...: else:
...: result = [n + a for n in b]
...: print(result)
...:
Now, if you want to make a command line script out of it, the usual methodology
is to create an argparse.ArgumentParser
instance and parse the
arguments like this
In [2]: if __name__ == '__main__':
...: from argparse import ArgumentParser
...: parser = ArgumentParser(
...: description='Multiply or add two numbers')
...: parser.add_argument('a', type=int, help='Number 1')
...: parser.add_argument('b', type=int, nargs='+',
...: help='A list of numbers to add `a` to')
...: parser.add_argument('-m', '--multiply', action='store_true',
...: help='Multiply the numbers instead of adding them')
...: args = parser.parse_args('3 2 -m'.split())
...: do_something(**vars(args))
...:
Now, if you parse the arguments, you get
In [3]: parser.print_help()
usage: __main__.py [-h] [-m] a b [b ...]
Multiply or add two numbers
positional arguments:
a Number 1
b A list of numbers to add `a` to
optional arguments:
-h, --help show this help message and exit
-m, --multiply Multiply the numbers instead of adding them
However, you could skip the entire lines above, if you just use the
funcargparse.FuncArgParser
In [4]: from funcargparse import FuncArgParser
In [5]: parser = FuncArgParser()
In [6]: parser.setup_args(do_something)
Out[6]: <function __main__.do_something(a, b, multiply=False)>
In [7]: parser.update_short(multiply='m')
In [8]: actions = parser.create_arguments()
In [9]: parser.print_help()
usage: __main__.py [-h] [-m] int int [int ...]
Multiply or add one number to the others
positional arguments:
int Number 1
int A list of numbers to add `a` to
optional arguments:
-h, --help show this help message and exit
-m, --multiply If True, the numbers are multiplied, not added
or you use the parser right in the beginning as a decorator
In [10]: @parser.update_shortf(multiply='m')
....: @parser.setup_args
....: def do_something(a, b, multiply=False):
....: """
....: Multiply or add one number to the others
....:
....: Parameters
....: ----------
....: a: int
....: Number 1
....: b: list of int
....: A list of numbers to add `a` to
....: multiply: bool
....: If True, the numbers are multiplied, not added
....: """
....: if multiply:
....: result = [n * a for n in b]
....: else:
....: result = [n + a for n in b]
....: print(result)
....:
In [11]: actions = parser.create_arguments()
In [12]: parser.print_help()
usage: __main__.py [-h] [-m] int int [int ...]
Multiply or add one number to the others
positional arguments:
int Number 1
int A list of numbers to add `a` to
optional arguments:
-h, --help show this help message and exit
-m, --multiply If True, the numbers are multiplied, not added
The FuncArgParser
interpretes the docstring
(see Interpretation guidelines for docstrings) and sets up the arguments.
Your '__main__'
part could then simply look like
In [13]: if __name__ == '__main__':
....: parser.parse_to_func()
....:
Usage¶
Generally the usage is
create an instance of the
FuncArgParser
classsetup the arguments using the
setup_args()
functionmodify the arguments (optional) either
in the
FuncArgParser.unfinished_arguments
dictionaryusing the
update_arg()
,update_short()
,update_long()
orappend2help()
methodsusing the equivalent decorator methods
update_argf()
,update_shortf()
,update_longf()
orappend2helpf()
create the arguments using the
create_arguments()
method
Subparsers¶
You can also use subparsers for controlling you program (see the
argparse.ArgumentParser.add_subparsers()
method). They can either be
implemented the classical way via
In [14]: subparsers = parser.add_subparsers()
In [15]: subparser = subparsers.add_parser('test')
And then as with the parent parser you can use function docstrings.
In [16]: @subparser.setup_args
....: def my_other_func(b=1):
....: """
....: Subparser summary
....:
....: Parameters
....: ----------
....: b: int
....: Anything"""
....: print(b * 500)
....:
In [17]: subparser.create_arguments()
Out[17]: [_StoreAction(option_strings=['-b'], dest='b', nargs=None, const=None, default=1, type=<class 'int'>, choices=None, help='Anything', metavar='int')]
In [18]: parser.print_help()
usage: __main__.py [-h] {test} ...
positional arguments:
{test}
optional arguments:
-h, --help show this help message and exit
On the other hand, you can use the setup_subparser()
method to directly create the subparser
In [19]: parser.add_subparsers()
Out[19]: _SubParsersAction(option_strings=[], dest='==SUPPRESS==', nargs='A...', const=None, default=None, type=None, choices={}, help=None, metavar=None)
In [20]: @parser.setup_subparser
....: def my_other_func(b=1):
....: """
....: Subparser summary
....:
....: Parameters
....: ----------
....: b: int
....: Anything"""
....: print(b * 500)
....:
In [21]: parser.create_arguments(subparsers=True)
Out[21]: []
In [22]: parser.print_help()
usage: __main__.py [-h] {my-other-func} ...
positional arguments:
{my-other-func}
my-other-func Subparser summary
optional arguments:
-h, --help show this help message and exit
which now created the my-other-func
sub command.
Chaining subparsers¶
Separate from the usage of the function docstring, we implemented the
possibilty to chain subparsers. This changes the handling of subparsers
compared to the default behaviour (which is inherited from the
argparse.ArgumentParser
). The difference can be shown in the following
example
In [23]: from argparse import ArgumentParser
In [24]: argparser = ArgumentParser()
In [25]: funcargparser = FuncArgParser()
In [26]: sps_argparse = argparser.add_subparsers()
In [27]: sps_funcargparse = funcargparser.add_subparsers(chain=True)
In [28]: sps_argparse.add_parser('dummy').add_argument('-a')
Out[28]: _StoreAction(option_strings=['-a'], dest='a', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
In [29]: sps_funcargparse.add_parser('dummy').add_argument('-a')
Out[29]: _StoreAction(option_strings=['-a'], dest='a', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
In [30]: ns_default = argparser.parse_args('dummy -a 3'.split())
In [31]: ns_chained = funcargparser.parse_args('dummy -a 3'.split())
In [32]: print(ns_default, ns_chained)
Namespace(a='3') Namespace(dummy=Namespace(a='3'))
So while the default behaviour is, to put the arguments in the main namespace like
In [33]: ns_default.a
Out[33]: '3'
the chained subparser procedure puts the commands for the 'dummy'
command
into an extra namespace like
In [34]: ns_chained.dummy.a
Out[34]: '3'
This has the advantages that we don’t mix up subparsers if we chain them. So here is an example demonstrating the power of it
In [35]: sps_argparse.add_parser('dummy2').add_argument('-a')
Out[35]: _StoreAction(option_strings=['-a'], dest='a', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
In [36]: sps_funcargparse.add_parser('dummy2').add_argument('-a')
Out[36]: _StoreAction(option_strings=['-a'], dest='a', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
# with allowing chained subcommands, we get
In [37]: ns_chained = funcargparser.parse_args('dummy -a 3 dummy2 -a 4'.split())
In [38]: print(ns_chained.dummy.a, ns_chained.dummy2.a)
3 4
# on the other side, the default ArgumentParser raises an error because
# chaining is not allowed
In [39]: ns_default = argparser.parse_args('dummy -a 3 dummy2 -a 4'.split())
An exception has occurred, use %tb to see the full traceback.
SystemExit: 2
Furthermore, you can use the parse_chained()
and the
parse_known_chained()
methods to parse directly to the
subparsers.
In [40]: parser = FuncArgParser()
In [41]: sps = parser.add_subparsers(chain=True)
In [42]: @parser.setup_subparser
....: def subcommand_1():
....: print('Calling subcommand 1')
....: return 1
....:
In [43]: @parser.setup_subparser
....: def subcommand_2():
....: print('Calling subcommand 2')
....: return 2
....:
In [44]: parser.create_arguments(True)
Out[44]: []
In [45]: parser.parse_chained('subcommand-1 subcommand-2'.split())
Calling subcommand 2
Calling subcommand 1
Out[45]: Namespace(subcommand_2=2, subcommand_1=1)
Warning
If you reuse an already existing command in the subcommand of another subcommand, the latter one get’s prefered. See this example
In [46]: sp = sps.add_parser('subcommand-3')
In [47]: sps1 = sp.add_subparsers(chain=True)
# create the same subparser subcommand-1 but as a subcommand of the
# subcommand-3 subparser
In [48]: @sp.setup_subparser
....: def subcommand_1():
....: print('Calling modified subcommand 1')
....: return 3.1
....:
In [49]: sp.create_arguments(True)
Out[49]: []
# subcommand-1 get's called
In [50]: parser.parse_chained('subcommand-1 subcommand-3'.split())
Calling subcommand 1
Out[50]: Namespace(subcommand_1=1, subcommand_3=None)
# subcommand-3.subcommand-1 get's called
In [51]: parser.parse_chained('subcommand-3 subcommand-1'.split())
Calling modified subcommand 1
Out[51]: Namespace(subcommand_3=Namespace(subcommand_1=3.1))