From f553fd3c79579ab596230edea5899dc5189b0ac6 Mon Sep 17 00:00:00 2001 From: "John L. Villalovos" Date: Mon, 9 May 2022 06:39:36 -0700 Subject: [PATCH] fix: duplicate subparsers being added to argparse Python 3.11 added an additional check in the argparse libary which detected duplicate subparsers being added. We had duplicate subparsers being added. Make sure we don't add duplicate subparsers. Closes: #2015 --- gitlab/v4/cli.py | 25 ++++++++++++++++++------- tests/unit/v4/__init__.py | 0 2 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 tests/unit/v4/__init__.py diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py index 245897e71..98430b965 100644 --- a/gitlab/v4/cli.py +++ b/gitlab/v4/cli.py @@ -200,11 +200,15 @@ def _populate_sub_parser_by_class( mgr_cls_name = f"{cls.__name__}Manager" mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name) + action_parsers: Dict[str, argparse.ArgumentParser] = {} for action_name in ["list", "get", "create", "update", "delete"]: if not hasattr(mgr_cls, action_name): continue - sub_parser_action = sub_parser.add_parser(action_name) + sub_parser_action = sub_parser.add_parser( + action_name, conflict_handler="resolve" + ) + action_parsers[action_name] = sub_parser_action sub_parser_action.add_argument("--sudo", required=False) if mgr_cls._from_parent_attrs: for x in mgr_cls._from_parent_attrs: @@ -268,7 +272,11 @@ def _populate_sub_parser_by_class( if cls.__name__ in cli.custom_actions: name = cls.__name__ for action_name in cli.custom_actions[name]: - sub_parser_action = sub_parser.add_parser(action_name) + # NOTE(jlvillal): If we put a function for the `default` value of + # the `get` it will always get called, which will break things. + sub_parser_action = action_parsers.get(action_name) + if sub_parser_action is None: + sub_parser_action = sub_parser.add_parser(action_name) # Get the attributes for URL/path construction if mgr_cls._from_parent_attrs: for x in mgr_cls._from_parent_attrs: @@ -298,7 +306,11 @@ def _populate_sub_parser_by_class( if mgr_cls.__name__ in cli.custom_actions: name = mgr_cls.__name__ for action_name in cli.custom_actions[name]: - sub_parser_action = sub_parser.add_parser(action_name) + # NOTE(jlvillal): If we put a function for the `default` value of + # the `get` it will always get called, which will break things. + sub_parser_action = action_parsers.get(action_name) + if sub_parser_action is None: + sub_parser_action = sub_parser.add_parser(action_name) if mgr_cls._from_parent_attrs: for x in mgr_cls._from_parent_attrs: sub_parser_action.add_argument( @@ -326,16 +338,15 @@ def extend_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: subparsers.required = True # populate argparse for all Gitlab Object - classes = [] + classes = set() for cls in gitlab.v4.objects.__dict__.values(): if not isinstance(cls, type): continue if issubclass(cls, gitlab.base.RESTManager): if cls._obj_cls is not None: - classes.append(cls._obj_cls) - classes.sort(key=operator.attrgetter("__name__")) + classes.add(cls._obj_cls) - for cls in classes: + for cls in sorted(classes, key=operator.attrgetter("__name__")): arg_name = cli.cls_to_what(cls) object_group = subparsers.add_parser(arg_name) diff --git a/tests/unit/v4/__init__.py b/tests/unit/v4/__init__.py new file mode 100644 index 000000000..e69de29bb