Features

Types

Argument types are read from the function’s type hints (see examples/annotations.py) or docstring. If type information for a parameter is given both as type hint and in the docstring, the types must match.

def func(arg1: int, arg2: str):
    ...

Docstrings can use the standard Sphinx-style

:param <type> <name>: <description>

.. or

:param <name>: <description>
:type <name>: <type>

.. Any of ``param``, ``parameter``, ``arg``, ``argument``, ``key``, and
   ``keyword`` can be used interchangeably, as can ``type`` and
   ``kwtype``.  Consistency is recommended but not enforced.

or Google- and Numpy-style docstrings (see examples/styles.py), which are converted using Napoleon [1]. If using one of these alternate styles and generating documentation with sphinx.ext.autodoc, be sure to also enable sphinx.ext.napoleon.

<type> is evaluated in the function’s global namespace when defopt.run is called.

See Standard types, Booleans, Lists, Choices, Tuples, Unions, and Parsers for more information on specific types.

Subcommands

If a list of commands are passed to defopt.run, they are treated as subcommands which are run by name.

defopt.run([func1, func2])

The command line usage will indicate this.

usage: test.py [-h] {func1,func2} ...

positional arguments:
  {func1,func2}

Underscores in function names are replaced by hyphens.

Friendlier subcommand names can be provided by calling defopt.run with a dict mapping subcommand names to functions. In that case, no underscore replacement occurs (as one can directly set names with hyphens).  .. code-block:: python   defopt.run({“friendly_func”: awkward_name, “func2”: other_name})  Command line usage will use the new names

usage: test.py [-h] {friendly_func,func2} ...

positional arguments:
  {friendly_func,func2}

Nested Subcommands

By using dictionaries, the subcommands can also be nested.

defopt.run({'func1': func1, 'sub': [func2, func3]})

The nested subcommands are accessed e.g. by test.py sub func2. The subcommands can be nested to an arbitrary level by using nested dictionaries.

A runnable example is available at examples/nested.py.

Standard types

For parameters annotated as str, int, float, and pathlib.Path, the type constructor is directly called on the argument passed in.

For parameters annotated as slice, the argument passed in is split at ":", the resulting fragments evaluated with ast.literal_eval (with empty fragments being converted to None), and the results passed to the slice constructor. For example, 1::2 results in slice(1, None, 2), which corresponds to the normal indexing syntax.

Flags

Python positional-or-keyword parameters are converted to CLI positional arguments, with their name unmodified [2]. Python keyword-only parameters are converted to CLI flags, with underscores replaced by hyphens. Additionally, one-letter short flags are generated for all flags that do not share their initial with other flags.

Optional Python parameters (i.e. with a default) are converted to optional CLI arguments (regardless of whether the Python parameter is positional-or-keyword or keyword-only); required Python parameters (i.e. with no default) are converted to required CLI arguments.

usage: test.py [-h] [-k KWONLY] positional_no_default [positional_with_default]

positional arguments:
  positional_no_default
  positional_with_default

optional arguments:
  -h, --help            show this help message and exit
  -k KWONLY, --kwonly KWONLY

Alternatively, one can make all optional Python parameters, regardless of whether they are keyword-only or not, also map to CLI flags, by passing strict_kwonly=False to defopt.run. (This behavior is similar to the informal approach previously commonly found on Python 2, which was to consider required parameters as positional and optional parameters as keyword.)

Auto-generated short flags can be overridden by passing a dictionary to defopt.run which maps flag names to single letters:

defopt.run(main, short={'keyword-arg': 'a'})

Now, -a is exactly equivalent to --keyword-arg:

-a KEYWORD_ARG, --keyword-arg KEYWORD_ARG

A runnable example is available at examples/short.py.

Passing an empty dictionary suppresses automatic short flag generation, without adding new flags.

Booleans

Boolean keyword-only parameters (or, as above, parameters with defaults, if strict_kwonly=False) are automatically converted to two separate flags: --name which stores True and --no-name which stores False. The help text and the default are displayed next to the --name flag:

--flag      Set "flag" to True
            (default: False)
--no-flag

Note that this does not apply to mandatory boolean parameters; these must be specified as one of 1/t/true or 0/f/false (case insensitive).

If no_negated_flags=True is passed to defopt.run, no negated flags (--no-name) are generated for boolean arguments that have False as their default value.

A runnable example is available at examples/booleans.py.

Lists

Lists are automatically converted to flags (regardless of whether they are positional-or-keyword, or keyword-only) which take zero or more arguments.

When declaring in a docstring that a parameter is a list, put the contained type in square brackets, even on Python versions which do not otherwise support that syntax:

:param list[int] numbers: A sequence of numbers

typing.List, typing.Sequence and typing.Iterable are all treated in the same way as list.

The list can now be specified on the command line using multiple arguments.

test.py --numbers 1 2 3

A runnable example is available at examples/lists.py.

Choices

Subclasses of enum.Enum are handled specially on the command line to produce more helpful output.

positional arguments:
  {red,blue,yellow}  Your favorite color

This also produces a more helpful message when an invalid option is chosen.

test.py: error: argument color: invalid choice: 'black'
                                (choose from 'red', 'blue', 'yellow')

A runnable example is available at examples/choices.py.

Likewise, typing.Literal and its backport typing_extensions.Literal are also supported.

Tuples

Typed tuples and typed namedtuples (as defined using typing.Tuple and typing.NamedTuple) consume as many command-line arguments as the tuple has fields, convert each argument to the correct type, and wrap them into the annotation class. When a typing.NamedTuple is used for an optional argument, the names of the fields are used in the help.

Unions

Union types can be specified with typing.Union[type1, type2], or, when using docstring annotations, as type1 or type2. The type1 | type2 syntax is also supported, if the underlying Python version supports it. When an argument is annotated with a union type, an attempt is made to convert the command-line argument with the parser for each of the members of the union, in the order they are given; the value returned by the first parser that does not raise a ValueError is used.

typing.Optional[type1], i.e. Union[type1, type(None)], is normally equivalent to type1. This is implemented using a parser for type(None) that raises ValueError on all inputs, and can thus be overloaded by setting a custom parser for type(None). As an exception to the “try parsers in order” rule given above, a parser for type(None) will always be tried first; this is so that e.g. Optional[str] can parse some user-chosen values as None and the others as str.

typing.Optional[bool] is treated separately, as a special case, to still act as a boolean flag. Defining a default value of None for the argument will result in receiving None if the option is not specified on the command line and either True or False if one of the two boolean flags are provided.

Collection types are not supported in unions; e.g. Union[List[type1]] is not supported (with the exception of Optional[List[type1]], which is always equivalent to List[type1]).

Note that unfortunately, in certain circumstances, Python will reorder members of a union. Most notably, List[Union[A, B]] caches the union type, so a later List[Union[B, A]] will be silently converted to List[Union[A, B]], which matters if some inputs are accepted by both the parser for A and the parser for B. Note that this problem does not affect list[Union[A, B]], on versions of Python that support it.

Parsers

Arbitrary argument types can be used as long as functions to parse them from strings are provided.

def parse_person(string):
    last, first = string.split(',')
    return Person(first.strip(), last.strip())

defopt.run(..., parsers={Person: parse_person})

Person objects can be now built directly from the command line.

test.py --person "VAN ROSSUM, Guido"

A runnable example is available at examples/parsers.py.

If the type of an annotation can be called with a single parameter and that parameter is annotated as str, then defopt will assume that the type is its own parser.

class StrWrapper:
    def __init__(self, s: str):
        self.s = s

def main(s: StrWrapper):
    pass

defopt.run(main)

StrWrapper objects can now be built directly from the command line.

test.py foo

Variable positional arguments

If the function definition contains *args, the parser will accept zero or more positional arguments. When specifying a type, specify the type of the elements, not the container.

def main(*numbers: int):
    """:param numbers: Positional numeric arguments"""

This will create a parser that accepts zero or more positional arguments which are individually parsed as integers. They are passed as they would be from code and received as a tuple.

test.py 1 2 3

If the argument is a list type (see Lists), this will instead create a flag that can be specified multiple times, each time creating a new list.

Variable keyword arguments (**kwargs) are not supported.

A runnable example is available at examples/starargs.py.

Private arguments

Arguments whose name start with an underscore will not be added to the parser.

Exceptions

Exception types can also be listed in the function’s docstring, with

:raises <type>: <description>

If the function call raises an exception whose type is mentioned in such a :raises: clause, the exception message is printed and the program exits with status code 1, but the traceback is suppressed.

A runnable example is available at examples/exceptions.py.

Additional parser features

Type information can be automatically added to the help text by passing show_types=True to defopt.run. Defaults are displayed by default (sic), but this can be turned off by passing show_defaults=False.

By default, a --version flag will be added; the version string is autodetected from the module where the function is defined (and the flag is suppressed if the version detection fails). Passing version="..." to defopt.run forces the version string, and passing version=False suppresses the flag.

Entry points

To use a script as a console entry point with setuptools, one needs to create a function that can be called without arguments.

def entry_point():
    defopt.run(main)

This entry point can now be referenced in the setup.py file.

setup(
    ...,
    entry_points={'console_scripts': ['name=test:entry_point']}
)

Alternatively, to keep scripts importable independently of defopt, arbitrary type-hinted functions can be directly run from the command line with

$ python -m defopt dotted.name args ...

which is equivalent to passing the dotted.name function to defopt.run and calling the resulting script with args .... The dotted.name can use a colon to separate the package name from the function name (as supported by relies on pkgutil.resolve_name).