diff --git a/.bzrignore b/.bzrignore new file mode 100644 index 0000000..c7e9832 --- /dev/null +++ b/.bzrignore @@ -0,0 +1,4 @@ +# IPython-specific files and pattersn that bzr should ignore +docs/dist +docs/build/* +docs/source/api/generated diff --git a/IPython/ColorANSI.py b/IPython/ColorANSI.py index fbf9573..783aa1d 100644 --- a/IPython/ColorANSI.py +++ b/IPython/ColorANSI.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Tools for coloring text in ANSI terminals. - -$Id: ColorANSI.py 2167 2007-03-21 06:57:50Z fperez $""" +""" #***************************************************************************** # Copyright (C) 2002-2006 Fernando Perez. @@ -10,10 +9,6 @@ $Id: ColorANSI.py 2167 2007-03-21 06:57:50Z fperez $""" # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license - __all__ = ['TermColors','InputTermColors','ColorScheme','ColorSchemeTable'] import os diff --git a/IPython/ConfigLoader.py b/IPython/ConfigLoader.py index c7a67f8..d64864d 100644 --- a/IPython/ConfigLoader.py +++ b/IPython/ConfigLoader.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Configuration loader - -$Id: ConfigLoader.py 1005 2006-01-12 08:39:26Z fperez $""" +""" #***************************************************************************** # Copyright (C) 2001-2006 Fernando Perez. @@ -10,10 +9,6 @@ $Id: ConfigLoader.py 1005 2006-01-12 08:39:26Z fperez $""" # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license - import exceptions import os from pprint import pprint @@ -73,14 +68,14 @@ class ConfigLoader: # avoid including the same file more than once if fname in self.included: return data - Xinfo = ultraTB.AutoFormattedTB() + Xinfo = ultraTB.AutoFormattedTB(color_scheme='NoColor') if convert==None and recurse_key : convert = {qwflat:recurse_key} # for production, change warn to 0: data.merge(read_dict(fname,convert,fs=self.field_sep,strip=1, warn=0,no_empty=0,**kw)) # keep track of successfully loaded files self.included.append(fname) - if recurse_key in data.keys(): + if recurse_key in data: for incfilename in data[recurse_key]: found=0 try: diff --git a/IPython/CrashHandler.py b/IPython/CrashHandler.py index 857abe5..b437cac 100644 --- a/IPython/CrashHandler.py +++ b/IPython/CrashHandler.py @@ -1,20 +1,20 @@ # -*- coding: utf-8 -*- """sys.excepthook for IPython itself, leaves a detailed report on disk. -$Id: CrashHandler.py 2908 2007-12-30 21:07:46Z vivainio $""" + +Authors +------- +- Fernando Perez +""" #***************************************************************************** -# Copyright (C) 2001-2006 Fernando Perez. +# Copyright (C) 2008-2009 The IPython Development Team +# Copyright (C) 2001-2007 Fernando Perez. # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license -__version__ = Release.version - #**************************************************************************** # Required modules @@ -23,10 +23,12 @@ import os import sys from pprint import pprint,pformat -# Homebrewed -from IPython.Itpl import Itpl,itpl,printpl -from IPython.ColorANSI import ColorScheme,ColorSchemeTable # too long names +# Our own +from IPython import Release from IPython import ultraTB +from IPython.ColorANSI import ColorScheme,ColorSchemeTable # too long names +from IPython.Itpl import Itpl,itpl,printpl + from IPython.genutils import * #**************************************************************************** @@ -166,7 +168,7 @@ $self.bug_tracker rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n') rpt_add('IPython version: %s \n\n' % Release.version) - rpt_add('SVN revision : %s \n\n' % Release.revision) + rpt_add('BZR revision : %s \n\n' % Release.revision) rpt_add('Platform info : os.name -> %s, sys.platform -> %s' % (os.name,sys.platform) ) rpt_add(sec_sep+'Current user configuration structure:\n\n') @@ -193,7 +195,7 @@ class IPythonCrashHandler(CrashHandler): # Set argument defaults app_name = 'IPython' - bug_tracker = 'http://projects.scipy.org/ipython/ipython/report' + bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug' contact_name,contact_email = Release.authors[AUTHOR_CONTACT][:2] crash_report_fname = 'IPython_crash_report.txt' # Call parent constructor @@ -210,7 +212,7 @@ class IPythonCrashHandler(CrashHandler): rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n') rpt_add('IPython version: %s \n\n' % Release.version) - rpt_add('SVN revision : %s \n\n' % Release.revision) + rpt_add('BZR revision : %s \n\n' % Release.revision) rpt_add('Platform info : os.name -> %s, sys.platform -> %s' % (os.name,sys.platform) ) rpt_add(sec_sep+'Current user configuration structure:\n\n') diff --git a/IPython/DPyGetOpt.py b/IPython/DPyGetOpt.py index dd2cf86..f4b918e 100644 --- a/IPython/DPyGetOpt.py +++ b/IPython/DPyGetOpt.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- """DPyGetOpt -- Demiurge Python GetOptions Module - $Id: DPyGetOpt.py 2872 2007-11-25 17:58:05Z fperez $ - This module is modeled after perl's Getopt::Long module-- which is, in turn, modeled after GNU's extended getopt() function. @@ -32,8 +30,7 @@ characters; ie-- 'foo|bar|baz=f@' specifies that all -foo, -bar, and -baz options that appear on within the parsed argument list must have a real number argument and that the accumulated list of values will be available under the name 'foo' - -$Id: DPyGetOpt.py 2872 2007-11-25 17:58:05Z fperez $""" +""" #***************************************************************************** # diff --git a/IPython/Debugger.py b/IPython/Debugger.py index 576395c..117b7d1 100644 --- a/IPython/Debugger.py +++ b/IPython/Debugger.py @@ -13,9 +13,7 @@ The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor changes. Licensing should therefore be under the standard Python terms. For details on the PSF (Python Software Foundation) standard license, see: -http://www.python.org/2.2.3/license.html - -$Id: Debugger.py 2913 2007-12-31 12:42:14Z vivainio $""" +http://www.python.org/2.2.3/license.html""" #***************************************************************************** # @@ -27,10 +25,6 @@ $Id: Debugger.py 2913 2007-12-31 12:42:14Z vivainio $""" # #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = 'Python' - import bdb import cmd import linecache @@ -39,7 +33,7 @@ import sys from IPython import PyColorize, ColorANSI, ipapi from IPython.genutils import Term -from IPython.excolors import ExceptionColors +from IPython.excolors import exception_colors # See if we can use pydb. has_pydb = False @@ -210,7 +204,7 @@ class Pdb(OldPdb): # Create color table: we copy the default one from the traceback # module and add a few attributes needed for debugging - self.color_scheme_table = ExceptionColors.copy() + self.color_scheme_table = exception_colors() # shorthands C = ColorANSI.TermColors @@ -257,8 +251,7 @@ class Pdb(OldPdb): # Create color table: we copy the default one from the traceback # module and add a few attributes needed for debugging - ExceptionColors.set_active_scheme(color_scheme) - self.color_scheme_table = ExceptionColors.copy() + self.color_scheme_table = exception_colors() # shorthands C = ColorANSI.TermColors diff --git a/IPython/Extensions/InterpreterExec.py b/IPython/Extensions/InterpreterExec.py index a35dceb..cca7c24 100644 --- a/IPython/Extensions/InterpreterExec.py +++ b/IPython/Extensions/InterpreterExec.py @@ -4,8 +4,7 @@ We define a special input line filter to allow typing lines which begin with '~', '/' or '.'. If one of those strings is encountered, it is automatically executed. - -$Id: InterpreterExec.py 2724 2007-09-07 08:05:38Z fperez $""" +""" #***************************************************************************** # Copyright (C) 2004 W.J. van der Laan @@ -15,11 +14,6 @@ $Id: InterpreterExec.py 2724 2007-09-07 08:05:38Z fperez $""" # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = 'W.J. van der Laan , '\ - '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license - # TODO: deprecated def prefilter_shell(self,line,continuation): """Alternate prefilter, modified for shell-like functionality. diff --git a/IPython/Extensions/InterpreterPasteInput.py b/IPython/Extensions/InterpreterPasteInput.py index bcedda2..d16c911 100644 --- a/IPython/Extensions/InterpreterPasteInput.py +++ b/IPython/Extensions/InterpreterPasteInput.py @@ -48,19 +48,21 @@ In [4]: >>> for i in range(len(a)): 2 a 3 little 4 lamb + + +Authors +------- +- Fernando Perez """ #***************************************************************************** -# Copyright (C) 2001-2006 Fernando Perez +# Copyright (C) 2008-2009 The IPython Development Team +# Copyright (C) 2001-2007 Fernando Perez # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license - # This file is an example of how to modify IPython's line-processing behavior # without touching the internal code. We'll define an alternate pre-processing # stage which allows a special form of input (which is invalid Python syntax) diff --git a/IPython/Extensions/PhysicalQInput.py b/IPython/Extensions/PhysicalQInput.py index c8d4929..04f05c0 100644 --- a/IPython/Extensions/PhysicalQInput.py +++ b/IPython/Extensions/PhysicalQInput.py @@ -12,18 +12,19 @@ g = 9.8 m/s**2 a = 2.3 m/s^2 # ^ -> ** automatically All other input is processed normally. + +Authors +------- +- Fernando Perez """ #***************************************************************************** -# Copyright (C) 2001-2004 Fernando Perez +# Copyright (C) 2008-2009 The IPython Development Team +# Copyright (C) 2001-2007 Fernando Perez # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license - # This file is an example of how to modify IPython's line-processing behavior # without touching the internal code. We'll define an alternate pre-processing # stage which allows a special form of input (which is invalid Python syntax) diff --git a/IPython/Extensions/PhysicalQInteractive.py b/IPython/Extensions/PhysicalQInteractive.py index 8d26117..45817bc 100644 --- a/IPython/Extensions/PhysicalQInteractive.py +++ b/IPython/Extensions/PhysicalQInteractive.py @@ -6,19 +6,21 @@ special method syntax. This just means moving them out to the global namespace. This module should always be loaded *after* math or Numeric, so it can -overwrite math functions with the versions that handle units.""" +overwrite math functions with the versions that handle units. + +Authors +------- +- Fernando Perez +""" #***************************************************************************** -# Copyright (C) 2001-2004 Fernando Perez +# Copyright (C) 2008-2009 The IPython Development Team +# Copyright (C) 2001-2007 Fernando Perez # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license - from Scientific.Physics.PhysicalQuantities import PhysicalQuantity # This code can be set up to work with Numeric or with math for providing the diff --git a/IPython/Extensions/clearcmd.py b/IPython/Extensions/clearcmd.py index 49fbd59..76cfbf7 100644 --- a/IPython/Extensions/clearcmd.py +++ b/IPython/Extensions/clearcmd.py @@ -5,43 +5,65 @@ import IPython.ipapi import gc ip = IPython.ipapi.get() - def clear_f(self,arg): """ Clear various data (e.g. stored history data) - %clear out - clear output history %clear in - clear input history + %clear out - clear output history %clear shadow_compress - Compresses shadow history (to speed up ipython) %clear shadow_nuke - permanently erase all entries in shadow history %clear dhist - clear dir history + %clear array - clear only variables that are NumPy arrays + + Examples: + + In [1]: clear in + Flushing input history + + In [2]: clear shadow_compress + Compressing shadow history + + In [3]: clear shadow_nuke + Erased all keys from shadow history + + In [4]: clear dhist + Clearing directory history """ api = self.getapi() + user_ns = self.user_ns # local lookup, heavily used + + for target in arg.split(): + if target == 'out': - print "Flushing output cache (%d entries)" % len(api.user_ns['_oh']) + print "Flushing output cache (%d entries)" % len(user_ns['_oh']) self.outputcache.flush() + elif target == 'in': print "Flushing input history" - from IPython import iplib pc = self.outputcache.prompt_count + 1 for n in range(1, pc): key = '_i'+`n` + user_ns.pop(key,None) try: - del self.user_ns[key] + del user_ns[key] except: pass # must be done in-place self.input_hist[:] = ['\n'] * pc self.input_hist_raw[:] = ['\n'] * pc + elif target == 'array': + # Support cleaning up numpy arrays try: - pylab=ip.IP.pylab - for x in self.user_ns.keys(): - if isinstance(self.user_ns[x],pylab.arraytype): - del self.user_ns[x] + from numpy import ndarray + # This must be done with items and not iteritems because we're + # going to modify the dict in-place. + for x,val in user_ns.items(): + if isinstance(val,ndarray): + del user_ns[x] except AttributeError: - print "Clear array only available in -pylab mode" - gc.collect() + print "Clear array only works if Numpy is available." elif target == 'shadow_compress': print "Compressing shadow history" @@ -51,16 +73,15 @@ def clear_f(self,arg): print "Erased all keys from shadow history " for k in ip.db.keys('shadowhist/*'): del ip.db[k] + elif target == 'dhist': print "Clearing directory history" - del ip.user_ns['_dh'][:] + del user_ns['_dh'][:] - + gc.collect() + +# Activate the extension ip.expose_magic("clear",clear_f) import ipy_completers ipy_completers.quick_completer( - '%clear','in out shadow_nuke shadow_compress dhist') - - - - + '%clear','in out shadow_nuke shadow_compress dhist') diff --git a/IPython/Extensions/ext_rescapture.py b/IPython/Extensions/ext_rescapture.py index eae7cb9..e02b232 100644 --- a/IPython/Extensions/ext_rescapture.py +++ b/IPython/Extensions/ext_rescapture.py @@ -6,9 +6,6 @@ Provides var = %magic blah blah var = !ls - -$Id: genutils.py 1077 2006-01-24 18:15:27Z vivainio $ - """ import IPython.ipapi diff --git a/IPython/Extensions/ipy_pretty.py b/IPython/Extensions/ipy_pretty.py new file mode 100644 index 0000000..924b7fa --- /dev/null +++ b/IPython/Extensions/ipy_pretty.py @@ -0,0 +1,132 @@ +""" Use pretty.py for configurable pretty-printing. + +Register pretty-printers for types using ipy_pretty.for_type() or +ipy_pretty.for_type_by_name(). For example, to use the example pretty-printer +for numpy dtype objects, add the following to your ipy_user_conf.py:: + + from IPython.Extensions import ipy_pretty + + ipy_pretty.activate() + + # If you want to have numpy always imported anyways: + import numpy + ipy_pretty.for_type(numpy.dtype, ipy_pretty.dtype_pprinter) + + # If you don't want to have numpy imported until it needs to be: + ipy_pretty.for_type_by_name('numpy', 'dtype', ipy_pretty.dtype_pprinter) +""" + +import IPython.ipapi +from IPython.genutils import Term + +from IPython.external import pretty + +ip = IPython.ipapi.get() + + +#### Implementation ############################################################ + +def pretty_result_display(self, arg): + """ Uber-pretty-printing display hook. + + Called for displaying the result to the user. + """ + + if ip.options.pprint: + verbose = getattr(ip.options, 'pretty_verbose', False) + out = pretty.pretty(arg, verbose=verbose) + 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. + Term.cout.write('\n') + print >>Term.cout, out + else: + raise TryNext + + +#### API ####################################################################### + +# Expose the for_type and for_type_by_name functions for easier use. +for_type = pretty.for_type +for_type_by_name = pretty.for_type_by_name + + +# FIXME: write deactivate(). We need a way to remove a hook. +def activate(): + """ Activate this extension. + """ + ip.set_hook('result_display', pretty_result_display, priority=99) + + +#### Example pretty-printers ################################################### + +def dtype_pprinter(obj, p, cycle): + """ A pretty-printer for numpy dtype objects. + """ + if cycle: + return p.text('dtype(...)') + if obj.fields is None: + p.text(repr(obj)) + else: + p.begin_group(7, 'dtype([') + for i, field in enumerate(obj.descr): + if i > 0: + p.text(',') + p.breakable() + p.pretty(field) + p.end_group(7, '])') + + +#### Tests ##################################################################### + +def test_pretty(): + """ + In [1]: from IPython.Extensions import ipy_pretty + + In [2]: ipy_pretty.activate() + + In [3]: class A(object): + ...: def __repr__(self): + ...: return 'A()' + ...: + ...: + + In [4]: a = A() + + In [5]: a + Out[5]: A() + + In [6]: def a_pretty_printer(obj, p, cycle): + ...: p.text('') + ...: + ...: + + In [7]: ipy_pretty.for_type(A, a_pretty_printer) + + In [8]: a + Out[8]: + + In [9]: class B(object): + ...: def __repr__(self): + ...: return 'B()' + ...: + ...: + + In [10]: B.__module__, B.__name__ + Out[10]: ('__main__', 'B') + + In [11]: def b_pretty_printer(obj, p, cycle): + ....: p.text('') + ....: + ....: + + In [12]: ipy_pretty.for_type_by_name('__main__', 'B', b_pretty_printer) + + In [13]: b = B() + + In [14]: b + Out[14]: + """ + assert False, "This should only be doctested, not run." + diff --git a/IPython/Extensions/ipy_profile_zope.py b/IPython/Extensions/ipy_profile_zope.py index 3c812f0..172853c 100644 --- a/IPython/Extensions/ipy_profile_zope.py +++ b/IPython/Extensions/ipy_profile_zope.py @@ -1,19 +1,22 @@ # -*- coding: utf-8 -*- -# +""" An ipython profile for zope and plone. + +Some ideas stolen from http://www.tomster.org. + + +Authors +------- +- Stefan Eletzhofer +""" + # File: ipy_profile_zope.py # # Copyright (c) InQuant GmbH # -# An ipython profile for zope and plone. Some ideas -# stolen from http://www.tomster.org. # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -__author__ = """Stefan Eletzhofer """ -__docformat__ = 'plaintext' -__revision__ = "$Revision$" - from IPython import ipapi from IPython import Release from types import StringType diff --git a/IPython/Extensions/ipy_winpdb.py b/IPython/Extensions/ipy_winpdb.py index 6992013..6c2d64b 100644 --- a/IPython/Extensions/ipy_winpdb.py +++ b/IPython/Extensions/ipy_winpdb.py @@ -7,6 +7,16 @@ Usage: %wdb pass Change the password (e.g. if you have forgotten the old one) + + +Notes +----- + +**WARNING**: As of March 2009 (IPython 0.10), WinPdb has a known bug, which +causes PyTables to become impossible to import if winpdb is loaded. Therefore, +if you need PyTables, do *not* use this extension. + +For more details: https://bugs.launchpad.net/ipython/+bug/249036 """ import os diff --git a/IPython/Extensions/pickleshare.py b/IPython/Extensions/pickleshare.py old mode 100644 new mode 100755 index 976ae8d..40bfb8b --- a/IPython/Extensions/pickleshare.py +++ b/IPython/Extensions/pickleshare.py @@ -40,8 +40,6 @@ import UserDict import warnings import glob -from sets import Set as set - def gethashfile(key): return ("%02x" % abs(hash(key) % 256))[-2:] diff --git a/IPython/Extensions/pspersistence.py b/IPython/Extensions/pspersistence.py index 7636493..563308d 100644 --- a/IPython/Extensions/pspersistence.py +++ b/IPython/Extensions/pspersistence.py @@ -3,8 +3,6 @@ %store magic for lightweight persistence. Stores variables, aliases etc. in PickleShare database. - -$Id: iplib.py 1107 2006-01-30 19:02:20Z vivainio $ """ import IPython.ipapi diff --git a/IPython/FakeModule.py b/IPython/FakeModule.py index 9905d52..cc53d99 100644 --- a/IPython/FakeModule.py +++ b/IPython/FakeModule.py @@ -4,8 +4,7 @@ Class which mimics a module. Needed to allow pickle to correctly resolve namespaces during IPython sessions. - -$Id: FakeModule.py 2754 2007-09-09 10:16:59Z fperez $""" +""" #***************************************************************************** # Copyright (C) 2002-2004 Fernando Perez. diff --git a/IPython/Gnuplot2.py b/IPython/Gnuplot2.py index 5019312..cc2eb17 100644 --- a/IPython/Gnuplot2.py +++ b/IPython/Gnuplot2.py @@ -12,8 +12,7 @@ This module is meant to be used as a drop-in replacement to the original Gnuplot, so it should be safe to do: import IPython.Gnuplot2 as Gnuplot - -$Id: Gnuplot2.py 1210 2006-03-13 01:19:31Z fperez $""" +""" import cStringIO import os diff --git a/IPython/GnuplotInteractive.py b/IPython/GnuplotInteractive.py index 0e99ba6..074d2f5 100644 --- a/IPython/GnuplotInteractive.py +++ b/IPython/GnuplotInteractive.py @@ -9,8 +9,7 @@ http://gnuplot-py.sourceforge.net/ See gphelp() below for details on the services offered by this module. Inspired by a suggestion/request from Arnd Baecker. - -$Id: GnuplotInteractive.py 389 2004-10-09 07:59:30Z fperez $""" +""" __all__ = ['Gnuplot','gp','gp_new','plot','plot2','splot','replot', 'hardcopy','gpdata','gpfile','gpstring','gpfunc','gpgrid', diff --git a/IPython/GnuplotRuntime.py b/IPython/GnuplotRuntime.py index 1e99dba..8bd5185 100644 --- a/IPython/GnuplotRuntime.py +++ b/IPython/GnuplotRuntime.py @@ -47,8 +47,7 @@ can be downloaded from: http://gnuplot-py.sourceforge.net/ Inspired by a suggestion/request from Arnd Baecker. - -$Id: GnuplotRuntime.py 389 2004-10-09 07:59:30Z fperez $""" +""" __all__ = ['Gnuplot','gp','gp_new','Data','File','Func','GridData', 'pm3d_config','eps_fix_bbox'] diff --git a/IPython/Itpl.py b/IPython/Itpl.py index 0003e30..7deba81 100644 --- a/IPython/Itpl.py +++ b/IPython/Itpl.py @@ -27,7 +27,7 @@ how to do interpolation: import Itpl sys.stdout = Itpl.filter() f = "fancy" - print "Isn't this $f?" + print "Is this not $f?" print "Standard output has been replaced with a $sys.stdout object." sys.stdout = Itpl.unfilter() print "Okay, back $to $normal." @@ -43,9 +43,7 @@ each time the instance is evaluated with str(instance). For example: print str(s) foo = "bar" print str(s) - -$Id: Itpl.py 2918 2007-12-31 14:34:47Z vivainio $ -""" # ' -> close an open quote for stupid emacs +""" #***************************************************************************** # diff --git a/IPython/Logger.py b/IPython/Logger.py index 8c936ee..be7bd7b 100644 --- a/IPython/Logger.py +++ b/IPython/Logger.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- """ Logger class for IPython's logging facilities. - -$Id: Logger.py 2875 2007-11-26 08:37:39Z fperez $ """ #***************************************************************************** @@ -16,11 +14,6 @@ $Id: Logger.py 2875 2007-11-26 08:37:39Z fperez $ #**************************************************************************** # Modules and globals -from IPython import Release -__author__ = '%s <%s>\n%s <%s>' % \ - ( Release.authors['Janko'] + Release.authors['Fernando'] ) -__license__ = Release.license - # Python standard modules import glob import os diff --git a/IPython/Magic.py b/IPython/Magic.py index e45adf9..f500698 100644 --- a/IPython/Magic.py +++ b/IPython/Magic.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Magic functions for InteractiveShell. - -$Id: Magic.py 2996 2008-01-30 06:31:39Z fperez $""" +""" #***************************************************************************** # Copyright (C) 2001 Janko Hauser and @@ -14,11 +13,6 @@ $Id: Magic.py 2996 2008-01-30 06:31:39Z fperez $""" #**************************************************************************** # Modules and globals -from IPython import Release -__author__ = '%s <%s>\n%s <%s>' % \ - ( Release.authors['Janko'] + Release.authors['Fernando'] ) -__license__ = Release.license - # Python standard modules import __builtin__ import bdb @@ -35,7 +29,6 @@ import textwrap from cStringIO import StringIO from getopt import getopt,GetoptError from pprint import pprint, pformat -from sets import Set # cProfile was added in Python2.5 try: @@ -75,7 +68,7 @@ def compress_dhist(dh): head, tail = dh[:-10], dh[-10:] newhead = [] - done = Set() + done = set() for h in head: if h in done: continue @@ -149,7 +142,7 @@ python-profiler package from non-free.""") filter(inst_magic,self.__dict__.keys()) + \ filter(inst_bound_magic,self.__class__.__dict__.keys()) out = [] - for fn in Set(magics): + for fn in set(magics): out.append(fn.replace('magic_','',1)) out.sort() return out @@ -1054,10 +1047,33 @@ Currently the magic system has the following functions:\n""" def magic_reset(self, parameter_s=''): """Resets the namespace by removing all names defined by the user. - Input/Output history are left around in case you need them.""" + Input/Output history are left around in case you need them. + + Parameters + ---------- + -y : force reset without asking for confirmation. + + Examples + -------- + In [6]: a = 1 + + In [7]: a + Out[7]: 1 + + In [8]: 'a' in _ip.user_ns + Out[8]: True - ans = self.shell.ask_yes_no( - "Once deleted, variables cannot be recovered. Proceed (y/[n])? ") + In [9]: %reset -f + + In [10]: 'a' in _ip.user_ns + Out[10]: False + """ + + if parameter_s == '-f': + ans = True + else: + ans = self.shell.ask_yes_no( + "Once deleted, variables cannot be recovered. Proceed (y/[n])? ") if not ans: print 'Nothing done.' return @@ -1067,7 +1083,7 @@ Currently the magic system has the following functions:\n""" # Also flush the private list of module references kept for script # execution protection - self.shell._user_main_modules[:] = [] + self.shell.clear_main_mod_cache() def magic_logstart(self,parameter_s=''): """Start logging anywhere in a session. @@ -1426,7 +1442,8 @@ Currently the magic system has the following functions:\n""" return None @testdec.skip_doctest - def magic_run(self, parameter_s ='',runner=None): + def magic_run(self, parameter_s ='',runner=None, + file_finder=get_py_filename): """Run the named file inside IPython as a program. Usage:\\ @@ -1541,7 +1558,7 @@ Currently the magic system has the following functions:\n""" mode='list',list_all=1) try: - filename = get_py_filename(arg_lst[0]) + filename = file_finder(arg_lst[0]) except IndexError: warn('you must provide at least a filename.') print '\n%run:\n',OInspect.getdoc(self.magic_run) @@ -1577,10 +1594,13 @@ Currently the magic system has the following functions:\n""" main_mod = FakeModule() prog_ns = main_mod.__dict__ prog_ns['__name__'] = name + # The shell MUST hold a reference to main_mod so after %run exits, # the python deletion mechanism doesn't zero it out (leaving - # dangling references) - self.shell._user_main_modules.append(main_mod) + # dangling references). However, we should drop old versions of + # main_mod. There is now a proper API to manage this caching in + # the main shell object, we use that. + self.shell.cache_main_mod(main_mod) # Since '%run foo' emulates 'python foo.py' at the cmd line, we must # set the __file__ global in the script's namespace diff --git a/IPython/OInspect.py b/IPython/OInspect.py index 83cf3a0..ecef640 100644 --- a/IPython/OInspect.py +++ b/IPython/OInspect.py @@ -5,8 +5,6 @@ Uses syntax highlighting for presenting the various information elements. Similar in spirit to the inspect module, but all calls take a name argument to reference the name under which an object is being read. - -$Id: OInspect.py 2843 2007-10-15 21:22:32Z fperez $ """ #***************************************************************************** @@ -16,10 +14,6 @@ $Id: OInspect.py 2843 2007-10-15 21:22:32Z fperez $ # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license - __all__ = ['Inspector','InspectColors'] # stdlib modules diff --git a/IPython/OutputTrap.py b/IPython/OutputTrap.py index d60216a..c63c798 100644 --- a/IPython/OutputTrap.py +++ b/IPython/OutputTrap.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Class to trap stdout and stderr and log them separately. - -$Id: OutputTrap.py 958 2005-12-27 23:17:51Z fperez $""" +""" #***************************************************************************** # Copyright (C) 2001-2004 Fernando Perez @@ -10,10 +9,6 @@ $Id: OutputTrap.py 958 2005-12-27 23:17:51Z fperez $""" # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license - import exceptions import sys from cStringIO import StringIO diff --git a/IPython/Prompts.py b/IPython/Prompts.py index df4030d..89b8d2a 100644 --- a/IPython/Prompts.py +++ b/IPython/Prompts.py @@ -1,21 +1,16 @@ # -*- coding: utf-8 -*- """ Classes for handling input/output prompts. - -$Id: Prompts.py 3026 2008-02-07 16:03:16Z vivainio $""" +""" #***************************************************************************** -# Copyright (C) 2001-2006 Fernando Perez +# Copyright (C) 2008-2009 The IPython Development Team +# Copyright (C) 2001-2007 Fernando Perez # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license -__version__ = Release.version - #**************************************************************************** # Required modules import __builtin__ @@ -26,11 +21,13 @@ import time # IPython's own from IPython import ColorANSI -from IPython.Itpl import ItplNS +from IPython import Release +from IPython.external.Itpl import ItplNS +from IPython.ipapi import TryNext from IPython.ipstruct import Struct from IPython.macro import Macro + from IPython.genutils import * -from IPython.ipapi import TryNext #**************************************************************************** #Color schemes for Prompts. @@ -168,7 +165,7 @@ prompt_specials_color = { # Carriage return r'\r': '\r', # Release version - r'\v': __version__, + r'\v': Release.version, # Root symbol ($ or #) r'\$': ROOT_SYMBOL, } diff --git a/IPython/PyColorize.py b/IPython/PyColorize.py index 74accae..7d463ea 100644 --- a/IPython/PyColorize.py +++ b/IPython/PyColorize.py @@ -1,34 +1,33 @@ # -*- coding: utf-8 -*- """ - Class and program to colorize python source code for ANSI terminals. +Class and program to colorize python source code for ANSI terminals. - Based on an HTML code highlighter by Jurgen Hermann found at: - http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52298 +Based on an HTML code highlighter by Jurgen Hermann found at: +http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52298 - Modifications by Fernando Perez (fperez@colorado.edu). +Modifications by Fernando Perez (fperez@colorado.edu). - Information on the original HTML highlighter follows: - - MoinMoin - Python Source Parser +Information on the original HTML highlighter follows: - Title: Colorize Python source using the built-in tokenizer - - Submitter: Jurgen Hermann - Last Updated:2001/04/06 - - Version no:1.2 +MoinMoin - Python Source Parser - Description: +Title: Colorize Python source using the built-in tokenizer - This code is part of MoinMoin (http://moin.sourceforge.net/) and converts - Python source code to HTML markup, rendering comments, keywords, - operators, numeric and string literals in different colors. +Submitter: Jurgen Hermann +Last Updated:2001/04/06 - It shows how to use the built-in keyword, token and tokenize modules to - scan Python source code and re-emit it with no changes to its original - formatting (which is the hard part). +Version no:1.2 - $Id: PyColorize.py 2586 2007-08-06 19:30:09Z vivainio $""" +Description: + +This code is part of MoinMoin (http://moin.sourceforge.net/) and converts +Python source code to HTML markup, rendering comments, keywords, +operators, numeric and string literals in different colors. + +It shows how to use the built-in keyword, token and tokenize modules to +scan Python source code and re-emit it with no changes to its original +formatting (which is the hard part). +""" __all__ = ['ANSICodeColors','Parser'] diff --git a/IPython/Shell.py b/IPython/Shell.py index 8f31694..5454f7b 100644 --- a/IPython/Shell.py +++ b/IPython/Shell.py @@ -3,8 +3,7 @@ All the matplotlib support code was co-developed with John Hunter, matplotlib's author. - -$Id: Shell.py 3024 2008-02-07 15:34:42Z darren.dale $""" +""" #***************************************************************************** # Copyright (C) 2001-2006 Fernando Perez @@ -13,10 +12,6 @@ $Id: Shell.py 3024 2008-02-07 15:34:42Z darren.dale $""" # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license - # Code begins # Stdlib imports import __builtin__ diff --git a/IPython/UserConfig/ipythonrc b/IPython/UserConfig/ipythonrc index 6f2efb7..c90044f 100644 --- a/IPython/UserConfig/ipythonrc +++ b/IPython/UserConfig/ipythonrc @@ -1,5 +1,4 @@ # -*- Mode: Shell-Script -*- Not really, but shows comments correctly -# $Id: ipythonrc 2156 2007-03-19 02:32:19Z fperez $ #*************************************************************************** # diff --git a/IPython/__init__.py b/IPython/__init__.py index 48c95af..2fb3281 100644 --- a/IPython/__init__.py +++ b/IPython/__init__.py @@ -25,12 +25,12 @@ IPython tries to: iii - serve as an embeddable, ready to go interpreter for your own programs. -IPython requires Python 2.3 or newer. - -$Id: __init__.py 2399 2007-05-26 10:23:10Z vivainio $""" +IPython requires Python 2.4 or newer. +""" #***************************************************************************** -# Copyright (C) 2001-2006 Fernando Perez. +# Copyright (C) 2008-2009 The IPython Development Team +# Copyright (C) 2001-2007 Fernando Perez. # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. diff --git a/IPython/background_jobs.py b/IPython/background_jobs.py index c9c3aa3..e418aaa 100644 --- a/IPython/background_jobs.py +++ b/IPython/background_jobs.py @@ -17,8 +17,6 @@ http://folk.uio.no/hpl/scripting (although ultimately no code from this text was used, as IPython's system is a separate implementation). - -$Id: background_jobs.py 994 2006-01-08 08:29:44Z fperez $ """ #***************************************************************************** @@ -28,10 +26,6 @@ $Id: background_jobs.py 994 2006-01-08 08:29:44Z fperez $ # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license - # Code begins import sys import threading diff --git a/IPython/completer.py b/IPython/completer.py index 52df78e..b8b5334 100644 --- a/IPython/completer.py +++ b/IPython/completer.py @@ -6,7 +6,6 @@ upstream and were accepted as of Python 2.3, but we need a lot more functionality specific to IPython, so this module will continue to live as an IPython-specific utility. ---------------------------------------------------------------------------- Original rlcompleter documentation: This requires the latest extension to the readline module (the diff --git a/IPython/deep_reload.py b/IPython/deep_reload.py index f578de2..c8685ba 100644 --- a/IPython/deep_reload.py +++ b/IPython/deep_reload.py @@ -12,8 +12,7 @@ Alternatively, you can add a dreload builtin alongside normal reload with: >>> __builtin__.dreload = deep_reload.reload This code is almost entirely based on knee.py from the standard library. - -$Id: deep_reload.py 958 2005-12-27 23:17:51Z fperez $""" +""" #***************************************************************************** # Copyright (C) 2001 Nathaniel Gray @@ -22,12 +21,6 @@ $Id: deep_reload.py 958 2005-12-27 23:17:51Z fperez $""" # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release # do it explicitly so pydoc can see it - pydoc bug -__author__ = '%s <%s>' % Release.authors['Nathan'] -__license__ = Release.license -__version__ = "0.5" -__date__ = "21 August 2001" - import __builtin__ import imp import sys diff --git a/IPython/excolors.py b/IPython/excolors.py index 068f2b6..0245eb5 100644 --- a/IPython/excolors.py +++ b/IPython/excolors.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- """ Color schemes for exception handling code in IPython. - -$Id: Prompts.py 638 2005-07-18 03:01:41Z fperez $""" +""" #***************************************************************************** # Copyright (C) 2005-2006 Fernando Perez @@ -11,99 +10,128 @@ $Id: Prompts.py 638 2005-07-18 03:01:41Z fperez $""" # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license -__version__ = Release.version - #**************************************************************************** # Required modules from IPython.ColorANSI import ColorSchemeTable, TermColors, ColorScheme -ExceptionColors = ColorSchemeTable() - -# Populate it with color schemes -C = TermColors # shorthand and local lookup -ExceptionColors.add_scheme(ColorScheme( - 'NoColor', - # The color to be used for the top line - topline = C.NoColor, - - # The colors to be used in the traceback - filename = C.NoColor, - lineno = C.NoColor, - name = C.NoColor, - vName = C.NoColor, - val = C.NoColor, - em = C.NoColor, - - # Emphasized colors for the last frame of the traceback - normalEm = C.NoColor, - filenameEm = C.NoColor, - linenoEm = C.NoColor, - nameEm = C.NoColor, - valEm = C.NoColor, - - # Colors for printing the exception - excName = C.NoColor, - line = C.NoColor, - caret = C.NoColor, - Normal = C.NoColor - )) - -# make some schemes as instances so we can copy them for modification easily -ExceptionColors.add_scheme(ColorScheme( - 'Linux', - # The color to be used for the top line - topline = C.LightRed, - - # The colors to be used in the traceback - filename = C.Green, - lineno = C.Green, - name = C.Purple, - vName = C.Cyan, - val = C.Green, - em = C.LightCyan, - - # Emphasized colors for the last frame of the traceback - normalEm = C.LightCyan, - filenameEm = C.LightGreen, - linenoEm = C.LightGreen, - nameEm = C.LightPurple, - valEm = C.LightBlue, - - # Colors for printing the exception - excName = C.LightRed, - line = C.Yellow, - caret = C.White, - Normal = C.Normal - )) - -# For light backgrounds, swap dark/light colors -ExceptionColors.add_scheme(ColorScheme( - 'LightBG', - # The color to be used for the top line - topline = C.Red, +def exception_colors(): + """Return a color table with fields for exception reporting. + + The table is an instance of ColorSchemeTable with schemes added for + 'Linux', 'LightBG' and 'NoColor' and fields for exception handling filled + in. + + Examples: + + >>> ec = exception_colors() + >>> ec.active_scheme_name + '' + >>> print ec.active_colors + None + + Now we activate a color scheme: + >>> ec.set_active_scheme('NoColor') + >>> ec.active_scheme_name + 'NoColor' + >>> ec.active_colors.keys() + ['em', 'caret', '__allownew', 'name', 'val', 'vName', 'Normal', 'normalEm', + 'filename', 'linenoEm', 'excName', 'lineno', 'valEm', 'filenameEm', + 'nameEm', 'line', 'topline'] + """ - # The colors to be used in the traceback - filename = C.LightGreen, - lineno = C.LightGreen, - name = C.LightPurple, - vName = C.Cyan, - val = C.LightGreen, - em = C.Cyan, - - # Emphasized colors for the last frame of the traceback - normalEm = C.Cyan, - filenameEm = C.Green, - linenoEm = C.Green, - nameEm = C.Purple, - valEm = C.Blue, - - # Colors for printing the exception - excName = C.Red, - #line = C.Brown, # brown often is displayed as yellow - line = C.Red, - caret = C.Normal, - Normal = C.Normal - )) + ex_colors = ColorSchemeTable() + + # Populate it with color schemes + C = TermColors # shorthand and local lookup + ex_colors.add_scheme(ColorScheme( + 'NoColor', + # The color to be used for the top line + topline = C.NoColor, + + # The colors to be used in the traceback + filename = C.NoColor, + lineno = C.NoColor, + name = C.NoColor, + vName = C.NoColor, + val = C.NoColor, + em = C.NoColor, + + # Emphasized colors for the last frame of the traceback + normalEm = C.NoColor, + filenameEm = C.NoColor, + linenoEm = C.NoColor, + nameEm = C.NoColor, + valEm = C.NoColor, + + # Colors for printing the exception + excName = C.NoColor, + line = C.NoColor, + caret = C.NoColor, + Normal = C.NoColor + )) + + # make some schemes as instances so we can copy them for modification easily + ex_colors.add_scheme(ColorScheme( + 'Linux', + # The color to be used for the top line + topline = C.LightRed, + + # The colors to be used in the traceback + filename = C.Green, + lineno = C.Green, + name = C.Purple, + vName = C.Cyan, + val = C.Green, + em = C.LightCyan, + + # Emphasized colors for the last frame of the traceback + normalEm = C.LightCyan, + filenameEm = C.LightGreen, + linenoEm = C.LightGreen, + nameEm = C.LightPurple, + valEm = C.LightBlue, + + # Colors for printing the exception + excName = C.LightRed, + line = C.Yellow, + caret = C.White, + Normal = C.Normal + )) + + # For light backgrounds, swap dark/light colors + ex_colors.add_scheme(ColorScheme( + 'LightBG', + # The color to be used for the top line + topline = C.Red, + + # The colors to be used in the traceback + filename = C.LightGreen, + lineno = C.LightGreen, + name = C.LightPurple, + vName = C.Cyan, + val = C.LightGreen, + em = C.Cyan, + + # Emphasized colors for the last frame of the traceback + normalEm = C.Cyan, + filenameEm = C.Green, + linenoEm = C.Green, + nameEm = C.Purple, + valEm = C.Blue, + + # Colors for printing the exception + excName = C.Red, + #line = C.Brown, # brown often is displayed as yellow + line = C.Red, + caret = C.Normal, + Normal = C.Normal, + )) + + return ex_colors + + +# For backwards compatibility, keep around a single global object. Note that +# this should NOT be used, the factory function should be used instead, since +# these objects are stateful and it's very easy to get strange bugs if any code +# modifies the module-level object's state. +ExceptionColors = exception_colors() diff --git a/IPython/external/Itpl.py b/IPython/external/Itpl.py index e792cc0..3423fe4 100644 --- a/IPython/external/Itpl.py +++ b/IPython/external/Itpl.py @@ -27,7 +27,7 @@ how to do interpolation: import Itpl sys.stdout = Itpl.filter() f = "fancy" - print "Isn't this $f?" + print "Is this not $f?" print "Standard output has been replaced with a $sys.stdout object." sys.stdout = Itpl.unfilter() print "Okay, back $to $normal." @@ -43,9 +43,7 @@ each time the instance is evaluated with str(instance). For example: print str(s) foo = "bar" print str(s) - -$Id: Itpl.py 2305 2007-05-04 05:34:42Z bgranger $ -""" # ' -> close an open quote for stupid emacs +""" #***************************************************************************** # diff --git a/IPython/external/mglob.py b/IPython/external/mglob.py old mode 100644 new mode 100755 index 3810f32..ed3679f --- a/IPython/external/mglob.py +++ b/IPython/external/mglob.py @@ -73,8 +73,6 @@ __version__ = "0.2" import os,glob,fnmatch,sys,re -from sets import Set as set - def expand(flist,exp_dirs = False): """ Expand the glob(s) in flist. diff --git a/IPython/external/pretty.py b/IPython/external/pretty.py new file mode 100644 index 0000000..6a7e855 --- /dev/null +++ b/IPython/external/pretty.py @@ -0,0 +1,705 @@ +# -*- coding: utf-8 -*- +""" + pretty + ~~ + + Python advanced pretty printer. This pretty printer is intended to + replace the old `pprint` python module which does not allow developers + to provide their own pretty print callbacks. + + This module is based on ruby's `prettyprint.rb` library by `Tanaka Akira`. + + + Example Usage + ============= + + To directly print the representation of an object use `pprint`:: + + from pretty import pprint + pprint(complex_object) + + To get a string of the output use `pretty`:: + + from pretty import pretty + string = pretty(complex_object) + + + Extending + ========= + + The pretty library allows developers to add pretty printing rules for their + own objects. This process is straightforward. All you have to do is to + add a `__pretty__` method to your object and call the methods on the + pretty printer passed:: + + class MyObject(object): + + def __pretty__(self, p, cycle): + ... + + Depending on the python version you want to support you have two + possibilities. The following list shows the python 2.5 version and the + compatibility one. + + + Here the example implementation of a `__pretty__` method for a list + subclass for python 2.5 and higher (python 2.5 requires the with statement + __future__ import):: + + class MyList(list): + + def __pretty__(self, p, cycle): + if cycle: + p.text('MyList(...)') + else: + with p.group(8, 'MyList([', '])'): + for idx, item in enumerate(self): + if idx: + p.text(',') + p.breakable() + p.pretty(item) + + The `cycle` parameter is `True` if pretty detected a cycle. You *have* to + react to that or the result is an infinite loop. `p.text()` just adds + non breaking text to the output, `p.breakable()` either adds a whitespace + or breaks here. If you pass it an argument it's used instead of the + default space. `p.pretty` prettyprints another object using the pretty print + method. + + The first parameter to the `group` function specifies the extra indentation + of the next line. In this example the next item will either be not + breaked (if the items are short enough) or aligned with the right edge of + the opening bracked of `MyList`. + + If you want to support python 2.4 and lower you can use this code:: + + class MyList(list): + + def __pretty__(self, p, cycle): + if cycle: + p.text('MyList(...)') + else: + p.begin_group(8, 'MyList([') + for idx, item in enumerate(self): + if idx: + p.text(',') + p.breakable() + p.pretty(item) + p.end_group(8, '])') + + If you just want to indent something you can use the group function + without open / close parameters. Under python 2.5 you can also use this + code:: + + with p.indent(2): + ... + + Or under python2.4 you might want to modify ``p.indentation`` by hand but + this is rather ugly. + + :copyright: 2007 by Armin Ronacher. + Portions (c) 2009 by Robert Kern. + :license: BSD License. +""" +import __future__ +import sys +import types +import re +import datetime +from StringIO import StringIO +from collections import deque + + +__all__ = ['pretty', 'pprint', 'PrettyPrinter', 'RepresentationPrinter', + 'for_type', 'for_type_by_name'] + + +_re_pattern_type = type(re.compile('')) + + +def pretty(obj, verbose=False, max_width=79, newline='\n'): + """ + Pretty print the object's representation. + """ + stream = StringIO() + printer = RepresentationPrinter(stream, verbose, max_width, newline) + printer.pretty(obj) + printer.flush() + return stream.getvalue() + + +def pprint(obj, verbose=False, max_width=79, newline='\n'): + """ + Like `pretty` but print to stdout. + """ + printer = RepresentationPrinter(sys.stdout, verbose, max_width, newline) + printer.pretty(obj) + printer.flush() + sys.stdout.write(newline) + sys.stdout.flush() + + +# add python2.5 context managers if we have the with statement feature +if hasattr(__future__, 'with_statement'): exec ''' +from __future__ import with_statement +from contextlib import contextmanager + +class _PrettyPrinterBase(object): + + @contextmanager + def indent(self, indent): + """with statement support for indenting/dedenting.""" + self.indentation += indent + try: + yield + finally: + self.indentation -= indent + + @contextmanager + def group(self, indent=0, open='', close=''): + """like begin_group / end_group but for the with statement.""" + self.begin_group(indent, open) + try: + with self.indent(indent): + yield + finally: + self.end_group(indent, close) +''' +else: + class _PrettyPrinterBase(object): + + def _unsupported(self, *a, **kw): + """unsupported operation""" + raise RuntimeError('not available in this python version') + group = indent = _unsupported + del _unsupported + + +class PrettyPrinter(_PrettyPrinterBase): + """ + Baseclass for the `RepresentationPrinter` prettyprinter that is used to + generate pretty reprs of objects. Contrary to the `RepresentationPrinter` + this printer knows nothing about the default pprinters or the `__pretty__` + callback method. + """ + + def __init__(self, output, max_width=79, newline='\n'): + self.output = output + self.max_width = max_width + self.newline = newline + self.output_width = 0 + self.buffer_width = 0 + self.buffer = deque() + + root_group = Group(0) + self.group_stack = [root_group] + self.group_queue = GroupQueue(root_group) + self.indentation = 0 + + def _break_outer_groups(self): + while self.max_width < self.output_width + self.buffer_width: + group = self.group_queue.deq() + if not group: + return + while group.breakables: + x = self.buffer.popleft() + self.output_width = x.output(self.output, self.output_width) + self.buffer_width -= x.width + while self.buffer and isinstance(self.buffer[0], Text): + x = self.buffer.popleft() + self.output_width = x.output(self.output, self.output_width) + self.buffer_width -= x.width + + def text(self, obj): + """Add literal text to the output.""" + width = len(obj) + if self.buffer: + text = self.buffer[-1] + if not isinstance(text, Text): + text = Text() + self.buffer.append(text) + text.add(obj, width) + self.buffer_width += width + self._break_outer_groups() + else: + self.output.write(obj) + self.output_width += width + + def breakable(self, sep=' '): + """ + Add a breakable separator to the output. This does not mean that it + will automatically break here. If no breaking on this position takes + place the `sep` is inserted which default to one space. + """ + width = len(sep) + group = self.group_stack[-1] + if group.want_break: + self.flush() + self.output.write(self.newline) + self.output.write(' ' * self.indentation) + self.output_width = self.indentation + self.buffer_width = 0 + else: + self.buffer.append(Breakable(sep, width, self)) + self.buffer_width += width + self._break_outer_groups() + + + def begin_group(self, indent=0, open=''): + """ + Begin a group. If you want support for python < 2.5 which doesn't has + the with statement this is the preferred way: + + p.begin_group(1, '{') + ... + p.end_group(1, '}') + + The python 2.5 expression would be this: + + with p.group(1, '{', '}'): + ... + + The first parameter specifies the indentation for the next line (usually + the width of the opening text), the second the opening text. All + parameters are optional. + """ + if open: + self.text(open) + group = Group(self.group_stack[-1].depth + 1) + self.group_stack.append(group) + self.group_queue.enq(group) + self.indentation += indent + + def end_group(self, dedent=0, close=''): + """End a group. See `begin_group` for more details.""" + self.indentation -= dedent + group = self.group_stack.pop() + if not group.breakables: + self.group_queue.remove(group) + if close: + self.text(close) + + def flush(self): + """Flush data that is left in the buffer.""" + for data in self.buffer: + self.output_width += data.output(self.output, self.output_width) + self.buffer.clear() + self.buffer_width = 0 + + +def _get_mro(obj_class): + """ Get a reasonable method resolution order of a class and its superclasses + for both old-style and new-style classes. + """ + if not hasattr(obj_class, '__mro__'): + # Old-style class. Mix in object to make a fake new-style class. + try: + obj_class = type(obj_class.__name__, (obj_class, object), {}) + except TypeError: + # Old-style extension type that does not descend from object. + # FIXME: try to construct a more thorough MRO. + mro = [obj_class] + else: + mro = obj_class.__mro__[1:-1] + else: + mro = obj_class.__mro__ + return mro + + +class RepresentationPrinter(PrettyPrinter): + """ + Special pretty printer that has a `pretty` method that calls the pretty + printer for a python object. + + This class stores processing data on `self` so you must *never* use + this class in a threaded environment. Always lock it or reinstanciate + it. + + Instances also have a verbose flag callbacks can access to control their + output. For example the default instance repr prints all attributes and + methods that are not prefixed by an underscore if the printer is in + verbose mode. + """ + + def __init__(self, output, verbose=False, max_width=79, newline='\n'): + PrettyPrinter.__init__(self, output, max_width, newline) + self.verbose = verbose + self.stack = [] + + def pretty(self, obj): + """Pretty print the given object.""" + obj_id = id(obj) + cycle = obj_id in self.stack + self.stack.append(obj_id) + self.begin_group() + try: + obj_class = getattr(obj, '__class__', None) or type(obj) + if hasattr(obj_class, '__pretty__'): + return obj_class.__pretty__(obj, self, cycle) + try: + printer = _singleton_pprinters[obj_id] + except (TypeError, KeyError): + pass + else: + return printer(obj, self, cycle) + for cls in _get_mro(obj_class): + if cls in _type_pprinters: + return _type_pprinters[cls](obj, self, cycle) + else: + printer = self._in_deferred_types(cls) + if printer is not None: + return printer(obj, self, cycle) + return _default_pprint(obj, self, cycle) + finally: + self.end_group() + self.stack.pop() + + def _in_deferred_types(self, cls): + """ + Check if the given class is specified in the deferred type registry. + + Returns the printer from the registry if it exists, and None if the + class is not in the registry. Successful matches will be moved to the + regular type registry for future use. + """ + mod = getattr(cls, '__module__', None) + name = getattr(cls, '__name__', None) + key = (mod, name) + printer = None + if key in _deferred_type_pprinters: + # Move the printer over to the regular registry. + printer = _deferred_type_pprinters.pop(key) + _type_pprinters[cls] = printer + return printer + + + +class Printable(object): + + def output(self, stream, output_width): + return output_width + + +class Text(Printable): + + def __init__(self): + self.objs = [] + self.width = 0 + + def output(self, stream, output_width): + for obj in self.objs: + stream.write(obj) + return output_width + self.width + + def add(self, obj, width): + self.objs.append(obj) + self.width += width + + +class Breakable(Printable): + + def __init__(self, seq, width, pretty): + self.obj = seq + self.width = width + self.pretty = pretty + self.indentation = pretty.indentation + self.group = pretty.group_stack[-1] + self.group.breakables.append(self) + + def output(self, stream, output_width): + self.group.breakables.popleft() + if self.group.want_break: + stream.write(self.pretty.newline) + stream.write(' ' * self.indentation) + return self.indentation + if not self.group.breakables: + self.pretty.group_queue.remove(self.group) + stream.write(self.obj) + return output_width + self.width + + +class Group(Printable): + + def __init__(self, depth): + self.depth = depth + self.breakables = deque() + self.want_break = False + + +class GroupQueue(object): + + def __init__(self, *groups): + self.queue = [] + for group in groups: + self.enq(group) + + def enq(self, group): + depth = group.depth + while depth > len(self.queue) - 1: + self.queue.append([]) + self.queue[depth].append(group) + + def deq(self): + for stack in self.queue: + for idx, group in enumerate(reversed(stack)): + if group.breakables: + del stack[idx] + group.want_break = True + return group + for group in stack: + group.want_break = True + del stack[:] + + def remove(self, group): + try: + self.queue[group.depth].remove(group) + except ValueError: + pass + + +_baseclass_reprs = (object.__repr__, types.InstanceType.__repr__) + + +def _default_pprint(obj, p, cycle): + """ + The default print function. Used if an object does not provide one and + it's none of the builtin objects. + """ + klass = getattr(obj, '__class__', None) or type(obj) + if getattr(klass, '__repr__', None) not in _baseclass_reprs: + # A user-provided repr. + p.text(repr(obj)) + return + p.begin_group(1, '<') + p.pretty(klass) + p.text(' at 0x%x' % id(obj)) + if cycle: + p.text(' ...') + elif p.verbose: + first = True + for key in dir(obj): + if not key.startswith('_'): + try: + value = getattr(obj, key) + except AttributeError: + continue + if isinstance(value, types.MethodType): + continue + if not first: + p.text(',') + p.breakable() + p.text(key) + p.text('=') + step = len(key) + 1 + p.indentation += step + p.pretty(value) + p.indentation -= step + first = False + p.end_group(1, '>') + + +def _seq_pprinter_factory(start, end): + """ + Factory that returns a pprint function useful for sequences. Used by + the default pprint for tuples, dicts, lists, sets and frozensets. + """ + def inner(obj, p, cycle): + if cycle: + return p.text(start + '...' + end) + step = len(start) + p.begin_group(step, start) + for idx, x in enumerate(obj): + if idx: + p.text(',') + p.breakable() + p.pretty(x) + if len(obj) == 1 and type(obj) is tuple: + # Special case for 1-item tuples. + p.text(',') + p.end_group(step, end) + return inner + + +def _dict_pprinter_factory(start, end): + """ + Factory that returns a pprint function used by the default pprint of + dicts and dict proxies. + """ + def inner(obj, p, cycle): + if cycle: + return p.text('{...}') + p.begin_group(1, start) + keys = obj.keys() + try: + keys.sort() + except Exception, e: + # Sometimes the keys don't sort. + pass + for idx, key in enumerate(keys): + if idx: + p.text(',') + p.breakable() + p.pretty(key) + p.text(': ') + p.pretty(obj[key]) + p.end_group(1, end) + return inner + + +def _super_pprint(obj, p, cycle): + """The pprint for the super type.""" + p.begin_group(8, '') + + +def _re_pattern_pprint(obj, p, cycle): + """The pprint function for regular expression patterns.""" + p.text('re.compile(') + pattern = repr(obj.pattern) + if pattern[:1] in 'uU': + pattern = pattern[1:] + prefix = 'ur' + else: + prefix = 'r' + pattern = prefix + pattern.replace('\\\\', '\\') + p.text(pattern) + if obj.flags: + p.text(',') + p.breakable() + done_one = False + for flag in ('TEMPLATE', 'IGNORECASE', 'LOCALE', 'MULTILINE', 'DOTALL', + 'UNICODE', 'VERBOSE', 'DEBUG'): + if obj.flags & getattr(re, flag): + if done_one: + p.text('|') + p.text('re.' + flag) + done_one = True + p.text(')') + + +def _type_pprint(obj, p, cycle): + """The pprint for classes and types.""" + if obj.__module__ in ('__builtin__', 'exceptions'): + name = obj.__name__ + else: + name = obj.__module__ + '.' + obj.__name__ + p.text(name) + + +def _repr_pprint(obj, p, cycle): + """A pprint that just redirects to the normal repr function.""" + p.text(repr(obj)) + + +def _function_pprint(obj, p, cycle): + """Base pprint for all functions and builtin functions.""" + if obj.__module__ in ('__builtin__', 'exceptions') or not obj.__module__: + name = obj.__name__ + else: + name = obj.__module__ + '.' + obj.__name__ + p.text('' % name) + + +def _exception_pprint(obj, p, cycle): + """Base pprint for all exceptions.""" + if obj.__class__.__module__ == 'exceptions': + name = obj.__class__.__name__ + else: + name = '%s.%s' % ( + obj.__class__.__module__, + obj.__class__.__name__ + ) + step = len(name) + 1 + p.begin_group(step, '(') + for idx, arg in enumerate(getattr(obj, 'args', ())): + if idx: + p.text(',') + p.breakable() + p.pretty(arg) + p.end_group(step, ')') + + +#: the exception base +try: + _exception_base = BaseException +except NameError: + _exception_base = Exception + + +#: printers for builtin types +_type_pprinters = { + int: _repr_pprint, + long: _repr_pprint, + float: _repr_pprint, + str: _repr_pprint, + unicode: _repr_pprint, + tuple: _seq_pprinter_factory('(', ')'), + list: _seq_pprinter_factory('[', ']'), + dict: _dict_pprinter_factory('{', '}'), + types.DictProxyType: _dict_pprinter_factory(''), + set: _seq_pprinter_factory('set([', '])'), + frozenset: _seq_pprinter_factory('frozenset([', '])'), + super: _super_pprint, + _re_pattern_type: _re_pattern_pprint, + type: _type_pprint, + types.ClassType: _type_pprint, + types.FunctionType: _function_pprint, + types.BuiltinFunctionType: _function_pprint, + types.SliceType: _repr_pprint, + types.MethodType: _repr_pprint, + xrange: _repr_pprint, + datetime.datetime: _repr_pprint, + datetime.timedelta: _repr_pprint, + _exception_base: _exception_pprint +} + +#: printers for types specified by name +_deferred_type_pprinters = { +} + +def for_type(typ, func): + """ + Add a pretty printer for a given type. + """ + oldfunc = _type_pprinters.get(typ, None) + if func is not None: + # To support easy restoration of old pprinters, we need to ignore Nones. + _type_pprinters[typ] = func + return oldfunc + +def for_type_by_name(type_module, type_name, func): + """ + Add a pretty printer for a type specified by the module and name of a type + rather than the type object itself. + """ + key = (type_module, type_name) + oldfunc = _deferred_type_pprinters.get(key, None) + if func is not None: + # To support easy restoration of old pprinters, we need to ignore Nones. + _deferred_type_pprinters[key] = func + return oldfunc + + +#: printers for the default singletons +_singleton_pprinters = dict.fromkeys(map(id, [None, True, False, Ellipsis, + NotImplemented]), _repr_pprint) + + +if __name__ == '__main__': + from random import randrange + class Foo(object): + def __init__(self): + self.foo = 1 + self.bar = re.compile(r'\s+') + self.blub = dict.fromkeys(range(30), randrange(1, 40)) + self.hehe = 23424.234234 + self.list = ["blub", "blah", self] + + def get_foo(self): + print "foo" + + pprint(Foo(), verbose=True) diff --git a/IPython/genutils.py b/IPython/genutils.py index 38f93ef..85f8d2d 100644 --- a/IPython/genutils.py +++ b/IPython/genutils.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- -""" -General purpose utilities. +"""General purpose utilities. This is a grab-bag of stuff I find useful in most programs I write. Some of these things are also convenient when working at the command line. - -$Id: genutils.py 2998 2008-01-31 10:06:04Z vivainio $""" +""" #***************************************************************************** # Copyright (C) 2001-2006 Fernando Perez. @@ -14,10 +12,6 @@ $Id: genutils.py 2998 2008-01-31 10:06:04Z vivainio $""" # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license - #**************************************************************************** # required modules from the Python standard library import __main__ @@ -928,12 +922,15 @@ def get_home_dir(): # first, check py2exe distribution root directory for _ipython. # This overrides all. Normally does not exist. - if '\\library.zip\\' in IPython.__file__.lower(): - root, rest = IPython.__file__.lower().split('library.zip') - if isdir(root + '_ipython'): - os.environ["IPYKITROOT"] = root.rstrip('\\') - return root - + if hasattr(sys, "frozen"): #Is frozen by py2exe + if '\\library.zip\\' in IPython.__file__.lower():#libraries compressed to zip-file + root, rest = IPython.__file__.lower().split('library.zip') + else: + root=os.path.join(os.path.split(IPython.__file__)[0],"../../") + root=os.path.abspath(root).rstrip('\\') + if isdir(os.path.join(root, '_ipython')): + os.environ["IPYKITROOT"] = root + return root try: homedir = env['HOME'] if not isdir(homedir): @@ -953,7 +950,7 @@ def get_home_dir(): if not isdir(homedir): raise HomeDirError return homedir - except: + except KeyError: try: # Use the registry to get the 'My Documents' folder. import _winreg as wreg @@ -992,8 +989,8 @@ def get_ipython_dir(): ipdir_def = '_ipython' home_dir = get_home_dir() ipdir = os.path.abspath(os.environ.get('IPYTHONDIR', - os.path.join(home_dir,ipdir_def))) - return ipdir + os.path.join(home_dir, ipdir_def))) + return ipdir.decode(sys.getfilesystemencoding()) def get_security_dir(): """Get the IPython security directory. @@ -1234,11 +1231,11 @@ def esc_quotes(strng): def make_quoted_expr(s): """Return string s in appropriate quotes, using raw string if possible. - Effectively this turns string: cd \ao\ao\ - to: r"cd \ao\ao\_"[:-1] - - Note the use of raw string and padding at the end to allow trailing backslash. + XXX - example removed because it caused encoding errors in documentation + generation. We need a new example that doesn't contain invalid chars. + Note the use of raw string and padding at the end to allow trailing + backslash. """ tail = '' diff --git a/IPython/gui/wx/ipshell_nonblocking.py b/IPython/gui/wx/ipshell_nonblocking.py index be9d47b..def47c5 100644 --- a/IPython/gui/wx/ipshell_nonblocking.py +++ b/IPython/gui/wx/ipshell_nonblocking.py @@ -62,11 +62,10 @@ class _Helper(object): ############################################################################## class _CodeExecutor(ThreadEx): ''' Thread that execute ipython code ''' - def __init__(self, instance, after): + def __init__(self, instance): ThreadEx.__init__(self) self.instance = instance - self._afterExecute = after - + def run(self): '''Thread main loop''' try: @@ -74,7 +73,7 @@ class _CodeExecutor(ThreadEx): self.instance._help_text = None self.instance._execute() # used for uper class to generate event after execution - self._afterExecute() + self.instance._after_execute() except KeyboardInterrupt: pass @@ -114,8 +113,7 @@ class NonBlockingIPShell(object): ''' #ipython0 initialisation self._IP = None - self._term = None - self.initIpython0(argv, user_ns, user_global_ns, + self.init_ipython0(argv, user_ns, user_global_ns, cin, cout, cerr, ask_exit_handler) @@ -127,33 +125,32 @@ class NonBlockingIPShell(object): #thread working vars self._line_to_execute = '' - + self._threading = True + #vars that will be checked by GUI loop to handle thread states... #will be replaced later by PostEvent GUI funtions... self._doc_text = None self._help_text = None self._add_button = None - def initIpython0(self, argv=[], user_ns={}, user_global_ns=None, + def init_ipython0(self, argv=[], user_ns={}, user_global_ns=None, cin=None, cout=None, cerr=None, ask_exit_handler=None): - ''' Initialize an ithon0 instance ''' + ''' Initialize an ipython0 instance ''' - #first we redefine in/out/error functions of IPython + #first we redefine in/out/error functions of IPython + #BUG: we've got a limitation form ipython0 there + #only one instance can be instanciated else tehre will be + #cin/cout/cerr clash... if cin: - IPython.Shell.Term.cin = cin + IPython.genutils.Term.cin = cin if cout: - IPython.Shell.Term.cout = cout + IPython.genutils.Term.cout = cout if cerr: - IPython.Shell.Term.cerr = cerr + IPython.genutils.Term.cerr = cerr - # This is to get rid of the blockage that accurs during - # IPython.Shell.InteractiveShell.user_setup() - IPython.iplib.raw_input = lambda x: None - - self._term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr) - excepthook = sys.excepthook + #Hack to save sys.displayhook, because ipython seems to overwrite it... self.sys_displayhook_ori = sys.displayhook @@ -163,7 +160,8 @@ class NonBlockingIPShell(object): embedded=True, shell_class=IPython.Shell.InteractiveShell) - #we restore sys.displayhook + #we save ipython0 displayhook and we restore sys.displayhook + self.displayhook = sys.displayhook sys.displayhook = self.sys_displayhook_ori #we replace IPython default encoding by wx locale encoding @@ -173,11 +171,12 @@ class NonBlockingIPShell(object): #we replace the ipython default pager by our pager self._IP.set_hook('show_in_pager', self._pager) - #we replace the ipython default shell command caller by our shell handler + #we replace the ipython default shell command caller + #by our shell handler self._IP.set_hook('shell_hook', self._shell) #we replace the ipython default input command caller by our method - IPython.iplib.raw_input_original = self._raw_input + IPython.iplib.raw_input_original = self._raw_input_original #we replace the ipython default exit command by our method self._IP.exit = ask_exit_handler #we replace the help command @@ -186,26 +185,68 @@ class NonBlockingIPShell(object): #we disable cpase magic... until we found a way to use it properly. #import IPython.ipapi ip = IPython.ipapi.get() - def bypassMagic(self, arg): + def bypass_magic(self, arg): print '%this magic is currently disabled.' - ip.expose_magic('cpaste', bypassMagic) + ip.expose_magic('cpaste', bypass_magic) + + import __builtin__ + __builtin__.raw_input = self._raw_input sys.excepthook = excepthook - #----------------------- Thread management section ---------------------- - def doExecute(self, line): + #----------------------- Thread management section ---------------------- + def do_execute(self, line): """ Tell the thread to process the 'line' command """ self._line_to_execute = line - #we launch the ipython line execution in a thread to make it interruptible - #with include it in self namespace to be able to call ce.raise_exc(KeyboardInterrupt) - self.ce = _CodeExecutor(self, self._afterExecute) - self.ce.start() - - #----------------------- IPython management section ---------------------- - def getDocText(self): + + if self._threading: + #we launch the ipython line execution in a thread to make it + #interruptible with include it in self namespace to be able + #to call ce.raise_exc(KeyboardInterrupt) + self.ce = _CodeExecutor(self) + self.ce.start() + else: + try: + self._doc_text = None + self._help_text = None + self._execute() + # used for uper class to generate event after execution + self._after_execute() + + except KeyboardInterrupt: + pass + + #----------------------- IPython management section ---------------------- + def get_threading(self): + """ + Returns threading status, is set to True, then each command sent to + the interpreter will be executed in a separated thread allowing, + for example, breaking a long running commands. + Disallowing it, permits better compatibilty with instance that is embedding + IPython instance. + + @return: Execution method + @rtype: bool + """ + return self._threading + + def set_threading(self, state): + """ + Sets threading state, if set to True, then each command sent to + the interpreter will be executed in a separated thread allowing, + for example, breaking a long running commands. + Disallowing it, permits better compatibilty with instance that is embedding + IPython instance. + + @param state: Sets threading state + @type bool + """ + self._threading = state + + def get_doc_text(self): """ Returns the output of the processing that need to be paged (if any) @@ -214,7 +255,7 @@ class NonBlockingIPShell(object): """ return self._doc_text - def getHelpText(self): + def get_help_text(self): """ Returns the output of the processing that need to be paged via help pager(if any) @@ -223,7 +264,7 @@ class NonBlockingIPShell(object): """ return self._help_text - def getBanner(self): + def get_banner(self): """ Returns the IPython banner for useful info on IPython instance @@ -232,7 +273,7 @@ class NonBlockingIPShell(object): """ return self._IP.BANNER - def getPromptCount(self): + def get_prompt_count(self): """ Returns the prompt number. Each time a user execute a line in the IPython shell the prompt count is increased @@ -242,7 +283,7 @@ class NonBlockingIPShell(object): """ return self._IP.outputcache.prompt_count - def getPrompt(self): + def get_prompt(self): """ Returns current prompt inside IPython instance (Can be In [...]: ot ...:) @@ -252,7 +293,7 @@ class NonBlockingIPShell(object): """ return self._prompt - def getIndentation(self): + def get_indentation(self): """ Returns the current indentation level Usefull to put the caret at the good start position if we want to do autoindentation. @@ -262,7 +303,7 @@ class NonBlockingIPShell(object): """ return self._IP.indent_current_nsp - def updateNamespace(self, ns_dict): + def update_namespace(self, ns_dict): ''' Add the current dictionary to the shell namespace. @@ -286,7 +327,7 @@ class NonBlockingIPShell(object): possibilities = self._IP.complete(split_line[-1]) if possibilities: - def _commonPrefix(str1, str2): + def _common_prefix(str1, str2): ''' Reduction function. returns common prefix of two given strings. @@ -302,13 +343,13 @@ class NonBlockingIPShell(object): if not str2.startswith(str1[:i+1]): return str1[:i] return str1 - common_prefix = reduce(_commonPrefix, possibilities) + common_prefix = reduce(_common_prefix, possibilities) completed = line[:-len(split_line[-1])]+common_prefix else: completed = line return completed, possibilities - def historyBack(self): + def history_back(self): ''' Provides one history command back. @@ -320,10 +361,10 @@ class NonBlockingIPShell(object): while((history == '' or history == '\n') and self._history_level >0): if self._history_level >= 1: self._history_level -= 1 - history = self._getHistory() + history = self._get_history() return history - def historyForward(self): + def history_forward(self): ''' Provides one history command forward. @@ -333,38 +374,38 @@ class NonBlockingIPShell(object): history = '' #the below while loop is used to suppress empty history lines while((history == '' or history == '\n') \ - and self._history_level <= self._getHistoryMaxIndex()): - if self._history_level < self._getHistoryMaxIndex(): + and self._history_level <= self._get_history_max_index()): + if self._history_level < self._get_history_max_index(): self._history_level += 1 - history = self._getHistory() + history = self._get_history() else: - if self._history_level == self._getHistoryMaxIndex(): - history = self._getHistory() + if self._history_level == self._get_history_max_index(): + history = self._get_history() self._history_level += 1 else: history = '' return history - def initHistoryIndex(self): + def init_history_index(self): ''' set history to last command entered ''' - self._history_level = self._getHistoryMaxIndex()+1 + self._history_level = self._get_history_max_index()+1 #----------------------- IPython PRIVATE management section -------------- - def _afterExecute(self): + def _after_execute(self): ''' Can be redefined to generate post event after excution is done ''' pass - #def _askExit(self): - # ''' - # Can be redefined to generate post event to exit the Ipython shell - # ''' - # pass + def _ask_exit(self): + ''' + Can be redefined to generate post event to exit the Ipython shell + ''' + pass - def _getHistoryMaxIndex(self): + def _get_history_max_index(self): ''' returns the max length of the history buffer @@ -373,7 +414,7 @@ class NonBlockingIPShell(object): ''' return len(self._IP.input_hist_raw)-1 - def _getHistory(self): + def _get_history(self): ''' Get's the command string of the current history level. @@ -388,7 +429,7 @@ class NonBlockingIPShell(object): This function is used as a callback replacment to IPython help pager function It puts the 'text' value inside the self._help_text string that can be retrived via - getHelpText function. + get_help_text function. ''' if self._help_text == None: self._help_text = text @@ -400,11 +441,11 @@ class NonBlockingIPShell(object): This function is used as a callback replacment to IPython pager function It puts the 'text' value inside the self._doc_text string that can be retrived via - getDocText function. + get_doc_text function. ''' self._doc_text = text - def _raw_input(self, prompt=''): + def _raw_input_original(self, prompt=''): ''' Custom raw_input() replacement. Get's current line from console buffer. @@ -416,13 +457,21 @@ class NonBlockingIPShell(object): ''' return self._line_to_execute + def _raw_input(self, prompt=''): + """ A replacement from python's raw_input. + """ + raise NotImplementedError + def _execute(self): ''' Executes the current line provided by the shell object. ''' + orig_stdout = sys.stdout sys.stdout = IPython.Shell.Term.cout - + #self.sys_displayhook_ori = sys.displayhook + #sys.displayhook = self.displayhook + try: line = self._IP.raw_input(None, self._iter_more) if self._IP.autoindent: @@ -440,8 +489,10 @@ class NonBlockingIPShell(object): except: self._IP.showtraceback() else: + self._IP.write(str(self._IP.outputcache.prompt_out).strip()) self._iter_more = self._IP.push(line) - if (self._IP.SyntaxTB.last_syntax_error and self._IP.rc.autoedit_syntax): + if (self._IP.SyntaxTB.last_syntax_error and \ + self._IP.rc.autoedit_syntax): self._IP.edit_syntax_error() if self._iter_more: self._prompt = str(self._IP.outputcache.prompt2).strip() @@ -450,8 +501,10 @@ class NonBlockingIPShell(object): else: self._prompt = str(self._IP.outputcache.prompt1).strip() self._IP.indent_current_nsp = 0 #we set indentation to 0 + sys.stdout = orig_stdout - + #sys.displayhook = self.sys_displayhook_ori + def _shell(self, ip, cmd): ''' Replacement method to allow shell commands without them blocking. @@ -462,7 +515,8 @@ class NonBlockingIPShell(object): @type cmd: string ''' stdin, stdout = os.popen4(cmd) - result = stdout.read().decode('cp437').encode(locale.getpreferredencoding()) + result = stdout.read().decode('cp437').\ + encode(locale.getpreferredencoding()) #we use print command because the shell command is called #inside IPython instance and thus is redirected to thread cout #"\x01\x1b[1;36m\x02" <-- add colour to the text... diff --git a/IPython/gui/wx/ipython_history.py b/IPython/gui/wx/ipython_history.py index 5053886..91f8769 100644 --- a/IPython/gui/wx/ipython_history.py +++ b/IPython/gui/wx/ipython_history.py @@ -29,17 +29,21 @@ class IPythonHistoryPanel(wx.Panel): self.filter_magic = wx.CheckBox(self, -1, "%: Magic keys") self.options={'filter_empty':{'value':'True', - 'checkbox':self.filter_empty,'True':True,'False':False, - 'setfunc':lambda x:None}, + 'checkbox':self.filter_empty, \ + 'True':True,'False':False, + 'setfunc':lambda x:None}, 'filter_doc':{'value':'True', - 'checkbox':self.filter_doc,'True':True,'False':False, - 'setfunc':lambda x:None}, + 'checkbox':self.filter_doc, \ + 'True':True,'False':False, + 'setfunc':lambda x:None}, 'filter_cmd':{'value':'True', - 'checkbox':self.filter_cmd,'True':True,'False':False, - 'setfunc':lambda x:None}, + 'checkbox':self.filter_cmd, \ + 'True':True,'False':False, + 'setfunc':lambda x:None}, 'filter_magic':{'value':'True', - 'checkbox':self.filter_magic,'True':True,'False':False, - 'setfunc':lambda x:None}, + 'checkbox':self.filter_magic, \ + 'True':True,'False':False, + 'setfunc':lambda x:None}, } self.reloadOptions(self.options) @@ -199,51 +203,81 @@ class PythonSTC(stc.StyledTextCtrl): self.SetLayoutCache(stc.STC_CACHE_PAGE) # Setup a margin to hold fold markers - #self.SetFoldFlags(16) ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER? + #self.SetFoldFlags(16) + ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER? self.SetMarginType(2, stc.STC_MARGIN_SYMBOL) self.SetMarginMask(2, stc.STC_MASK_FOLDERS) self.SetMarginSensitive(2, True) self.SetMarginWidth(2, 12) if self.fold_symbols == 0: - # Arrow pointing right for contracted folders, arrow pointing down for expanded - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_ARROWDOWN, "black", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_ARROW, "black", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "black", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "black", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black") + # Arrow pointing right for contracted folders, + # arrow pointing down for expanded + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \ + stc.STC_MARK_ARROWDOWN, "black", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \ + stc.STC_MARK_ARROW, "black", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \ + stc.STC_MARK_EMPTY, "black", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \ + stc.STC_MARK_EMPTY, "black", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \ + stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \ + stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \ + stc.STC_MARK_EMPTY, "white", "black") elif self.fold_symbols == 1: # Plus for contracted folders, minus for expanded - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_MINUS, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_PLUS, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \ + stc.STC_MARK_MINUS, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \ + stc.STC_MARK_PLUS, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \ + stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \ + stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \ + stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \ + stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \ + stc.STC_MARK_EMPTY, "white", "black") elif self.fold_symbols == 2: # Like a flattened tree control using circular headers and curved joins - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_CIRCLEMINUS, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_CIRCLEPLUS, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNERCURVE, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_CIRCLEPLUSCONNECTED, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNERCURVE, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \ + stc.STC_MARK_CIRCLEMINUS, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \ + stc.STC_MARK_CIRCLEPLUS, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \ + stc.STC_MARK_VLINE, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \ + stc.STC_MARK_LCORNERCURVE, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \ + stc.STC_MARK_CIRCLEPLUSCONNECTED, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \ + stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \ + stc.STC_MARK_TCORNERCURVE, "white", "#404040") elif self.fold_symbols == 3: # Like a flattened tree control using square headers - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \ + stc.STC_MARK_BOXMINUS, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \ + stc.STC_MARK_BOXPLUS, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \ + stc.STC_MARK_VLINE, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \ + stc.STC_MARK_LCORNER, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \ + stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \ + stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \ + stc.STC_MARK_TCORNER, "white", "#808080") self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI) @@ -363,7 +397,7 @@ class PythonSTC(stc.StyledTextCtrl): if braceAtCaret < 0: charAfter = self.GetCharAt(caretPos) styleAfter = self.GetStyleAt(caretPos) - + if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR: braceAtCaret = caretPos diff --git a/IPython/gui/wx/ipython_view.py b/IPython/gui/wx/ipython_view.py old mode 100644 new mode 100755 index c189780..2319b28 --- a/IPython/gui/wx/ipython_view.py +++ b/IPython/gui/wx/ipython_view.py @@ -1,5 +1,5 @@ #!/usr/bin/python -# -*- coding: iso-8859-15 -*- +# -*- coding: utf-8 -*- ''' Provides IPython WX console widgets. @@ -19,7 +19,7 @@ available under the terms of the BSD which accompanies this distribution, and is available at U{http://www.opensource.org/licenses/bsd-license.php} ''' -__version__ = 0.8 +__version__ = 0.9 __author__ = "Laurent Dufrechou" __email__ = "laurent.dufrechou _at_ gmail.com" __license__ = "BSD" @@ -33,6 +33,8 @@ from StringIO import StringIO import sys import codecs import locale +import time + for enc in (locale.getpreferredencoding(), sys.getfilesystemencoding(), sys.getdefaultencoding()): @@ -63,17 +65,43 @@ class WxNonBlockingIPShell(NonBlockingIPShell): self.parent = parent self.ask_exit_callback = ask_exit_handler - self._IP.exit = self._askExit + self._IP.exit = self._ask_exit def addGUIShortcut(self, text, func): wx.CallAfter(self.parent.add_button_handler, button_info={ 'text':text, 'func':self.parent.doExecuteLine(func)}) - def _askExit(self): + def _raw_input(self, prompt=''): + """ A replacement from python's raw_input. + """ + self.answer = None + if(self._threading == True): + wx.CallAfter(self._yesNoBox, prompt) + while self.answer is None: + time.sleep(.1) + else: + self._yesNoBox(prompt) + return self.answer + + def _yesNoBox(self, prompt): + """ yes/no box managed with wx.CallAfter jsut in case caler is executed in a thread""" + dlg = wx.TextEntryDialog( + self.parent, prompt, + 'Input requested', 'Python') + dlg.SetValue("") + + answer = '' + if dlg.ShowModal() == wx.ID_OK: + answer = dlg.GetValue() + + dlg.Destroy() + self.answer = answer + + def _ask_exit(self): wx.CallAfter(self.ask_exit_callback, ()) - def _afterExecute(self): + def _after_execute(self): wx.CallAfter(self.parent.evtStateExecuteDone, ()) @@ -249,22 +277,15 @@ class WxConsoleView(stc.StyledTextCtrl): @type text: string ''' try: - #print >>sys.__stdout__,'entering' wx.MutexGuiEnter() - #print >>sys.__stdout__,'locking the GUI' #be sure not to be interrutpted before the MutexGuiLeave! self.write(text) - #print >>sys.__stdout__,'done' - except KeyboardInterrupt: - #print >>sys.__stdout__,'got keyboard interrupt' wx.MutexGuiLeave() - #print >>sys.__stdout__,'interrupt unlock the GUI' raise KeyboardInterrupt wx.MutexGuiLeave() - #print >>sys.__stdout__,'normal unlock the GUI' def write(self, text): @@ -419,7 +440,7 @@ class WxConsoleView(stc.StyledTextCtrl): self.AutoCompSetIgnoreCase(False) self.AutoCompSetAutoHide(False) #let compute the length ot last word - splitter = [' ', '(', '[', '{'] + splitter = [' ', '(', '[', '{','='] last_word = self.getCurrentLine() for breaker in splitter: last_word = last_word.split(breaker)[-1] @@ -439,7 +460,6 @@ class WxConsoleView(stc.StyledTextCtrl): @return: Return True if event as been catched. @rtype: boolean ''' - if not self.AutoCompActive(): if event.GetKeyCode() == wx.WXK_HOME: if event.Modifiers == wx.MOD_NONE: @@ -554,24 +574,30 @@ class IPShellWidget(wx.Panel): #with intro='' if intro is None: welcome_text = "Welcome to WxIPython Shell.\n\n" - welcome_text+= self.IP.getBanner() + welcome_text+= self.IP.get_banner() welcome_text+= "!command -> Execute command in shell\n" welcome_text+= "TAB -> Autocompletion\n" else: welcome_text = intro self.text_ctrl = WxConsoleView(self, - self.IP.getPrompt(), + self.IP.get_prompt(), intro=welcome_text, background_color=background_color) - self.cout.write = self.text_ctrl.asyncWrite - option_text = wx.StaticText(self, -1, "Options:") self.completion_option = wx.CheckBox(self, -1, "Scintilla Completion") + self.completion_option.SetToolTip(wx.ToolTip( + "Selects the completion type:\nEither Ipython default style or Scintilla one")) #self.completion_option.SetValue(False) self.background_option = wx.CheckBox(self, -1, "White Background") + self.background_option.SetToolTip(wx.ToolTip( + "Selects the back ground color: BLACK or WHITE")) #self.background_option.SetValue(False) + self.threading_option = wx.CheckBox(self, -1, "Execute in thread") + self.threading_option.SetToolTip(wx.ToolTip( + "Use threading: infinite loop don't freeze the GUI and commands can be breaked\nNo threading: maximum compatibility")) + #self.threading_option.SetValue(False) self.options={'completion':{'value':'IPYTHON', 'checkbox':self.completion_option,'STC':True,'IPYTHON':False, @@ -579,12 +605,20 @@ class IPShellWidget(wx.Panel): 'background_color':{'value':'BLACK', 'checkbox':self.background_option,'WHITE':True,'BLACK':False, 'setfunc':self.text_ctrl.setBackgroundColor}, + 'threading':{'value':'True', + 'checkbox':self.threading_option,'True':True,'False':False, + 'setfunc':self.IP.set_threading}, } + + #self.cout.write dEfault option is asynchroneous because default sate is threading ON + self.cout.write = self.text_ctrl.asyncWrite + #we reloard options self.reloadOptions(self.options) self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress) self.completion_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionCompletion) self.background_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionBackgroundColor) + self.threading_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionThreading) ### making the layout of the panel ### sizer = wx.BoxSizer(wx.VERTICAL) @@ -596,7 +630,9 @@ class IPShellWidget(wx.Panel): (5, 5), (self.completion_option, 0, wx.ALIGN_CENTER_VERTICAL), (8, 8), - (self.background_option, 0, wx.ALIGN_CENTER_VERTICAL) + (self.background_option, 0, wx.ALIGN_CENTER_VERTICAL), + (8, 8), + (self.threading_option, 0, wx.ALIGN_CENTER_VERTICAL) ]) self.SetAutoLayout(True) sizer.Fit(self) @@ -619,13 +655,15 @@ class IPShellWidget(wx.Panel): self.text_ctrl.write('\n') lines_to_execute = lines.replace('\t',' '*4) lines_to_execute = lines_to_execute.replace('\r','') - self.IP.doExecute(lines_to_execute.encode(ENCODING)) + self.IP.do_execute(lines_to_execute.encode(ENCODING)) self.updateHistoryTracker(lines) + if(self.text_ctrl.getCursorPos()!=0): + self.text_ctrl.removeCurrentLine() self.setCurrentState('WAIT_END_OF_EXECUTION') def evtStateExecuteDone(self,evt): - self.doc = self.IP.getDocText() - self.help = self.IP.getHelpText() + self.doc = self.IP.get_doc_text() + self.help = self.IP.get_help_text() if self.doc: self.pager_lines = self.doc[7:].split('\n') self.pager_state = 'INIT' @@ -637,15 +675,17 @@ class IPShellWidget(wx.Panel): self.setCurrentState('SHOW_DOC') self.pager(self.help) else: + if(self.text_ctrl.getCursorPos()!=0): + self.text_ctrl.removeCurrentLine() self.stateShowPrompt() def stateShowPrompt(self): self.setCurrentState('SHOW_PROMPT') - self.text_ctrl.setPrompt(self.IP.getPrompt()) - self.text_ctrl.setIndentation(self.IP.getIndentation()) - self.text_ctrl.setPromptCount(self.IP.getPromptCount()) + self.text_ctrl.setPrompt(self.IP.get_prompt()) + self.text_ctrl.setIndentation(self.IP.get_indentation()) + self.text_ctrl.setPromptCount(self.IP.get_prompt_count()) self.text_ctrl.showPrompt() - self.IP.initHistoryIndex() + self.IP.init_history_index() self.setCurrentState('IDLE') def setCurrentState(self, state): @@ -751,11 +791,11 @@ class IPShellWidget(wx.Panel): if self.cur_state == 'IDLE': if event.KeyCode == wx.WXK_UP: - history = self.IP.historyBack() + history = self.IP.history_back() self.text_ctrl.writeHistory(history) return if event.KeyCode == wx.WXK_DOWN: - history = self.IP.historyForward() + history = self.IP.history_forward() self.text_ctrl.writeHistory(history) return if event.KeyCode == wx.WXK_TAB: @@ -802,7 +842,20 @@ class IPShellWidget(wx.Panel): self.updateOptionTracker('background_color', self.options['background_color']['value']) self.text_ctrl.SetFocus() - + + def evtCheckOptionThreading(self, event): + if event.IsChecked(): + self.options['threading']['value']='True' + self.IP.set_threading(True) + self.cout.write = self.text_ctrl.asyncWrite + else: + self.options['threading']['value']='False' + self.IP.set_threading(False) + self.cout.write = self.text_ctrl.write + self.updateOptionTracker('threading', + self.options['threading']['value']) + self.text_ctrl.SetFocus() + def getOptions(self): return self.options @@ -813,7 +866,13 @@ class IPShellWidget(wx.Panel): self.options[key]['checkbox'].SetValue(self.options[key][value]) self.options[key]['setfunc'](value) - + if self.options['threading']['value']=='True': + self.IP.set_threading(True) + self.cout.write = self.text_ctrl.asyncWrite + else: + self.IP.set_threading(False) + self.cout.write = self.text_ctrl.write + #------------------------ Hook Section ----------------------------------- def updateOptionTracker(self,name,value): ''' @@ -881,5 +940,3 @@ if __name__ == '__main__': shell = frame.shell app.MainLoop() - - diff --git a/IPython/gui/wx/wxIPython.py b/IPython/gui/wx/wxIPython.py index 0d0d249..b724e53 100644 --- a/IPython/gui/wx/wxIPython.py +++ b/IPython/gui/wx/wxIPython.py @@ -10,10 +10,18 @@ from wx.lib.wordwrap import wordwrap from IPython.gui.wx.ipython_view import IPShellWidget from IPython.gui.wx.ipython_history import IPythonHistoryPanel +#used to invoke ipython1 wx implementation +### FIXME ### temporary disabled due to interference with 'show_in_pager' hook +is_sync_frontend_ok = False +try: + from IPython.frontend.wx.ipythonx import IPythonXController +except ImportError: + is_sync_frontend_ok = False + #used to create options.conf file in user directory from IPython.ipapi import get -__version__ = 0.8 +__version__ = 0.91 __author__ = "Laurent Dufrechou" __email__ = "laurent.dufrechou _at_ gmail.com" __license__ = "BSD" @@ -27,7 +35,7 @@ class MyFrame(wx.Frame): application with movables windows""" def __init__(self, parent=None, id=-1, title="WxIPython", pos=wx.DefaultPosition, - size=(800, 600), style=wx.DEFAULT_FRAME_STYLE): + size=(800, 600), style=wx.DEFAULT_FRAME_STYLE, sync_ok=False): wx.Frame.__init__(self, parent, id, title, pos, size, style) self._mgr = wx.aui.AuiManager() @@ -41,12 +49,18 @@ class MyFrame(wx.Frame): self.ipython_panel = IPShellWidget(self,background_color = "BLACK") #self.ipython_panel = IPShellWidget(self,background_color = "WHITE") - + if(sync_ok): + self.ipython_panel2 = IPythonXController(self) + else: + self.ipython_panel2 = None self.ipython_panel.setHistoryTrackerHook(self.history_panel.write) self.ipython_panel.setStatusTrackerHook(self.updateStatus) self.ipython_panel.setAskExitHandler(self.OnExitDlg) self.ipython_panel.setOptionTrackerHook(self.optionSave) + #Create a notebook to display different IPython shell implementations + self.nb = wx.aui.AuiNotebook(self) + self.optionLoad() self.statusbar = self.createStatus() @@ -55,7 +69,11 @@ class MyFrame(wx.Frame): ######################################################################## ### add the panes to the manager # main panels - self._mgr.AddPane(self.ipython_panel , wx.CENTER, "IPython Shell") + self._mgr.AddPane(self.nb , wx.CENTER, "IPython Shells") + self.nb.AddPage(self.ipython_panel , "IPython0 Shell") + if(sync_ok): + self.nb.AddPage(self.ipython_panel2, "IPython1 Synchroneous Shell") + self._mgr.AddPane(self.history_panel , wx.RIGHT, "IPython history") # now we specify some panel characteristics @@ -77,7 +95,10 @@ class MyFrame(wx.Frame): warn_text = 'Hello from IPython and wxPython.\n' warn_text +='Please Note that this work is still EXPERIMENTAL\n' warn_text +='It does NOT emulate currently all the IPython functions.\n' - + warn_text +="\nIf you use MATPLOTLIB with show() you'll need to deactivate the THREADING option.\n" + if(not sync_ok): + warn_text +="\n->No twisted package detected, IPython1 example deactivated." + dlg = wx.MessageDialog(self, warn_text, 'Warning Box', @@ -146,13 +167,6 @@ class MyFrame(wx.Frame): about_menu = wx.Menu() about_menu.Append(wx.ID_HIGHEST+3, "About") - #view_menu.AppendSeparator() - #options_menu = wx.Menu() - #options_menu.AppendCheckItem(wx.ID_HIGHEST+7, "Allow Floating") - #options_menu.AppendCheckItem(wx.ID_HIGHEST+8, "Transparent Hint") - #options_menu.AppendCheckItem(wx.ID_HIGHEST+9, "Transparent Hint Fade-in") - - mb.Append(file_menu, "File") mb.Append(view_menu, "View") mb.Append(about_menu, "About") @@ -233,17 +247,17 @@ class MyFrame(wx.Frame): #----------------------------------------- class MyApp(wx.PySimpleApp): """Creating our application""" - def __init__(self): + def __init__(self, sync_ok=False): wx.PySimpleApp.__init__(self) - self.frame = MyFrame() + self.frame = MyFrame(sync_ok=sync_ok) self.frame.Show() #----------------------------------------- #Main loop #----------------------------------------- def main(): - app = MyApp() + app = MyApp(is_sync_frontend_ok) app.SetTopWindow(app.frame) app.MainLoop() diff --git a/IPython/history.py b/IPython/history.py index 004b820..40508c8 100644 --- a/IPython/history.py +++ b/IPython/history.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - """ History related magics and functionality """ # Stdlib imports @@ -7,7 +6,7 @@ import fnmatch import os # IPython imports -from IPython.genutils import Term, ask_yes_no +from IPython.genutils import Term, ask_yes_no, warn import IPython.ipapi def magic_history(self, parameter_s = ''): @@ -47,8 +46,6 @@ def magic_history(self, parameter_s = ''): -f FILENAME: instead of printing the output to the screen, redirect it to the given file. The file is always overwritten, though IPython asks for confirmation first if it already exists. - - """ ip = self.api @@ -62,31 +59,28 @@ def magic_history(self, parameter_s = ''): try: outfname = opts['f'] except KeyError: - outfile = Term.cout + outfile = Term.cout # default # We don't want to close stdout at the end! close_at_end = False else: if os.path.exists(outfname): - ans = ask_yes_no("File %r exists. Overwrite?" % outfname) - if not ans: + if not ask_yes_no("File %r exists. Overwrite?" % outfname): print 'Aborting.' return - else: - outfile = open(outfname,'w') - close_at_end = True - - if opts.has_key('t'): + outfile = open(outfname,'w') + close_at_end = True + + if 't' in opts: input_hist = shell.input_hist - elif opts.has_key('r'): + elif 'r' in opts: input_hist = shell.input_hist_raw else: input_hist = shell.input_hist - - + default_length = 40 pattern = None - if opts.has_key('g'): + if 'g' in opts: init = 1 final = len(input_hist) parts = parameter_s.split(None,1) @@ -138,13 +132,11 @@ def magic_history(self, parameter_s = ''): outfile.close() - def magic_hist(self, parameter_s=''): """Alternate name for %history.""" return self.magic_history(parameter_s) - def rep_f(self, arg): r""" Repeat a command, or get command to input line for editing @@ -173,11 +165,9 @@ def rep_f(self, arg): %rep foo Place the most recent line that has the substring "foo" to next input. - (e.g. 'svn ci -m foobar'). - + (e.g. 'svn ci -m foobar'). """ - opts,args = self.parse_options(arg,'',mode='list') ip = self.api if not args: @@ -206,7 +196,6 @@ def rep_f(self, arg): ip.set_next_input(str(h).rstrip()) return - try: lines = self.extract_input_slices(args, True) print "lines",lines @@ -215,7 +204,6 @@ def rep_f(self, arg): print "Not found in recent history:", args - _sentinel = object() class ShadowHist: @@ -259,23 +247,12 @@ class ShadowHist: if k == idx: return v -def test_shist(): - from IPython.Extensions import pickleshare - db = pickleshare.PickleShareDB('~/shist') - s = ShadowHist(db) - s.add('hello') - s.add('world') - s.add('hello') - s.add('hello') - s.add('karhu') - print "all",s.all() - print s.get(2) def init_ipython(ip): + import ipy_completers + ip.expose_magic("rep",rep_f) ip.expose_magic("hist",magic_hist) ip.expose_magic("history",magic_history) - import ipy_completers ipy_completers.quick_completer('%hist' ,'-g -t -r -n') -#test_shist() diff --git a/IPython/hooks.py b/IPython/hooks.py index fdbea8e..dce185e 100644 --- a/IPython/hooks.py +++ b/IPython/hooks.py @@ -32,8 +32,7 @@ ip.set_hook('editor', calljed) You can then enable the functionality by doing 'import myiphooks' somewhere in your configuration files or ipython command line. - -$Id: hooks.py 2998 2008-01-31 10:06:04Z vivainio $""" +""" #***************************************************************************** # Copyright (C) 2005 Fernando Perez. @@ -42,11 +41,7 @@ $Id: hooks.py 2998 2008-01-31 10:06:04Z vivainio $""" # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release from IPython import ipapi -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license -__version__ = Release.version import os,bisect from genutils import Term,shell diff --git a/IPython/ipapi.py b/IPython/ipapi.py index 81580cd..da6bcf4 100644 --- a/IPython/ipapi.py +++ b/IPython/ipapi.py @@ -24,7 +24,6 @@ That way the module is imported at startup and you can have all your personal configuration (as opposed to boilerplate ipythonrc-PROFILENAME stuff) in there. ------------------------------------------------ import IPython.ipapi ip = IPython.ipapi.get() @@ -187,7 +186,6 @@ class IPApi(object): self.set_custom_exc = ip.set_custom_exc self.user_ns = ip.user_ns - self.user_ns['_ip'] = self self.set_crash_handler = ip.set_crash_handler diff --git a/IPython/iplib.py b/IPython/iplib.py index 4084f66..612fa2e 100644 --- a/IPython/iplib.py +++ b/IPython/iplib.py @@ -2,10 +2,9 @@ """ IPython -- An enhanced Interactive Python -Requires Python 2.3 or newer. +Requires Python 2.4 or newer. This file contains all the classes and helper functions specific to IPython. - """ #***************************************************************************** @@ -27,12 +26,6 @@ This file contains all the classes and helper functions specific to IPython. #**************************************************************************** # Modules and globals -from IPython import Release -__author__ = '%s <%s>\n%s <%s>' % \ - ( Release.authors['Janko'] + Release.authors['Fernando'] ) -__license__ = Release.license -__version__ = Release.version - # Python standard modules import __main__ import __builtin__ @@ -54,9 +47,6 @@ import sys import tempfile import traceback import types -import warnings -warnings.filterwarnings('ignore', r'.*sets module*') -from sets import Set from pprint import pprint, pformat # IPython's own modules @@ -292,6 +282,13 @@ class InteractiveShell(object,Magic): # This is the namespace where all normal user variables live self.user_ns = user_ns self.user_global_ns = user_global_ns + + # An auxiliary namespace that checks what parts of the user_ns were + # loaded at startup, so we can list later only variables defined in + # actual interactive use. Since it is always a subset of user_ns, it + # doesn't need to be seaparately tracked in the ns_table + self.user_config_ns = {} + # A namespace to keep track of internal data structures to prevent # them from cluttering user-visible stuff. Will be updated later self.internal_ns = {} @@ -301,6 +298,24 @@ class InteractiveShell(object,Magic): # of positional arguments of the alias. self.alias_table = {} + # Now that FakeModule produces a real module, we've run into a nasty + # problem: after script execution (via %run), the module where the user + # code ran is deleted. Now that this object is a true module (needed + # so docetst and other tools work correctly), the Python module + # teardown mechanism runs over it, and sets to None every variable + # present in that module. Top-level references to objects from the + # script survive, because the user_ns is updated with them. However, + # calling functions defined in the script that use other things from + # the script will fail, because the function's closure had references + # to the original objects, which are now all None. So we must protect + # these modules from deletion by keeping a cache. To avoid keeping + # stale modules around (we only need the one from the last run), we use + # a dict keyed with the full path to the script, so only the last + # version of the module is held in the cache. The %reset command will + # flush this cache. See the cache_main_mod() and clear_main_mod_cache() + # methods for details on use. + self._user_main_modules = {} + # A table holding all the namespaces IPython deals with, so that # introspection facilities can search easily. self.ns_table = {'user':user_ns, @@ -309,9 +324,14 @@ class InteractiveShell(object,Magic): 'internal':self.internal_ns, 'builtin':__builtin__.__dict__ } - # The user namespace MUST have a pointer to the shell itself. - self.user_ns[name] = self + # Similarly, track all namespaces where references can be held and that + # we can safely clear (so it can NOT include builtin). This one can be + # a simple list. + self.ns_refs_table = [ user_ns, user_global_ns, self.user_config_ns, + self.alias_table, self.internal_ns, + self._user_main_modules ] + # We need to insert into sys.modules something that looks like a # module but which accesses the IPython namespace, for shelve and # pickle to work interactively. Normally they rely on getting @@ -336,28 +356,13 @@ class InteractiveShell(object,Magic): #print "pickle hack in place" # dbg #print 'main_name:',main_name # dbg sys.modules[main_name] = FakeModule(self.user_ns) - - # Now that FakeModule produces a real module, we've run into a nasty - # problem: after script execution (via %run), the module where the user - # code ran is deleted. Now that this object is a true module (needed - # so docetst and other tools work correctly), the Python module - # teardown mechanism runs over it, and sets to None every variable - # present in that module. This means that later calls to functions - # defined in the script (which have become interactively visible after - # script exit) fail, because they hold references to objects that have - # become overwritten into None. The only solution I see right now is - # to protect every FakeModule used by %run by holding an internal - # reference to it. This private list will be used for that. The - # %reset command will flush it as well. - self._user_main_modules = [] - + # List of input with multi-line handling. - # Fill its zero entry, user counter starts at 1 - self.input_hist = InputList(['\n']) + self.input_hist = InputList() # This one will hold the 'raw' input history, without any # pre-processing. This will allow users to retrieve the input just as # it was exactly typed in by the user, with %hist -r. - self.input_hist_raw = InputList(['\n']) + self.input_hist_raw = InputList() # list of visited directories try: @@ -383,17 +388,7 @@ class InteractiveShell(object,Magic): no_alias[key] = 1 no_alias.update(__builtin__.__dict__) self.no_alias = no_alias - - # make global variables for user access to these - self.user_ns['_ih'] = self.input_hist - self.user_ns['_oh'] = self.output_hist - self.user_ns['_dh'] = self.dir_hist - - # user aliases to input and output histories - self.user_ns['In'] = self.input_hist - self.user_ns['Out'] = self.output_hist - self.user_ns['_sh'] = IPython.shadowns # Object variable to store code object waiting execution. This is # used mainly by the multithreaded shells, but it can come in handy in # other situations. No need to use a Queue here, since it's a single @@ -586,11 +581,13 @@ class InteractiveShell(object,Magic): else: auto_alias = () self.auto_alias = [s.split(None,1) for s in auto_alias] - # Produce a public API instance self.api = IPython.ipapi.IPApi(self) + # Initialize all user-visible namespaces + self.init_namespaces() + # Call the actual (public) initializer self.init_auto_alias() @@ -601,10 +598,6 @@ class InteractiveShell(object,Magic): #TODO: remove this, redundant self.add_builtins() - - - - # end __init__ def var_expand(self,cmd,depth=0): @@ -633,16 +626,15 @@ class InteractiveShell(object,Magic): """ rc = self.rc try: - self.db = pickleshare.PickleShareDB(rc.ipythondir + "/db") + self.db = pickleshare.PickleShareDB(rc.ipythondir + "/db") except exceptions.UnicodeDecodeError: print "Your ipythondir can't be decoded to unicode!" print "Please set HOME environment variable to something that" print r"only has ASCII characters, e.g. c:\home" print "Now it is",rc.ipythondir sys.exit() - self.shadowhist = IPython.history.ShadowHist(self.db) - - + self.shadowhist = IPython.history.ShadowHist(self.db) + def post_config_initialization(self): """Post configuration init method @@ -662,7 +654,6 @@ class InteractiveShell(object,Magic): # Load readline proper if rc.readline: self.init_readline() - # local shortcut, this is used a LOT self.log = self.logger.log @@ -729,6 +720,39 @@ class InteractiveShell(object,Magic): if batchrun and not self.rc.interact: self.ask_exit() + def init_namespaces(self): + """Initialize all user-visible namespaces to their minimum defaults. + + Certain history lists are also initialized here, as they effectively + act as user namespaces. + + Note + ---- + All data structures here are only filled in, they are NOT reset by this + method. If they were not empty before, data will simply be added to + therm. + """ + # The user namespace MUST have a pointer to the shell itself. + self.user_ns[self.name] = self + + # Store the public api instance + self.user_ns['_ip'] = self.api + + # make global variables for user access to the histories + self.user_ns['_ih'] = self.input_hist + self.user_ns['_oh'] = self.output_hist + self.user_ns['_dh'] = self.dir_hist + + # user aliases to input and output histories + self.user_ns['In'] = self.input_hist + self.user_ns['Out'] = self.output_hist + + self.user_ns['_sh'] = IPython.shadowns + + # Fill the history zero entry, user counter starts at 1 + self.input_hist.append('\n') + self.input_hist_raw.append('\n') + def add_builtins(self): """Store ipython references into the builtin namespace. @@ -909,7 +933,6 @@ class InteractiveShell(object,Magic): call_pdb = property(_get_call_pdb,_set_call_pdb,None, 'Control auto-activation of pdb at exceptions') - # These special functions get installed in the builtin namespace, to # provide programmatic (pure python) access to magics, aliases and system # calls. This is important for logging, user scripting, and more. @@ -1139,7 +1162,8 @@ IPython will create a minimal default configuration for you. inif = 'ipythonrc.ini' else: inif = 'ipythonrc' - minimal_setup = {'ipy_user_conf.py' : 'import ipy_defaults', inif : '# intentionally left blank' } + minimal_setup = {'ipy_user_conf.py' : 'import ipy_defaults', + inif : '# intentionally left blank' } os.makedirs(ipythondir, mode = 0777) for f, cont in minimal_setup.items(): open(ipythondir + '/' + f,'w').write(cont) @@ -1257,7 +1281,27 @@ want to merge them back into the new files.""" % locals() except OSError: pass + # Clear all user namespaces to release all references cleanly. + self.reset() + + # Run user hooks self.hooks.shutdown_hook() + + def reset(self): + """Clear all internal namespaces. + + Note that this is much more aggressive than %reset, since it clears + fully all namespaces, as well as all input/output lists. + """ + for ns in self.ns_refs_table: + ns.clear() + + # Clear input and output histories + self.input_hist[:] = [] + self.input_hist_raw[:] = [] + self.output_hist.clear() + # Restore the user namespaces to minimal usability + self.init_namespaces() def savehist(self): """Save input history to a file (via readline library).""" @@ -1298,7 +1342,6 @@ want to merge them back into the new files.""" % locals() finally: readline.read_history_file(self.histfile) return wrapper - def pre_readline(self): """readline hook to be used at the start of each line. @@ -1371,6 +1414,7 @@ want to merge them back into the new files.""" % locals() # not run as the syntax for libedit is different. if not readline.uses_libedit: for rlcommand in self.rc.readline_parse_and_bind: + #print "loading rl:",rlcommand # dbg readline.parse_and_bind(rlcommand) # remove some chars from the delimiters list @@ -1396,7 +1440,59 @@ want to merge them back into the new files.""" % locals() if self.rc.quiet: return True return ask_yes_no(prompt,default) - + + def cache_main_mod(self,mod): + """Cache a main module. + + When scripts are executed via %run, we must keep a reference to their + __main__ module (a FakeModule instance) around so that Python doesn't + clear it, rendering objects defined therein useless. + + This method keeps said reference in a private dict, keyed by the + absolute path of the module object (which corresponds to the script + path). This way, for multiple executions of the same script we only + keep one copy of __main__ (the last one), thus preventing memory leaks + from old references while allowing the objects from the last execution + to be accessible. + + Parameters + ---------- + mod : a module object + + Examples + -------- + + In [10]: import IPython + + In [11]: _ip.IP.cache_main_mod(IPython) + + In [12]: IPython.__file__ in _ip.IP._user_main_modules + Out[12]: True + """ + self._user_main_modules[os.path.abspath(mod.__file__) ] = mod + + def clear_main_mod_cache(self): + """Clear the cache of main modules. + + Mainly for use by utilities like %reset. + + Examples + -------- + + In [15]: import IPython + + In [16]: _ip.IP.cache_main_mod(IPython) + + In [17]: len(_ip.IP._user_main_modules) > 0 + Out[17]: True + + In [18]: _ip.IP.clear_main_mod_cache() + + In [19]: len(_ip.IP._user_main_modules) == 0 + Out[19]: True + """ + self._user_main_modules.clear() + def _should_recompile(self,e): """Utility routine for edit_syntax_error""" @@ -1556,8 +1652,6 @@ want to merge them back into the new files.""" % locals() self.set_completer() except KeyboardInterrupt: self.write("\nKeyboardInterrupt\n") - - def mainloop(self,banner=None): """Creates the local namespace and starts the mainloop. @@ -1585,7 +1679,9 @@ want to merge them back into the new files.""" % locals() try: self.interact(banner) #self.interact_with_readline() - # XXX for testing of a readline-decoupled repl loop, call interact_with_readline above + + # XXX for testing of a readline-decoupled repl loop, call + # interact_with_readline above break except KeyboardInterrupt: @@ -1866,7 +1962,7 @@ want to merge them back into the new files.""" % locals() """ line = fn + " " + rest - done = Set() + done = set() while 1: pre,fn,rest = prefilter.splitUserInput(line, prefilter.shell_line_split) @@ -1978,7 +2074,6 @@ want to merge them back into the new files.""" % locals() # NOT skip even a blank line if we are in a code block (more is # true) - if line or more: # push to raw history, so hist line numbers stay in sync self.input_hist_raw.append("# " + line + "\n") diff --git a/IPython/ipmaker.py b/IPython/ipmaker.py index 6065535..08244d4 100644 --- a/IPython/ipmaker.py +++ b/IPython/ipmaker.py @@ -5,32 +5,28 @@ IPython -- An enhanced Interactive Python Requires Python 2.1 or better. This file contains the main make_IPython() starter function. - -$Id: ipmaker.py 2930 2008-01-11 07:03:11Z vivainio $""" +""" #***************************************************************************** -# Copyright (C) 2001-2006 Fernando Perez. +# Copyright (C) 2008-2009 The IPython Development Team +# Copyright (C) 2001-2007 Fernando Perez. # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license -__version__ = Release.version - try: credits._Printer__data = """ Python: %s - IPython: Fernando Perez, Janko Hauser, Nathan Gray, and many users. + IPython: The IPython Development Team. See http://ipython.scipy.org for more information.""" \ % credits._Printer__data copyright._Printer__data += """ - Copyright (c) 2001-2004 Fernando Perez, Janko Hauser, Nathan Gray. + Copyright (c) 2008-2009 The IPython Development Team. + Copyright (c) 2001-2007 Fernando Perez, Janko Hauser, Nathan Gray. All Rights Reserved.""" except NameError: # Can happen if ipython was started with 'python -S', so that site.py is @@ -51,6 +47,7 @@ from pprint import pprint,pformat # Our own from IPython import DPyGetOpt +from IPython import Release from IPython.ipstruct import Struct from IPython.OutputTrap import OutputTrap from IPython.ConfigLoader import ConfigLoader @@ -108,8 +105,6 @@ def make_IPython(argv=None,user_ns=None,user_global_ns=None,debug=1, IP.user_ns['help'] = _Helper() except ImportError: warn('help() not available - check site.py') - IP.user_config_ns = {} - if DEVDEBUG: # For developer debugging only (global flag) @@ -121,7 +116,7 @@ def make_IPython(argv=None,user_ns=None,user_global_ns=None,debug=1, 'for more information.\n' % (sys.version.split('\n')[0],), "IPython %s -- An enhanced Interactive Python." - % (__version__,), + % (Release.version,), """\ ? -> Introduction and overview of IPython's features. %quickref -> Quick reference. @@ -131,20 +126,15 @@ object? -> Details about 'object'. ?object also works, ?? prints more. IP.usage = interactive_usage - # Platform-dependent suffix and directory names. We use _ipython instead - # of .ipython under win32 b/c there's software that breaks with .named - # directories on that platform. + # Platform-dependent suffix. if os.name == 'posix': rc_suffix = '' - ipdir_def = '.ipython' else: rc_suffix = '.ini' - ipdir_def = '_ipython' # default directory for configuration - ipythondir_def = os.path.abspath(os.environ.get('IPYTHONDIR', - os.path.join(IP.home_dir,ipdir_def))) - + ipythondir_def = get_ipython_dir() + sys.path.insert(0, '') # add . to sys.path. Fix from Prabhu Ramachandran # we need the directory where IPython itself is installed @@ -335,7 +325,7 @@ object? -> Details about 'object'. ?object also works, ?? prints more. sys.exit() if opts_all.Version: - print __version__ + print Release.version sys.exit() if opts_all.magic_docstrings: diff --git a/IPython/ipstruct.py b/IPython/ipstruct.py index b85e9d5..ef11c47 100644 --- a/IPython/ipstruct.py +++ b/IPython/ipstruct.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Mimic C structs with lots of extra functionality. - -$Id: ipstruct.py 1950 2006-11-28 19:15:35Z vivainio $""" +""" #***************************************************************************** # Copyright (C) 2001-2004 Fernando Perez @@ -10,10 +9,6 @@ $Id: ipstruct.py 1950 2006-11-28 19:15:35Z vivainio $""" # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license - __all__ = ['Struct'] import types @@ -163,8 +158,22 @@ class Struct: return self.__dict__[key] def __contains__(self,key): - """Allows use of the 'in' operator.""" - return self.__dict__.has_key(key) + """Allows use of the 'in' operator. + + Examples: + >>> s = Struct(x=1) + >>> 'x' in s + True + >>> 'y' in s + False + >>> s[4] = None + >>> 4 in s + True + >>> s.z = None + >>> 'z' in s + True + """ + return key in self.__dict__ def __iadd__(self,other): """S += S2 is a shorthand for S.merge(S2).""" @@ -246,12 +255,13 @@ class Struct: Optionally, one or more key=value pairs can be given at the end for direct update.""" - # The funny name __loc_data__ is to prevent a common variable name which - # could be a fieled of a Struct to collide with this parameter. The problem - # would arise if the function is called with a keyword with this same name - # that a user means to add as a Struct field. + # The funny name __loc_data__ is to prevent a common variable name + # which could be a fieled of a Struct to collide with this + # parameter. The problem would arise if the function is called with a + # keyword with this same name that a user means to add as a Struct + # field. newdict = Struct.__make_dict(self,__loc_data__,**kw) - for k,v in newdict.items(): + for k,v in newdict.iteritems(): self[k] = v def merge(self,__loc_data__=None,__conflict_solve=None,**kw): diff --git a/IPython/kernel/core/prompts.py b/IPython/kernel/core/prompts.py index 4c572dc..be654ac 100644 --- a/IPython/kernel/core/prompts.py +++ b/IPython/kernel/core/prompts.py @@ -1,6 +1,10 @@ # encoding: utf-8 +"""Classes for handling input/output prompts. -"""Classes for handling input/output prompts.""" +Authors +------- +- Fernando Perez +""" __docformat__ = "restructuredtext en" @@ -15,12 +19,6 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license -__version__ = Release.version - -#**************************************************************************** # Required modules import __builtin__ import os @@ -32,12 +30,11 @@ import time from IPython.external.Itpl import ItplNS from macro import Macro -# Temporarily use this until it is ported to ipython1 - from IPython import ColorANSI +from IPython import Release +from IPython.ipapi import TryNext from IPython.ipstruct import Struct from IPython.genutils import * -from IPython.ipapi import TryNext #**************************************************************************** #Color schemes for Prompts. @@ -159,7 +156,7 @@ prompt_specials_color = { # Carriage return r'\r': '\r', # Release version - r'\v': __version__, + r'\v': Release.version, # Root symbol ($ or #) r'\$': ROOT_SYMBOL, } diff --git a/IPython/kernel/core/ultraTB.py b/IPython/kernel/core/ultraTB.py index 123d6ae..a11a0eb 100644 --- a/IPython/kernel/core/ultraTB.py +++ b/IPython/kernel/core/ultraTB.py @@ -1,5 +1,4 @@ -# encoding: utf-8 - +# -*- coding: utf-8 -*- """ ultraTB.py -- Spice up your tracebacks! @@ -60,26 +59,15 @@ ColorSchemeTable class. Currently the following exist: You can implement other color schemes easily, the syntax is fairly self-explanatory. Please send back new schemes you develop to the author for possible inclusion in future releases. +""" -$Id: ultraTB.py 2480 2007-07-06 19:33:43Z fperez $""" - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team +#***************************************************************************** +# Copyright (C) 2001 Nathaniel Gray +# Copyright (C) 2001-2004 Fernando Perez # # 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 import Release -__author__ = '%s <%s>\n%s <%s>' % (Release.authors['Nathan']+ - Release.authors['Fernando']) -__license__ = Release.license +#***************************************************************************** # Required modules import inspect @@ -104,7 +92,7 @@ from inspect import getsourcefile, getfile, getmodule,\ # Modified pdb which doesn't damage IPython's readline handling from IPython import Debugger, PyColorize from IPython.ipstruct import Struct -from IPython.excolors import ExceptionColors +from IPython.excolors import exception_colors from IPython.genutils import Term,uniq_stable,error,info # Globals @@ -141,11 +129,18 @@ def findsource(object): FIXED version with which we monkeypatch the stdlib to work around a bug.""" file = getsourcefile(object) or getfile(object) - module = getmodule(object, file) - if module: - lines = linecache.getlines(file, module.__dict__) + # If the object is a frame, then trying to get the globals dict from its + # module won't work. Instead, the frame object itself has the globals + # dictionary. + globals_dict = None + if inspect.isframe(object): + # XXX: can this ever be false? + globals_dict = object.f_globals else: - lines = linecache.getlines(file) + module = getmodule(object, file) + if module: + globals_dict = module.__dict__ + lines = linecache.getlines(file, globals_dict) if not lines: raise IOError('could not get source code') @@ -202,11 +197,31 @@ def findsource(object): if sys.version_info[:2] >= (2,5): inspect.findsource = findsource +def fix_frame_records_filenames(records): + """Try to fix the filenames in each record from inspect.getinnerframes(). + + Particularly, modules loaded from within zip files have useless filenames + attached to their code object, and inspect.getinnerframes() just uses it. + """ + fixed_records = [] + for frame, filename, line_no, func_name, lines, index in records: + # Look inside the frame's globals dictionary for __file__, which should + # be better. + better_fn = frame.f_globals.get('__file__', None) + if isinstance(better_fn, str): + # Check the type just in case someone did something weird with + # __file__. It might also be None if the error occurred during + # import. + filename = better_fn + fixed_records.append((frame, filename, line_no, func_name, lines, index)) + return fixed_records + + def _fixed_getinnerframes(etb, context=1,tb_offset=0): import linecache LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5 - records = inspect.getinnerframes(etb, context) + records = fix_frame_records_filenames(inspect.getinnerframes(etb, context)) # If the error is at the console, don't build any context, since it would # otherwise produce 5 blank lines printed out (there is no file at the @@ -299,7 +314,7 @@ class TBTools: self.call_pdb = call_pdb # Create color table - self.color_scheme_table = ExceptionColors + self.color_scheme_table = exception_colors() self.set_colors(color_scheme) self.old_scheme = color_scheme # save initial value for toggles @@ -356,8 +371,8 @@ class ListTB(TBTools): def __call__(self, etype, value, elist): Term.cout.flush() - Term.cerr.flush() print >> Term.cerr, self.text(etype,value,elist) + Term.cerr.flush() def text(self,etype, value, elist,context=5): """Return a color formatted string with the traceback info.""" @@ -424,7 +439,8 @@ class ListTB(TBTools): Also lifted nearly verbatim from traceback.py """ - + + have_filedata = False Colors = self.Colors list = [] try: @@ -438,8 +454,9 @@ class ListTB(TBTools): try: msg, (filename, lineno, offset, line) = value except: - pass + have_filedata = False else: + have_filedata = True #print 'filename is',filename # dbg if not filename: filename = "" list.append('%s File %s"%s"%s, line %s%d%s\n' % \ @@ -469,6 +486,12 @@ class ListTB(TBTools): Colors.Normal, s)) else: list.append('%s\n' % str(stype)) + + # vds:>> + if have_filedata: + __IPYTHON__.hooks.synchronize_with_editor(filename, lineno, 0) + # vds:<< + return list def _some_str(self, value): @@ -780,6 +803,15 @@ class VerboseTB(TBTools): for name in names: value = text_repr(getattr(evalue, name)) exception.append('\n%s%s = %s' % (indent, name, value)) + + # vds: >> + if records: + filepath, lnum = records[-1][1:3] + #print "file:", str(file), "linenb", str(lnum) # dbg + filepath = os.path.abspath(filepath) + __IPYTHON__.hooks.synchronize_with_editor(filepath, lnum, 0) + # vds: << + # return all our info assembled as a single string return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) ) @@ -834,8 +866,8 @@ class VerboseTB(TBTools): (etype, evalue, etb) = info or sys.exc_info() self.tb = etb Term.cout.flush() - Term.cerr.flush() print >> Term.cerr, self.text(etype, evalue, etb) + Term.cerr.flush() # Changed so an instance can just be called as VerboseTB_inst() and print # out the right info on its own. @@ -845,7 +877,10 @@ class VerboseTB(TBTools): self.handler() else: self.handler((etype, evalue, etb)) - self.debugger() + try: + self.debugger() + except KeyboardInterrupt: + print "\nKeyboardInterrupt" #---------------------------------------------------------------------------- class FormattedTB(VerboseTB,ListTB): @@ -953,14 +988,17 @@ class AutoFormattedTB(FormattedTB): if out is None: out = Term.cerr Term.cout.flush() - out.flush() if tb_offset is not None: tb_offset, self.tb_offset = self.tb_offset, tb_offset print >> out, self.text(etype, evalue, etb) self.tb_offset = tb_offset else: print >> out, self.text(etype, evalue, etb) - self.debugger() + out.flush() + try: + self.debugger() + except KeyboardInterrupt: + print "\nKeyboardInterrupt" def text(self,etype=None,value=None,tb=None,context=5,mode=None): if etype is None: diff --git a/IPython/kernel/core/util.py b/IPython/kernel/core/util.py index 7465aff..1dceb4a 100644 --- a/IPython/kernel/core/util.py +++ b/IPython/kernel/core/util.py @@ -70,8 +70,8 @@ def esc_quotes(strng): def make_quoted_expr(s): """Return string s in appropriate quotes, using raw string if possible. - Effectively this turns string: cd \ao\ao\ - to: r"cd \ao\ao\_"[:-1] + XXX - example removed because it caused encoding errors in documentation + generation. We need a new example that doesn't contain invalid chars. Note the use of raw string and padding at the end to allow trailing backslash. diff --git a/IPython/kernel/engineservice.py b/IPython/kernel/engineservice.py index 26577f2..b301f0f 100644 --- a/IPython/kernel/engineservice.py +++ b/IPython/kernel/engineservice.py @@ -693,7 +693,7 @@ class QueuedEngine(object): @queue def execute(self, lines): pass - + @queue def push(self, namespace): pass diff --git a/IPython/kernel/multiengine.py b/IPython/kernel/multiengine.py index 6468a04..4d1fe6c 100644 --- a/IPython/kernel/multiengine.py +++ b/IPython/kernel/multiengine.py @@ -335,7 +335,7 @@ class MultiEngine(ControllerAdapterBase): #--------------------------------------------------------------------------- # IEngineMultiplexer methods #--------------------------------------------------------------------------- - + def execute(self, lines, targets='all'): return self._performOnEnginesAndGatherBoth('execute', lines, targets=targets) diff --git a/IPython/kernel/multienginefc.py b/IPython/kernel/multienginefc.py index ec51e47..30de28d 100644 --- a/IPython/kernel/multienginefc.py +++ b/IPython/kernel/multienginefc.py @@ -131,7 +131,7 @@ class FCSynchronousMultiEngineFromMultiEngine(Referenceable): def _addDeferredIDCallback(self, did, callback, *args, **kwargs): self._deferredIDCallbacks[did] = (callback, args, kwargs) return did - + #--------------------------------------------------------------------------- # IEngineMultiplexer related methods #--------------------------------------------------------------------------- @@ -346,7 +346,7 @@ class FCFullSynchronousMultiEngineClient(object): #--------------------------------------------------------------------------- # IEngineMultiplexer related methods #--------------------------------------------------------------------------- - + def execute(self, lines, targets='all', block=True): d = self.remote_reference.callRemote('execute', lines, targets, block) d.addCallback(self.unpackage) diff --git a/IPython/kernel/scripts/ipcluster b/IPython/kernel/scripts/ipcluster old mode 100644 new mode 100755 diff --git a/IPython/kernel/scripts/ipcluster.py b/IPython/kernel/scripts/ipcluster.py old mode 100644 new mode 100755 index 02f8060..640a41a --- a/IPython/kernel/scripts/ipcluster.py +++ b/IPython/kernel/scripts/ipcluster.py @@ -18,19 +18,24 @@ import os import re import sys import signal +import tempfile pjoin = os.path.join from twisted.internet import reactor, defer from twisted.internet.protocol import ProcessProtocol -from twisted.python import failure, log from twisted.internet.error import ProcessDone, ProcessTerminated from twisted.internet.utils import getProcessOutput +from twisted.python import failure, log from IPython.external import argparse from IPython.external import Itpl +from IPython.genutils import get_ipython_dir, num_cpus +from IPython.kernel.fcutil import have_crypto +from IPython.kernel.error import SecurityError +from IPython.kernel.fcutil import have_crypto from IPython.kernel.twistedutil import gatherBoth from IPython.kernel.util import printer -from IPython.genutils import get_ipython_dir, num_cpus + #----------------------------------------------------------------------------- # General process handling code @@ -42,8 +47,11 @@ def find_exe(cmd): except ImportError: raise ImportError('you need to have pywin32 installed for this to work') else: - (path, offest) = win32api.SearchPath(os.environ['PATH'],cmd) - return path + try: + (path, offest) = win32api.SearchPath(os.environ['PATH'],cmd + '.exe') + except: + (path, offset) = win32api.SearchPath(os.environ['PATH'],cmd + '.bat') + return path class ProcessStateError(Exception): pass @@ -74,10 +82,10 @@ class LauncherProcessProtocol(ProcessProtocol): ) else: raise UnknownStatus("unknown exit status, this is probably a bug in Twisted") - + def outReceived(self, data): log.msg(data) - + def errReceived(self, data): log.err(data) @@ -171,7 +179,13 @@ class ControllerLauncher(ProcessLauncher): def __init__(self, extra_args=None): if sys.platform == 'win32': - args = [find_exe('ipcontroller.bat')] + # This logic is needed because the ipcontroller script doesn't + # always get installed in the same way or in the same location. + from IPython.kernel.scripts import ipcontroller + script_location = ipcontroller.__file__.replace('.pyc', '.py') + # The -u option here turns on unbuffered output, which is required + # on Win32 to prevent wierd conflict and problems with Twisted + args = [find_exe('python'), '-u', script_location] else: args = ['ipcontroller'] self.extra_args = extra_args @@ -185,7 +199,13 @@ class EngineLauncher(ProcessLauncher): def __init__(self, extra_args=None): if sys.platform == 'win32': - args = [find_exe('ipengine.bat')] + # This logic is needed because the ipcontroller script doesn't + # always get installed in the same way or in the same location. + from IPython.kernel.scripts import ipengine + script_location = ipengine.__file__.replace('.pyc', '.py') + # The -u option here turns on unbuffered output, which is required + # on Win32 to prevent wierd conflict and problems with Twisted + args = [find_exe('python'), '-u', script_location] else: args = ['ipengine'] self.extra_args = extra_args @@ -253,7 +273,7 @@ class BatchEngineSet(object): self.context = {} self.context.update(kwargs) self.batch_file = self.template_file+'-run' - + def parse_job_id(self, output): m = re.match(self.job_id_regexp, output) if m is not None: @@ -273,7 +293,7 @@ class BatchEngineSet(object): f = open(self.batch_file,'w') f.write(script_as_string) f.close() - + def handle_error(self, f): f.printTraceback() f.raiseException() @@ -285,7 +305,7 @@ class BatchEngineSet(object): d.addCallback(self.parse_job_id) d.addErrback(self.handle_error) return d - + def kill(self): d = getProcessOutput(self.delete_command, [self.job_id],env=os.environ) @@ -301,6 +321,140 @@ class PBSEngineSet(BatchEngineSet): BatchEngineSet.__init__(self, template_file, **kwargs) +sshx_template="""#!/bin/sh +"$@" &> /dev/null & +echo $! +""" + +engine_killer_template="""#!/bin/sh +ps -fu `whoami` | grep '[i]pengine' | awk '{print $2}' | xargs kill -TERM +""" + +class SSHEngineSet(object): + sshx_template=sshx_template + engine_killer_template=engine_killer_template + + def __init__(self, engine_hosts, sshx=None, ipengine="ipengine"): + """Start a controller on localhost and engines using ssh. + + The engine_hosts argument is a dict with hostnames as keys and + the number of engine (int) as values. sshx is the name of a local + file that will be used to run remote commands. This file is used + to setup the environment properly. + """ + + self.temp_dir = tempfile.gettempdir() + if sshx is not None: + self.sshx = sshx + else: + # Write the sshx.sh file locally from our template. + self.sshx = os.path.join( + self.temp_dir, + '%s-main-sshx.sh' % os.environ['USER'] + ) + f = open(self.sshx, 'w') + f.writelines(self.sshx_template) + f.close() + self.engine_command = ipengine + self.engine_hosts = engine_hosts + # Write the engine killer script file locally from our template. + self.engine_killer = os.path.join( + self.temp_dir, + '%s-local-engine_killer.sh' % os.environ['USER'] + ) + f = open(self.engine_killer, 'w') + f.writelines(self.engine_killer_template) + f.close() + + def start(self, send_furl=False): + dlist = [] + for host in self.engine_hosts.keys(): + count = self.engine_hosts[host] + d = self._start(host, count, send_furl) + dlist.append(d) + return gatherBoth(dlist, consumeErrors=True) + + def _start(self, hostname, count=1, send_furl=False): + if send_furl: + d = self._scp_furl(hostname) + else: + d = defer.succeed(None) + d.addCallback(lambda r: self._scp_sshx(hostname)) + d.addCallback(lambda r: self._ssh_engine(hostname, count)) + return d + + def _scp_furl(self, hostname): + scp_cmd = "scp ~/.ipython/security/ipcontroller-engine.furl %s:.ipython/security/" % (hostname) + cmd_list = scp_cmd.split() + cmd_list[1] = os.path.expanduser(cmd_list[1]) + log.msg('Copying furl file: %s' % scp_cmd) + d = getProcessOutput(cmd_list[0], cmd_list[1:], env=os.environ) + return d + + def _scp_sshx(self, hostname): + scp_cmd = "scp %s %s:%s/%s-sshx.sh" % ( + self.sshx, hostname, + self.temp_dir, os.environ['USER'] + ) + print + log.msg("Copying sshx: %s" % scp_cmd) + sshx_scp = scp_cmd.split() + d = getProcessOutput(sshx_scp[0], sshx_scp[1:], env=os.environ) + return d + + def _ssh_engine(self, hostname, count): + exec_engine = "ssh %s sh %s/%s-sshx.sh %s" % ( + hostname, self.temp_dir, + os.environ['USER'], self.engine_command + ) + cmds = exec_engine.split() + dlist = [] + log.msg("about to start engines...") + for i in range(count): + log.msg('Starting engines: %s' % exec_engine) + d = getProcessOutput(cmds[0], cmds[1:], env=os.environ) + dlist.append(d) + return gatherBoth(dlist, consumeErrors=True) + + def kill(self): + dlist = [] + for host in self.engine_hosts.keys(): + d = self._killall(host) + dlist.append(d) + return gatherBoth(dlist, consumeErrors=True) + + def _killall(self, hostname): + d = self._scp_engine_killer(hostname) + d.addCallback(lambda r: self._ssh_kill(hostname)) + # d.addErrback(self._exec_err) + return d + + def _scp_engine_killer(self, hostname): + scp_cmd = "scp %s %s:%s/%s-engine_killer.sh" % ( + self.engine_killer, + hostname, + self.temp_dir, + os.environ['USER'] + ) + cmds = scp_cmd.split() + log.msg('Copying engine_killer: %s' % scp_cmd) + d = getProcessOutput(cmds[0], cmds[1:], env=os.environ) + return d + + def _ssh_kill(self, hostname): + kill_cmd = "ssh %s sh %s/%s-engine_killer.sh" % ( + hostname, + self.temp_dir, + os.environ['USER'] + ) + log.msg('Killing engine: %s' % kill_cmd) + kill_cmd = kill_cmd.split() + d = getProcessOutput(kill_cmd[0], kill_cmd[1:], env=os.environ) + return d + + def _exec_err(self, r): + log.msg(r) + #----------------------------------------------------------------------------- # Main functions for the different types of clusters #----------------------------------------------------------------------------- @@ -311,13 +465,28 @@ class PBSEngineSet(BatchEngineSet): # The main functions should then just parse the command line arguments, create # the appropriate class and call a 'start' method. -def main_local(args): - cont_args = [] - cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller')) +def check_security(args, cont_args): + if (not args.x or not args.y) and not have_crypto: + log.err(""" +OpenSSL/pyOpenSSL is not available, so we can't run in secure mode. +Try running ipcluster with the -xy flags: ipcluster local -xy -n 4""") + reactor.stop() + return False if args.x: cont_args.append('-x') if args.y: cont_args.append('-y') + return True + + +def main_local(args): + cont_args = [] + cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller')) + + # Check security settings before proceeding + if not check_security(args, cont_args): + return + cl = ControllerLauncher(extra_args=cont_args) dstart = cl.start() def start_engines(cont_pid): @@ -343,13 +512,15 @@ def main_local(args): dstart.addCallback(delay_start) dstart.addErrback(lambda f: f.raiseException()) + def main_mpirun(args): cont_args = [] cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller')) - if args.x: - cont_args.append('-x') - if args.y: - cont_args.append('-y') + + # Check security settings before proceeding + if not check_security(args, cont_args): + return + cl = ControllerLauncher(extra_args=cont_args) dstart = cl.start() def start_engines(cont_pid): @@ -379,13 +550,15 @@ def main_mpirun(args): dstart.addCallback(delay_start) dstart.addErrback(lambda f: f.raiseException()) + def main_pbs(args): cont_args = [] cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller')) - if args.x: - cont_args.append('-x') - if args.y: - cont_args.append('-y') + + # Check security settings before proceeding + if not check_security(args, cont_args): + return + cl = ControllerLauncher(extra_args=cont_args) dstart = cl.start() def start_engines(r): @@ -402,6 +575,49 @@ def main_pbs(args): dstart.addErrback(lambda f: f.raiseException()) +def main_ssh(args): + """Start a controller on localhost and engines using ssh. + + Your clusterfile should look like:: + + send_furl = False # True, if you want + engines = { + 'engine_host1' : engine_count, + 'engine_host2' : engine_count2 + } + """ + clusterfile = {} + execfile(args.clusterfile, clusterfile) + if not clusterfile.has_key('send_furl'): + clusterfile['send_furl'] = False + + cont_args = [] + cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller')) + + # Check security settings before proceeding + if not check_security(args, cont_args): + return + + cl = ControllerLauncher(extra_args=cont_args) + dstart = cl.start() + def start_engines(cont_pid): + ssh_set = SSHEngineSet(clusterfile['engines'], sshx=args.sshx) + def shutdown(signum, frame): + d = ssh_set.kill() + # d.addErrback(log.err) + cl.interrupt_then_kill(1.0) + reactor.callLater(2.0, reactor.stop) + signal.signal(signal.SIGINT,shutdown) + d = ssh_set.start(clusterfile['send_furl']) + return d + + def delay_start(cont_pid): + reactor.callLater(1.0, start_engines, cont_pid) + + dstart.addCallback(delay_start) + dstart.addErrback(lambda f: f.raiseException()) + + def get_args(): base_parser = argparse.ArgumentParser(add_help=False) base_parser.add_argument( @@ -473,6 +689,27 @@ def get_args(): default='pbs.template' ) parser_pbs.set_defaults(func=main_pbs) + + parser_ssh = subparsers.add_parser( + 'ssh', + help='run a cluster using ssh, should have ssh-keys setup', + parents=[base_parser] + ) + parser_ssh.add_argument( + '--clusterfile', + type=str, + dest='clusterfile', + help='python file describing the cluster', + default='clusterfile.py', + ) + parser_ssh.add_argument( + '--sshx', + type=str, + dest='sshx', + help='sshx launcher helper' + ) + parser_ssh.set_defaults(func=main_ssh) + args = parser.parse_args() return args diff --git a/IPython/kernel/scripts/ipcontroller b/IPython/kernel/scripts/ipcontroller old mode 100644 new mode 100755 diff --git a/IPython/kernel/scripts/ipcontroller.py b/IPython/kernel/scripts/ipcontroller.py old mode 100644 new mode 100755 diff --git a/IPython/kernel/scripts/ipengine b/IPython/kernel/scripts/ipengine old mode 100644 new mode 100755 diff --git a/IPython/kernel/scripts/ipengine.py b/IPython/kernel/scripts/ipengine.py old mode 100644 new mode 100755 diff --git a/IPython/kernel/tests/engineservicetest.py b/IPython/kernel/tests/engineservicetest.py index 4a1c6f8..9544db3 100644 --- a/IPython/kernel/tests/engineservicetest.py +++ b/IPython/kernel/tests/engineservicetest.py @@ -49,8 +49,8 @@ validCommands = ['a=5', time.sleep(0.1)""", """from math import cos; x = 1.0*cos(0.5)""", # Semicolons lead to Discard ast nodes that should be discarded - """from sets import Set -s = Set() + """s = 1 +s = set() """, # Trailing whitespace should be allowed. """import math math.cos(1.0)""", # Test a method call with a discarded return value diff --git a/IPython/macro.py b/IPython/macro.py index e28ffae..30cf4fb 100644 --- a/IPython/macro.py +++ b/IPython/macro.py @@ -9,7 +9,6 @@ import IPython.ipapi - from IPython.genutils import Term from IPython.ipapi import IPyAutocall @@ -41,4 +40,4 @@ class Macro(IPyAutocall): def __getstate__(self): """ needed for safe pickling via %store """ - return {'value': self.value} \ No newline at end of file + return {'value': self.value} diff --git a/IPython/numutils.py b/IPython/numutils.py index d809277..977d7ee 100644 --- a/IPython/numutils.py +++ b/IPython/numutils.py @@ -4,8 +4,7 @@ A set of convenient utilities for numerical work. Most of this module requires Numerical Python or is meant to be used with it. See http://www.pfdubois.com/numpy for details. - -$Id: numutils.py 958 2005-12-27 23:17:51Z fperez $""" +""" #***************************************************************************** # Copyright (C) 2001-2005 Fernando Perez @@ -14,10 +13,6 @@ $Id: numutils.py 958 2005-12-27 23:17:51Z fperez $""" # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license - __all__ = ['sum_flat','mean_flat','rms_flat','base_repr','binary_repr', 'amin','amax','amap','zeros_like','empty_like', 'frange','diagonal_matrix','identity', diff --git a/IPython/platutils.py b/IPython/platutils.py index 7f84769..6d1d94c 100644 --- a/IPython/platutils.py +++ b/IPython/platutils.py @@ -12,10 +12,6 @@ for your operation system, from platutils_PLATFORMNAME module. # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Ville'] -__license__ = Release.license - import os import sys @@ -35,8 +31,26 @@ else: # Functionality that's logically common to all platforms goes here, each # platform-specific module only provides the bits that are OS-dependent. -def freeze_term_title(): - _platutils.ignore_termtitle = True +# XXX - I'm still not happy with a module global for this, but at least now +# there is a public, cross-platform way of toggling the term title control on +# and off. We should make this a stateful object later on so that each user +# can have its own instance if needed. +def toggle_set_term_title(val): + """Control whether set_term_title is active or not. + + set_term_title() allows writing to the console titlebar. In embedded + widgets this can cause problems, so this call can be used to toggle it on + or off as needed. + + The default state of the module is for the function to be disabled. + + Parameters + ---------- + val : bool + If True, set_term_title() actually writes to the terminal (using the + appropriate platform-specific module). If False, it is a no-op. + """ + _platutils.ignore_termtitle = not(val) def set_term_title(title): @@ -45,3 +59,12 @@ def set_term_title(title): if _platutils.ignore_termtitle: return _platutils.set_term_title(title) + + +#----------------------------------------------------------------------------- +# Deprecated functions +#----------------------------------------------------------------------------- +def freeze_term_title(): + warnings.warn("This function is deprecated, use toggle_set_term_title()") + _platutils.ignore_termtitle = True + diff --git a/IPython/platutils_dummy.py b/IPython/platutils_dummy.py index 86dc990..1066ac1 100644 --- a/IPython/platutils_dummy.py +++ b/IPython/platutils_dummy.py @@ -3,19 +3,20 @@ This has empty implementation of the platutils functions, used for unsupported operating systems. + +Authors +------- +- Ville Vainio """ #***************************************************************************** -# Copyright (C) 2001-2006 Fernando Perez +# Copyright (C) 2008-2009 The IPython Development Team +# Copyright (C) 2001-2007 Fernando Perez # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Ville'] -__license__ = Release.license - # This variable is part of the expected API of the module: ignore_termtitle = True diff --git a/IPython/platutils_posix.py b/IPython/platutils_posix.py index d0dbc0a..e4d162b 100644 --- a/IPython/platutils_posix.py +++ b/IPython/platutils_posix.py @@ -12,14 +12,10 @@ to use these functions in platform agnostic fashion. # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Ville'] -__license__ = Release.license - import sys import os -ignore_termtitle = False +ignore_termtitle = True def _dummy_op(*a, **b): """ A no-op function """ @@ -27,7 +23,7 @@ def _dummy_op(*a, **b): def _set_term_title_xterm(title): """ Change virtual terminal title in xterm-workalikes """ - sys.stdout.write('\033]%d;%s\007' % (0,title)) + sys.stdout.write('\033]0;%s\007' % title) if os.environ.get('TERM','') == 'xterm': diff --git a/IPython/platutils_win32.py b/IPython/platutils_win32.py index 630a9ca..36f9b31 100644 --- a/IPython/platutils_win32.py +++ b/IPython/platutils_win32.py @@ -12,13 +12,9 @@ to use these functions in platform agnostic fashion. # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Ville'] -__license__ = Release.license - import os -ignore_termtitle = False +ignore_termtitle = True try: import ctypes diff --git a/IPython/rlineimpl.py b/IPython/rlineimpl.py index 5253419..aa0ba63 100644 --- a/IPython/rlineimpl.py +++ b/IPython/rlineimpl.py @@ -5,8 +5,7 @@ Readline is used throughout IPython as 'import IPython.rlineimpl as readline'. In addition to normal readline stuff, this module provides have_readline boolean and _outputfile variable used in genutils. - -$Id: Magic.py 1096 2006-01-28 20:08:02Z vivainio $""" +""" import sys @@ -53,4 +52,4 @@ if have_readline: _rl.clear_history except AttributeError: def clear_history(): pass - _rl.clear_history = clear_history \ No newline at end of file + _rl.clear_history = clear_history diff --git a/IPython/shellglobals.py b/IPython/shellglobals.py index 48994bf..34ce467 100644 --- a/IPython/shellglobals.py +++ b/IPython/shellglobals.py @@ -1,6 +1,13 @@ -from IPython.genutils import Term,warn,error,flag_calls, ask_yes_no +"""Some globals used by the main Shell classes. +""" + +#----------------------------------------------------------------------------- +# Module imports +#----------------------------------------------------------------------------- -import thread,inspect +# stdlib +import inspect +import thread try: import ctypes @@ -8,8 +15,12 @@ try: except ImportError: HAS_CTYPES = False +# our own +from IPython.genutils import Term,warn,error,flag_calls, ask_yes_no +#----------------------------------------------------------------------------- # Globals +#----------------------------------------------------------------------------- # global flag to pass around information about Ctrl-C without exceptions KBINT = False @@ -22,13 +33,11 @@ MAIN_THREAD_ID = thread.get_ident() # Tag when runcode() is active, for exception handling CODE_RUN = None - #----------------------------------------------------------------------------- # This class is trivial now, but I want to have it in to publish a clean # interface. Later when the internals are reorganized, code that uses this # shouldn't have to change. - if HAS_CTYPES: # Add async exception support. Trick taken from: # http://sebulba.wikispaces.com/recipe+thread2 @@ -81,16 +90,12 @@ else: # Set global flag so that runsource can know that Ctrl-C was hit KBINT = True + def run_in_frontend(src): - """ Check if source snippet can be run in the REPL thread, as opposed to GUI mainloop - - (to prevent unnecessary hanging of mainloop). - + """ Check if source snippet can be run in the REPL thread, as opposed to + GUI mainloop (to prevent unnecessary hanging of mainloop). """ if src.startswith('_ip.system(') and not '\n' in src: return True return False - - - diff --git a/IPython/strdispatch.py b/IPython/strdispatch.py index 55e2eab..7113537 100644 --- a/IPython/strdispatch.py +++ b/IPython/strdispatch.py @@ -8,7 +8,6 @@ import re from IPython.hooks import CommandChainDispatcher import IPython.hooks - # Code begins class StrDispatch(object): """Dispatch (lookup) a set of strings / regexps for match. diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py index 6cbe0a7..5d588a0 100644 --- a/IPython/testing/decorators.py +++ b/IPython/testing/decorators.py @@ -1,12 +1,10 @@ """Decorators for labeling test objects. -Decorators that merely return a modified version of the original -function object are straightforward. Decorators that return a new -function object need to use -nose.tools.make_decorator(original_function)(decorator) in returning -the decorator, in order to preserve metadata such as function name, -setup and teardown functions and so on - see nose.tools for more -information. +Decorators that merely return a modified version of the original function +object are straightforward. Decorators that return a new function object need +to use nose.tools.make_decorator(original_function)(decorator) in returning the +decorator, in order to preserve metadata such as function name, setup and +teardown functions and so on - see nose.tools for more information. This module provides a set of useful decorators meant to be ready to use in your own tests. See the bottom of the file for the ready-made ones, and if you @@ -115,6 +113,115 @@ def make_label_dec(label,ds=None): return decor + +# Inspired by numpy's skipif, but uses the full apply_wrapper utility to +# preserve function metadata better and allows the skip condition to be a +# callable. +def skipif(skip_condition, msg=None): + ''' Make function raise SkipTest exception if skip_condition is true + + Parameters + ---------- + skip_condition : bool or callable. + Flag to determine whether to skip test. If the condition is a + callable, it is used at runtime to dynamically make the decision. This + is useful for tests that may require costly imports, to delay the cost + until the test suite is actually executed. + msg : string + Message to give on raising a SkipTest exception + + Returns + ------- + decorator : function + Decorator, which, when applied to a function, causes SkipTest + to be raised when the skip_condition was True, and the function + to be called normally otherwise. + + Notes + ----- + You will see from the code that we had to further decorate the + decorator with the nose.tools.make_decorator function in order to + transmit function name, and various other metadata. + ''' + + def skip_decorator(f): + # Local import to avoid a hard nose dependency and only incur the + # import time overhead at actual test-time. + import nose + + # Allow for both boolean or callable skip conditions. + if callable(skip_condition): + skip_val = lambda : skip_condition() + else: + skip_val = lambda : skip_condition + + def get_msg(func,msg=None): + """Skip message with information about function being skipped.""" + if msg is None: out = 'Test skipped due to test condition.' + else: out = msg + return "Skipping test: %s. %s" % (func.__name__,out) + + # We need to define *two* skippers because Python doesn't allow both + # return with value and yield inside the same function. + def skipper_func(*args, **kwargs): + """Skipper for normal test functions.""" + if skip_val(): + raise nose.SkipTest(get_msg(f,msg)) + else: + return f(*args, **kwargs) + + def skipper_gen(*args, **kwargs): + """Skipper for test generators.""" + if skip_val(): + raise nose.SkipTest(get_msg(f,msg)) + else: + for x in f(*args, **kwargs): + yield x + + # Choose the right skipper to use when building the actual generator. + if nose.util.isgenerator(f): + skipper = skipper_gen + else: + skipper = skipper_func + + return nose.tools.make_decorator(f)(skipper) + + return skip_decorator + +# A version with the condition set to true, common case just to attacha message +# to a skip decorator +def skip(msg=None): + """Decorator factory - mark a test function for skipping from test suite. + + :Parameters: + msg : string + Optional message to be added. + + :Returns: + decorator : function + Decorator, which, when applied to a function, causes SkipTest + to be raised, with the optional message added. + """ + + return skipif(True,msg) + + +#----------------------------------------------------------------------------- +# Utility functions for decorators +def numpy_not_available(): + """Can numpy be imported? Returns true if numpy does NOT import. + + This is used to make a decorator to skip tests that require numpy to be + available, but delay the 'import numpy' to test execution time. + """ + try: + import numpy + np_not_avail = False + except ImportError: + np_not_avail = True + + return np_not_avail + #----------------------------------------------------------------------------- # Decorators for public use @@ -125,36 +232,23 @@ skip_doctest = make_label_dec('skip_doctest', omit from testing, while preserving the docstring for introspection, help, etc.""") -def skip(msg=''): - """Decorator - mark a test function for skipping from test suite. - - This function *is* already a decorator, it is not a factory like - make_label_dec or some of those in decorators_numpy. - - :Parameters: - - func : function - Test function to be skipped - - msg : string - Optional message to be added. - """ - - import nose - - def inner(func): +# Decorators to skip certain tests on specific platforms. +skip_win32 = skipif(sys.platform == 'win32', + "This test does not run under Windows") +skip_linux = skipif(sys.platform == 'linux2', + "This test does not run under Linux") +skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X") - def wrapper(*a,**k): - if msg: out = '\n'+msg - else: out = '' - raise nose.SkipTest("Skipping test for function: %s%s" % - (func.__name__,out)) - return apply_wrapper(wrapper,func) +# Decorators to skip tests if not on specific platforms. +skip_if_not_win32 = skipif(sys.platform != 'win32', + "This test only runs under Windows") +skip_if_not_linux = skipif(sys.platform != 'linux2', + "This test only runs under Linux") +skip_if_not_osx = skipif(sys.platform != 'darwin', + "This test only runs under OSX") - return inner +# Other skip decorators +skipif_not_numpy = skipif(numpy_not_available,"This test requires numpy") -# Decorators to skip certain tests on specific platforms. -skip_win32 = skipif(sys.platform=='win32',"This test does not run under Windows") -skip_linux = skipif(sys.platform=='linux2',"This test does not run under Linux") -skip_osx = skipif(sys.platform=='darwin',"This test does not run under OSX") +skipknownfailure = skip('This test is known to fail') diff --git a/IPython/testing/decorators_numpy.py b/IPython/testing/decorators_numpy.py index dd9783e..e6a56cb 100644 --- a/IPython/testing/decorators_numpy.py +++ b/IPython/testing/decorators_numpy.py @@ -46,13 +46,16 @@ def setastest(tf=True): return t return set_test -def skipif(skip_condition, msg=None): +def skipif(skip_condition=True, msg=None): ''' Make function raise SkipTest exception if skip_condition is true Parameters - --------- - skip_condition : bool - Flag to determine whether to skip test (True) or not (False) + ---------- + skip_condition : bool or callable. + Flag to determine whether to skip test. If the condition is a + callable, it is used at runtime to dynamically make the decision. This + is useful for tests that may require costly imports, to delay the cost + until the test suite is actually executed. msg : string Message to give on raising a SkipTest exception diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py index 262ef28..14f8eba 100644 --- a/IPython/testing/iptest.py +++ b/IPython/testing/iptest.py @@ -1,16 +1,52 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- """IPython Test Suite Runner. + +This module provides a main entry point to a user script to test IPython itself +from the command line. The main() routine can be used in a similar manner to +the ``nosetests`` script, and it takes similar arguments, but if no arguments +are given it defaults to testing all of IPython. This should be preferred to +using plain ``nosetests`` because a number of nose plugins necessary to test +IPython correctly are automatically configured by this code. """ +#----------------------------------------------------------------------------- +# Module imports +#----------------------------------------------------------------------------- + +# stdlib import sys import warnings -from nose.core import TestProgram +# third-party import nose.plugins.builtin +from nose.core import TestProgram +# Our own imports from IPython.testing.plugin.ipdoctest import IPythonDoctest +#----------------------------------------------------------------------------- +# Constants and globals +#----------------------------------------------------------------------------- + +# For the IPythonDoctest plugin, we need to exclude certain patterns that cause +# testing problems. We should strive to minimize the number of skipped +# modules, since this means untested code. As the testing machinery +# solidifies, this list should eventually become empty. +EXCLUDE = ['IPython/external/', + 'IPython/platutils_win32', + 'IPython/frontend/cocoa', + 'IPython_doctest_plugin', + 'IPython/Gnuplot', + 'IPython/Extensions/ipy_', + 'IPython/Extensions/clearcmd', + 'IPython/Extensions/PhysicalQIn', + 'IPython/Extensions/scitedirector', + ] + +#----------------------------------------------------------------------------- +# Functions and classes +#----------------------------------------------------------------------------- + def main(): """Run the IPython test suite. """ @@ -18,36 +54,45 @@ def main(): warnings.filterwarnings('ignore', 'This will be removed soon. Use IPython.testing.util instead') - - # construct list of plugins, omitting the existing doctest plugin - plugins = [IPythonDoctest()] + argv = sys.argv + [ + # Loading ipdoctest causes problems with Twisted. + # I am removing this as a temporary fix to get the + # test suite back into working shape. Our nose + # plugin needs to be gone through with a fine + # toothed comb to find what is causing the problem. + # '--with-ipdoctest', + '--doctest-tests','--doctest-extension=txt', + '--detailed-errors', + + # We add --exe because of setuptools' imbecility (it + # blindly does chmod +x on ALL files). Nose does the + # right thing and it tries to avoid executables, + # setuptools unfortunately forces our hand here. This + # has been discussed on the distutils list and the + # setuptools devs refuse to fix this problem! + '--exe', + ] + + # Detect if any tests were required by explicitly calling an IPython + # submodule or giving a specific path + has_tests = False + for arg in sys.argv: + if 'IPython' in arg or arg.endswith('.py') or \ + (':' in arg and '.py' in arg): + has_tests = True + break + # If nothing was specifically requested, test full IPython + if not has_tests: + argv.append('IPython') + + # Construct list of plugins, omitting the existing doctest plugin. + plugins = [IPythonDoctest(EXCLUDE)] for p in nose.plugins.builtin.plugins: plug = p() if plug.name == 'doctest': continue - #print 'adding plugin:',plug.name # dbg + #print '*** adding plugin:',plug.name # dbg plugins.append(plug) - argv = sys.argv + ['--doctest-tests','--doctest-extension=txt', - '--detailed-errors', - - # We add --exe because of setuptools' imbecility (it - # blindly does chmod +x on ALL files). Nose does the - # right thing and it tries to avoid executables, - # setuptools unfortunately forces our hand here. This - # has been discussed on the distutils list and the - # setuptools devs refuse to fix this problem! - '--exe', - ] - - has_ip = False - for arg in sys.argv: - if 'IPython' in arg: - has_ip = True - break - - if not has_ip: - argv.append('IPython') - TestProgram(argv=argv,plugins=plugins) diff --git a/IPython/testing/plugin/Makefile b/IPython/testing/plugin/Makefile index 9d8157a..dd65022 100644 --- a/IPython/testing/plugin/Makefile +++ b/IPython/testing/plugin/Makefile @@ -36,8 +36,8 @@ deco: magic: plugin $(NOSE) IPython.Magic -ipipe: plugin - $(NOSE) IPython.Extensions.ipipe +excolors: plugin + $(NOSE) IPython.excolors iplib: plugin $(NOSE) IPython.iplib diff --git a/IPython/testing/plugin/dtexample.py b/IPython/testing/plugin/dtexample.py index 3a29b4a..453aac3 100644 --- a/IPython/testing/plugin/dtexample.py +++ b/IPython/testing/plugin/dtexample.py @@ -29,9 +29,6 @@ def ipfunc(): In [1]: import os - In [2]: cd / - / - In [3]: 2+3 Out[3]: 5 diff --git a/IPython/testing/plugin/ipdoctest.py b/IPython/testing/plugin/ipdoctest.py index 5971332..64e6a2c 100644 --- a/IPython/testing/plugin/ipdoctest.py +++ b/IPython/testing/plugin/ipdoctest.py @@ -65,13 +65,28 @@ log = logging.getLogger(__name__) # test globals. Once we move over to a clean magic system, this will be done # with much less ugliness. +class py_file_finder(object): + def __init__(self,test_filename): + self.test_filename = test_filename + + def __call__(self,name): + from IPython.genutils import get_py_filename + try: + return get_py_filename(name) + except IOError: + test_dir = os.path.dirname(self.test_filename) + new_path = os.path.join(test_dir,name) + return get_py_filename(new_path) + + def _run_ns_sync(self,arg_s,runner=None): """Modified version of %run that syncs testing namespaces. This is strictly needed for running doctests that call %run. """ - out = _ip.IP.magic_run_ori(arg_s,runner) + finder = py_file_finder(_run_ns_sync.test_filename) + out = _ip.IP.magic_run_ori(arg_s,runner,finder) _run_ns_sync.test_globs.update(_ip.user_ns) return out @@ -129,8 +144,7 @@ def start_ipython(): # Start IPython instance. We customize it to start with minimal frills. user_ns,global_ns = IPython.ipapi.make_user_namespaces(ipnsdict(),dict()) - - IPython.Shell.IPShell(['--classic','--noterm_title'], + IPython.Shell.IPShell(['--colors=NoColor','--noterm_title'], user_ns,global_ns) # Deactivate the various python system hooks added by ipython for @@ -172,13 +186,19 @@ def is_extension_module(filename): return os.path.splitext(filename)[1].lower() in ('.so','.pyd') -class nodoc(object): +class DocTestSkip(object): + """Object wrapper for doctests to be skipped.""" + + ds_skip = """Doctest to skip. + >>> 1 #doctest: +SKIP + """ + def __init__(self,obj): self.obj = obj def __getattribute__(self,key): if key == '__doc__': - return None + return DocTestSkip.ds_skip else: return getattr(object.__getattribute__(self,'obj'),key) @@ -222,7 +242,7 @@ class DocTestFinder(doctest.DocTestFinder): if hasattr(obj,"skip_doctest"): #print 'SKIPPING DOCTEST FOR:',obj # dbg - obj = nodoc(obj) + obj = DocTestSkip(obj) doctest.DocTestFinder._find(self,tests, obj, name, module, source_lines, globs, seen) @@ -372,7 +392,6 @@ class DocTestCase(doctests.DocTestCase): self._dt_test.globs = _ip.IP.user_ns doctests.DocTestCase.setUp(self) - # A simple subclassing of the original with a different class name, so we can @@ -444,7 +463,11 @@ class IPDocTestParser(doctest.DocTestParser): """Convert input IPython source into valid Python.""" out = [] newline = out.append - for lnum,line in enumerate(source.splitlines()): + #print 'IPSRC:\n',source,'\n###' # dbg + # The input source must be first stripped of all bracketing whitespace + # and turned into lines, so it looks to the parser like regular user + # input + for lnum,line in enumerate(source.strip().splitlines()): newline(_ip.IP.prefilter(line,lnum>0)) newline('') # ensure a closing newline, needed by doctest #print "PYSRC:", '\n'.join(out) # dbg @@ -638,7 +661,8 @@ class IPDocTestRunner(doctest.DocTestRunner,object): # when called (rather than unconconditionally updating test.globs here # for all examples, most of which won't be calling %run anyway). _run_ns_sync.test_globs = test.globs - + _run_ns_sync.test_filename = test.filename + return super(IPDocTestRunner,self).run(test, compileflags,out,clear_globs) @@ -656,6 +680,22 @@ class ExtensionDoctest(doctests.Doctest): name = 'extdoctest' # call nosetests with --with-extdoctest enabled = True + def __init__(self,exclude_patterns=None): + """Create a new ExtensionDoctest plugin. + + Parameters + ---------- + + exclude_patterns : sequence of strings, optional + These patterns are compiled as regular expressions, subsequently used + to exclude any filename which matches them from inclusion in the test + suite (using pattern.search(), NOT pattern.match() ). + """ + if exclude_patterns is None: + exclude_patterns = [] + self.exclude_patterns = map(re.compile,exclude_patterns) + doctests.Doctest.__init__(self) + def options(self, parser, env=os.environ): Plugin.options(self, parser, env) parser.add_option('--doctest-tests', action='store_true', @@ -688,6 +728,7 @@ class ExtensionDoctest(doctests.Doctest): self.globs = None self.extraglobs = None + def loadTestsFromExtensionModule(self,filename): bpath,mod = os.path.split(filename) modname = os.path.splitext(mod)[0] @@ -703,8 +744,8 @@ class ExtensionDoctest(doctests.Doctest): # a few modifications to control output checking. def loadTestsFromModule(self, module): - #print 'lTM',module # dbg - + #print '*** ipdoctest - lTM',module # dbg + if not self.matches(module.__name__): log.debug("Doctest doesn't want module %s", module) return @@ -733,8 +774,6 @@ class ExtensionDoctest(doctests.Doctest): def loadTestsFromFile(self, filename): - #print 'lTF',filename # dbg - if is_extension_module(filename): for t in self.loadTestsFromExtensionModule(filename): yield t @@ -761,22 +800,10 @@ class ExtensionDoctest(doctests.Doctest): Modified version that accepts extension modules as valid containers for doctests. """ - print 'Filename:',filename # dbg - - # XXX - temporarily hardcoded list, will move to driver later - exclude = ['IPython/external/', - 'IPython/platutils_win32', - 'IPython/frontend/cocoa', - 'IPython_doctest_plugin', - 'IPython/Gnuplot', - 'IPython/Extensions/ipy_', - 'IPython/Extensions/PhysicalQIn', - 'IPython/Extensions/scitedirector', - 'IPython/testing/plugin', - ] - - for fex in exclude: - if fex in filename: # substring + #print '*** ipdoctest- wantFile:',filename # dbg + + for pat in self.exclude_patterns: + if pat.search(filename): #print '###>>> SKIP:',filename # dbg return False @@ -791,6 +818,23 @@ class IPythonDoctest(ExtensionDoctest): """ name = 'ipdoctest' # call nosetests with --with-ipdoctest enabled = True + + def makeTest(self, obj, parent): + """Look for doctests in the given object, which will be a + function, method or class. + """ + # always use whitespace and ellipsis options + optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS + + doctests = self.finder.find(obj, module=getmodule(parent)) + if doctests: + for test in doctests: + if len(test.examples) == 0: + continue + + yield DocTestCase(test, obj=obj, + optionflags=optionflags, + checker=self.checker) def configure(self, options, config): diff --git a/IPython/tests/obj_del.py b/IPython/tests/obj_del.py new file mode 100644 index 0000000..089182e --- /dev/null +++ b/IPython/tests/obj_del.py @@ -0,0 +1,34 @@ +"""Test code for https://bugs.launchpad.net/ipython/+bug/239054 + +WARNING: this script exits IPython! It MUST be run in a subprocess. + +When you run the following script from CPython it prints: +__init__ is here +__del__ is here + +and creates the __del__.txt file + +When you run it from IPython it prints: +__init__ is here + +When you exit() or Exit from IPython neothing is printed and no file is created +(the file thing is to make sure __del__ is really never called and not that +just the output is eaten). + +Note that if you call %reset in IPython then everything is Ok. + +IPython should do the equivalent of %reset and release all the references it +holds before exit. This behavior is important when working with binding objects +that rely on __del__. If the current behavior has some use case then I suggest +to add a configuration option to IPython to control it. +""" +import sys + +class A(object): + def __del__(self): + print 'object A deleted' + +a = A() + +# Now, we force an exit, the caller will check that the del printout was given +_ip.IP.ask_exit() diff --git a/IPython/tests/tclass.py b/IPython/tests/tclass.py new file mode 100644 index 0000000..1e8e1dd --- /dev/null +++ b/IPython/tests/tclass.py @@ -0,0 +1,26 @@ +"""Simple script to instantiate a class for testing %run""" + +import sys + +# An external test will check that calls to f() work after %run +class foo: pass + +def f(): + return foo() + +# We also want to ensure that while objects remain available for immediate +# access, objects from *previous* runs of the same script get collected, to +# avoid accumulating massive amounts of old references. +class C(object): + def __init__(self,name): + self.name = name + + def __del__(self): + print 'Deleting object:',self.name + +try: + name = sys.argv[1] +except IndexError: + pass +else: + c = C(name) diff --git a/IPython/tests/test_genutils.py b/IPython/tests/test_genutils.py index 9072ec1..ec79815 100644 --- a/IPython/tests/test_genutils.py +++ b/IPython/tests/test_genutils.py @@ -15,18 +15,277 @@ __docformat__ = "restructuredtext en" # Imports #----------------------------------------------------------------------------- +# stdlib +import os +import shutil +import sys +import tempfile + +from os.path import join, abspath, split + +# third-party +import nose.tools as nt + +from nose import with_setup +from nose.tools import raises + +# Our own +import IPython from IPython import genutils +from IPython.testing.decorators import skipif, skip_if_not_win32 + +# Platform-dependent imports +try: + import _winreg as wreg +except ImportError: + #Fake _winreg module on none windows platforms + import new + sys.modules["_winreg"] = new.module("_winreg") + import _winreg as wreg + #Add entries that needs to be stubbed by the testing code + (wreg.OpenKey, wreg.QueryValueEx,) = (None, None) + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- +env = os.environ +TEST_FILE_PATH = split(abspath(__file__))[0] +TMP_TEST_DIR = tempfile.mkdtemp() +HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir") +IP_TEST_DIR = join(HOME_TEST_DIR,'_ipython') +# +# Setup/teardown functions/decorators +# + +def setup(): + """Setup testenvironment for the module: + + - Adds dummy home dir tree + """ + # Do not mask exceptions here. In particular, catching WindowsError is a + # problem because that exception is only defined on Windows... + os.makedirs(IP_TEST_DIR) + +def teardown(): + """Teardown testenvironment for the module: + + - Remove dummy home dir tree + """ + # Note: we remove the parent test dir, which is the root of all test + # subdirs we may have created. Use shutil instead of os.removedirs, so + # that non-empty directories are all recursively removed. + shutil.rmtree(TMP_TEST_DIR) + + +def setup_environment(): + """Setup testenvironment for some functions that are tested + in this module. In particular this functions stores attributes + and other things that we need to stub in some test functions. + This needs to be done on a function level and not module level because + each testfunction needs a pristine environment. + """ + global oldstuff, platformstuff + oldstuff = (env.copy(), os.name, genutils.get_home_dir, IPython.__file__,) + + if os.name == 'nt': + platformstuff = (wreg.OpenKey, wreg.QueryValueEx,) + + if 'IPYTHONDIR' in env: + del env['IPYTHONDIR'] + +def teardown_environment(): + """Restore things that were remebered by the setup_environment function + """ + (oldenv, os.name, genutils.get_home_dir, IPython.__file__,) = oldstuff + for key in env.keys(): + if key not in oldenv: + del env[key] + env.update(oldenv) + if hasattr(sys, 'frozen'): + del sys.frozen + if os.name == 'nt': + (wreg.OpenKey, wreg.QueryValueEx,) = platformstuff + +# Build decorator that uses the setup_environment/setup_environment +with_enivronment = with_setup(setup_environment, teardown_environment) + + +# +# Tests for get_home_dir +# + +@skip_if_not_win32 +@with_enivronment +def test_get_home_dir_1(): + """Testcase for py2exe logic, un-compressed lib + """ + sys.frozen = True + + #fake filename for IPython.__init__ + IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py")) + + home_dir = genutils.get_home_dir() + nt.assert_equal(home_dir, abspath(HOME_TEST_DIR)) + +@skip_if_not_win32 +@with_enivronment +def test_get_home_dir_2(): + """Testcase for py2exe logic, compressed lib + """ + sys.frozen = True + #fake filename for IPython.__init__ + IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower() + + home_dir = genutils.get_home_dir() + nt.assert_equal(home_dir, abspath(HOME_TEST_DIR).lower()) + +@with_enivronment +def test_get_home_dir_3(): + """Testcase $HOME is set, then use its value as home directory.""" + env["HOME"] = HOME_TEST_DIR + home_dir = genutils.get_home_dir() + nt.assert_equal(home_dir, env["HOME"]) + +@with_enivronment +def test_get_home_dir_4(): + """Testcase $HOME is not set, os=='poix'. + This should fail with HomeDirError""" + + os.name = 'posix' + if 'HOME' in env: del env['HOME'] + nt.assert_raises(genutils.HomeDirError, genutils.get_home_dir) + +@skip_if_not_win32 +@with_enivronment +def test_get_home_dir_5(): + """Testcase $HOME is not set, os=='nt' + env['HOMEDRIVE'],env['HOMEPATH'] points to path.""" + + os.name = 'nt' + if 'HOME' in env: del env['HOME'] + env['HOMEDRIVE'], env['HOMEPATH'] = os.path.splitdrive(HOME_TEST_DIR) + home_dir = genutils.get_home_dir() + nt.assert_equal(home_dir, abspath(HOME_TEST_DIR)) + +@skip_if_not_win32 +@with_enivronment +def test_get_home_dir_6(): + """Testcase $HOME is not set, os=='nt' + env['HOMEDRIVE'],env['HOMEPATH'] do not point to path. + env['USERPROFILE'] points to path + """ + + os.name = 'nt' + if 'HOME' in env: del env['HOME'] + env['HOMEDRIVE'], env['HOMEPATH'] = os.path.abspath(TEST_FILE_PATH), "DOES NOT EXIST" + env["USERPROFILE"] = abspath(HOME_TEST_DIR) + + home_dir = genutils.get_home_dir() + nt.assert_equal(home_dir, abspath(HOME_TEST_DIR)) + +# Should we stub wreg fully so we can run the test on all platforms? +@skip_if_not_win32 +@with_enivronment +def test_get_home_dir_7(): + """Testcase $HOME is not set, os=='nt' + env['HOMEDRIVE'],env['HOMEPATH'], env['USERPROFILE'] missing + """ + os.name = 'nt' + if 'HOME' in env: del env['HOME'] + if 'HOMEDRIVE' in env: del env['HOMEDRIVE'] + + #Stub windows registry functions + def OpenKey(x, y): + class key: + def Close(self): + pass + return key() + def QueryValueEx(x, y): + return [abspath(HOME_TEST_DIR)] + + wreg.OpenKey = OpenKey + wreg.QueryValueEx = QueryValueEx -def test_get_home_dir(): - """Make sure we can get the home directory.""" home_dir = genutils.get_home_dir() + nt.assert_equal(home_dir, abspath(HOME_TEST_DIR)) + + +# +# Tests for get_ipython_dir +# + +@with_enivronment +def test_get_ipython_dir_1(): + """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions.""" + env['IPYTHONDIR'] = "someplace/.ipython" + ipdir = genutils.get_ipython_dir() + nt.assert_equal(ipdir, os.path.abspath("someplace/.ipython")) -def test_get_ipython_dir(): - """Make sure we can get the ipython directory.""" + +@with_enivronment +def test_get_ipython_dir_2(): + """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions.""" + genutils.get_home_dir = lambda : "someplace" + os.name = "posix" + ipdir = genutils.get_ipython_dir() + nt.assert_equal(ipdir, os.path.abspath(os.path.join("someplace", ".ipython"))) + +@with_enivronment +def test_get_ipython_dir_3(): + """test_get_ipython_dir_3, Testcase to see if we can call get_ipython_dir without Exceptions.""" + genutils.get_home_dir = lambda : "someplace" + os.name = "nt" ipdir = genutils.get_ipython_dir() + nt.assert_equal(ipdir, os.path.abspath(os.path.join("someplace", "_ipython"))) + + +# +# Tests for get_security_dir +# +@with_enivronment def test_get_security_dir(): - """Make sure we can get the ipython/security directory.""" + """Testcase to see if we can call get_security_dir without Exceptions.""" sdir = genutils.get_security_dir() - \ No newline at end of file + + +# +# Tests for popkey +# + +def test_popkey_1(): + """test_popkey_1, Basic usage test of popkey + """ + dct = dict(a=1, b=2, c=3) + nt.assert_equal(genutils.popkey(dct, "a"), 1) + nt.assert_equal(dct, dict(b=2, c=3)) + nt.assert_equal(genutils.popkey(dct, "b"), 2) + nt.assert_equal(dct, dict(c=3)) + nt.assert_equal(genutils.popkey(dct, "c"), 3) + nt.assert_equal(dct, dict()) + +def test_popkey_2(): + """test_popkey_2, Test to see that popkey of non occuring keys + generates a KeyError exception + """ + dct = dict(a=1, b=2, c=3) + nt.assert_raises(KeyError, genutils.popkey, dct, "d") + +def test_popkey_3(): + """test_popkey_3, Tests to see that popkey calls returns the correct value + and that the key/value was removed from the dict. + """ + dct = dict(a=1, b=2, c=3) + nt.assert_equal(genutils.popkey(dct, "A", 13), 13) + nt.assert_equal(dct, dict(a=1, b=2, c=3)) + nt.assert_equal(genutils.popkey(dct, "B", 14), 14) + nt.assert_equal(dct, dict(a=1, b=2, c=3)) + nt.assert_equal(genutils.popkey(dct, "C", 15), 15) + nt.assert_equal(dct, dict(a=1, b=2, c=3)) + nt.assert_equal(genutils.popkey(dct, "a"), 1) + nt.assert_equal(dct, dict(b=2, c=3)) + nt.assert_equal(genutils.popkey(dct, "b"), 2) + nt.assert_equal(dct, dict(c=3)) + nt.assert_equal(genutils.popkey(dct, "c"), 3) + nt.assert_equal(dct, dict()) diff --git a/IPython/tests/test_iplib.py b/IPython/tests/test_iplib.py new file mode 100644 index 0000000..42fa1b6 --- /dev/null +++ b/IPython/tests/test_iplib.py @@ -0,0 +1,17 @@ +"""Tests for the key iplib module, where the main ipython class is defined. +""" + +import nose.tools as nt + + +def test_reset(): + """reset must clear most namespaces.""" + ip = _ip.IP + ip.reset() # first, it should run without error + # Then, check that most namespaces end up empty + for ns in ip.ns_refs_table: + if ns is ip.user_ns: + # The user namespace is reset with some data, so we can't check for + # it being empty + continue + nt.assert_equals(len(ns),0) diff --git a/IPython/tests/test_magic.py b/IPython/tests/test_magic.py index 4228600..eeb175c 100644 --- a/IPython/tests/test_magic.py +++ b/IPython/tests/test_magic.py @@ -1,8 +1,21 @@ -""" Tests for various magic functions - -Needs to be run by nose (to make ipython session available) +"""Tests for various magic functions. +Needs to be run by nose (to make ipython session available). """ + +# Standard library imports +import os +import sys + +# Third-party imports +import nose.tools as nt + +# From our own code +from IPython.testing import decorators as dec + +#----------------------------------------------------------------------------- +# Test functions begin + def test_rehashx(): # clear up everything _ip.IP.alias_table.clear() @@ -19,3 +32,104 @@ def test_rehashx(): # rehashx must fill up syscmdlist scoms = _ip.db['syscmdlist'] assert len(scoms) > 10 + + +def doctest_run_ns(): + """Classes declared %run scripts must be instantiable afterwards. + + In [11]: run tclass + + In [12]: isinstance(f(),foo) + Out[12]: True + """ + + +def doctest_run_ns2(): + """Classes declared %run scripts must be instantiable afterwards. + + In [3]: run tclass.py + + In [4]: run tclass first_pass + + In [5]: run tclass second_pass + Deleting object: first_pass + """ + + +def doctest_hist_f(): + """Test %hist -f with temporary filename. + + In [9]: import tempfile + + In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-') + + In [11]: %history -n -f $tfile 3 + """ + + +def doctest_hist_r(): + """Test %hist -r + + XXX - This test is not recording the output correctly. Not sure why... + + In [6]: x=1 + + In [7]: hist -n -r 2 + x=1 # random + hist -n -r 2 # random + """ + + +def test_obj_del(): + """Test that object's __del__ methods are called on exit.""" + test_dir = os.path.dirname(__file__) + del_file = os.path.join(test_dir,'obj_del.py') + out = _ip.IP.getoutput('ipython %s' % del_file) + nt.assert_equals(out,'object A deleted') + + +def test_shist(): + # Simple tests of ShadowHist class - test generator. + import os, shutil, tempfile + + from IPython.Extensions import pickleshare + from IPython.history import ShadowHist + + tfile = tempfile.mktemp('','tmp-ipython-') + + db = pickleshare.PickleShareDB(tfile) + s = ShadowHist(db) + s.add('hello') + s.add('world') + s.add('hello') + s.add('hello') + s.add('karhu') + + yield nt.assert_equals,s.all(),[(1, 'hello'), (2, 'world'), (3, 'karhu')] + + yield nt.assert_equal,s.get(2),'world' + + shutil.rmtree(tfile) + +@dec.skipif_not_numpy +def test_numpy_clear_array_undec(): + _ip.ex('import numpy as np') + _ip.ex('a = np.empty(2)') + + yield nt.assert_true,'a' in _ip.user_ns + _ip.magic('clear array') + yield nt.assert_false,'a' in _ip.user_ns + + +@dec.skip() +def test_fail_dec(*a,**k): + yield nt.assert_true, False + +@dec.skip('This one shouldn not run') +def test_fail_dec2(*a,**k): + yield nt.assert_true, False + +@dec.skipknownfailure +def test_fail_dec3(*a,**k): + yield nt.assert_true, False + diff --git a/IPython/twshell.py b/IPython/twshell.py index c14c15a..f00ab02 100644 --- a/IPython/twshell.py +++ b/IPython/twshell.py @@ -1,3 +1,7 @@ +"""Twisted shell support. + +XXX - This module is missing proper docs. +""" import sys from twisted.internet import reactor, threads diff --git a/IPython/ultraTB.py b/IPython/ultraTB.py index f0f3cb9..a11a0eb 100644 --- a/IPython/ultraTB.py +++ b/IPython/ultraTB.py @@ -59,8 +59,7 @@ ColorSchemeTable class. Currently the following exist: You can implement other color schemes easily, the syntax is fairly self-explanatory. Please send back new schemes you develop to the author for possible inclusion in future releases. - -$Id: ultraTB.py 2908 2007-12-30 21:07:46Z vivainio $""" +""" #***************************************************************************** # Copyright (C) 2001 Nathaniel Gray @@ -70,11 +69,6 @@ $Id: ultraTB.py 2908 2007-12-30 21:07:46Z vivainio $""" # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = '%s <%s>\n%s <%s>' % (Release.authors['Nathan']+ - Release.authors['Fernando']) -__license__ = Release.license - # Required modules import inspect import keyword @@ -98,7 +92,7 @@ from inspect import getsourcefile, getfile, getmodule,\ # Modified pdb which doesn't damage IPython's readline handling from IPython import Debugger, PyColorize from IPython.ipstruct import Struct -from IPython.excolors import ExceptionColors +from IPython.excolors import exception_colors from IPython.genutils import Term,uniq_stable,error,info # Globals @@ -320,7 +314,7 @@ class TBTools: self.call_pdb = call_pdb # Create color table - self.color_scheme_table = ExceptionColors + self.color_scheme_table = exception_colors() self.set_colors(color_scheme) self.old_scheme = color_scheme # save initial value for toggles diff --git a/IPython/usage.py b/IPython/usage.py index 1d5cf35..7620d96 100644 --- a/IPython/usage.py +++ b/IPython/usage.py @@ -6,13 +6,6 @@ # the file COPYING, distributed as part of this software. #***************************************************************************** -# $Id: usage.py 2723 2007-09-07 07:44:16Z fperez $ - -from IPython import Release -__author__ = '%s <%s>' % Release.authors['Fernando'] -__license__ = Release.license -__version__ = Release.version - __doc__ = """ IPython -- An enhanced Interactive Python ========================================= @@ -650,5 +643,3 @@ or python names. The following magic functions are currently available: """ - - diff --git a/IPython/wildcard.py b/IPython/wildcard.py index b92ab5f..3e62d8a 100644 --- a/IPython/wildcard.py +++ b/IPython/wildcard.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- """Support for wildcard pattern matching in object inspection. -$Id: OInspect.py 608 2005-07-06 17:52:32Z fperez $ +Authors +------- +- Jörgen Stenarson """ #***************************************************************************** @@ -11,10 +13,6 @@ $Id: OInspect.py 608 2005-07-06 17:52:32Z fperez $ # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import Release -__author__ = "Jörgen Stenarson " -__license__ = Release.license - import __builtin__ import exceptions import pdb diff --git a/docs/Makefile b/docs/Makefile index 506264b..d7e9363 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -5,13 +5,14 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = +SRCDIR = source # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SRCDIR) -.PHONY: help clean html web pickle htmlhelp latex changes linkcheck +.PHONY: help clean html web pickle htmlhelp latex changes linkcheck api help: @echo "Please use \`make ' where is one of" @@ -28,7 +29,7 @@ help: @echo "dist all, and then puts the results in dist/" clean: - -rm -rf build/* dist/* + -rm -rf build/* dist/* $(SRCDIR)/api/generated pdf: latex cd build/latex && make all-pdf @@ -41,12 +42,16 @@ dist: clean all cp -al build/html dist/ @echo "Build finished. Final docs are in dist/" -html: +html: api mkdir -p build/html build/doctrees $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html @echo @echo "Build finished. The HTML pages are in build/html." +api: + python autogen_api.py + @echo "Build API docs finished." + pickle: mkdir -p build/pickle build/doctrees $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle diff --git a/docs/autogen_api.py b/docs/autogen_api.py new file mode 100755 index 0000000..c7ab055 --- /dev/null +++ b/docs/autogen_api.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +"""Script to auto-generate our API docs. +""" +# stdlib imports +import os +import sys + +# local imports +sys.path.append(os.path.abspath('sphinxext')) +from apigen import ApiDocWriter + +#***************************************************************************** +if __name__ == '__main__': + pjoin = os.path.join + package = 'IPython' + outdir = pjoin('source','api','generated') + docwriter = ApiDocWriter(package,rst_extension='.txt') + docwriter.package_skip_patterns += [r'\.fixes$', + r'\.externals$', + r'\.Extensions', + r'\.kernel.config', + r'\.attic', + ] + docwriter.module_skip_patterns += [ r'\.FakeModule', + r'\.cocoa', + r'\.ipdoctest', + r'\.Gnuplot', + ] + docwriter.write_api_docs(outdir) + docwriter.write_index(outdir, 'gen', + relative_to = pjoin('source','api') + ) + print '%d files written' % len(docwriter.written_modules) diff --git a/docs/source/api/index.txt b/docs/source/api/index.txt new file mode 100644 index 0000000..d5c55f4 --- /dev/null +++ b/docs/source/api/index.txt @@ -0,0 +1,12 @@ +.. _api-index: + +################### + The IPython API +################### + +.. htmlonly:: + + :Release: |version| + :Date: |today| + +.. include:: generated/gen.txt diff --git a/docs/source/changes.txt b/docs/source/changes.txt index f1971d2..2408bbf 100644 --- a/docs/source/changes.txt +++ b/docs/source/changes.txt @@ -27,6 +27,9 @@ Release dev New features ------------ +* The new ipcluster now has a fully working ssh mode that should work on + Linux, Unix and OS X. Thanks to Vishal Vatsa for implementing this! + * The wonderful TextMate editor can now be used with %edit on OS X. Thanks to Matt Foster for this patch. @@ -59,6 +62,8 @@ New features Bug fixes --------- +* Numerous bugs on Windows with the new ipcluster have been fixed. + * The ipengine and ipcontroller scripts now handle missing furl files more gracefully by giving better error messages. diff --git a/docs/source/conf.py b/docs/source/conf.py index 06e1430..198b195 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -36,9 +36,13 @@ execfile('../../IPython/Release.py',iprelease) # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', - 'inheritance_diagram', 'only_directives', + 'sphinx.ext.doctest', + + 'only_directives', + 'inheritance_diagram', 'ipython_console_highlighting', # 'plot_directive', # disabled for now, needs matplotlib + 'numpydoc', # to preprocess docstrings ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/source/development/coding_guide.txt b/docs/source/development/coding_guide.txt new file mode 100644 index 0000000..761fdbb --- /dev/null +++ b/docs/source/development/coding_guide.txt @@ -0,0 +1,141 @@ +============== + Coding guide +============== + + +Coding conventions +================== + +In general, we'll try to follow the standard Python style conventions as +described in Python's `PEP 8`_, the official Python Style Guide. + +.. _PEP 8: http://www.python.org/peps/pep-0008.html + +Other comments: + +- In a large file, top level classes and functions should be separated by 2-3 + lines to make it easier to separate them visually. + +- Use 4 spaces for indentation, *never* use hard tabs. + +- Keep the ordering of methods the same in classes that have the same methods. + This is particularly true for classes that implement similar interfaces and + for interfaces that are similar. + +Naming conventions +------------------ + +In terms of naming conventions, we'll follow the guidelines of PEP 8. Some of +the existing code doesn't honor this perfectly, but for all new IPython code +(and much existing code is being refactored), we'll use: + +- All ``lowercase`` module names. + +- ``CamelCase`` for class names. + +- ``lowercase_with_underscores`` for methods, functions, variables and + attributes. + +This may be confusing as some of the existing codebase uses a different +convention (``lowerCamelCase`` for methods and attributes). Slowly, we will +move IPython over to the new convention, providing shadow names for backward +compatibility in public interfaces. + +There are, however, some important exceptions to these rules. In some cases, +IPython code will interface with packages (Twisted, Wx, Qt) that use other +conventions. At some level this makes it impossible to adhere to our own +standards at all times. In particular, when subclassing classes that use other +naming conventions, you must follow their naming conventions. To deal with +cases like this, we propose the following policy: + +- If you are subclassing a class that uses different conventions, use its + naming conventions throughout your subclass. Thus, if you are creating a + Twisted Protocol class, used Twisted's + ``namingSchemeForMethodsAndAttributes.`` + +- All IPython's official interfaces should use our conventions. In some cases + this will mean that you need to provide shadow names (first implement + ``fooBar`` and then ``foo_bar = fooBar``). We want to avoid this at all + costs, but it will probably be necessary at times. But, please use this + sparingly! + +Implementation-specific *private* methods will use +``_single_underscore_prefix``. Names with a leading double underscore will +*only* be used in special cases, as they makes subclassing difficult (such +names are not easily seen by child classes). + +Occasionally some run-in lowercase names are used, but mostly for very short +names or where we are implementing methods very similar to existing ones in a +base class (like ``runlines()`` where ``runsource()`` and ``runcode()`` had +established precedent). + +The old IPython codebase has a big mix of classes and modules prefixed with an +explicit ``IP``. In Python this is mostly unnecessary, redundant and frowned +upon, as namespaces offer cleaner prefixing. The only case where this approach +is justified is for classes which are expected to be imported into external +namespaces and a very generic name (like Shell) is too likely to clash with +something else. We'll need to revisit this issue as we clean up and refactor +the code, but in general we should remove as many unnecessary ``IP``/``ip`` +prefixes as possible. However, if a prefix seems absolutely necessary the more +specific ``IPY`` or ``ipy`` are preferred. + + +.. _devel-testing: + +Testing system +============== + +It is extremely important that all code contributed to IPython has tests. Tests +should be written as unittests, doctests or as entities that the `Nose`_ +testing package will find. Regardless of how the tests are written, we will use +`Nose`_ for discovering and running the tests. `Nose`_ will be required to run +the IPython test suite, but will not be required to simply use IPython. + +.. _Nose: http://code.google.com/p/python-nose/ + +Tests of `Twisted`__ using code should be written by subclassing the +``TestCase`` class that comes with ``twisted.trial.unittest``. When this is +done, `Nose`_ will be able to run the tests and the twisted reactor will be +handled correctly. + +.. __: http://www.twistedmatrix.com + +Each subpackage in IPython should have its own ``tests`` directory that +contains all of the tests for that subpackage. This allows each subpackage to +be self-contained. If a subpackage has any dependencies beyond the Python +standard library, the tests for that subpackage should be skipped if the +dependencies are not found. This is very important so users don't get tests +failing simply because they don't have dependencies. + +We also need to look into use Noses ability to tag tests to allow a more +modular approach of running tests. + +.. _devel-config: + +Configuration system +==================== + +IPython uses `.ini`_ files for configuration purposes. This represents a huge +improvement over the configuration system used in IPython. IPython works with +these files using the `ConfigObj`_ package, which IPython includes as +``ipython1/external/configobj.py``. + +Currently, we are using raw `ConfigObj`_ objects themselves. Each subpackage of +IPython should contain a ``config`` subdirectory that contains all of the +configuration information for the subpackage. To see how configuration +information is defined (along with defaults) see at the examples in +``ipython1/kernel/config`` and ``ipython1/core/config``. Likewise, to see how +the configuration information is used, see examples in +``ipython1/kernel/scripts/ipengine.py``. + +Eventually, we will add a new layer on top of the raw `ConfigObj`_ objects. We +are calling this new layer, ``tconfig``, as it will use a `Traits`_-like +validation model. We won't actually use `Traits`_, but will implement +something similar in pure Python. But, even in this new system, we will still +use `ConfigObj`_ and `.ini`_ files underneath the hood. Talk to Fernando if you +are interested in working on this part of IPython. The current prototype of +``tconfig`` is located in the IPython sandbox. + +.. _.ini: http://docs.python.org/lib/module-ConfigParser.html +.. _ConfigObj: http://www.voidspace.org.uk/python/configobj.html +.. _Traits: http://code.enthought.com/traits/ diff --git a/docs/source/development/doc_guide.txt b/docs/source/development/doc_guide.txt new file mode 100644 index 0000000..9a0e453 --- /dev/null +++ b/docs/source/development/doc_guide.txt @@ -0,0 +1,103 @@ +.. _documenting-ipython: + +===================== + Documenting IPython +===================== + +Standalone documentation +======================== + +All standalone documentation should be written in plain text (``.txt``) files +using `reStructuredText`_ for markup and formatting. All such documentation +should be placed in the top level directory ``docs`` of the IPython source +tree. Or, when appropriate, a suitably named subdirectory should be used. The +documentation in this location will serve as the main source for IPython +documentation and all existing documentation should be converted to this +format. + +The actual HTML and PDF docs are built using the Sphinx_ documentation +generation tool. Sphinx has been adopted as the default documentation tool for +Python itself as of version 2.6, as well as by a number of projects that +IPython is related with, such as numpy, scipy, matplotlib, sage and nipy. + +.. _reStructuredText: http://docutils.sourceforge.net/rst.html +.. _Sphinx: http://sphinx.pocoo.org/ + + +The rest of this document is mostly taken from the `matploblib +documentation`__; we are using a number of Sphinx tools and extensions written +by the matplotlib team and will mostly follow their conventions, which are +nicely spelled out in their guide. What follows is thus a lightly adapted +version of the matplotlib documentation guide, taken with permission from the +MPL team. + +.. __: http://matplotlib.sourceforge.net/devel/documenting_mpl.html + + +A bit of Python code:: + + for i in range(10): + print i, + print "A big number:",2**34 + +An interactive Python session:: + + >>> from IPython import genutils + >>> genutils.get_ipython_dir() + '/home/fperez/.ipython' + + +An IPython session: + +.. code-block:: ipython + + In [7]: import IPython + + In [8]: print "This IPython is version:",IPython.__version__ + This IPython is version: 0.9.1 + + In [9]: 2+4 + Out[9]: 6 + + +A bit of shell code: + +.. code-block:: bash + + cd /tmp + echo "My home directory is: $HOME" + ls + + +Docstring format +================ + +Good docstrings are very important. Unfortunately, Python itself only provides +a rather loose standard for docstrings (`PEP 257`_), and there is no universally +accepted convention for all the different parts of a complete docstring. +However, the NumPy project has established a very reasonable standard, and has +developed some tools to support the smooth inclusion of such docstrings in +Sphinx-generated manuals. Rather than inventing yet another pseudo-standard, +IPython will be henceforth documented using the NumPy conventions; we carry +copies of some of the NumPy support tools to remain self-contained, but share +back upstream with NumPy any improvements or fixes we may make to the tools. + +The `NumPy documentation guidelines`_ contain detailed information on this +standard, and for a quick overview, the NumPy `example docstring`_ is a useful +read. + +As in the past IPython used epydoc, currently many docstrings still use epydoc +conventions. We will update them as we go, but all new code should be fully +documented using the NumPy standard. + +.. _PEP 257: http://www.python.org/peps/pep-0257.html +.. _NumPy documentation guidelines: http://projects.scipy.org/numpy/wiki/CodingStyleGuidelines + +.. _example docstring: http://projects.scipy.org/numpy/browser/trunk/doc/EXAMPLE_DOCSTRING.txt + +Additional PEPs of interest regarding documentation of code. While both of +these were rejected, the ideas therein form much of the basis of docutils (the +machinery to process reStructuredText): + +- `Docstring Processing System Framework `_ +- `Docutils Design Specification `_ diff --git a/docs/source/development/index.txt b/docs/source/development/index.txt index 92e0975..97640a3 100644 --- a/docs/source/development/index.txt +++ b/docs/source/development/index.txt @@ -1,12 +1,15 @@ -================== -Development -================== +=========================== + IPython Developer's Guide +=========================== .. toctree:: :maxdepth: 2 - development.txt + overview.txt + coding_guide.txt + doc_guide.txt roadmap.txt + notification_blueprint.txt config_blueprint.txt reorg.txt diff --git a/docs/source/development/development.txt b/docs/source/development/overview.txt similarity index 69% rename from docs/source/development/development.txt rename to docs/source/development/overview.txt index 0e61d88..b4552b8 100644 --- a/docs/source/development/development.txt +++ b/docs/source/development/overview.txt @@ -17,7 +17,29 @@ How to contribute to IPython IPython development is done using Bazaar [Bazaar]_ and Launchpad [Launchpad]_. This makes it easy for people to contribute to the development of IPython. -Here is a sketch of how to get going. +There are several ways in which you can join in. + +If you have a small change that you want to send to the team, you can edit your +bazaar checkout of IPython (see below) in-place, and ask bazaar for the +differences:: + + $ cd /path/to/your/copy/of/ipython + $ bzr diff > my_fixes.diff + +This produces a patch file with your fixes, which we can apply to the source +tree. This file should then be attached to a ticket in our `bug tracker +`_, indicating what it does. + +This model of creating small, self-contained patches works very well and there +are open source projects that do their entire development this way. However, +in IPython we have found that for tracking larger changes, making use of +bazaar's full capabilities in conjunction with Launchpad's code hosting +services makes for a much better experience. + +Making your own branch of IPython allows you to refine your changes over time, +track the development of the main team, and propose your own full version of +the code for others to use and review, with a minimum amount of fuss. The next +parts of this document will explain how to do this. Install Bazaar and create a Launchpad account --------------------------------------------- @@ -102,12 +124,16 @@ commands:: $ bzr merge ../ipython $ bzr commit -m "Merging changes from trunk" -Along the way, you should also run the IPython test suite. You can do this using the :command:`iptest` command:: +Along the way, you should also run the IPython test suite. You can do this +using the :command:`iptest` command (which is basically a customized version of +:command:`nosetests`):: $ cd $ iptest -The :command:`iptest` command will also pick up and run any tests you have written. +The :command:`iptest` command will also pick up and run any tests you have +written. See :ref:`_devel_testing` for further details on the testing system. + Post your branch and request a code review ------------------------------------------ @@ -151,7 +177,8 @@ source tree. The documentation in this location will serve as the main source for IPython documentation and all existing documentation should be converted to this format. -To build the final documentation, we use Sphinx [Sphinx]_. Once you have Sphinx installed, you can build the html docs yourself by doing:: +To build the final documentation, we use Sphinx [Sphinx]_. Once you have +Sphinx installed, you can build the html docs yourself by doing:: $ cd ipython-mybranch/docs $ make html @@ -198,7 +225,8 @@ Naming conventions In terms of naming conventions, we'll follow the guidelines from the `Style Guide for Python Code`_. -For all new IPython code (and much existing code is being refactored), we'll use: +For all new IPython code (and much existing code is being refactored), we'll +use: * All ``lowercase`` module names. @@ -270,17 +298,119 @@ twisted reactor will be handled correctly. Each subpackage in IPython should have its own :file:`tests` directory that contains all of the tests for that subpackage. This allows each subpackage to -be self-contained. If a subpackage has any dependencies beyond the Python -standard library, the tests for that subpackage should be skipped if the -dependencies are not found. This is very important so users don't get tests -failing simply because they don't have dependencies. - -To run the IPython test suite, use the :command:`iptest` command that is installed with IPython:: +be self-contained. A good convention to follow is to have a file named +:file:`test_foo.py` for each module :file:`foo.py` in the package. This makes +it easy to organize the tests, though like most conventions, it's OK to break +it if logic and common sense dictate otherwise. + +If a subpackage has any dependencies beyond the Python standard library, the +tests for that subpackage should be skipped if the dependencies are not +found. This is very important so users don't get tests failing simply because +they don't have dependencies. We ship a set of decorators in the +:mod:`IPython.testing` package to tag tests that may be platform-specific or +otherwise may have restrictions; if the existing ones don't fit your needs, add +a new decorator in that location so other tests can reuse it. + +To run the IPython test suite, use the :command:`iptest` command that is +installed with IPython (if you are using IPython in-place, without installing +it, you can find this script in the :file:`scripts` directory):: $ iptest -This command runs Nose with the proper options and extensions. - +This command runs Nose with the proper options and extensions. By default, +:command:`iptest` runs the entire IPython test suite (skipping tests that may +be platform-specific or which depend on tools you may not have). But you can +also use it to run only one specific test file, or a specific test function. +For example, this will run only the :file:`test_magic` file from the test +suite:: + + $ iptest IPython.tests.test_magic + ---------------------------------------------------------------------- + Ran 10 tests in 0.348s + + OK (SKIP=3) + Deleting object: second_pass + +while the ``path:function`` syntax allows you to select a specific function in +that file to run:: + + $ iptest IPython.tests.test_magic:test_obj_del + ---------------------------------------------------------------------- + Ran 1 test in 0.204s + + OK + +Since :command:`iptest` is based on nosetests, you can pass it any regular +nosetests option. For example, you can use ``--pdb`` or ``--pdb-failures`` to +automatically activate the interactive Pdb debugger on errors or failures. See +the nosetests documentation for further details. + +A few tips for writing tests +---------------------------- + +You can write tests either as normal test files, using all the conventions that +Nose recognizes, or as doctests. Note that *all* IPython functions should have +at least one example that serves as a doctest, whenever technically feasible. +However, example doctests should only be in the main docstring if they are *a +good example*, i.e. if they convey useful information about the function. If +you simply would like to write a test as a doctest, put it in a separate test +file and write a no-op function whose only purpose is its docstring. + +Note, however, that in a file named :file:`test_X`, functions whose only test +is their docstring (as a doctest) and which have no test functionality of their +own, should be called *doctest_foo* instead of *test_foo*, otherwise they get +double-counted (the empty function call is counted as a test, which just +inflates tests numbers artificially). This restriction does not apply to +functions in files with other names, due to how Nose discovers tests. + +You can use IPython examples in your docstrings. Those can make full use of +IPython functionality (magics, variable substitution, etc), but be careful to +keep them generic enough that they run identically on all Operating Systems. + +The prompts in your doctests can be either of the plain Python ``>>>`` variety +or ``In [1]:`` IPython style. Since this is the IPython system, after all, we +encourage you to use IPython prompts throughout, unless you are illustrating a +specific aspect of the normal prompts (such as the ``%doctest_mode`` magic). + +If a test isn't safe to run inside the main nose process (e.g. because it loads +a GUI toolkit), consider running it in a subprocess and capturing its output +for evaluation and test decision later. Here is an example of how to do it, by +relying on the builtin ``_ip`` object that contains the public IPython api as +defined in :mod:`IPython.ipapi`:: + + def test_obj_del(): + """Test that object's __del__ methods are called on exit.""" + test_dir = os.path.dirname(__file__) + del_file = os.path.join(test_dir,'obj_del.py') + out = _ip.IP.getoutput('ipython %s' % del_file) + nt.assert_equals(out,'object A deleted') + + + +If a doctest contains input whose output you don't want to verify identically +via doctest (random output, an object id, etc), you can mark a docstring with +``#random``. All of these test will have their code executed but no output +checking will be done:: + + >>> 1+3 + junk goes here... # random + + >>> 1+2 + again, anything goes #random + if multiline, the random mark is only needed once. + + >>> 1+2 + You can also put the random marker at the end: + # random + + >>> 1+2 + # random + .. or at the beginning. + +In a case where you want an *entire* docstring to be executed but not verified +(this only serves to check that the code runs without crashing, so it should be +used very sparingly), you can put ``# all-random`` in the docstring. + .. _devel_config: Release checklist @@ -303,6 +433,20 @@ Most of the release process is automated by the :file:`release` script in the #. Celebrate! +Porting to 3.0 +============== + +There are no definite plans for porting of IPython to python 3. The major +issue is the dependency on twisted framework for the networking/threading +stuff. It is possible that it the traditional IPython interactive console +could be ported more easily since it has no such dependency. Here are a few +things that will need to be considered when doing such a port especially +if we want to have a codebase that works directly on both 2.x and 3.x. + + 1. The syntax for exceptions changed (PEP 3110). The old + `except exc, var` changed to `except exc as var`. At last + count there was 78 occurences of this usage in the codebase + .. [Bazaar] Bazaar. http://bazaar-vcs.org/ .. [Launchpad] Launchpad. http://www.launchpad.net/ipython .. [reStructuredText] reStructuredText. http://docutils.sourceforge.net/rst.html diff --git a/docs/source/index.txt b/docs/source/index.txt index 688e167..2180bc9 100644 --- a/docs/source/index.txt +++ b/docs/source/index.txt @@ -17,10 +17,11 @@ IPython Documentation interactive/index.txt parallel/index.txt config/index.txt - changes.txt - development/index.txt faq.txt history.txt + changes.txt + development/index.txt + api/index.txt license_and_copyright.txt credits.txt diff --git a/docs/source/install/index.txt b/docs/source/install/index.txt index 6ddc5b0..f6cd618 100644 --- a/docs/source/install/index.txt +++ b/docs/source/install/index.txt @@ -1,8 +1,8 @@ .. _install_index: -================== +============ Installation -================== +============ .. toctree:: :maxdepth: 2 diff --git a/docs/source/install/install.txt b/docs/source/install/install.txt index 41c5050..43377b3 100644 --- a/docs/source/install/install.txt +++ b/docs/source/install/install.txt @@ -1,29 +1,45 @@ Overview ======== -This document describes the steps required to install IPython. IPython is organized into a number of subpackages, each of which has its own dependencies. All of the subpackages come with IPython, so you don't need to download and install them separately. However, to use a given subpackage, you will need to install all of its dependencies. +This document describes the steps required to install IPython. IPython is +organized into a number of subpackages, each of which has its own dependencies. +All of the subpackages come with IPython, so you don't need to download and +install them separately. However, to use a given subpackage, you will need to +install all of its dependencies. Please let us know if you have problems installing IPython or any of its -dependencies. IPython requires Python version 2.4 or greater. We have not tested -IPython with the upcoming 2.6 or 3.0 versions. +dependencies. IPython requires Python version 2.4 or greater. Light testing +has been done on version 2.6, and so far everything looks fine. We have *not* +yet started to port IPython to Python 3.0, where the language changes are much +more significant. .. warning:: - IPython will not work with Python 2.3 or below. + IPython will not work with Python 2.4 or below. -Some of the installation approaches use the :mod:`setuptools` package and its :command:`easy_install` command line program. In many scenarios, this provides the most simple method of installing IPython and its dependencies. It is not required though. More information about :mod:`setuptools` can be found on its website. +Some of the installation approaches use the :mod:`setuptools` package and its +:command:`easy_install` command line program. In many scenarios, this provides +the most simple method of installing IPython and its dependencies. It is not +required though. More information about :mod:`setuptools` can be found on its +website. -More general information about installing Python packages can be found in Python's documentation at http://www.python.org/doc/. +More general information about installing Python packages can be found in +Python's documentation at http://www.python.org/doc/. Quickstart ========== -If you have :mod:`setuptools` installed and you are on OS X or Linux (not Windows), the following will download and install IPython *and* the main optional dependencies:: +If you have :mod:`setuptools` installed and you are on OS X or Linux (not +Windows), the following will download and install IPython *and* the main +optional dependencies:: $ easy_install ipython[kernel,security,test] -This will get Twisted, zope.interface and Foolscap, which are needed for IPython's parallel computing features as well as the nose package, which will enable you to run IPython's test suite. To run IPython's test suite, use the :command:`iptest` command:: +This will get Twisted, zope.interface and Foolscap, which are needed for +IPython's parallel computing features as well as the nose package, which will +enable you to run IPython's test suite. To run IPython's test suite, use the +:command:`iptest` command:: $ iptest @@ -32,12 +48,19 @@ Read on for more specific details and instructions for Windows. Installing IPython itself ========================= -Given a properly built Python, the basic interactive IPython shell will work with no external dependencies. However, some Python distributions (particularly on Windows and OS X), don't come with a working :mod:`readline` module. The IPython shell will work without :mod:`readline`, but will lack many features that users depend on, such as tab completion and command line editing. See below for details of how to make sure you have a working :mod:`readline`. +Given a properly built Python, the basic interactive IPython shell will work +with no external dependencies. However, some Python distributions +(particularly on Windows and OS X), don't come with a working :mod:`readline` +module. The IPython shell will work without :mod:`readline`, but will lack +many features that users depend on, such as tab completion and command line +editing. See below for details of how to make sure you have a working +:mod:`readline`. Installation using easy_install ------------------------------- -If you have :mod:`setuptools` installed, the easiest way of getting IPython is to simple use :command:`easy_install`:: +If you have :mod:`setuptools` installed, the easiest way of getting IPython is +to simple use :command:`easy_install`:: $ easy_install ipython @@ -46,68 +69,91 @@ That's it. Installation from source ------------------------ -If you don't want to use :command:`easy_install`, or don't have it installed, just grab the latest stable build of IPython from `here `_. Then do the following:: +If you don't want to use :command:`easy_install`, or don't have it installed, +just grab the latest stable build of IPython from `here +`_. Then do the following:: $ tar -xzf ipython.tar.gz $ cd ipython $ python setup.py install -If you are installing to a location (like ``/usr/local``) that requires higher permissions, you may need to run the last command with :command:`sudo`. +If you are installing to a location (like ``/usr/local``) that requires higher +permissions, you may need to run the last command with :command:`sudo`. Windows ------- -There are a few caveats for Windows users. The main issue is that a basic ``python setup.py install`` approach won't create ``.bat`` file or Start Menu shortcuts, which most users want. To get an installation with these, there are two choices: +There are a few caveats for Windows users. The main issue is that a basic +``python setup.py install`` approach won't create ``.bat`` file or Start Menu +shortcuts, which most users want. To get an installation with these, there are +two choices: -1. Install using :command:`easy_install`. +1. Install using :command:`easy_install`. -2. Install using our binary ``.exe`` Windows installer, which can be found at `here `_ +2. Install using our binary ``.exe`` Windows installer, which can be found at + `here `_ -3. Install from source, but using :mod:`setuptools` (``python setupegg.py install``). +3. Install from source, but using :mod:`setuptools` (``python setupegg.py + install``). Installing the development version ---------------------------------- -It is also possible to install the development version of IPython from our `Bazaar `_ source code -repository. To do this you will need to have Bazaar installed on your system. Then just do:: +It is also possible to install the development version of IPython from our +`Bazaar `_ source code repository. To do this you will +need to have Bazaar installed on your system. Then just do:: $ bzr branch lp:ipython $ cd ipython $ python setup.py install -Again, this last step on Windows won't create ``.bat`` files or Start Menu shortcuts, so you will have to use one of the other approaches listed above. +Again, this last step on Windows won't create ``.bat`` files or Start Menu +shortcuts, so you will have to use one of the other approaches listed above. -Some users want to be able to follow the development branch as it changes. If you have :mod:`setuptools` installed, this is easy. Simply replace the last step by:: +Some users want to be able to follow the development branch as it changes. If +you have :mod:`setuptools` installed, this is easy. Simply replace the last +step by:: $ python setupegg.py develop -This creates links in the right places and installs the command line script to the appropriate places. Then, if you want to update your IPython at any time, just do:: +This creates links in the right places and installs the command line script to +the appropriate places. Then, if you want to update your IPython at any time, +just do:: $ bzr pull Basic optional dependencies =========================== -There are a number of basic optional dependencies that most users will want to get. These are: +There are a number of basic optional dependencies that most users will want to +get. These are: * readline (for command line editing, tab completion, etc.) * nose (to run the IPython test suite) * pexpect (to use things like irunner) -If you are comfortable installing these things yourself, have at it, otherwise read on for more details. +If you are comfortable installing these things yourself, have at it, otherwise +read on for more details. readline -------- -In principle, all Python distributions should come with a working :mod:`readline` module. But, reality is not quite that simple. There are two common situations where you won't have a working :mod:`readline` module: +In principle, all Python distributions should come with a working +:mod:`readline` module. But, reality is not quite that simple. There are two +common situations where you won't have a working :mod:`readline` module: * If you are using the built-in Python on Mac OS X. * If you are running Windows, which doesn't have a :mod:`readline` module. -On OS X, the built-in Python doesn't not have :mod:`readline` because of license issues. Starting with OS X 10.5 (Leopard), Apple's built-in Python has a BSD-licensed not-quite-compatible readline replacement. As of IPython 0.9, many of the issues related to the differences between readline and libedit have been resolved. For many users, libedit may be sufficient. +On OS X, the built-in Python doesn't not have :mod:`readline` because of +license issues. Starting with OS X 10.5 (Leopard), Apple's built-in Python has +a BSD-licensed not-quite-compatible readline replacement. As of IPython 0.9, +many of the issues related to the differences between readline and libedit have +been resolved. For many users, libedit may be sufficient. -Most users on OS X will want to get the full :mod:`readline` module. To get a working :mod:`readline` module, just do (with :mod:`setuptools` installed):: +Most users on OS X will want to get the full :mod:`readline` module. To get a +working :mod:`readline` module, just do (with :mod:`setuptools` installed):: $ easy_install readline @@ -117,20 +163,22 @@ Most users on OS X will want to get the full :mod:`readline` module. To get a w official python.org binaries) already have readline installed so you don't have to do this step. -If needed, the readline egg can be build and installed from source (see the wiki page at http://ipython.scipy.org/moin/InstallationOSXLeopard). +If needed, the readline egg can be build and installed from source (see the +wiki page at http://ipython.scipy.org/moin/InstallationOSXLeopard). On Windows, you will need the PyReadline module. PyReadline is a separate, Windows only implementation of readline that uses native Windows calls through :mod:`ctypes`. The easiest way of installing PyReadline is you use the binary -installer available `here `_. The -:mod:`ctypes` module, which comes with Python 2.5 and greater, is required by -PyReadline. It is available for Python 2.4 at -http://python.net/crew/theller/ctypes. +installer available `here `_. The :mod:`ctypes` +module, which comes with Python 2.5 and greater, is required by PyReadline. It +is available for Python 2.4 at http://python.net/crew/theller/ctypes. nose ---- -To run the IPython test suite you will need the :mod:`nose` package. Nose provides a great way of sniffing out and running all of the IPython tests. The simplest way of getting nose, is to use :command:`easy_install`:: +To run the IPython test suite you will need the :mod:`nose` package. Nose +provides a great way of sniffing out and running all of the IPython tests. The +simplest way of getting nose, is to use :command:`easy_install`:: $ easy_install nose @@ -138,7 +186,9 @@ Another way of getting this is to do:: $ easy_install ipython[test] -For more installation options, see the `nose website `_. Once you have nose installed, you can run IPython's test suite using the iptest command:: +For more installation options, see the `nose website +`_. Once you have nose +installed, you can run IPython's test suite using the iptest command:: $ iptest @@ -146,7 +196,8 @@ For more installation options, see the `nose website `_ package is used in IPython's :command:`irunner` script. On Unix platforms (including OS X), just do:: +The `pexpect `_ package is used in IPython's +:command:`irunner` script. On Unix platforms (including OS X), just do:: $ easy_install pexpect @@ -155,7 +206,9 @@ Windows users are out of luck as pexpect does not run there. Dependencies for IPython.kernel (parallel computing) ==================================================== -The IPython kernel provides a nice architecture for parallel computing. The main focus of this architecture is on interactive parallel computing. These features require a number of additional packages: +The IPython kernel provides a nice architecture for parallel computing. The +main focus of this architecture is on interactive parallel computing. These +features require a number of additional packages: * zope.interface (yep, we use interfaces) * Twisted (asynchronous networking framework) @@ -170,14 +223,17 @@ On a Unix style platform (including OS X), if you want to use :mod:`setuptools`, zope.interface and Twisted -------------------------- -Twisted [Twisted]_ and zope.interface [ZopeInterface]_ are used for networking related things. On Unix -style platforms (including OS X), the simplest way of getting the these is to -use :command:`easy_install`:: +Twisted [Twisted]_ and zope.interface [ZopeInterface]_ are used for networking +related things. On Unix style platforms (including OS X), the simplest way of +getting the these is to use :command:`easy_install`:: $ easy_install zope.interface $ easy_install Twisted -Of course, you can also download the source tarballs from the `Twisted website `_ and the `zope.interface page at PyPI `_ and do the usual ``python setup.py install`` if you prefer. +Of course, you can also download the source tarballs from the `Twisted website +`_ and the `zope.interface page at PyPI +`_ and do the usual ``python +setup.py install`` if you prefer. Windows is a bit different. For zope.interface and Twisted, simply get the latest binary ``.exe`` installer from the Twisted website. This installer includes both zope.interface and Twisted and should just work. @@ -190,12 +246,15 @@ On all platforms a simple:: $ easy_install foolscap -should work. You can also download the source tarballs from the `Foolscap website `_ and do ``python setup.py install`` if you prefer. +should work. You can also download the source tarballs from the `Foolscap +website `_ and do ``python setup.py install`` +if you prefer. pyOpenSSL --------- -IPython requires an older version of pyOpenSSL [pyOpenSSL]_ (0.6 rather than the current 0.7). There are a couple of options for getting this: +IPython requires an older version of pyOpenSSL [pyOpenSSL]_ (0.6 rather than +the current 0.7). There are a couple of options for getting this: 1. Most Linux distributions have packages for pyOpenSSL. 2. The built-in Python 2.5 on OS X 10.5 already has it installed. @@ -209,9 +268,14 @@ Dependencies for IPython.frontend (the IPython GUI) wxPython -------- -Starting with IPython 0.9, IPython has a new IPython.frontend package that has a nice wxPython based IPython GUI. As you would expect, this GUI requires wxPython. Most Linux distributions have wxPython packages available and the built-in Python on OS X comes with wxPython preinstalled. For Windows, a binary installer is available on the `wxPython website `_. +Starting with IPython 0.9, IPython has a new IPython.frontend package that has +a nice wxPython based IPython GUI. As you would expect, this GUI requires +wxPython. Most Linux distributions have wxPython packages available and the +built-in Python on OS X comes with wxPython preinstalled. For Windows, a +binary installer is available on the `wxPython website +`_. .. [Twisted] Twisted matrix. http://twistedmatrix.org .. [ZopeInterface] http://pypi.python.org/pypi/zope.interface .. [Foolscap] Foolscap network protocol. http://foolscap.lothar.com/trac -.. [pyOpenSSL] pyOpenSSL. http://pyopenssl.sourceforge.net \ No newline at end of file +.. [pyOpenSSL] pyOpenSSL. http://pyopenssl.sourceforge.net diff --git a/docs/source/interactive/reference.txt b/docs/source/interactive/reference.txt index f81587f..023a5ec 100644 --- a/docs/source/interactive/reference.txt +++ b/docs/source/interactive/reference.txt @@ -490,9 +490,9 @@ following example defines a new magic command, %impall:: ip.expose_magic('impall', doimp) You can also define your own aliased names for magic functions. In your -ipythonrc file, placing a line like: +ipythonrc file, placing a line like:: -execute __IP.magic_cl = __IP.magic_clear + execute __IP.magic_cl = __IP.magic_clear will define %cl as a new name for %clear. @@ -502,1572 +502,9 @@ magic functions at any time and their docstrings. You can also type information on the '?' system) to get information about any particular magic function you are interested in. +The API documentation for the :mod:`IPython.Magic` module contains the full +docstrings of all currently available magic commands. -Magic commands --------------- - -The rest of this section is automatically generated for each release -from the docstrings in the IPython code. Therefore the formatting is -somewhat minimal, but this method has the advantage of having -information always in sync with the code. - -A list of all the magic commands available in IPython's default -installation follows. This is similar to what you'll see by simply -typing %magic at the prompt, but that will also give you information -about magic commands you may have added as part of your personal -customizations. - -.. magic_start - -**%Exit**:: - - Exit IPython without confirmation. - -**%Pprint**:: - - Toggle pretty printing on/off. - -**%alias**:: - - Define an alias for a system command. - - '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd' - - Then, typing 'alias_name params' will execute the system command 'cmd - params' (from your underlying operating system). - - Aliases have lower precedence than magic functions and Python normal - variables, so if 'foo' is both a Python variable and an alias, the - alias can not be executed until 'del foo' removes the Python variable. - - You can use the %l specifier in an alias definition to represent the - whole line when the alias is called. For example: - - In [2]: alias all echo "Input in brackets: <%l>"\ - In [3]: all hello world\ - Input in brackets: - - You can also define aliases with parameters using %s specifiers (one - per parameter): - - In [1]: alias parts echo first %s second %s\ - In [2]: %parts A B\ - first A second B\ - In [3]: %parts A\ - Incorrect number of arguments: 2 expected.\ - parts is an alias to: 'echo first %s second %s' - - Note that %l and %s are mutually exclusive. You can only use one or - the other in your aliases. - - Aliases expand Python variables just like system calls using ! or !! - do: all expressions prefixed with '$' get expanded. For details of - the semantic rules, see PEP-215: - http://www.python.org/peps/pep-0215.html. This is the library used by - IPython for variable expansion. If you want to access a true shell - variable, an extra $ is necessary to prevent its expansion by IPython: - - In [6]: alias show echo\ - In [7]: PATH='A Python string'\ - In [8]: show $PATH\ - A Python string\ - In [9]: show $$PATH\ - /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:... - - You can use the alias facility to acess all of $PATH. See the %rehash - and %rehashx functions, which automatically create aliases for the - contents of your $PATH. - - If called with no parameters, %alias prints the current alias table. - -**%autocall**:: - - Make functions callable without having to type parentheses. - - Usage: - - %autocall [mode] - - The mode can be one of: 0->Off, 1->Smart, 2->Full. If not given, the - value is toggled on and off (remembering the previous state). - - In more detail, these values mean: - - 0 -> fully disabled - - 1 -> active, but do not apply if there are no arguments on the line. - - In this mode, you get: - - In [1]: callable - Out[1]: - - In [2]: callable 'hello' - ------> callable('hello') - Out[2]: False - - 2 -> Active always. Even if no arguments are present, the callable - object is called: - - In [4]: callable - ------> callable() - - Note that even with autocall off, you can still use '/' at the start of - a line to treat the first argument on the command line as a function - and add parentheses to it: - - In [8]: /str 43 - ------> str(43) - Out[8]: '43' - -**%autoindent**:: - - Toggle autoindent on/off (if available). - -**%automagic**:: - - Make magic functions callable without having to type the initial %. - - Without argumentsl toggles on/off (when off, you must call it as - %automagic, of course). With arguments it sets the value, and you can - use any of (case insensitive): - - - on,1,True: to activate - - - off,0,False: to deactivate. - - Note that magic functions have lowest priority, so if there's a - variable whose name collides with that of a magic fn, automagic won't - work for that function (you get the variable instead). However, if you - delete the variable (del var), the previously shadowed magic function - becomes visible to automagic again. - -**%bg**:: - - Run a job in the background, in a separate thread. - - For example, - - %bg myfunc(x,y,z=1) - - will execute 'myfunc(x,y,z=1)' in a background thread. As soon as the - execution starts, a message will be printed indicating the job - number. If your job number is 5, you can use - - myvar = jobs.result(5) or myvar = jobs[5].result - - to assign this result to variable 'myvar'. - - IPython has a job manager, accessible via the 'jobs' object. You can - type jobs? to get more information about it, and use jobs. to see - its attributes. All attributes not starting with an underscore are - meant for public use. - - In particular, look at the jobs.new() method, which is used to create - new jobs. This magic %bg function is just a convenience wrapper - around jobs.new(), for expression-based jobs. If you want to create a - new job with an explicit function object and arguments, you must call - jobs.new() directly. - - The jobs.new docstring also describes in detail several important - caveats associated with a thread-based model for background job - execution. Type jobs.new? for details. - - You can check the status of all jobs with jobs.status(). - - The jobs variable is set by IPython into the Python builtin namespace. - If you ever declare a variable named 'jobs', you will shadow this - name. You can either delete your global jobs variable to regain - access to the job manager, or make a new name and assign it manually - to the manager (stored in IPython's namespace). For example, to - assign the job manager to the Jobs name, use: - - Jobs = __builtins__.jobs - -**%bookmark**:: - - Manage IPython's bookmark system. - - %bookmark - set bookmark to current dir - %bookmark - set bookmark to - %bookmark -l - list all bookmarks - %bookmark -d - remove bookmark - %bookmark -r - remove all bookmarks - - You can later on access a bookmarked folder with: - %cd -b - or simply '%cd ' if there is no directory called AND - there is such a bookmark defined. - - Your bookmarks persist through IPython sessions, but they are - associated with each profile. - -**%cd**:: - - Change the current working directory. - - This command automatically maintains an internal list of directories - you visit during your IPython session, in the variable _dh. The - command %dhist shows this history nicely formatted. You can also - do 'cd -' to see directory history conveniently. - - Usage: - - cd 'dir': changes to directory 'dir'. - - cd -: changes to the last visited directory. - - cd -: changes to the n-th directory in the directory history. - - cd -b : jump to a bookmark set by %bookmark - (note: cd is enough if there is no - directory , but a bookmark with the name exists.) - 'cd -b ' allows you to tab-complete bookmark names. - - Options: - - -q: quiet. Do not print the working directory after the cd command is - executed. By default IPython's cd command does print this directory, - since the default prompts do not display path information. - - Note that !cd doesn't work for this purpose because the shell where - !command runs is immediately discarded after executing 'command'. - -**%clear**:: - - Clear various data (e.g. stored history data) - - %clear out - clear output history - %clear in - clear input history - %clear shadow_compress - Compresses shadow history (to speed up ipython) - %clear shadow_nuke - permanently erase all entries in shadow history - %clear dhist - clear dir history - -**%color_info**:: - - Toggle color_info. - - The color_info configuration parameter controls whether colors are - used for displaying object details (by things like %psource, %pfile or - the '?' system). This function toggles this value with each call. - - Note that unless you have a fairly recent pager (less works better - than more) in your system, using colored object information displays - will not work properly. Test it and see. - -**%colors**:: - - Switch color scheme for prompts, info system and exception handlers. - - Currently implemented schemes: NoColor, Linux, LightBG. - - Color scheme names are not case-sensitive. - -**%cpaste**:: - - Allows you to paste & execute a pre-formatted code block from clipboard - - You must terminate the block with '--' (two minus-signs) alone on the - line. You can also provide your own sentinel with '%paste -s %%' ('%%' - is the new sentinel for this operation) - - The block is dedented prior to execution to enable execution of method - definitions. '>' and '+' characters at the beginning of a line are - ignored, to allow pasting directly from e-mails or diff files. The - executed block is also assigned to variable named 'pasted_block' for - later editing with '%edit pasted_block'. - - You can also pass a variable name as an argument, e.g. '%cpaste foo'. - This assigns the pasted block to variable 'foo' as string, without - dedenting or executing it. - - Do not be alarmed by garbled output on Windows (it's a readline bug). - Just press enter and type -- (and press enter again) and the block - will be what was just pasted. - - IPython statements (magics, shell escapes) are not supported (yet). - -**%debug**:: - - Activate the interactive debugger in post-mortem mode. - - If an exception has just occurred, this lets you inspect its stack - frames interactively. Note that this will always work only on the last - traceback that occurred, so you must call this quickly after an - exception that you wish to inspect has fired, because if another one - occurs, it clobbers the previous one. - - If you want IPython to automatically do this on every exception, see - the %pdb magic for more details. - -**%dhist**:: - - Print your history of visited directories. - - %dhist -> print full history\ - %dhist n -> print last n entries only\ - %dhist n1 n2 -> print entries between n1 and n2 (n1 not included)\ - - This history is automatically maintained by the %cd command, and - always available as the global list variable _dh. You can use %cd - - to go to directory number . - - Note that most of time, you should view directory history by entering - cd -. - -**%dirs**:: - - Return the current directory stack. - -**%doctest_mode**:: - - Toggle doctest mode on and off. - - This mode allows you to toggle the prompt behavior between normal - IPython prompts and ones that are as similar to the default IPython - interpreter as possible. - - It also supports the pasting of code snippets that have leading '>>>' - and '...' prompts in them. This means that you can paste doctests from - files or docstrings (even if they have leading whitespace), and the - code will execute correctly. You can then use '%history -tn' to see - the translated history without line numbers; this will give you the - input after removal of all the leading prompts and whitespace, which - can be pasted back into an editor. - - With these features, you can switch into this mode easily whenever you - need to do testing and changes to doctests, without having to leave - your existing IPython session. - -**%ed**:: - - Alias to %edit. - -**%edit**:: - - Bring up an editor and execute the resulting code. - - Usage: - %edit [options] [args] - - %edit runs IPython's editor hook. The default version of this hook is - set to call the __IPYTHON__.rc.editor command. This is read from your - environment variable $EDITOR. If this isn't found, it will default to - vi under Linux/Unix and to notepad under Windows. See the end of this - docstring for how to change the editor hook. - - You can also set the value of this editor via the command line option - '-editor' or in your ipythonrc file. This is useful if you wish to use - specifically for IPython an editor different from your typical default - (and for Windows users who typically don't set environment variables). - - This command allows you to conveniently edit multi-line code right in - your IPython session. - - If called without arguments, %edit opens up an empty editor with a - temporary file and will execute the contents of this file when you - close it (don't forget to save it!). - - - Options: - - -n : open the editor at a specified line number. By default, - the IPython editor hook uses the unix syntax 'editor +N filename', but - you can configure this by providing your own modified hook if your - favorite editor supports line-number specifications with a different - syntax. - - -p: this will call the editor with the same data as the previous time - it was used, regardless of how long ago (in your current session) it - was. - - -r: use 'raw' input. This option only applies to input taken from the - user's history. By default, the 'processed' history is used, so that - magics are loaded in their transformed version to valid Python. If - this option is given, the raw input as typed as the command line is - used instead. When you exit the editor, it will be executed by - IPython's own processor. - - -x: do not execute the edited code immediately upon exit. This is - mainly useful if you are editing programs which need to be called with - command line arguments, which you can then do using %run. - - - Arguments: - - If arguments are given, the following possibilites exist: - - - The arguments are numbers or pairs of colon-separated numbers (like - 1 4:8 9). These are interpreted as lines of previous input to be - loaded into the editor. The syntax is the same of the %macro command. - - - If the argument doesn't start with a number, it is evaluated as a - variable and its contents loaded into the editor. You can thus edit - any string which contains python code (including the result of - previous edits). - - - If the argument is the name of an object (other than a string), - IPython will try to locate the file where it was defined and open the - editor at the point where it is defined. You can use `%edit function` - to load an editor exactly at the point where 'function' is defined, - edit it and have the file be executed automatically. - - If the object is a macro (see %macro for details), this opens up your - specified editor with a temporary file containing the macro's data. - Upon exit, the macro is reloaded with the contents of the file. - - Note: opening at an exact line is only supported under Unix, and some - editors (like kedit and gedit up to Gnome 2.8) do not understand the - '+NUMBER' parameter necessary for this feature. Good editors like - (X)Emacs, vi, jed, pico and joe all do. - - - If the argument is not found as a variable, IPython will look for a - file with that name (adding .py if necessary) and load it into the - editor. It will execute its contents with execfile() when you exit, - loading any code in the file into your interactive namespace. - - After executing your code, %edit will return as output the code you - typed in the editor (except when it was an existing file). This way - you can reload the code in further invocations of %edit as a variable, - via _ or Out[], where is the prompt number of - the output. - - Note that %edit is also available through the alias %ed. - - This is an example of creating a simple function inside the editor and - then modifying it. First, start up the editor: - - In [1]: ed\ - Editing... done. Executing edited code...\ - Out[1]: 'def foo():\n print "foo() was defined in an editing session"\n' - - We can then call the function foo(): - - In [2]: foo()\ - foo() was defined in an editing session - - Now we edit foo. IPython automatically loads the editor with the - (temporary) file where foo() was previously defined: - - In [3]: ed foo\ - Editing... done. Executing edited code... - - And if we call foo() again we get the modified version: - - In [4]: foo()\ - foo() has now been changed! - - Here is an example of how to edit a code snippet successive - times. First we call the editor: - - In [8]: ed\ - Editing... done. Executing edited code...\ - hello\ - Out[8]: "print 'hello'\n" - - Now we call it again with the previous output (stored in _): - - In [9]: ed _\ - Editing... done. Executing edited code...\ - hello world\ - Out[9]: "print 'hello world'\n" - - Now we call it with the output #8 (stored in _8, also as Out[8]): - - In [10]: ed _8\ - Editing... done. Executing edited code...\ - hello again\ - Out[10]: "print 'hello again'\n" - - - Changing the default editor hook: - - If you wish to write your own editor hook, you can put it in a - configuration file which you load at startup time. The default hook - is defined in the IPython.hooks module, and you can use that as a - starting example for further modifications. That file also has - general instructions on how to set a new hook for use once you've - defined it. - -**%env**:: - - List environment variables. - -**%exit**:: - - Exit IPython, confirming if configured to do so. - - You can configure whether IPython asks for confirmation upon exit by - setting the confirm_exit flag in the ipythonrc file. - -**%hist**:: - - Alternate name for %history. - -**%history**:: - - Print input history (_i variables), with most recent last. - - %history -> print at most 40 inputs (some may be multi-line)\ - %history n -> print at most n inputs\ - %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\ - - Each input's number is shown, and is accessible as the - automatically generated variable _i. Multi-line statements are - printed starting at a new line for easy copy/paste. - - - Options: - - -n: do NOT print line numbers. This is useful if you want to get a - printout of many lines which can be directly pasted into a text - editor. - - This feature is only available if numbered prompts are in use. - - -t: (default) print the 'translated' history, as IPython understands it. - IPython filters your input and converts it all into valid Python source - before executing it (things like magics or aliases are turned into - function calls, for example). With this option, you'll see the native - history instead of the user-entered version: '%cd /' will be seen as - '_ip.magic("%cd /")' instead of '%cd /'. - - -r: print the 'raw' history, i.e. the actual commands you typed. - - -g: treat the arg as a pattern to grep for in (full) history. - This includes the "shadow history" (almost all commands ever written). - Use '%hist -g' to show full shadow history (may be very long). - In shadow history, every index nuwber starts with 0. - - -f FILENAME: instead of printing the output to the screen, redirect it to - the given file. The file is always overwritten, though IPython asks for - confirmation first if it already exists. - -**%logoff**:: - - Temporarily stop logging. - - You must have previously started logging. - -**%logon**:: - - Restart logging. - - This function is for restarting logging which you've temporarily - stopped with %logoff. For starting logging for the first time, you - must use the %logstart function, which allows you to specify an - optional log filename. - -**%logstart**:: - - Start logging anywhere in a session. - - %logstart [-o|-r|-t] [log_name [log_mode]] - - If no name is given, it defaults to a file named 'ipython_log.py' in your - current directory, in 'rotate' mode (see below). - - '%logstart name' saves to file 'name' in 'backup' mode. It saves your - history up to that point and then continues logging. - - %logstart takes a second optional parameter: logging mode. This can be one - of (note that the modes are given unquoted):\ - append: well, that says it.\ - backup: rename (if exists) to name~ and start name.\ - global: single logfile in your home dir, appended to.\ - over : overwrite existing log.\ - rotate: create rotating logs name.1~, name.2~, etc. - - Options: - - -o: log also IPython's output. In this mode, all commands which - generate an Out[NN] prompt are recorded to the logfile, right after - their corresponding input line. The output lines are always - prepended with a '#[Out]# ' marker, so that the log remains valid - Python code. - - Since this marker is always the same, filtering only the output from - a log is very easy, using for example a simple awk call: - - awk -F'#\[Out\]# ' '{if($2) {print $2}}' ipython_log.py - - -r: log 'raw' input. Normally, IPython's logs contain the processed - input, so that user lines are logged in their final form, converted - into valid Python. For example, %Exit is logged as - '_ip.magic("Exit"). If the -r flag is given, all input is logged - exactly as typed, with no transformations applied. - - -t: put timestamps before each input line logged (these are put in - comments). - -**%logstate**:: - - Print the status of the logging system. - -**%logstop**:: - - Fully stop logging and close log file. - - In order to start logging again, a new %logstart call needs to be made, - possibly (though not necessarily) with a new filename, mode and other - options. - -**%lsmagic**:: - - List currently available magic functions. - -**%macro**:: - - Define a set of input lines as a macro for future re-execution. - - Usage:\ - %macro [options] name n1-n2 n3-n4 ... n5 .. n6 ... - - Options: - - -r: use 'raw' input. By default, the 'processed' history is used, - so that magics are loaded in their transformed version to valid - Python. If this option is given, the raw input as typed as the - command line is used instead. - - This will define a global variable called `name` which is a string - made of joining the slices and lines you specify (n1,n2,... numbers - above) from your input history into a single string. This variable - acts like an automatic function which re-executes those lines as if - you had typed them. You just type 'name' at the prompt and the code - executes. - - The notation for indicating number ranges is: n1-n2 means 'use line - numbers n1,...n2' (the endpoint is included). That is, '5-7' means - using the lines numbered 5,6 and 7. - - Note: as a 'hidden' feature, you can also use traditional python slice - notation, where N:M means numbers N through M-1. - - For example, if your history contains (%hist prints it): - - 44: x=1\ - 45: y=3\ - 46: z=x+y\ - 47: print x\ - 48: a=5\ - 49: print 'x',x,'y',y\ - - you can create a macro with lines 44 through 47 (included) and line 49 - called my_macro with: - - In [51]: %macro my_macro 44-47 49 - - Now, typing `my_macro` (without quotes) will re-execute all this code - in one pass. - - You don't need to give the line-numbers in order, and any given line - number can appear multiple times. You can assemble macros with any - lines from your input history in any order. - - The macro is a simple object which holds its value in an attribute, - but IPython's display system checks for macros and executes them as - code instead of printing them when you type their name. - - You can view a macro's contents by explicitly printing it with: - - 'print macro_name'. - - For one-off cases which DON'T contain magic function calls in them you - can obtain similar results by explicitly executing slices from your - input history with: - - In [60]: exec In[44:48]+In[49] - -**%magic**:: - - Print information about the magic function system. - -**%mglob**:: - - This program allows specifying filenames with "mglob" mechanism. - Supported syntax in globs (wilcard matching patterns):: - - *.cpp ?ellowo* - - obvious. Differs from normal glob in that dirs are not included. - Unix users might want to write this as: "*.cpp" "?ellowo*" - rec:/usr/share=*.txt,*.doc - - get all *.txt and *.doc under /usr/share, - recursively - rec:/usr/share - - All files under /usr/share, recursively - rec:*.py - - All .py files under current working dir, recursively - foo - - File or dir foo - !*.bak readme* - - readme*, exclude files ending with .bak - !.svn/ !.hg/ !*_Data/ rec:. - - Skip .svn, .hg, foo_Data dirs (and their subdirs) in recurse. - Trailing / is the key, \ does not work! - dir:foo - - the directory foo if it exists (not files in foo) - dir:* - - all directories in current folder - foo.py bar.* !h* rec:*.py - - Obvious. !h* exclusion only applies for rec:*.py. - foo.py is *not* included twice. - @filelist.txt - - All files listed in 'filelist.txt' file, on separate lines. - -**%page**:: - - Pretty print the object and display it through a pager. - - %page [options] OBJECT - - If no object is given, use _ (last output). - - Options: - - -r: page str(object), don't pretty-print it. - -**%pdb**:: - - Control the automatic calling of the pdb interactive debugger. - - Call as '%pdb on', '%pdb 1', '%pdb off' or '%pdb 0'. If called without - argument it works as a toggle. - - When an exception is triggered, IPython can optionally call the - interactive pdb debugger after the traceback printout. %pdb toggles - this feature on and off. - - The initial state of this feature is set in your ipythonrc - configuration file (the variable is called 'pdb'). - - If you want to just activate the debugger AFTER an exception has fired, - without having to type '%pdb on' and rerunning your code, you can use - the %debug magic. - -**%pdef**:: - - Print the definition header for any callable object. - - If the object is a class, print the constructor information. - -**%pdoc**:: - - Print the docstring for an object. - - If the given object is a class, it will print both the class and the - constructor docstrings. - -**%pfile**:: - - Print (or run through pager) the file where an object is defined. - - The file opens at the line where the object definition begins. IPython - will honor the environment variable PAGER if set, and otherwise will - do its best to print the file in a convenient form. - - If the given argument is not an object currently defined, IPython will - try to interpret it as a filename (automatically adding a .py extension - if needed). You can thus use %pfile as a syntax highlighting code - viewer. - -**%pinfo**:: - - Provide detailed information about an object. - - '%pinfo object' is just a synonym for object? or ?object. - -**%popd**:: - - Change to directory popped off the top of the stack. - -**%profile**:: - - Print your currently active IPyhton profile. - -**%prun**:: - - Run a statement through the python code profiler. - - Usage:\ - %prun [options] statement - - The given statement (which doesn't require quote marks) is run via the - python profiler in a manner similar to the profile.run() function. - Namespaces are internally managed to work correctly; profile.run - cannot be used in IPython because it makes certain assumptions about - namespaces which do not hold under IPython. - - Options: - - -l : you can place restrictions on what or how much of the - profile gets printed. The limit value can be: - - * A string: only information for function names containing this string - is printed. - - * An integer: only these many lines are printed. - - * A float (between 0 and 1): this fraction of the report is printed - (for example, use a limit of 0.4 to see the topmost 40% only). - - You can combine several limits with repeated use of the option. For - example, '-l __init__ -l 5' will print only the topmost 5 lines of - information about class constructors. - - -r: return the pstats.Stats object generated by the profiling. This - object has all the information about the profile in it, and you can - later use it for further analysis or in other functions. - - -s : sort profile by given key. You can provide more than one key - by using the option several times: '-s key1 -s key2 -s key3...'. The - default sorting key is 'time'. - - The following is copied verbatim from the profile documentation - referenced below: - - When more than one key is provided, additional keys are used as - secondary criteria when the there is equality in all keys selected - before them. - - Abbreviations can be used for any key names, as long as the - abbreviation is unambiguous. The following are the keys currently - defined: - - Valid Arg Meaning\ - "calls" call count\ - "cumulative" cumulative time\ - "file" file name\ - "module" file name\ - "pcalls" primitive call count\ - "line" line number\ - "name" function name\ - "nfl" name/file/line\ - "stdname" standard name\ - "time" internal time - - Note that all sorts on statistics are in descending order (placing - most time consuming items first), where as name, file, and line number - searches are in ascending order (i.e., alphabetical). The subtle - distinction between "nfl" and "stdname" is that the standard name is a - sort of the name as printed, which means that the embedded line - numbers get compared in an odd way. For example, lines 3, 20, and 40 - would (if the file names were the same) appear in the string order - "20" "3" and "40". In contrast, "nfl" does a numeric compare of the - line numbers. In fact, sort_stats("nfl") is the same as - sort_stats("name", "file", "line"). - - -T : save profile results as shown on screen to a text - file. The profile is still shown on screen. - - -D : save (via dump_stats) profile statistics to given - filename. This data is in a format understod by the pstats module, and - is generated by a call to the dump_stats() method of profile - objects. The profile is still shown on screen. - - If you want to run complete programs under the profiler's control, use - '%run -p [prof_opts] filename.py [args to program]' where prof_opts - contains profiler specific options as described here. - - You can read the complete documentation for the profile module with:\ - In [1]: import profile; profile.help() - -**%psearch**:: - - Search for object in namespaces by wildcard. - - %psearch [options] PATTERN [OBJECT TYPE] - - Note: ? can be used as a synonym for %psearch, at the beginning or at - the end: both a*? and ?a* are equivalent to '%psearch a*'. Still, the - rest of the command line must be unchanged (options come first), so - for example the following forms are equivalent - - %psearch -i a* function - -i a* function? - ?-i a* function - - Arguments: - - PATTERN - - where PATTERN is a string containing * as a wildcard similar to its - use in a shell. The pattern is matched in all namespaces on the - search path. By default objects starting with a single _ are not - matched, many IPython generated objects have a single - underscore. The default is case insensitive matching. Matching is - also done on the attributes of objects and not only on the objects - in a module. - - [OBJECT TYPE] - - Is the name of a python type from the types module. The name is - given in lowercase without the ending type, ex. StringType is - written string. By adding a type here only objects matching the - given type are matched. Using all here makes the pattern match all - types (this is the default). - - Options: - - -a: makes the pattern match even objects whose names start with a - single underscore. These names are normally ommitted from the - search. - - -i/-c: make the pattern case insensitive/sensitive. If neither of - these options is given, the default is read from your ipythonrc - file. The option name which sets this value is - 'wildcards_case_sensitive'. If this option is not specified in your - ipythonrc file, IPython's internal default is to do a case sensitive - search. - - -e/-s NAMESPACE: exclude/search a given namespace. The pattern you - specifiy can be searched in any of the following namespaces: - 'builtin', 'user', 'user_global','internal', 'alias', where - 'builtin' and 'user' are the search defaults. Note that you should - not use quotes when specifying namespaces. - - 'Builtin' contains the python module builtin, 'user' contains all - user data, 'alias' only contain the shell aliases and no python - objects, 'internal' contains objects used by IPython. The - 'user_global' namespace is only used by embedded IPython instances, - and it contains module-level globals. You can add namespaces to the - search with -s or exclude them with -e (these options can be given - more than once). - - Examples: - - %psearch a* -> objects beginning with an a - %psearch -e builtin a* -> objects NOT in the builtin space starting in a - %psearch a* function -> all functions beginning with an a - %psearch re.e* -> objects beginning with an e in module re - %psearch r*.e* -> objects that start with e in modules starting in r - %psearch r*.* string -> all strings in modules beginning with r - - Case sensitve search: - - %psearch -c a* list all object beginning with lower case a - - Show objects beginning with a single _: - - %psearch -a _* list objects beginning with a single underscore - -**%psource**:: - - Print (or run through pager) the source code for an object. - -**%pushd**:: - - Place the current dir on stack and change directory. - - Usage:\ - %pushd ['dirname'] - -**%pwd**:: - - Return the current working directory path. - -**%pycat**:: - - Show a syntax-highlighted file through a pager. - - This magic is similar to the cat utility, but it will assume the file - to be Python source and will show it with syntax highlighting. - -**%quickref**:: - - Show a quick reference sheet - -**%quit**:: - - Exit IPython, confirming if configured to do so (like %exit) - -**%r**:: - - Repeat previous input. - - Note: Consider using the more powerfull %rep instead! - - If given an argument, repeats the previous command which starts with - the same string, otherwise it just repeats the previous input. - - Shell escaped commands (with ! as first character) are not recognized - by this system, only pure python code and magic commands. - -**%rehashdir**:: - - Add executables in all specified dirs to alias table - - Usage: - - %rehashdir c:/bin;c:/tools - - Add all executables under c:/bin and c:/tools to alias table, in - order to make them directly executable from any directory. - - Without arguments, add all executables in current directory. - -**%rehashx**:: - - Update the alias table with all executable files in $PATH. - - This version explicitly checks that every entry in $PATH is a file - with execute access (os.X_OK), so it is much slower than %rehash. - - Under Windows, it checks executability as a match agains a - '|'-separated string of extensions, stored in the IPython config - variable win_exec_ext. This defaults to 'exe|com|bat'. - - This function also resets the root module cache of module completer, - used on slow filesystems. - -**%rep**:: - - Repeat a command, or get command to input line for editing - - - %rep (no arguments): - - Place a string version of last computation result (stored in the special '_' - variable) to the next input prompt. Allows you to create elaborate command - lines without using copy-paste:: - - $ l = ["hei", "vaan"] - $ "".join(l) - ==> heivaan - $ %rep - $ heivaan_ <== cursor blinking - - %rep 45 - - Place history line 45 to next input prompt. Use %hist to find out the - number. - - %rep 1-4 6-7 3 - - Repeat the specified lines immediately. Input slice syntax is the same as - in %macro and %save. - - %rep foo - - Place the most recent line that has the substring "foo" to next input. - (e.g. 'svn ci -m foobar'). - -**%reset**:: - - Resets the namespace by removing all names defined by the user. - - Input/Output history are left around in case you need them. - -**%run**:: - - Run the named file inside IPython as a program. - - Usage:\ - %run [-n -i -t [-N] -d [-b] -p [profile options]] file [args] - - Parameters after the filename are passed as command-line arguments to - the program (put in sys.argv). Then, control returns to IPython's - prompt. - - This is similar to running at a system prompt:\ - $ python file args\ - but with the advantage of giving you IPython's tracebacks, and of - loading all variables into your interactive namespace for further use - (unless -p is used, see below). - - The file is executed in a namespace initially consisting only of - __name__=='__main__' and sys.argv constructed as indicated. It thus - sees its environment as if it were being run as a stand-alone program - (except for sharing global objects such as previously imported - modules). But after execution, the IPython interactive namespace gets - updated with all variables defined in the program (except for __name__ - and sys.argv). This allows for very convenient loading of code for - interactive work, while giving each program a 'clean sheet' to run in. - - Options: - - -n: __name__ is NOT set to '__main__', but to the running file's name - without extension (as python does under import). This allows running - scripts and reloading the definitions in them without calling code - protected by an ' if __name__ == "__main__" ' clause. - - -i: run the file in IPython's namespace instead of an empty one. This - is useful if you are experimenting with code written in a text editor - which depends on variables defined interactively. - - -e: ignore sys.exit() calls or SystemExit exceptions in the script - being run. This is particularly useful if IPython is being used to - run unittests, which always exit with a sys.exit() call. In such - cases you are interested in the output of the test results, not in - seeing a traceback of the unittest module. - - -t: print timing information at the end of the run. IPython will give - you an estimated CPU time consumption for your script, which under - Unix uses the resource module to avoid the wraparound problems of - time.clock(). Under Unix, an estimate of time spent on system tasks - is also given (for Windows platforms this is reported as 0.0). - - If -t is given, an additional -N option can be given, where - must be an integer indicating how many times you want the script to - run. The final timing report will include total and per run results. - - For example (testing the script uniq_stable.py): - - In [1]: run -t uniq_stable - - IPython CPU timings (estimated):\ - User : 0.19597 s.\ - System: 0.0 s.\ - - In [2]: run -t -N5 uniq_stable - - IPython CPU timings (estimated):\ - Total runs performed: 5\ - Times : Total Per run\ - User : 0.910862 s, 0.1821724 s.\ - System: 0.0 s, 0.0 s. - - -d: run your program under the control of pdb, the Python debugger. - This allows you to execute your program step by step, watch variables, - etc. Internally, what IPython does is similar to calling: - - pdb.run('execfile("YOURFILENAME")') - - with a breakpoint set on line 1 of your file. You can change the line - number for this automatic breakpoint to be by using the -bN option - (where N must be an integer). For example: - - %run -d -b40 myscript - - will set the first breakpoint at line 40 in myscript.py. Note that - the first breakpoint must be set on a line which actually does - something (not a comment or docstring) for it to stop execution. - - When the pdb debugger starts, you will see a (Pdb) prompt. You must - first enter 'c' (without qoutes) to start execution up to the first - breakpoint. - - Entering 'help' gives information about the use of the debugger. You - can easily see pdb's full documentation with "import pdb;pdb.help()" - at a prompt. - - -p: run program under the control of the Python profiler module (which - prints a detailed report of execution times, function calls, etc). - - You can pass other options after -p which affect the behavior of the - profiler itself. See the docs for %prun for details. - - In this mode, the program's variables do NOT propagate back to the - IPython interactive namespace (because they remain in the namespace - where the profiler executes them). - - Internally this triggers a call to %prun, see its documentation for - details on the options available specifically for profiling. - - There is one special usage for which the text above doesn't apply: - if the filename ends with .ipy, the file is run as ipython script, - just as if the commands were written on IPython prompt. - -**%runlog**:: - - Run files as logs. - - Usage:\ - %runlog file1 file2 ... - - Run the named files (treating them as log files) in sequence inside - the interpreter, and return to the prompt. This is much slower than - %run because each line is executed in a try/except block, but it - allows running files with syntax errors in them. - - Normally IPython will guess when a file is one of its own logfiles, so - you can typically use %run even for logs. This shorthand allows you to - force any file to be treated as a log file. - -**%save**:: - - Save a set of lines to a given filename. - - Usage:\ - %save [options] filename n1-n2 n3-n4 ... n5 .. n6 ... - - Options: - - -r: use 'raw' input. By default, the 'processed' history is used, - so that magics are loaded in their transformed version to valid - Python. If this option is given, the raw input as typed as the - command line is used instead. - - This function uses the same syntax as %macro for line extraction, but - instead of creating a macro it saves the resulting string to the - filename you specify. - - It adds a '.py' extension to the file if you don't do so yourself, and - it asks for confirmation before overwriting existing files. - -**%sc**:: - - Shell capture - execute a shell command and capture its output. - - DEPRECATED. Suboptimal, retained for backwards compatibility. - - You should use the form 'var = !command' instead. Example: - - "%sc -l myfiles = ls ~" should now be written as - - "myfiles = !ls ~" - - myfiles.s, myfiles.l and myfiles.n still apply as documented - below. - - -- - %sc [options] varname=command - - IPython will run the given command using commands.getoutput(), and - will then update the user's interactive namespace with a variable - called varname, containing the value of the call. Your command can - contain shell wildcards, pipes, etc. - - The '=' sign in the syntax is mandatory, and the variable name you - supply must follow Python's standard conventions for valid names. - - (A special format without variable name exists for internal use) - - Options: - - -l: list output. Split the output on newlines into a list before - assigning it to the given variable. By default the output is stored - as a single string. - - -v: verbose. Print the contents of the variable. - - In most cases you should not need to split as a list, because the - returned value is a special type of string which can automatically - provide its contents either as a list (split on newlines) or as a - space-separated string. These are convenient, respectively, either - for sequential processing or to be passed to a shell command. - - For example: - - # Capture into variable a - In [9]: sc a=ls *py - - # a is a string with embedded newlines - In [10]: a - Out[10]: 'setup.py win32_manual_post_install.py' - - # which can be seen as a list: - In [11]: a.l - Out[11]: ['setup.py', 'win32_manual_post_install.py'] - - # or as a whitespace-separated string: - In [12]: a.s - Out[12]: 'setup.py win32_manual_post_install.py' - - # a.s is useful to pass as a single command line: - In [13]: !wc -l $a.s - 146 setup.py - 130 win32_manual_post_install.py - 276 total - - # while the list form is useful to loop over: - In [14]: for f in a.l: - ....: !wc -l $f - ....: - 146 setup.py - 130 win32_manual_post_install.py - - Similiarly, the lists returned by the -l option are also special, in - the sense that you can equally invoke the .s attribute on them to - automatically get a whitespace-separated string from their contents: - - In [1]: sc -l b=ls *py - - In [2]: b - Out[2]: ['setup.py', 'win32_manual_post_install.py'] - - In [3]: b.s - Out[3]: 'setup.py win32_manual_post_install.py' - - In summary, both the lists and strings used for ouptut capture have - the following special attributes: - - .l (or .list) : value as list. - .n (or .nlstr): value as newline-separated string. - .s (or .spstr): value as space-separated string. - -**%store**:: - - Lightweight persistence for python variables. - - Example: - - ville@badger[~]|1> A = ['hello',10,'world']\ - ville@badger[~]|2> %store A\ - ville@badger[~]|3> Exit - - (IPython session is closed and started again...) - - ville@badger:~$ ipython -p pysh\ - ville@badger[~]|1> print A - - ['hello', 10, 'world'] - - Usage: - - %store - Show list of all variables and their current values\ - %store - Store the *current* value of the variable to disk\ - %store -d - Remove the variable and its value from storage\ - %store -z - Remove all variables from storage\ - %store -r - Refresh all variables from store (delete current vals)\ - %store foo >a.txt - Store value of foo to new file a.txt\ - %store foo >>a.txt - Append value of foo to file a.txt\ - - It should be noted that if you change the value of a variable, you - need to %store it again if you want to persist the new value. - - Note also that the variables will need to be pickleable; most basic - python types can be safely %stored. - - Also aliases can be %store'd across sessions. - -**%sx**:: - - Shell execute - run a shell command and capture its output. - - %sx command - - IPython will run the given command using commands.getoutput(), and - return the result formatted as a list (split on '\n'). Since the - output is _returned_, it will be stored in ipython's regular output - cache Out[N] and in the '_N' automatic variables. - - Notes: - - 1) If an input line begins with '!!', then %sx is automatically - invoked. That is, while: - !ls - causes ipython to simply issue system('ls'), typing - !!ls - is a shorthand equivalent to: - %sx ls - - 2) %sx differs from %sc in that %sx automatically splits into a list, - like '%sc -l'. The reason for this is to make it as easy as possible - to process line-oriented shell output via further python commands. - %sc is meant to provide much finer control, but requires more - typing. - - 3) Just like %sc -l, this is a list with special attributes: - - .l (or .list) : value as list. - .n (or .nlstr): value as newline-separated string. - .s (or .spstr): value as whitespace-separated string. - - This is very useful when trying to use such lists as arguments to - system commands. - -**%system_verbose**:: - - Set verbose printing of system calls. - - If called without an argument, act as a toggle - -**%time**:: - - Time execution of a Python statement or expression. - - The CPU and wall clock times are printed, and the value of the - expression (if any) is returned. Note that under Win32, system time - is always reported as 0, since it can not be measured. - - This function provides very basic timing functionality. In Python - 2.3, the timeit module offers more control and sophistication, so this - could be rewritten to use it (patches welcome). - - Some examples: - - In [1]: time 2**128 - CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s - Wall time: 0.00 - Out[1]: 340282366920938463463374607431768211456L - - In [2]: n = 1000000 - - In [3]: time sum(range(n)) - CPU times: user 1.20 s, sys: 0.05 s, total: 1.25 s - Wall time: 1.37 - Out[3]: 499999500000L - - In [4]: time print 'hello world' - hello world - CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s - Wall time: 0.00 - - Note that the time needed by Python to compile the given expression - will be reported if it is more than 0.1s. In this example, the - actual exponentiation is done by Python at compilation time, so while - the expression can take a noticeable amount of time to compute, that - time is purely due to the compilation: - - In [5]: time 3**9999; - CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s - Wall time: 0.00 s - - In [6]: time 3**999999; - CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s - Wall time: 0.00 s - Compiler : 0.78 s - -**%timeit**:: - - Time execution of a Python statement or expression - - Usage:\ - %timeit [-n -r [-t|-c]] statement - - Time execution of a Python statement or expression using the timeit - module. - - Options: - -n: execute the given statement times in a loop. If this value - is not given, a fitting value is chosen. - - -r: repeat the loop iteration times and take the best result. - Default: 3 - - -t: use time.time to measure the time, which is the default on Unix. - This function measures wall time. - - -c: use time.clock to measure the time, which is the default on - Windows and measures wall time. On Unix, resource.getrusage is used - instead and returns the CPU user time. - - -p

: use a precision of

digits to display the timing result. - Default: 3 - - - Examples:\ - In [1]: %timeit pass - 10000000 loops, best of 3: 53.3 ns per loop - - In [2]: u = None - - In [3]: %timeit u is None - 10000000 loops, best of 3: 184 ns per loop - - In [4]: %timeit -r 4 u == None - 1000000 loops, best of 4: 242 ns per loop - - In [5]: import time - - In [6]: %timeit -n1 time.sleep(2) - 1 loops, best of 3: 2 s per loop - - - The times reported by %timeit will be slightly higher than those - reported by the timeit.py script when variables are accessed. This is - due to the fact that %timeit executes the statement in the namespace - of the shell, compared with timeit.py, which uses a single setup - statement to import function or create variables. Generally, the bias - does not matter as long as results from timeit.py are not mixed with - those from %timeit. - -**%unalias**:: - - Remove an alias - -**%upgrade**:: - - Upgrade your IPython installation - - This will copy the config files that don't yet exist in your - ipython dir from the system config dir. Use this after upgrading - IPython if you don't wish to delete your .ipython dir. - - Call with -nolegacy to get rid of ipythonrc* files (recommended for - new users) - -**%which**:: - - %which => search PATH for files matching cmd. Also scans aliases. - - Traverses PATH and prints all files (not just executables!) that match the - pattern on command line. Probably more useful in finding stuff - interactively than 'which', which only prints the first matching item. - - Also discovers and expands aliases, so you'll see what will be executed - when you call an alias. - - Example: - - [~]|62> %which d - d -> ls -F --color=auto - == c:\cygwin\bin\ls.exe - c:\cygwin\bin\d.exe - - [~]|64> %which diff* - diff3 -> diff3 - == c:\cygwin\bin\diff3.exe - diff -> diff - == c:\cygwin\bin\diff.exe - c:\cygwin\bin\diff.exe - c:\cygwin\bin\diff3.exe - -**%who**:: - - Print all interactive variables, with some minimal formatting. - - If any arguments are given, only variables whose type matches one of - these are printed. For example: - - %who function str - - will only list functions and strings, excluding all other types of - variables. To find the proper type names, simply use type(var) at a - command line to see how python prints type names. For example: - - In [1]: type('hello')\ - Out[1]: - - indicates that the type name for strings is 'str'. - - %who always excludes executed names loaded through your configuration - file and things which are internal to IPython. - - This is deliberate, as typically you may load many modules and the - purpose of %who is to show you only what you've manually defined. - -**%who_ls**:: - - Return a sorted list of all interactive variables. - - If arguments are given, only variables of types matching these - arguments are returned. - -**%whos**:: - - Like %who, but gives some extra information about each variable. - - The same type filtering of %who can be applied here. - - For all variables, the type is printed. Additionally it prints: - - - For {},[],(): their length. - - - For numpy and Numeric arrays, a summary with shape, number of - elements, typecode and size in memory. - - - Everything else: a string representation, snipping their middle if - too long. - -**%xmode**:: - - Switch modes for the exception handlers. - - Valid modes: Plain, Context and Verbose. - - If called without arguments, acts as a toggle. - -.. magic_end Access to the standard Python help ---------------------------------- diff --git a/docs/source/overview.txt b/docs/source/overview.txt index 9ee38cb..184a0ea 100644 --- a/docs/source/overview.txt +++ b/docs/source/overview.txt @@ -91,10 +91,10 @@ Main features of the interactive shell IPython has an internal job manager called jobs, and a convenience backgrounding magic function called :samp:`%bg`. -* The ability to expand python variables when calling the system - shell. In a shell command, any python variable prefixed with :samp:`$` is - expanded. A double :samp:`$$` allows passing a literal :samp:`$` to the shell (for - access to shell and environment variables like :envvar:`PATH`). +* The ability to expand python variables when calling the system shell. In a + shell command, any python variable prefixed with :samp:`$` is expanded. A + double :samp:`$$` allows passing a literal :samp:`$` to the shell (for access + to shell and environment variables like :envvar:`PATH`). * Filesystem navigation, via a magic :samp:`%cd` command, along with a persistent bookmark system (using :samp:`%bookmark`) for fast access to @@ -150,17 +150,16 @@ Main features of the interactive shell about the local namespaces (very useful in debugging and data analysis situations). -* Easy debugger access. You can set IPython to call up an enhanced - version of the Python debugger (pdb) every time there is an - uncaught exception. This drops you inside the code which triggered - the exception with all the data live and it is possible to - navigate the stack to rapidly isolate the source of a bug. The - :samp:`%run` magic command (with the :samp:`-d` option) can run any script under - pdb's control, automatically setting initial breakpoints for you. - This version of pdb has IPython-specific improvements, including - tab-completion and traceback coloring support. For even easier - debugger access, try :samp:`%debug` after seeing an exception. winpdb is - also supported, see ipy_winpdb extension. +* Easy debugger access. You can set IPython to call up an enhanced version of + the Python debugger (pdb) every time there is an uncaught exception. This + drops you inside the code which triggered the exception with all the data + live and it is possible to navigate the stack to rapidly isolate the source + of a bug. The :samp:`%run` magic command (with the :samp:`-d` option) can run + any script under pdb's control, automatically setting initial breakpoints for + you. This version of pdb has IPython-specific improvements, including + tab-completion and traceback coloring support. For even easier debugger + access, try :samp:`%debug` after seeing an exception. winpdb is also + supported, see ipy_winpdb extension. * Profiler support. You can run single statements (similar to :samp:`profile.run()`) or complete programs under the profiler's control. @@ -176,10 +175,11 @@ Main features of the interactive shell Interactive parallel computing ============================== -Increasingly, parallel computer hardware, such as multicore CPUs, clusters and supercomputers, is becoming ubiquitous. Over the last 3 years, we have developed an -architecture within IPython that allows such hardware to be used quickly and easily -from Python. Moreover, this architecture is designed to support interactive and -collaborative parallel computing. +Increasingly, parallel computer hardware, such as multicore CPUs, clusters and +supercomputers, is becoming ubiquitous. Over the last 3 years, we have +developed an architecture within IPython that allows such hardware to be used +quickly and easily from Python. Moreover, this architecture is designed to +support interactive and collaborative parallel computing. The main features of this system are: @@ -204,16 +204,16 @@ The main features of this system are: * Capabilities based security model with full encryption of network connections. -* Share live parallel jobs with other users securely. We call this collaborative - parallel computing. +* Share live parallel jobs with other users securely. We call this + collaborative parallel computing. * Dynamically load balanced task farming system. * Robust error handling. Python exceptions raised in parallel execution are gathered and presented to the top-level code. -For more information, see our :ref:`overview ` of using IPython for -parallel computing. +For more information, see our :ref:`overview ` of using IPython +for parallel computing. Portability and Python requirements ----------------------------------- @@ -225,8 +225,7 @@ work with some minor changes. IPython is known to work on the following operating systems: * Linux - * AIX - * Most other Unix-like OSs (Solaris, BSD, etc.) + * Most other Unix-like OSs (AIX, Solaris, BSD, etc.) * Mac OS X * Windows (CygWin, XP, Vista, etc.) diff --git a/docs/source/parallel/index.txt b/docs/source/parallel/index.txt index 0b4e378..3ea9e0f 100644 --- a/docs/source/parallel/index.txt +++ b/docs/source/parallel/index.txt @@ -13,4 +13,4 @@ Using IPython for parallel computing parallel_task.txt parallel_mpi.txt parallel_security.txt - + visionhpc.txt diff --git a/docs/source/parallel/parallel_process.txt b/docs/source/parallel/parallel_process.txt index 660d06d..d35ffc9 100644 --- a/docs/source/parallel/parallel_process.txt +++ b/docs/source/parallel/parallel_process.txt @@ -53,6 +53,8 @@ The :command:`ipcluster` command provides a simple way of starting a controller 2. When engines are started using the :command:`mpirun` command that comes with most MPI [MPI]_ implementations 3. When engines are started using the PBS [PBS]_ batch system. +4. When the controller is started on localhost and the engines are started on + remote nodes using :command:`ssh`. .. note:: @@ -66,7 +68,8 @@ The :command:`ipcluster` command provides a simple way of starting a controller :file:`~/.ipython/security` directory live on a shared filesystem that is seen by both the controller and engines. If you don't have a shared file system you will need to use :command:`ipcontroller` and - :command:`ipengine` directly. + :command:`ipengine` directly. This constraint can be relaxed if you are + using the :command:`ssh` method to start the cluster. Underneath the hood, :command:`ipcluster` just uses :command:`ipcontroller` and :command:`ipengine` to perform the steps described above. @@ -159,6 +162,75 @@ Additional command line options for this mode can be found by doing:: $ ipcluster pbs -h +Using :command:`ipcluster` in SSH mode +-------------------------------------- + +The SSH mode uses :command:`ssh` to execute :command:`ipengine` on remote +nodes and the :command:`ipcontroller` on localhost. + +When using using this mode it highly recommended that you have set up SSH keys and are using ssh-agent [SSH]_ for password-less logins. + +To use this mode you need a python file describing the cluster, here is an example of such a "clusterfile": + +.. sourcecode:: python + + send_furl = True + engines = { 'host1.example.com' : 2, + 'host2.example.com' : 5, + 'host3.example.com' : 1, + 'host4.example.com' : 8 } + +Since this is a regular python file usual python syntax applies. Things to note: + +* The `engines` dict, where the keys is the host we want to run engines on and + the value is the number of engines to run on that host. +* send_furl can either be `True` or `False`, if `True` it will copy over the + furl needed for :command:`ipengine` to each host. + +The ``--clusterfile`` command line option lets you specify the file to use for +the cluster definition. Once you have your cluster file and you can +:command:`ssh` into the remote hosts with out an password you are ready to +start your cluster like so: + +.. sourcecode:: bash + + $ ipcluster ssh --clusterfile /path/to/my/clusterfile.py + + +Two helper shell scripts are used to start and stop :command:`ipengine` on remote hosts: + +* sshx.sh +* engine_killer.sh + +Defaults for both of these are contained in the source code for :command:`ipcluster`. The default scripts are written to a local file in a tmep directory and then copied to a temp directory on the remote host and executed from there. On most Unix, Linux and OS X systems this is /tmp. + +The default sshx.sh is the following: + +.. sourcecode:: bash + + #!/bin/sh + "$@" &> /dev/null & + echo $! + +If you want to use a custom sshx.sh script you need to use the ``--sshx`` +option and specify the file to use. Using a custom sshx.sh file could be +helpful when you need to setup the environment on the remote host before +executing :command:`ipengine`. + +For a detailed options list: + +.. sourcecode:: bash + + $ ipcluster ssh -h + +Current limitations of the SSH mode of :command:`ipcluster` are: + +* Untested on Windows. Would require a working :command:`ssh` on Windows. + Also, we are using shell scripts to setup and execute commands on remote + hosts. +* :command:`ipcontroller` is started on localhost, with no option to start it + on a remote node. + Using the :command:`ipcontroller` and :command:`ipengine` commands ================================================================== @@ -249,3 +321,4 @@ the log files to us will often help us to debug any problems. .. [PBS] Portable Batch System. http://www.openpbs.org/ +.. [SSH] SSH-Agent http://en.wikipedia.org/wiki/Ssh-agent diff --git a/docs/source/parallel/vision_beam_pattern.png b/docs/source/parallel/vision_beam_pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..e00a01a37fc1cb9182c3f16f29434150c54a75c3 GIT binary patch literal 151053 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 Fe*x%3>r(&# literal 0 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 Qe+S4tRF*E1eD3@I0aS)fzW@LL literal 0 Hc$@ + + ---------------------------------------------------------------------- + Ran 419 tests in 33.971s + + FAILED (SKIP=4, errors=1) + +In three more runs of the test suite I was able to reproduce this error +sometimes but not always; for now I think we can move on but we need to +investigate further. Especially if we start seeing problems in real use (the +test suite stresses the networking layer in particular ways that aren't +necessarily typical of normal use). + +Next, I started an 8-engine cluster via:: + + perez@opt-login01[~]> ipcluster -n 8 + Starting controller: Controller PID: 30845 + ^X Starting engines: Engines PIDs: [30846, 30847, 30848, 30849, + 30850, 30851, 30852, 30853] + Log files: /home/perez/.ipython/log/ipcluster-30845-* + + Your cluster is up and running. + + [... etc] + +and in a separate ipython session checked that the cluster is running and I can +access all the engines:: + + In [1]: from IPython.kernel import client + + In [2]: mec = client.MultiEngineClient() + + In [3]: mec.get_ids() + Out[3]: [0, 1, 2, 3, 4, 5, 6, 7] + +and run trivial code in them (after importing the ``random`` module in all +engines):: + + In [11]: mec.execute("x=random.randint(0,10)") + Out[11]: + + [0] In [3]: x=random.randint(0,10) + [1] In [3]: x=random.randint(0,10) + [2] In [3]: x=random.randint(0,10) + [3] In [3]: x=random.randint(0,10) + [4] In [3]: x=random.randint(0,10) + [5] In [3]: x=random.randint(0,10) + [6] In [3]: x=random.randint(0,10) + [7] In [3]: x=random.randint(0,10) + + In [12]: mec.pull('x') + Out[12]: [10, 0, 8, 10, 2, 9, 10, 7] + + +We'll continue conducting more complex tests later, including instaling Vision +locally and running the beam demo. + + +Michel's original instructions +============================== + +I got a Vision network that reproduces the beam pattern demo working: + +.. image:: vision_beam_pattern.png + :width: 400 + :target: vision_beam_pattern.png + :align: center + + +I created a package called beamPattern that provides the function run() in its +__init__.py file. + +A subpackage beamPattern/VisionInterface provides Vision nodes for: + +- computing Elevation and Azimuth from a 3D vector + +- Reading .mat files + +- taking the results gathered from the engines and creating the output that a + single engine would have had produced + +The Mec node connect to a controller. In my network it was local but an furl +can be specified to connect to a remote controller. + +The PRun Func node is from the IPython library of nodes. the import statement +is used to get the run function from the beamPattern package and bu puting +"run" in the function entry of this node we push this function to the engines. +In addition to the node will create input ports for all arguments of the +function being pushed (i.e. the run function) + +The second input port on PRun Fun take an integer specifying the rank of the +argument we want to scatter. All other arguments will be pushed to the engines. + +The ElevAzim node has a 3D vector widget and computes the El And Az values +which are passed into the PRun Fun node through the ports created +automatically. The Mat node allows to select the .mat file, reads it and passed +the data to the locdata port created automatically on PRun Func + +The calculation is executed in parallel, and the results are gathered and +output. Instead of having a list of 3 vectors we nd up with a list of n*3 +vectors where n is the number of engines. unpackDectorResults will turn it into +a list of 3. We then plot x, y, and 10*log10(z) + + +Installation +------------ + +- inflate beamPattern into the site-packages directory for the MGL tools. + +- place the appended IPythonNodes.py and StandardNodes.py into the Vision + package of the MGL tools. + +- place the appended items.py in the NetworkEditor package of the MGL tools + +- run vision for the network beamPat5_net.py:: + + vision beamPat5_net.py + +Once the network is running, you can: + +- double click on the MEC node and either use an emptty string for the furl to + connect to a local engine or cut and paste the furl to the engine you want to + use + +- click on the yellow lighting bold to run the network. + +- Try modifying the MAT file or change the Vector used top compute elevation + and Azimut. + + +Fernando's notes +================ + +- I had to install IPython and all its dependencies for the python used by the + MGL tools. + +- Then I had to install scipy 0.6.0 for it, since the nodes needed Scipy. To + do this I sourced the mglenv.sh script and then ran:: + + python setup.py install --prefix=~/usr/opt/mgl + + +Using PBS +========= + +The following PBS script can be used to start the engines:: + + #PBS -N bgranger-ipython + #PBS -j oe + #PBS -l walltime=00:10:00 + #PBS -l nodes=4:ppn=4 + + cd $PBS_O_WORKDIR + export PATH=$HOME/usr/local/bin + export PYTHONPATH=$HOME/usr/local/lib/python2.4/site-packages + /usr/local/bin/mpiexec -n 16 ipengine + + +If this file is called ``ipython_pbs.sh``, then the in one login windows +(i.e. on the head-node -- ``opt-login01.osc.edu``), run ``ipcontroller``. In +another login window on the same node, run the above script:: + + qsub ipython_pbs.sh + +If you look at the first window, you will see some diagnostic output +from ipcontroller. You can then get the furl from your own +``~/.ipython/security`` directory and then connect to it remotely. + +You might need to set up an SSH tunnel, however; if this doesn't work as +advertised:: + + ssh -L 10115:localhost:10105 bic + + +Links to other resources +======================== + +- http://www.osc.edu/~unpingco/glenn_NewLynx2_Demo.avi + diff --git a/docs/sphinxext/apigen.py b/docs/sphinxext/apigen.py new file mode 100644 index 0000000..afce9da --- /dev/null +++ b/docs/sphinxext/apigen.py @@ -0,0 +1,426 @@ +"""Attempt to generate templates for module reference with Sphinx + +XXX - we exclude extension modules + +To include extension modules, first identify them as valid in the +``_uri2path`` method, then handle them in the ``_parse_module`` script. + +We get functions and classes by parsing the text of .py files. +Alternatively we could import the modules for discovery, and we'd have +to do that for extension modules. This would involve changing the +``_parse_module`` method to work via import and introspection, and +might involve changing ``discover_modules`` (which determines which +files are modules, and therefore which module URIs will be passed to +``_parse_module``). + +NOTE: this is a modified version of a script originally shipped with the +PyMVPA project, which we've adapted for NIPY use. PyMVPA is an MIT-licensed +project.""" + +# Stdlib imports +import os +import re + +# Functions and classes +class ApiDocWriter(object): + ''' Class for automatic detection and parsing of API docs + to Sphinx-parsable reST format''' + + # only separating first two levels + rst_section_levels = ['*', '=', '-', '~', '^'] + + def __init__(self, + package_name, + rst_extension='.rst', + package_skip_patterns=None, + module_skip_patterns=None, + ): + ''' Initialize package for parsing + + Parameters + ---------- + package_name : string + Name of the top-level package. *package_name* must be the + name of an importable package + rst_extension : string, optional + Extension for reST files, default '.rst' + package_skip_patterns : None or sequence of {strings, regexps} + Sequence of strings giving URIs of packages to be excluded + Operates on the package path, starting at (including) the + first dot in the package path, after *package_name* - so, + if *package_name* is ``sphinx``, then ``sphinx.util`` will + result in ``.util`` being passed for earching by these + regexps. If is None, gives default. Default is: + ['\.tests$'] + module_skip_patterns : None or sequence + Sequence of strings giving URIs of modules to be excluded + Operates on the module name including preceding URI path, + back to the first dot after *package_name*. For example + ``sphinx.util.console`` results in the string to search of + ``.util.console`` + If is None, gives default. Default is: + ['\.setup$', '\._'] + ''' + if package_skip_patterns is None: + package_skip_patterns = ['\\.tests$'] + if module_skip_patterns is None: + module_skip_patterns = ['\\.setup$', '\\._'] + self.package_name = package_name + self.rst_extension = rst_extension + self.package_skip_patterns = package_skip_patterns + self.module_skip_patterns = module_skip_patterns + + def get_package_name(self): + return self._package_name + + def set_package_name(self, package_name): + ''' Set package_name + + >>> docwriter = ApiDocWriter('sphinx') + >>> import sphinx + >>> docwriter.root_path == sphinx.__path__[0] + True + >>> docwriter.package_name = 'docutils' + >>> import docutils + >>> docwriter.root_path == docutils.__path__[0] + True + ''' + # It's also possible to imagine caching the module parsing here + self._package_name = package_name + self.root_module = __import__(package_name) + self.root_path = self.root_module.__path__[0] + self.written_modules = None + + package_name = property(get_package_name, set_package_name, None, + 'get/set package_name') + + def _get_object_name(self, line): + ''' Get second token in line + >>> docwriter = ApiDocWriter('sphinx') + >>> docwriter._get_object_name(" def func(): ") + 'func' + >>> docwriter._get_object_name(" class Klass(object): ") + 'Klass' + >>> docwriter._get_object_name(" class Klass: ") + 'Klass' + ''' + name = line.split()[1].split('(')[0].strip() + # in case we have classes which are not derived from object + # ie. old style classes + return name.rstrip(':') + + def _uri2path(self, uri): + ''' Convert uri to absolute filepath + + Parameters + ---------- + uri : string + URI of python module to return path for + + Returns + ------- + path : None or string + Returns None if there is no valid path for this URI + Otherwise returns absolute file system path for URI + + Examples + -------- + >>> docwriter = ApiDocWriter('sphinx') + >>> import sphinx + >>> modpath = sphinx.__path__[0] + >>> res = docwriter._uri2path('sphinx.builder') + >>> res == os.path.join(modpath, 'builder.py') + True + >>> res = docwriter._uri2path('sphinx') + >>> res == os.path.join(modpath, '__init__.py') + True + >>> docwriter._uri2path('sphinx.does_not_exist') + + ''' + if uri == self.package_name: + return os.path.join(self.root_path, '__init__.py') + path = uri.replace('.', os.path.sep) + path = path.replace(self.package_name + os.path.sep, '') + path = os.path.join(self.root_path, path) + # XXX maybe check for extensions as well? + if os.path.exists(path + '.py'): # file + path += '.py' + elif os.path.exists(os.path.join(path, '__init__.py')): + path = os.path.join(path, '__init__.py') + else: + return None + return path + + def _path2uri(self, dirpath): + ''' Convert directory path to uri ''' + relpath = dirpath.replace(self.root_path, self.package_name) + if relpath.startswith(os.path.sep): + relpath = relpath[1:] + return relpath.replace(os.path.sep, '.') + + def _parse_module(self, uri): + ''' Parse module defined in *uri* ''' + filename = self._uri2path(uri) + if filename is None: + # nothing that we could handle here. + return ([],[]) + f = open(filename, 'rt') + functions, classes = self._parse_lines(f) + f.close() + return functions, classes + + def _parse_lines(self, linesource): + ''' Parse lines of text for functions and classes ''' + functions = [] + classes = [] + for line in linesource: + if line.startswith('def ') and line.count('('): + # exclude private stuff + name = self._get_object_name(line) + if not name.startswith('_'): + functions.append(name) + elif line.startswith('class '): + # exclude private stuff + name = self._get_object_name(line) + if not name.startswith('_'): + classes.append(name) + else: + pass + functions.sort() + classes.sort() + return functions, classes + + def generate_api_doc(self, uri): + '''Make autodoc documentation template string for a module + + Parameters + ---------- + uri : string + python location of module - e.g 'sphinx.builder' + + Returns + ------- + S : string + Contents of API doc + ''' + # get the names of all classes and functions + functions, classes = self._parse_module(uri) + if not len(functions) and not len(classes): + print 'WARNING: Empty -',uri # dbg + return '' + + # Make a shorter version of the uri that omits the package name for + # titles + uri_short = re.sub(r'^%s\.' % self.package_name,'',uri) + + ad = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n' + + chap_title = uri_short + ad += (chap_title+'\n'+ self.rst_section_levels[1] * len(chap_title) + + '\n\n') + + # Set the chapter title to read 'module' for all modules except for the + # main packages + if '.' in uri: + title = 'Module: :mod:`' + uri_short + '`' + else: + title = ':mod:`' + uri_short + '`' + ad += title + '\n' + self.rst_section_levels[2] * len(title) + + if len(classes): + ad += '\nInheritance diagram for ``%s``:\n\n' % uri + ad += '.. inheritance-diagram:: %s \n' % uri + ad += ' :parts: 3\n' + + ad += '\n.. automodule:: ' + uri + '\n' + ad += '\n.. currentmodule:: ' + uri + '\n' + multi_class = len(classes) > 1 + multi_fx = len(functions) > 1 + if multi_class: + ad += '\n' + 'Classes' + '\n' + \ + self.rst_section_levels[2] * 7 + '\n' + elif len(classes) and multi_fx: + ad += '\n' + 'Class' + '\n' + \ + self.rst_section_levels[2] * 5 + '\n' + for c in classes: + ad += '\n:class:`' + c + '`\n' \ + + self.rst_section_levels[multi_class + 2 ] * \ + (len(c)+9) + '\n\n' + ad += '\n.. autoclass:: ' + c + '\n' + # must NOT exclude from index to keep cross-refs working + ad += ' :members:\n' \ + ' :undoc-members:\n' \ + ' :show-inheritance:\n' \ + '\n' \ + ' .. automethod:: __init__\n' + if multi_fx: + ad += '\n' + 'Functions' + '\n' + \ + self.rst_section_levels[2] * 9 + '\n\n' + elif len(functions) and multi_class: + ad += '\n' + 'Function' + '\n' + \ + self.rst_section_levels[2] * 8 + '\n\n' + for f in functions: + # must NOT exclude from index to keep cross-refs working + ad += '\n.. autofunction:: ' + uri + '.' + f + '\n\n' + return ad + + def _survives_exclude(self, matchstr, match_type): + ''' Returns True if *matchstr* does not match patterns + + ``self.package_name`` removed from front of string if present + + Examples + -------- + >>> dw = ApiDocWriter('sphinx') + >>> dw._survives_exclude('sphinx.okpkg', 'package') + True + >>> dw.package_skip_patterns.append('^\\.badpkg$') + >>> dw._survives_exclude('sphinx.badpkg', 'package') + False + >>> dw._survives_exclude('sphinx.badpkg', 'module') + True + >>> dw._survives_exclude('sphinx.badmod', 'module') + True + >>> dw.module_skip_patterns.append('^\\.badmod$') + >>> dw._survives_exclude('sphinx.badmod', 'module') + False + ''' + if match_type == 'module': + patterns = self.module_skip_patterns + elif match_type == 'package': + patterns = self.package_skip_patterns + else: + raise ValueError('Cannot interpret match type "%s"' + % match_type) + # Match to URI without package name + L = len(self.package_name) + if matchstr[:L] == self.package_name: + matchstr = matchstr[L:] + for pat in patterns: + try: + pat.search + except AttributeError: + pat = re.compile(pat) + if pat.search(matchstr): + return False + return True + + def discover_modules(self): + ''' Return module sequence discovered from ``self.package_name`` + + + Parameters + ---------- + None + + Returns + ------- + mods : sequence + Sequence of module names within ``self.package_name`` + + Examples + -------- + >>> dw = ApiDocWriter('sphinx') + >>> mods = dw.discover_modules() + >>> 'sphinx.util' in mods + True + >>> dw.package_skip_patterns.append('\.util$') + >>> 'sphinx.util' in dw.discover_modules() + False + >>> + ''' + modules = [self.package_name] + # raw directory parsing + for dirpath, dirnames, filenames in os.walk(self.root_path): + # Check directory names for packages + root_uri = self._path2uri(os.path.join(self.root_path, + dirpath)) + for dirname in dirnames[:]: # copy list - we modify inplace + package_uri = '.'.join((root_uri, dirname)) + if (self._uri2path(package_uri) and + self._survives_exclude(package_uri, 'package')): + modules.append(package_uri) + else: + dirnames.remove(dirname) + # Check filenames for modules + for filename in filenames: + module_name = filename[:-3] + module_uri = '.'.join((root_uri, module_name)) + if (self._uri2path(module_uri) and + self._survives_exclude(module_uri, 'module')): + modules.append(module_uri) + return sorted(modules) + + def write_modules_api(self, modules,outdir): + # write the list + written_modules = [] + for m in modules: + api_str = self.generate_api_doc(m) + if not api_str: + continue + # write out to file + outfile = os.path.join(outdir, + m + self.rst_extension) + fileobj = open(outfile, 'wt') + fileobj.write(api_str) + fileobj.close() + written_modules.append(m) + self.written_modules = written_modules + + def write_api_docs(self, outdir): + """Generate API reST files. + + Parameters + ---------- + outdir : string + Directory name in which to store files + We create automatic filenames for each module + + Returns + ------- + None + + Notes + ----- + Sets self.written_modules to list of written modules + """ + if not os.path.exists(outdir): + os.mkdir(outdir) + # compose list of modules + modules = self.discover_modules() + self.write_modules_api(modules,outdir) + + def write_index(self, outdir, froot='gen', relative_to=None): + """Make a reST API index file from written files + + Parameters + ---------- + path : string + Filename to write index to + outdir : string + Directory to which to write generated index file + froot : string, optional + root (filename without extension) of filename to write to + Defaults to 'gen'. We add ``self.rst_extension``. + relative_to : string + path to which written filenames are relative. This + component of the written file path will be removed from + outdir, in the generated index. Default is None, meaning, + leave path as it is. + """ + if self.written_modules is None: + raise ValueError('No modules written') + # Get full filename path + path = os.path.join(outdir, froot+self.rst_extension) + # Path written into index is relative to rootpath + if relative_to is not None: + relpath = outdir.replace(relative_to + os.path.sep, '') + else: + relpath = outdir + idx = open(path,'wt') + w = idx.write + w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n') + w('.. toctree::\n\n') + for f in self.written_modules: + w(' %s\n' % os.path.join(relpath,f)) + idx.close() diff --git a/docs/sphinxext/docscrape.py b/docs/sphinxext/docscrape.py new file mode 100644 index 0000000..f374b3d --- /dev/null +++ b/docs/sphinxext/docscrape.py @@ -0,0 +1,497 @@ +"""Extract reference documentation from the NumPy source tree. + +""" + +import inspect +import textwrap +import re +import pydoc +from StringIO import StringIO +from warnings import warn +4 +class Reader(object): + """A line-based string reader. + + """ + def __init__(self, data): + """ + Parameters + ---------- + data : str + String with lines separated by '\n'. + + """ + if isinstance(data,list): + self._str = data + else: + self._str = data.split('\n') # store string as list of lines + + self.reset() + + def __getitem__(self, n): + return self._str[n] + + def reset(self): + self._l = 0 # current line nr + + def read(self): + if not self.eof(): + out = self[self._l] + self._l += 1 + return out + else: + return '' + + def seek_next_non_empty_line(self): + for l in self[self._l:]: + if l.strip(): + break + else: + self._l += 1 + + def eof(self): + return self._l >= len(self._str) + + def read_to_condition(self, condition_func): + start = self._l + for line in self[start:]: + if condition_func(line): + return self[start:self._l] + self._l += 1 + if self.eof(): + return self[start:self._l+1] + return [] + + def read_to_next_empty_line(self): + self.seek_next_non_empty_line() + def is_empty(line): + return not line.strip() + return self.read_to_condition(is_empty) + + def read_to_next_unindented_line(self): + def is_unindented(line): + return (line.strip() and (len(line.lstrip()) == len(line))) + return self.read_to_condition(is_unindented) + + def peek(self,n=0): + if self._l + n < len(self._str): + return self[self._l + n] + else: + return '' + + def is_empty(self): + return not ''.join(self._str).strip() + + +class NumpyDocString(object): + def __init__(self,docstring): + docstring = textwrap.dedent(docstring).split('\n') + + self._doc = Reader(docstring) + self._parsed_data = { + 'Signature': '', + 'Summary': [''], + 'Extended Summary': [], + 'Parameters': [], + 'Returns': [], + 'Raises': [], + 'Warns': [], + 'Other Parameters': [], + 'Attributes': [], + 'Methods': [], + 'See Also': [], + 'Notes': [], + 'Warnings': [], + 'References': '', + 'Examples': '', + 'index': {} + } + + self._parse() + + def __getitem__(self,key): + return self._parsed_data[key] + + def __setitem__(self,key,val): + if not self._parsed_data.has_key(key): + warn("Unknown section %s" % key) + else: + self._parsed_data[key] = val + + def _is_at_section(self): + self._doc.seek_next_non_empty_line() + + if self._doc.eof(): + return False + + l1 = self._doc.peek().strip() # e.g. Parameters + + if l1.startswith('.. index::'): + return True + + l2 = self._doc.peek(1).strip() # ---------- or ========== + return l2.startswith('-'*len(l1)) or l2.startswith('='*len(l1)) + + def _strip(self,doc): + i = 0 + j = 0 + for i,line in enumerate(doc): + if line.strip(): break + + for j,line in enumerate(doc[::-1]): + if line.strip(): break + + return doc[i:len(doc)-j] + + def _read_to_next_section(self): + section = self._doc.read_to_next_empty_line() + + while not self._is_at_section() and not self._doc.eof(): + if not self._doc.peek(-1).strip(): # previous line was empty + section += [''] + + section += self._doc.read_to_next_empty_line() + + return section + + def _read_sections(self): + while not self._doc.eof(): + data = self._read_to_next_section() + name = data[0].strip() + + if name.startswith('..'): # index section + yield name, data[1:] + elif len(data) < 2: + yield StopIteration + else: + yield name, self._strip(data[2:]) + + def _parse_param_list(self,content): + r = Reader(content) + params = [] + while not r.eof(): + header = r.read().strip() + if ' : ' in header: + arg_name, arg_type = header.split(' : ')[:2] + else: + arg_name, arg_type = header, '' + + desc = r.read_to_next_unindented_line() + desc = dedent_lines(desc) + + params.append((arg_name,arg_type,desc)) + + return params + + + _name_rgx = re.compile(r"^\s*(:(?P\w+):`(?P[a-zA-Z0-9_.-]+)`|" + r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) + def _parse_see_also(self, content): + """ + func_name : Descriptive text + continued text + another_func_name : Descriptive text + func_name1, func_name2, :meth:`func_name`, func_name3 + + """ + items = [] + + def parse_item_name(text): + """Match ':role:`name`' or 'name'""" + m = self._name_rgx.match(text) + if m: + g = m.groups() + if g[1] is None: + return g[3], None + else: + return g[2], g[1] + raise ValueError("%s is not a item name" % text) + + def push_item(name, rest): + if not name: + return + name, role = parse_item_name(name) + items.append((name, list(rest), role)) + del rest[:] + + current_func = None + rest = [] + + for line in content: + if not line.strip(): continue + + m = self._name_rgx.match(line) + if m and line[m.end():].strip().startswith(':'): + push_item(current_func, rest) + current_func, line = line[:m.end()], line[m.end():] + rest = [line.split(':', 1)[1].strip()] + if not rest[0]: + rest = [] + elif not line.startswith(' '): + push_item(current_func, rest) + current_func = None + if ',' in line: + for func in line.split(','): + push_item(func, []) + elif line.strip(): + current_func = line + elif current_func is not None: + rest.append(line.strip()) + push_item(current_func, rest) + return items + + def _parse_index(self, section, content): + """ + .. index: default + :refguide: something, else, and more + + """ + def strip_each_in(lst): + return [s.strip() for s in lst] + + out = {} + section = section.split('::') + if len(section) > 1: + out['default'] = strip_each_in(section[1].split(','))[0] + for line in content: + line = line.split(':') + if len(line) > 2: + out[line[1]] = strip_each_in(line[2].split(',')) + return out + + def _parse_summary(self): + """Grab signature (if given) and summary""" + if self._is_at_section(): + return + + summary = self._doc.read_to_next_empty_line() + summary_str = " ".join([s.strip() for s in summary]).strip() + if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str): + self['Signature'] = summary_str + if not self._is_at_section(): + self['Summary'] = self._doc.read_to_next_empty_line() + else: + self['Summary'] = summary + + if not self._is_at_section(): + self['Extended Summary'] = self._read_to_next_section() + + def _parse(self): + self._doc.reset() + self._parse_summary() + + for (section,content) in self._read_sections(): + if not section.startswith('..'): + section = ' '.join([s.capitalize() for s in section.split(' ')]) + if section in ('Parameters', 'Attributes', 'Methods', + 'Returns', 'Raises', 'Warns'): + self[section] = self._parse_param_list(content) + elif section.startswith('.. index::'): + self['index'] = self._parse_index(section, content) + elif section == 'See Also': + self['See Also'] = self._parse_see_also(content) + else: + self[section] = content + + # string conversion routines + + def _str_header(self, name, symbol='-'): + return [name, len(name)*symbol] + + def _str_indent(self, doc, indent=4): + out = [] + for line in doc: + out += [' '*indent + line] + return out + + def _str_signature(self): + if self['Signature']: + return [self['Signature'].replace('*','\*')] + [''] + else: + return [''] + + def _str_summary(self): + if self['Summary']: + return self['Summary'] + [''] + else: + return [] + + def _str_extended_summary(self): + if self['Extended Summary']: + return self['Extended Summary'] + [''] + else: + return [] + + def _str_param_list(self, name): + out = [] + if self[name]: + out += self._str_header(name) + for param,param_type,desc in self[name]: + out += ['%s : %s' % (param, param_type)] + out += self._str_indent(desc) + out += [''] + return out + + def _str_section(self, name): + out = [] + if self[name]: + out += self._str_header(name) + out += self[name] + out += [''] + return out + + def _str_see_also(self, func_role): + if not self['See Also']: return [] + out = [] + out += self._str_header("See Also") + last_had_desc = True + for func, desc, role in self['See Also']: + if role: + link = ':%s:`%s`' % (role, func) + elif func_role: + link = ':%s:`%s`' % (func_role, func) + else: + link = "`%s`_" % func + if desc or last_had_desc: + out += [''] + out += [link] + else: + out[-1] += ", %s" % link + if desc: + out += self._str_indent([' '.join(desc)]) + last_had_desc = True + else: + last_had_desc = False + out += [''] + return out + + def _str_index(self): + idx = self['index'] + out = [] + out += ['.. index:: %s' % idx.get('default','')] + for section, references in idx.iteritems(): + if section == 'default': + continue + out += [' :%s: %s' % (section, ', '.join(references))] + return out + + def __str__(self, func_role=''): + out = [] + out += self._str_signature() + out += self._str_summary() + out += self._str_extended_summary() + for param_list in ('Parameters','Returns','Raises'): + out += self._str_param_list(param_list) + out += self._str_section('Warnings') + out += self._str_see_also(func_role) + for s in ('Notes','References','Examples'): + out += self._str_section(s) + out += self._str_index() + return '\n'.join(out) + + +def indent(str,indent=4): + indent_str = ' '*indent + if str is None: + return indent_str + lines = str.split('\n') + return '\n'.join(indent_str + l for l in lines) + +def dedent_lines(lines): + """Deindent a list of lines maximally""" + return textwrap.dedent("\n".join(lines)).split("\n") + +def header(text, style='-'): + return text + '\n' + style*len(text) + '\n' + + +class FunctionDoc(NumpyDocString): + def __init__(self, func, role='func', doc=None): + self._f = func + self._role = role # e.g. "func" or "meth" + if doc is None: + doc = inspect.getdoc(func) or '' + try: + NumpyDocString.__init__(self, doc) + except ValueError, e: + print '*'*78 + print "ERROR: '%s' while parsing `%s`" % (e, self._f) + print '*'*78 + #print "Docstring follows:" + #print doclines + #print '='*78 + + if not self['Signature']: + func, func_name = self.get_func() + try: + # try to read signature + argspec = inspect.getargspec(func) + argspec = inspect.formatargspec(*argspec) + argspec = argspec.replace('*','\*') + signature = '%s%s' % (func_name, argspec) + except TypeError, e: + signature = '%s()' % func_name + self['Signature'] = signature + + def get_func(self): + func_name = getattr(self._f, '__name__', self.__class__.__name__) + if inspect.isclass(self._f): + func = getattr(self._f, '__call__', self._f.__init__) + else: + func = self._f + return func, func_name + + def __str__(self): + out = '' + + func, func_name = self.get_func() + signature = self['Signature'].replace('*', '\*') + + roles = {'func': 'function', + 'meth': 'method'} + + if self._role: + if not roles.has_key(self._role): + print "Warning: invalid role %s" % self._role + out += '.. %s:: %s\n \n\n' % (roles.get(self._role,''), + func_name) + + out += super(FunctionDoc, self).__str__(func_role=self._role) + return out + + +class ClassDoc(NumpyDocString): + def __init__(self,cls,modulename='',func_doc=FunctionDoc,doc=None): + if not inspect.isclass(cls): + raise ValueError("Initialise using a class. Got %r" % cls) + self._cls = cls + + if modulename and not modulename.endswith('.'): + modulename += '.' + self._mod = modulename + self._name = cls.__name__ + self._func_doc = func_doc + + if doc is None: + doc = pydoc.getdoc(cls) + + NumpyDocString.__init__(self, doc) + + @property + def methods(self): + return [name for name,func in inspect.getmembers(self._cls) + if not name.startswith('_') and callable(func)] + + def __str__(self): + out = '' + out += super(ClassDoc, self).__str__() + out += "\n\n" + + #for m in self.methods: + # print "Parsing `%s`" % m + # out += str(self._func_doc(getattr(self._cls,m), 'meth')) + '\n\n' + # out += '.. index::\n single: %s; %s\n\n' % (self._name, m) + + return out + + diff --git a/docs/sphinxext/docscrape_sphinx.py b/docs/sphinxext/docscrape_sphinx.py new file mode 100644 index 0000000..77ed271 --- /dev/null +++ b/docs/sphinxext/docscrape_sphinx.py @@ -0,0 +1,136 @@ +import re, inspect, textwrap, pydoc +from docscrape import NumpyDocString, FunctionDoc, ClassDoc + +class SphinxDocString(NumpyDocString): + # string conversion routines + def _str_header(self, name, symbol='`'): + return ['.. rubric:: ' + name, ''] + + def _str_field_list(self, name): + return [':' + name + ':'] + + def _str_indent(self, doc, indent=4): + out = [] + for line in doc: + out += [' '*indent + line] + return out + + def _str_signature(self): + return [''] + if self['Signature']: + return ['``%s``' % self['Signature']] + [''] + else: + return [''] + + def _str_summary(self): + return self['Summary'] + [''] + + def _str_extended_summary(self): + return self['Extended Summary'] + [''] + + def _str_param_list(self, name): + out = [] + if self[name]: + out += self._str_field_list(name) + out += [''] + for param,param_type,desc in self[name]: + out += self._str_indent(['**%s** : %s' % (param.strip(), + param_type)]) + out += [''] + out += self._str_indent(desc,8) + out += [''] + return out + + def _str_section(self, name): + out = [] + if self[name]: + out += self._str_header(name) + out += [''] + content = textwrap.dedent("\n".join(self[name])).split("\n") + out += content + out += [''] + return out + + def _str_see_also(self, func_role): + out = [] + if self['See Also']: + see_also = super(SphinxDocString, self)._str_see_also(func_role) + out = ['.. seealso::', ''] + out += self._str_indent(see_also[2:]) + return out + + def _str_warnings(self): + out = [] + if self['Warnings']: + out = ['.. warning::', ''] + out += self._str_indent(self['Warnings']) + return out + + def _str_index(self): + idx = self['index'] + out = [] + if len(idx) == 0: + return out + + out += ['.. index:: %s' % idx.get('default','')] + for section, references in idx.iteritems(): + if section == 'default': + continue + elif section == 'refguide': + out += [' single: %s' % (', '.join(references))] + else: + out += [' %s: %s' % (section, ','.join(references))] + return out + + def _str_references(self): + out = [] + if self['References']: + out += self._str_header('References') + if isinstance(self['References'], str): + self['References'] = [self['References']] + out.extend(self['References']) + out += [''] + return out + + def __str__(self, indent=0, func_role="obj"): + out = [] + out += self._str_signature() + out += self._str_index() + [''] + out += self._str_summary() + out += self._str_extended_summary() + for param_list in ('Parameters', 'Attributes', 'Methods', + 'Returns','Raises'): + out += self._str_param_list(param_list) + out += self._str_warnings() + out += self._str_see_also(func_role) + out += self._str_section('Notes') + out += self._str_references() + out += self._str_section('Examples') + out = self._str_indent(out,indent) + return '\n'.join(out) + +class SphinxFunctionDoc(SphinxDocString, FunctionDoc): + pass + +class SphinxClassDoc(SphinxDocString, ClassDoc): + pass + +def get_doc_object(obj, what=None, doc=None): + if what is None: + if inspect.isclass(obj): + what = 'class' + elif inspect.ismodule(obj): + what = 'module' + elif callable(obj): + what = 'function' + else: + what = 'object' + if what == 'class': + return SphinxClassDoc(obj, '', func_doc=SphinxFunctionDoc, doc=doc) + elif what in ('function', 'method'): + return SphinxFunctionDoc(obj, '', doc=doc) + else: + if doc is None: + doc = pydoc.getdoc(obj) + return SphinxDocString(doc) + diff --git a/docs/sphinxext/inheritance_diagram.py b/docs/sphinxext/inheritance_diagram.py index 213abf8..407fc13 100644 --- a/docs/sphinxext/inheritance_diagram.py +++ b/docs/sphinxext/inheritance_diagram.py @@ -39,11 +39,20 @@ except ImportError: from md5 import md5 from docutils.nodes import Body, Element -from docutils.writers.html4css1 import HTMLTranslator -from sphinx.latexwriter import LaTeXTranslator from docutils.parsers.rst import directives from sphinx.roles import xfileref_role +def my_import(name): + """Module importer - taken from the python documentation. + + This function allows importing names with dots in them.""" + + mod = __import__(name) + components = name.split('.') + for comp in components[1:]: + mod = getattr(mod, comp) + return mod + class DotException(Exception): pass @@ -84,11 +93,15 @@ class InheritanceGraph(object): path = (path and path.rstrip('.')) if not path: path = base - if not path: - raise ValueError( - "Invalid class or module '%s' specified for inheritance diagram" % name) try: module = __import__(path, None, None, []) + # We must do an import of the fully qualified name. Otherwise if a + # subpackage 'a.b' is requested where 'import a' does NOT provide + # 'a.b' automatically, then 'a.b' will not be found below. This + # second call will force the equivalent of 'import a.b' to happen + # after the top-level import above. + my_import(fullname) + except ImportError: raise ValueError( "Could not import class or module '%s' specified for inheritance diagram" % name) @@ -277,12 +290,16 @@ class inheritance_diagram(Body, Element): """ pass -def inheritance_diagram_directive_run(class_names, options, state): +def inheritance_diagram_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, + state_machine): """ Run when the inheritance_diagram directive is first encountered. """ node = inheritance_diagram() + class_names = arguments + # Create a graph starting with the list of classes graph = InheritanceGraph(class_names) @@ -315,15 +332,12 @@ def html_output_graph(self, node): graph_hash = get_graph_hash(node) name = "inheritance%s" % graph_hash - png_path = os.path.join('_static', name + ".png") - - path = '_static' - source = self.document.attributes['source'] - count = source.split('/doc/')[-1].count('/') - for i in range(count): - if os.path.exists(path): break - path = '../'+path - path = '../'+path #specifically added for matplotlib + path = '_images' + dest_path = os.path.join(setup.app.builder.outdir, path) + if not os.path.exists(dest_path): + os.makedirs(dest_path) + png_path = os.path.join(dest_path, name + ".png") + path = setup.app.builder.imgpath # Create a mapping from fully-qualified class names to URLs. urls = {} @@ -349,11 +363,14 @@ def latex_output_graph(self, node): graph_hash = get_graph_hash(node) name = "inheritance%s" % graph_hash - pdf_path = os.path.join('_static', name + ".pdf") + dest_path = os.path.abspath(os.path.join(setup.app.builder.outdir, '_images')) + if not os.path.exists(dest_path): + os.makedirs(dest_path) + pdf_path = os.path.abspath(os.path.join(dest_path, name + ".pdf")) graph.run_dot(['-Tpdf', '-o%s' % pdf_path], name, parts, graph_options={'size': '"6.0,6.0"'}) - return '\\includegraphics{../../%s}' % pdf_path + return '\n\\includegraphics{%s}\n\n' % pdf_path def visit_inheritance_diagram(inner_func): """ @@ -377,47 +394,14 @@ def visit_inheritance_diagram(inner_func): def do_nothing(self, node): pass -options_spec = { - 'parts': directives.nonnegative_int - } - -# Deal with the old and new way of registering directives -try: - from docutils.parsers.rst import Directive -except ImportError: - from docutils.parsers.rst.directives import _directives - def inheritance_diagram_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, - state_machine): - return inheritance_diagram_directive_run(arguments, options, state) - inheritance_diagram_directive.__doc__ = __doc__ - inheritance_diagram_directive.arguments = (1, 100, 0) - inheritance_diagram_directive.options = options_spec - inheritance_diagram_directive.content = 0 - _directives['inheritance-diagram'] = inheritance_diagram_directive -else: - class inheritance_diagram_directive(Directive): - has_content = False - required_arguments = 1 - optional_arguments = 100 - final_argument_whitespace = False - option_spec = options_spec - - def run(self): - return inheritance_diagram_directive_run( - self.arguments, self.options, self.state) - inheritance_diagram_directive.__doc__ = __doc__ - - directives.register_directive('inheritance-diagram', - inheritance_diagram_directive) - def setup(app): - app.add_node(inheritance_diagram) - - HTMLTranslator.visit_inheritance_diagram = \ - visit_inheritance_diagram(html_output_graph) - HTMLTranslator.depart_inheritance_diagram = do_nothing - - LaTeXTranslator.visit_inheritance_diagram = \ - visit_inheritance_diagram(latex_output_graph) - LaTeXTranslator.depart_inheritance_diagram = do_nothing + setup.app = app + setup.confdir = app.confdir + + app.add_node( + inheritance_diagram, + latex=(visit_inheritance_diagram(latex_output_graph), do_nothing), + html=(visit_inheritance_diagram(html_output_graph), do_nothing)) + app.add_directive( + 'inheritance-diagram', inheritance_diagram_directive, + False, (1, 100, 0), parts = directives.nonnegative_int) diff --git a/docs/sphinxext/ipython_console_highlighting.py b/docs/sphinxext/ipython_console_highlighting.py index 4f0104d..00f9abd 100644 --- a/docs/sphinxext/ipython_console_highlighting.py +++ b/docs/sphinxext/ipython_console_highlighting.py @@ -1,18 +1,32 @@ +"""reST directive for syntax-highlighting ipython interactive sessions. +""" + +#----------------------------------------------------------------------------- +# Needed modules + +# Standard library +import re + +# Third party from pygments.lexer import Lexer, do_insertions -from pygments.lexers.agile import PythonConsoleLexer, PythonLexer, \ - PythonTracebackLexer +from pygments.lexers.agile import (PythonConsoleLexer, PythonLexer, + PythonTracebackLexer) from pygments.token import Comment, Generic + from sphinx import highlighting -import re + +#----------------------------------------------------------------------------- +# Global constants line_re = re.compile('.*?\n') +#----------------------------------------------------------------------------- +# Code begins - classes and functions + class IPythonConsoleLexer(Lexer): """ For IPython console output or doctests, such as: - Tracebacks are not currently supported. - .. sourcecode:: ipython In [1]: a = 'foo' @@ -24,7 +38,14 @@ class IPythonConsoleLexer(Lexer): foo In [4]: 1 / 0 + + Notes: + + - Tracebacks are not currently supported. + + - It assumes the default IPython prompts, not customized ones. """ + name = 'IPython console session' aliases = ['ipython'] mimetypes = ['text/x-ipython-console'] @@ -72,4 +93,6 @@ class IPythonConsoleLexer(Lexer): pylexer.get_tokens_unprocessed(curcode)): yield item +#----------------------------------------------------------------------------- +# Register the extension as a valid pygments lexer highlighting.lexers['ipython'] = IPythonConsoleLexer() diff --git a/docs/sphinxext/numpydoc.py b/docs/sphinxext/numpydoc.py new file mode 100644 index 0000000..ff6c44c --- /dev/null +++ b/docs/sphinxext/numpydoc.py @@ -0,0 +1,116 @@ +""" +======== +numpydoc +======== + +Sphinx extension that handles docstrings in the Numpy standard format. [1] + +It will: + +- Convert Parameters etc. sections to field lists. +- Convert See Also section to a See also entry. +- Renumber references. +- Extract the signature from the docstring, if it can't be determined otherwise. + +.. [1] http://projects.scipy.org/scipy/numpy/wiki/CodingStyleGuidelines#docstring-standard + +""" + +import os, re, pydoc +from docscrape_sphinx import get_doc_object, SphinxDocString +import inspect + +def mangle_docstrings(app, what, name, obj, options, lines, + reference_offset=[0]): + if what == 'module': + # Strip top title + title_re = re.compile(r'^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*', + re.I|re.S) + lines[:] = title_re.sub('', "\n".join(lines)).split("\n") + else: + doc = get_doc_object(obj, what, "\n".join(lines)) + lines[:] = str(doc).split("\n") + + if app.config.numpydoc_edit_link and hasattr(obj, '__name__') and \ + obj.__name__: + if hasattr(obj, '__module__'): + v = dict(full_name="%s.%s" % (obj.__module__, obj.__name__)) + else: + v = dict(full_name=obj.__name__) + lines += ['', '.. htmlonly::', ''] + lines += [' %s' % x for x in + (app.config.numpydoc_edit_link % v).split("\n")] + + # replace reference numbers so that there are no duplicates + references = [] + for l in lines: + l = l.strip() + if l.startswith('.. ['): + try: + references.append(int(l[len('.. ['):l.index(']')])) + except ValueError: + print "WARNING: invalid reference in %s docstring" % name + + # Start renaming from the biggest number, otherwise we may + # overwrite references. + references.sort() + if references: + for i, line in enumerate(lines): + for r in references: + new_r = reference_offset[0] + r + lines[i] = lines[i].replace('[%d]_' % r, + '[%d]_' % new_r) + lines[i] = lines[i].replace('.. [%d]' % r, + '.. [%d]' % new_r) + + reference_offset[0] += len(references) + +def mangle_signature(app, what, name, obj, options, sig, retann): + # Do not try to inspect classes that don't define `__init__` + if (inspect.isclass(obj) and + 'initializes x; see ' in pydoc.getdoc(obj.__init__)): + return '', '' + + if not (callable(obj) or hasattr(obj, '__argspec_is_invalid_')): return + if not hasattr(obj, '__doc__'): return + + doc = SphinxDocString(pydoc.getdoc(obj)) + if doc['Signature']: + sig = re.sub("^[^(]*", "", doc['Signature']) + return sig, '' + +def initialize(app): + try: + app.connect('autodoc-process-signature', mangle_signature) + except: + monkeypatch_sphinx_ext_autodoc() + +def setup(app, get_doc_object_=get_doc_object): + global get_doc_object + get_doc_object = get_doc_object_ + + app.connect('autodoc-process-docstring', mangle_docstrings) + app.connect('builder-inited', initialize) + app.add_config_value('numpydoc_edit_link', None, True) + +#------------------------------------------------------------------------------ +# Monkeypatch sphinx.ext.autodoc to accept argspecless autodocs (Sphinx < 0.5) +#------------------------------------------------------------------------------ + +def monkeypatch_sphinx_ext_autodoc(): + global _original_format_signature + import sphinx.ext.autodoc + + if sphinx.ext.autodoc.format_signature is our_format_signature: + return + + print "[numpydoc] Monkeypatching sphinx.ext.autodoc ..." + _original_format_signature = sphinx.ext.autodoc.format_signature + sphinx.ext.autodoc.format_signature = our_format_signature + +def our_format_signature(what, obj): + r = mangle_signature(None, what, None, obj, None, None, None) + if r is not None: + return r[0] + else: + return _original_format_signature(what, obj) diff --git a/tools/alldeps/Makefile b/tools/alldeps/Makefile new file mode 100644 index 0000000..8076acd --- /dev/null +++ b/tools/alldeps/Makefile @@ -0,0 +1,130 @@ +# Simple makefile to rapidly deploy IPython with all its dependencies. + +# Configuration section. The version numbers and paths declared here may +# change with each release. + +# IPython version +IPYTHON_VER=0.9.1 + +# Declare here version numbers of all the dependencies +PYOPENSSL_VER=0.6 +ZOPE_INTERFACE_VER=3.4.1 +TWISTED_VER=8.1.0 +FOOLSCAP_VER=0.3.1 +NOSE_VER=0.10.3 + +# Repository URLs for all packages. Make sure these are correct for each +# release, since projects may change paths! +IPYTHON_REPO=http://ipython.scipy.org/dist +PYOPENSSL_REPO=http://downloads.sourceforge.net/pyopenssl +ZOPE_INTERFACE_REPO=http://pypi.python.org/packages/source/z/zope.interface +TWISTED_REPO=http://tmrc.mit.edu/mirror/twisted/Twisted/8.1 +FOOLSCAP_REPO=http://foolscap.lothar.com/releases +NOSE_REPO=http://somethingaboutorange.com/mrl/projects/nose + +#----------------------------------------------------------------------------- +# Main code begins. There shouldn't be much to change here with each release. +# + +# Hand-written files to ship in self-contained tarball +SOURCES=pkginstall pkginstall.cfg Makefile README.txt README.html + +# Versions of tarballs we ship +IPYTHON=ipython-$(IPYTHON_VER).tar.gz +IP_ALLDEPS=ipython-alldeps-$(IPYTHON_VER) + +PYOPENSSL=pyOpenSSL-$(PYOPENSSL_VER).tar.gz +ZOPE_INTERFACE=zope.interface-$(ZOPE_INTERFACE_VER).tar.gz +NOSE=nose-$(NOSE_VER).tar.gz +TWISTED=Twisted-$(TWISTED_VER).tar.bz2 +FOOLSCAP=foolscap-$(FOOLSCAP_VER).tar.gz + +TARBALLS=$(PYOPENSSL) $(ZOPE_INTERFACE) $(TWISTED) $(FOOLSCAP) \ +$(NOSE) $(IPYTHON) + +# URLs for downloads + +#----------------------------------------------------------------------------- +# Target declaration +# + +# Targets to install, in correct dependency order +install: pyopenssl zope.interface twisted foolscap nose ipython + echo + echo "IPython Installation finished." + echo "You can now run the ipython test suite by running:" + echo "iptest" + echo "If all tests pass, you can delete this entire directory." + echo + +# Download targets +download: $(TARBALLS) + +$(IPYTHON): + wget $(IPYTHON_REPO)/$(IPYTHON) + +$(PYOPENSSL): + wget $(PYOPENSSL_REPO)/$(PYOPENSSL) + +$(ZOPE_INTERFACE): + wget $(ZOPE_INTERFACE_REPO)/$(ZOPE_INTERFACE) + +$(TWISTED): + wget $(TWISTED_REPO)/$(TWISTED) + +$(FOOLSCAP): + wget $(FOOLSCAP_REPO)/$(FOOLSCAP) + +$(NOSE): + wget $(NOSE_REPO)/$(NOSE) + + +# The calls to pkginstall must use the actual Python package name +nose: $(NOSE) + ./pkginstall nose + +zope.interface: $(ZOPE_INTERFACE) + ./pkginstall zope.interface zope + +pyopenssl: $(PYOPENSSL) + ./pkginstall pyOpenSSL OpenSSL + +twisted: $(TWISTED) + ./pkginstall Twisted + +foolscap: $(FOOLSCAP) + ./pkginstall foolscap + +ipython: $(IPYTHON) + ./pkginstall ipython IPython + +# Distribution targets +dist: $(IP_ALLDEPS).tar + +$(IP_ALLDEPS).tar: download readme + -mkdir $(IP_ALLDEPS) + -ln $(SOURCES) $(TARBALLS) $(IP_ALLDEPS)/ + tar cf $(IP_ALLDEPS).tar $(IP_ALLDEPS) + rm -rf $(IP_ALLDEPS) + +readme: README.html + +README.html: README.txt + rst2html README.txt > README.html + +# Auxiliary targets +upload: dist + rsync -e ssh -av README.html $(IP_ALLDEPS).tar \ + ipython@ipython.scipy.org:www/dist/alldeps + +clean: + ls -p | grep /$ | xargs rm -rf + rm -f $(IP_ALLDEPS)* *~ + +distclean: clean + rm -f $(TARBALLS) + rm README.html + +info: + echo "TARBALLS" + echo $(TARBALLS) diff --git a/tools/alldeps/README.txt b/tools/alldeps/README.txt new file mode 100644 index 0000000..f40ec7a --- /dev/null +++ b/tools/alldeps/README.txt @@ -0,0 +1,109 @@ +=========================================================== + Self-contained IPython installation with all dependencies +=========================================================== + +This is a self-contained source distribution of IPython with all its +*non-graphical* dependencies, that installs in a single ``make`` call to your +home directory (by default) or any location of your choice. + +This distribution is meant for developer-type usage in Unix environments, it is +*not* an easy way to get IPython working on Windows, since it assumes the +presence of a working compiler and development tools. + +Currently, the distribution contains:: + + ipython-0.9.1.tar.gz + pyOpenSSL-0.6.tar.gz + zope.interface-3.4.1.tar.gz + Twisted-8.1.0.tar.bz2 + foolscap-0.3.1.tar.gz + nose-0.10.3.tar.gz + + +Usage +===== + +Download the single tarball where this README file lives and unpack it. If +your system is already configured as described below, these lines will do the +whole job:: + + wget http://ipython.scipy.org/dist/alldeps/ipython-alldeps-0.9.1.tar + tar xf ipython-alldeps-0.9.1.tar + cd ipython-alldeps-0.9.1 + make + +If all goes well, then just type:: + + iptest + +to run IPython's test suite. + + +It is meant to be used in an environment where you have your ``$PATH``, +``$PYTHONPATH``, etc variables properly configured, so that the installation of +packages can be made with (using ``~/usr/local`` as an example):: + + python setup.py install --prefix=~/usr/local + +For an explanation of how to do this, see below. + +You can configure the default prefix used by editing the file +``pkginstall.cfg``, where you can also override the python version used for the +process. If your system is configured in this manner, you can simply type:: + + make + +and this will build and install all of IPython's non-graphical dependencies on +your system, assuming you have Python, a compiler, the Python headers and the +SSL headers available. + + +.. _environment_configuration: + +Environment configuration +========================= + +Below is an example of what to put in your ``~/.bashrc`` file to configure your +environment as described in this document, in a reasonably portable manner that +takes 64-bit operating systems into account:: + + # For processor dependent config + MACHINE=$(uname -m) + + # Python version information + PYVER=$(python -ESV 2>&1) + PYVER_MINOR=${PYVER#Python } + PYVER_MAJOR=${PYVER_MINOR:0:3} + + function export_paths { + # Export useful paths based on a common prefix + + # Input: a path prefix + + local prefix=$1 + local pp + local lp + local pypath=python${PYVER_MAJOR}/site-packages + + # Compute paths with 64-bit specifics + if [[ $MACHINE == "x86_64" ]]; then + lp=$prefix/lib64:$prefix/lib + pp=$prefix/lib64/$pypath:$prefix/lib/$pypath + else + lp=$prefix/lib + pp=$prefix/lib/$pypath + fi + + # Set paths based on given prefix + export PATH=$prefix/bin:$PATH + export CPATH=$prefix/include:$CPATH + export LD_LIBRARY_PATH=$lp:$LD_LIBRARY_PATH + export LIBRARY_PATH=$lp:$LIBRARY_PATH + export PYTHONPATH=$pp:$PYTHONPATH + } + + # Actually call the export function to set the paths. If you want more than + # one such prefix, note that the call *prepends* the new prefix to the + # existing paths, so later calls take priority. + + export_paths $HOME/usr/local diff --git a/tools/alldeps/pkginstall b/tools/alldeps/pkginstall new file mode 100755 index 0000000..14a731a --- /dev/null +++ b/tools/alldeps/pkginstall @@ -0,0 +1,119 @@ +#!/bin/bash +# +# Simple installation shell script for Python packages. +# +# Usage: +# pkginstall PAKPREFIX [PYPACKAGE] +# +# PAKPREFIX: prefix of the package as distributed in the tarball. +# +# PYPACKAGE: name of the Python package as it will end up installed. If not +# given, it defaults to PAKPREFIX. +# + +#----------------------------------------------------------------------------- +# Process command-line args +# +PAKPREFIX=$1 +PYPACKAGE=${2:-$PAKPREFIX} + +#----------------------------------------------------------------------------- +# Configure main variables +# +# Defaults for variables that the .cfg file may override. +PYTHON_DEFAULT=python +PREFIX_DEFAULT=$HOME/usr/local + +# Read config file which may declare user values for these variables. +source ./pkginstall.cfg + +# Set the variables we'll actually use, either from the config file or from our +# defaults. +PYTHON=${PYTHON-${PYTHON_DEFAULT}} +PREFIX=${PREFIX-${PREFIX_DEFAULT}} + +#----------------------------------------------------------------------------- +# 'Main' code begins +# + +# Find the actual python executable path +PYTHONX=$(which $PYTHON) +if [[ ! -x $PYTHONX ]]; then + echo "ERROR: no python executable found at given path: $PYTHON" + echo "Aborting." + exit 1 +fi + +# Python version information. PYTHONV holds a versioned string used to build +# the site-packages path for the actual Python version we'll use. +PYVER=$($PYTHONX -ESV 2>&1) +PYVER_MINOR=${PYVER#Python } +PYVER_MAJOR=${PYVER_MINOR:0:3} +PYTHONV=python${PYVER_MAJOR} + +# Set prefixes and other variables for the installation path. +SITEPKG=${PREFIX}/lib/${PYTHONV}/site-packages +SITEPKG64=${PREFIX}/lib64/${PYTHONV}/site-packages + +# User diagnostics of current config +echo "Configuration:" +echo " PYTHON : $PYTHON" +echo " PYTHONX : $PYTHONX" +echo " PREFIX : $PREFIX" +echo " SITEPKG : $SITEPKG" +echo " SITEPKG64: $SITEPKG64" + +# Find tarball +tarball=$(ls *$PAKPREFIX*.tar.*) + +if [[ -z $tarball ]]; then + echo "ERROR: tarball not found for $PYPACKAGE" + exit 1 +fi + +# Figure out the name of the directory and compression format to use to unpack +pakdir=$(echo $tarball | awk -F '.tar.' '{print $1}') +tarfmt=$(echo $tarball | awk -F '.tar.' '{print $2}') + +if [[ $tarfmt == "gz" ]]; then + tarflag="z" +else + tarflag="j" +fi + +# Unpack the tarball if needed +if [[ ! -d $pakdir ]]; then + echo "Unpacking tarball: $tarball" + tar -x -${tarflag} -f $tarball + + if [[ ! -d $pakdir ]]; then + echo "Tarball $tarball unpacked to unexpected path, aborting" + exit 1 + fi +fi + +# Remove existing ${PYPACKAGE} to make sure the build doesn't pick up spurious +# things. We don't touch the bin/ dir or anything else because it's hard to +# know what goes there in advance. But this should prevent most serious +# problems. +rm -rf $SITEPKG/${PYPACKAGE} +rm -rf $SITEPKG/${PYPACKAGE}*.egg +rm -rf $SITEPKG/${PYPACKAGE}*.egg-info + +rm -rf $SITEPKG64/${PYPACKAGE} +rm -rf $SITEPKG64/${PYPACKAGE}*.egg +rm -rf $SITEPKG64/${PYPACKAGE}*.egg-info + +# Make/install phase + +# Set python search path correctly +export PYTHONPATH=$SITEPKG:$SITEPKG64:$PYTHONPATH + +# Ensure install dirs exist +mkdir -p $SITEPKG +mkdir -p $SITEPKG64 + +cd ${pakdir} +rm -rf build dist +$PYTHONX setup.py clean +time $PYTHONX setup.py install --prefix=$PREFIX diff --git a/tools/alldeps/pkginstall.cfg b/tools/alldeps/pkginstall.cfg new file mode 100644 index 0000000..34e2f2d --- /dev/null +++ b/tools/alldeps/pkginstall.cfg @@ -0,0 +1,27 @@ +# -*- sh -*- +# +# Configuration for the pkginstall script. +# This script uses bash syntax, as it will be sourced by a bash script. + +# Uncomment and set the variables you want, otherwise pkginstall has sensible +# defaults predefined. These can also be declared either as environment +# variables (which can be done by the makefile calling this script). + +#----------------------------------------------------------------------------- +# +# Executable for Python. +# +# You can set this to an explicit full path if you don't want the default +# (simply 'python') to be the version used to install this package. + +#PYTHON=python + +#----------------------------------------------------------------------------- +# +# Default prefix. +# +# This should be a valid input the setup.py script as the --prefix argument. +# That is, your $PYTHONPATH should contain $PREFIX/lib/pythonX.Y/site-packages, +# your $PATH should contain $PREFIX/bin, etc. + +#PREFIX=$HOME/usr/local diff --git a/tools/compile.py b/tools/compile.py old mode 100644 new mode 100755