diff --git a/fire/core.py b/fire/core.py index 8e23e76b..c36e76f7 100644 --- a/fire/core.py +++ b/fire/core.py @@ -884,12 +884,13 @@ def _ParseKeywordArgs(args, fn_spec): # Determine the keyword. keyword = '' # Indicates no valid keyword has been found yet. - if (key in fn_args - or (is_bool_syntax and key.startswith('no') and key[2:] in fn_args) - or fn_keywords): + if key in fn_args or ( + is_bool_syntax and key.startswith('no') and key[2:] in fn_args): keyword = key elif len(key) == 1: - # This may be a shortcut flag. + # This may be a shortcut flag. Check fn_args before falling back to + # fn_keywords so that short flags like -f expand to the matching + # fn_arg (e.g. 'first_arg') even when **kwargs is present. matching_fn_args = [arg for arg in fn_args if arg[0] == key] if len(matching_fn_args) == 1: keyword = matching_fn_args[0] @@ -898,6 +899,10 @@ def _ParseKeywordArgs(args, fn_spec): f"The argument '{argument}' is ambiguous as it could " f"refer to any of the following arguments: {matching_fn_args}" ) + elif fn_keywords: + keyword = key + elif fn_keywords: + keyword = key # Determine the value. if not keyword: diff --git a/fire/fire_test.py b/fire/fire_test.py index 99b4a7c6..72510888 100644 --- a/fire/fire_test.py +++ b/fire/fire_test.py @@ -491,6 +491,23 @@ def testSingleCharFlagParsingExactMatch(self): fire.Fire(tc.SimilarArgNames, command=['identity2', '-a', '-alpha']), (True, True)) + def testSingleCharFlagParsingWithKwargs(self): + # Short flags should map to their matching fn_arg even when **kwargs is + # present (regression test for github.com/google/python-fire/issues/454). + self.assertEqual( + fire.Fire(tc.fn_with_defaults_and_kwargs, + command=['-f=hello', '-s=world']), + ('hello', 'world', {})) + self.assertEqual( + fire.Fire(tc.fn_with_defaults_and_kwargs, + command=['-f', 'hello', '-s', 'world']), + ('hello', 'world', {})) + # Unrecognised single-char flags should still be forwarded to **kwargs. + self.assertEqual( + fire.Fire(tc.fn_with_defaults_and_kwargs, + command=['--unknown_key=42']), + ('left', 'right', {'unknown_key': 42})) + def testSingleCharFlagParsingCapitalLetter(self): self.assertEqual( fire.Fire(tc.CapitalizedArgNames, diff --git a/fire/test_components.py b/fire/test_components.py index 887a0dc6..cffbfc85 100644 --- a/fire/test_components.py +++ b/fire/test_components.py @@ -556,6 +556,11 @@ def fn_with_kwarg_and_defaults(arg1, arg2, opt=True, **kwargs): return kwargs.get('arg3') +def fn_with_defaults_and_kwargs(first_arg='left', second_arg='right', **kwargs): + """Function with named defaults plus **kwargs, used to test short flags.""" + return (first_arg, second_arg, kwargs) + + def fn_with_multiple_defaults(first='first', last='last', late='late'): """Function with kwarg and defaults.