1 # -*- coding: utf-8 -*-
6 Implements the debug interface for Jinja. This module does some pretty
7 ugly stuff with the Python traceback system in order to achieve tracebacks
8 with correct line numbers, locals and contents.
10 :copyright: (c) 2009 by the Jinja Team.
11 :license: BSD, see LICENSE for more details.
15 from jinja2.utils import CodeType, missing, internal_code
16 from jinja2.exceptions import TemplateSyntaxError
19 class TracebackFrameProxy(object):
20 """Proxies a traceback frame."""
22 def __init__(self, tb):
25 def _set_tb_next(self, next):
26 if tb_set_next is not None:
27 tb_set_next(self.tb, next and next.tb or None)
30 def _get_tb_next(self):
33 tb_next = property(_get_tb_next, _set_tb_next)
34 del _get_tb_next, _set_tb_next
37 def is_jinja_frame(self):
38 return '__jinja_template__' in self.tb.tb_frame.f_globals
40 def __getattr__(self, name):
41 return getattr(self.tb, name)
44 class ProcessedTraceback(object):
45 """Holds a Jinja preprocessed traceback for priting or reraising."""
47 def __init__(self, exc_type, exc_value, frames):
48 assert frames, 'no frames for this traceback?'
49 self.exc_type = exc_type
50 self.exc_value = exc_value
53 def chain_frames(self):
54 """Chains the frames. Requires ctypes or the speedups extension."""
56 for tb in self.frames:
57 if prev_tb is not None:
60 prev_tb.tb_next = None
62 def render_as_text(self, limit=None):
63 """Return a string with the traceback."""
64 lines = traceback.format_exception(self.exc_type, self.exc_value,
65 self.frames[0], limit=limit)
66 return ''.join(lines).rstrip()
69 def is_template_syntax_error(self):
70 """`True` if this is a template syntax error."""
71 return isinstance(self.exc_value, TemplateSyntaxError)
75 """Exception info tuple with a proxy around the frame objects."""
76 return self.exc_type, self.exc_value, self.frames[0]
79 def standard_exc_info(self):
80 """Standard python exc_info for re-raising"""
81 return self.exc_type, self.exc_value, self.frames[0].tb
84 def make_traceback(exc_info, source_hint=None):
85 """Creates a processed traceback object from the exc_info."""
86 exc_type, exc_value, tb = exc_info
87 if isinstance(exc_value, TemplateSyntaxError):
88 exc_info = translate_syntax_error(exc_value, source_hint)
89 return translate_exception(exc_info)
92 def translate_syntax_error(error, source=None):
93 """Rewrites a syntax error to please traceback systems."""
95 error.translated = True
96 exc_info = (type(error), error, None)
97 filename = error.filename
99 filename = '<unknown>'
100 return fake_exc_info(exc_info, filename, error.lineno)
103 def translate_exception(exc_info):
104 """If passed an exc_info it will automatically rewrite the exceptions
105 all the way down to the correct line numbers and frames.
107 initial_tb = tb = exc_info[2].tb_next
110 while tb is not None:
111 # skip frames decorated with @internalcode. These are internal
112 # calls we can't avoid and that are useless in template debugging
114 if tb.tb_frame.f_code in internal_code:
118 # save a reference to the next frame if we override the current
119 # one with a faked one.
122 # fake template exceptions
123 template = tb.tb_frame.f_globals.get('__jinja_template__')
124 if template is not None:
125 lineno = template.get_corresponding_lineno(tb.tb_lineno)
126 tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
129 frames.append(TracebackFrameProxy(tb))
132 # if we don't have any exceptions in the frames left, we have to
133 # reraise it unchanged.
134 # XXX: can we backup here? when could this happen?
136 raise exc_info[0], exc_info[1], exc_info[2]
138 traceback = ProcessedTraceback(exc_info[0], exc_info[1], frames)
139 if tb_set_next is not None:
140 traceback.chain_frames()
144 def fake_exc_info(exc_info, filename, lineno):
145 """Helper for `translate_exception`."""
146 exc_type, exc_value, tb = exc_info
148 # figure the real context out
150 real_locals = tb.tb_frame.f_locals.copy()
151 ctx = real_locals.get('context')
153 locals = ctx.get_all()
156 for name, value in real_locals.iteritems():
157 if name.startswith('l_') and value is not missing:
158 locals[name[2:]] = value
160 # if there is a local called __jinja_exception__, we get
161 # rid of it to not break the debug functionality.
162 locals.pop('__jinja_exception__', None)
166 # assamble fake globals we need
168 '__name__': filename,
169 '__file__': filename,
170 '__jinja_exception__': exc_info[:2]
173 # and fake the exception
174 code = compile('\n' * (lineno - 1) + 'raise __jinja_exception__[0], ' +
175 '__jinja_exception__[1]', filename, 'exec')
177 # if it's possible, change the name of the code. This won't work
178 # on some python environments such as google appengine
181 location = 'template'
183 function = tb.tb_frame.f_code.co_name
184 if function == 'root':
185 location = 'top-level template code'
186 elif function.startswith('block_'):
187 location = 'block "%s"' % function[6:]
189 location = 'template'
190 code = CodeType(0, code.co_nlocals, code.co_stacksize,
191 code.co_flags, code.co_code, code.co_consts,
192 code.co_names, code.co_varnames, filename,
193 location, code.co_firstlineno,
194 code.co_lnotab, (), ())
198 # execute the code and catch the new traceback
200 exec code in globals, locals
202 exc_info = sys.exc_info()
203 new_tb = exc_info[2].tb_next
205 # return without this frame
206 return exc_info[:2] + (new_tb,)
209 def _init_ugly_crap():
210 """This function implements a few ugly things so that we can patch the
211 traceback objects. The function returned allows resetting `tb_next` on
212 any python traceback object.
215 from types import TracebackType
217 # figure out side of _Py_ssize_t
218 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
219 _Py_ssize_t = ctypes.c_int64
221 _Py_ssize_t = ctypes.c_int
224 class _PyObject(ctypes.Structure):
226 _PyObject._fields_ = [
227 ('ob_refcnt', _Py_ssize_t),
228 ('ob_type', ctypes.POINTER(_PyObject))
232 if object.__basicsize__ != ctypes.sizeof(_PyObject):
233 class _PyObject(ctypes.Structure):
235 _PyObject._fields_ = [
236 ('_ob_next', ctypes.POINTER(_PyObject)),
237 ('_ob_prev', ctypes.POINTER(_PyObject)),
238 ('ob_refcnt', _Py_ssize_t),
239 ('ob_type', ctypes.POINTER(_PyObject))
242 class _Traceback(_PyObject):
244 _Traceback._fields_ = [
245 ('tb_next', ctypes.POINTER(_Traceback)),
246 ('tb_frame', ctypes.POINTER(_PyObject)),
247 ('tb_lasti', ctypes.c_int),
248 ('tb_lineno', ctypes.c_int)
251 def tb_set_next(tb, next):
252 """Set the tb_next attribute of a traceback object."""
253 if not (isinstance(tb, TracebackType) and
254 (next is None or isinstance(next, TracebackType))):
255 raise TypeError('tb_set_next arguments must be traceback objects')
256 obj = _Traceback.from_address(id(tb))
257 if tb.tb_next is not None:
258 old = _Traceback.from_address(id(tb.tb_next))
261 obj.tb_next = ctypes.POINTER(_Traceback)()
263 next = _Traceback.from_address(id(next))
265 obj.tb_next = ctypes.pointer(next)
270 # try to get a tb_set_next implementation
272 from jinja2._speedups import tb_set_next
275 tb_set_next = _init_ugly_crap()