From cfd87e9b6f141655b43a3e28ae90c70d932f0124 2011-11-27 07:09:55 From: Fernando Perez Date: 2011-11-27 07:09:55 Subject: [PATCH] Refactor gui/pylab integration to eliminate code duplication. Also, fix a few tests that the previous commit broke. --- diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 7c3dc32..e88c44a 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -62,6 +62,7 @@ from IPython.core.payload import PayloadManager 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 @@ class InteractiveShell(SingletonConfigurable, Magic): # 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 diff --git a/IPython/core/magic.py b/IPython/core/magic.py index e39d289..bb28845 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -50,7 +50,7 @@ from IPython.core.profiledir import ProfileDir 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 @@ Defaulting color scheme to 'NoColor'""" 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 @@ Defaulting color scheme to 'NoColor'""" 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 @@ Defaulting color scheme to 'NoColor'""" 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. diff --git a/IPython/lib/pylabtools.py b/IPython/core/pylabtools.py similarity index 91% rename from IPython/lib/pylabtools.py rename to IPython/core/pylabtools.py index 1187d55..c48b722 100644 --- a/IPython/lib/pylabtools.py +++ b/IPython/core/pylabtools.py @@ -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,53 +247,57 @@ def import_pylab(user_ns, backend, import_all=True, shell=None): ) exec s in user_ns - if shell is not None: - # All local executions are done in a fresh namespace and we then update - # the set of 'hidden' keys so these variables don't show up in %who - # (which is meant to show only what the user has manually defined). - ns = {} - exec s in 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 - - 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 - ns['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 - ns['display'] = user_ns['display'] = display - ns['getfigs'] = user_ns['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 ns - # Update the set of hidden variables with anything we've done here. - shell.user_ns_hidden.update(ns) + +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: + 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 + + 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): @@ -318,8 +323,8 @@ 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) + configure_shell(shell, backend, user_ns) print """ Welcome to pylab, a matplotlib-based Python environment [backend: %s]. For more information, type 'help(pylab)'.""" % backend diff --git a/IPython/lib/tests/test_pylabtools.py b/IPython/core/tests/test_pylabtools.py similarity index 92% rename from IPython/lib/tests/test_pylabtools.py rename to IPython/core/tests/test_pylabtools.py index c64e01f..3c12523 100644 --- a/IPython/lib/tests/test_pylabtools.py +++ b/IPython/core/tests/test_pylabtools.py @@ -57,6 +57,7 @@ def test_figure_to_svg(): def test_import_pylab(): ip = get_ipython() - pt.import_pylab(ip.user_ns, 'inline', import_all=False, shell=ip) - nt.assert_true('plt' in ip.user_ns) - nt.assert_equal(ip.user_ns['np'], np) + ns = {} + pt.import_pylab(ns, import_all=False) + nt.assert_true('plt' in ns) + nt.assert_equal(ns['np'], np) diff --git a/IPython/frontend/terminal/interactiveshell.py b/IPython/frontend/terminal/interactiveshell.py index 7196ca7..7c37282 100644 --- a/IPython/frontend/terminal/interactiveshell.py +++ b/IPython/frontend/terminal/interactiveshell.py @@ -29,8 +29,7 @@ except: 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 @@ -520,40 +519,9 @@ class TerminalInteractiveShell(InteractiveShell): # 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 + def enable_gui(self, gui=None): + from IPython.lib.inputhook import enable_gui enable_gui(gui) - self.magic_run = self._pylab_magic_run #------------------------------------------------------------------------- # Things related to exiting diff --git a/IPython/zmq/ipkernel.py b/IPython/zmq/ipkernel.py index 453de87..5536073 100755 --- a/IPython/zmq/ipkernel.py +++ b/IPython/zmq/ipkernel.py @@ -679,10 +679,11 @@ loop_map = { 'wx' : loop_wx, 'tk' : loop_tk, 'gtk': loop_gtk, + None : None, } def enable_gui(gui, kernel=None): - """Enable integration with a give GUI""" + """Enable integration with a given GUI""" if kernel is None: kernel = IPKernelApp.instance().kernel if gui not in loop_map: diff --git a/IPython/zmq/pylab/backend_inline.py b/IPython/zmq/pylab/backend_inline.py index 7bf9b1f..e806588 100644 --- a/IPython/zmq/pylab/backend_inline.py +++ b/IPython/zmq/pylab/backend_inline.py @@ -16,7 +16,7 @@ from matplotlib._pylab_helpers import Gcf # 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 diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py index 942ea43..0c292cb 100644 --- a/IPython/zmq/zmqshell.py +++ b/IPython/zmq/zmqshell.py @@ -390,78 +390,9 @@ class ZMQInteractiveShell(InteractiveShell): } 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). - """ + def enable_gui(self, gui=None): 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 - + enable_gui(gui) # A few magics that are adapted to the specifics of using pexpect and a # remote terminal @@ -567,7 +498,6 @@ class ZMQInteractiveShell(InteractiveShell): 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 +508,5 @@ class ZMQInteractiveShell(InteractiveShell): ) self.payload_manager.write_payload(payload) + InteractiveShellABC.register(ZMQInteractiveShell)