Skip to content

Commit

Permalink
Merge pull request ipython#1052 from fperez/pylab-fix
Browse files Browse the repository at this point in the history
Fix bug in pylab support introduced in ipython#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
fperez committed Nov 27, 2011
2 parents a1e4911 + 3f11378 commit f15123a
Show file tree
Hide file tree
Showing 10 changed files with 371 additions and 378 deletions.
41 changes: 40 additions & 1 deletion IPython/core/interactiveshell.py
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
24 changes: 15 additions & 9 deletions IPython/core/magic.py
Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
99 changes: 58 additions & 41 deletions IPython/lib/pylabtools.py → IPython/core/pylabtools.py
Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -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)
53 changes: 8 additions & 45 deletions IPython/frontend/terminal/interactiveshell.py
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -516,45 +518,6 @@ def int0(x):
return False
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
#-------------------------------------------------------------------------
Expand Down
4 changes: 2 additions & 2 deletions IPython/testing/iptest.py
Expand Up @@ -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'))
Expand Down

0 comments on commit f15123a

Please sign in to comment.