Basic Usage
By itself, ArgTyper will only create parsers with very basic additional information on arguments. To enhance the generated output and extend functionality, ArgTyper provides a set of decorators. The following examples provide an overview of some of the features of ArgTyper.
Command
The argtyper.Command decorator can be used to pass additional arguments to the created ArgumentParser instance:
import argtyper
@argtyper.Command(
description="Having a better description can improve your CLIs",
epilog="And some more text at the end",
)
def hello(name: str, amount: int = 2):
print("\n".join([f"Hello {name.upper()}"] * amount))
at = argtyper.ArgTyper(hello)
at()
This will produce the following output for the parser.
$ python command.py -h
usage: command.py [-h] [--amount AMOUNT] NAME
Having a better description can improve your CLIs
positional arguments:
NAME provide argument of type STR
optional arguments:
-h, --help show this help message and exit
--amount AMOUNT provide argument of type INT
And some more text at the end
It takes the same arguments that you can pass to argparse.ArgumentParser.
You can also pass help as argument, which will be used if the command is used in a subparser.
Additionally, Command also gives you the ability to exclude arguments from the generated parser, set default values or hardcode the values for certain argument names or types. A description of the corresponding arguments can be found in the class description.
Argument
The argtyper.Argument decorator can be used to pass additional arguments to parser.add_argument()
It takes the same arguments that you can pass to argparse.ArgumentParser.add_argument. The main difference is that the first argument here is the name of the function argument to annotate.
import argtyper
@argtyper.Argument(
"amount", "repetitions", help="How often should we say hello?", metavar="reps"
)
@argtyper.Argument("name", "--name", "--n", help="Give me your name", default="Yoda")
def hello(name: str, amount: int = 2):
print("\n".join([f"Hello {name.upper()}"] * amount))
at = argtyper.ArgTyper(hello, version="This is %(prog)s version 1.3.3.7")
at()
This will produce the following output for the parser.
$ python argument.py -h
usage: argument.py [-h] [--name NAME] [--version] REPS
positional arguments:
REPS How often should we say hello?
optional arguments:
-h, --help show this help message and exit
--name NAME, --n NAME
Give me your name
--version, -v show program's version number and exit
In this example we changed settings for the arguments amount and name. This way we added more information to the help output.
But additionally, we are also able to change the underlying parser.
In this case, we changed the optional argument amount to a required argument with the new name reps. The important thing here
is the argument repetitions, which does not include the optional argument prefix -. Therefore it will be interpreted as required.
Conversely, we changed the argument name to an optional argument by using names which are prefixed with a -. Additionally we set a default value
(Yoda) to be passed to name, in case the argument is not passed on the command line.
SubCommand
The argtyper.SubCommand decorator can be used to create additional subparsers.
This is merely a helper function which does not have any direct counterpart in argparse.
It does, however, reuse information attached with argtyper.Command to subfunctions functions.
import argtyper
def footer1(footer: str, hide: bool = False):
if not hide:
print(f"-- {footer}")
async def footer2(footer: str, repeat: int = 2):
print(f"{footer}" * repeat)
# Subcommands are `registered` at the parent
@argtyper.SubCommand(footer1, name="footer_main")
@argtyper.SubCommand(footer2)
def hello(name: str, amount: int = 5):
print(f"Hello {name.upper()}\n" * amount)
at = argtyper.ArgTyper(hello, arg_defaults={"amount": 2})
at()
This will produce the following output for the parser.
$ python subcommand.py -h
usage: subcommand.py [-h] [--amount AMOUNT] NAME {footer2,footer_main} ...
positional arguments:
NAME provide argument of type STR
{footer2,footer_main}
optional arguments:
-h, --help show this help message and exit
--amount AMOUNT provide argument of type INT
SubCommand simply takes two arguments. The first one is the function (either Callable or name:str), the second one is an optional alternative name to be used for this subcommand.
SubParser
The argtyper.SubParser decorator can be used to add additional information to the
created subparsers instance. It is basically a wrapper around argparse.ArgumentParser.add_subparsers and takes the same arguments.
import argtyper
def footer1(footer: str, hide: bool = False):
if not hide:
print(f"-- {footer}")
async def footer2(footer: str, repeat: int = 2):
print(f"{footer}" * repeat)
# Hint: The ordering of decorators is irrelevant
@argtyper.SubParser(
title="Subcommands",
description="The additional commands to append",
help="We can run this",
)
# Subcommands are `registered` at the parent
@argtyper.SubCommand(footer1, name="footer_main")
@argtyper.SubCommand(footer2)
def hello(name: str, amount: int = 5):
print(f"Hello {name.upper()}\n" * amount)
at = argtyper.ArgTyper(hello, arg_defaults={"amount": 2})
at()
This will produce the following output for the parser.
$ python subparser.py -h
usage: subparser.py [-h] [--amount AMOUNT] NAME {footer2,footer_main} ...
positional arguments:
NAME provide argument of type STR
optional arguments:
-h, --help show this help message and exit
--amount AMOUNT provide argument of type INT
Subcommands:
The additional commands to append
{footer2,footer_main}
We can run this
The subcommands have now been grouped into their own sections with their own description.
ArgumentGroup
The argtyper.ArgumentGroup decorator can be used to add argument groups to the parser.
It takes a list or argument names (as they appear in the function definition) and wraps them with
argparse.ArgumentParser.add_argument_group
during parser creation.
import argtyper
@argtyper.ArgumentGroup(
["firstname", "lastname"],
title="Name details",
description="Give your full name here",
)
@argtyper.ArgumentGroup(
["nickname", "firstname"],
title="Nickname details",
description="Give your Nickname here",
)
@argtyper.Argument(
"amount", "repetitions", help="How often should we say hello?", metavar="reps"
)
@argtyper.Argument(
"lastname", "--name", "--n", help="Give me your name", default="Yoda"
)
def hello(nickname: str, firstname: str, lastname: str, amount: int = 2):
print("\n".join([f"Hello {firstname} '{nickname.upper()}' {lastname}"] * amount))
at = argtyper.ArgTyper(hello)
at()
This will produce the following output for the parser.
$ python argument_group.py -h
usage: argument_group.py [-h] [--name LASTNAME]
NICKNAME FIRSTNAME FIRSTNAME REPS
positional arguments:
REPS How often should we say hello?
optional arguments:
-h, --help show this help message and exit
Nickname details:
Give your Nickname here
NICKNAME provide argument of type STR
FIRSTNAME provide argument of type STR
Name details:
Give your full name here
FIRSTNAME provide argument of type STR
--name LASTNAME, --n LASTNAME
Give me your name
The arguments have now been grouped into their own sections with their own description.
Warning
There is no check if you put the same argument into different groups. They might then appear more than once on the command line.
MutuallyExclusiveArgumentGroup
The argtyper.MutuallyExclusiveArgumentGroup decorator can be used to add mutually exclusive argument groups to the parser.
This wraps argparse.ArgumentParser.add_mutually_exclusive_group.
import argtyper
@argtyper.MutuallyExclusiveArgumentGroup(["foo", "bar"], required=True)
def hello(nickname: str, foo: str = None, bar: str = None):
print(locals())
if foo:
print(f"{nickname} + {foo}")
else:
print(f"{bar} + {nickname}")
at = argtyper.ArgTyper(hello)
at()
This will produce the following output for the parser.
$ python exclusive_group.py -h
usage: exclusive_group.py [-h] (--foo FOO | --bar BAR) NICKNAME
positional arguments:
NICKNAME provide argument of type STR
optional arguments:
-h, --help show this help message and exit
--foo FOO provide argument of type STR
--bar BAR provide argument of type STR
The arguments have now been grouped into their own sections with their own description.
But now the parser will also make sure that at most one (or exactly one, if required=True) of the arguments in the group is set.
$ python exclusive_group.py supernick --foo FOO --bar BAR
usage: exclusive_group.py [-h] (--foo FOO | --bar BAR) NICKNAME
Error: exclusive_group.py: argument --bar: not allowed with argument --foo
Conclusion
While all decorators have been presented mostly on their own, they can of course also be combined at will.