From f4d0cb7dd4141d59e008ce7613d1f0dd839bcc9d 2009-12-31 09:55:07 From: Fernando Perez Date: 2009-12-31 09:55:07 Subject: [PATCH] First semi-complete support for -pylab and %pylab. Now the magic works, -pylab -*thread work (with one or two dashes, and alone or combined). Other minor fixes and cleanups, including initializing the exception mode correctly. --- diff --git a/IPython/core/ipapp.py b/IPython/core/ipapp.py index dae34e0..960ba3b 100755 --- a/IPython/core/ipapp.py +++ b/IPython/core/ipapp.py @@ -27,53 +27,30 @@ Notes import logging import os import sys -import warnings -from IPython.core.application import Application, BaseAppArgParseConfigLoader from IPython.core import release +from IPython.core.application import Application, BaseAppArgParseConfigLoader +from IPython.core.error import UsageError from IPython.core.iplib import InteractiveShell +from IPython.core.pylabtools import pylab_activate from IPython.config.loader import ( NoConfigDefault, Config, PyFileConfigLoader ) - from IPython.lib import inputhook - from IPython.utils.genutils import filefind, get_ipython_dir #----------------------------------------------------------------------------- # Utilities and helpers #----------------------------------------------------------------------------- - ipython_desc = """ A Python shell with automatic history (input and output), dynamic object introspection, easier configuration, command completion, access to the system shell and more. """ -def pylab_warning(): - msg = """ - -IPython's -pylab mode has been disabled until matplotlib supports this version -of IPython. This version of IPython has greatly improved GUI integration that -matplotlib will soon be able to take advantage of. This will eventually -result in greater stability and a richer API for matplotlib under IPython. -However during this transition, you will either need to use an older version -of IPython, or do the following to use matplotlib interactively:: - - import matplotlib - matplotlib.interactive(True) - matplotlib.use('wxagg') # adjust for your backend - %gui -a wx # adjust for your GUI - from matplotlib import pyplot as plt - -See the %gui magic for information on the new interface. -""" - warnings.warn(msg, category=DeprecationWarning, stacklevel=1) - - #----------------------------------------------------------------------------- # Main classes and functions #----------------------------------------------------------------------------- @@ -267,23 +244,21 @@ cl_args = ( action='store_true', dest='Global.force_interact', default=NoConfigDefault, help="If running code from the command line, become interactive afterwards.") ), - (('--wthread',), dict( + (('--wthread','-wthread'), dict( action='store_true', dest='Global.wthread', default=NoConfigDefault, help="Enable wxPython event loop integration.") ), - (('--q4thread','--qthread'), dict( + (('--q4thread','--qthread','-q4thread','-qthread'), dict( action='store_true', dest='Global.q4thread', default=NoConfigDefault, help="Enable Qt4 event loop integration. Qt3 is no longer supported.") ), - (('--gthread',), dict( + (('--gthread','-gthread'), dict( action='store_true', dest='Global.gthread', default=NoConfigDefault, help="Enable GTK event loop integration.") ), - # # These are only here to get the proper deprecation warnings (('--pylab',), dict( action='store_true', dest='Global.pylab', default=NoConfigDefault, - help="Disabled. Pylab has been disabled until matplotlib " - "supports this version of IPython.") + help="Pre-load matplotlib and numpy for interactive use.") ) ) @@ -341,6 +316,9 @@ class IPythonApp(Application): Global.q4thread = False Global.gthread = False + # Pylab off by default + Global.pylab = False + def create_command_line_config(self): """Create and return a command line config loader.""" return IPythonAppCLConfigLoader( @@ -348,14 +326,6 @@ class IPythonApp(Application): version=release.version ) - def post_load_command_line_config(self): - """Do actions after loading cl config.""" - clc = self.command_line_config - - # Display the deprecation warnings about threaded shells - if hasattr(clc.Global, 'pylab'): - pylab_warning() - del clc.Global['pylab'] def load_file_config(self): if hasattr(self.command_line_config.Global, 'quick'): @@ -433,29 +403,43 @@ class IPythonApp(Application): if self.log_level <= logging.INFO: print # Now a variety of things that happen after the banner is printed. - self._enable_gui() + self._enable_gui_pylab() self._load_extensions() self._run_exec_lines() self._run_exec_files() self._run_cmd_line_code() + self._configure_xmode() + + def _enable_gui_pylab(self): + """Enable GUI event loop integration, taking pylab into account.""" + Global = self.master_config.Global + + # Select which gui to use + if Global.wthread: + gui = inputhook.GUI_WX + elif Global.q4thread: + gui = inputhook.GUI_QT + elif Global.gthread: + gui = inputhook.GUI_GTK + else: + gui = None + + if Global.pylab: + activate = self.shell.enable_pylab + else: + # Enable only GUI integration, no pylab + activate = inputhook.enable_gui + + if gui or Global.pylab: + try: + m = "Enabling GUI event loop integration, toolkit=%s, pylab=%s"\ + % (gui, Global.pylab) + self.log.info(m) + activate(gui) + except: + self.log.warn("Error in enabling GUI event loop integration:") + self.shell.showtraceback() - def _enable_gui(self): - """Enable GUI event loop integration.""" - config = self.master_config - try: - # Enable GUI integration - if config.Global.wthread: - self.log.info("Enabling wx GUI event loop integration") - inputhook.enable_wx(app=True) - elif config.Global.q4thread: - self.log.info("Enabling Qt4 GUI event loop integration") - inputhook.enable_qt4(app=True) - elif config.Global.gthread: - self.log.info("Enabling GTK GUI event loop integration") - inputhook.enable_gtk(app=True) - except: - self.log.warn("Error in enabling GUI event loop integration:") - self.shell.showtraceback() def _load_extensions(self): """Load all IPython extensions in Global.extensions. @@ -540,12 +524,18 @@ class IPythonApp(Application): self.log.warn("Error in executing file in user namespace: %s" % fname) self.shell.showtraceback() + def _configure_xmode(self): + # XXX - shouldn't this be read from the config? I'm still a little + # lost with all the details of handling the new config guys... + self.shell.InteractiveTB.set_mode(mode=self.shell.xmode) + def start_app(self): if self.master_config.Global.interact: self.log.debug("Starting IPython's mainloop...") self.shell.mainloop() + def load_default_config(ipython_dir=None): """Load the default config file from the default ipython_dir. diff --git a/IPython/core/iplib.py b/IPython/core/iplib.py index de12b2d..2e8f190 100644 --- a/IPython/core/iplib.py +++ b/IPython/core/iplib.py @@ -31,34 +31,37 @@ import sys import tempfile from contextlib import nested -from IPython.core import ultratb from IPython.core import debugger, oinspect -from IPython.core import shadowns from IPython.core import history as ipcorehist from IPython.core import prefilter +from IPython.core import shadowns +from IPython.core import ultratb from IPython.core.alias import AliasManager from IPython.core.builtin_trap import BuiltinTrap +from IPython.core.component import Component from IPython.core.display_trap import DisplayTrap +from IPython.core.error import TryNext, UsageError from IPython.core.fakemodule import FakeModule, init_fakemod_dict from IPython.core.logger import Logger from IPython.core.magic import Magic -from IPython.core.prompts import CachedOutput from IPython.core.prefilter import PrefilterManager -from IPython.core.component import Component +from IPython.core.prompts import CachedOutput +from IPython.core.pylabtools import pylab_activate from IPython.core.usage import interactive_usage, default_banner -from IPython.core.error import TryNext, UsageError - -from IPython.utils import pickleshare from IPython.external.Itpl import ItplNS +from IPython.lib.inputhook import enable_gui from IPython.lib.backgroundjobs import BackgroundJobManager -from IPython.utils.ipstruct import Struct from IPython.utils import PyColorize -from IPython.utils.genutils import * +from IPython.utils import pickleshare from IPython.utils.genutils import get_ipython_dir +from IPython.utils.ipstruct import Struct from IPython.utils.platutils import toggle_set_term_title, set_term_title from IPython.utils.strdispatch import StrDispatch from IPython.utils.syspathcontext import prepended_to_syspath +# XXX - need to clean up this import * line +from IPython.utils.genutils import * + # from IPython.utils import growl # growl.start("IPython") @@ -70,7 +73,6 @@ from IPython.utils.traitlets import ( # Globals #----------------------------------------------------------------------------- - # store the builtin raw_input globally, and use this always, in case user code # overwrites it (like wx.py.PyShell does) raw_input_original = raw_input @@ -78,12 +80,10 @@ raw_input_original = raw_input # compiled regexps for autoindent management dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass') - #----------------------------------------------------------------------------- # Utilities #----------------------------------------------------------------------------- - ini_spaces_re = re.compile(r'^(\s+)') @@ -2445,6 +2445,18 @@ class InteractiveShell(Component, Magic): return ask_yes_no(prompt,default) #------------------------------------------------------------------------- + # Things related to GUI support and pylab + #------------------------------------------------------------------------- + + def enable_pylab(self, gui=None): + """ + """ + gui = pylab_activate(self.user_ns, gui) + enable_gui(gui) + self.magic_run = self._pylab_magic_run + + + #------------------------------------------------------------------------- # Things related to IPython exiting #------------------------------------------------------------------------- diff --git a/IPython/core/magic.py b/IPython/core/magic.py index 21009e8..1f64083 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -44,21 +44,26 @@ except ImportError: # Homebrewed import IPython -from IPython.utils import wildcard +import IPython.utils.generics + from IPython.core import debugger, oinspect from IPython.core.error import TryNext +from IPython.core.error import UsageError from IPython.core.fakemodule import FakeModule +from IPython.core.macro import Macro +from IPython.core.page import page from IPython.core.prefilter import ESC_MAGIC +from IPython.core.pylabtools import mpl_runner +from IPython.lib.inputhook import enable_gui from IPython.external.Itpl import Itpl, itpl, printpl,itplns +from IPython.testing import decorators as testdec +from IPython.utils import platutils +from IPython.utils import wildcard from IPython.utils.PyColorize import Parser from IPython.utils.ipstruct import Struct -from IPython.core.macro import Macro + +# XXX - We need to switch to explicit imports here with genutils from IPython.utils.genutils import * -from IPython.core.page import page -from IPython.utils import platutils -import IPython.utils.generics -from IPython.core.error import UsageError -from IPython.testing import decorators as testdec #*************************************************************************** # Utility functions @@ -82,130 +87,6 @@ def compress_dhist(dh): return newhead + tail -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. - """ - - # Initialize matplotlib to interactive mode always - import matplotlib - - # If user specifies a GUI, that dictates the backend, otherwise we read the - # user's mpl default from the mpl rc structure - g2b = {'tk': 'TkAgg', - 'gtk': 'GTKAgg', - 'wx': 'WXAgg', - 'qt': 'Qt4Agg', # qt3 not supported - 'qt4': 'Qt4Agg' } - - if gui: - # select backend based on requested gui - backend = g2b[gui] - else: - backend = matplotlib.rcParams['backend'] - # In this case, we need to find what the appropriate gui selection call - # should be for IPython, so we can activate inputhook accordingly - b2g = dict(zip(g2b.values(),g2b.keys())) - gui = b2g[backend] - - # 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 - - # XXX For now leave this commented out, but depending on discussions with - # mpl-dev, we may be able to allow interactive switching... - #import matplotlib.pyplot - #matplotlib.pyplot.switch_backend(backend) - - pylab.show._needmain = False - # We need to detect at runtime whether show() is called by the user. - # For this, we wrap it into a decorator which adds a 'called' flag. - pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive) - - # 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 numpy as np\n" - "import matplotlib\n" - "from matplotlib import pylab, mlab, pyplot as plt\n" - ) in user_ns - - if import_all: - exec("from matplotlib.pylab import *\n" - "from numpy import *\n") in user_ns - - matplotlib.interactive(True) - - print """ -Welcome to pylab, a matplotlib-based Python environment. -Backend in use: %s -For more information, type 'help(pylab)'.""" % backend - - return gui - -# We need a little factory function here to create the closure where -# safe_execfile can live. -def mpl_runner(safe_execfile): - """Factory to return a matplotlib-enabled runner for %run. - - Parameters - ---------- - safe_execfile : function - This must be a function with the same interface as the - :meth:`safe_execfile` method of IPython. - - Returns - ------- - A function suitable for use as the ``runner`` argument of the %run magic - function. - """ - - def mpl_execfile(fname,*where,**kw): - """matplotlib-aware wrapper around safe_execfile. - - Its interface is identical to that of the :func:`execfile` builtin. - - This is ultimately a call to execfile(), but wrapped in safeties to - properly handle interactive rendering.""" - - import matplotlib - import matplotlib.pylab as pylab - - #print '*** Matplotlib runner ***' # dbg - # turn off rendering until end of script - is_interactive = matplotlib.rcParams['interactive'] - matplotlib.interactive(False) - safe_execfile(fname,*where,**kw) - matplotlib.interactive(is_interactive) - # make rendering call now, if the user tried to do it - if pylab.draw_if_interactive.called: - pylab.draw() - pylab.draw_if_interactive.called = False - - return mpl_execfile - - #*************************************************************************** # Main class implementing Magic functionality @@ -3568,7 +3449,7 @@ Defaulting color scheme to 'NoColor'""" oc.prompt_out.pad_left = False shell.pprint = False - + shell.magic_xmode('Plain') else: @@ -3624,26 +3505,9 @@ Defaulting color scheme to 'NoColor'""" This is highly recommended for most users. """ - from IPython.lib import inputhook - opts, arg = self.parse_options(parameter_s,'a') - if not arg: - inputhook.clear_inputhook() - return - - guis = {'tk': inputhook.enable_tk, - 'gtk':inputhook.enable_gtk, - 'wx': inputhook.enable_wx, - 'qt': inputhook.enable_qt4, # qt3 not supported - 'qt4': inputhook.enable_qt4 } - try: - gui = guis[arg] - except KeyError: - e="Invalid GUI request %r, valid ones are:%s" % (arg, guis.keys()) - raise UsageError(e) - - #print 'Switching IPython gui support to:', arg, 'a' in opts # dbg - return gui('a' in opts) + if arg=='': arg = None + return enable_gui(arg, 'a' in opts) def magic_load_ext(self, module_str): """Load an IPython extension by its module name.""" @@ -3757,9 +3621,6 @@ Defaulting color scheme to 'NoColor'""" Backend in use: Qt4Agg For more information, type 'help(pylab)'. """ - - gui = pylab_activate(self.shell.user_ns, s) - self.shell.magic_gui('-a %s' % gui) - self.shell.magic_run = self._pylab_magic_run + self.shell.enable_pylab(s) # end Magic diff --git a/IPython/core/pylabtools.py b/IPython/core/pylabtools.py new file mode 100644 index 0000000..e4423af --- /dev/null +++ b/IPython/core/pylabtools.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +"""Pylab (matplotlib) support utilities. + +Authors +------- +Fernando Perez. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2009 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 +#----------------------------------------------------------------------------- +from IPython.utils.genutils 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. + + 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. + """ + + # Initialize matplotlib to interactive mode always + import matplotlib + + # If user specifies a GUI, that dictates the backend, otherwise we read the + # user's mpl default from the mpl rc structure + g2b = {'tk': 'TkAgg', + 'gtk': 'GTKAgg', + 'wx': 'WXAgg', + 'qt': 'Qt4Agg', # qt3 not supported + 'qt4': 'Qt4Agg' } + + if gui: + # select backend based on requested gui + backend = g2b[gui] + else: + backend = matplotlib.rcParams['backend'] + # In this case, we need to find what the appropriate gui selection call + # should be for IPython, so we can activate inputhook accordingly + b2g = dict(zip(g2b.values(),g2b.keys())) + gui = b2g[backend] + + # 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 + + # XXX For now leave this commented out, but depending on discussions with + # mpl-dev, we may be able to allow interactive switching... + #import matplotlib.pyplot + #matplotlib.pyplot.switch_backend(backend) + + pylab.show._needmain = False + # We need to detect at runtime whether show() is called by the user. + # For this, we wrap it into a decorator which adds a 'called' flag. + pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive) + + # 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 user_ns + + if import_all: + exec("from matplotlib.pylab import *\n" + "from numpy import *\n") in user_ns + + matplotlib.interactive(True) + + print """ +Welcome to pylab, a matplotlib-based Python environment. +Backend in use: %s +For more information, type 'help(pylab)'.""" % backend + + return gui + +# We need a little factory function here to create the closure where +# safe_execfile can live. +def mpl_runner(safe_execfile): + """Factory to return a matplotlib-enabled runner for %run. + + Parameters + ---------- + safe_execfile : function + This must be a function with the same interface as the + :meth:`safe_execfile` method of IPython. + + Returns + ------- + A function suitable for use as the ``runner`` argument of the %run magic + function. + """ + + def mpl_execfile(fname,*where,**kw): + """matplotlib-aware wrapper around safe_execfile. + + Its interface is identical to that of the :func:`execfile` builtin. + + This is ultimately a call to execfile(), but wrapped in safeties to + properly handle interactive rendering.""" + + import matplotlib + import matplotlib.pylab as pylab + + #print '*** Matplotlib runner ***' # dbg + # turn off rendering until end of script + is_interactive = matplotlib.rcParams['interactive'] + matplotlib.interactive(False) + safe_execfile(fname,*where,**kw) + matplotlib.interactive(is_interactive) + # make rendering call now, if the user tried to do it + if pylab.draw_if_interactive.called: + pylab.draw() + pylab.draw_if_interactive.called = False + + return mpl_execfile diff --git a/IPython/lib/inputhook.py b/IPython/lib/inputhook.py index 0043379..cec5431 100755 --- a/IPython/lib/inputhook.py +++ b/IPython/lib/inputhook.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# encoding: utf-8 +# coding: utf-8 """ Inputhook management for GUI event loop integration. """ @@ -24,6 +24,7 @@ import sys # Constants for identifying the GUI toolkits. GUI_WX = 'wx' +GUI_QT = 'qt' GUI_QT4 = 'qt4' GUI_GTK = 'gtk' GUI_TK = 'tk' @@ -326,8 +327,17 @@ class InputHookManager(object): self._installed = True return original - def clear_inputhook(self): - """Set PyOS_InputHook to NULL and return the previous one.""" + def clear_inputhook(self, app=None): + """Set PyOS_InputHook to NULL and return the previous one. + + Parameters + ---------- + app : optional, ignored + This parameter is allowed only so that clear_inputhook() can be + called with a similar interface as all the ``enable_*`` methods. But + the actual value of the parameter is ignored. This uniform interface + makes it easier to have user-level entry points in the main IPython + app like :meth:`enable_gui`.""" pyos_inputhook_ptr = self.get_pyos_inputhook() original = self.get_pyos_inputhook_as_func() pyos_inputhook_ptr.value = ctypes.c_void_p(None).value @@ -523,3 +533,39 @@ set_inputhook = inputhook_manager.set_inputhook current_gui = inputhook_manager.current_gui clear_app_refs = inputhook_manager.clear_app_refs spin = inputhook_manager.spin + + +# Convenience function to switch amongst them +def enable_gui(gui=None, app=True): + """Switch amongst GUI input hooks by name. + + This is just a utility wrapper around the methods of the InputHookManager + object. + + Parameters + ---------- + gui : optional, string or None + If None, clears input hook, otherwise it must be one of the recognized + GUI names (see ``GUI_*`` constants in module). + + app : optional, bool + If true, create an app object and return it. + + Returns + ------- + The output of the underlying gui switch routine, typically the actual + PyOS_InputHook wrapper object or the GUI toolkit app created, if there was + one. + """ + guis = {None: clear_inputhook, + GUI_TK: enable_tk, + GUI_GTK: enable_gtk, + GUI_WX: enable_wx, + GUI_QT: enable_qt4, # qt3 not supported + GUI_QT4: enable_qt4 } + try: + gui_hook = guis[gui] + except KeyError: + e="Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys()) + raise ValueError(e) + return gui_hook(app)