From b59c25e0581fe342c58c9f75d3d76e215ab0fa3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 14 Apr 2022 08:48:35 +0200 Subject: [PATCH] Create ``_ExtendArgument`` --- pylint/config/argument.py | 49 +++++++++++++++++++++++++++-- pylint/config/arguments_manager.py | 12 +++++++ pylint/config/arguments_provider.py | 12 +++++++ pylint/config/callback_actions.py | 21 +++++++++++++ pylint/config/utils.py | 16 +++++++++- 5 files changed, 107 insertions(+), 3 deletions(-) diff --git a/pylint/config/argument.py b/pylint/config/argument.py index 66e22e4a29..9be36f79e0 100644 --- a/pylint/config/argument.py +++ b/pylint/config/argument.py @@ -27,8 +27,9 @@ from pylint import interfaces from pylint import utils as pylint_utils -from pylint.config.callback_actions import _CallbackAction +from pylint.config.callback_actions import _CallbackAction, _ExtendAction from pylint.config.deprecation_actions import _NewNamesAction, _OldNamesAction +from pylint.constants import PY38_PLUS if sys.version_info >= (3, 8): from typing import Literal @@ -294,7 +295,7 @@ def __init__( self, *, flags: List[str], - action: Type[argparse._StoreAction], + action: Type[argparse.Action], default: _ArgumentTypes, arg_type: str, choices: Optional[List[str]], @@ -330,6 +331,50 @@ def __init__( """ +class _ExtendArgument(_DeprecationArgument): + """Class for extend arguments to be parsed by an argparse.ArgumentsParser. + + This is based on the parameters passed to argparse.ArgumentsParser.add_message. + See: + https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument + """ + + def __init__( + self, + *, + flags: List[str], + action: Literal["extend"], + default: _ArgumentTypes, + arg_type: str, + metavar: str, + arg_help: str, + hide_help: bool, + section: Optional[str], + choices: Optional[List[str]], + dest: Optional[str], + ) -> None: + # The extend action is included in the stdlib from 3.8+ + if PY38_PLUS: + action_class = argparse._ExtendAction # type: ignore[attr-defined] + else: + action_class = _ExtendAction + + self.dest = dest + """The destination of the argument.""" + + super().__init__( + flags=flags, + action=action_class, + default=default, + arg_type=arg_type, + choices=choices, + arg_help=arg_help, + metavar=metavar, + hide_help=hide_help, + section=section, + ) + + class _StoreOldNamesArgument(_DeprecationArgument): """Store arguments while also handling old names. diff --git a/pylint/config/arguments_manager.py b/pylint/config/arguments_manager.py index 2bdd344b48..ff2f4cbe91 100644 --- a/pylint/config/arguments_manager.py +++ b/pylint/config/arguments_manager.py @@ -19,6 +19,7 @@ from pylint.config.argument import ( _Argument, _CallableArgument, + _ExtendArgument, _StoreArgument, _StoreNewNamesArgument, _StoreOldNamesArgument, @@ -187,6 +188,17 @@ def _add_parser_option( action=argument.action, help=argument.help, ) + elif isinstance(argument, _ExtendArgument): + section_group.add_argument( + *argument.flags, + action=argument.action, + default=argument.default, + type=argument.type, # type: ignore[arg-type] # incorrect typing in typeshed + help=argument.help, + metavar=argument.metavar, + choices=argument.choices, + dest=argument.dest, + ) else: raise UnrecognizedArgumentAction diff --git a/pylint/config/arguments_provider.py b/pylint/config/arguments_provider.py index 25d667eb29..8613539078 100644 --- a/pylint/config/arguments_provider.py +++ b/pylint/config/arguments_provider.py @@ -135,6 +135,18 @@ def set_option(self, optname, value, action=None, optdict=None): setattr(self.config, optname, _list + (value,)) else: _list.append(value) + elif action == "extend": + _list = getattr(self.config, optname, None) + if _list is None: + if isinstance(value, (list, tuple)): + _list = value + elif value is not None: + _list = [value] + setattr(self.config, optname, _list) + elif isinstance(_list, tuple): + setattr(self.config, optname, _list + (value,)) + else: + _list.extend(value) elif ( action == "callback" or (not isinstance(action, str)) diff --git a/pylint/config/callback_actions.py b/pylint/config/callback_actions.py index a3904c5032..22a4dcfaf3 100644 --- a/pylint/config/callback_actions.py +++ b/pylint/config/callback_actions.py @@ -52,6 +52,27 @@ def __call__( return None +class _ExtendAction(argparse._AppendAction): + """Action that adds the value to a pre-existing list. + + It is directly copied from the stdlib implementation which is only available + on 3.8+. + """ + + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Union[str, Sequence[Any], None], + option_string: Optional[str] = None, + ) -> None: + assert isinstance(values, (tuple, list)) + current = getattr(namespace, self.dest, []) + assert isinstance(current, list) + current.extend(values) + setattr(namespace, self.dest, current) + + class _AccessRunObjectAction(_CallbackAction): """Action that has access to the Run object.""" diff --git a/pylint/config/utils.py b/pylint/config/utils.py index 92beeac1ea..eb3f21e4f1 100644 --- a/pylint/config/utils.py +++ b/pylint/config/utils.py @@ -13,6 +13,7 @@ from pylint import extensions, utils from pylint.config.argument import ( _CallableArgument, + _ExtendArgument, _StoreArgument, _StoreNewNamesArgument, _StoreOldNamesArgument, @@ -33,6 +34,7 @@ def _convert_option_to_argument( _CallableArgument, _StoreOldNamesArgument, _StoreNewNamesArgument, + _ExtendArgument, ]: """Convert an optdict to an Argument class instance.""" if "level" in optdict and "hide" not in optdict: @@ -84,7 +86,19 @@ def _convert_option_to_argument( DeprecationWarning, ) default = None - + if action == "extend": + return _ExtendArgument( + flags=flags, + action=action, + default=default, + arg_type=optdict["type"], + choices=choices, + arg_help=optdict.get("help", ""), + metavar=optdict.get("metavar", ""), + hide_help=optdict.get("hide", False), + section=optdict.get("group", None), + dest=optdict.get("dest", None), + ) if "kwargs" in optdict: if "old_names" in optdict["kwargs"]: return _StoreOldNamesArgument(