Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Pylab fix #1052

Merged
merged 9 commits into from

2 participants

Fernando Perez Min RK
Fernando Perez
Owner

When merging #648, we inadvertently broke pretty badly the pylab support. I added a test to catch the problem and started fixing things, but quickly saw we had a pretty messy setup in our pylab support machinery, just the product of the fast development we had during the creation of the qt console and zeromq support. The only way to fix this cleanly was to refactor things a little bit, but I think it's in decent shape now.

The main problem is that we had a ton of code duplication, with several methods implemented twice in the terminal and zmq shells, but with actually minimal differences. But the duplication meant that fixing a but required hunting down a lot of stuff around.

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.

Merging this is, however, very urgent because right now master is fully broken. I'm going to see if @minrk is available on IRC and can have a quick look. If not, I'll merge it immediately, but having the pull request helps to easily see what I did in isolation, and we can do a post-merge reivew. I did verify that it fixes the problem we had, added new tests, and checked that both the qt console and the notebook work fine.

fperez added some commits
Fernando Perez fperez Fix critical bug with pylab support inadvertently introduced in #648.
code used it as a dict.  Updated that code to handle a dict correctly,
and added tests to catch this issue in the future (also increases test
coverage of pylab code).
cd84e0e
Fernando Perez fperez Refactor gui/pylab integration to eliminate code duplication.
Also, fix a few tests that the previous commit broke.
cfd87e9
Fernando Perez fperez Move zmq event loop support into a separate file.
This avoids a circular import problem, and also organizes more cleanly
the code that is event-loop specific.
0e21764
Fernando Perez fperez Minor cleanups after a check with pyflakes of the refactored code. 5c4e9a0
Fernando Perez fperez Add missing file. 37afbb3
IPython/core/pylabtools.py
((53 lines not shown))
+ shell : InteractiveShell instance
+ If None, this function returns immediately.
+
+ 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 shell is None:
+ return
+
+ user_ns = shell.user_ns if user_ns is None else user_ns
+
+ # 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
Min RK Owner
minrk added a note

This will make ipython --pylab fail in the absence of zmq.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/core/pylabtools.py
((45 lines not shown))
- exec s in shell.user_ns_hidden
+
+
+def configure_shell(shell, backend, user_ns=None):
+ """Configure an IPython shell object for matplotlib use.
+
+ Parameters
+ ----------
+ shell : InteractiveShell instance
+ If None, this function returns immediately.
+
+ 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 shell is None:
Min RK Owner
minrk added a note

I would just not call this function if shell is None - no need for it to conditionally do nothing if it was given inappropriate args.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Fernando Perez
Owner

Merging now after detailed review with @minrk on IRC. If anyone else spots any issues let me know, and I'm happy to revisit. But at least @minrk did have a careful look, and he spotted a number of problems (all fixed now).

Fernando Perez fperez merged commit f15123a into from
Matthias Bussonnier Carreau referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Fernando Perez fperez referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Fernando Perez fperez referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
matthew von rocketstein mattvonrocketstein referenced this pull request from a commit in mattvonrocketstein/ipython
Fernando Perez fperez Avoid calling inline config if no shell - per @minrk feedback on #1052 c74d8de
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 27, 2011
  1. Fernando Perez

    Fix critical bug with pylab support inadvertently introduced in #648.

    fperez authored
    code used it as a dict.  Updated that code to handle a dict correctly,
    and added tests to catch this issue in the future (also increases test
    coverage of pylab code).
  2. Fernando Perez

    Refactor gui/pylab integration to eliminate code duplication.

    fperez authored
    Also, fix a few tests that the previous commit broke.
  3. Fernando Perez

    Move zmq event loop support into a separate file.

    fperez authored
    This avoids a circular import problem, and also organizes more cleanly
    the code that is event-loop specific.
  4. Fernando Perez
  5. Fernando Perez

    Add missing file.

    fperez authored
  6. Fernando Perez
  7. Fernando Perez
  8. Fernando Perez
  9. Fernando Perez

    fix docstring

    fperez authored
This page is out of date. Refresh to see the latest.
41 IPython/core/interactiveshell.py
View
@@ -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
24 IPython/core/magic.py
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.
99 IPython/lib/pylabtools.py → IPython/core/pylabtools.py
View
@@ -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
9 IPython/lib/tests/test_pylabtools.py → IPython/core/tests/test_pylabtools.py
View
@@ -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)
53 IPython/frontend/terminal/interactiveshell.py
View
@@ -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
#-------------------------------------------------------------------------
4 IPython/testing/iptest.py
View
@@ -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'))
206 IPython/zmq/eventloops.py
View
@@ -0,0 +1,206 @@
+# encoding: utf-8
+"""Event loop integration for the ZeroMQ-based kernels.
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2011 The IPython Development Team
+
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import sys
+
+# System library imports.
+import zmq
+
+# Local imports.
+from IPython.utils import io
+
+#------------------------------------------------------------------------------
+# Eventloops for integrating the Kernel into different GUIs
+#------------------------------------------------------------------------------
+
+def loop_qt4(kernel):
+ """Start a kernel with PyQt4 event loop integration."""
+
+ from IPython.external.qt_for_kernel import QtCore
+ from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
+
+ kernel.app = get_app_qt4([" "])
+ kernel.app.setQuitOnLastWindowClosed(False)
+ kernel.timer = QtCore.QTimer()
+ kernel.timer.timeout.connect(kernel.do_one_iteration)
+ # Units for the timer are in milliseconds
+ kernel.timer.start(1000*kernel._poll_interval)
+ start_event_loop_qt4(kernel.app)
+
+
+def loop_wx(kernel):
+ """Start a kernel with wx event loop support."""
+
+ import wx
+ from IPython.lib.guisupport import start_event_loop_wx
+
+ doi = kernel.do_one_iteration
+ # Wx uses milliseconds
+ poll_interval = int(1000*kernel._poll_interval)
+
+ # We have to put the wx.Timer in a wx.Frame for it to fire properly.
+ # We make the Frame hidden when we create it in the main app below.
+ class TimerFrame(wx.Frame):
+ def __init__(self, func):
+ wx.Frame.__init__(self, None, -1)
+ self.timer = wx.Timer(self)
+ # Units for the timer are in milliseconds
+ self.timer.Start(poll_interval)
+ self.Bind(wx.EVT_TIMER, self.on_timer)
+ self.func = func
+
+ def on_timer(self, event):
+ self.func()
+
+ # We need a custom wx.App to create our Frame subclass that has the
+ # wx.Timer to drive the ZMQ event loop.
+ class IPWxApp(wx.App):
+ def OnInit(self):
+ self.frame = TimerFrame(doi)
+ self.frame.Show(False)
+ return True
+
+ # The redirect=False here makes sure that wx doesn't replace
+ # sys.stdout/stderr with its own classes.
+ kernel.app = IPWxApp(redirect=False)
+ start_event_loop_wx(kernel.app)
+
+
+def loop_tk(kernel):
+ """Start a kernel with the Tk event loop."""
+
+ import Tkinter
+ doi = kernel.do_one_iteration
+ # Tk uses milliseconds
+ poll_interval = int(1000*kernel._poll_interval)
+ # For Tkinter, we create a Tk object and call its withdraw method.
+ class Timer(object):
+ def __init__(self, func):
+ self.app = Tkinter.Tk()
+ self.app.withdraw()
+ self.func = func
+
+ def on_timer(self):
+ self.func()
+ self.app.after(poll_interval, self.on_timer)
+
+ def start(self):
+ self.on_timer() # Call it once to get things going.
+ self.app.mainloop()
+
+ kernel.timer = Timer(doi)
+ kernel.timer.start()
+
+
+def loop_gtk(kernel):
+ """Start the kernel, coordinating with the GTK event loop"""
+ from .gui.gtkembed import GTKEmbed
+
+ gtk_kernel = GTKEmbed(kernel)
+ gtk_kernel.start()
+
+
+def loop_cocoa(kernel):
+ """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
+ via the matplotlib MacOSX backend.
+ """
+ import matplotlib
+ if matplotlib.__version__ < '1.1.0':
+ kernel.log.warn(
+ "MacOSX backend in matplotlib %s doesn't have a Timer, "
+ "falling back on Tk for CFRunLoop integration. Note that "
+ "even this won't work if Tk is linked against X11 instead of "
+ "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, "
+ "you must use matplotlib >= 1.1.0, or a native libtk."
+ )
+ return loop_tk(kernel)
+
+ from matplotlib.backends.backend_macosx import TimerMac, show
+
+ # scale interval for sec->ms
+ poll_interval = int(1000*kernel._poll_interval)
+
+ real_excepthook = sys.excepthook
+ def handle_int(etype, value, tb):
+ """don't let KeyboardInterrupts look like crashes"""
+ if etype is KeyboardInterrupt:
+ io.raw_print("KeyboardInterrupt caught in CFRunLoop")
+ else:
+ real_excepthook(etype, value, tb)
+
+ # add doi() as a Timer to the CFRunLoop
+ def doi():
+ # restore excepthook during IPython code
+ sys.excepthook = real_excepthook
+ kernel.do_one_iteration()
+ # and back:
+ sys.excepthook = handle_int
+
+ t = TimerMac(poll_interval)
+ t.add_callback(doi)
+ t.start()
+
+ # but still need a Poller for when there are no active windows,
+ # during which time mainloop() returns immediately
+ poller = zmq.Poller()
+ poller.register(kernel.shell_socket, zmq.POLLIN)
+
+ while True:
+ try:
+ # double nested try/except, to properly catch KeyboardInterrupt
+ # due to pyzmq Issue #130
+ try:
+ # don't let interrupts during mainloop invoke crash_handler:
+ sys.excepthook = handle_int
+ show.mainloop()
+ sys.excepthook = real_excepthook
+ # use poller if mainloop returned (no windows)
+ # scale by extra factor of 10, since it's a real poll
+ poller.poll(10*poll_interval)
+ kernel.do_one_iteration()
+ except:
+ raise
+ except KeyboardInterrupt:
+ # Ctrl-C shouldn't crash the kernel
+ io.raw_print("KeyboardInterrupt caught in kernel")
+ finally:
+ # ensure excepthook is restored
+ sys.excepthook = real_excepthook
+
+# mapping of keys to loop functions
+loop_map = {
+ 'qt' : loop_qt4,
+ 'qt4': loop_qt4,
+ 'inline': None,
+ 'osx': loop_cocoa,
+ 'wx' : loop_wx,
+ 'tk' : loop_tk,
+ 'gtk': loop_gtk,
+ None : None,
+}
+
+
+def enable_gui(gui, kernel=None):
+ """Enable integration with a given GUI"""
+ if kernel is None:
+ from .ipkernel import IPKernelApp
+ kernel = IPKernelApp.instance().kernel
+ if gui not in loop_map:
+ raise ValueError("GUI %r not supported" % gui)
+ loop = loop_map[gui]
+ if kernel.eventloop is not None and kernel.eventloop is not loop:
+ raise RuntimeError("Cannot activate multiple GUI eventloops")
+ kernel.eventloop = loop
231 IPython/zmq/ipkernel.py
View
@@ -39,12 +39,11 @@
from IPython.utils.jsonutil import json_clean
from IPython.lib import pylabtools
from IPython.utils.traitlets import (
- Any, List, Instance, Float, Dict, Bool, Unicode, CaselessStrEnum
+ Any, Instance, Float, Dict, CaselessStrEnum
)
from entry_point import base_launch_kernel
from kernelapp import KernelApp, kernel_flags, kernel_aliases
-from iostream import OutStream
from session import Session, Message
from zmqshell import ZMQInteractiveShell
@@ -212,16 +211,16 @@ def record_ports(self, ports):
def _publish_pyin(self, code, parent):
"""Publish the code request on the pyin stream."""
- pyin_msg = self.session.send(self.iopub_socket, u'pyin',{u'code':code}, parent=parent)
+ self.session.send(self.iopub_socket, u'pyin', {u'code':code},
+ parent=parent)
def execute_request(self, ident, parent):
- status_msg = self.session.send(self.iopub_socket,
- u'status',
- {u'execution_state':u'busy'},
- parent=parent
- )
-
+ self.session.send(self.iopub_socket,
+ u'status',
+ {u'execution_state':u'busy'},
+ parent=parent )
+
try:
content = parent[u'content']
code = content[u'code']
@@ -331,11 +330,10 @@ def execute_request(self, ident, parent):
if reply_msg['content']['status'] == u'error':
self._abort_queue()
- status_msg = self.session.send(self.iopub_socket,
- u'status',
- {u'execution_state':u'idle'},
- parent=parent
- )
+ self.session.send(self.iopub_socket,
+ u'status',
+ {u'execution_state':u'idle'},
+ parent=parent )
def complete_request(self, ident, parent):
txt, matches = self._complete(parent)
@@ -375,7 +373,8 @@ def history_request(self, ident, parent):
elif hist_access_type == 'search':
pattern = parent['content']['pattern']
- hist = self.shell.history_manager.search(pattern, raw=raw, output=output)
+ hist = self.shell.history_manager.search(pattern, raw=raw,
+ output=output)
else:
hist = []
@@ -396,7 +395,8 @@ def connect_request(self, ident, parent):
def shutdown_request(self, ident, parent):
self.shell.exit_now = True
- self._shutdown_message = self.session.msg(u'shutdown_reply', parent['content'], parent)
+ self._shutdown_message = self.session.msg(u'shutdown_reply',
+ parent['content'], parent)
sys.exit(0)
#---------------------------------------------------------------------------
@@ -427,8 +427,10 @@ def _abort_queue(self):
time.sleep(0.1)
def _no_raw_input(self):
- """Raise StdinNotImplentedError if active frontend doesn't support stdin."""
- raise StdinNotImplementedError("raw_input was called, but this frontend does not support stdin.")
+ """Raise StdinNotImplentedError if active frontend doesn't support
+ stdin."""
+ raise StdinNotImplementedError("raw_input was called, but this "
+ "frontend does not support stdin.")
def _raw_input(self, prompt, ident, parent):
# Flush output before making the request.
@@ -437,7 +439,8 @@ def _raw_input(self, prompt, ident, parent):
# Send the input request.
content = json_clean(dict(prompt=prompt))
- msg = self.session.send(self.stdin_socket, u'input_request', content, parent, ident=ident)
+ self.session.send(self.stdin_socket, u'input_request', content, parent,
+ ident=ident)
# Await a response.
while True:
@@ -510,189 +513,6 @@ def _at_shutdown(self):
# before Python truly shuts down.
time.sleep(0.01)
-
-#------------------------------------------------------------------------------
-# Eventloops for integrating the Kernel into different GUIs
-#------------------------------------------------------------------------------
-
-
-def loop_qt4(kernel):
- """Start a kernel with PyQt4 event loop integration."""
-
- from IPython.external.qt_for_kernel import QtCore
- from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
-
- kernel.app = get_app_qt4([" "])
- kernel.app.setQuitOnLastWindowClosed(False)
- kernel.timer = QtCore.QTimer()
- kernel.timer.timeout.connect(kernel.do_one_iteration)
- # Units for the timer are in milliseconds
- kernel.timer.start(1000*kernel._poll_interval)
- start_event_loop_qt4(kernel.app)
-
-
-def loop_wx(kernel):
- """Start a kernel with wx event loop support."""
-
- import wx
- from IPython.lib.guisupport import start_event_loop_wx
-
- doi = kernel.do_one_iteration
- # Wx uses milliseconds
- poll_interval = int(1000*kernel._poll_interval)
-
- # We have to put the wx.Timer in a wx.Frame for it to fire properly.
- # We make the Frame hidden when we create it in the main app below.
- class TimerFrame(wx.Frame):
- def __init__(self, func):
- wx.Frame.__init__(self, None, -1)
- self.timer = wx.Timer(self)
- # Units for the timer are in milliseconds
- self.timer.Start(poll_interval)
- self.Bind(wx.EVT_TIMER, self.on_timer)
- self.func = func
-
- def on_timer(self, event):
- self.func()
-
- # We need a custom wx.App to create our Frame subclass that has the
- # wx.Timer to drive the ZMQ event loop.
- class IPWxApp(wx.App):
- def OnInit(self):
- self.frame = TimerFrame(doi)
- self.frame.Show(False)
- return True
-
- # The redirect=False here makes sure that wx doesn't replace
- # sys.stdout/stderr with its own classes.
- kernel.app = IPWxApp(redirect=False)
- start_event_loop_wx(kernel.app)
-
-
-def loop_tk(kernel):
- """Start a kernel with the Tk event loop."""
-
- import Tkinter
- doi = kernel.do_one_iteration
- # Tk uses milliseconds
- poll_interval = int(1000*kernel._poll_interval)
- # For Tkinter, we create a Tk object and call its withdraw method.
- class Timer(object):
- def __init__(self, func):
- self.app = Tkinter.Tk()
- self.app.withdraw()
- self.func = func
-
- def on_timer(self):
- self.func()
- self.app.after(poll_interval, self.on_timer)
-
- def start(self):
- self.on_timer() # Call it once to get things going.
- self.app.mainloop()
-
- kernel.timer = Timer(doi)
- kernel.timer.start()
-
-
-def loop_gtk(kernel):
- """Start the kernel, coordinating with the GTK event loop"""
- from .gui.gtkembed import GTKEmbed
-
- gtk_kernel = GTKEmbed(kernel)
- gtk_kernel.start()
-
-
-def loop_cocoa(kernel):
- """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
- via the matplotlib MacOSX backend.
- """
- import matplotlib
- if matplotlib.__version__ < '1.1.0':
- kernel.log.warn(
- "MacOSX backend in matplotlib %s doesn't have a Timer, "
- "falling back on Tk for CFRunLoop integration. Note that "
- "even this won't work if Tk is linked against X11 instead of "
- "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, "
- "you must use matplotlib >= 1.1.0, or a native libtk."
- )
- return loop_tk(kernel)
-
- from matplotlib.backends.backend_macosx import TimerMac, show
-
- # scale interval for sec->ms
- poll_interval = int(1000*kernel._poll_interval)
-
- real_excepthook = sys.excepthook
- def handle_int(etype, value, tb):
- """don't let KeyboardInterrupts look like crashes"""
- if etype is KeyboardInterrupt:
- io.raw_print("KeyboardInterrupt caught in CFRunLoop")
- else:
- real_excepthook(etype, value, tb)
-
- # add doi() as a Timer to the CFRunLoop
- def doi():
- # restore excepthook during IPython code
- sys.excepthook = real_excepthook
- kernel.do_one_iteration()
- # and back:
- sys.excepthook = handle_int
-
- t = TimerMac(poll_interval)
- t.add_callback(doi)
- t.start()
-
- # but still need a Poller for when there are no active windows,
- # during which time mainloop() returns immediately
- poller = zmq.Poller()
- poller.register(kernel.shell_socket, zmq.POLLIN)
-
- while True:
- try:
- # double nested try/except, to properly catch KeyboardInterrupt
- # due to pyzmq Issue #130
- try:
- # don't let interrupts during mainloop invoke crash_handler:
- sys.excepthook = handle_int
- show.mainloop()
- sys.excepthook = real_excepthook
- # use poller if mainloop returned (no windows)
- # scale by extra factor of 10, since it's a real poll
- poller.poll(10*poll_interval)
- kernel.do_one_iteration()
- except:
- raise
- except KeyboardInterrupt:
- # Ctrl-C shouldn't crash the kernel
- io.raw_print("KeyboardInterrupt caught in kernel")
- finally:
- # ensure excepthook is restored
- sys.excepthook = real_excepthook
-
-# mapping of keys to loop functions
-loop_map = {
- 'qt' : loop_qt4,
- 'qt4': loop_qt4,
- 'inline': None,
- 'osx': loop_cocoa,
- 'wx' : loop_wx,
- 'tk' : loop_tk,
- 'gtk': loop_gtk,
-}
-
-def enable_gui(gui, kernel=None):
- """Enable integration with a give GUI"""
- if kernel is None:
- kernel = IPKernelApp.instance().kernel
- if gui not in loop_map:
- raise ValueError("GUI %r not supported" % gui)
- loop = loop_map[gui]
- if kernel.eventloop is not None and kernel.eventloop is not loop:
- raise RuntimeError("Cannot activate multiple GUI eventloops")
- kernel.eventloop = loop
-
-
#-----------------------------------------------------------------------------
# Aliases and Flags for the IPKernelApp
#-----------------------------------------------------------------------------
@@ -767,7 +587,8 @@ def init_kernel(self):
# replace pyerr-sending traceback with stdout
_showtraceback = shell._showtraceback
def print_tb(etype, evalue, stb):
- print ("Error initializing pylab, pylab mode will not be active", file=io.stderr)
+ print ("Error initializing pylab, pylab mode will not "
+ "be active", file=io.stderr)
print (shell.InteractiveTB.stb2text(stb), file=io.stdout)
shell._showtraceback = print_tb
@@ -790,8 +611,8 @@ def init_shell(self):
def launch_kernel(*args, **kwargs):
"""Launches a localhost IPython kernel, binding to the specified ports.
- This function simply calls entry_point.base_launch_kernel with the right first
- command to start an ipkernel. See base_launch_kernel for arguments.
+ This function simply calls entry_point.base_launch_kernel with the right
+ first command to start an ipkernel. See base_launch_kernel for arguments.
Returns
-------
2  IPython/zmq/pylab/backend_inline.py
View
@@ -16,7 +16,7 @@
# Local imports.
from IPython.config.configurable import SingletonConfigurable
from IPython.core.displaypub import publish_display_data
-from IPython.lib.pylabtools import print_figure, select_figure_format
+from IPython.core.pylabtools import print_figure, select_figure_format
from IPython.utils.traitlets import Dict, Instance, CaselessStrEnum, CBool
from IPython.utils.warn import warn
80 IPython/zmq/zmqshell.py
View
@@ -109,6 +109,11 @@ def _exiter_default(self):
keepkernel_on_exit = None
+ # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
+ # interactive input being read; we provide event loop support in ipkernel
+ from .eventloops import enable_gui
+ enable_gui = staticmethod(enable_gui)
+
def init_environment(self):
"""Configure the user's environment.
@@ -390,79 +395,6 @@ def magic_edit(self,parameter_s='',last_call=['','']):
}
self.payload_manager.write_payload(payload)
- def magic_gui(self, parameter_s=''):
- """Enable or disable IPython GUI event loop integration.
-
- %gui [GUINAME]
-
- This magic replaces IPython's threaded shells that were activated
- using the (pylab/wthread/etc.) command line flags. GUI toolkits
- can now be enabled at runtime and keyboard
- interrupts should work without any problems. The following toolkits
- are supported: wxPython, PyQt4, PyGTK, Cocoa, and Tk::
-
- %gui wx # enable wxPython event loop integration
- %gui qt4|qt # enable PyQt4 event loop integration
- %gui gtk # enable PyGTK event loop integration
- %gui OSX # enable Cocoa event loop integration (requires matplotlib 1.1)
- %gui tk # enable Tk 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.zmq.ipkernel import enable_gui
- opts, arg = self.parse_options(parameter_s, '')
- if arg=='': arg = None
- try:
- 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 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 [default: inline]
-
- If given, dictates the choice of matplotlib GUI backend to use
- (should be one of IPython's supported backends, 'inline', 'qt', 'osx',
- 'tk', 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).
- """
- from IPython.zmq.ipkernel import enable_gui
- # 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 = pylabtools.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
- try:
- enable_gui(gui)
- except Exception as e:
- # print simple error message, rather than traceback if we can't
- # hook up the GUI
- error(str(e))
- self.magic_run = self._pylab_magic_run
-
-
# A few magics that are adapted to the specifics of using pexpect and a
# remote terminal
@@ -567,7 +499,6 @@ def magic_qtconsole(self, arg_s):
except Exception as e:
error("Could not start qtconsole: %r" % e)
return
-
def set_next_input(self, text):
"""Send the specified text to the frontend to be presented at the next
@@ -578,4 +509,5 @@ def set_next_input(self, text):
)
self.payload_manager.write_payload(payload)
+
InteractiveShellABC.register(ZMQInteractiveShell)
Something went wrong with that request. Please try again.