From d43a918bb5c98a80e0a791d1546fb477516e2d39 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 16 Aug 2026 12:20:53 +0000 Subject: [PATCH 1/3] Initial plan From f03cf8fc298410b4bab1e7a74e8f1374970ceee8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 16 Aug 2026 12:23:24 +0000 Subject: [PATCH 2/3] fix: skip nargs="*" override when action is append/extend on list fields Co-authored-by: lebrice <13387299+lebrice@users.noreply.github.com> --- ...39_20260816_122256_uncommited-changes.json | 352 ++++++++++++++++++ simple_parsing/wrappers/field_wrapper.py | 8 +- test/test_custom_args.py | 17 + 3 files changed, 376 insertions(+), 1 deletion(-) create mode 100644 .benchmarks/Linux-CPython-3.12-64bit/0001_d43a918bb5c98a80e0a791d1546fb477516e2d39_20260816_122256_uncommited-changes.json diff --git a/.benchmarks/Linux-CPython-3.12-64bit/0001_d43a918bb5c98a80e0a791d1546fb477516e2d39_20260816_122256_uncommited-changes.json b/.benchmarks/Linux-CPython-3.12-64bit/0001_d43a918bb5c98a80e0a791d1546fb477516e2d39_20260816_122256_uncommited-changes.json new file mode 100644 index 00000000..b602c214 --- /dev/null +++ b/.benchmarks/Linux-CPython-3.12-64bit/0001_d43a918bb5c98a80e0a791d1546fb477516e2d39_20260816_122256_uncommited-changes.json @@ -0,0 +1,352 @@ +{ + "machine_info": { + "node": "runnervmzvulz", + "processor": "x86_64", + "machine": "x86_64", + "python_compiler": "GCC 13.3.0", + "python_implementation": "CPython", + "python_implementation_version": "3.12.3", + "python_version": "3.12.3", + "python_build": [ + "main", + "Jun 19 2026 12:46:00" + ], + "release": "6.17.0-1022-azure", + "system": "Linux", + "cpu": { + "python_version": "3.12.3.final.0 (64 bit)", + "cpuinfo_version": [ + 9, + 0, + 0 + ], + "cpuinfo_version_string": "9.0.0", + "arch": "X86_64", + "bits": 64, + "count": 4, + "arch_string_raw": "x86_64", + "vendor_id_raw": "AuthenticAMD", + "brand_raw": "AMD EPYC 7763 64-Core Processor", + "hz_advertised_friendly": "3.1224 GHz", + "hz_actual_friendly": "3.1224 GHz", + "hz_advertised": [ + 3122396000, + 0 + ], + "hz_actual": [ + 3122396000, + 0 + ], + "stepping": 1, + "model": 1, + "family": 25, + "flags": [ + "3dnowext", + "3dnowprefetch", + "abm", + "adx", + "aes", + "aperfmperf", + "apic", + "arat", + "avx", + "avx2", + "bmi1", + "bmi2", + "clflush", + "clflushopt", + "clwb", + "clzero", + "cmov", + "cmp_legacy", + "constant_tsc", + "cpuid", + "cr8_legacy", + "cx16", + "cx8", + "de", + "decodeassists", + "erms", + "extd_apicid", + "f16c", + "flushbyasid", + "fma", + "fpu", + "fsgsbase", + "fsrm", + "fxsr", + "fxsr_opt", + "ht", + "hypervisor", + "invpcid", + "lahf_lm", + "lm", + "mca", + "mce", + "misalignsse", + "mmx", + "mmxext", + "movbe", + "msr", + "mtrr", + "nonstop_tsc", + "nopl", + "npt", + "nrip_save", + "nx", + "osvw", + "osxsave", + "pae", + "pat", + "pausefilter", + "pcid", + "pclmulqdq", + "pdpe1gb", + "pfthreshold", + "pge", + "pni", + "popcnt", + "pse", + "pse36", + "rdpid", + "rdpru", + "rdrand", + "rdrnd", + "rdseed", + "rdtscp", + "rep_good", + "sep", + "sha", + "sha_ni", + "smap", + "smep", + "sse", + "sse2", + "sse4_1", + "sse4_2", + "sse4a", + "ssse3", + "svm", + "syscall", + "topoext", + "tsc", + "tsc_known_freq", + "tsc_reliable", + "tsc_scale", + "umip", + "user_shstk", + "v_vmsave_vmload", + "vaes", + "vmcb_clean", + "vme", + "vmmcall", + "vpclmulqdq", + "xgetbv1", + "xsave", + "xsavec", + "xsaveerptr", + "xsaveopt", + "xsaves" + ], + "l3_cache_size": 524288, + "l2_cache_size": 1048576, + "l1_data_cache_size": 65536, + "l1_instruction_cache_size": 65536, + "l2_cache_line_size": 512, + "l2_cache_associativity": 6 + } + }, + "commit_info": { + "id": "d43a918bb5c98a80e0a791d1546fb477516e2d39", + "time": "2026-08-16T12:20:53Z", + "author_time": "2026-08-16T12:20:53Z", + "dirty": true, + "project": "SimpleParsing", + "branch": "copilot/fix-append-nested-list-issue" + }, + "benchmarks": [ + { + "group": "import", + "name": "test_import_performance", + "fullname": "test/test_performance.py::test_import_performance", + "params": null, + "param": null, + "extra_info": {}, + "options": { + "disable_gc": false, + "timer": "perf_counter", + "min_rounds": 5, + "max_time": 1.0, + "min_time": 5e-06, + "warmup": false + }, + "stats": { + "min": 0.00018908399999872927, + "max": 0.00032208200002514786, + "mean": 0.00019978930595977964, + "stddev": 1.2985702569181796e-05, + "rounds": 1997, + "median": 0.00019386200006010768, + "iqr": 8.130749989732067e-06, + "q1": 0.00019240674998854956, + "q3": 0.00020053749997828163, + "iqr_outliers": 383, + "stddev_outliers": 383, + "outliers": "383;383", + "ld15iqr": 0.00018908399999872927, + "hd15iqr": 0.00021283800003857323, + "ops": 5005.2729058547, + "total": 0.39897924400167994, + "iterations": 1 + } + }, + { + "group": "parse", + "name": "test_parse_performance", + "fullname": "test/test_performance.py::test_parse_performance", + "params": null, + "param": null, + "extra_info": {}, + "options": { + "disable_gc": false, + "timer": "perf_counter", + "min_rounds": 5, + "max_time": 1.0, + "min_time": 5e-06, + "warmup": false + }, + "stats": { + "min": 0.008394857999974192, + "max": 0.04338674899997841, + "mean": 0.00914522897087232, + "stddev": 0.0034271965921238885, + "rounds": 103, + "median": 0.008734351999919454, + "iqr": 0.00020016949994783317, + "q1": 0.008646325000029265, + "q3": 0.008846494499977098, + "iqr_outliers": 9, + "stddev_outliers": 1, + "outliers": "1;9", + "ld15iqr": 0.008394857999974192, + "hd15iqr": 0.009172009999929287, + "ops": 109.34663343968901, + "total": 0.941958583999849, + "iterations": 1 + } + }, + { + "group": "serialization", + "name": "test_serialization_performance[.yaml]", + "fullname": "test/test_performance.py::test_serialization_performance[.yaml]", + "params": { + "filetype": ".yaml" + }, + "param": ".yaml", + "extra_info": {}, + "options": { + "disable_gc": false, + "timer": "perf_counter", + "min_rounds": 5, + "max_time": 1.0, + "min_time": 5e-06, + "warmup": false + }, + "stats": { + "min": 0.01021128200000021, + "max": 0.012378642000044238, + "mean": 0.01045516444444527, + "stddev": 0.0002405500964929078, + "rounds": 90, + "median": 0.010413895999988654, + "iqr": 0.0001490390000071784, + "q1": 0.01036055099996247, + "q3": 0.010509589999969648, + "iqr_outliers": 2, + "stddev_outliers": 4, + "outliers": "4;2", + "ld15iqr": 0.01021128200000021, + "hd15iqr": 0.011007951000010507, + "ops": 95.64651090029392, + "total": 0.9409648000000743, + "iterations": 1 + } + }, + { + "group": "serialization", + "name": "test_serialization_performance[.json]", + "fullname": "test/test_performance.py::test_serialization_performance[.json]", + "params": { + "filetype": ".json" + }, + "param": ".json", + "extra_info": {}, + "options": { + "disable_gc": false, + "timer": "perf_counter", + "min_rounds": 5, + "max_time": 1.0, + "min_time": 5e-06, + "warmup": false + }, + "stats": { + "min": 0.001766301000088788, + "max": 0.002143917000012152, + "mean": 0.0018544515762684642, + "stddev": 5.9114198784749366e-05, + "rounds": 531, + "median": 0.0018398180000076536, + "iqr": 3.95930000536282e-05, + "q1": 0.001821006249997481, + "q3": 0.0018605992500511093, + "iqr_outliers": 56, + "stddev_outliers": 79, + "outliers": "79;56", + "ld15iqr": 0.001766301000088788, + "hd15iqr": 0.0019278620000022784, + "ops": 539.2429830991891, + "total": 0.9847137869985545, + "iterations": 1 + } + }, + { + "group": "serialization", + "name": "test_serialization_performance[.pkl]", + "fullname": "test/test_performance.py::test_serialization_performance[.pkl]", + "params": { + "filetype": ".pkl" + }, + "param": ".pkl", + "extra_info": {}, + "options": { + "disable_gc": false, + "timer": "perf_counter", + "min_rounds": 5, + "max_time": 1.0, + "min_time": 5e-06, + "warmup": false + }, + "stats": { + "min": 0.0016506150000168418, + "max": 0.0020245429999476983, + "mean": 0.0017392260739412645, + "stddev": 6.25401831206712e-05, + "rounds": 568, + "median": 0.0017266814999743474, + "iqr": 4.657150003595234e-05, + "q1": 0.0017040199999769357, + "q3": 0.001750591500012888, + "iqr_outliers": 39, + "stddev_outliers": 55, + "outliers": "55;39", + "ld15iqr": 0.0016506150000168418, + "hd15iqr": 0.00182627300000604, + "ops": 574.9683810419755, + "total": 0.9878804099986382, + "iterations": 1 + } + } + ], + "datetime": "2026-08-16T12:23:17.113339+00:00", + "version": "5.2.3" +} \ No newline at end of file diff --git a/simple_parsing/wrappers/field_wrapper.py b/simple_parsing/wrappers/field_wrapper.py index afae7558..8ec5bc47 100644 --- a/simple_parsing/wrappers/field_wrapper.py +++ b/simple_parsing/wrappers/field_wrapper.py @@ -354,7 +354,13 @@ def enum_to_str(e): elif self.is_list: logger.debug(f"Adding a List attribute '{self.name}': {self.type}") - _arg_options["nargs"] = "*" + # When action="append" or action="extend", each flag occurrence is a + # single element; argparse handles accumulation itself, so nargs="*" + # would wrap each occurrence in an extra list. Skip the nargs + # override in that case and let the user-supplied (or default None) + # nargs take effect. + if self.action not in ("append", "extend"): + _arg_options["nargs"] = "*" if self.is_reused: # TODO: Only the 'single-level' lists (not reused) use the new diff --git a/test/test_custom_args.py b/test/test_custom_args.py index c6c45618..7c396cf7 100644 --- a/test/test_custom_args.py +++ b/test/test_custom_args.py @@ -239,6 +239,23 @@ class Foo(TestSetup): Foo.setup("--task_sequence train bob test") +def test_append_action_on_list_field(): + """Regression test for https://github.com/lebrice/SimpleParsing/issues/366. + + When action="append" is used on a list-typed field, each occurrence of the + flag should add a single element to the list rather than wrapping it in an + extra one-item list. + """ + import simple_parsing + + @dataclass + class Args: + header: list[str] = field(default_factory=list, action="append") + + result = simple_parsing.parse(Args, args=["--header", "Auth", "--header", "Accept"]) + assert result.header == ["Auth", "Accept"], result.header + + if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--foo", action="store_const", const=42) From 4eede1e9b4f81204e19502fe90a5e6beabc90f03 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 16 Aug 2026 12:25:16 +0000 Subject: [PATCH 3/3] test: add regression tests for append/extend action on list fields Co-authored-by: lebrice <13387299+lebrice@users.noreply.github.com> --- ...e8_20260816_122443_uncommited-changes.json | 352 ++++++++++++++++++ simple_parsing/wrappers/field_wrapper.py | 12 +- test/test_custom_args.py | 14 + 3 files changed, 372 insertions(+), 6 deletions(-) create mode 100644 .benchmarks/Linux-CPython-3.12-64bit/0002_f03cf8fc298410b4bab1e7a74e8f1374970ceee8_20260816_122443_uncommited-changes.json diff --git a/.benchmarks/Linux-CPython-3.12-64bit/0002_f03cf8fc298410b4bab1e7a74e8f1374970ceee8_20260816_122443_uncommited-changes.json b/.benchmarks/Linux-CPython-3.12-64bit/0002_f03cf8fc298410b4bab1e7a74e8f1374970ceee8_20260816_122443_uncommited-changes.json new file mode 100644 index 00000000..35239e56 --- /dev/null +++ b/.benchmarks/Linux-CPython-3.12-64bit/0002_f03cf8fc298410b4bab1e7a74e8f1374970ceee8_20260816_122443_uncommited-changes.json @@ -0,0 +1,352 @@ +{ + "machine_info": { + "node": "runnervmzvulz", + "processor": "x86_64", + "machine": "x86_64", + "python_compiler": "GCC 13.3.0", + "python_implementation": "CPython", + "python_implementation_version": "3.12.3", + "python_version": "3.12.3", + "python_build": [ + "main", + "Jun 19 2026 12:46:00" + ], + "release": "6.17.0-1022-azure", + "system": "Linux", + "cpu": { + "python_version": "3.12.3.final.0 (64 bit)", + "cpuinfo_version": [ + 9, + 0, + 0 + ], + "cpuinfo_version_string": "9.0.0", + "arch": "X86_64", + "bits": 64, + "count": 4, + "arch_string_raw": "x86_64", + "vendor_id_raw": "AuthenticAMD", + "brand_raw": "AMD EPYC 7763 64-Core Processor", + "hz_advertised_friendly": "3.2454 GHz", + "hz_actual_friendly": "3.2454 GHz", + "hz_advertised": [ + 3245404000, + 0 + ], + "hz_actual": [ + 3245404000, + 0 + ], + "stepping": 1, + "model": 1, + "family": 25, + "flags": [ + "3dnowext", + "3dnowprefetch", + "abm", + "adx", + "aes", + "aperfmperf", + "apic", + "arat", + "avx", + "avx2", + "bmi1", + "bmi2", + "clflush", + "clflushopt", + "clwb", + "clzero", + "cmov", + "cmp_legacy", + "constant_tsc", + "cpuid", + "cr8_legacy", + "cx16", + "cx8", + "de", + "decodeassists", + "erms", + "extd_apicid", + "f16c", + "flushbyasid", + "fma", + "fpu", + "fsgsbase", + "fsrm", + "fxsr", + "fxsr_opt", + "ht", + "hypervisor", + "invpcid", + "lahf_lm", + "lm", + "mca", + "mce", + "misalignsse", + "mmx", + "mmxext", + "movbe", + "msr", + "mtrr", + "nonstop_tsc", + "nopl", + "npt", + "nrip_save", + "nx", + "osvw", + "osxsave", + "pae", + "pat", + "pausefilter", + "pcid", + "pclmulqdq", + "pdpe1gb", + "pfthreshold", + "pge", + "pni", + "popcnt", + "pse", + "pse36", + "rdpid", + "rdpru", + "rdrand", + "rdrnd", + "rdseed", + "rdtscp", + "rep_good", + "sep", + "sha", + "sha_ni", + "smap", + "smep", + "sse", + "sse2", + "sse4_1", + "sse4_2", + "sse4a", + "ssse3", + "svm", + "syscall", + "topoext", + "tsc", + "tsc_known_freq", + "tsc_reliable", + "tsc_scale", + "umip", + "user_shstk", + "v_vmsave_vmload", + "vaes", + "vmcb_clean", + "vme", + "vmmcall", + "vpclmulqdq", + "xgetbv1", + "xsave", + "xsavec", + "xsaveerptr", + "xsaveopt", + "xsaves" + ], + "l3_cache_size": 524288, + "l2_cache_size": 1048576, + "l1_data_cache_size": 65536, + "l1_instruction_cache_size": 65536, + "l2_cache_line_size": 512, + "l2_cache_associativity": 6 + } + }, + "commit_info": { + "id": "f03cf8fc298410b4bab1e7a74e8f1374970ceee8", + "time": "2026-08-16T12:23:24Z", + "author_time": "2026-08-16T12:23:24Z", + "dirty": true, + "project": "SimpleParsing", + "branch": "copilot/fix-append-nested-list-issue" + }, + "benchmarks": [ + { + "group": "import", + "name": "test_import_performance", + "fullname": "test/test_performance.py::test_import_performance", + "params": null, + "param": null, + "extra_info": {}, + "options": { + "disable_gc": false, + "timer": "perf_counter", + "min_rounds": 5, + "max_time": 1.0, + "min_time": 5e-06, + "warmup": false + }, + "stats": { + "min": 0.0001889530000198647, + "max": 0.000305189999949107, + "mean": 0.00020029103625947381, + "stddev": 1.2245313439804082e-05, + "rounds": 2096, + "median": 0.00019427300003371784, + "iqr": 8.656999966660806e-06, + "q1": 0.00019279000002825342, + "q3": 0.00020144699999491422, + "iqr_outliers": 425, + "stddev_outliers": 442, + "outliers": "442;425", + "ld15iqr": 0.0001889530000198647, + "hd15iqr": 0.0002144510000334776, + "ops": 4992.7346658914685, + "total": 0.4198100119998571, + "iterations": 1 + } + }, + { + "group": "parse", + "name": "test_parse_performance", + "fullname": "test/test_performance.py::test_parse_performance", + "params": null, + "param": null, + "extra_info": {}, + "options": { + "disable_gc": false, + "timer": "perf_counter", + "min_rounds": 5, + "max_time": 1.0, + "min_time": 5e-06, + "warmup": false + }, + "stats": { + "min": 0.008509984000056647, + "max": 0.009540197999967859, + "mean": 0.008764884134606632, + "stddev": 0.00018386069683185054, + "rounds": 104, + "median": 0.008731471999965379, + "iqr": 0.0001719359999583503, + "q1": 0.0086608505000072, + "q3": 0.00883278649996555, + "iqr_outliers": 5, + "stddev_outliers": 21, + "outliers": "21;5", + "ld15iqr": 0.008509984000056647, + "hd15iqr": 0.009226653000041551, + "ops": 114.09163939220515, + "total": 0.9115479499990897, + "iterations": 1 + } + }, + { + "group": "serialization", + "name": "test_serialization_performance[.yaml]", + "fullname": "test/test_performance.py::test_serialization_performance[.yaml]", + "params": { + "filetype": ".yaml" + }, + "param": ".yaml", + "extra_info": {}, + "options": { + "disable_gc": false, + "timer": "perf_counter", + "min_rounds": 5, + "max_time": 1.0, + "min_time": 5e-06, + "warmup": false + }, + "stats": { + "min": 0.010593406999987565, + "max": 0.011153362999948513, + "mean": 0.010793124909096978, + "stddev": 0.0001123684066801466, + "rounds": 88, + "median": 0.010786623000058171, + "iqr": 0.00016890049994344736, + "q1": 0.010700573000008262, + "q3": 0.01086947349995171, + "iqr_outliers": 1, + "stddev_outliers": 23, + "outliers": "23;1", + "ld15iqr": 0.010593406999987565, + "hd15iqr": 0.011153362999948513, + "ops": 92.65157296170554, + "total": 0.949794992000534, + "iterations": 1 + } + }, + { + "group": "serialization", + "name": "test_serialization_performance[.json]", + "fullname": "test/test_performance.py::test_serialization_performance[.json]", + "params": { + "filetype": ".json" + }, + "param": ".json", + "extra_info": {}, + "options": { + "disable_gc": false, + "timer": "perf_counter", + "min_rounds": 5, + "max_time": 1.0, + "min_time": 5e-06, + "warmup": false + }, + "stats": { + "min": 0.0017738520000420976, + "max": 0.0025298140000131752, + "mean": 0.001865216760993584, + "stddev": 7.951950508331161e-05, + "rounds": 523, + "median": 0.0018427400000291527, + "iqr": 4.463350006744804e-05, + "q1": 0.0018254632499576928, + "q3": 0.0018700967500251409, + "iqr_outliers": 65, + "stddev_outliers": 67, + "outliers": "67;65", + "ld15iqr": 0.0017738520000420976, + "hd15iqr": 0.0019371270000192453, + "ops": 536.1307173045717, + "total": 0.9755083659996444, + "iterations": 1 + } + }, + { + "group": "serialization", + "name": "test_serialization_performance[.pkl]", + "fullname": "test/test_performance.py::test_serialization_performance[.pkl]", + "params": { + "filetype": ".pkl" + }, + "param": ".pkl", + "extra_info": {}, + "options": { + "disable_gc": false, + "timer": "perf_counter", + "min_rounds": 5, + "max_time": 1.0, + "min_time": 5e-06, + "warmup": false + }, + "stats": { + "min": 0.0016477059999715493, + "max": 0.0020297899999377478, + "mean": 0.0017231925178561726, + "stddev": 5.509244811844662e-05, + "rounds": 560, + "median": 0.00171150049999369, + "iqr": 3.0281500016826612e-05, + "q1": 0.0016964674999826457, + "q3": 0.0017267489999994723, + "iqr_outliers": 45, + "stddev_outliers": 55, + "outliers": "55;45", + "ld15iqr": 0.0016524360000857996, + "hd15iqr": 0.001779592999923807, + "ops": 580.3182114811536, + "total": 0.9649878099994567, + "iterations": 1 + } + } + ], + "datetime": "2026-08-16T12:25:04.241917+00:00", + "version": "5.2.3" +} \ No newline at end of file diff --git a/simple_parsing/wrappers/field_wrapper.py b/simple_parsing/wrappers/field_wrapper.py index 8ec5bc47..06be4b89 100644 --- a/simple_parsing/wrappers/field_wrapper.py +++ b/simple_parsing/wrappers/field_wrapper.py @@ -354,12 +354,12 @@ def enum_to_str(e): elif self.is_list: logger.debug(f"Adding a List attribute '{self.name}': {self.type}") - # When action="append" or action="extend", each flag occurrence is a - # single element; argparse handles accumulation itself, so nargs="*" - # would wrap each occurrence in an extra list. Skip the nargs - # override in that case and let the user-supplied (or default None) - # nargs take effect. - if self.action not in ("append", "extend"): + # When action="append", each flag occurrence adds a single element and + # argparse handles accumulation itself. Setting nargs="*" would cause + # each occurrence to be collected into a one-item list before being + # appended, producing a nested list. Skip the nargs override so the + # user-supplied (or default None) nargs takes effect. + if self.action != "append": _arg_options["nargs"] = "*" if self.is_reused: diff --git a/test/test_custom_args.py b/test/test_custom_args.py index 7c396cf7..58816321 100644 --- a/test/test_custom_args.py +++ b/test/test_custom_args.py @@ -256,6 +256,20 @@ class Args: assert result.header == ["Auth", "Accept"], result.header +def test_extend_action_on_list_field(): + """action="extend" on a list field should work correctly with multiple values.""" + import simple_parsing + + @dataclass + class Args: + header: list[str] = field(default_factory=list, action="extend", nargs="*") + + result = simple_parsing.parse( + Args, args=["--header", "Auth", "Accept", "--header", "Content-Type"] + ) + assert result.header == ["Auth", "Accept", "Content-Type"], result.header + + if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--foo", action="store_const", const=42)