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) 2010 by the Jinja Team.
11 :license: BSD, see LICENSE for more details.
15 from types import TracebackType
16 from jinja2.utils import CodeType, missing, internal_code
17 from jinja2.exceptions import TemplateSyntaxError
19 # on pypy we can take advantage of transparent proxies
21 from __pypy__ import tproxy
26 # how does the raise helper look like?
28 exec "raise TypeError, 'foo'"
30 raise_helper = 'raise __jinja_exception__[1]'
32 raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]'
35 class TracebackFrameProxy(object):
36 """Proxies a traceback frame."""
38 def __init__(self, tb):
46 def set_next(self, next):
47 if tb_set_next is not None:
48 tb_set_next(self.tb, next and next.tb or None)
52 def is_jinja_frame(self):
53 return '__jinja_template__' in self.tb.tb_frame.f_globals
55 def __getattr__(self, name):
56 return getattr(self.tb, name)
59 def make_frame_proxy(frame):
60 proxy = TracebackFrameProxy(frame)
63 def operation_handler(operation, *args, **kwargs):
64 if operation in ('__getattribute__', '__getattr__'):
65 return getattr(proxy, args[0])
66 elif operation == '__setattr__':
67 proxy.__setattr__(*args, **kwargs)
69 return getattr(proxy, operation)(*args, **kwargs)
70 return tproxy(TracebackType, operation_handler)
73 class ProcessedTraceback(object):
74 """Holds a Jinja preprocessed traceback for priting or reraising."""
76 def __init__(self, exc_type, exc_value, frames):
77 assert frames, 'no frames for this traceback?'
78 self.exc_type = exc_type
79 self.exc_value = exc_value
82 # newly concatenate the frames (which are proxies)
84 for tb in self.frames:
85 if prev_tb is not None:
88 prev_tb.set_next(None)
90 def render_as_text(self, limit=None):
91 """Return a string with the traceback."""
92 lines = traceback.format_exception(self.exc_type, self.exc_value,
93 self.frames[0], limit=limit)
94 return ''.join(lines).rstrip()
96 def render_as_html(self, full=False):
97 """Return a unicode string with the traceback as rendered HTML."""
98 from jinja2.debugrenderer import render_traceback
99 return u'%s\n\n<!--\n%s\n-->' % (
100 render_traceback(self, full=full),
101 self.render_as_text().decode('utf-8', 'replace')
105 def is_template_syntax_error(self):
106 """`True` if this is a template syntax error."""
107 return isinstance(self.exc_value, TemplateSyntaxError)
111 """Exception info tuple with a proxy around the frame objects."""
112 return self.exc_type, self.exc_value, self.frames[0]
115 def standard_exc_info(self):
116 """Standard python exc_info for re-raising"""
118 # the frame will be an actual traceback (or transparent proxy) if
119 # we are on pypy or a python implementation with support for tproxy
120 if type(tb) is not TracebackType:
122 return self.exc_type, self.exc_value, tb
125 def make_traceback(exc_info, source_hint=None):
126 """Creates a processed traceback object from the exc_info."""
127 exc_type, exc_value, tb = exc_info
128 if isinstance(exc_value, TemplateSyntaxError):
129 exc_info = translate_syntax_error(exc_value, source_hint)
133 return translate_exception(exc_info, initial_skip)
136 def translate_syntax_error(error, source=None):
137 """Rewrites a syntax error to please traceback systems."""
138 error.source = source
139 error.translated = True
140 exc_info = (error.__class__, error, None)
141 filename = error.filename
143 filename = '<unknown>'
144 return fake_exc_info(exc_info, filename, error.lineno)
147 def translate_exception(exc_info, initial_skip=0):
148 """If passed an exc_info it will automatically rewrite the exceptions
149 all the way down to the correct line numbers and frames.
154 # skip some internal frames if wanted
155 for x in xrange(initial_skip):
160 while tb is not None:
161 # skip frames decorated with @internalcode. These are internal
162 # calls we can't avoid and that are useless in template debugging
164 if tb.tb_frame.f_code in internal_code:
168 # save a reference to the next frame if we override the current
169 # one with a faked one.
172 # fake template exceptions
173 template = tb.tb_frame.f_globals.get('__jinja_template__')
174 if template is not None:
175 lineno = template.get_corresponding_lineno(tb.tb_lineno)
176 tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
179 frames.append(make_frame_proxy(tb))
182 # if we don't have any exceptions in the frames left, we have to
183 # reraise it unchanged.
184 # XXX: can we backup here? when could this happen?
186 raise exc_info[0], exc_info[1], exc_info[2]
188 return ProcessedTraceback(exc_info[0], exc_info[1], frames)
191 def fake_exc_info(exc_info, filename, lineno):
192 """Helper for `translate_exception`."""
193 exc_type, exc_value, tb = exc_info
195 # figure the real context out
197 real_locals = tb.tb_frame.f_locals.copy()
198 ctx = real_locals.get('context')
200 locals = ctx.get_all()
203 for name, value in real_locals.iteritems():
204 if name.startswith('l_') and value is not missing:
205 locals[name[2:]] = value
207 # if there is a local called __jinja_exception__, we get
208 # rid of it to not break the debug functionality.
209 locals.pop('__jinja_exception__', None)
213 # assamble fake globals we need
215 '__name__': filename,
216 '__file__': filename,
217 '__jinja_exception__': exc_info[:2],
219 # we don't want to keep the reference to the template around
220 # to not cause circular dependencies, but we mark it as Jinja
221 # frame for the ProcessedTraceback
222 '__jinja_template__': None
225 # and fake the exception
226 code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec')
228 # if it's possible, change the name of the code. This won't work
229 # on some python environments such as google appengine
232 location = 'template'
234 function = tb.tb_frame.f_code.co_name
235 if function == 'root':
236 location = 'top-level template code'
237 elif function.startswith('block_'):
238 location = 'block "%s"' % function[6:]
240 location = 'template'
241 code = CodeType(0, code.co_nlocals, code.co_stacksize,
242 code.co_flags, code.co_code, code.co_consts,
243 code.co_names, code.co_varnames, filename,
244 location, code.co_firstlineno,
245 code.co_lnotab, (), ())
249 # execute the code and catch the new traceback
251 exec code in globals, locals
253 exc_info = sys.exc_info()
254 new_tb = exc_info[2].tb_next
256 # return without this frame
257 return exc_info[:2] + (new_tb,)
260 def _init_ugly_crap():
261 """This function implements a few ugly things so that we can patch the
262 traceback objects. The function returned allows resetting `tb_next` on
263 any python traceback object. Do not attempt to use this on non cpython
267 from types import TracebackType
269 # figure out side of _Py_ssize_t
270 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
271 _Py_ssize_t = ctypes.c_int64
273 _Py_ssize_t = ctypes.c_int
276 class _PyObject(ctypes.Structure):
278 _PyObject._fields_ = [
279 ('ob_refcnt', _Py_ssize_t),
280 ('ob_type', ctypes.POINTER(_PyObject))
284 if hasattr(sys, 'getobjects'):
285 class _PyObject(ctypes.Structure):
287 _PyObject._fields_ = [
288 ('_ob_next', ctypes.POINTER(_PyObject)),
289 ('_ob_prev', ctypes.POINTER(_PyObject)),
290 ('ob_refcnt', _Py_ssize_t),
291 ('ob_type', ctypes.POINTER(_PyObject))
294 class _Traceback(_PyObject):
296 _Traceback._fields_ = [
297 ('tb_next', ctypes.POINTER(_Traceback)),
298 ('tb_frame', ctypes.POINTER(_PyObject)),
299 ('tb_lasti', ctypes.c_int),
300 ('tb_lineno', ctypes.c_int)
303 def tb_set_next(tb, next):
304 """Set the tb_next attribute of a traceback object."""
305 if not (isinstance(tb, TracebackType) and
306 (next is None or isinstance(next, TracebackType))):
307 raise TypeError('tb_set_next arguments must be traceback objects')
308 obj = _Traceback.from_address(id(tb))
309 if tb.tb_next is not None:
310 old = _Traceback.from_address(id(tb.tb_next))
313 obj.tb_next = ctypes.POINTER(_Traceback)()
315 next = _Traceback.from_address(id(next))
317 obj.tb_next = ctypes.pointer(next)
322 # try to get a tb_set_next implementation if we don't have transparent
327 from jinja2._debugsupport import tb_set_next
330 tb_set_next = _init_ugly_crap()