From 1bba73950fa01cee6bf534730926c33992eca588 2011-01-24 05:29:41 From: Brian Granger Date: 2011-01-24 05:29:41 Subject: [PATCH] Lots of work on the display system, focused on pylab stuff. * Moved the pprint attribute to PlainTextFormatter. * Added DisplayFormatter.plain_text_only for disabling all but the plain text formatting. This is now used by doctest_mode. * Remove result_display hook. * Fixed doctest_mode magic. * Refactored inline matplotlib backend to use new display system. * Added display formatter for matplotlib.figure.Figure objects. so the display system will show the svg for them. * Added display and display_png to the user's namespace in pylab. * Cleaned up pylabtools.py --- diff --git a/IPython/config/default/ipython_config.py b/IPython/config/default/ipython_config.py index e44d6a1..a753d6f 100644 --- a/IPython/config/default/ipython_config.py +++ b/IPython/config/default/ipython_config.py @@ -81,8 +81,6 @@ c = get_config() # c.InteractiveShell.pdb = False -# c.InteractiveShell.pprint = True - # c.InteractiveShell.prompt_in1 = 'In [\#]: ' # c.InteractiveShell.prompt_in2 = ' .\D.: ' # c.InteractiveShell.prompt_out = 'Out[\#]: ' @@ -129,6 +127,12 @@ c = get_config() # c.InteractiveShell.xmode = 'Context' #----------------------------------------------------------------------------- +# Formatter and display options +#----------------------------------------------------------------------------- + +# c.PlainTextFormatter.pprint = True + +#----------------------------------------------------------------------------- # PrefilterManager options #----------------------------------------------------------------------------- diff --git a/IPython/core/formatters.py b/IPython/core/formatters.py index 1df7eb6..e823665 100644 --- a/IPython/core/formatters.py +++ b/IPython/core/formatters.py @@ -37,6 +37,9 @@ from IPython.utils.traitlets import Bool, Dict, Int, Str class DisplayFormatter(Configurable): + # When set to true only the default plain text formatter will be used. + plain_text_only = Bool(False, config=True) + # A dict of formatter whose keys are format types (MIME types) and whose # values are subclasses of BaseFormatter. formatters = Dict(config=True) @@ -93,6 +96,19 @@ class DisplayFormatter(Configurable): except for those included in this argument. """ format_dict = {} + + # If plain text only is active + if self.plain_text_only: + formatter = self.formatters['text/plain'] + try: + data = formatter(obj) + except: + # FIXME: log the exception + raise + if data is not None: + format_dict['text/plain'] = data + return format_dict + for format_type, formatter in self.formatters.items(): if include is not None: if format_type not in include: @@ -133,6 +149,9 @@ class FormatterABC(object): # The format type of the data returned, usually a MIME type. format_type = 'text/plain' + # Is the formatter enabled... + enabled = True + @abc.abstractmethod def __call__(self, obj): """Return a JSON'able representation of the object. @@ -171,6 +190,8 @@ class BaseFormatter(Configurable): format_type = Str('text/plain') + enabled = Bool(True, config=True) + print_method = Str('__repr__') # The singleton printers. @@ -193,28 +214,31 @@ class BaseFormatter(Configurable): def __call__(self, obj): """Compute the format for an object.""" - obj_id = id(obj) - try: - obj_class = getattr(obj, '__class__', None) or type(obj) - if hasattr(obj_class, self.print_method): - printer = getattr(obj_class, self.print_method) - return printer(obj) + if self.enabled: + obj_id = id(obj) try: - printer = self.singleton_printers[obj_id] - except (TypeError, KeyError): - pass - else: - return printer(obj) - for cls in pretty._get_mro(obj_class): - if cls in self.type_printers: - return self.type_printers[cls](obj) + obj_class = getattr(obj, '__class__', None) or type(obj) + if hasattr(obj_class, self.print_method): + printer = getattr(obj_class, self.print_method) + return printer(obj) + try: + printer = self.singleton_printers[obj_id] + except (TypeError, KeyError): + pass else: - printer = self._in_deferred_types(cls) - if printer is not None: - return printer(obj) + return printer(obj) + for cls in pretty._get_mro(obj_class): + if cls in self.type_printers: + return self.type_printers[cls](obj) + else: + printer = self._in_deferred_types(cls) + if printer is not None: + return printer(obj) + return None + except Exception: + pass + else: return None - except Exception: - pass def for_type(self, typ, func): """Add a format function for a given type. @@ -281,6 +305,7 @@ class BaseFormatter(Configurable): self.type_printers[cls] = printer return printer + class PlainTextFormatter(BaseFormatter): """The default pretty-printer. @@ -308,6 +333,10 @@ class PlainTextFormatter(BaseFormatter): # The format type of data returned. format_type = Str('text/plain') + # This subclass ignores this attribute as it always need to return + # something. + enabled = Bool(True, config=False) + # Look for a __pretty__ methods to use for pretty printing. print_method = Str('__pretty__') diff --git a/IPython/core/hooks.py b/IPython/core/hooks.py index 5ea055b..712342f 100644 --- a/IPython/core/hooks.py +++ b/IPython/core/hooks.py @@ -156,33 +156,6 @@ class CommandChainDispatcher: return iter(self.chain) -def result_display(self,arg): - """ Default display hook. - - Called for displaying the result to the user. - """ - - if self.pprint: - try: - out = pformat(arg) - except: - # Work around possible bugs in pformat - out = repr(arg) - if '\n' in out: - # So that multi-line strings line up with the left column of - # the screen, instead of having the output prompt mess up - # their first line. - IPython.utils.io.Term.cout.write('\n') - print >>IPython.utils.io.Term.cout, out - else: - # By default, the interactive prompt uses repr() to display results, - # so we should honor this. Users who'd rather use a different - # mechanism can easily override this hook. - print >>IPython.utils.io.Term.cout, repr(arg) - # the default display hook doesn't manipulate the value to put in history - return None - - def input_prefilter(self,line): """ Default input prefilter diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 42e417c..16cf17e 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -172,7 +172,6 @@ class InteractiveShell(Configurable, Magic): config=True) pdb = CBool(False, config=True) - pprint = CBool(True, config=True) profile = Str('', config=True) prompt_in1 = Str('In [\\#]: ', config=True) prompt_in2 = Str(' .\\D.: ', config=True) diff --git a/IPython/core/magic.py b/IPython/core/magic.py index 531c9ed..12df58c 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -2424,12 +2424,12 @@ Defaulting color scheme to 'NoColor'""" else: shell.inspector.set_active_scheme('NoColor') - def magic_Pprint(self, parameter_s=''): + def magic_pprint(self, parameter_s=''): """Toggle pretty printing on/off.""" - - self.shell.pprint = 1 - self.shell.pprint + ptformatter = self.shell.display_formatter.formatters['text/plain'] + ptformatter.pprint = bool(1 - ptformatter.pprint) print 'Pretty printing has been turned', \ - ['OFF','ON'][self.shell.pprint] + ['OFF','ON'][ptformatter.pprint] def magic_Exit(self, parameter_s=''): """Exit IPython.""" @@ -3163,6 +3163,8 @@ Defaulting color scheme to 'NoColor'""" shell = self.shell oc = shell.displayhook meta = shell.meta + disp_formatter = self.shell.display_formatter + ptformatter = disp_formatter.formatters['text/plain'] # dstore is a data store kept in the instance metadata bag to track any # changes we make, so we can undo them later. dstore = meta.setdefault('doctest_mode',Struct()) @@ -3170,12 +3172,13 @@ Defaulting color scheme to 'NoColor'""" # save a few values we'll need to recover later mode = save_dstore('mode',False) - save_dstore('rc_pprint',shell.pprint) + save_dstore('rc_pprint',ptformatter.pprint) save_dstore('xmode',shell.InteractiveTB.mode) save_dstore('rc_separate_out',shell.separate_out) save_dstore('rc_separate_out2',shell.separate_out2) save_dstore('rc_prompts_pad_left',shell.prompts_pad_left) save_dstore('rc_separate_in',shell.separate_in) + save_dstore('rc_plain_text_only',disp_formatter.plain_text_only) if mode == False: # turn on @@ -3191,7 +3194,8 @@ Defaulting color scheme to 'NoColor'""" oc.prompt1.pad_left = oc.prompt2.pad_left = \ oc.prompt_out.pad_left = False - shell.pprint = False + ptformatter.pprint = False + disp_formatter.plain_text_only = True shell.magic_xmode('Plain') else: @@ -3208,7 +3212,8 @@ Defaulting color scheme to 'NoColor'""" oc.prompt1.pad_left = oc.prompt2.pad_left = \ oc.prompt_out.pad_left = dstore.rc_prompts_pad_left - shell.pprint = dstore.rc_pprint + ptformatter.pprint = dstore.rc_pprint + disp_formatter.plain_text_only = dstore.rc_plain_text_only shell.magic_xmode(dstore.xmode) diff --git a/IPython/frontend/terminal/ipapp.py b/IPython/frontend/terminal/ipapp.py index 874a63f..1e51e43 100755 --- a/IPython/frontend/terminal/ipapp.py +++ b/IPython/frontend/terminal/ipapp.py @@ -188,10 +188,10 @@ class IPAppConfigLoader(BaseAppConfigLoader): action='store_false', dest='InteractiveShell.pdb', help="Disable auto calling the pdb debugger after every exception.") paa('--pprint', - action='store_true', dest='InteractiveShell.pprint', + action='store_true', dest='PlainTextFormatter.pprint', help="Enable auto pretty printing of results.") paa('--no-pprint', - action='store_false', dest='InteractiveShell.pprint', + action='store_false', dest='PlainTextFormatter.pprint', help="Disable auto auto pretty printing of results.") paa('--prompt-in1','-pi1', type=str, dest='InteractiveShell.prompt_in1', @@ -443,7 +443,7 @@ class IPythonApp(Application): if hasattr(config.Global, 'classic'): if config.Global.classic: config.InteractiveShell.cache_size = 0 - config.InteractiveShell.pprint = 0 + config.PlainTextFormatter.pprint = 0 config.InteractiveShell.prompt_in1 = '>>> ' config.InteractiveShell.prompt_in2 = '... ' config.InteractiveShell.prompt_out = '' diff --git a/IPython/lib/pylabtools.py b/IPython/lib/pylabtools.py index 5a13a19..41894c9 100644 --- a/IPython/lib/pylabtools.py +++ b/IPython/lib/pylabtools.py @@ -19,6 +19,8 @@ Authors # Imports #----------------------------------------------------------------------------- +from cStringIO import StringIO + from IPython.utils.decorators import flag_calls # If user specifies a GUI, that dictates the backend, otherwise we read the @@ -31,7 +33,80 @@ backends = {'tk': 'TkAgg', 'inline' : 'module://IPython.zmq.pylab.backend_inline'} #----------------------------------------------------------------------------- -# Main classes and functions +# Matplotlib utilities +#----------------------------------------------------------------------------- + +def figsize(sizex, sizey): + """Set the default figure size to be [sizex, sizey]. + + This is just an easy to remember, convenience wrapper that sets:: + + matplotlib.rcParams['figure.figsize'] = [sizex, sizey] + """ + import matplotlib + matplotlib.rcParams['figure.figsize'] = [sizex, sizey] + + +def figure_to_svg(fig): + """Convert a figure to svg for inline display.""" + fc = fig.get_facecolor() + ec = fig.get_edgecolor() + fig.set_facecolor('white') + fig.set_edgecolor('white') + try: + string_io = StringIO() + fig.canvas.print_figure(string_io, format='svg') + svg = string_io.getvalue() + finally: + fig.set_facecolor(fc) + fig.set_edgecolor(ec) + return svg + + +# 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 + + +#----------------------------------------------------------------------------- +# Code for initializing matplotlib and importing pylab #----------------------------------------------------------------------------- @@ -111,7 +186,7 @@ def import_pylab(user_ns, backend, import_all=True, shell=None): # function that will pick up the results for display. This can only be # done with access to the real shell object. if backend == backends['inline']: - from IPython.zmq.pylab.backend_inline import flush_svg, figsize + from IPython.zmq.pylab.backend_inline import flush_svg from matplotlib import pyplot shell.register_post_execute(flush_svg) # The typical default figure size is too large for inline use. We @@ -120,11 +195,20 @@ def import_pylab(user_ns, backend, import_all=True, shell=None): # Add 'figsize' to pyplot and to the user's namespace user_ns['figsize'] = pyplot.figsize = figsize shell.user_ns_hidden['figsize'] = figsize - else: - from IPython.zmq.pylab.backend_inline import pastefig - from matplotlib import pyplot - # Add 'paste' to pyplot and to the user's namespace - user_ns['pastefig'] = pyplot.pastefig = pastefig + + # The old pastefig function has been replaced by display + # Always add this svg formatter so display works. + from IPython.zmq.pylab.backend_inline import figure_to_svg + from IPython.core.display import display, display_svg + svg_formatter = shell.display_formatter.formatters['image/svg+xml'] + svg_formatter.for_type_by_name( + 'matplotlib.figure','Figure',figure_to_svg + ) + # Add display and display_png to the user's namespace + user_ns['display'] = display + shell.user_ns_hidden['display'] = display + user_ns['display_svg'] = display_svg + shell.user_ns_hidden['display_svg'] = display_svg if import_all: s = ("from matplotlib.pylab import *\n" @@ -165,44 +249,3 @@ 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/utils/generics.py b/IPython/utils/generics.py index 4fcc32b..a482d49 100644 --- a/IPython/utils/generics.py +++ b/IPython/utils/generics.py @@ -2,15 +2,6 @@ """Generic functions for extending IPython. See http://cheeseshop.python.org/pypi/simplegeneric. - -Here is an example from IPython.utils.text:: - - def print_lsstring(arg): - "Prettier (non-repr-like) and more informative printer for LSString" - print "LSString (.p, .n, .l, .s available). Value:" - print arg - - print_lsstring = result_display.when_type(LSString)(print_lsstring) """ #----------------------------------------------------------------------------- diff --git a/IPython/utils/ipstruct.py b/IPython/utils/ipstruct.py index b5fe613..e9898b1 100644 --- a/IPython/utils/ipstruct.py +++ b/IPython/utils/ipstruct.py @@ -388,8 +388,6 @@ class Struct(dict): inv_conflict_solve_user[func] = inv_conflict_solve_user[name] del inv_conflict_solve_user[name] conflict_solve.update(self.__dict_invert(inv_conflict_solve_user)) - #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg - #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve) for key in data_dict: if key not in self: self[key] = data_dict[key] diff --git a/IPython/zmq/pylab/backend_inline.py b/IPython/zmq/pylab/backend_inline.py index aaa9a8e..8a42357 100644 --- a/IPython/zmq/pylab/backend_inline.py +++ b/IPython/zmq/pylab/backend_inline.py @@ -6,103 +6,39 @@ from __future__ import print_function # Standard library imports -from cStringIO import StringIO -# System library imports. import matplotlib from matplotlib.backends.backend_svg import new_figure_manager from matplotlib._pylab_helpers import Gcf # Local imports. from IPython.core.displaypub import publish_display_data +from IPython.lib.pylabtools import figure_to_svg #----------------------------------------------------------------------------- # Functions #----------------------------------------------------------------------------- -def show(close=True): +def show(close=False): """Show all figures as SVG payloads sent to the IPython clients. Parameters ---------- close : bool, optional If true, a ``plt.close('all')`` call is automatically issued after - sending all the SVG figures. + sending all the SVG figures. If this is set, the figures will entirely + removed from the internal list of figures. """ for figure_manager in Gcf.get_all_fig_managers(): - send_svg_canvas(figure_manager.canvas) + send_svg_figure(figure_manager.canvas.figure) if close: matplotlib.pyplot.close('all') + # This flag will be reset by draw_if_interactive when called show._draw_called = False -def figsize(sizex, sizey): - """Set the default figure size to be [sizex, sizey]. - - This is just an easy to remember, convenience wrapper that sets:: - - matplotlib.rcParams['figure.figsize'] = [sizex, sizey] - """ - matplotlib.rcParams['figure.figsize'] = [sizex, sizey] - - -def pastefig(*figs): - """Paste one or more figures into the console workspace. - - If no arguments are given, all available figures are pasted. If the - argument list contains references to invalid figures, a warning is printed - but the function continues pasting further figures. - - Parameters - ---------- - figs : tuple - A tuple that can contain any mixture of integers and figure objects. - """ - if not figs: - show(close=False) - else: - fig_managers = Gcf.get_all_fig_managers() - fig_index = dict( [(fm.canvas.figure, fm.canvas) for fm in fig_managers] - + [ (fm.canvas.figure.number, fm.canvas) for fm in fig_managers] ) - - for fig in figs: - canvas = fig_index.get(fig) - if canvas is None: - print('Warning: figure %s not available.' % fig) - else: - send_svg_canvas(canvas) - - -def send_svg_canvas(canvas): - """Draw the current canvas and send it as an SVG payload. - """ - # Set the background to white instead so it looks good on black. We store - # the current values to restore them at the end. - fc = canvas.figure.get_facecolor() - ec = canvas.figure.get_edgecolor() - canvas.figure.set_facecolor('white') - canvas.figure.set_edgecolor('white') - try: - publish_display_data( - 'IPython.zmq.pylab.backend_inline.send_svg_canvas', - 'Matplotlib Plot', - {'image/svg+xml' : svg_from_canvas(canvas)} - ) - finally: - canvas.figure.set_facecolor(fc) - canvas.figure.set_edgecolor(ec) - - -def svg_from_canvas(canvas): - """ Return a string containing the SVG representation of a FigureCanvasSvg. - """ - string_io = StringIO() - canvas.print_figure(string_io, format='svg') - return string_io.getvalue() - - def draw_if_interactive(): """ Is called after every pylab drawing command @@ -119,5 +55,19 @@ def flush_svg(): prior code execution, there had been any calls to draw_if_interactive. """ if show._draw_called: - show(close=True) + # Show is called with the default close=False here, otherwise, the + # Figure will be closed and not available for future plotting. + show() show._draw_called = False + + +def send_svg_figure(fig): + """Draw the current figure and send it as an SVG payload. + """ + svg = figure_to_svg(fig) + publish_display_data( + 'IPython.zmq.pylab.backend_inline.send_svg_figure', + 'Matplotlib Plot', + {'image/svg+xml' : svg} + ) + diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py index 1b66539..f3cd292 100644 --- a/IPython/zmq/zmqshell.py +++ b/IPython/zmq/zmqshell.py @@ -203,6 +203,8 @@ class ZMQInteractiveShell(InteractiveShell): # Shorthands shell = self.shell + disp_formatter = self.shell.display_formatter + ptformatter = disp_formatter.formatters['text/plain'] # dstore is a data store kept in the instance metadata bag to track any # changes we make, so we can undo them later. dstore = shell.meta.setdefault('doctest_mode', Struct()) @@ -210,16 +212,19 @@ class ZMQInteractiveShell(InteractiveShell): # save a few values we'll need to recover later mode = save_dstore('mode', False) - save_dstore('rc_pprint', shell.pprint) + save_dstore('rc_pprint', ptformatter.pprint) + save_dstore('rc_plain_text_only',disp_formatter.plain_text_only) save_dstore('xmode', shell.InteractiveTB.mode) if mode == False: # turn on - shell.pprint = False + ptformatter.pprint = False + disp_formatter.plain_text_only = True shell.magic_xmode('Plain') else: # turn off - shell.pprint = dstore.rc_pprint + ptformatter.pprint = dstore.rc_pprint + disp_formatter.plain_text_only = dstore.rc_plain_text_only shell.magic_xmode(dstore.xmode) # Store new mode and inform on console diff --git a/setupbase.py b/setupbase.py index 835e87a..946f573 100644 --- a/setupbase.py +++ b/setupbase.py @@ -213,13 +213,7 @@ def find_data_files(): data_files = [ (manpagebase, manpages), (pjoin(docdirbase, 'extensions'), igridhelpfiles), ] + manual_files + example_files - - ## import pprint # dbg - ## print('*'*80) - ## print('data files') - ## pprint.pprint(data_files) - ## print('*'*80) - + return data_files