##// END OF EJS Templates
Complete implementation of interactive traceback support....
Fernando Perez -
Show More
@@ -0,0 +1,119 b''
1 """Compiler tools with improved interactive support.
2
3 Provides compilation machinery similar to codeop, but with caching support so
4 we can provide interactive tracebacks.
5
6 Authors
7 -------
8 * Robert Kern
9 * Fernando Perez
10 """
11
12 # Note: though it might be more natural to name this module 'compiler', that
13 # name is in the stdlib and name collisions with the stdlib tend to produce
14 # weird problems (often with third-party tools).
15
16 #-----------------------------------------------------------------------------
17 # Copyright (C) 2010 The IPython Development Team.
18 #
19 # Distributed under the terms of the BSD License.
20 #
21 # The full license is in the file COPYING.txt, distributed with this software.
22 #-----------------------------------------------------------------------------
23
24 #-----------------------------------------------------------------------------
25 # Imports
26 #-----------------------------------------------------------------------------
27 from __future__ import print_function
28
29 # Stdlib imports
30 import codeop
31 import hashlib
32 import linecache
33 import time
34
35 #-----------------------------------------------------------------------------
36 # Local utilities
37 #-----------------------------------------------------------------------------
38
39 def code_name(code, number=0):
40 """ Compute a (probably) unique name for code for caching.
41 """
42 hash_digest = hashlib.md5(code).hexdigest()
43 # Include the number and 12 characters of the hash in the name. It's
44 # pretty much impossible that in a single session we'll have collisions
45 # even with truncated hashes, and the full one makes tracebacks too long
46 return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12])
47
48 #-----------------------------------------------------------------------------
49 # Classes and functions
50 #-----------------------------------------------------------------------------
51
52 class CachingCompiler(object):
53 """A compiler that caches code compiled from interactive statements.
54 """
55
56 def __init__(self):
57 self._compiler = codeop.CommandCompiler()
58
59 # This is ugly, but it must be done this way to allow multiple
60 # simultaneous ipython instances to coexist. Since Python itself
61 # directly accesses the data structures in the linecache module, and
62 # the cache therein is global, we must work with that data structure.
63 # We must hold a reference to the original checkcache routine and call
64 # that in our own check_cache() below, but the special IPython cache
65 # must also be shared by all IPython instances. If we were to hold
66 # separate caches (one in each CachingCompiler instance), any call made
67 # by Python itself to linecache.checkcache() would obliterate the
68 # cached data from the other IPython instances.
69 if not hasattr(linecache, '_ipython_cache'):
70 linecache._ipython_cache = {}
71 if not hasattr(linecache, '_checkcache_ori'):
72 linecache._checkcache_ori = linecache.checkcache
73 # Now, we must monkeypatch the linecache directly so that parts of the
74 # stdlib that call it outside our control go through our codepath
75 # (otherwise we'd lose our tracebacks).
76 linecache.checkcache = self.check_cache
77
78 @property
79 def compiler_flags(self):
80 """Flags currently active in the compilation process.
81 """
82 return self._compiler.compiler.flags
83
84 def __call__(self, code, symbol, number=0):
85 """Compile some code while caching its contents such that the inspect
86 module can find it later.
87
88 Parameters
89 ----------
90 code : str
91 Source code to be compiled, one or more lines.
92
93 symbol : str
94 One of 'single', 'exec' or 'eval' (see the builtin ``compile``
95 documentation for further details on these fields).
96
97 number : int, optional
98 An integer argument identifying the code, useful for informational
99 purposes in tracebacks (typically it will be the IPython prompt
100 number).
101 """
102 name = code_name(code, number)
103 code_obj = self._compiler(code, name, symbol)
104 entry = (len(code), time.time(),
105 [line+'\n' for line in code.splitlines()], name)
106 # Cache the info both in the linecache (a global cache used internally
107 # by most of Python's inspect/traceback machinery), and in our cache
108 linecache.cache[name] = entry
109 linecache._ipython_cache[name] = entry
110 return code_obj
111
112 def check_cache(self, *args):
113 """Call linecache.checkcache() safely protecting our cached values.
114 """
115 # First call the orignal checkcache as intended
116 linecache._checkcache_ori(*args)
117 # Then, update back the cache with our data, so that tracebacks related
118 # to our compiled codes can be produced.
119 linecache.cache.update(linecache._ipython_cache)
@@ -0,0 +1,62 b''
1 """Tests for the compilerop module.
2 """
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2010 The IPython Development Team.
5 #
6 # Distributed under the terms of the BSD License.
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14 from __future__ import print_function
15
16 # Stdlib imports
17 import linecache
18
19 # Third-party imports
20 import nose.tools as nt
21
22 # Our own imports
23 from IPython.core import compilerop
24
25 #-----------------------------------------------------------------------------
26 # Test functions
27 #-----------------------------------------------------------------------------
28
29 def test_code_name():
30 code = 'x=1'
31 name = compilerop.code_name(code)
32 nt.assert_true(name.startswith('<ipython-input-0'))
33
34
35 def test_code_name2():
36 code = 'x=1'
37 name = compilerop.code_name(code, 9)
38 nt.assert_true(name.startswith('<ipython-input-9'))
39
40
41 def test_compiler():
42 """Test the compiler correctly compiles and caches inputs
43 """
44 cp = compilerop.CachingCompiler()
45 ncache = len(linecache.cache)
46 cp('x=1', 'single')
47 nt.assert_true(len(linecache.cache) > ncache)
48
49
50 def test_compiler_check_cache():
51 """Test the compiler properly manages the cache.
52 """
53 # Rather simple-minded tests that just exercise the API
54 cp = compilerop.CachingCompiler()
55 cp('x=1', 'single', 99)
56 # Ensure now that after clearing the cache, our entries survive
57 cp.check_cache()
58 for k in linecache.cache:
59 if k.startswith('<ipython-input-99'):
60 break
61 else:
62 raise AssertionError('Entry for input-99 missing from linecache')
@@ -40,6 +40,7 b' from IPython.core import shadowns'
40 from IPython.core import ultratb
40 from IPython.core import ultratb
41 from IPython.core.alias import AliasManager
41 from IPython.core.alias import AliasManager
42 from IPython.core.builtin_trap import BuiltinTrap
42 from IPython.core.builtin_trap import BuiltinTrap
43 from IPython.core.compilerop import CachingCompiler
43 from IPython.core.display_trap import DisplayTrap
44 from IPython.core.display_trap import DisplayTrap
44 from IPython.core.displayhook import DisplayHook
45 from IPython.core.displayhook import DisplayHook
45 from IPython.core.error import TryNext, UsageError
46 from IPython.core.error import TryNext, UsageError
@@ -136,39 +137,6 b' class MultipleInstanceError(Exception):'
136 # Main IPython class
137 # Main IPython class
137 #-----------------------------------------------------------------------------
138 #-----------------------------------------------------------------------------
138
139
139
140 ######## Code to be moved later if it works, meant to try to get proper
141 ######## tracebacks
142
143 import hashlib
144 import linecache
145 import time
146 import types
147
148 def code_name(code):
149 """ Compute a (probably) unique name for code for caching.
150 """
151 hash_digest = hashlib.md5(code).hexdigest()
152 return '<code %s>' % hash_digest
153
154
155 class CachingCompiler(codeop.CommandCompiler):
156
157 def __call__(self, code, filename, symbol):
158 """ Compile some code while caching its contents such that the inspect
159 module can find it later.
160 """
161 #code += '\n'
162 name = code_name(code)
163 code_obj = codeop.CommandCompiler.__call__(self, code, name, symbol)
164 linecache.cache[name] = (len(code),
165 time.time(),
166 [line+'\n' for line in code.splitlines()],
167 name)
168 return code_obj
169
170 #############
171
172 class InteractiveShell(Configurable, Magic):
140 class InteractiveShell(Configurable, Magic):
173 """An enhanced, interactive shell for Python."""
141 """An enhanced, interactive shell for Python."""
174
142
@@ -401,7 +369,6 b' class InteractiveShell(Configurable, Magic):'
401 self.more = False
369 self.more = False
402
370
403 # command compiler
371 # command compiler
404 #self.compile = codeop.CommandCompiler()
405 self.compile = CachingCompiler()
372 self.compile = CachingCompiler()
406
373
407 # User input buffers
374 # User input buffers
@@ -1134,7 +1101,7 b' class InteractiveShell(Configurable, Magic):'
1134 # We need to special-case 'print', which as of python2.6 registers as a
1101 # We need to special-case 'print', which as of python2.6 registers as a
1135 # function but should only be treated as one if print_function was
1102 # function but should only be treated as one if print_function was
1136 # loaded with a future import. In this case, just bail.
1103 # loaded with a future import. In this case, just bail.
1137 if (oname == 'print' and not (self.compile.compiler.flags &
1104 if (oname == 'print' and not (self.compile.compiler_flags &
1138 __future__.CO_FUTURE_PRINT_FUNCTION)):
1105 __future__.CO_FUTURE_PRINT_FUNCTION)):
1139 return {'found':found, 'obj':obj, 'namespace':ospace,
1106 return {'found':found, 'obj':obj, 'namespace':ospace,
1140 'ismagic':ismagic, 'isalias':isalias, 'parent':parent}
1107 'ismagic':ismagic, 'isalias':isalias, 'parent':parent}
@@ -1293,7 +1260,8 b' class InteractiveShell(Configurable, Magic):'
1293 # internal code. Valid modes: ['Plain','Context','Verbose']
1260 # internal code. Valid modes: ['Plain','Context','Verbose']
1294 self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain',
1261 self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain',
1295 color_scheme='NoColor',
1262 color_scheme='NoColor',
1296 tb_offset = 1)
1263 tb_offset = 1,
1264 check_cache=self.compile.check_cache)
1297
1265
1298 # The instance will store a pointer to the system-wide exception hook,
1266 # The instance will store a pointer to the system-wide exception hook,
1299 # so that runtime code (such as magics) can access it. This is because
1267 # so that runtime code (such as magics) can access it. This is because
@@ -2159,14 +2127,15 b' class InteractiveShell(Configurable, Magic):'
2159
2127
2160 # Get the main body to run as a cell
2128 # Get the main body to run as a cell
2161 ipy_body = ''.join(blocks[:-1])
2129 ipy_body = ''.join(blocks[:-1])
2162 retcode = self.run_code(ipy_body, post_execute=False)
2130 retcode = self.run_source(ipy_body, symbol='exec',
2131 post_execute=False)
2163 if retcode==0:
2132 if retcode==0:
2164 # And the last expression via runlines so it produces output
2133 # And the last expression via runlines so it produces output
2165 self.run_one_block(last)
2134 self.run_one_block(last)
2166 else:
2135 else:
2167 # Run the whole cell as one entity, storing both raw and
2136 # Run the whole cell as one entity, storing both raw and
2168 # processed input in history
2137 # processed input in history
2169 self.run_code(ipy_cell)
2138 self.run_source(ipy_cell, symbol='exec')
2170
2139
2171 # Each cell is a *single* input, regardless of how many lines it has
2140 # Each cell is a *single* input, regardless of how many lines it has
2172 self.execution_count += 1
2141 self.execution_count += 1
@@ -2242,7 +2211,8 b' class InteractiveShell(Configurable, Magic):'
2242 if more:
2211 if more:
2243 self.push_line('\n')
2212 self.push_line('\n')
2244
2213
2245 def run_source(self, source, filename='<ipython console>', symbol='single'):
2214 def run_source(self, source, filename=None,
2215 symbol='single', post_execute=True):
2246 """Compile and run some source in the interpreter.
2216 """Compile and run some source in the interpreter.
2247
2217
2248 Arguments are as for compile_command().
2218 Arguments are as for compile_command().
@@ -2284,7 +2254,7 b' class InteractiveShell(Configurable, Magic):'
2284 print 'encoding', self.stdin_encoding # dbg
2254 print 'encoding', self.stdin_encoding # dbg
2285
2255
2286 try:
2256 try:
2287 code = self.compile(usource,filename,symbol)
2257 code = self.compile(usource, symbol, self.execution_count)
2288 except (OverflowError, SyntaxError, ValueError, TypeError, MemoryError):
2258 except (OverflowError, SyntaxError, ValueError, TypeError, MemoryError):
2289 # Case 1
2259 # Case 1
2290 self.showsyntaxerror(filename)
2260 self.showsyntaxerror(filename)
@@ -2301,7 +2271,7 b' class InteractiveShell(Configurable, Magic):'
2301 # buffer attribute as '\n'.join(self.buffer).
2271 # buffer attribute as '\n'.join(self.buffer).
2302 self.code_to_run = code
2272 self.code_to_run = code
2303 # now actually execute the code object
2273 # now actually execute the code object
2304 if self.run_code(code) == 0:
2274 if self.run_code(code, post_execute) == 0:
2305 return False
2275 return False
2306 else:
2276 else:
2307 return None
2277 return None
@@ -2480,7 +2450,7 b' class InteractiveShell(Configurable, Magic):'
2480 sys._getframe(depth+1).f_locals # locals
2450 sys._getframe(depth+1).f_locals # locals
2481 ))
2451 ))
2482
2452
2483 def mktempfile(self,data=None):
2453 def mktempfile(self, data=None, prefix='ipython_edit_'):
2484 """Make a new tempfile and return its filename.
2454 """Make a new tempfile and return its filename.
2485
2455
2486 This makes a call to tempfile.mktemp, but it registers the created
2456 This makes a call to tempfile.mktemp, but it registers the created
@@ -2491,7 +2461,7 b' class InteractiveShell(Configurable, Magic):'
2491 - data(None): if data is given, it gets written out to the temp file
2461 - data(None): if data is given, it gets written out to the temp file
2492 immediately, and the file is closed again."""
2462 immediately, and the file is closed again."""
2493
2463
2494 filename = tempfile.mktemp('.py','ipython_edit_')
2464 filename = tempfile.mktemp('.py', prefix)
2495 self.tempfiles.append(filename)
2465 self.tempfiles.append(filename)
2496
2466
2497 if data:
2467 if data:
@@ -92,8 +92,6 b' ZeroDivisionError Traceback (most recent call last)'
92 30 mode = 'div'
92 30 mode = 'div'
93 31
93 31
94 ---> 32 bar(mode)
94 ---> 32 bar(mode)
95 33
96 34
97 <BLANKLINE>
95 <BLANKLINE>
98 ... in bar(mode)
96 ... in bar(mode)
99 14 "bar"
97 14 "bar"
@@ -128,8 +126,6 b' ZeroDivisionError Traceback (most recent call last)'
128 ---> 32 bar(mode)
126 ---> 32 bar(mode)
129 global bar = <function bar at ...>
127 global bar = <function bar at ...>
130 global mode = 'div'
128 global mode = 'div'
131 33
132 34
133 <BLANKLINE>
129 <BLANKLINE>
134 ... in bar(mode='div')
130 ... in bar(mode='div')
135 14 "bar"
131 14 "bar"
@@ -186,8 +182,6 b' SystemExit Traceback (most recent call last)'
186 30 mode = 'div'
182 30 mode = 'div'
187 31
183 31
188 ---> 32 bar(mode)
184 ---> 32 bar(mode)
189 33
190 34
191 <BLANKLINE>
185 <BLANKLINE>
192 ...bar(mode)
186 ...bar(mode)
193 20 except:
187 20 except:
@@ -218,8 +212,6 b' SystemExit Traceback (most recent call last)'
218 ---> 32 bar(mode)
212 ---> 32 bar(mode)
219 global bar = <function bar at ...>
213 global bar = <function bar at ...>
220 global mode = 'exit'
214 global mode = 'exit'
221 33
222 34
223 <BLANKLINE>
215 <BLANKLINE>
224 ... in bar(mode='exit')
216 ... in bar(mode='exit')
225 20 except:
217 20 except:
@@ -244,11 +244,6 b' def _fixed_getinnerframes(etb, context=1,tb_offset=0):'
244 start = max(maybeStart, 0)
244 start = max(maybeStart, 0)
245 end = start + context
245 end = start + context
246 lines = linecache.getlines(file)[start:end]
246 lines = linecache.getlines(file)[start:end]
247 # pad with empty lines if necessary
248 if maybeStart < 0:
249 lines = (['\n'] * -maybeStart) + lines
250 if len(lines) < context:
251 lines += ['\n'] * (context - len(lines))
252 buf = list(records[i])
247 buf = list(records[i])
253 buf[LNUM_POS] = lnum
248 buf[LNUM_POS] = lnum
254 buf[INDEX_POS] = lnum - 1 - start
249 buf[INDEX_POS] = lnum - 1 - start
@@ -279,7 +274,15 b' def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None):'
279 _line_format = _parser.format2
274 _line_format = _parser.format2
280
275
281 for line in lines:
276 for line in lines:
282 new_line, err = _line_format(line,'str',scheme)
277 # FIXME: we need to ensure the source is a pure string at this point,
278 # else the coloring code makes a royal mess. This is in need of a
279 # serious refactoring, so that all of the ultratb and PyColorize code
280 # is unicode-safe. So for now this is rather an ugly hack, but
281 # necessary to at least have readable tracebacks. Improvements welcome!
282 if type(line)==unicode:
283 line = line.encode('utf-8', 'replace')
284
285 new_line, err = _line_format(line, 'str', scheme)
283 if not err: line = new_line
286 if not err: line = new_line
284
287
285 if i == lnum:
288 if i == lnum:
@@ -636,7 +639,8 b' class VerboseTB(TBTools):'
636 would appear in the traceback)."""
639 would appear in the traceback)."""
637
640
638 def __init__(self,color_scheme = 'Linux', call_pdb=False, ostream=None,
641 def __init__(self,color_scheme = 'Linux', call_pdb=False, ostream=None,
639 tb_offset=0, long_header=False, include_vars=True):
642 tb_offset=0, long_header=False, include_vars=True,
643 check_cache=None):
640 """Specify traceback offset, headers and color scheme.
644 """Specify traceback offset, headers and color scheme.
641
645
642 Define how many frames to drop from the tracebacks. Calling it with
646 Define how many frames to drop from the tracebacks. Calling it with
@@ -648,6 +652,14 b' class VerboseTB(TBTools):'
648 self.tb_offset = tb_offset
652 self.tb_offset = tb_offset
649 self.long_header = long_header
653 self.long_header = long_header
650 self.include_vars = include_vars
654 self.include_vars = include_vars
655 # By default we use linecache.checkcache, but the user can provide a
656 # different check_cache implementation. This is used by the IPython
657 # kernel to provide tracebacks for interactive code that is cached,
658 # by a compiler instance that flushes the linecache but preserves its
659 # own code cache.
660 if check_cache is None:
661 check_cache = linecache.checkcache
662 self.check_cache = check_cache
651
663
652 def structured_traceback(self, etype, evalue, etb, tb_offset=None,
664 def structured_traceback(self, etype, evalue, etb, tb_offset=None,
653 context=5):
665 context=5):
@@ -723,7 +735,7 b' class VerboseTB(TBTools):'
723 frames = []
735 frames = []
724 # Flush cache before calling inspect. This helps alleviate some of the
736 # Flush cache before calling inspect. This helps alleviate some of the
725 # problems with python 2.3's inspect.py.
737 # problems with python 2.3's inspect.py.
726 linecache.checkcache()
738 ##self.check_cache()
727 # Drop topmost frames if requested
739 # Drop topmost frames if requested
728 try:
740 try:
729 # Try the default getinnerframes and Alex's: Alex's fixes some
741 # Try the default getinnerframes and Alex's: Alex's fixes some
@@ -1034,7 +1046,8 b' class FormattedTB(VerboseTB, ListTB):'
1034
1046
1035 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
1047 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
1036 ostream=None,
1048 ostream=None,
1037 tb_offset=0, long_header=False, include_vars=False):
1049 tb_offset=0, long_header=False, include_vars=False,
1050 check_cache=None):
1038
1051
1039 # NEVER change the order of this list. Put new modes at the end:
1052 # NEVER change the order of this list. Put new modes at the end:
1040 self.valid_modes = ['Plain','Context','Verbose']
1053 self.valid_modes = ['Plain','Context','Verbose']
@@ -1042,7 +1055,8 b' class FormattedTB(VerboseTB, ListTB):'
1042
1055
1043 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1056 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1044 ostream=ostream, tb_offset=tb_offset,
1057 ostream=ostream, tb_offset=tb_offset,
1045 long_header=long_header, include_vars=include_vars)
1058 long_header=long_header, include_vars=include_vars,
1059 check_cache=check_cache)
1046
1060
1047 # Different types of tracebacks are joined with different separators to
1061 # Different types of tracebacks are joined with different separators to
1048 # form a single string. They are taken from this dict
1062 # form a single string. They are taken from this dict
@@ -1067,7 +1081,7 b' class FormattedTB(VerboseTB, ListTB):'
1067 else:
1081 else:
1068 # We must check the source cache because otherwise we can print
1082 # We must check the source cache because otherwise we can print
1069 # out-of-date source code.
1083 # out-of-date source code.
1070 linecache.checkcache()
1084 self.check_cache()
1071 # Now we can extract and format the exception
1085 # Now we can extract and format the exception
1072 elist = self._extract_tb(tb)
1086 elist = self._extract_tb(tb)
1073 return ListTB.structured_traceback(
1087 return ListTB.structured_traceback(
@@ -97,7 +97,7 b' In [7]: autocall 0'
97 Automatic calling is: OFF
97 Automatic calling is: OFF
98
98
99 In [8]: cos pi
99 In [8]: cos pi
100 File "<ipython console>", line 1
100 File "<ipython-input-8-6bd7313dd9a9>", line 1
101 cos pi
101 cos pi
102 ^
102 ^
103 SyntaxError: invalid syntax
103 SyntaxError: invalid syntax
General Comments 0
You need to be logged in to leave comments. Login now