import sys
from argparse import Namespace
from typing import Any, Callable, List, Optional, Sequence, Tuple, TypeVar, Union
import click
from click import Command
[docs]Function = TypeVar("Function", bound=Callable[..., Any])
[docs]FunctionOrCommand = TypeVar(
"FunctionOrCommand", bound=Union[Callable[..., Any], Command]
)
[docs]ArgumentDecorator = Callable[[FunctionOrCommand], FunctionOrCommand]
[docs]CommandDecorator = Callable[[Function], Command]
[docs]class ArgumentParser:
[docs] def parse_args(
self,
args: Optional[Sequence[str]] = None,
namespace: Optional[Namespace] = None,
) -> Namespace:
raise NotImplementedError
[docs] def parse_known_args(
self,
args: Optional[Sequence[str]] = None,
namespace: Optional[Namespace] = None,
) -> Tuple[Namespace, List[str]]:
raise NotImplementedError
[docs]class ClickArgumentParser(ArgumentParser):
_unrecognized_args_attr = "args"
def __init__(
self,
params: Sequence[ArgumentDecorator],
command: Optional[CommandDecorator] = None,
help_option_names: Optional[Sequence[str]] = None,
):
if help_option_names is None:
help_option_names = ["-h", "--help"]
if command is None:
command = click.command(
context_settings=dict(help_option_names=help_option_names)
)
self._params = params
self._command = command
self._help_option_names = help_option_names
def _callback(self, *args, **kwargs):
return args, kwargs
def _parse_args(
self,
args: Optional[Sequence[str]] = None,
namespace: Optional[Namespace] = None,
ignore_unknown_options: bool = False,
allow_extra_args: bool = False,
allow_interspersed_args: bool = True,
) -> Tuple[Namespace, List[str]]:
if args is None:
args = sys.argv[1:]
else:
args = list(args)
if namespace is None:
namespace = Namespace()
def callback(*args, **kwargs):
return self._callback(*args, **kwargs)
if ignore_unknown_options:
unrecognized_args_param = click.argument(
self._unrecognized_args_attr,
nargs=-1,
type=click.UNPROCESSED,
metavar="[ARGS]...",
)
callback = unrecognized_args_param(callback)
for decorator in reversed(self._params):
callback = decorator(callback)
command = self._command(callback)
context_settings = dict(
help_option_names=self._help_option_names,
ignore_unknown_options=ignore_unknown_options,
allow_extra_args=allow_extra_args,
allow_interspersed_args=allow_interspersed_args,
)
command.context_settings.update(context_settings)
ret = command.main(args, standalone_mode=False)
if isinstance(ret, int):
sys.exit(ret)
callback_args, callback_kwargs = ret
assert len(callback_args) == 0
for key, value in callback_kwargs.items():
setattr(namespace, key, value)
unrecognized_args = []
if hasattr(namespace, self._unrecognized_args_attr):
unrecognized_args = getattr(namespace, self._unrecognized_args_attr)
unrecognized_args = list(unrecognized_args)
delattr(namespace, self._unrecognized_args_attr)
return namespace, unrecognized_args
[docs] def parse_args(
self,
args: Optional[Sequence[str]] = None,
namespace: Optional[Namespace] = None,
) -> Namespace:
namespace, _unrecognized_args = self._parse_args(args, namespace)
return namespace
[docs] def parse_known_args(
self,
args: Optional[Sequence[str]] = None,
namespace: Optional[Namespace] = None,
) -> Tuple[Namespace, List[str]]:
namespace, unrecognized_args = self._parse_args(
args,
namespace,
ignore_unknown_options=True,
)
return namespace, unrecognized_args