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.