From 89511978f9818663363347c60133fe62c3738e91 Mon Sep 17 00:00:00 2001 From: "Devin J. Pohly" Date: Tue, 6 Feb 2024 13:53:25 -0600 Subject: [PATCH 1/5] tests: add test for trailing underscores --- tests/test_assembling.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_assembling.py b/tests/test_assembling.py index 23c17a0..6243513 100644 --- a/tests/test_assembling.py +++ b/tests/test_assembling.py @@ -669,6 +669,25 @@ def cmd(foo_pos, bar_pos, *args, foo_kwonly="foo_kwonly", bar_kwonly): ] +@pytest.mark.xfail() +def test_trailing_underscore_in_argument_name(): + "Stripping trailing underscores from named options" + + def cmd(*, foo_="foo", bar_): + return (foo_, bar_) + + parser = argh.ArghParser() + parser.add_argument = MagicMock() + parser.set_default_command( + cmd, name_mapping_policy=NameMappingPolicy.BY_NAME_IF_KWONLY + ) + help_tmpl = argh.constants.DEFAULT_ARGUMENT_TEMPLATE + assert parser.add_argument.mock_calls == [ + call("-f", "--foo", default="foo", type=str, help=help_tmpl), + call("-b", "--bar", required=True, help=help_tmpl), + ] + + @patch("argh.assembling.COMPLETION_ENABLED", True) def test_custom_argument_completer(): "Issue #33: Enable custom per-argument shell completion" From 55a3cda3320480f385128f1c92954219fe959597 Mon Sep 17 00:00:00 2001 From: "Devin J. Pohly" Date: Tue, 6 Feb 2024 14:16:46 -0600 Subject: [PATCH 2/5] feat: strip trailing underscore from option names Fixes #222. --- src/argh/assembling.py | 3 +++ tests/test_assembling.py | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/argh/assembling.py b/src/argh/assembling.py index 7a690ea..cad4319 100644 --- a/src/argh/assembling.py +++ b/src/argh/assembling.py @@ -153,6 +153,9 @@ def infer_argspecs_from_function( ) def _make_cli_arg_names_options(arg_name) -> Tuple[List[str], List[str]]: + # str.removesuffix() can be used here starting with Python 3.9 + if arg_name.endswith("_"): + arg_name = arg_name[:-1] cliified_arg_name = arg_name.replace("_", "-") positionals = [cliified_arg_name] can_have_short_opt = arg_name[0] not in conflicting_opts diff --git a/tests/test_assembling.py b/tests/test_assembling.py index 6243513..f1dfd35 100644 --- a/tests/test_assembling.py +++ b/tests/test_assembling.py @@ -669,7 +669,6 @@ def cmd(foo_pos, bar_pos, *args, foo_kwonly="foo_kwonly", bar_kwonly): ] -@pytest.mark.xfail() def test_trailing_underscore_in_argument_name(): "Stripping trailing underscores from named options" From b89c6d9fcbdd4bfbee93ec6797d702e568478bec Mon Sep 17 00:00:00 2001 From: "Devin J. Pohly" Date: Tue, 6 Feb 2024 14:37:05 -0600 Subject: [PATCH 3/5] docs: document trailing underscore behavior --- src/argh/assembling.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/argh/assembling.py b/src/argh/assembling.py index cad4319..3150a8a 100644 --- a/src/argh/assembling.py +++ b/src/argh/assembling.py @@ -409,6 +409,13 @@ def set_default_command( If the parser was created with ``add_help=True`` (which is by default), option name ``-h`` is silently removed from any argument. + .. note:: + + Function argument names ending with an underscore will have a single + trailing underscore removed before being converted to CLI arguments. + This allows CLI arguments to have names that would otherwise clash with + a reserved word or shadow a builtin. + """ func_signature = inspect.signature(function) From 9c709b8fef139fac8a2ec0ab38e621966b703198 Mon Sep 17 00:00:00 2001 From: "Devin J. Pohly" Date: Tue, 6 Feb 2024 15:09:13 -0600 Subject: [PATCH 4/5] tests: integration test for trailing underscores --- tests/test_integration.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_integration.py b/tests/test_integration.py index f863386..21e7cfc 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -594,6 +594,19 @@ def cmd(a_b): assert run(parser, "hello").out == "hello\n" +@pytest.mark.xfail +def test_trailing_underscore_keys(): + """One trailing underscore is ignored in function args.""" + + def cmd(*, a, b_, c__): + return f"a='{a}' b_='{b_}' c__='{c__}'" + + parser = DebugArghParser() + parser.set_default_command(cmd) + + assert run(parser, "--a x --b y --c- z").out == "a='x' b_='y' c__='z'\n" + + @mock.patch("argh.assembling.COMPLETION_ENABLED", True) def test_custom_argument_completer(): "Issue #33: Enable custom per-argument shell completion" From f3ff6111c60bedcab72dc8393b759722d49bb5df Mon Sep 17 00:00:00 2001 From: "Devin J. Pohly" Date: Tue, 6 Feb 2024 15:16:27 -0600 Subject: [PATCH 5/5] fix: remove underscores when matching input args --- src/argh/dispatching.py | 6 +++++- tests/test_integration.py | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/argh/dispatching.py b/src/argh/dispatching.py index ef413f6..9b332cc 100644 --- a/src/argh/dispatching.py +++ b/src/argh/dispatching.py @@ -373,7 +373,11 @@ def _flat_key(key): kwonly_names = [p.name for p in func_params if p.kind == p.KEYWORD_ONLY] varargs_names = [p.name for p in func_params if p.kind == p.VAR_POSITIONAL] positional_values = [values_by_arg_name[name] for name in positional_names] - values_by_name = dict((k, values_by_arg_name[k]) for k in kwonly_names) + # str.removesuffix() can be used from Python 3.9 onward + values_by_name = dict( + (k, values_by_arg_name[k[:-1] if k.endswith("_") else k]) + for k in kwonly_names + ) # *args if varargs_names: diff --git a/tests/test_integration.py b/tests/test_integration.py index 21e7cfc..f42c011 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -594,7 +594,6 @@ def cmd(a_b): assert run(parser, "hello").out == "hello\n" -@pytest.mark.xfail def test_trailing_underscore_keys(): """One trailing underscore is ignored in function args."""