Permalink
Browse files

Merge pull request #1052 from fperez/pylab-fix

Fix bug in pylab support introduced in #648, and refactor the pylab/gui support to eliminate a lot of code duplication we had in a number of places. 

Now all duplicate code is gone, and the only real difference is how gui event loops are integrated, which is reduced to a single static method that each relevant class grabs from its specific machinery.
  • Loading branch information...
2 parents a1e4911 + 3f11378 commit f15123ac96a192589c2613c4733a4a26ae2dac15 @fperez fperez committed Nov 27, 2011
@@ -62,6 +62,7 @@
from IPython.core.plugin import PluginManager
from IPython.core.prefilter import PrefilterManager, ESC_MAGIC
from IPython.core.profiledir import ProfileDir
+from IPython.core.pylabtools import pylab_activate
from IPython.external.Itpl import ItplNS
from IPython.utils import PyColorize
from IPython.utils import io
@@ -2531,8 +2532,46 @@ def run_code(self, code_obj):
# Things related to GUI support and pylab
#-------------------------------------------------------------------------
+ def enable_gui(self, gui=None):
+ raise NotImplementedError('Implement enable_gui in a subclass')
+
def enable_pylab(self, gui=None, import_all=True):
- raise NotImplementedError('Implement enable_pylab in a subclass')
+ """Activate pylab support at runtime.
+
+ This turns on support for matplotlib, preloads into the interactive
+ namespace all of numpy and pylab, and configures IPython to correctly
+ interact with the GUI event loop. The GUI backend to be used can be
+ optionally selected with the optional :param:`gui` argument.
+
+ Parameters
+ ----------
+ gui : optional, string
+
+ If given, dictates the choice of matplotlib GUI backend to use
+ (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
+ 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
+ matplotlib (as dictated by the matplotlib build-time options plus the
+ user's matplotlibrc configuration file). Note that not all backends
+ make sense in all contexts, for example a terminal ipython can't
+ display figures inline.
+ """
+
+ # We want to prevent the loading of pylab to pollute the user's
+ # namespace as shown by the %who* magics, so we execute the activation
+ # code in an empty namespace, and we update *both* user_ns and
+ # user_ns_hidden with this information.
+ ns = {}
+ try:
+ gui = pylab_activate(ns, gui, import_all, self)
+ except KeyError:
+ error("Backend %r not supported" % gui)
+ return
+ self.user_ns.update(ns)
+ self.user_ns_hidden.update(ns)
+ # Now we must activate the gui pylab wants to use, and fix %run to take
+ # plot updates into account
+ self.enable_gui(gui)
+ self.magic_run = self._pylab_magic_run
#-------------------------------------------------------------------------
# Utilities
View
@@ -50,7 +50,7 @@
from IPython.core.macro import Macro
from IPython.core import magic_arguments, page
from IPython.core.prefilter import ESC_MAGIC
-from IPython.lib.pylabtools import mpl_runner
+from IPython.core.pylabtools import mpl_runner
from IPython.testing.skipdoctest import skip_doctest
from IPython.utils import py3compat
from IPython.utils.io import file_read, nlprint
@@ -3305,24 +3305,30 @@ def magic_gui(self, parameter_s=''):
This magic replaces IPython's threaded shells that were activated
using the (pylab/wthread/etc.) command line flags. GUI toolkits
- can now be enabled, disabled and changed at runtime and keyboard
+ can now be enabled at runtime and keyboard
interrupts should work without any problems. The following toolkits
- are supported: wxPython, PyQt4, PyGTK, and Tk::
+ are supported: wxPython, PyQt4, PyGTK, Tk and Cocoa (OSX)::
%gui wx # enable wxPython event loop integration
%gui qt4|qt # enable PyQt4 event loop integration
%gui gtk # enable PyGTK event loop integration
%gui tk # enable Tk event loop integration
+ %gui OSX # enable Cocoa event loop integration
+ # (requires %matplotlib 1.1)
%gui # disable all event loop integration
WARNING: after any of these has been called you can simply create
an application object, but DO NOT start the event loop yourself, as
we have already handled that.
"""
- from IPython.lib.inputhook import enable_gui
opts, arg = self.parse_options(parameter_s, '')
if arg=='': arg = None
- return enable_gui(arg)
+ try:
+ return self.enable_gui(arg)
+ except Exception as e:
+ # print simple error message, rather than traceback if we can't
+ # hook up the GUI
+ error(str(e))
def magic_load_ext(self, module_str):
"""Load an IPython extension by its module name."""
@@ -3416,9 +3422,9 @@ def magic_pylab(self, s):
Parameters
----------
guiname : optional
- One of the valid arguments to the %gui magic ('qt', 'wx', 'gtk', 'osx' or
- 'tk'). If given, the corresponding Matplotlib backend is used,
- otherwise matplotlib's default (which you can override in your
+ One of the valid arguments to the %gui magic ('qt', 'wx', 'gtk',
+ 'osx' or 'tk'). If given, the corresponding Matplotlib backend is
+ used, otherwise matplotlib's default (which you can override in your
matplotlib config file) is used.
Examples
@@ -3449,7 +3455,7 @@ def magic_pylab(self, s):
else:
import_all_status = True
- self.shell.enable_pylab(s,import_all=import_all_status)
+ self.shell.enable_pylab(s, import_all=import_all_status)
def magic_tb(self, s):
"""Print the last traceback with the currently active exception mode.
@@ -232,7 +232,8 @@ def activate_matplotlib(backend):
# For this, we wrap it into a decorator which adds a 'called' flag.
pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
-def import_pylab(user_ns, backend, import_all=True, shell=None):
+
+def import_pylab(user_ns, import_all=True):
"""Import the standard pylab symbols into user_ns."""
# Import numpy as np/pyplot as plt are conventions we're trying to
@@ -246,48 +247,62 @@ def import_pylab(user_ns, backend, import_all=True, shell=None):
)
exec s in user_ns
- if shell is not None:
- exec s in shell.user_ns_hidden
- # If using our svg payload backend, register the post-execution
- # function that will pick up the results for display. This can only be
- # done with access to the real shell object.
- #
- from IPython.zmq.pylab.backend_inline import InlineBackend
-
- cfg = InlineBackend.instance(config=shell.config)
- cfg.shell = shell
- if cfg not in shell.configurables:
- shell.configurables.append(cfg)
-
- if backend == backends['inline']:
- from IPython.zmq.pylab.backend_inline import flush_figures
- from matplotlib import pyplot
- shell.register_post_execute(flush_figures)
- # load inline_rc
- pyplot.rcParams.update(cfg.rc)
-
- # Add 'figsize' to pyplot and to the user's namespace
- user_ns['figsize'] = pyplot.figsize = figsize
- shell.user_ns_hidden['figsize'] = figsize
-
- # Setup the default figure format
- fmt = cfg.figure_format
- select_figure_format(shell, fmt)
-
- # The old pastefig function has been replaced by display
- from IPython.core.display import display
- # Add display and display_png to the user's namespace
- user_ns['display'] = display
- shell.user_ns_hidden['display'] = display
- user_ns['getfigs'] = getfigs
- shell.user_ns_hidden['getfigs'] = getfigs
-
if import_all:
s = ("from matplotlib.pylab import *\n"
"from numpy import *\n")
exec s in user_ns
- if shell is not None:
- exec s in shell.user_ns_hidden
+
+
+def configure_inline_support(shell, backend, user_ns=None):
+ """Configure an IPython shell object for matplotlib use.
+
+ Parameters
+ ----------
+ shell : InteractiveShell instance
+
+ backend : matplotlib backend
+
+ user_ns : dict
+ A namespace where all configured variables will be placed. If not given,
+ the `user_ns` attribute of the shell object is used.
+ """
+ # If using our svg payload backend, register the post-execution
+ # function that will pick up the results for display. This can only be
+ # done with access to the real shell object.
+
+ # Note: if we can't load the inline backend, then there's no point
+ # continuing (such as in terminal-only shells in environments without
+ # zeromq available).
+ try:
+ from IPython.zmq.pylab.backend_inline import InlineBackend
+ except ImportError:
+ return
+
+ user_ns = shell.user_ns if user_ns is None else user_ns
+
+ cfg = InlineBackend.instance(config=shell.config)
+ cfg.shell = shell
+ if cfg not in shell.configurables:
+ shell.configurables.append(cfg)
+
+ if backend == backends['inline']:
+ from IPython.zmq.pylab.backend_inline import flush_figures
+ from matplotlib import pyplot
+ shell.register_post_execute(flush_figures)
+ # load inline_rc
+ pyplot.rcParams.update(cfg.rc)
+ # Add 'figsize' to pyplot and to the user's namespace
+ user_ns['figsize'] = pyplot.figsize = figsize
+
+ # Setup the default figure format
+ fmt = cfg.figure_format
+ select_figure_format(shell, fmt)
+
+ # The old pastefig function has been replaced by display
+ from IPython.core.display import display
+ # Add display and getfigs to the user's namespace
+ user_ns['display'] = display
+ user_ns['getfigs'] = getfigs
def pylab_activate(user_ns, gui=None, import_all=True, shell=None):
@@ -313,8 +328,10 @@ def pylab_activate(user_ns, gui=None, import_all=True, shell=None):
"""
gui, backend = find_gui_and_backend(gui)
activate_matplotlib(backend)
- import_pylab(user_ns, backend, import_all, shell)
-
+ import_pylab(user_ns, import_all)
+ if shell is not None:
+ configure_inline_support(shell, backend, user_ns)
+
print """
Welcome to pylab, a matplotlib-based Python environment [backend: %s].
For more information, type 'help(pylab)'.""" % backend
@@ -20,6 +20,7 @@
import nose.tools as nt
from matplotlib import pyplot as plt
+import numpy as np
# Our own imports
from IPython.testing import decorators as dec
@@ -52,3 +53,11 @@ def test_figure_to_svg():
plt.draw()
svg = pt.print_figure(fig, 'svg')[:100].lower()
yield nt.assert_true('doctype svg' in svg)
+
+
+def test_import_pylab():
+ ip = get_ipython()
+ ns = {}
+ pt.import_pylab(ns, import_all=False)
+ nt.assert_true('plt' in ns)
+ nt.assert_equal(ns['np'], np)
@@ -29,8 +29,7 @@
from IPython.core.error import TryNext
from IPython.core.usage import interactive_usage, default_banner
from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
-from IPython.lib.inputhook import enable_gui
-from IPython.lib.pylabtools import pylab_activate
+from IPython.core.pylabtools import pylab_activate
from IPython.testing.skipdoctest import skip_doctest
from IPython.utils import py3compat
from IPython.utils.terminal import toggle_set_term_title, set_term_title
@@ -171,10 +170,13 @@ class TerminalInteractiveShell(InteractiveShell):
help="Enable auto setting the terminal title."
)
- def __init__(self, config=None, ipython_dir=None, profile_dir=None, user_ns=None,
- user_module=None, custom_exceptions=((),None),
- usage=None, banner1=None, banner2=None,
- display_banner=None):
+ # In the terminal, GUI control is done via PyOS_InputHook
+ from IPython.lib.inputhook import enable_gui
+ enable_gui = staticmethod(enable_gui)
+
+ def __init__(self, config=None, ipython_dir=None, profile_dir=None,
+ user_ns=None, user_module=None, custom_exceptions=((),None),
+ usage=None, banner1=None, banner2=None, display_banner=None):
super(TerminalInteractiveShell, self).__init__(
config=config, profile_dir=profile_dir, user_ns=user_ns,
@@ -517,45 +519,6 @@ def int0(x):
return True
#-------------------------------------------------------------------------
- # Things related to GUI support and pylab
- #-------------------------------------------------------------------------
-
- def enable_pylab(self, gui=None, import_all=True):
- """Activate pylab support at runtime.
-
- This turns on support for matplotlib, preloads into the interactive
- namespace all of numpy and pylab, and configures IPython to correcdtly
- interact with the GUI event loop. The GUI backend to be used can be
- optionally selected with the optional :param:`gui` argument.
-
- Parameters
- ----------
- gui : optional, string
-
- If given, dictates the choice of matplotlib GUI backend to use
- (should be one of IPython's supported backends, 'tk', 'qt', 'wx' or
- 'gtk'), otherwise we use the default chosen by matplotlib (as
- dictated by the matplotlib build-time options plus the user's
- matplotlibrc configuration file).
- """
- # We want to prevent the loading of pylab to pollute the user's
- # namespace as shown by the %who* magics, so we execute the activation
- # code in an empty namespace, and we update *both* user_ns and
- # user_ns_hidden with this information.
- ns = {}
- try:
- gui = pylab_activate(ns, gui, import_all)
- except KeyError:
- error("Backend %r not supported" % gui)
- return
- self.user_ns.update(ns)
- self.user_ns_hidden.update(ns)
- # Now we must activate the gui pylab wants to use, and fix %run to take
- # plot updates into account
- enable_gui(gui)
- self.magic_run = self._pylab_magic_run
-
- #-------------------------------------------------------------------------
# Things related to exiting
#-------------------------------------------------------------------------
@@ -238,8 +238,8 @@ def make_exclude():
exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
if not have['matplotlib']:
- exclusions.extend([ipjoin('lib', 'pylabtools'),
- ipjoin('lib', 'tests', 'test_pylabtools')])
+ exclusions.extend([ipjoin('core', 'pylabtools'),
+ ipjoin('core', 'tests', 'test_pylabtools')])
if not have['tornado']:
exclusions.append(ipjoin('frontend', 'html'))
Oops, something went wrong.

0 comments on commit f15123a

Please sign in to comment.