Skip to content

Commit 29bfbf6

Browse files
committed
Fix environment variables ignored in subparsers (#350)
1 parent 64e96f5 commit 29bfbf6

2 files changed

Lines changed: 49 additions & 5 deletions

File tree

configargparse.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -953,26 +953,41 @@ def __init__(self, *args, **kwargs):
953953
def _find_insertion_index(self, args):
954954
"""Find the right index to insert config/env var args into the command line.
955955
956-
Handles three cases: if '--' separator exists, insert before it so
957-
injected args don't end up in the positional-only region. If any
958-
positional arg uses REMAINDER and there are no optional args on the
959-
command line, prepend so REMAINDER doesn't swallow them. Otherwise
960-
insert before the first optional arg, or append if none.
956+
Inserts before the ``--`` separator if present, before a subparser
957+
command so the parent parser sees injected args first, before the
958+
first optional arg, or at position 0 when a REMAINDER positional
959+
exists. Falls back to appending.
961960
"""
962961
if "--" in args:
963962
return args.index("--")
963+
964+
# Find the first subcommand index, if any
965+
subcmd_index = None
966+
for action in self._actions:
967+
if isinstance(action, argparse._SubParsersAction) and action.choices:
968+
for i, arg in enumerate(args):
969+
if arg in action.choices:
970+
subcmd_index = i
971+
break
972+
break
973+
974+
if subcmd_index is not None:
975+
return subcmd_index
976+
964977
first_opt = None
965978
for i, arg in enumerate(args):
966979
if arg.startswith(tuple(self.prefix_chars)):
967980
first_opt = i
968981
break
969982
if first_opt is not None:
970983
return first_opt
984+
971985
# No optional args on command line
972986
if any(
973987
a.is_positional_arg and a.nargs == argparse.REMAINDER for a in self._actions
974988
):
975989
return 0
990+
976991
return len(args)
977992

978993
def parse_args(

tests/test_configargparse.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,35 @@ def testSubParsers(self):
538538
config_file1.close()
539539
config_file2.close()
540540

541+
def testSubParserEnvVar(self):
542+
"""Test that env vars work with subparsers (issue #350)."""
543+
parser = configargparse.ArgumentParser(auto_env_var_prefix="")
544+
parser.add_argument("--tenant", env_var="TENANT", required=True)
545+
subparsers = parser.add_subparsers(dest="action")
546+
appliance_parser = subparsers.add_parser("appliance")
547+
appliance_parser.add_argument("--token", env_var="TOKEN", required=True)
548+
549+
# Test parent parser env var via env_vars param
550+
ns = parser.parse_args(
551+
args=["appliance", "--token", "test-token"],
552+
env_vars={"TENANT": "test-tenant"},
553+
)
554+
self.assertEqual(ns.tenant, "test-tenant")
555+
self.assertEqual(ns.token, "test-token")
556+
self.assertEqual(ns.action, "appliance")
557+
558+
# Test both parent and subparser env vars via real os.environ
559+
os.environ["TENANT"] = "test-tenant"
560+
os.environ["TOKEN"] = "test-token"
561+
try:
562+
ns = parser.parse_args(args=["appliance"])
563+
self.assertEqual(ns.tenant, "test-tenant")
564+
self.assertEqual(ns.token, "test-token")
565+
self.assertEqual(ns.action, "appliance")
566+
finally:
567+
del os.environ["TENANT"]
568+
del os.environ["TOKEN"]
569+
541570
def testAddArgsErrors(self):
542571
self.assertRaisesRegex(
543572
ValueError,

0 commit comments

Comments
 (0)