From 20e0542a587accd6985ce9a6ff0c7bd031594dc1 2007-01-05 10:36:58 From: fperez Date: 2007-01-05 10:36:58 Subject: [PATCH] Add support for set_trace-like functionality, but with IPython's enhanced debugger. --- diff --git a/IPython/Debugger.py b/IPython/Debugger.py index 25039c1..1480fb9 100644 --- a/IPython/Debugger.py +++ b/IPython/Debugger.py @@ -15,7 +15,7 @@ details on the PSF (Python Software Foundation) standard license, see: http://www.python.org/2.2.3/license.html -$Id: Debugger.py 1961 2006-12-05 21:02:40Z vivainio $""" +$Id: Debugger.py 2014 2007-01-05 10:36:58Z fperez $""" #***************************************************************************** # @@ -42,7 +42,7 @@ import linecache import os import sys -from IPython import PyColorize, ColorANSI +from IPython import PyColorize, ColorANSI, ipapi from IPython.genutils import Term from IPython.excolors import ExceptionColors @@ -63,6 +63,86 @@ if has_pydb: else: from pdb import Pdb as OldPdb +# Allow the set_trace code to operate outside of an ipython instance, even if +# it does so with some limitations. The rest of this support is implemented in +# the Tracer constructor. +def BdbQuit_excepthook(et,ev,tb): + if et==bdb.BdbQuit: + print 'Exiting Debugger.' + else: + ehook.excepthook_ori(et,ev,tb) + +def BdbQuit_IPython_excepthook(self,et,ev,tb): + print 'Exiting Debugger.' + +class Tracer(object): + """Class for local debugging, similar to pdb.set_trace. + + Instances of this class, when called, behave like pdb.set_trace, but + providing IPython's enhanced capabilities. + + This is implemented as a class which must be initialized in your own code + and not as a standalone function because we need to detect at runtime + whether IPython is already active or not. That detection is done in the + constructor, ensuring that this code plays nicely with a running IPython, + while functioning acceptably (though with limitations) if outside of it. + """ + + def __init__(self,colors=None): + """Create a local debugger instance. + + :Parameters: + + - `colors` (None): a string containing the name of the color scheme to + use, it must be one of IPython's valid color schemes. If not given, the + function will default to the current IPython scheme when running inside + IPython, and to 'NoColor' otherwise. + + Usage example: + + from IPython.Debugger import Tracer; debug_here = Tracer() + + ... later in your code + debug_here() # -> will open up the debugger at that point. + + Once the debugger activates, you can use all of its regular commands to + step through code, set breakpoints, etc. See the pdb documentation + from the Python standard library for usage details. + """ + + global __IPYTHON__ + try: + __IPYTHON__ + except NameError: + # Outside of ipython, we set our own exception hook manually + __IPYTHON__ = ipapi.get(True,False) + BdbQuit_excepthook.excepthook_ori = sys.excepthook + sys.excepthook = BdbQuit_excepthook + def_colors = 'NoColor' + try: + # Limited tab completion support + import rlcompleter,readline + readline.parse_and_bind('tab: complete') + except ImportError: + pass + else: + # In ipython, we use its custom exception handler mechanism + ip = ipapi.get() + def_colors = ip.options.colors + ip.set_custom_exc((bdb.BdbQuit,),BdbQuit_IPython_excepthook) + + if colors is None: + colors = def_colors + self.debugger = Pdb(colors) + + def __call__(self): + """Starts an interactive debugger at the point where called. + + This is similar to the pdb.set_trace() function from the std lib, but + using IPython's enhanced debugger.""" + + self.debugger.set_trace(sys._getframe().f_back) + def decorate_fn_with_doc(new_fn, old_fn, additional_text=""): """Make new_fn have old_fn's doc string. This is particularly useful for the do_... commands that hook into the help system. @@ -172,6 +252,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() # shorthands diff --git a/IPython/ipapi.py b/IPython/ipapi.py index 4783845..c8491b2 100644 --- a/IPython/ipapi.py +++ b/IPython/ipapi.py @@ -91,6 +91,12 @@ class IPythonNotRunning: without ipython. Obviously code which uses the ipython object for computations will not work, but this allows a wider range of code to transparently work whether ipython is being used or not.""" + + def __init__(self,warn=True): + if warn: + self.dummy = self._dummy_warn + else: + self.dummy = self._dummy_silent def __str__(self): return "" @@ -100,26 +106,33 @@ class IPythonNotRunning: def __getattr__(self,name): return self.dummy - def dummy(self,*args,**kw): + def _dummy_warn(self,*args,**kw): """Dummy function, which doesn't do anything but warn.""" + warn("IPython is not running, this is a dummy no-op function") + def _dummy_silent(self,*args,**kw): + """Dummy function, which doesn't do anything and emits no warnings.""" + pass + _recent = None -def get(allow_dummy=False): +def get(allow_dummy=False,dummy_warn=True): """Get an IPApi object. If allow_dummy is true, returns an instance of IPythonNotRunning instead of None if not running under IPython. + If dummy_warn is false, the dummy instance will be completely silent. + Running this should be the first thing you do when writing extensions that can be imported as normal modules. You can then direct all the configuration operations against the returned object. """ global _recent if allow_dummy and not _recent: - _recent = IPythonNotRunning() + _recent = IPythonNotRunning(dummy_warn) return _recent class IPApi: diff --git a/IPython/iplib.py b/IPython/iplib.py index 1c2ab5c..607d36c 100644 --- a/IPython/iplib.py +++ b/IPython/iplib.py @@ -6,7 +6,7 @@ Requires Python 2.3 or newer. This file contains all the classes and helper functions specific to IPython. -$Id: iplib.py 1977 2006-12-11 16:52:12Z fperez $ +$Id: iplib.py 2014 2007-01-05 10:36:58Z fperez $ """ #***************************************************************************** @@ -1460,6 +1460,7 @@ want to merge them back into the new files.""" % locals() etype, value, tb = sys.exc_info() else: etype, value, tb = exc_tuple + if etype is SyntaxError: self.showsyntaxerror(filename) else: @@ -1470,11 +1471,14 @@ want to merge them back into the new files.""" % locals() sys.last_type = etype sys.last_value = value sys.last_traceback = tb - - self.InteractiveTB(etype,value,tb,tb_offset=tb_offset) - if self.InteractiveTB.call_pdb and self.has_readline: - # pdb mucks up readline, fix it back - self.readline.set_completer(self.Completer.complete) + + if etype in self.custom_exceptions: + self.CustomTB(etype,value,tb) + else: + self.InteractiveTB(etype,value,tb,tb_offset=tb_offset) + if self.InteractiveTB.call_pdb and self.has_readline: + # pdb mucks up readline, fix it back + self.readline.set_completer(self.Completer.complete) def mainloop(self,banner=None): """Creates the local namespace and starts the mainloop. diff --git a/doc/ChangeLog b/doc/ChangeLog index d4eb2a9..36ec9a5 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,12 @@ +2007-01-05 Fernando Perez + + * IPython/iplib.py (showtraceback): ensure that we correctly call + custom handlers in all cases (some with pdb were slipping through, + but I'm not exactly sure why). + + * IPython/Debugger.py (Tracer.__init__): added new class to + support set_trace-like usage of IPython's enhanced debugger. + 2006-12-24 Ville Vainio * ipmaker.py: more informative message when ipy_user_conf diff --git a/tools/testrel b/tools/testrel index cbc3a37..a83b85e 100755 --- a/tools/testrel +++ b/tools/testrel @@ -12,7 +12,7 @@ cd ~/ipython/ipython ./setup.py sdist --formats=gztar # Build rpm -python2.4 ./setup.py bdist_rpm --binary-only --release=py24 --python=/usr/bin/python2.4 +#python2.4 ./setup.py bdist_rpm --binary-only --release=py24 --python=/usr/bin/python2.4 # Build eggs ./eggsetup.py bdist_egg diff --git a/tools/testupload b/tools/testupload index f066c5d..7024e67 100755 --- a/tools/testupload +++ b/tools/testupload @@ -1,6 +1,6 @@ #!/bin/sh # clean public testing/ dir and upload -ssh "rm -f ipython@ipython.scipy.org:www/dist/testing/*" +#ssh "rm -f ipython@ipython.scipy.org:www/dist/testing/*" cd ~/ipython/ipython/dist scp * ipython@ipython.scipy.org:www/dist/testing/