diff --git a/CHANGELOG.md b/CHANGELOG.md index 59985f0a..c87aece9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ Refactoring: - `[core.plugin]` [Issue #707](https://github.com/datafolklabs/cement/issues/707) - `[core.template]` [Issue #708](https://github.com/datafolklabs/cement/issues/708) - `[ext.alarm]` [Issue #709](https://github.com/datafolklabs/cement/issues/709) + - `[ext.argparse]` [Issue #710](https://github.com/datafolklabs/cement/issues/710) - `[utils.fs]` [Issue #688](https://github.com/datafolklabs/cement/issues/688) - `[utils.misc]` [Issue #689](https://github.com/datafolklabs/cement/issues/689) - `[utils.shell]` [Issue #690](https://github.com/datafolklabs/cement/issues/690) diff --git a/cement/ext/ext_argparse.py b/cement/ext/ext_argparse.py index 8ce75271..9e5e7f0d 100644 --- a/cement/ext/ext_argparse.py +++ b/cement/ext/ext_argparse.py @@ -2,21 +2,27 @@ Cement argparse extension module. """ +from __future__ import annotations import re +from dataclasses import dataclass from argparse import ArgumentParser, RawDescriptionHelpFormatter, SUPPRESS +from typing import Any, Callable, List, Dict, Tuple, Optional, TYPE_CHECKING from ..core.arg import ArgumentHandler from ..core.controller import ControllerHandler from ..core.exc import FrameworkError from ..utils.misc import minimal_logger +if TYPE_CHECKING: + from ..core.foundation import App, ArgparseArgumentType # pragma: nocover + LOG = minimal_logger(__name__) -def _clean_label(label): +def _clean_label(label: str) -> str: return re.sub('_', '-', label) -def _clean_func(func): +def _clean_func(func: str) -> Optional[str]: if func is None: return None else: @@ -35,7 +41,7 @@ class ArgparseArgumentHandler(ArgumentParser, ArgumentHandler): on initialization. """ - class Meta: + class Meta(ArgumentHandler.Meta): """Handler meta-data.""" @@ -56,19 +62,21 @@ class ArgparseArgumentHandler(ArgumentParser, ArgumentHandler): ``unknown_args``. """ - def __init__(self, *args, **kw): + _meta: Meta # type: ignore + + def __init__(self, *args: Any, **kw: Any) -> None: super().__init__(*args, **kw) self.config = None self.unknown_args = None self.parsed_args = None - def parse(self, arg_list): + def parse(self, *args: List[str]) -> object: """ Parse a list of arguments, and return them as an object. Meaning an argument name of 'foo' will be stored as parsed_args.foo. Args: - arg_list (list): A list of arguments (generally sys.argv) to be + args (list): A list of arguments (generally sys.argv) to be parsed. Returns: @@ -77,15 +85,14 @@ class ArgparseArgumentHandler(ArgumentParser, ArgumentHandler): """ if self._meta.ignore_unknown_arguments is True: - args, unknown = self.parse_known_args(arg_list) - self.parsed_args = args - self.unknown_args = unknown + known_args, unknown_args = self.parse_known_args(*args) + self.parsed_args = known_args # type: ignore + self.unknown_args = unknown_args # type: ignore else: - args = self.parse_args(arg_list) - self.parsed_args = args + self.parsed_args = self.parse_args(*args) # type: ignore return self.parsed_args - def add_argument(self, *args, **kw): + def add_argument(self, *args: Any, **kw: Any) -> None: # type: ignore """ Add an argument to the parser. Arguments and keyword arguments are passed directly to ``ArgumentParser.add_argument()``. @@ -94,6 +101,17 @@ class ArgparseArgumentHandler(ArgumentParser, ArgumentHandler): super().add_argument(*args, **kw) +@dataclass +class CommandMeta: + label: str + func_name: str + exposed: bool + hide: bool + arguments: List[ArgparseArgumentType] + parser_options: Dict[str, Any] + controller: ArgparseController + + class expose(object): """ @@ -135,25 +153,31 @@ class expose(object): """ # pylint: disable=W0622 - def __init__(self, hide=False, arguments=[], label=None, **parser_options): + def __init__(self, + hide: bool = False, + arguments: List[ArgparseArgumentType] = [], + label: Optional[str] = None, + **parser_options: Any) -> None: self.hide = hide self.arguments = arguments self.label = label self.parser_options = parser_options - def __call__(self, func): + def __call__(self, func: Callable) -> Callable: if self.label is None: self.label = func.__name__ - metadict = {} - metadict['label'] = _clean_label(self.label) - metadict['func_name'] = func.__name__ - metadict['exposed'] = True - metadict['hide'] = self.hide - metadict['arguments'] = self.arguments - metadict['parser_options'] = self.parser_options - metadict['controller'] = None # added by the controller - func.__cement_meta__ = metadict + meta = CommandMeta( + label=_clean_label(self.label), + func_name=func.__name__, + exposed=True, + hide=self.hide, + arguments=self.arguments, + parser_options=self.parser_options, + controller=None # type: ignore + ) + + func.__cement_meta__ = meta return func @@ -206,7 +230,7 @@ class ArgparseController(ControllerHandler): """ - class Meta: + class Meta(ControllerHandler.Meta): """ Controller meta-data (can be passed as keyword arguments to the parent @@ -218,19 +242,19 @@ class ArgparseController(ControllerHandler): interface = 'controller' #: The string identifier for the controller. - label = None + label: str = None # type: ignore #: A list of aliases for the controller/sub-parser. **Only available #: in Python > 3**. - aliases = [] + aliases: List[str] = [] #: A config [section] to merge config_defaults into. Cement will #: default to controller.