From 087388fb17c8e08e4544b6a80846e3ce8f542610 2010-08-26 04:55:10 From: Brian Granger Date: 2010-08-26 04:55:10 Subject: [PATCH] Merge branch 'newkernel' into upstream-newkernel Conflicts: IPython/frontend/qt/console/scripts/ipythonqt.py IPython/zmq/ipkernel.py --- diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index cb2150d..26bf430 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -194,8 +194,8 @@ class InteractiveShell(Configurable, Magic): # TODO: this part of prompt management should be moved to the frontends. # Use custom TraitTypes that convert '0'->'' and '\\n'->'\n' separate_in = SeparateStr('\n', config=True) - separate_out = SeparateStr('\n', config=True) - separate_out2 = SeparateStr('\n', config=True) + separate_out = SeparateStr('', config=True) + separate_out2 = SeparateStr('', config=True) system_header = Str('IPython system call: ', config=True) system_verbose = CBool(False, config=True) wildcards_case_sensitive = CBool(True, config=True) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index 499b082..61664e5 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -604,7 +604,7 @@ class ListTB(TBTools): return ListTB.structured_traceback(self, etype, value, []) - def show_exception_only(self, etype, value): + def show_exception_only(self, etype, evalue): """Only print the exception type and message, without a traceback. Parameters diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 4fbd87e..e2aaab0 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -5,7 +5,6 @@ import sys # System library imports from pygments.lexers import PythonLexer from PyQt4 import QtCore, QtGui -import zmq # Local imports from IPython.core.inputsplitter import InputSplitter diff --git a/IPython/frontend/qt/console/scripts/ipythonqt.py b/IPython/frontend/qt/console/scripts/ipythonqt.py index fba9e3e..cbc6fd5 100755 --- a/IPython/frontend/qt/console/scripts/ipythonqt.py +++ b/IPython/frontend/qt/console/scripts/ipythonqt.py @@ -4,7 +4,7 @@ """ # Systemm library imports -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui # Local imports from IPython.external.argparse import ArgumentParser @@ -37,8 +37,11 @@ def main(): egroup = kgroup.add_mutually_exclusive_group() egroup.add_argument('--pure', action='store_true', help = \ 'use a pure Python kernel instead of an IPython kernel') - egroup.add_argument('--pylab', action='store_true', - help='use a kernel with PyLab enabled') + egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?', + const='auto', help = \ + "Pre-load matplotlib and numpy for interactive use. If GUI is not \ + given, the GUI backend is matplotlib's, otherwise use one of: \ + ['tk', 'gtk', 'qt', 'wx', 'payload-svg'].") wgroup = parser.add_argument_group('widget options') wgroup.add_argument('--paging', type=str, default='inside', @@ -48,9 +51,9 @@ def main(): help='enable rich text support') wgroup.add_argument('--tab-simple', action='store_true', help='do tab completion ala a Unix terminal') - + args = parser.parse_args() - + # Don't let Qt or ZMQ swallow KeyboardInterupts. import signal signal.signal(signal.SIGINT, signal.SIG_DFL) @@ -66,7 +69,10 @@ def main(): if args.rich: kernel_manager.start_kernel(pylab='payload-svg') else: - kernel_manager.start_kernel(pylab='qt4') + if args.pylab == 'auto': + kernel_manager.start_kernel(pylab='qt4') + else: + kernel_manager.start_kernel(pylab=args.pylab) else: kernel_manager.start_kernel() kernel_manager.start_channels() diff --git a/IPython/lib/inputhook.py b/IPython/lib/inputhook.py index cec5431..cf5deae 100755 --- a/IPython/lib/inputhook.py +++ b/IPython/lib/inputhook.py @@ -76,7 +76,7 @@ def appstart_qt4(app): except ImportError: app.exec_() """ - from PyQt4 import QtCore, QtGui + from PyQt4 import QtCore assert isinstance(app, QtCore.QCoreApplication) if app is not None: @@ -241,6 +241,7 @@ class InputHookManager(object): mainloop at anytime but startup. """ import Tkinter + # FIXME: gtk is not imported here and we shouldn't be using gtk.main! orig_mainloop = gtk.main dumb_ml = _DummyMainloop(orig_mainloop, self, GUI_TK) Tkinter.Misc.mainloop = dumb_ml @@ -252,7 +253,7 @@ class InputHookManager(object): This is for internal IPython use only and user code should not call this. Instead, they should issue the raw GUI calls themselves. """ - from PyQt4 import QtCore, QtGui + from PyQt4 import QtCore app = QtCore.QCoreApplication.instance() if app is not None: diff --git a/IPython/lib/pylabtools.py b/IPython/lib/pylabtools.py index 7149746..297c60a 100644 --- a/IPython/lib/pylabtools.py +++ b/IPython/lib/pylabtools.py @@ -23,29 +23,21 @@ from IPython.utils.decorators import flag_calls # Main classes and functions #----------------------------------------------------------------------------- -def pylab_activate(user_ns, gui=None, import_all=True): - """Activate pylab mode in the user's namespace. - Loads and initializes numpy, matplotlib and friends for interactive use. +def find_gui_and_backend(gui=None): + """Given a gui string return the gui and mpl backend. Parameters ---------- - user_ns : dict - Namespace where the imports will occur. - - gui : optional, string - A valid gui name following the conventions of the %gui magic. - - import_all : optional, boolean - If true, an 'import *' is done from numpy and pylab. + gui : str + Can be one of ('tk','gtk','wx','qt','qt4','payload-svg'). Returns ------- - The actual gui used (if not given as input, it was obtained from matplotlib - itself, and will be needed next to configure IPython's gui integration. + A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg', + 'WXAgg','Qt4Agg','module://IPython.zmq.pylab.backend_payload_svg'). """ - # Initialize matplotlib to interactive mode always import matplotlib # If user specifies a GUI, that dictates the backend, otherwise we read the @@ -54,7 +46,9 @@ def pylab_activate(user_ns, gui=None, import_all=True): 'gtk': 'GTKAgg', 'wx': 'WXAgg', 'qt': 'Qt4Agg', # qt3 not supported - 'qt4': 'Qt4Agg' } + 'qt4': 'Qt4Agg', + 'payload-svg' : \ + 'module://IPython.zmq.pylab.backend_payload_svg'} if gui: # select backend based on requested gui @@ -65,10 +59,21 @@ def pylab_activate(user_ns, gui=None, import_all=True): # should be for IPython, so we can activate inputhook accordingly b2g = dict(zip(g2b.values(),g2b.keys())) gui = b2g.get(backend, None) + return gui, backend + + +def activate_matplotlib(backend): + """Activate the given backend and set interactive to True.""" + + import matplotlib + if backend.startswith('module://'): + # Work around bug in matplotlib: matplotlib.use converts the + # backend_id to lowercase even if a module name is specified! + matplotlib.rcParams['backend'] = backend + else: + matplotlib.use(backend) + matplotlib.interactive(True) - # We must set the desired backend before importing pylab - matplotlib.use(backend) - # This must be imported last in the matplotlib series, after # backend/interactivity choices have been made import matplotlib.pylab as pylab @@ -83,6 +88,9 @@ def pylab_activate(user_ns, gui=None, import_all=True): # 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, import_all=True): + """Import the standard pylab symbols into user_ns.""" + # Import numpy as np/pyplot as plt are conventions we're trying to # somewhat standardize on. Making them available to users by default # will greatly help this. @@ -97,7 +105,31 @@ def pylab_activate(user_ns, gui=None, import_all=True): exec("from matplotlib.pylab import *\n" "from numpy import *\n") in user_ns - matplotlib.interactive(True) + +def pylab_activate(user_ns, gui=None, import_all=True): + """Activate pylab mode in the user's namespace. + + Loads and initializes numpy, matplotlib and friends for interactive use. + + Parameters + ---------- + user_ns : dict + Namespace where the imports will occur. + + gui : optional, string + A valid gui name following the conventions of the %gui magic. + + import_all : optional, boolean + If true, an 'import *' is done from numpy and pylab. + + Returns + ------- + The actual gui used (if not given as input, it was obtained from matplotlib + itself, and will be needed next to configure IPython's gui integration. + """ + gui, backend = find_gui_and_backend(gui) + activate_matplotlib(backend) + import_pylab(user_ns) print """ Welcome to pylab, a matplotlib-based Python environment [backend: %s]. diff --git a/IPython/zmq/entry_point.py b/IPython/zmq/entry_point.py index 5357b90..a48904d 100644 --- a/IPython/zmq/entry_point.py +++ b/IPython/zmq/entry_point.py @@ -3,6 +3,7 @@ launchers. """ # Standard library imports. +import os import socket from subprocess import Popen import sys @@ -63,7 +64,10 @@ def make_kernel(namespace, kernel_factory, """ Creates a kernel. """ # Install minimal exception handling - sys.excepthook = FormattedTB(mode='Verbose', ostream=sys.__stdout__) + color_scheme = 'LightBG' if sys.platform == 'darwin' else 'Linux' + sys.excepthook = FormattedTB( + mode='Verbose', color_scheme=color_scheme, ostream=sys.__stdout__ + ) # Create a context, a session, and the kernel sockets. io.rprint("Starting the kernel...") @@ -84,6 +88,7 @@ def make_kernel(namespace, kernel_factory, # Redirect input streams and set a display hook. if out_stream_factory: + pass sys.stdout = out_stream_factory(session, pub_socket, u'stdout') sys.stderr = out_stream_factory(session, pub_socket, u'stderr') if display_hook_factory: diff --git a/IPython/zmq/ipkernel.py b/IPython/zmq/ipkernel.py index 3ec7b26..92fdd1c 100755 --- a/IPython/zmq/ipkernel.py +++ b/IPython/zmq/ipkernel.py @@ -27,8 +27,8 @@ import zmq # Local imports. from IPython.config.configurable import Configurable from IPython.utils import io +from IPython.lib import pylabtools from IPython.utils.traitlets import Instance -from completer import KernelCompleter from entry_point import base_launch_kernel, make_argument_parser, make_kernel, \ start_kernel from iostream import OutStream @@ -51,15 +51,6 @@ class Kernel(Configurable): pub_socket = Instance('zmq.Socket') req_socket = Instance('zmq.Socket') - # Maps user-friendly backend names to matplotlib backend identifiers. - _pylab_map = { 'tk': 'TkAgg', - 'gtk': 'GTKAgg', - 'wx': 'WXAgg', - 'qt': 'Qt4Agg', # qt3 not supported - 'qt4': 'Qt4Agg', - 'payload-svg' : \ - 'module://IPython.zmq.pylab.backend_payload_svg' } - def __init__(self, **kwargs): super(Kernel, self).__init__(**kwargs) @@ -79,61 +70,33 @@ class Kernel(Configurable): for msg_type in msg_types: self.handlers[msg_type] = getattr(self, msg_type) - def activate_pylab(self, backend=None, import_all=True): - """ Activates pylab in this kernel's namespace. - - Parameters: - ----------- - backend : str, optional - A valid backend name. - - import_all : bool, optional - If true, an 'import *' is done from numpy and pylab. - """ - # FIXME: This is adapted from IPython.lib.pylabtools.pylab_activate. - # Common functionality should be refactored. - - # We must set the desired backend before importing pylab. - import matplotlib - if backend: - backend_id = self._pylab_map[backend] - if backend_id.startswith('module://'): - # Work around bug in matplotlib: matplotlib.use converts the - # backend_id to lowercase even if a module name is specified! - matplotlib.rcParams['backend'] = backend_id + def do_one_iteration(self): + try: + ident = self.reply_socket.recv(zmq.NOBLOCK) + except zmq.ZMQError, e: + if e.errno == zmq.EAGAIN: + return else: - matplotlib.use(backend_id) - - # Import numpy as np/pyplot as plt are conventions we're trying to - # somewhat standardize on. Making them available to users by default - # will greatly help this. - exec ("import numpy\n" - "import matplotlib\n" - "from matplotlib import pylab, mlab, pyplot\n" - "np = numpy\n" - "plt = pyplot\n" - ) in self.shell.user_ns - - if import_all: - exec("from matplotlib.pylab import *\n" - "from numpy import *\n") in self.shell.user_ns - - matplotlib.interactive(True) + raise + # FIXME: Bug in pyzmq/zmq? + # assert self.reply_socket.rcvmore(), "Missing message part." + msg = self.reply_socket.recv_json() + omsg = Message(msg) + io.rprint('\n') + io.rprint(omsg) + handler = self.handlers.get(omsg.msg_type, None) + if handler is None: + io.rprinte("UNKNOWN MESSAGE TYPE:", omsg) + else: + handler(ident, omsg) def start(self): """ Start the kernel main loop. """ while True: - ident = self.reply_socket.recv() - assert self.reply_socket.rcvmore(), "Missing message part." - msg = self.reply_socket.recv_json() - omsg = Message(msg) - io.rprint('\n', omsg) - handler = self.handlers.get(omsg.msg_type, None) - if handler is None: - io.rprinte("UNKNOWN MESSAGE TYPE:", omsg) - else: - handler(ident, omsg) + time.sleep(0.05) + self.do_one_iteration() + #--------------------------------------------------------------------------- # Kernel request handlers @@ -331,6 +294,82 @@ class Kernel(Configurable): return symbol, [] + +class QtKernel(Kernel): + """A Kernel subclass with Qt support.""" + + def start(self): + """Start a kernel with QtPy4 event loop integration.""" + + from PyQt4 import QtGui, QtCore + self.app = QtGui.QApplication([]) + self.app.setQuitOnLastWindowClosed (False) + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.do_one_iteration) + self.timer.start(50) + self.app.exec_() + + +class WxKernel(Kernel): + """A Kernel subclass with Wx support.""" + + def start(self): + """Start a kernel with wx event loop support.""" + + import wx + doi = self.do_one_iteration + + # 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) + self.timer.Start(50) + 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. + self.app = IPWxApp(redirect=False) + self.app.MainLoop() + + +class TkKernel(Kernel): + """A Kernel subclass with Tk support.""" + + def start(self): + """Start a Tk enabled event loop.""" + + import Tkinter + doi = self.do_one_iteration + + # 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(50, self.on_timer) + def start(self): + self.on_timer() # Call it once to get things going. + self.app.mainloop() + + self.timer = Timer(doi) + self.timer.start() + #----------------------------------------------------------------------------- # Kernel main and launch functions #----------------------------------------------------------------------------- @@ -387,13 +426,30 @@ given, the GUI backend is matplotlib's, otherwise use one of: \ ['tk', 'gtk', 'qt', 'wx', 'payload-svg'].") namespace = parser.parse_args() - kernel = make_kernel(namespace, Kernel, OutStream) + kernel_class = Kernel + + _kernel_classes = { + 'qt' : QtKernel, + 'qt4' : QtKernel, + 'payload-svg':Kernel, + 'wx' : WxKernel, + 'tk' : TkKernel + } if namespace.pylab: if namespace.pylab == 'auto': - kernel.activate_pylab() + gui, backend = pylabtools.find_gui_and_backend() else: - kernel.activate_pylab(namespace.pylab) - + gui, backend = pylabtools.find_gui_and_backend(namespace.pylab) + kernel_class = _kernel_classes.get(gui) + if kernel_class is None: + raise ValueError('GUI is not supported: %r' % gui) + pylabtools.activate_matplotlib(backend) + + kernel = make_kernel(namespace, kernel_class, OutStream) + + if namespace.pylab: + pylabtools.import_pylab(kernel.shell.user_ns) + start_kernel(namespace, kernel) if __name__ == '__main__': diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py index 0e2c1a8..b87767e 100644 --- a/IPython/zmq/zmqshell.py +++ b/IPython/zmq/zmqshell.py @@ -15,7 +15,7 @@ from IPython.utils.traitlets import Instance, Type, Dict from IPython.utils.warn import warn from IPython.zmq.session import extract_header from IPython.core.payloadpage import install_payload_page - +from session import Session # Install the payload version of page. install_payload_page() @@ -23,7 +23,7 @@ install_payload_page() class ZMQDisplayHook(DisplayHook): - session = Instance('IPython.zmq.session.Session') + session = Instance(Session) pub_socket = Instance('zmq.Socket') parent_header = Dict({})