Thanks to visit codestin.com
Credit goes to github.com

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
110 changes: 60 additions & 50 deletions fire/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,8 @@ def _MakeParseFn(fn):

def _ParseFn(args):
"""Parses the list of `args` into (varargs, kwargs), remaining_args."""
kwargs, remaining_args = _ParseKeywordArgs(args, all_args, fn_spec.varkw)
kwargs, remaining_kwargs, remaining_args = \
_ParseKeywordArgs(args, all_args, fn_spec.varkw)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: for consistency w/ codebase, let's do:

kwargs, remaining_kwargs, remaining_args = _ParseKeywordArgs(
    args, all_args, fn_spec.varkw)


# Note: _ParseArgs modifies kwargs.
parsed_args, kwargs, remaining_args, capacity = _ParseArgs(
Expand Down Expand Up @@ -594,6 +595,7 @@ def _ParseFn(args):
varargs[index] = _ParseValue(value, None, None, metadata)

varargs = parsed_args + varargs
remaining_args += remaining_kwargs

consumed_args = args[:len(args) - len(remaining_args)]
return (varargs, kwargs), consumed_args, remaining_args, capacity
Expand Down Expand Up @@ -681,64 +683,72 @@ def _ParseKeywordArgs(args, fn_args, fn_keywords):
fn_keywords: The argument name for **kwargs, or None if **kwargs not used
Returns:
kwargs: A dictionary mapping keywords to values.
remaining_kwargs: A list of the unused kwargs from the original args.
remaining_args: A list of the unused arguments from the original args.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Please update the Returns: section of the docstring.
  • Also how about "remaining_kwargs" instead of unconsumed_named_args for consistency with the existing terminology?

"""
kwargs = {}
if args:
remaining_args = []
skip_argument = False

for index, argument in enumerate(args):
if skip_argument:
skip_argument = False
continue

arg_consumed = False
if argument.startswith('--'):
# This is a named argument; get its value from this arg or the next.
got_argument = False

keyword = argument[2:]
contains_equals = '=' in keyword
is_bool_syntax = (
not contains_equals and
(index + 1 == len(args) or args[index + 1].startswith('--')))
if contains_equals:
keyword, value = keyword.split('=', 1)
got_argument = True
elif is_bool_syntax:
# Since there's no next arg or the next arg is a Flag, we consider
# this flag to be a boolean.
got_argument = True
if keyword in fn_args:
value = 'True'
elif keyword.startswith('no'):
keyword = keyword[2:]
value = 'False'
else:
value = 'True'
remaining_kwargs = []
remaining_args = []

if not args:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I add a return statement here because pylint complains about "Too many nested blocks (6/5)". Not sure if I should do it this way.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems ok to me.

Another possibility would be moving some of the inner blocks into their own functions, but I don't see a super clean way to do that.

return kwargs, remaining_kwargs, remaining_args

skip_argument = False

for index, argument in enumerate(args):
if skip_argument:
skip_argument = False
continue

arg_consumed = False
if argument.startswith('--'):
# This is a named argument; get its value from this arg or the next.
got_argument = False

keyword = argument[2:]
contains_equals = '=' in keyword
is_bool_syntax = (
not contains_equals and
(index + 1 == len(args) or args[index + 1].startswith('--')))
if contains_equals:
keyword, value = keyword.split('=', 1)
got_argument = True
elif is_bool_syntax:
# Since there's no next arg or the next arg is a Flag, we consider
# this flag to be a boolean.
got_argument = True
if keyword in fn_args:
value = 'True'
elif keyword.startswith('no'):
keyword = keyword[2:]
value = 'False'
else:
if index + 1 < len(args):
value = args[index + 1]
got_argument = True
value = 'True'
else:
if index + 1 < len(args):
value = args[index + 1]
got_argument = True

keyword = keyword.replace('-', '_')
keyword = keyword.replace('-', '_')

# In order for us to consume the argument as a keyword arg, we either:
# Need to be explicitly expecting the keyword, or we need to be
# accepting **kwargs.
if got_argument and (keyword in fn_args or fn_keywords):
# In order for us to consume the argument as a keyword arg, we either:
# Need to be explicitly expecting the keyword, or we need to be
# accepting **kwargs.
if got_argument:
skip_argument = not contains_equals and not is_bool_syntax
arg_consumed = True
if keyword in fn_args or fn_keywords:
kwargs[keyword] = value
skip_argument = not contains_equals and not is_bool_syntax
arg_consumed = True
else:
remaining_kwargs.append(argument)
if skip_argument:
remaining_kwargs.append(args[index + 1])

if not arg_consumed:
# The argument was not consumed, so it is still a remaining argument.
remaining_args.append(argument)
else:
remaining_args = args
if not arg_consumed:
# The argument was not consumed, so it is still a remaining argument.
remaining_args.append(argument)

return kwargs, remaining_args
return kwargs, remaining_kwargs, remaining_args


def _ParseValue(value, index, arg, metadata):
Expand Down
13 changes: 9 additions & 4 deletions fire/fire_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,10 +368,15 @@ def testBoolParsingLessExpectedCases(self):
fire.Fire(tc.MixedDefaults,
command=['identity', 'True', '10']), (True, 10))

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing that may be worthwhile to test is the behavior in the presence of separators / chained function calls.

For example, with these components:

def example_component(arg1, kwarg2='default2'):
  def inner_component(kwarg3='default3'):
    return kwarg3
  return inner_component
def example_component(arg1, *varargs):
  def inner_component(kwarg3='default3'):
    return kwarg3
  return inner_component

What is the behavior of these calls?

fire.Fire(example_component, command=['value1', '-', '--kwarg3', 'value3']) == 'value3'
fire.Fire(example_component, command=['value1', '--kwarg3', 'value3'])

# Note: Does not return ('--test', '0').
self.assertEqual(fire.Fire(tc.MixedDefaults,
command=['identity', '--alpha', '--test']),
(True, '--test'))
# Note: Does not return (True, '--test') or ('--test', 0).
with self.assertRaisesFireExit(2):
fire.Fire(tc.MixedDefaults, command=['identity', '--alpha', '--test'])

self.assertEqual(
fire.Fire(
tc.MixedDefaults,
command=['identity', '--alpha', 'True', '"--test"']),
(True, '--test'))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm.

# To get ('--test', '0'), use one of the following:
self.assertEqual(fire.Fire(tc.MixedDefaults,
command=['identity', '--alpha=--test']),
Expand Down