From 5c548cb0b9fbd433db74db9c5c9bc246aa440139 Mon Sep 17 00:00:00 2001 From: David Bieber Date: Thu, 2 Mar 2017 01:43:12 +0000 Subject: [PATCH 1/2] Updates documentation and IPython interact method. Copybara generated commit for Python Fire. PiperOrigin-RevId: 148953241 Change-Id: I401d960bb345ff107235aa9e1f7de2916bd48a21 Reviewed-on: https://team-review.git.corp.google.com/63444 Reviewed-by: David Bieber --- doc/using-cli.md | 47 +++++++++++++++++++++++-------------------- fire/interact.py | 10 ++++----- fire/interact_test.py | 27 ++++++++++++++++--------- 3 files changed, 48 insertions(+), 36 deletions(-) diff --git a/doc/using-cli.md b/doc/using-cli.md index fb1f61c2..ab4050bc 100644 --- a/doc/using-cli.md +++ b/doc/using-cli.md @@ -4,23 +4,25 @@ ## Basic usage -Every Fire command (even just running your Fire CLI with no arguments) always -corresponds to a Python component. +Every Fire command corresponds to a Python component. -This can be any Python component, e.g. an object, a function, a class, or a -module. To see what Python component a Fire command corresponds to, append -`-- --help` to the command. For example, to see what Python component the -command `widget whack` corresponds to, run `widget whack -- --help`. To see what -Python component the command `widget whack 5` corresponds to, run -`widget whack 5 -- --help`. To see what Python component the command `widget` -corresponds to, run `widget -- --help`. +The simplest Fire command consists of running your program with no additional +arguments. This command corresponds to the Python component you called the +`Fire` function on. If you did not supply an object in the call to `Fire`, then +the context in which `Fire` was called will be used as the Python component. -When you first go to use a Fire CLI you're unfamiliar with, let's say it is -called `widget`, start by seeing what Python component the command corresponds -to without any arguments, by calling `widget -- --help`. +You can append `-- --help` to any command to see what Python component it +corresponds to, as well as the various ways in which you can extend the command. +Flags are always separated from the Fire command by an isolated `--` in order +to distinguish between flags and named arguments. + +Given a Fire command that corresponds to a Python object, you can extend that +command to access a member of that object, call it with arguments if it is a +function, instantiate it if it is a class, or index into it if it is a list. + +Read on to learn about how you can write a Fire command corresponding to +whatever Python component you're looking for. -The following sections discuss how you can write a Fire command corresponding -to whatever Python component you're looking for. ### Accessing members of an object @@ -37,14 +39,15 @@ named high_score, then you can add the argument 'high-score' to your command, and the resulting new command corresponds to the value of the high_score property. + ### Accessing members of a dict If your command corresponds to a dict, you can extend your command by adding the name of one of the dict's keys as an argument. -For example, `widget function-that-returns-dict key` will correspond to the -value of the item with key "key" in the dict returned by -function_that_returns_dict. +For example, `widget function-that-returns-dict key` will correspond to the +value of the item with key `key` in the dict returned by +`function_that_returns_dict`. ### Accessing members of a list or tuple @@ -77,8 +80,8 @@ You can force a function that takes a variable number of arguments to be evaluated by adding a separator (the default separator is the hyphen, "-"). This will prevent arguments to the right of the separator from being consumed for calling the function. This is useful if the function has arguments with default -values, or if the function accepts *varargs, or if the function accepts -**kwargs. +values, or if the function accepts \*varargs, or if the function accepts +\*\*kwargs. See also the section on [Changing the Separator](#separator-flag). @@ -86,9 +89,9 @@ See also the section on [Changing the Separator](#separator-flag). ### Instantiating a class If your command corresponds to a class, you can extend your command by adding -the arguments of the class's __init__ function. Arguments can be specified -positionally, or by name. To specify an argument by name, use flag syntax. See -the section on [calling a function](#calling-a-function) for more details. +the arguments of the class's \_\_init\_\_ function. Arguments must be specified +by name, using the flags syntax. See the section on +[calling a function](#calling-a-function) for more details. ## Using Flags with Fire CLIs diff --git a/fire/interact.py b/fire/interact.py index 729ebffa..4f470dca 100644 --- a/fire/interact.py +++ b/fire/interact.py @@ -74,16 +74,16 @@ def _AvailableString(variables, verbose=False): ).format(liststrs='\n'.join(liststrs)) -def _EmbedIPython(variables): - """Drops into an IPython REPL with variables available as local variables. +def _EmbedIPython(variables, argv=None): + """Drops into an IPython REPL with variables available for use. Args: variables: A dict of variables to make available. Keys are variable names. Values are variable values. + argv: The argv to use for starting ipython. Defaults to an empty list. """ - lcl = locals() - lcl.update(**variables) - IPython.embed() + argv = argv or [] + IPython.start_ipython(argv=argv, user_ns=variables) def _EmbedCode(variables): diff --git a/fire/interact_test.py b/fire/interact_test.py index f22b94d6..9112142b 100644 --- a/fire/interact_test.py +++ b/fire/interact_test.py @@ -16,6 +16,8 @@ from __future__ import division from __future__ import print_function +import StringIO + from fire import interact import mock @@ -24,21 +26,28 @@ class InteractTest(unittest.TestCase): - @mock.patch('IPython.embed') - def testInteract(self, mock_embed): - self.assertFalse(mock_embed.called) + @mock.patch('IPython.start_ipython') + def testInteract(self, mock_ipython): + self.assertFalse(mock_ipython.called) interact.Embed({}) - self.assertTrue(mock_embed.called) + self.assertTrue(mock_ipython.called) - @mock.patch('IPython.embed') - def testInteractVariables(self, mock_embed): - self.assertFalse(mock_embed.called) + @mock.patch('IPython.start_ipython') + def testInteractVariables(self, mock_ipython): + self.assertFalse(mock_ipython.called) interact.Embed({ 'count': 10, 'mock': mock, }) - self.assertTrue(mock_embed.called) - + self.assertTrue(mock_ipython.called) + + @mock.patch('sys.stderr', new_callable=StringIO.StringIO) + @mock.patch('sys.stdout', new_callable=StringIO.StringIO) + def testEmbedIPythonString(self, mock_stdout, mock_stderr): + argv = ['-c', 'print(x); exit()'] + interact._EmbedIPython({'x': 'This is a string.'}, argv=argv) + self.assertEqual('This is a string.\n', mock_stdout.getvalue()) + self.assertEqual(mock_stderr.getvalue(), '') if __name__ == '__main__': unittest.main() From 373df45cc91044373a53ad4c9b670cab8421f615 Mon Sep 17 00:00:00 2001 From: David Bieber Date: Wed, 1 Mar 2017 21:23:08 -0800 Subject: [PATCH 2/2] Update interact test to support Python 2 and 3. --- fire/interact_test.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/fire/interact_test.py b/fire/interact_test.py index 9112142b..3176d58c 100644 --- a/fire/interact_test.py +++ b/fire/interact_test.py @@ -16,7 +16,10 @@ from __future__ import division from __future__ import print_function -import StringIO +try: + from cStringIO import StringIO +except ImportError: + from io import StringIO from fire import interact import mock @@ -41,12 +44,12 @@ def testInteractVariables(self, mock_ipython): }) self.assertTrue(mock_ipython.called) - @mock.patch('sys.stderr', new_callable=StringIO.StringIO) - @mock.patch('sys.stdout', new_callable=StringIO.StringIO) + @mock.patch('sys.stderr', new_callable=StringIO) + @mock.patch('sys.stdout', new_callable=StringIO) def testEmbedIPythonString(self, mock_stdout, mock_stderr): - argv = ['-c', 'print(x); exit()'] + argv = ['-c', 'print(x);'] interact._EmbedIPython({'x': 'This is a string.'}, argv=argv) - self.assertEqual('This is a string.\n', mock_stdout.getvalue()) + self.assertIn('This is a string.\n', mock_stdout.getvalue()) self.assertEqual(mock_stderr.getvalue(), '') if __name__ == '__main__':