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 missing, internal_code
17
from jinja2.exceptions import TemplateSyntaxError
18
from jinja2._compat import iteritems, reraise, code_type
20
# on pypy we can take advantage of transparent proxies
22
from __pypy__ import tproxy
27
# how does the raise helper look like?
29
exec("raise TypeError, 'foo'")
31
raise_helper = 'raise __jinja_exception__[1]'
33
raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]'
36
class TracebackFrameProxy(object):
37
"""Proxies a traceback frame."""
39
def __init__(self, tb):
47
def set_next(self, next):
48
if tb_set_next is not None:
50
tb_set_next(self.tb, next and next.tb or None)
52
# this function can fail due to all the hackery it does
53
# on various python implementations. We just catch errors
54
# down and ignore them if necessary.
59
def is_jinja_frame(self):
60
return '__jinja_template__' in self.tb.tb_frame.f_globals
62
def __getattr__(self, name):
63
return getattr(self.tb, name)
66
def make_frame_proxy(frame):
67
proxy = TracebackFrameProxy(frame)
70
def operation_handler(operation, *args, **kwargs):
71
if operation in ('__getattribute__', '__getattr__'):
72
return getattr(proxy, args[0])
73
elif operation == '__setattr__':
74
proxy.__setattr__(*args, **kwargs)
76
return getattr(proxy, operation)(*args, **kwargs)
77
return tproxy(TracebackType, operation_handler)
80
class ProcessedTraceback(object):
81
"""Holds a Jinja preprocessed traceback for printing or reraising."""
83
def __init__(self, exc_type, exc_value, frames):
84
assert frames, 'no frames for this traceback?'
85
self.exc_type = exc_type
86
self.exc_value = exc_value
89
# newly concatenate the frames (which are proxies)
91
for tb in self.frames:
92
if prev_tb is not None:
95
prev_tb.set_next(None)
97
def render_as_text(self, limit=None):
98
"""Return a string with the traceback."""
99
lines = traceback.format_exception(self.exc_type, self.exc_value,
100
self.frames[0], limit=limit)
101
return ''.join(lines).rstrip()
103
def render_as_html(self, full=False):
104
"""Return a unicode string with the traceback as rendered HTML."""
105
from jinja2.debugrenderer import render_traceback
106
return u'%s\n\n<!--\n%s\n-->' % (
107
render_traceback(self, full=full),
108
self.render_as_text().decode('utf-8', 'replace')
112
def is_template_syntax_error(self):
113
"""`True` if this is a template syntax error."""
114
return isinstance(self.exc_value, TemplateSyntaxError)
118
"""Exception info tuple with a proxy around the frame objects."""
119
return self.exc_type, self.exc_value, self.frames[0]
122
def standard_exc_info(self):
123
"""Standard python exc_info for re-raising"""
125
# the frame will be an actual traceback (or transparent proxy) if
126
# we are on pypy or a python implementation with support for tproxy
127
if type(tb) is not TracebackType:
129
return self.exc_type, self.exc_value, tb
132
def make_traceback(exc_info, source_hint=None):
133
"""Creates a processed traceback object from the exc_info."""
134
exc_type, exc_value, tb = exc_info
135
if isinstance(exc_value, TemplateSyntaxError):
136
exc_info = translate_syntax_error(exc_value, source_hint)
140
return translate_exception(exc_info, initial_skip)
143
def translate_syntax_error(error, source=None):
144
"""Rewrites a syntax error to please traceback systems."""
145
error.source = source
146
error.translated = True
147
exc_info = (error.__class__, error, None)
148
filename = error.filename
150
filename = '<unknown>'
151
return fake_exc_info(exc_info, filename, error.lineno)
154
def translate_exception(exc_info, initial_skip=0):
155
"""If passed an exc_info it will automatically rewrite the exceptions
156
all the way down to the correct line numbers and frames.
161
# skip some internal frames if wanted
162
for x in range(initial_skip):
167
while tb is not None:
168
# skip frames decorated with @internalcode. These are internal
169
# calls we can't avoid and that are useless in template debugging
171
if tb.tb_frame.f_code in internal_code:
175
# save a reference to the next frame if we override the current
176
# one with a faked one.
179
# fake template exceptions
180
template = tb.tb_frame.f_globals.get('__jinja_template__')
181
if template is not None:
182
lineno = template.get_corresponding_lineno(tb.tb_lineno)
183
tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
186
frames.append(make_frame_proxy(tb))
189
# if we don't have any exceptions in the frames left, we have to
190
# reraise it unchanged.
191
# XXX: can we backup here? when could this happen?
193
reraise(exc_info[0], exc_info[1], exc_info[2])
195
return ProcessedTraceback(exc_info[0], exc_info[1], frames)
198
def fake_exc_info(exc_info, filename, lineno):
199
"""Helper for `translate_exception`."""
200
exc_type, exc_value, tb = exc_info
202
# figure the real context out
204
real_locals = tb.tb_frame.f_locals.copy()
205
ctx = real_locals.get('context')
207
locals = ctx.get_all()
210
for name, value in iteritems(real_locals):
211
if name.startswith('l_') and value is not missing:
212
locals[name[2:]] = value
214
# if there is a local called __jinja_exception__, we get
215
# rid of it to not break the debug functionality.
216
locals.pop('__jinja_exception__', None)
220
# assamble fake globals we need
222
'__name__': filename,
223
'__file__': filename,
224
'__jinja_exception__': exc_info[:2],
226
# we don't want to keep the reference to the template around
227
# to not cause circular dependencies, but we mark it as Jinja
228
# frame for the ProcessedTraceback
229
'__jinja_template__': None
232
# and fake the exception
233
code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec')
235
# if it's possible, change the name of the code. This won't work
236
# on some python environments such as google appengine
239
location = 'template'
241
function = tb.tb_frame.f_code.co_name
242
if function == 'root':
243
location = 'top-level template code'
244
elif function.startswith('block_'):
245
location = 'block "%s"' % function[6:]
247
location = 'template'
248
code = code_type(0, code.co_nlocals, code.co_stacksize,
249
code.co_flags, code.co_code, code.co_consts,
250
code.co_names, code.co_varnames, filename,
251
location, code.co_firstlineno,
252
code.co_lnotab, (), ())
256
# execute the code and catch the new traceback
258
exec(code, globals, locals)
260
exc_info = sys.exc_info()
261
new_tb = exc_info[2].tb_next
263
# return without this frame
264
return exc_info[:2] + (new_tb,)
267
def _init_ugly_crap():
268
"""This function implements a few ugly things so that we can patch the
269
traceback objects. The function returned allows resetting `tb_next` on
270
any python traceback object. Do not attempt to use this on non cpython
274
from types import TracebackType
276
# figure out side of _Py_ssize_t
277
if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
278
_Py_ssize_t = ctypes.c_int64
280
_Py_ssize_t = ctypes.c_int
283
class _PyObject(ctypes.Structure):
285
_PyObject._fields_ = [
286
('ob_refcnt', _Py_ssize_t),
287
('ob_type', ctypes.POINTER(_PyObject))
291
if hasattr(sys, 'getobjects'):
292
class _PyObject(ctypes.Structure):
294
_PyObject._fields_ = [
295
('_ob_next', ctypes.POINTER(_PyObject)),
296
('_ob_prev', ctypes.POINTER(_PyObject)),
297
('ob_refcnt', _Py_ssize_t),
298
('ob_type', ctypes.POINTER(_PyObject))
301
class _Traceback(_PyObject):
303
_Traceback._fields_ = [
304
('tb_next', ctypes.POINTER(_Traceback)),
305
('tb_frame', ctypes.POINTER(_PyObject)),
306
('tb_lasti', ctypes.c_int),
307
('tb_lineno', ctypes.c_int)
310
def tb_set_next(tb, next):
311
"""Set the tb_next attribute of a traceback object."""
312
if not (isinstance(tb, TracebackType) and
313
(next is None or isinstance(next, TracebackType))):
314
raise TypeError('tb_set_next arguments must be traceback objects')
315
obj = _Traceback.from_address(id(tb))
316
if tb.tb_next is not None:
317
old = _Traceback.from_address(id(tb.tb_next))
320
obj.tb_next = ctypes.POINTER(_Traceback)()
322
next = _Traceback.from_address(id(next))
324
obj.tb_next = ctypes.pointer(next)
329
# try to get a tb_set_next implementation if we don't have transparent
334
tb_set_next = _init_ugly_crap()