Skip to content

Commit 952e20d

Browse files
authored
Make ipython fully optional (google#78)
* Fall back on custom info function if IPython's oinspect is not available, thereby making IPython an optional dependency. * Test IPython if IPython available, otherwise test with 'code' module.
1 parent 3126e2c commit 952e20d

File tree

6 files changed

+86
-43
lines changed

6 files changed

+86
-43
lines changed

.travis.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@ before_install:
1010
install:
1111
- python setup.py develop
1212
script:
13-
- python -m pytest
13+
- python -m pytest # Run the tests without IPython.
14+
- pip install ipython
15+
- python -m pytest # Now run the tests with IPython.
1416
- if [[ $TRAVIS_PYTHON_VERSION != 3.6 ]]; then pylint fire --ignore=test_components_py3.py,parser_fuzz_test.py; fi

fire/core.py

+2-29
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ def _Fire(component, args, context, name=None):
352352

353353
try:
354354
target = component.__name__
355-
filename, lineno = _GetFileAndLine(component)
355+
filename, lineno = inspectutils.GetFileAndLine(component)
356356

357357
component, consumed_args, remaining_args, capacity = _CallCallable(
358358
component, remaining_args)
@@ -428,7 +428,7 @@ def _Fire(component, args, context, name=None):
428428
component, consumed_args, remaining_args = _GetMember(
429429
component, remaining_args)
430430

431-
filename, lineno = _GetFileAndLine(component)
431+
filename, lineno = inspectutils.GetFileAndLine(component)
432432

433433
component_trace.AddAccessedProperty(
434434
component, target, consumed_args, filename, lineno)
@@ -486,33 +486,6 @@ def _Fire(component, args, context, name=None):
486486
return component_trace
487487

488488

489-
def _GetFileAndLine(component):
490-
"""Returns the filename and line number of component.
491-
492-
Args:
493-
component: A component to find the source information for, usually a class
494-
or routine.
495-
Returns:
496-
filename: The name of the file where component is defined.
497-
lineno: The line number where component is defined.
498-
"""
499-
if inspect.isbuiltin(component):
500-
return None, None
501-
502-
try:
503-
filename = inspect.getsourcefile(component)
504-
except TypeError:
505-
return None, None
506-
507-
try:
508-
unused_code, lineindex = inspect.findsource(component)
509-
lineno = lineindex + 1
510-
except IOError:
511-
lineno = None
512-
513-
return filename, lineno
514-
515-
516489
def _GetMember(component, args):
517490
"""Returns a subcomponent of component by consuming an arg from args.
518491

fire/helputils_test.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ def testHelpStringObject(self):
4343
self.assertIn('Type: NoDefaults', helpstring)
4444
self.assertIn('String form: <fire.test_components.NoDefaults object at ',
4545
helpstring)
46-
self.assertIn('test_components.py', helpstring)
46+
# TODO: We comment this out since it only works with IPython:
47+
# self.assertIn('test_components.py', helpstring)
4748
self.assertIn('Usage: double\n'
4849
' triple', helpstring)
4950

fire/inspectutils.py

+64-3
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,33 @@ def GetFullArgSpec(fn):
111111
kwonlyargs, kwonlydefaults, annotations)
112112

113113

114+
def GetFileAndLine(component):
115+
"""Returns the filename and line number of component.
116+
117+
Args:
118+
component: A component to find the source information for, usually a class
119+
or routine.
120+
Returns:
121+
filename: The name of the file where component is defined.
122+
lineno: The line number where component is defined.
123+
"""
124+
if inspect.isbuiltin(component):
125+
return None, None
126+
127+
try:
128+
filename = inspect.getsourcefile(component)
129+
except TypeError:
130+
return None, None
131+
132+
try:
133+
unused_code, lineindex = inspect.findsource(component)
134+
lineno = lineindex + 1
135+
except IOError:
136+
lineno = None
137+
138+
return filename, lineno
139+
140+
114141
def Info(component):
115142
"""Returns a dict with information about the given component.
116143
@@ -130,9 +157,12 @@ def Info(component):
130157
Returns:
131158
A dict with information about the component.
132159
"""
133-
import IPython # pylint: disable=g-import-not-at-top
134-
inspector = IPython.core.oinspect.Inspector()
135-
info = inspector.info(component)
160+
try:
161+
from IPython.core import oinspect # pylint: disable=g-import-not-at-top
162+
inspector = oinspect.Inspector()
163+
info = inspector.info(component)
164+
except ImportError:
165+
info = _InfoBackup(component)
136166

137167
try:
138168
unused_code, lineindex = inspect.findsource(component)
@@ -141,3 +171,34 @@ def Info(component):
141171
info['line'] = None
142172

143173
return info
174+
175+
176+
def _InfoBackup(component):
177+
"""Returns a dict with information about the given component.
178+
179+
This function is to be called only in the case that IPython's
180+
oinspect module is not available. The info dict it produces may
181+
contain less information that contained in the info dict produced
182+
by oinspect.
183+
184+
Args:
185+
component: The component to analyze.
186+
Returns:
187+
A dict with information about the component.
188+
"""
189+
info = {}
190+
191+
info['type_name'] = type(component).__name__
192+
info['string_form'] = str(component)
193+
194+
filename, lineno = GetFileAndLine(component)
195+
info['file'] = filename
196+
info['line'] = lineno
197+
info['docstring'] = inspect.getdoc(component)
198+
199+
try:
200+
info['length'] = str(len(component))
201+
except (TypeError, AttributeError):
202+
pass
203+
204+
return info

fire/interact_test.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,29 @@
2424
import mock
2525

2626

27+
try:
28+
import IPython # pylint: disable=unused-import, g-import-not-at-top
29+
INTERACT_METHOD = 'IPython.start_ipython'
30+
except ImportError:
31+
INTERACT_METHOD = 'code.InteractiveConsole'
32+
33+
2734
class InteractTest(testutils.BaseTestCase):
2835

29-
@mock.patch('IPython.start_ipython')
30-
def testInteract(self, mock_ipython):
31-
self.assertFalse(mock_ipython.called)
36+
@mock.patch(INTERACT_METHOD)
37+
def testInteract(self, mock_interact_method):
38+
self.assertFalse(mock_interact_method.called)
3239
interact.Embed({})
33-
self.assertTrue(mock_ipython.called)
40+
self.assertTrue(mock_interact_method.called)
3441

35-
@mock.patch('IPython.start_ipython')
36-
def testInteractVariables(self, mock_ipython):
37-
self.assertFalse(mock_ipython.called)
42+
@mock.patch(INTERACT_METHOD)
43+
def testInteractVariables(self, mock_interact_method):
44+
self.assertFalse(mock_interact_method.called)
3845
interact.Embed({
3946
'count': 10,
4047
'mock': mock,
4148
})
42-
self.assertTrue(mock_ipython.called)
49+
self.assertTrue(mock_interact_method.called)
4350

4451
if __name__ == '__main__':
4552
testutils.main()

setup.py

-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
A library for automatically generating command line interfaces.""".strip()
3131

3232
DEPENDENCIES = [
33-
'ipython<6.0',
3433
'six',
3534
]
3635

0 commit comments

Comments
 (0)