From 8dc60e341de86b00eec0ba703fe544ce08178dda Mon Sep 17 00:00:00 2001 From: bw2 Date: Tue, 30 Dec 2025 21:01:06 -0500 Subject: [PATCH 1/3] Fix -- separator handling and document aliases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit addresses two issues: 1. Fix -- separator handling with config files and env vars (Issue #298) - Modified parse_known_args() to insert config file and env var args before the first optional argument (starts with -) instead of prepending/appending them - This preserves the -- separator position and ensures positional args after -- are not mixed with config/env var args - Added comprehensive test case testDoubleDashSeparator() to verify the fix works with config files and environment variables 2. Document all available aliases (Issue #297) - Enhanced the Aliases section in README.rst to document all available alias names - Organized aliases into clear categories: Class, Method, Function, and HelpFormatter aliases - Each alias now shows what it maps to for clarity Technical details: - The original code used the 'nargs' flag to decide whether to append or prepend args, which caused config args to be placed after -- when action="append" was used - The new implementation always inserts before the first optional arg, which correctly handles -- separator in all cases 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- README.rst | 37 +++++++++++++++++++--------- configargparse.py | 28 ++++++++++++++++----- tests/test_configargparse.py | 47 ++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 18 deletions(-) diff --git a/README.rst b/README.rst index 60b9a9d5..cdd81cac 100644 --- a/README.rst +++ b/README.rst @@ -433,22 +433,35 @@ Aliases ~~~~~~~ The configargparse.ArgumentParser API inherits its class and method -names from argparse and also provides the following shorter names for +names from argparse and also provides the following shorter alias names for convenience: -- p = configargparse.get_arg_parser() # get global singleton instance -- p = configargparse.get_parser() -- p = configargparse.ArgParser() # create a new instance -- p = configargparse.Parser() -- p.add_arg(..) -- p.add(..) -- options = p.parse(..) +**Class Aliases:** -HelpFormatters: +- ``ArgParser`` = ``ArgumentParser`` (create a new instance) +- ``Parser`` = ``ArgumentParser`` -- RawFormatter = RawDescriptionHelpFormatter -- DefaultsFormatter = ArgumentDefaultsHelpFormatter -- DefaultsRawFormatter = ArgumentDefaultsRawHelpFormatter +**Method Aliases:** + +- ``p.add(..)`` = ``p.add_argument(..)`` +- ``p.add_arg(..)`` = ``p.add_argument(..)`` +- ``p.parse(..)`` = ``p.parse_args(..)`` +- ``p.parse_known(..)`` = ``p.parse_known_args(..)`` + +**Function Aliases (for global singleton):** + +- ``configargparse.get_arg_parser()`` = ``configargparse.get_argument_parser()`` +- ``configargparse.get_parser()`` = ``configargparse.get_argument_parser()`` +- ``configargparse.getArgumentParser()`` = ``configargparse.get_argument_parser()`` +- ``configargparse.getArgParser()`` = ``configargparse.get_argument_parser()`` +- ``configargparse.getParser()`` = ``configargparse.get_argument_parser()`` +- ``configargparse.initArgumentParser()`` = ``configargparse.init_argument_parser()`` + +**HelpFormatter Aliases:** + +- ``RawFormatter`` = ``RawDescriptionHelpFormatter`` +- ``DefaultsFormatter`` = ``ArgumentDefaultsHelpFormatter`` +- ``DefaultsRawFormatter`` = ``ArgumentDefaultsRawHelpFormatter`` API Documentation ~~~~~~~~~~~~~~~~~ diff --git a/configargparse.py b/configargparse.py index db92aa6b..7219e49f 100644 --- a/configargparse.py +++ b/configargparse.py @@ -968,10 +968,18 @@ def parse_known_args( value = [elem.strip() for elem in value[1:-1].split(",")] env_var_args += self.convert_item_to_command_line_arg(action, key, value) - if nargs: - args = args + env_var_args + # Insert env var args before the first optional arg (starts with -) + # to preserve -- separator and positional args that come after it. + # If nargs is True, we still need to respect the -- separator. + insertion_index = 0 + for i, arg in enumerate(args): + if arg.startswith(tuple(self.prefix_chars)): + insertion_index = i + break else: - args = env_var_args + args + # No optional args found, append to end + insertion_index = len(args) + args = args[:insertion_index] + env_var_args + args[insertion_index:] if env_var_args: self._source_to_settings[_ENV_VAR_SOURCE_KEY] = OrderedDict( @@ -1054,10 +1062,18 @@ def parse_known_args( ): nargs = True - if nargs: - args = args + config_args + # Insert config args before the first optional arg (starts with -) + # to preserve -- separator and positional args that come after it. + # If nargs is True, we still need to respect the -- separator. + insertion_index = 0 + for i, arg in enumerate(args): + if arg.startswith(tuple(self.prefix_chars)): + insertion_index = i + break else: - args = config_args + args + # No optional args found, append to end + insertion_index = len(args) + args = args[:insertion_index] + config_args + args[insertion_index:] # save default settings for use by print_values() default_settings = OrderedDict() diff --git a/tests/test_configargparse.py b/tests/test_configargparse.py index d46fde66..889f62f1 100644 --- a/tests/test_configargparse.py +++ b/tests/test_configargparse.py @@ -1344,6 +1344,53 @@ def error_func(path): args="-g file.txt", ) + def testDoubleDashSeparator(self): + """Test that -- separator correctly separates optional from positional args + when config file or env vars are used. Regression test for issue #298.""" + # Create a config file with list option + config_file = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".conf") + config_file.write("list = [1, 2, 3]\n") + config_file.flush() + + try: + # Test with config file + self.initParser(args_for_setting_config_path=["-c", "--config"]) + self.parser.add_argument("--list", action="append") + self.parser.add_argument("positional_arg", nargs="*") + + # Parse with -- separator + ns = self.parse(args=f"-c {config_file.name} -- foo bar") + + # Positional args should only contain 'foo' and 'bar' + # NOT the config file values '1', '2', '3' + self.assertEqual(ns.positional_arg, ["foo", "bar"]) + self.assertEqual(ns.list, ["1", "2", "3"]) + + # Test without -- separator (config file args should be inserted correctly) + ns = self.parse(args=f"-c {config_file.name} foo bar") + self.assertEqual(ns.positional_arg, ["foo", "bar"]) + self.assertEqual(ns.list, ["1", "2", "3"]) + + # Test with env var and -- separator + self.initParser() + self.parser.add_argument("--list", action="append", env_var="MY_LIST") + self.parser.add_argument("positional_arg", nargs="*") + + old_env = os.environ.get("MY_LIST") + try: + os.environ["MY_LIST"] = "[1,2,3]" + ns = self.parse(args="-- foo bar") + self.assertEqual(ns.positional_arg, ["foo", "bar"]) + self.assertEqual(ns.list, ["1", "2", "3"]) + finally: + if old_env is not None: + os.environ["MY_LIST"] = old_env + elif "MY_LIST" in os.environ: + del os.environ["MY_LIST"] + + finally: + os.unlink(config_file.name) + class TestConfigFileParsers(TestCase): """Test ConfigFileParser subclasses in isolation""" From fdb7a1f050582aafcbe1bbe8fc740585e86dd79a Mon Sep 17 00:00:00 2001 From: bw2 Date: Tue, 30 Dec 2025 21:04:08 -0500 Subject: [PATCH 2/3] Fix Black formatting --- tests/test_configargparse.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_configargparse.py b/tests/test_configargparse.py index 889f62f1..f263d18d 100644 --- a/tests/test_configargparse.py +++ b/tests/test_configargparse.py @@ -1348,7 +1348,9 @@ def testDoubleDashSeparator(self): """Test that -- separator correctly separates optional from positional args when config file or env vars are used. Regression test for issue #298.""" # Create a config file with list option - config_file = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".conf") + config_file = tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".conf" + ) config_file.write("list = [1, 2, 3]\n") config_file.flush() From 4b87a4e3d456df69212bf4a708943ea743e1cd69 Mon Sep 17 00:00:00 2001 From: bw2 Date: Tue, 30 Dec 2025 21:04:57 -0500 Subject: [PATCH 3/3] Fix Windows file locking issue in testDoubleDashSeparator --- tests/test_configargparse.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_configargparse.py b/tests/test_configargparse.py index f263d18d..f7479af5 100644 --- a/tests/test_configargparse.py +++ b/tests/test_configargparse.py @@ -1353,6 +1353,7 @@ def testDoubleDashSeparator(self): ) config_file.write("list = [1, 2, 3]\n") config_file.flush() + config_file.close() # Close file to avoid Windows file locking issues try: # Test with config file