Cli

Low-level interface to CLI parser.

Yuio’s primary interface for building CLIs is yuio.app; this is low-level module that actually parses arguments. Because of this, yuio.cli doesn’t expose a convenient interface for building CLIs. Instead, it exposes a set of classes that describe an interface; yuio.app and yuio.config compose these classes and pass them to CliParser.

This module is inspired by argparse, but there are differences:

  • all flags should start with -, other symbols are not supported (at least for now);

  • unlike argparse, this module doesn’t rely on partial parsing and sub-parses. Instead, it operates like a regular state machine, and any unmatched flags or arguments are reported right away;

  • it uses nested namespaces, one namespace per subcommand. When a subcommand is encountered, a new namespace is created and assigned to the corresponding dest in the parent namespace;

  • namespaces are abstracted away by the Namespace protocol, which has an interface similar to dict;

  • options from base command can be specified after a subcommand argument, unless subcommand shadows them. This is possible because we don’t do partial parsing.

    For example, consider this program:

    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument("-v", "--verbose", action="count")
    subparsers = parser.add_subparsers()
    subcommand = subparsers.add_parser("subcommand")
    

    Argparse will not recognize --verbose if it’s specified after subcommand, but yuio.cli handles this just fine:

    $ prog subcommand --verbose
    
  • there’s no distinction between nargs=None and nargs=1; however, there is a distinction between argument being specified inline or not. This allows us to supply arguments for options with nargs=0.

    See Handling flags with optional values for details;

  • the above point also allows us to disambiguate positional arguments and arguments with nargs="*":

    $ prog --array='a b c' subcommand
    

    See Handling options with multiple values for details;

  • this parser tracks information about argument positions and offsets, allowing it to display rich error messages;

  • we expose more knobs to tweak help formatting; see functions on Option for details.

Commands and sub-commands

class yuio.cli.Command(
*,
name: str,
desc: str,
help: str | Disabled,
epilog: str,
usage: str,
options: list[Option[Any]],
subcommands: dict[str, Command[Namespace] | LazyCommand[Namespace]],
subcommand_required: bool,
ns_ctor: Callable[[], NamespaceT],
dest: str,
ns_dest: str,
metavar: str = '<subcommand>',
)

Data about CLI interface of a single command or subcommand.

name: str

Canonical name of this command.

desc: str

Long description for a command.

help: str | Literal[_Placeholders.DISABLED]

Help message for this command, displayed when listing subcommands.

epilog: str

Long description printed after command help.

usage: str

Override for usage section of CLI help.

options: list[Option[Any]]

Options for this command.

subcommands: dict[str, Command[Namespace] | LazyCommand[Namespace]]

Last positional option can be a sub-command.

This is a map from subcommand’s name or alias to subcommand’s implementation.

subcommand_required: bool

Whether subcommand is required or optional. If no subcommands are given, this attribute is ignored.

ns_ctor: Callable[[], NamespaceT]

A constructor that will be called to create namespace for command’s arguments.

dest: str

Where to save subcommand’s name.

ns_dest: str

Where to save subcommand’s namespace.

metavar: str = '<subcommand>'

Meta variable used for subcommand option.

class yuio.cli.LazyCommand(
*,
help: str | Disabled | None,
loader: Callable[[], Command[NamespaceT]],
)

Lazy loader for data about CLI interface of a single command or subcommand.

help: str | Literal[_Placeholders.DISABLED] | None

Help message for this command, displayed when listing subcommands.

loader: Callable[[], Command[NamespaceT]]

Callback that loads the rest of the command data.

load()

Load full command data.

get_help() str | Disabled

Get or load command help.

Flags and positionals

class yuio.cli.Option(
*,
flags: list[str] | Positional,
allow_inline_arg: bool,
allow_implicit_inline_arg: bool,
nargs: NArgs,
allow_no_args: bool,
required: bool,
metavar: str | tuple[str, ...],
mutex_group: None | MutuallyExclusiveGroup,
usage: Collapse | bool,
help: str | Disabled,
help_group: HelpGroup | None,
default_desc: str | None,
show_if_inherited: bool,
allow_abbrev: bool,
dest: str,
)

Base class for a CLI option.

flags: list[str] | yuio.Positional

Flags corresponding to this option. Positional options have flags set to yuio.POSITIONAL.

allow_inline_arg: bool

Whether to allow specifying argument inline (i.e. --foo=bar).

Inline arguments are handled separately from normal arguments, and nargs setting does not affect them.

Positional options can’t take inline arguments, so this attribute has no effect on them.

allow_implicit_inline_arg: bool

Whether to allow specifying argument inline with short flags without equals sign (i.e. -fValue).

Inline arguments are handled separately from normal arguments, and nargs setting does not affect them.

Positional options can’t take inline arguments, so this attribute has no effect on them.

nargs: NArgs

How many arguments this option takes.

allow_no_args: bool

Whether to allow passing no arguments even if nargs requires some.

required: bool

Makes this option required. The parsing will fail if this option is not encountered among CLI arguments.

Note that positional arguments are always parsed; if no positionals are given, all positional options are processed with zero arguments, at which point they’ll fail nargs check. Thus, required has no effect on positionals.

metavar: str | tuple[str, ...]

Option’s meta variable, used for displaying help messages.

If nargs is an integer, this can be a tuple of strings, one for each argument. If nargs is zero, this can be an empty tuple.

mutex_group: None | MutuallyExclusiveGroup

Mutually exclusive group for this option. Positional options can’t have mutex groups.

usage: yuio.Collapse | bool

Specifies whether this option should be displayed in CLI usage. Positional options are always displayed, regardless of this setting.

help: str | yuio.Disabled

Help message for an option.

help_group: HelpGroup | None

Group for this flag, default is OPTS_GROUP for flags and ARGS_GROUP for positionals. Positionals are flags are never mixed together; if they appear in the same group, the group title will be repeated twice.

default_desc: str | None

Overrides description of default value.

show_if_inherited: bool

Force-show this flag if it’s inherited from parent command. Positionals can’t be inherited because subcommand argument always goes last.

allow_abbrev: bool

Allow abbreviation for this option.

dest: str

Key where to store parsed argument.

abstractmethod process(
cli_parser: CliParser[Namespace],
flag: Flag | None,
arguments: Argument | list[Argument],
ns: Namespace,
)

Process this argument.

This method is called every time an option is encountered on the command line. It should parse option’s args and merge them with previous values, if there are any.

When option’s arguments are passed separately (i.e. --opt arg1 arg2 ...), args is given as a list. List’s length is checked against nargs before this method is called.

When option’s arguments are passed as an inline value (i.e. --long=arg or -Sarg), the args is given as a string. nargs are not checked in this case, giving you an opportunity to handle inline option however you like.

Parameters:
  • cli_parser – CLI parser instance that’s doing the parsing. Not to be confused with yuio.parse.Parser.

  • flag – flag that set this option. This will be set to None for positional arguments.

  • arguments – option arguments, see above.

  • ns – namespace where parsed arguments should be stored.

Raises:

ArgumentError, ParsingError.

post_process(
cli_parser: CliParser[Namespace],
arguments: list[Argument],
ns: Namespace,
)

Called once at the end of parsing to post-process all arguments.

Parameters:
  • cli_parser – CLI parser instance that’s doing the parsing. Not to be confused with yuio.parse.Parser.

  • arguments – option arguments that were ever passed to this option.

  • ns – namespace where parsed arguments should be stored.

Raises:

ArgumentError, ParsingError.

property primary_short_flag: str | None

Short flag that will be displayed in CLI help.

property primary_long_flags: list[str] | None

Long flags that will be displayed in CLI help.

format_usage(
ctx: ReprContext,
/,
) tuple[ColorizedString, bool]

Allows customizing how this option looks in CLI usage.

Parameters:

ctx – repr context for formatting help.

Returns:

a string that will be used to represent this option in program’s usage section.

format_metavar(
ctx: ReprContext,
/,
) ColorizedString

Allows customizing how this option looks in CLI help.

Parameters:

ctx – repr context for formatting help.

Returns:

a string that will be appended to the list of option’s flags to format an entry for this option in CLI help message.

format_help_tail(
ctx: ReprContext,
/,
*,
all: bool = False,
) ColorizedString

Format additional content that will be added to the end of the help message, such as aliases, default value, etc.

Parameters:
  • ctx – repr context for formatting help.

  • all – whether --help=all was specified.

Returns:

a string that will be appended to the main help message.

format_alias_flags(
ctx: ReprContext,
/,
*,
all: bool = False,
) list[ColorizedString | tuple[ColorizedString, str]] | None

Format alias flags that weren’t included in primary_short_flag and primary_long_flags.

Parameters:
  • ctx – repr context for formatting help.

  • all – whether --help=all was specified.

Returns:

a list of strings, one per each alias.

format_default(
ctx: ReprContext,
/,
*,
all: bool = False,
) ColorizedString | None

Format default value that will be included in the CLI help.

Parameters:
  • ctx – repr context for formatting help.

  • all – whether --help=all was specified.

Returns:

a string that will be appended to the main help message.

nth_metavar(n: int) str

Get metavar for n-th argument for this option.

class yuio.cli.ValueOption(
*,
flags: list[str] | Positional,
allow_inline_arg: bool,
allow_implicit_inline_arg: bool,
nargs: NArgs,
allow_no_args: bool,
required: bool,
metavar: str | tuple[str, ...],
mutex_group: None | MutuallyExclusiveGroup,
usage: Collapse | bool,
help: str | Disabled,
help_group: HelpGroup | None,
default_desc: str | None,
show_if_inherited: bool,
allow_abbrev: bool,
dest: str,
merge: Callable[[T, T], T] | None,
default: object,
)

Base class for options that parse arguments and assign them to namespace.

This base handles assigning parsed value to the target destination and merging values if option is invoked multiple times. Call self.set(ns, value) from Option.process() to set result of option processing.

merge: Callable[[T, T], T] | None

Function to merge previous and new value.

default: object

Default value that will be used if this flag is not given.

Used for formatting help, does not affect actual parsing.

set(ns: Namespace, value: T)

Save new value. If merge is given, automatically merge old and new value.

class yuio.cli.ParserOption(
*,
flags: list[str] | Positional,
allow_inline_arg: bool,
allow_implicit_inline_arg: bool,
nargs: NArgs,
allow_no_args: bool,
required: bool,
metavar: str | tuple[str, ...],
mutex_group: None | MutuallyExclusiveGroup,
usage: Collapse | bool,
help: str | Disabled,
help_group: HelpGroup | None,
default_desc: str | None,
show_if_inherited: bool,
allow_abbrev: bool,
dest: str,
merge: Callable[[T, T], T] | None,
default: object,
parser: Parser[T],
)

Base class for options that use yuio.parse to process arguments.

parser: yuio.parse.Parser[T]

A parser used to parse option’s arguments.

class yuio.cli.BoolOption(
*,
pos_flags: list[str],
neg_flags: list[str],
required: bool = False,
mutex_group: None | MutuallyExclusiveGroup = None,
usage: Collapse | bool = True,
help: str | Disabled = '',
help_group: HelpGroup | None = None,
show_if_inherited: bool = False,
dest: str,
parser: Parser[bool] | None = None,
merge: Callable[[bool, bool], bool] | None = None,
default: bool | Missing = yuio.MISSING,
allow_abbrev: bool = True,
default_desc: str | None = None,
)

An option that combines StoreTrueOption, StoreFalseOption, and ParseOneOption.

If any of the pos_flags are given without arguments, it works like StoreTrueOption.

If any of the neg_flags are given, it works like StoreFalseOption.

If any of the pos_flags are given with an inline argument, the argument is parsed as a bool.

Note

Bool option has nargs set to 0, so non-inline arguments (i.e. --json false) are not recognized. You should always use inline argument to set boolean flag’s value (i.e. --json=false). This avoids ambiguity in cases like the following:

$ prog --json subcommand  # Ok
$ prog --json=true subcommand  # Ok
$ prog --json true subcommand  # Not allowed
Example:
option = yuio.cli.BoolOption(
    pos_flags=["--json"],
    neg_flags=["--no-json"],
    dest=...,
)
$ prog --json  # Set `dest` to `True`
$ prog --no-json  # Set `dest` to `False`
$ prog --json=$value  # Set `dest` to parsed `$value`
pos_flags: list[str]

List of flag names that enable this boolean option. Should be non-empty.

neg_flags: list[str]

List of flag names that disable this boolean option.

class yuio.cli.ParseOneOption(
*,
flags: list[str] | Positional,
required: bool = False,
mutex_group: None | MutuallyExclusiveGroup = None,
usage: Collapse | bool = True,
help: str | Disabled = '',
help_group: HelpGroup | None = None,
show_if_inherited: bool = False,
dest: str,
parser: Parser[T],
merge: Callable[[T, T], T] | None = None,
default: T | Missing = yuio.MISSING,
allow_abbrev: bool = True,
default_desc: str | None = None,
)

An option with a single argument that uses Yuio parser.

class yuio.cli.ParseManyOption(
*,
flags: list[str] | Positional,
required: bool = False,
mutex_group: None | MutuallyExclusiveGroup = None,
usage: Collapse | bool = True,
help: str | Disabled = '',
help_group: HelpGroup | None = None,
show_if_inherited: bool = False,
dest: str,
parser: Parser[T],
merge: Callable[[T, T], T] | None = None,
default: T | Missing = yuio.MISSING,
allow_abbrev: bool = True,
default_desc: str | None = None,
)

An option with multiple arguments that uses Yuio parser.

class yuio.cli.StoreConstOption(
*,
flags: list[str],
required: bool = False,
mutex_group: None | MutuallyExclusiveGroup = None,
usage: Collapse | bool = True,
help: str | Disabled = '',
help_group: HelpGroup | None = None,
show_if_inherited: bool = False,
dest: str,
merge: Callable[[T, T], T] | None = None,
default: T | Missing = yuio.MISSING,
const: T,
allow_abbrev: bool = True,
default_desc: str | None = None,
)

An option with no arguments that stores a constant to namespace.

const: T

Constant that will be stored.

class yuio.cli.StoreFalseOption(
*,
flags: list[str],
required: bool = False,
mutex_group: None | MutuallyExclusiveGroup = None,
usage: Collapse | bool = True,
help: str | Disabled = '',
help_group: HelpGroup | None = None,
show_if_inherited: bool = False,
dest: str,
default: bool | Missing = yuio.MISSING,
allow_abbrev: bool = True,
default_desc: str | None = None,
)

An option that stores False to namespace.

class yuio.cli.StoreTrueOption(
*,
flags: list[str],
required: bool = False,
mutex_group: None | MutuallyExclusiveGroup = None,
usage: Collapse | bool = True,
help: str | Disabled = '',
help_group: HelpGroup | None = None,
show_if_inherited: bool = False,
dest: str,
default: bool | Missing = yuio.MISSING,
allow_abbrev: bool = True,
default_desc: str | None = None,
)

An option that stores True to namespace.

class yuio.cli.CountOption(
*,
flags: list[str],
required: bool = False,
mutex_group: None | MutuallyExclusiveGroup = None,
usage: Collapse | bool = True,
help: str | Disabled = '',
help_group: HelpGroup | None = None,
show_if_inherited: bool = False,
dest: str,
default: int | Missing = yuio.MISSING,
allow_abbrev: bool = True,
default_desc: str | None = None,
)

An option that counts number of its appearances on the command line.

class yuio.cli.VersionOption(
*,
version: str,
flags: list[str] = ['-V', '--version'],
usage: Collapse | bool = yuio.COLLAPSE,
help: str | Disabled = 'Print program version and exit.',
help_group: HelpGroup | None = HelpGroup(title='Misc options', help='', collapse=False, _slug=None),
allow_abbrev: bool = True,
)

An option that prints app’s version and stops the program.

version: str

Version to print.

class yuio.cli.HelpOption(
*,
flags: list[str] = ['-h', '--help'],
usage: Collapse | bool = yuio.COLLAPSE,
help: str | Disabled = 'Print this message and exit.',
help_group: HelpGroup | None = HelpGroup(title='Misc options', help='', collapse=False, _slug=None),
allow_abbrev: bool = True,
)

An option that prints help message and stops the program.

Namespace

class yuio.cli.Namespace(*args, **kwargs)

Protocol for namespace implementations.

abstractmethod __getitem__(key: str, /) Any
abstractmethod __setitem__(key: str, value: Any, /)
abstractmethod __contains__(key: str, /) bool
class yuio.cli.ConfigNamespace(config: ConfigT)

Wrapper that makes Config instances behave like namespaces.

property config: ConfigT

Wrapped config instance.

CLI parser

class yuio.cli.CliParser(
command: Command[NamespaceT],
/,
*,
help_parser: DocParser,
allow_abbrev: bool,
)

CLI arguments parser.

Parameters:
  • command – root command.

  • allow_abbrev – allow abbreviating CLI flags if that doesn’t create ambiguity.

  • help_parser – help parser that will be used to parse and display help for options that’ve failed to parse.

parse(args: list[str] | None = None) NamespaceT

Parse arguments and invoke their actions.

Parameters:

args – CLI arguments, not including the program name (i.e. the first argument). If None, use sys.argv instead.

Returns:

namespace with parsed arguments.

Raises:

ArgumentError, ParsingError.

class yuio.cli.Argument(
value: str,
index: int,
pos: int,
metavar: str,
flag: Flag | None,
)

Represents a CLI argument, or its part.

For positionals, this will contain the entire argument. For inline values, this will contain value substring and its position relative to the full value.

Example:

Consider the following command arguments:

--arg=value

Argument "value" will be represented as:

Argument(value="value", index=0, pos=6, flag="--arg", metavar=...)
value: str

Contents of the argument.

index: int

Index of this argument in the array that was passed to CliParser.parse().

Note that this array does not include executable name, so indexes are shifted relative to sys.argv.

pos: int

Position of the value relative to the original argument string.

metavar: str

Meta variable for this argument.

flag: Flag | None

If this argument belongs to a flag, this attribute will contain flag’s name.

class yuio.cli.Flag(value: 'str', index: 'int')
value: str

Name of the flag.

index: int

Index of this flag in the array that was passed to CliParser.parse().

Note that this array does not include executable name, so indexes are shifted relative to sys.argv.

class yuio.cli.ArgumentError(
*args,
flag: Flag | None = None,
arguments: Argument | list[Argument] | None = None,
n_arg: int | None = None,
pos: tuple[int, int] | None = None,
path: list[tuple[Any, str | None]] | None = None,
option: Option[Any] | None = None,
)

Error that happened during argument parsing.

flag: Flag | None

Flag that caused this error. Can be None if error is caused by a positional argument.

arguments: Argument | list[Argument] | None

Arguments that caused this error.

This can be a single argument, or multiple arguments. In the later case, n_arg should correspond to the argument that failed to parse. If n_arg is None, then all arguments are treated as faulty.

Note

Don’t confuse arguments and args: the latter contains formatting arguments and is defined in the BaseException class.

pos: tuple[int, int] | None

Position in the original string in which this error has occurred (start and end indices).

If n_arg is set, and arguments is given as a list, then this position is relative to the argument at index n_arg.

If arguments is given as a single argument (not a list), then this position is relative to that argument.

Otherwise, position is ignored.

n_arg: int | None

Index of the argument that caused the error.

path: list[tuple[Any, str | None]] | None

Same as in ParsingError.path. Can be present if parser uses parse_config() for validation.

option: Option[Any] | None

Option which caused failure.

classmethod from_parsing_error(
e: ParsingError,
/,
*,
flag: Flag | None = None,
arguments: Argument | list[Argument] | None = None,
option: Option[Any] | None = None,
)

Convert parsing error to argument error.

type yuio.cli.NArgs = int | Literal['+']

Type alias for nargs.

Note

"*" from argparse is equivalent to nargs="+" with allow_no_args=True; "?" from argparse is equivalent to nargs=1 with allow_no_args=True.

Option grouping

class yuio.cli.MutuallyExclusiveGroup(*, required: bool = False)

A sentinel for creating mutually exclusive groups.

Pass an instance of this class all field()s that should be mutually exclusive.

required: bool = False

Require that one of the mutually exclusive options is always given.

class yuio.cli.HelpGroup(
title: str,
*,
help: str | Disabled = '',
collapse: bool = False,
_slug: str | None = None,
)

Group of flags in CLI help.

title: str

Title for this group.

help: str | Literal[_Placeholders.DISABLED] = ''

Help message for an option.

collapse: bool = False

Hide options from this group in CLI help, but show group’s title and help.

yuio.cli.ARGS_GROUP = HelpGroup(title='Arguments', help='', collapse=False, _slug=None)

Help group for positional arguments.

yuio.cli.SUBCOMMANDS_GROUP = HelpGroup(title='Subcommands', help='', collapse=False, _slug=None)

Help group for subcommands.

yuio.cli.OPTS_GROUP = HelpGroup(title='Options', help='', collapse=False, _slug=None)

Help group for flags.

yuio.cli.MISC_GROUP = HelpGroup(title='Misc options', help='', collapse=False, _slug=None)

Help group for misc flags such as --help or --version.