Supported Type Annotations
The following gives a short overview on (known) supporte type hints for ArgTyper. Since ArgTyper internally tries to convert type annotions to their base types, there might be more.
Warning
Be ware that default values are never verified and can be arbitrarily set.
Standard Types
import argtyper
from pathlib import Path
def type_test(
name,
yesno: bool,
flag: bool = False,
f: Path = None,
):
print(f"name: {name}")
print(f"yesno: {yesno}")
print(f"flag: {flag}")
print(f"f: {f}")
at = argtyper.ArgTyper(type_test)
at()
This would produce the following output for the parser:
$ python supported_types.py -h
usage: supported_types.py [-h] [--flag] [-f F] NAME YESNO
positional arguments:
NAME provide argument of type STR
YESNO provide a boolean value (e.g. 'yes', 't', 'false', '0')
optional arguments:
-h, --help show this help message and exit
--flag toggle option (default: False)
-f F provide argument of type PATH
$ python supported_types.py 'Han Solo' 1
name: Han Solo
yesno: True
flag: False
f: None
$ python supported_types.py 'Han Solo' false --flag
name: Han Solo
yesno: False
flag: True
f: None
$ python supported_types.py 'Han Solo' false --flag -f somepath
name: Han Solo
yesno: False
flag: True
f: somepath
$ python supported_types.py 'Han Solo' foo
usage: supported_types.py [-h] [--flag] [-f F] NAME YESNO
Error: supported_types.py: argument yesno: invalid choice 'foo' (choose from ['0', 'off', 'f', 'false', 'n', 'no', 'nope'] / ['1', 'true', 'yes', 'on', 't', 'y', 'yep'])
Lists
import argtyper
from typing import List
def type_test(
pos_arg: List,
opt_arg: List[int] = None,
):
print(f"Positional Arg: {pos_arg}")
print(f"Optional Arg: {opt_arg}")
at = argtyper.ArgTyper(type_test)
at()
This would produce the following output for the parser:
$ python type_list.py -h
usage: type_list.py [-h] [--opt_arg OPT_ARG [OPT_ARG ...]]
POS_ARG [POS_ARG ...]
positional arguments:
POS_ARG provide a list
optional arguments:
-h, --help show this help message and exit
--opt_arg OPT_ARG [OPT_ARG ...]
provide a list of INT values
$ python type_list.py 1 a b
Positional Arg: ['1', 'a', 'b']
Optional Arg: None
$ python type_list.py 1 a b --opt_arg 1 2 3 4
Positional Arg: ['1', 'a', 'b']
Optional Arg: [1, 2, 3, 4]
$ python type_list.py 1 a b --opt_arg z 1
usage: type_list.py [-h] [--opt_arg OPT_ARG [OPT_ARG ...]]
POS_ARG [POS_ARG ...]
Error: type_list.py: argument --opt_arg: invalid int value: 'z'
Tuples
import argtyper
from typing import Tuple
def type_test(
pos_arg: Tuple[int, float],
opt_arg: Tuple[str, int, str] = ("Nr.", 1, "Tester"),
):
print(f"Positional Arg: {pos_arg}")
print(f"Optional Arg: {opt_arg}")
at = argtyper.ArgTyper(type_test)
at()
This would produce the following output for the parser:
$ python type_tuple.py -h
usage: type_tuple.py [-h] [--opt_arg STR INT STR] pos_arg pos_arg
positional arguments:
pos_arg provide 2 arguments with corresponding types ('INT',
'FLOAT')
optional arguments:
-h, --help show this help message and exit
--opt_arg STR INT STR
provide 3 arguments with corresponding types ('STR',
'INT', 'STR')
$ python type_tuple.py 1 0.03
Positional Arg: (1, 0.03)
Optional Arg: ('Nr.', 1, 'Tester')
$ python type_tuple.py 1 0.03 --opt_arg more 2 see
Positional Arg: (1, 0.03)
Optional Arg: ('more', 2, 'see')
$ python type_tuple.py 1 z
usage: type_tuple.py [-h] [--opt_arg STR INT STR] pos_arg pos_arg
Error: type_tuple.py: argument pos_arg[1]: could not convert string to float: 'z'
Literals
import argtyper
from typing import Literal
def type_test(
pos_arg: Literal["yes", "no"], opt_arg: Literal["true", "false", 10, 20] = 30
):
print(f"Positional Arg: {pos_arg}, {type(pos_arg)}")
print(f"Optional Arg: {opt_arg}, {type(opt_arg)}")
at = argtyper.ArgTyper(type_test)
at()
This would produce the following output for the parser:
$ python type_literal.py -h
usage: type_literal.py [-h] [--opt_arg {true,false,10,20}] {yes,no}
positional arguments:
{yes,no} choose one of the given options
optional arguments:
-h, --help show this help message and exit
--opt_arg {true,false,10,20}
choose one of the given options
$ python type_literal.py yes
Positional Arg: yes, <class 'str'>
Optional Arg: 30, <class 'int'>
$ python type_literal.py no --opt_arg more 2 see
usage: type_literal.py [-h] [--opt_arg {true,false,10,20}] {yes,no}
Error: type_literal.py: argument --opt_arg: invalid choice 'more' (choose from ['true', 'false', 10, 20])
$ python type_literal.py 1
usage: type_literal.py [-h] [--opt_arg {true,false,10,20}] {yes,no}
Error: type_literal.py: argument {yes,no}: invalid choice '1' (choose from ['yes', 'no'])
$ python type_literal.py yes --opt_arg 30
usage: type_literal.py [-h] [--opt_arg {true,false,10,20}] {yes,no}
Error: type_literal.py: argument --opt_arg: invalid choice '30' (choose from ['true', 'false', 10, 20])
Custom Arguments
You can create arbitrary arguments. The will receive the command line argument value
as string in the class’ __init__ function. For example, you can use this
for custom verification of arguments. You only need to raise an Exception to
provoke a parsing error and exit.
import argtyper
class CustomArg(object):
def __init__(self, string):
self.data = string
if "jedi" not in string.lower():
raise ValueError("Error in custom Argument")
def __str__(self):
return f"Custom Argument({self.data})"
def type_test(
pos_arg: CustomArg,
opt_arg: CustomArg = None,
):
print(f"Positional Arg: {pos_arg}")
print(f"Optional Arg: {opt_arg}")
at = argtyper.ArgTyper(type_test)
at()
This would produce the following output for the parser:
$ python type_custom_arg.py -h
usage: type_custom_arg.py [-h] [--opt_arg OPT_ARG] POS_ARG
positional arguments:
POS_ARG provide argument of type CUSTOMARG
optional arguments:
-h, --help show this help message and exit
--opt_arg OPT_ARG provide argument of type CUSTOMARG
A successful execution would look like this:
$ python type_custom_arg.py jedi
Positional Arg: Custom Argument(jedi)
Optional Arg: None
A failed execution, which throws an error, would look like this:
$ python type_custom_arg.py sith
usage: type_custom_arg.py [-h] [--opt_arg OPT_ARG] POS_ARG
Error: type_custom_arg.py: argument POS_ARG: invalid CustomArg value: 'sith'
Custom Actions
Custom actions are more powerful, as they have more control during creation of the parser
and also during handling of arguments. CustomActions are subclasses of argparse.Action.
from argtyper.base import remove_uuid4_prefix
import argtyper
import argparse
class CustomAction(argparse.Action):
def __init__(self, option_strings, dest, nargs=None, **kwargs):
if nargs is not None:
raise ValueError("nargs not allowed")
if "help" not in kwargs:
kwargs["help"] = f"This is a custom action"
super(CustomAction, self).__init__(option_strings, dest, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
print("Parsing custom action: %r %r %r" % (namespace, values, option_string))
setattr(namespace, self.dest, values)
print(f"Argument Name: {argtyper.base.remove_uuid4_prefix(self.dest)}")
print("------\nAction parsing finished\n-----\n")
def type_test(
pos_arg: CustomAction,
opt_arg: CustomAction = None,
):
print(f"Positional Arg: {pos_arg}")
print(f"Optional Arg: {opt_arg}")
at = argtyper.ArgTyper(type_test)
at()
This would produce the following output for the parser:
$ python type_custom_action.py -h
usage: type_custom_action.py [-h] [--opt_arg OPT_ARG] POS_ARG
positional arguments:
POS_ARG This is a custom action
optional arguments:
-h, --help show this help message and exit
--opt_arg OPT_ARG This is a custom action
And if run, we can see that CustomAction.__call__() is executed during parsing, and before
execution is handed over from the parser to the actual function type_test.
$ python type_custom_action.py 'custom action input'
Parsing custom action: Namespace(e7c28963e3ea4f5aa488bbf4a2737a85_opt_arg=None, **{'33be54a6c9644424bbf9e213e5351d2f_pos_arg': None}) 'custom action input' None
Argument Name: pos_arg
------
Action parsing finished
-----
Positional Arg: custom action input
Optional Arg: None
Another thing that can be seen here are the argument names in the namespace. Internally, ArgTyper prefixes destinations with a random uuid4 value, to prevent argument name collisions in parent and child functions.
The only places where you should come into contact with that are either if you implement custom actions,
or if you use the parser instance directly yourself. For both cases you can use argtyper.base.remove_uuid4_prefix(<string>)()
to remove the prefix and retrieve the correct argument names from dest.