Skip to content

Commit d798e3a

Browse files
authored
Fix #350: environment variables ignored in subparsers
1 parent 445dd2e commit d798e3a

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
@@ -994,26 +994,41 @@ def __init__(self, *args, **kwargs):
994994
def _find_insertion_index(self, args):
995995
"""Find the right index to insert config/env var args into the command line.
996996
997-
Handles three cases: if '--' separator exists, insert before it so
998-
injected args don't end up in the positional-only region. If any
999-
positional arg uses REMAINDER and there are no optional args on the
1000-
command line, prepend so REMAINDER doesn't swallow them. Otherwise
1001-
insert before the first optional arg, or append if none.
997+
Inserts before the ``--`` separator if present, before a subparser
998+
command so the parent parser sees injected args first, before the
999+
first optional arg, or at position 0 when a REMAINDER positional
1000+
exists. Falls back to appending.
10021001
"""
10031002
if "--" in args:
10041003
return args.index("--")
1004+
1005+
# Find the first subcommand index, if any
1006+
subcmd_index = None
1007+
for action in self._actions:
1008+
if isinstance(action, argparse._SubParsersAction) and action.choices:
1009+
for i, arg in enumerate(args):
1010+
if arg in action.choices:
1011+
subcmd_index = i
1012+
break
1013+
break
1014+
1015+
if subcmd_index is not None:
1016+
return subcmd_index
1017+
10051018
first_opt = None
10061019
for i, arg in enumerate(args):
10071020
if arg.startswith(tuple(self.prefix_chars)):
10081021
first_opt = i
10091022
break
10101023
if first_opt is not None:
10111024
return first_opt
1025+
10121026
# No optional args on command line
10131027
if any(
10141028
a.is_positional_arg and a.nargs == argparse.REMAINDER for a in self._actions
10151029
):
10161030
return 0
1031+
10171032
return len(args)
10181033

10191034
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)