Skip to content

Commit

Permalink
Accept a list of arguments for the argument (in addition to continuin…
Browse files Browse the repository at this point in the history
…g to accept a string).

- Update tests to pass Fire args as a list instead of as a string.
- Also uses named argument for command= throughout unit tests.

    Copybara generated commit for Python Fire.
    
PiperOrigin-RevId: 162780662
Change-Id: I1cb0d32510fa5f43e50d85819ebe39f5fecaddce
Reviewed-on: https://team-review.git.corp.google.com/85622
Reviewed-by: David Bieber <[email protected]>
  • Loading branch information
dbieber committed Jul 21, 2017
1 parent 84c5a46 commit b5053ba
Show file tree
Hide file tree
Showing 4 changed files with 366 additions and 191 deletions.
19 changes: 13 additions & 6 deletions fire/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ def Fire(component=None, command=None, name=None):
Args:
component: The initial target component.
command: Optional. If supplied, this is the command executed. If not
supplied, then the command is taken from sys.argv instead.
supplied, then the command is taken from sys.argv instead. This can be
a string or a list of strings; a list of strings is preferred.
name: Optional. The name of the command as entered at the command line.
Used in interactive mode and for generating the completion script.
Returns:
Expand All @@ -94,19 +95,25 @@ def Fire(component=None, command=None, name=None):
to call or class left to instantiate, the resulting current component is
the final result.
Raises:
ValueError: If the command argument is supplied, but not a string or a
sequence of arguments.
FireExit: When Fire encounters a FireError, Fire will raise a FireExit with
code 2. When used with the help or trace flags, Fire will raise a
FireExit with code 0 if successful.
"""
name = name or os.path.basename(sys.argv[0])

# Get args as a list.
if command is None:
if isinstance(command, six.string_types):
args = shlex.split(command)
elif isinstance(command, (list, tuple)):
args = command
elif command is None:
# Use the command line args by default if no command is specified.
args = sys.argv[1:]
else:
# Otherwise use the specified command.
args = shlex.split(command)

name = name or os.path.basename(sys.argv[0])
raise ValueError('The command argument must be a string or a sequence of '
'arguments.')

# Determine the calling context.
caller = inspect.stack()[1]
Expand Down
23 changes: 12 additions & 11 deletions fire/core_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@ def testOneLineResultCircularRef(self):

@mock.patch('fire.interact.Embed')
def testInteractiveMode(self, mock_embed):
core.Fire(tc.TypedProperties, 'alpha')
core.Fire(tc.TypedProperties, command=['alpha'])
self.assertFalse(mock_embed.called)
core.Fire(tc.TypedProperties, 'alpha -- -i')
core.Fire(tc.TypedProperties, command=['alpha', '--', '-i'])
self.assertTrue(mock_embed.called)

@mock.patch('fire.interact.Embed')
def testInteractiveModeFullArgument(self, mock_embed):
core.Fire(tc.TypedProperties, 'alpha -- --interactive')
core.Fire(tc.TypedProperties, command=['alpha', '--', '--interactive'])
self.assertTrue(mock_embed.called)

@mock.patch('fire.interact.Embed')
def testInteractiveModeVariables(self, mock_embed):
core.Fire(tc.WithDefaults, 'double 2 -- -i')
core.Fire(tc.WithDefaults, command=['double', '2', '--', '-i'])
self.assertTrue(mock_embed.called)
(variables, verbose), unused_kwargs = mock_embed.call_args
self.assertFalse(verbose)
Expand All @@ -62,7 +62,8 @@ def testInteractiveModeVariables(self, mock_embed):

@mock.patch('fire.interact.Embed')
def testInteractiveModeVariablesWithName(self, mock_embed):
core.Fire(tc.WithDefaults, 'double 2 -- -i -v', name='D')
core.Fire(tc.WithDefaults,
command=['double', '2', '--', '-i', '-v'], name='D')
self.assertTrue(mock_embed.called)
(variables, verbose), unused_kwargs = mock_embed.call_args
self.assertTrue(verbose)
Expand All @@ -74,21 +75,21 @@ def testInteractiveModeVariablesWithName(self, mock_embed):
def testImproperUseOfHelp(self):
# This should produce a warning explaining the proper use of help.
with self.assertRaisesFireExit(2, 'The proper way to show help.*Usage:'):
core.Fire(tc.TypedProperties, 'alpha --help')
core.Fire(tc.TypedProperties, command=['alpha', '--help'])

def testProperUseOfHelp(self):
with self.assertRaisesFireExit(0, 'Usage:.*upper'):
core.Fire(tc.TypedProperties, 'gamma -- --help')
core.Fire(tc.TypedProperties, command=['gamma', '--', '--help'])

def testInvalidParameterRaisesFireExit(self):
with self.assertRaisesFireExit(2, 'runmisspelled'):
core.Fire(tc.Kwargs, 'props --a=1 --b=2 runmisspelled')
core.Fire(tc.Kwargs, command=['props', '--a=1', '--b=2', 'runmisspelled'])

def testErrorRaising(self):
# Errors in user code should not be caught; they should surface as normal.
# This will lead to exit status code 1 for the client program.
with self.assertRaises(ValueError):
core.Fire(tc.ErrorRaiser, 'fail')
core.Fire(tc.ErrorRaiser, command=['fail'])

def testFireError(self):
error = core.FireError('Example error')
Expand All @@ -100,9 +101,9 @@ def testFireErrorMultipleValues(self):

def testPrintEmptyDict(self):
with self.assertOutputMatches(stdout='{}', stderr=None):
core.Fire(tc.EmptyDictOutput, 'totally_empty')
core.Fire(tc.EmptyDictOutput, command=['totally_empty'])
with self.assertOutputMatches(stdout='{}', stderr=None):
core.Fire(tc.EmptyDictOutput, 'nothing_printable')
core.Fire(tc.EmptyDictOutput, command=['nothing_printable'])


if __name__ == '__main__':
Expand Down
61 changes: 39 additions & 22 deletions fire/decorators_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ def example7(self, arg1, arg2=None, *varargs, **kwargs):
class FireDecoratorsTest(testutils.BaseTestCase):

def testSetParseFnsNamedArgs(self):
self.assertEqual(core.Fire(NoDefaults, 'double 2'), 4)
self.assertEqual(core.Fire(NoDefaults, 'triple 4'), 12.0)
self.assertEqual(core.Fire(NoDefaults, command=['double', '2']), 4)
self.assertEqual(core.Fire(NoDefaults, command=['triple', '4']), 12.0)

def testSetParseFnsPositionalArgs(self):
self.assertEqual(core.Fire(NoDefaults, 'quadruple 5'), 20)
self.assertEqual(core.Fire(NoDefaults, command=['quadruple', '5']), 20)

def testSetParseFnsFnWithPositionalArgs(self):
self.assertEqual(core.Fire(double, '5'), 10)
self.assertEqual(core.Fire(double, command=['5']), 10)

def testSetParseFnsDefaultsFromPython(self):
# When called from Python, function should behave normally.
Expand All @@ -109,10 +109,13 @@ def testSetParseFnsDefaultsFromPython(self):

def testSetParseFnsDefaultsFromFire(self):
# Fire should use the decorator to know how to parse string arguments.
self.assertEqual(core.Fire(WithDefaults, 'example1'), (10, int))
self.assertEqual(core.Fire(WithDefaults, 'example1 10'), (10, float))
self.assertEqual(core.Fire(WithDefaults, 'example1 13'), (13, float))
self.assertEqual(core.Fire(WithDefaults, 'example1 14.0'), (14, float))
self.assertEqual(core.Fire(WithDefaults, command=['example1']), (10, int))
self.assertEqual(core.Fire(WithDefaults, command=['example1', '10']),
(10, float))
self.assertEqual(core.Fire(WithDefaults, command=['example1', '13']),
(13, float))
self.assertEqual(core.Fire(WithDefaults, command=['example1', '14.0']),
(14, float))

def testSetParseFnsNamedDefaultsFromPython(self):
# When called from Python, function should behave normally.
Expand All @@ -122,33 +125,47 @@ def testSetParseFnsNamedDefaultsFromPython(self):

def testSetParseFnsNamedDefaultsFromFire(self):
# Fire should use the decorator to know how to parse string arguments.
self.assertEqual(core.Fire(WithDefaults, 'example2'), (10, int))
self.assertEqual(core.Fire(WithDefaults, 'example2 10'), (10, float))
self.assertEqual(core.Fire(WithDefaults, 'example2 13'), (13, float))
self.assertEqual(core.Fire(WithDefaults, 'example2 14.0'), (14, float))
self.assertEqual(core.Fire(WithDefaults, command=['example2']), (10, int))
self.assertEqual(core.Fire(WithDefaults, command=['example2', '10']),
(10, float))
self.assertEqual(core.Fire(WithDefaults, command=['example2', '13']),
(13, float))
self.assertEqual(core.Fire(WithDefaults, command=['example2', '14.0']),
(14, float))

def testSetParseFnsPositionalAndNamed(self):
self.assertEqual(core.Fire(MixedArguments, 'example3 10 10'), (10, '10'))
self.assertEqual(core.Fire(MixedArguments, ['example3', '10', '10']),
(10, '10'))

def testSetParseFnsOnlySomeTypes(self):
self.assertEqual(core.Fire(PartialParseFn, 'example4 10 10'), ('10', 10))
self.assertEqual(core.Fire(PartialParseFn, 'example5 10 10'), (10, '10'))
self.assertEqual(
core.Fire(PartialParseFn, command=['example4', '10', '10']), ('10', 10))
self.assertEqual(
core.Fire(PartialParseFn, command=['example5', '10', '10']), (10, '10'))

def testSetParseFnsForKeywordArgs(self):
self.assertEqual(core.Fire(WithKwargs, 'example6'), ('default', 0))
self.assertEqual(
core.Fire(WithKwargs, 'example6 --herring "red"'), ('default', 0))
core.Fire(WithKwargs, command=['example6']), ('default', 0))
self.assertEqual(
core.Fire(WithKwargs, command=['example6', '--herring', '"red"']),
('default', 0))
self.assertEqual(
core.Fire(WithKwargs, 'example6 --mode train'), ('train', 0))
self.assertEqual(core.Fire(WithKwargs, 'example6 --mode 3'), ('3', 0))
core.Fire(WithKwargs, command=['example6', '--mode', 'train']),
('train', 0))
self.assertEqual(core.Fire(WithKwargs, command=['example6', '--mode', '3']),
('3', 0))
self.assertEqual(
core.Fire(WithKwargs, 'example6 --mode -1 --count 10'), ('-1', 10))
core.Fire(WithKwargs,
command=['example6', '--mode', '-1', '--count', '10']),
('-1', 10))
self.assertEqual(
core.Fire(WithKwargs, 'example6 --count -2'), ('default', -2))
core.Fire(WithKwargs, command=['example6', '--count', '-2']),
('default', -2))

def testSetParseFn(self):
self.assertEqual(
core.Fire(WithVarArgs, 'example7 1 --arg2=2 3 4 --kwarg=5'),
core.Fire(WithVarArgs,
command=['example7', '1', '--arg2=2', '3', '4', '--kwarg=5']),
('1', '2', ('3', '4'), {'kwarg': '5'}))


Expand Down
Loading

0 comments on commit b5053ba

Please sign in to comment.