Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions configargparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -953,26 +953,41 @@ def __init__(self, *args, **kwargs):
def _find_insertion_index(self, args):
"""Find the right index to insert config/env var args into the command line.

Handles three cases: if '--' separator exists, insert before it so
injected args don't end up in the positional-only region. If any
positional arg uses REMAINDER and there are no optional args on the
command line, prepend so REMAINDER doesn't swallow them. Otherwise
insert before the first optional arg, or append if none.
Inserts before the ``--`` separator if present, before a subparser
command so the parent parser sees injected args first, before the
first optional arg, or at position 0 when a REMAINDER positional
exists. Falls back to appending.
"""
if "--" in args:
return args.index("--")

# Find the first subcommand index, if any
subcmd_index = None
for action in self._actions:
if isinstance(action, argparse._SubParsersAction) and action.choices:
for i, arg in enumerate(args):
if arg in action.choices:
subcmd_index = i
break
break

if subcmd_index is not None:
return subcmd_index

first_opt = None
for i, arg in enumerate(args):
if arg.startswith(tuple(self.prefix_chars)):
first_opt = i
break
if first_opt is not None:
return first_opt

# No optional args on command line
if any(
a.is_positional_arg and a.nargs == argparse.REMAINDER for a in self._actions
):
return 0

return len(args)

def parse_args(
Expand Down
29 changes: 29 additions & 0 deletions tests/test_configargparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,35 @@ def testSubParsers(self):
config_file1.close()
config_file2.close()

def testSubParserEnvVar(self):
"""Test that env vars work with subparsers (issue #350)."""
parser = configargparse.ArgumentParser(auto_env_var_prefix="")
parser.add_argument("--tenant", env_var="TENANT", required=True)
subparsers = parser.add_subparsers(dest="action")
appliance_parser = subparsers.add_parser("appliance")
appliance_parser.add_argument("--token", env_var="TOKEN", required=True)

# Test parent parser env var via env_vars param
ns = parser.parse_args(
args=["appliance", "--token", "test-token"],
env_vars={"TENANT": "test-tenant"},
)
self.assertEqual(ns.tenant, "test-tenant")
self.assertEqual(ns.token, "test-token")
self.assertEqual(ns.action, "appliance")

# Test both parent and subparser env vars via real os.environ
os.environ["TENANT"] = "test-tenant"
os.environ["TOKEN"] = "test-token"
try:
ns = parser.parse_args(args=["appliance"])
self.assertEqual(ns.tenant, "test-tenant")
self.assertEqual(ns.token, "test-token")
self.assertEqual(ns.action, "appliance")
finally:
del os.environ["TENANT"]
del os.environ["TOKEN"]

def testAddArgsErrors(self):
self.assertRaisesRegex(
ValueError,
Expand Down