Repository: ipython/ipython

Issue:
Avoid importing from IPython.parallel in core
IPython.core.interactiveshell imports an error class from IPython.parallel:

https://github.com/ipython/ipython/blob/master/IPython/core/interactiveshell.py#L89

This pulls in the parallel machinery, zmq, numpy, paramiko, and so on, so it adds significantly to startup time and memory use.

The comment in the file suggests a better way of handling it - move the error class into IPython.core.errors, and have IPython.parallel import it from there.



Discussion:
This should be fixed when #2044 is taken care of.


Resolving patch:
diff --git a/IPython/core/display.py b/IPython/core/display.py
--- a/IPython/core/display.py
+++ b/IPython/core/display.py
@@ -186,6 +186,8 @@ def display_latex(*objs, **kwargs):
 
 def display_json(*objs, **kwargs):
     """Display the JSON representation of an object.
+    
+    Note that not many frontends support displaying JSON.
 
     Parameters
     ----------
diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py
--- a/IPython/core/interactiveshell.py
+++ b/IPython/core/interactiveshell.py
@@ -86,22 +86,6 @@
 from IPython.utils.warn import warn, error
 import IPython.core.hooks
 
-# FIXME: do this in a function to avoid circular dependencies
-# A better solution is to remove IPython.parallel.error,
-# and place those classes in IPython.core.error.
-
-class RemoteError(Exception):
-    pass
-
-def _import_remote_error():
-    global RemoteError
-    try:
-        from IPython.parallel.error import RemoteError
-    except:
-        pass
-
-_import_remote_error()
-
 #-----------------------------------------------------------------------------
 # Globals
 #-----------------------------------------------------------------------------
@@ -1735,10 +1719,6 @@ def showtraceback(self,exc_tuple = None,filename=None,tb_offset=None,
                 self.showsyntaxerror(filename)
             elif etype is UsageError:
                 self.write_err("UsageError: %s" % value)
-            elif issubclass(etype, RemoteError):
-                # IPython.parallel remote exceptions.
-                # Draw the remote traceback, not the local one.
-                self._showtraceback(etype, value, value.render_traceback())
             else:
                 if exception_only:
                     stb = ['An exception has occurred, use %tb to see '
@@ -1746,7 +1726,13 @@ def showtraceback(self,exc_tuple = None,filename=None,tb_offset=None,
                     stb.extend(self.InteractiveTB.get_exception_only(etype,
                                                                      value))
                 else:
-                    stb = self.InteractiveTB.structured_traceback(etype,
+                    try:
+                        # Exception classes can customise their traceback - we
+                        # use this in IPython.parallel for exceptions occurring
+                        # in the engines. This should return a list of strings.
+                        stb = value._render_traceback_()
+                    except Exception:
+                        stb = self.InteractiveTB.structured_traceback(etype,
                                             value, tb, tb_offset=tb_offset)
 
                     self._showtraceback(etype, value, stb)
diff --git a/IPython/parallel/error.py b/IPython/parallel/error.py
--- a/IPython/parallel/error.py
+++ b/IPython/parallel/error.py
@@ -193,6 +193,9 @@ def __str__(self):
     def render_traceback(self):
         """render traceback to a list of lines"""
         return (self.traceback or "No traceback available").splitlines()
+    
+    # Special method for custom tracebacks within IPython
+    _render_traceback_ = render_traceback
 
     def print_traceback(self, excid=None):
         """print my traceback"""


Test patch:
diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py
--- a/IPython/core/tests/test_interactiveshell.py
+++ b/IPython/core/tests/test_interactiveshell.py
@@ -33,6 +33,7 @@
 
 # Our own
 from IPython.testing.decorators import skipif
+from IPython.testing import tools as tt
 from IPython.utils import io
 
 #-----------------------------------------------------------------------------
@@ -401,6 +402,18 @@ def test_1(self):
         cmd = ur'''python -c "'åäö'"   '''
         ip.system_raw(cmd)
 
+class TestModules(unittest.TestCase, tt.TempFileMixin):
+    def test_extraneous_loads(self):
+        """Test we're not loading modules on startup that we shouldn't.
+        """
+        self.mktmp("import sys\n"
+                   "print('numpy' in sys.modules)\n"
+                   "print('IPython.parallel' in sys.modules)\n"
+                   "print('IPython.zmq' in sys.modules)\n"
+                   )
+        out = "False\nFalse\nFalse\n"
+        tt.ipexec_validate(self.fname, out)
+
 
 def test__IPYTHON__():
     # This shouldn't raise a NameError, that's all