From 4350decf00a1036842083b6d169a4994f257714f 2013-06-27 18:14:06
From: Fernando Perez <Fernando.Perez@berkeley.edu>
Date: 2013-06-27 18:14:06
Subject: [PATCH] Replace exec/eval with proper __import__.

Unfortunately for some very weird reason, this code can't be called as
a function, so I inlined a verbatim copy of importstring.import_item.

---

diff --git a/IPython/frontend.py b/IPython/frontend.py
index a076ced..edce202 100644
--- a/IPython/frontend.py
+++ b/IPython/frontend.py
@@ -19,9 +19,20 @@ printed.
 # Imports
 #-----------------------------------------------------------------------------
 from __future__ import print_function
+
+# Stdlib
 import sys
 import types
 
+m = """\
+*** WARNING*** : The top-level `frontend` package has been deprecated.
+All its subpackages have been moved to the top `IPython` level."""
+
+print(m, file=sys.stderr)
+
+# FIXME: turn this into a Warning once we've fixed all our own imports.
+#raise DeprecationWarning(m)
+
 #-----------------------------------------------------------------------------
 # Class declarations
 #-----------------------------------------------------------------------------
@@ -29,20 +40,35 @@ import types
 class ShimModule(types.ModuleType):
 
     def __getattribute__(self, key):
-        m = ("*** WARNING*** : The top-level `frontend` module has been deprecated.\n"
-        "Please import %s directly from the `IPython` level." % key)
-
-        # FIXME: I don't understand why, but if the print statement below is
-        # redirected to stderr, this shim module stops working.  It seems the
-        # Python import machinery has problem with redirected prints happening
-        # during the import process.  If we can't figure out a solution, we may
-        # need to leave it to print to default stdout.
-        print(m)
+        # Use the equivalent of import_item(name), see below
+        name = 'IPython.' + key
+
+        # NOTE: the code below is copied *verbatim* from
+        # importstring.import_item. For some very strange reason that makes no
+        # sense to me, if we call it *as a function*, it doesn't work.  This
+        # has something to do with the deep bowels of the import machinery and
+        # I couldn't find a way to make the code work as a standard function
+        # call.  But at least since it's an unmodified copy of import_item,
+        # which is used extensively and has a test suite, we can be reasonably
+        # confident this is OK.  If anyone finds how to call the function, all
+        # the below could be replaced simply with:
+        #
+        # from IPython.utils.importstring import import_item
+        # return import_item('IPython.' + key)
         
-        # FIXME: this seems to work fine, but we should replace it with an
-        # __import__ call instead of using exec/eval.
-        exec 'from IPython import %s' % key
-        return eval(key)
+        parts = name.rsplit('.', 1)
+        if len(parts) == 2:
+            # called with 'foo.bar....'
+            package, obj = parts
+            module = __import__(package, fromlist=[obj])
+            try:
+                pak = module.__dict__[obj]
+            except KeyError:
+                raise ImportError('No module named %s' % obj)
+            return pak
+        else:
+            # called with un-dotted string
+            return __import__(parts[0])
 
 
 # Unconditionally insert the shim into sys.modules so that further import calls