actions
Recursively Nesting Sub-Parsers Action for Typed Argument Parsing.
The actions
module contains the SubParsersAction
class, which is an action
that provides recursive namespace nesting when parsing sub-commands. It also
contains the BooleanOptionalAction
class, which is a direct backport of the
Python standard library argparse
class of the same name.
SubParsersAction
Recursively Nesting Sub-Parsers Action for Typed Argument Parsing.
This custom action differs in functionality from the existing standard argparse SubParsersAction because it nests the resultant sub-namespace directly into the supplied parent namespace, rather than iterating through and updating the parent namespace object with each argument individually.
Examples:
Construct ArgumentParser
:
# Create Argument Parser
parser = argparse.ArgumentParser()
# Add Example Global Argument
parser.add_argument("--time")
# Add SubParsersAction
subparsers = parser.add_subparsers()
# Add Example 'walk' Command with Arguments
walk = subparsers.add_parser("walk")
walk.add_argument("--speed")
walk.add_argument("--distance")
# Add Example 'talk' Command with Arguments
talk = subparsers.add_parser("talk")
talk.add_argument("--volume")
talk.add_argument("--topic")
Parse the Arguments:
--time 3 walk --speed 7 --distance 42
Check Resultant Namespaces:
Original: Namespace(time=3, speed=7, distance=42)
Custom: Namespace(time=3, walk=Namespace(speed=7, distance=42))
This behaviour results in a final namespace structure which is much easier to parse, where subcommands are easily identified and nested into their own namespace recursively.
Source code in pydantic_argparse/argparse/actions.py
class SubParsersAction(argparse._SubParsersAction):
"""Recursively Nesting Sub-Parsers Action for Typed Argument Parsing.
This custom action differs in functionality from the existing standard
argparse SubParsersAction because it nests the resultant sub-namespace
directly into the supplied parent namespace, rather than iterating through
and updating the parent namespace object with each argument individually.
Example:
Construct `ArgumentParser`:
```python
# Create Argument Parser
parser = argparse.ArgumentParser()
# Add Example Global Argument
parser.add_argument("--time")
# Add SubParsersAction
subparsers = parser.add_subparsers()
# Add Example 'walk' Command with Arguments
walk = subparsers.add_parser("walk")
walk.add_argument("--speed")
walk.add_argument("--distance")
# Add Example 'talk' Command with Arguments
talk = subparsers.add_parser("talk")
talk.add_argument("--volume")
talk.add_argument("--topic")
```
Parse the Arguments:
```console
--time 3 walk --speed 7 --distance 42
```
Check Resultant Namespaces:
```python
Original: Namespace(time=3, speed=7, distance=42)
Custom: Namespace(time=3, walk=Namespace(speed=7, distance=42))
```
This behaviour results in a final namespace structure which is much easier
to parse, where subcommands are easily identified and nested into their own
namespace recursively.
"""
def __call__(
self,
parser: argparse.ArgumentParser,
namespace: argparse.Namespace,
values: Union[str, Sequence[Any], None],
option_string: Optional[str] = None,
) -> None:
"""Parses arguments into a namespace with the specified subparser.
This custom method parses arguments with the specified subparser, then
embeds the resultant sub-namespace into the supplied parent namespace.
Args:
parser (argparse.ArgumentParser): Parent argument parser object.
namespace (argparse.Namespace): Parent namespace being parsed to.
values (Union[str, Sequence[Any], None]): Arguments to parse.
option_string (Optional[str]): Optional option string (not used).
Raises:
argparse.ArgumentError: Raised if subparser name does not exist.
"""
# Check values object is a sequence
# In order to not violate the Liskov Substitution Principle (LSP), the
# function signature for __call__ must match the base Action class. As
# such, this function signature also accepts 'str' and 'None' types for
# the values argument. However, in reality, this should only ever be a
# list of strings here, so we just do a type cast.
values = cast(List[str], values)
# Get Parser Name and Remaining Argument Strings
parser_name, *arg_strings = values
# Try select the parser
try:
# Select the parser
parser = self._name_parser_map[parser_name]
except KeyError as exc:
# Parser doesn't exist, raise an exception
raise argparse.ArgumentError(
self,
f"unknown parser {parser_name} (choices: {', '.join(self._name_parser_map)})",
) from exc
# Parse all the remaining options into a sub-namespace, then embed this
# sub-namespace into the parent namespace
subnamespace, arg_strings = parser.parse_known_args(arg_strings)
setattr(namespace, parser_name, subnamespace)
# Store any unrecognized options on the parent namespace, so that the
# top level parser can decide what to do with them
if arg_strings:
vars(namespace).setdefault(argparse._UNRECOGNIZED_ARGS_ATTR, [])
getattr(namespace, argparse._UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
__call__(self, parser, namespace, values, option_string=None)
special
Parses arguments into a namespace with the specified subparser.
This custom method parses arguments with the specified subparser, then embeds the resultant sub-namespace into the supplied parent namespace.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
parser |
argparse.ArgumentParser |
Parent argument parser object. |
required |
namespace |
argparse.Namespace |
Parent namespace being parsed to. |
required |
values |
Union[str, Sequence[Any], None] |
Arguments to parse. |
required |
option_string |
Optional[str] |
Optional option string (not used). |
None |
Exceptions:
Type | Description |
---|---|
argparse.ArgumentError |
Raised if subparser name does not exist. |
Source code in pydantic_argparse/argparse/actions.py
def __call__(
self,
parser: argparse.ArgumentParser,
namespace: argparse.Namespace,
values: Union[str, Sequence[Any], None],
option_string: Optional[str] = None,
) -> None:
"""Parses arguments into a namespace with the specified subparser.
This custom method parses arguments with the specified subparser, then
embeds the resultant sub-namespace into the supplied parent namespace.
Args:
parser (argparse.ArgumentParser): Parent argument parser object.
namespace (argparse.Namespace): Parent namespace being parsed to.
values (Union[str, Sequence[Any], None]): Arguments to parse.
option_string (Optional[str]): Optional option string (not used).
Raises:
argparse.ArgumentError: Raised if subparser name does not exist.
"""
# Check values object is a sequence
# In order to not violate the Liskov Substitution Principle (LSP), the
# function signature for __call__ must match the base Action class. As
# such, this function signature also accepts 'str' and 'None' types for
# the values argument. However, in reality, this should only ever be a
# list of strings here, so we just do a type cast.
values = cast(List[str], values)
# Get Parser Name and Remaining Argument Strings
parser_name, *arg_strings = values
# Try select the parser
try:
# Select the parser
parser = self._name_parser_map[parser_name]
except KeyError as exc:
# Parser doesn't exist, raise an exception
raise argparse.ArgumentError(
self,
f"unknown parser {parser_name} (choices: {', '.join(self._name_parser_map)})",
) from exc
# Parse all the remaining options into a sub-namespace, then embed this
# sub-namespace into the parent namespace
subnamespace, arg_strings = parser.parse_known_args(arg_strings)
setattr(namespace, parser_name, subnamespace)
# Store any unrecognized options on the parent namespace, so that the
# top level parser can decide what to do with them
if arg_strings:
vars(namespace).setdefault(argparse._UNRECOGNIZED_ARGS_ATTR, [])
getattr(namespace, argparse._UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
BooleanOptionalAction
Action for parsing paired GNU-style boolean arguments.
This backported action provides the functionality for parsing paired GNU-style boolean arguments, such as "--foo/--no-foo". This style of argument allows us to easily provide required boolean arguments.
This action was added into the Python standard library argparse
module
in BPO-8538
and is available in
Python 3.9 and above. In order to support Python 3.8 we directly backport
the class and make it available here.
Source: https://github.com/python/cpython/blob/v3.11.0/Lib/argparse.py#L878-L914
Source code in pydantic_argparse/argparse/actions.py
class BooleanOptionalAction(argparse.Action): # pragma: no cover
"""Action for parsing paired GNU-style boolean arguments.
This backported action provides the functionality for parsing paired
GNU-style boolean arguments, such as "--foo/--no-foo". This style of
argument allows us to easily provide *required* boolean arguments.
This action was added into the Python standard library `argparse` module
in [`BPO-8538`](https://bugs.python.org/issue8538) and is available in
Python 3.9 and above. In order to support Python 3.8 we directly backport
the class and make it available here.
Source:
<https://github.com/python/cpython/blob/v3.11.0/Lib/argparse.py#L878-L914>
"""
def __init__(
self,
option_strings: Sequence[str],
dest: str,
default: Optional[Union[T, str]] = None,
type: Optional[Union[Callable[[str], T], argparse.FileType]] = None, # noqa: A002
choices: Optional[Iterable[T]] = None,
required: bool = False,
help: Optional[str] = None, # noqa: A002
metavar: Optional[Union[str, Tuple[str, ...]]] = None,
) -> None:
"""Instantiates the Boolean Optional Action.
This creates the default provided "--<OPT>" option strings which set
the argument to `True`. It also creates alternative pair "--no-<OPT>"
option strings which set the argument to `False`.
Args:
option_strings (Sequence[str]): Option strings.
dest (str): Destination variable to save the value to.
default (Optional[Union[T, str]]): Default value of the option.
type (Optional[Union[Callable[[str], T], argparse.FileType]]): Type
to cast the option to.
choices (Optional[Iterable[T]]): Allowed values for the option.
required (bool): Whether the option is required.
help (Optional[str]): Help string for the option.
metavar (Optional[Union[str, Tuple[str, ...]]]): Meta variable name
for the option.
"""
# Initialise intermediary option strings list
_option_strings = []
# Loop through passed in option strings
for option_string in option_strings:
# Append the option string to the new list
_option_strings.append(option_string)
# Check if this option string is a "--<OPT>" option string
if option_string.startswith("--"):
# Create a "--no-<OPT>" negated option string
option_string = "--no-" + option_string[2:]
# Append the negated option string to the new list as well
_option_strings.append(option_string)
# Initialise Super Class
super().__init__(
option_strings=_option_strings,
dest=dest,
nargs=0,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar,
)
def __call__(
self,
parser: argparse.ArgumentParser,
namespace: argparse.Namespace,
values: Optional[Union[str, Sequence[Any]]],
option_string: Optional[str] = None,
) -> None:
"""Parses the provided boolean arguments into a namespace.
This custom method parses arguments as booleans, negating the values of
any arguments prepended with "--no-".
Args:
parser (argparse.ArgumentParser): Parent argument parser object.
namespace (argparse.Namespace): Parent namespace being parsed to.
values (Optional[Union[str, Sequence[Any]]]): Arguments to parse.
option_string (Optional[str]): Optional option string.
"""
# Check if the passed in option string matches our option strings
if option_string in self.option_strings:
# Set a boolean value on the namespace
# If the option string starts with "--no-", then negate the value
setattr(namespace, self.dest, not option_string.startswith("--no-")) # type: ignore[union-attr]
def format_usage(self) -> str:
"""Formats the usage string.
Returns:
str: Usage string for the option.
"""
# Format and return usage string
return " | ".join(self.option_strings)
__init__(self, option_strings, dest, default=None, type=None, choices=None, required=False, help=None, metavar=None)
special
Instantiates the Boolean Optional Action.
This creates the default provided "--True
. It also creates alternative pair "--no-False
.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
option_strings |
Sequence[str] |
Option strings. |
required |
dest |
str |
Destination variable to save the value to. |
required |
default |
Optional[Union[T, str]] |
Default value of the option. |
None |
type |
Optional[Union[Callable[[str], T], argparse.FileType]] |
Type to cast the option to. |
None |
choices |
Optional[Iterable[T]] |
Allowed values for the option. |
None |
required |
bool |
Whether the option is required. |
False |
help |
Optional[str] |
Help string for the option. |
None |
metavar |
Optional[Union[str, Tuple[str, ...]]] |
Meta variable name for the option. |
None |
Source code in pydantic_argparse/argparse/actions.py
def __init__(
self,
option_strings: Sequence[str],
dest: str,
default: Optional[Union[T, str]] = None,
type: Optional[Union[Callable[[str], T], argparse.FileType]] = None, # noqa: A002
choices: Optional[Iterable[T]] = None,
required: bool = False,
help: Optional[str] = None, # noqa: A002
metavar: Optional[Union[str, Tuple[str, ...]]] = None,
) -> None:
"""Instantiates the Boolean Optional Action.
This creates the default provided "--<OPT>" option strings which set
the argument to `True`. It also creates alternative pair "--no-<OPT>"
option strings which set the argument to `False`.
Args:
option_strings (Sequence[str]): Option strings.
dest (str): Destination variable to save the value to.
default (Optional[Union[T, str]]): Default value of the option.
type (Optional[Union[Callable[[str], T], argparse.FileType]]): Type
to cast the option to.
choices (Optional[Iterable[T]]): Allowed values for the option.
required (bool): Whether the option is required.
help (Optional[str]): Help string for the option.
metavar (Optional[Union[str, Tuple[str, ...]]]): Meta variable name
for the option.
"""
# Initialise intermediary option strings list
_option_strings = []
# Loop through passed in option strings
for option_string in option_strings:
# Append the option string to the new list
_option_strings.append(option_string)
# Check if this option string is a "--<OPT>" option string
if option_string.startswith("--"):
# Create a "--no-<OPT>" negated option string
option_string = "--no-" + option_string[2:]
# Append the negated option string to the new list as well
_option_strings.append(option_string)
# Initialise Super Class
super().__init__(
option_strings=_option_strings,
dest=dest,
nargs=0,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar,
)
__call__(self, parser, namespace, values, option_string=None)
special
Parses the provided boolean arguments into a namespace.
This custom method parses arguments as booleans, negating the values of any arguments prepended with "--no-".
Parameters:
Name | Type | Description | Default |
---|---|---|---|
parser |
argparse.ArgumentParser |
Parent argument parser object. |
required |
namespace |
argparse.Namespace |
Parent namespace being parsed to. |
required |
values |
Optional[Union[str, Sequence[Any]]] |
Arguments to parse. |
required |
option_string |
Optional[str] |
Optional option string. |
None |
Source code in pydantic_argparse/argparse/actions.py
def __call__(
self,
parser: argparse.ArgumentParser,
namespace: argparse.Namespace,
values: Optional[Union[str, Sequence[Any]]],
option_string: Optional[str] = None,
) -> None:
"""Parses the provided boolean arguments into a namespace.
This custom method parses arguments as booleans, negating the values of
any arguments prepended with "--no-".
Args:
parser (argparse.ArgumentParser): Parent argument parser object.
namespace (argparse.Namespace): Parent namespace being parsed to.
values (Optional[Union[str, Sequence[Any]]]): Arguments to parse.
option_string (Optional[str]): Optional option string.
"""
# Check if the passed in option string matches our option strings
if option_string in self.option_strings:
# Set a boolean value on the namespace
# If the option string starts with "--no-", then negate the value
setattr(namespace, self.dest, not option_string.startswith("--no-")) # type: ignore[union-attr]
format_usage(self)
Formats the usage string.
Returns:
Type | Description |
---|---|
str |
Usage string for the option. |
Source code in pydantic_argparse/argparse/actions.py
def format_usage(self) -> str:
"""Formats the usage string.
Returns:
str: Usage string for the option.
"""
# Format and return usage string
return " | ".join(self.option_strings)