From 37982a95e60a89f041bf50162d5f8cae2a577f43 Mon Sep 17 00:00:00 2001 From: lo-0l <141224667+lo-0l@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:27:21 +0200 Subject: [PATCH] fix: accept lists for custom actions with nargs The list-value check in convert_item_to_command_line_arg required the action to be a _StoreAction or _AppendAction instance, so an argument declared with a custom argparse.Action subclass and nargs="+" was rejected when its value came from a config file, even though the same argument parsed fine from the command line. The class check is not what the error message advertises, and it is redundant: actions that cannot consume several values (store_const, count, help, ...) already have nargs == 0 or None, so they still fall through to the error. Keep the nargs test only. Fixes #354 --- configargparse.py | 20 +++++++++----------- tests/test_configargparse.py | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/configargparse.py b/configargparse.py index 01cb589..6d843b4 100644 --- a/configargparse.py +++ b/configargparse.py @@ -1472,17 +1472,15 @@ def convert_item_to_command_line_arg(self, action, key, value): "'false', 'yes', 'no', 'on', 'off', '1' or '0'" % (key, value) ) elif isinstance(value, list): - accepts_list_and_has_nargs = ( - action is not None - and action.nargs is not None - and ( - isinstance(action, argparse._StoreAction) - or isinstance(action, argparse._AppendAction) - ) - and ( - action.nargs in ("+", "*") - or (isinstance(action.nargs, int) and action.nargs > 1) - ) + # Only nargs is relevant here: any action, including a custom + # argparse.Action subclass, consumes several values when nargs + # allows it. Testing the action class as well would be redundant, + # since actions that cannot consume a list have an nargs that + # fails the test below anyway: 0 or None for store_const, count + # and friends, 'A...' for subparsers, '...' for REMAINDER. + accepts_list_and_has_nargs = action is not None and ( + action.nargs in ("+", "*") + or (isinstance(action.nargs, int) and action.nargs > 1) ) if action is None or isinstance(action, argparse._AppendAction): diff --git a/tests/test_configargparse.py b/tests/test_configargparse.py index b6ec6be..291c392 100644 --- a/tests/test_configargparse.py +++ b/tests/test_configargparse.py @@ -430,6 +430,32 @@ def testPositionalAndConfigVarLists(self): self.assertEqual(ns.arg, ["Shell", "someword", "anotherword"]) self.assertEqual(ns.a, "positional_value") + def testCustomActionWithNargsAndConfigVarList(self): + # https://github.com/bw2/ConfigArgParse/issues/354 + class CustomAction(configargparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values) + + self.initParser() + self.add_arg("-x", "--arg", nargs="+", action=CustomAction) + + ns = self.parse("", config_file_contents="""arg = [foo, bar]""") + self.assertEqual(ns.arg, ["foo", "bar"]) + + def testCustomActionWithoutNargsAndConfigVarList(self): + class CustomAction(configargparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values) + + self.initParser() + self.add_arg("-x", "--arg", action=CustomAction) + + self.assertParseArgsRaises( + "arg can't be set to a list", + args="", + config_file_contents="""arg = [foo, bar]""", + ) + def testMutuallyExclusiveArgs(self): config_file = tempfile.NamedTemporaryFile(mode="w", delete=False)