Skip to content

Commit

Permalink
Copybara generated commit for Python Fire.
Browse files Browse the repository at this point in the history
  - Added support of calling callable to Python Fire.
  - Use six.text_type instead of unicode for Python 2/3 compatibility in parser_fuzz_test.

PiperOrigin-RevId: 202218864
Change-Id: I87a1a719c16c2162722f4bbee749eafb485b3f99
Reviewed-on: https://team-review.git.corp.google.com/280130
Reviewed-by: Joe Chen <zuhaochen@google.com>
  • Loading branch information
joejoevictor committed Jun 28, 2018
1 parent eb8f053 commit b55cebd
Show file tree
Hide file tree
Showing 36 changed files with 149 additions and 61 deletions.
2 changes: 1 addition & 1 deletion examples/cipher/cipher.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion examples/cipher/cipher_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion examples/diff/diff.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion examples/diff/diff_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion examples/diff/difffull.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion examples/identity/identity.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion examples/widget/collector.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion examples/widget/collector_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion examples/widget/widget.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion examples/widget/widget_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion fire/__init__.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
6 changes: 3 additions & 3 deletions fire/completion.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -221,7 +221,7 @@ def Completions(component, verbose=False):
return [str(index) for index in range(len(component))]

if inspect.isgenerator(component):
# TODO: There are currently no commands available for generators.
# TODO(dbieber): There are currently no commands available for generators.
return []

return [
Expand Down Expand Up @@ -276,7 +276,7 @@ def _Commands(component, depth=3):
return

for member_name, member in _Members(component):
# TODO: Also skip components we've already seen.
# TODO(dbieber): Also skip components we've already seen.
member_name = _FormatForCommand(member_name)

yield (member_name,)
Expand Down
2 changes: 1 addition & 1 deletion fire/completion_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
85 changes: 65 additions & 20 deletions fire/core.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -237,7 +237,7 @@ def _IsHelpShortcut(component_trace, remaining_args):

def _PrintResult(component_trace, verbose=False):
"""Prints the result of the Fire call to stdout in a human readable way."""
# TODO: Design human readable deserializable serialization method
# TODO(dbieber): Design human readable deserializable serialization method
# and move serialization to it's own module.
result = component_trace.GetResult()

Expand Down Expand Up @@ -298,7 +298,7 @@ def _ComponentVisible(component, verbose=False):

def _OneLineResult(result):
"""Returns result serialized to a single line string."""
# TODO: Ensure line is fewer than eg 120 characters.
# TODO(dbieber): Ensure line is fewer than eg 120 characters.
if isinstance(result, six.string_types):
return str(result).replace('\n', ' ')

Expand Down Expand Up @@ -404,20 +404,12 @@ def _Fire(component, args, context, name=None):
isclass = inspect.isclass(component)

try:
target = component.__name__
filename, lineno = inspectutils.GetFileAndLine(component)

component, consumed_args, remaining_args, capacity = _CallCallable(
component, remaining_args)

# Update the trace.
if isclass:
component_trace.AddInstantiatedClass(
component, target, consumed_args, filename, lineno, capacity)
else:
component_trace.AddCalledRoutine(
component, target, consumed_args, filename, lineno, capacity)

component, remaining_args = _CallAndUpdateTrace(
component,
remaining_args,
component_trace,
treatment='class' if isclass else 'routine',
target=component.__name__)
except FireError as error:
component_trace.AddError(error, initial_args)
return component_trace
Expand Down Expand Up @@ -454,7 +446,7 @@ def _Fire(component, args, context, name=None):
else:
# The target isn't present in the dict as a string, but maybe it is as
# another type.
# TODO: Consider alternatives for accessing non-string keys.
# TODO(dbieber): Consider alternatives for accessing non-string keys.
found_target = False
for key, value in component.items():
if target == str(key):
Expand Down Expand Up @@ -487,8 +479,19 @@ def _Fire(component, args, context, name=None):
component, target, consumed_args, filename, lineno)

except FireError as error:
component_trace.AddError(error, initial_args)
return component_trace
if not callable(component):
component_trace.AddError(error, initial_args)
return component_trace

# If we can't access the member, we try to treat component as a callable
try:
component, remaining_args = _CallAndUpdateTrace(component,
remaining_args,
component_trace,
treatment='callable')
except FireError as error:
component_trace.AddError(error, initial_args)
return component_trace

if used_separator:
# Add back in the arguments from after the separator.
Expand Down Expand Up @@ -569,6 +572,48 @@ def _GetMember(component, args):
raise FireError('Could not consume arg:', arg)


def _CallAndUpdateTrace(component, args, component_trace, treatment='class',
target=None):
"""Call the component and update FireTrace.
The component could a class, a routine, or a callable object. This function
will attempt to call the callable and add corresponding action trace to
component_trace if the invocation is successful. If not, raise FireError.
Args:
component: The component to call
args: Args for calling the component
component_trace: FireTrace object that contains action trace
treatment: Type of treatment used. Indicating whether we treat the component
as a class, a routine, or a callable.
target: Target in FireTrace element, default is None. If the value is None,
the component itself will be used as target.
Returns:
component: The object that is the result of the callable call.
remaining_args: The remaining args that haven't been consumed yet.
"""
if not target:
target = component
filename, lineno = inspectutils.GetFileAndLine(component)
component, consumed_args, remaining_args, capacity = _CallCallable(
component.__call__ if treatment == 'callable' else component, args)

# TODO(joejoevictor): Consolidate AddInstantiatedClass, AddCalledRoutine, and
# AddCalledCallable into one method since the only different between those
# methods is the 'action' attribute of the FireTraceElement they created.
if treatment == 'class':
component_trace.AddInstantiatedClass(
component, target, consumed_args, filename, lineno, capacity)
elif treatment == 'routine':
component_trace.AddCalledRoutine(
component, target, consumed_args, filename, lineno, capacity)
else:
component_trace.AddCalledCallable(
component, target, consumed_args, filename, lineno, capacity)

return component, remaining_args


def _CallCallable(fn, args):
"""Calls the function fn by consuming args from args.
Expand Down
14 changes: 11 additions & 3 deletions fire/core_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -72,7 +72,7 @@ def testInteractiveModeVariablesWithName(self, mock_embed):
self.assertEqual(variables['D'], tc.WithDefaults)
self.assertIsInstance(variables['trace'], trace.FireTrace)

# TODO: Use parameterized tests to break up repetitive tests.
# TODO(dbieber): Use parameterized tests to break up repetitive tests.
def testHelpWithClass(self):
with self.assertRaisesFireExit(0, 'Usage:.*ARG1'):
core.Fire(tc.InstanceVars, command=['--', '--help'])
Expand Down Expand Up @@ -134,12 +134,20 @@ def testPrintEmptyDict(self):
with self.assertOutputMatches(stdout='{}', stderr=None):
core.Fire(tc.EmptyDictOutput, command=['nothing_printable'])

def testPrintDict(self):
def testPrintOrderedDict(self):
with self.assertOutputMatches(stdout=r'A:\s+A\s+2:\s+2\s+', stderr=None):
core.Fire(tc.OrderedDictionary, command=['non_empty'])
with self.assertOutputMatches(stdout='{}'):
core.Fire(tc.OrderedDictionary, command=['empty'])

def testCallable(self):
with self.assertOutputMatches(stdout=r'foo:\s+foo\s+', stderr=None):
core.Fire(tc.CallableWithKeywordArgument(), command=['--foo=foo'])
with self.assertOutputMatches(stdout=r'foo\s+', stderr=None):
core.Fire(tc.CallableWithKeywordArgument(), command=['print_msg', 'foo'])
with self.assertOutputMatches(stdout=r'', stderr=None):
core.Fire(tc.CallableWithKeywordArgument(), command=[])


if __name__ == '__main__':
testutils.main()
2 changes: 1 addition & 1 deletion fire/decorators.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion fire/decorators_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion fire/fire_import_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion fire/fire_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion fire/helputils.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
4 changes: 2 additions & 2 deletions fire/helputils_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -43,7 +43,7 @@ def testHelpStringObject(self):
self.assertIn('Type: NoDefaults', helpstring)
self.assertIn('String form: <fire.test_components.NoDefaults object at ',
helpstring)
# TODO: We comment this out since it only works with IPython:
# TODO(dbieber): We comment this out since it only works with IPython:
# self.assertIn('test_components.py', helpstring)
self.assertIn('Usage: double\n'
' triple', helpstring)
Expand Down
2 changes: 1 addition & 1 deletion fire/inspectutils.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion fire/inspectutils_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion fire/interact.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion fire/interact_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
4 changes: 2 additions & 2 deletions fire/parser.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -30,7 +30,7 @@ def CreateParser():
parser.add_argument('--completion', nargs='?', const='bash', type=str)
parser.add_argument('--help', '-h', action='store_true')
parser.add_argument('--trace', '-t', action='store_true')
# TODO: Consider allowing name to be passed as an argument.
# TODO(dbieber): Consider allowing name to be passed as an argument.
return parser


Expand Down
2 changes: 1 addition & 1 deletion fire/parser_fuzz_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion fire/parser_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
13 changes: 12 additions & 1 deletion fire/test_components.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -248,3 +248,14 @@ def non_empty(self):
ordered_dict['A'] = 'A'
ordered_dict[2] = 2
return ordered_dict


class CallableWithKeywordArgument(object):
"""Test class for supporting callable."""

def __call__(self, **kwargs):
for key, value in kwargs.items():
print('%s: %s' % (key, value))

def print_msg(self, msg):
print(msg)
2 changes: 1 addition & 1 deletion fire/test_components_py3.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion fire/test_components_test.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion fire/testutils.py
@@ -1,4 +1,4 @@
# Copyright (C) 2017 Google Inc.
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down

0 comments on commit b55cebd

Please sign in to comment.