##// END OF EJS Templates
Merge pull request #876 from minrk/customtb...
Fernando Perez -
r5010:aa846e31 merge
parent child Browse files
Show More
@@ -27,6 +27,7 b' Authors:'
27 # Imports
27 # Imports
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 import atexit
30 import glob
31 import glob
31 import logging
32 import logging
32 import os
33 import os
@@ -156,6 +157,9 b' class BaseIPythonApplication(Application):'
156 """Create a crash handler, typically setting sys.excepthook to it."""
157 """Create a crash handler, typically setting sys.excepthook to it."""
157 self.crash_handler = self.crash_handler_class(self)
158 self.crash_handler = self.crash_handler_class(self)
158 sys.excepthook = self.crash_handler
159 sys.excepthook = self.crash_handler
160 def unset_crashhandler():
161 sys.excepthook = sys.__excepthook__
162 atexit.register(unset_crashhandler)
159
163
160 def _ipython_dir_changed(self, name, old, new):
164 def _ipython_dir_changed(self, name, old, new):
161 if old in sys.path:
165 if old in sys.path:
@@ -38,7 +38,7 b' from IPython.core.excolors import exception_colors'
38 has_pydb = False
38 has_pydb = False
39 prompt = 'ipdb> '
39 prompt = 'ipdb> '
40 #We have to check this directly from sys.argv, config struct not yet available
40 #We have to check this directly from sys.argv, config struct not yet available
41 if '-pydb' in sys.argv:
41 if '--pydb' in sys.argv:
42 try:
42 try:
43 import pydb
43 import pydb
44 if hasattr(pydb.pydb, "runl") and pydb.version>'1.17':
44 if hasattr(pydb.pydb, "runl") and pydb.version>'1.17':
@@ -64,7 +64,7 b' def BdbQuit_excepthook(et,ev,tb):'
64 else:
64 else:
65 BdbQuit_excepthook.excepthook_ori(et,ev,tb)
65 BdbQuit_excepthook.excepthook_ori(et,ev,tb)
66
66
67 def BdbQuit_IPython_excepthook(self,et,ev,tb):
67 def BdbQuit_IPython_excepthook(self,et,ev,tb,tb_offset=None):
68 print 'Exiting Debugger.'
68 print 'Exiting Debugger.'
69
69
70
70
@@ -104,8 +104,8 b' class Tracer(object):'
104 """
104 """
105
105
106 try:
106 try:
107 ip = ipapi.get()
107 ip = get_ipython()
108 except:
108 except NameError:
109 # Outside of ipython, we set our own exception hook manually
109 # Outside of ipython, we set our own exception hook manually
110 BdbQuit_excepthook.excepthook_ori = sys.excepthook
110 BdbQuit_excepthook.excepthook_ori = sys.excepthook
111 sys.excepthook = BdbQuit_excepthook
111 sys.excepthook = BdbQuit_excepthook
@@ -1447,29 +1447,37 b' class InteractiveShell(SingletonConfigurable, Magic):'
1447
1447
1448 Set a custom exception handler, which will be called if any of the
1448 Set a custom exception handler, which will be called if any of the
1449 exceptions in exc_tuple occur in the mainloop (specifically, in the
1449 exceptions in exc_tuple occur in the mainloop (specifically, in the
1450 run_code() method.
1450 run_code() method).
1451
1451
1452 Inputs:
1452 Parameters
1453 ----------
1454
1455 exc_tuple : tuple of exception classes
1456 A *tuple* of exception classes, for which to call the defined
1457 handler. It is very important that you use a tuple, and NOT A
1458 LIST here, because of the way Python's except statement works. If
1459 you only want to trap a single exception, use a singleton tuple::
1453
1460
1454 - exc_tuple: a *tuple* of valid exceptions to call the defined
1461 exc_tuple == (MyCustomException,)
1455 handler for. It is very important that you use a tuple, and NOT A
1456 LIST here, because of the way Python's except statement works. If
1457 you only want to trap a single exception, use a singleton tuple:
1458
1462
1459 exc_tuple == (MyCustomException,)
1463 handler : callable
1464 handler must have the following signature::
1460
1465
1461 - handler: this must be defined as a function with the following
1466 def my_handler(self, etype, value, tb, tb_offset=None):
1462 basic interface::
1467 ...
1468 return structured_traceback
1463
1469
1464 def my_handler(self, etype, value, tb, tb_offset=None)
1470 Your handler must return a structured traceback (a list of strings),
1465 ...
1471 or None.
1466 # The return value must be
1467 return structured_traceback
1468
1472
1469 This will be made into an instance method (via types.MethodType)
1473 This will be made into an instance method (via types.MethodType)
1470 of IPython itself, and it will be called if any of the exceptions
1474 of IPython itself, and it will be called if any of the exceptions
1471 listed in the exc_tuple are caught. If the handler is None, an
1475 listed in the exc_tuple are caught. If the handler is None, an
1472 internal basic one is used, which just prints basic info.
1476 internal basic one is used, which just prints basic info.
1477
1478 To protect IPython from crashes, if your handler ever raises an
1479 exception or returns an invalid result, it will be immediately
1480 disabled.
1473
1481
1474 WARNING: by putting in your own exception handler into IPython's main
1482 WARNING: by putting in your own exception handler into IPython's main
1475 execution loop, you run a very good chance of nasty crashes. This
1483 execution loop, you run a very good chance of nasty crashes. This
@@ -1478,16 +1486,62 b' class InteractiveShell(SingletonConfigurable, Magic):'
1478 assert type(exc_tuple)==type(()) , \
1486 assert type(exc_tuple)==type(()) , \
1479 "The custom exceptions must be given AS A TUPLE."
1487 "The custom exceptions must be given AS A TUPLE."
1480
1488
1481 def dummy_handler(self,etype,value,tb):
1489 def dummy_handler(self,etype,value,tb,tb_offset=None):
1482 print '*** Simple custom exception handler ***'
1490 print '*** Simple custom exception handler ***'
1483 print 'Exception type :',etype
1491 print 'Exception type :',etype
1484 print 'Exception value:',value
1492 print 'Exception value:',value
1485 print 'Traceback :',tb
1493 print 'Traceback :',tb
1486 #print 'Source code :','\n'.join(self.buffer)
1494 #print 'Source code :','\n'.join(self.buffer)
1487
1495
1488 if handler is None: handler = dummy_handler
1496 def validate_stb(stb):
1489
1497 """validate structured traceback return type
1490 self.CustomTB = types.MethodType(handler,self)
1498
1499 return type of CustomTB *should* be a list of strings, but allow
1500 single strings or None, which are harmless.
1501
1502 This function will *always* return a list of strings,
1503 and will raise a TypeError if stb is inappropriate.
1504 """
1505 msg = "CustomTB must return list of strings, not %r" % stb
1506 if stb is None:
1507 return []
1508 elif isinstance(stb, basestring):
1509 return [stb]
1510 elif not isinstance(stb, list):
1511 raise TypeError(msg)
1512 # it's a list
1513 for line in stb:
1514 # check every element
1515 if not isinstance(line, basestring):
1516 raise TypeError(msg)
1517 return stb
1518
1519 if handler is None:
1520 wrapped = dummy_handler
1521 else:
1522 def wrapped(self,etype,value,tb,tb_offset=None):
1523 """wrap CustomTB handler, to protect IPython from user code
1524
1525 This makes it harder (but not impossible) for custom exception
1526 handlers to crash IPython.
1527 """
1528 try:
1529 stb = handler(self,etype,value,tb,tb_offset=tb_offset)
1530 return validate_stb(stb)
1531 except:
1532 # clear custom handler immediately
1533 self.set_custom_exc((), None)
1534 print >> io.stderr, "Custom TB Handler failed, unregistering"
1535 # show the exception in handler first
1536 stb = self.InteractiveTB.structured_traceback(*sys.exc_info())
1537 print >> io.stdout, self.InteractiveTB.stb2text(stb)
1538 print >> io.stdout, "The original exception:"
1539 stb = self.InteractiveTB.structured_traceback(
1540 (etype,value,tb), tb_offset=tb_offset
1541 )
1542 return stb
1543
1544 self.CustomTB = types.MethodType(wrapped,self)
1491 self.custom_exceptions = exc_tuple
1545 self.custom_exceptions = exc_tuple
1492
1546
1493 def excepthook(self, etype, value, tb):
1547 def excepthook(self, etype, value, tb):
@@ -1556,11 +1610,7 b' class InteractiveShell(SingletonConfigurable, Magic):'
1556 sys.last_value = value
1610 sys.last_value = value
1557 sys.last_traceback = tb
1611 sys.last_traceback = tb
1558 if etype in self.custom_exceptions:
1612 if etype in self.custom_exceptions:
1559 # FIXME: Old custom traceback objects may just return a
1560 # string, in that case we just put it into a list
1561 stb = self.CustomTB(etype, value, tb, tb_offset)
1613 stb = self.CustomTB(etype, value, tb, tb_offset)
1562 if isinstance(ctb, basestring):
1563 stb = [stb]
1564 else:
1614 else:
1565 if exception_only:
1615 if exception_only:
1566 stb = ['An exception has occurred, use %tb to see '
1616 stb = ['An exception has occurred, use %tb to see '
@@ -1571,9 +1621,11 b' class InteractiveShell(SingletonConfigurable, Magic):'
1571 stb = self.InteractiveTB.structured_traceback(etype,
1621 stb = self.InteractiveTB.structured_traceback(etype,
1572 value, tb, tb_offset=tb_offset)
1622 value, tb, tb_offset=tb_offset)
1573
1623
1624 self._showtraceback(etype, value, stb)
1574 if self.call_pdb:
1625 if self.call_pdb:
1575 # drop into debugger
1626 # drop into debugger
1576 self.debugger(force=True)
1627 self.debugger(force=True)
1628 return
1577
1629
1578 # Actually show the traceback
1630 # Actually show the traceback
1579 self._showtraceback(etype, value, stb)
1631 self._showtraceback(etype, value, stb)
@@ -50,6 +50,14 b" addflag('pdb', 'InteractiveShell.pdb',"
50 "Enable auto calling the pdb debugger after every exception.",
50 "Enable auto calling the pdb debugger after every exception.",
51 "Disable auto calling the pdb debugger after every exception."
51 "Disable auto calling the pdb debugger after every exception."
52 )
52 )
53 # pydb flag doesn't do any config, as core.debugger switches on import,
54 # which is before parsing. This just allows the flag to be passed.
55 shell_flags.update(dict(
56 pydb = ({},
57 """"Use the third party 'pydb' package as debugger, instead of pdb.
58 Requires that pydb is installed."""
59 )
60 ))
53 addflag('pprint', 'PlainTextFormatter.pprint',
61 addflag('pprint', 'PlainTextFormatter.pprint',
54 "Enable auto pretty printing of results.",
62 "Enable auto pretty printing of results.",
55 "Disable auto auto pretty printing of results."
63 "Disable auto auto pretty printing of results."
@@ -146,3 +146,37 b' class InteractiveShellTestCase(unittest.TestCase):'
146 finally:
146 finally:
147 # Reset compiler flags so we don't mess up other tests.
147 # Reset compiler flags so we don't mess up other tests.
148 ip.compile.reset_compiler_flags()
148 ip.compile.reset_compiler_flags()
149
150 def test_bad_custom_tb(self):
151 """Check that InteractiveShell is protected from bad custom exception handlers"""
152 ip = get_ipython()
153 from IPython.utils import io
154 save_stderr = io.stderr
155 try:
156 # capture stderr
157 io.stderr = StringIO()
158 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
159 self.assertEquals(ip.custom_exceptions, (IOError,))
160 ip.run_cell(u'raise IOError("foo")')
161 self.assertEquals(ip.custom_exceptions, ())
162 self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
163 finally:
164 io.stderr = save_stderr
165
166 def test_bad_custom_tb_return(self):
167 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
168 ip = get_ipython()
169 from IPython.utils import io
170 save_stderr = io.stderr
171 try:
172 # capture stderr
173 io.stderr = StringIO()
174 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
175 self.assertEquals(ip.custom_exceptions, (NameError,))
176 ip.run_cell(u'a=abracadabra')
177 self.assertEquals(ip.custom_exceptions, ())
178 self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
179 finally:
180 io.stderr = save_stderr
181
182
General Comments 0
You need to be logged in to leave comments. Login now