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: Copyright 2008 by Armin Ronacher.
14 from jinja2.utils import CodeType
17 def translate_exception(exc_info):
18 """If passed an exc_info it will automatically rewrite the exceptions
19 all the way down to the correct line numbers and frames.
21 result_tb = prev_tb = None
22 initial_tb = tb = exc_info[2].tb_next
25 template = tb.tb_frame.f_globals.get('__jinja_template__')
26 if template is not None:
27 lineno = template.get_corresponding_lineno(tb.tb_lineno)
28 tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
35 return exc_info[:2] + (result_tb or initial_tb,)
38 def fake_exc_info(exc_info, filename, lineno, tb_back=None):
39 """Helper for `translate_exception`."""
40 exc_type, exc_value, tb = exc_info
42 # figure the real context out
43 real_locals = tb.tb_frame.f_locals.copy()
44 ctx = real_locals.get('context')
46 locals = ctx.get_all()
49 for name, value in real_locals.iteritems():
50 if name.startswith('l_'):
51 locals[name[2:]] = value
53 # if there is a local called __jinja_exception__, we get
54 # rid of it to not break the debug functionality.
55 locals.pop('__jinja_exception__', None)
57 # assamble fake globals we need
61 '__jinja_exception__': exc_info[:2]
64 # and fake the exception
65 code = compile('\n' * (lineno - 1) + 'raise __jinja_exception__[0], ' +
66 '__jinja_exception__[1]', filename, 'exec')
68 # if it's possible, change the name of the code. This won't work
69 # on some python environments such as google appengine
71 function = tb.tb_frame.f_code.co_name
72 if function == 'root':
73 location = 'top-level template code'
74 elif function.startswith('block_'):
75 location = 'block "%s"' % function[6:]
78 code = CodeType(0, code.co_nlocals, code.co_stacksize,
79 code.co_flags, code.co_code, code.co_consts,
80 code.co_names, code.co_varnames, filename,
81 location, code.co_firstlineno,
82 code.co_lnotab, (), ())
86 # execute the code and catch the new traceback
88 exec code in globals, locals
90 exc_info = sys.exc_info()
91 new_tb = exc_info[2].tb_next
93 # now we can patch the exc info accordingly
94 if tb_set_next is not None:
95 if tb_back is not None:
96 tb_set_next(tb_back, new_tb)
98 tb_set_next(new_tb, tb.tb_next)
100 # return without this frame
101 return exc_info[:2] + (new_tb,)
104 def _init_ugly_crap():
105 """This function implements a few ugly things so that we can patch the
106 traceback objects. The function returned allows resetting `tb_next` on
107 any python traceback object.
110 from types import TracebackType
112 # figure out side of _Py_ssize_t
113 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
114 _Py_ssize_t = ctypes.c_int64
116 _Py_ssize_t = ctypes.c_int
119 class _PyObject(ctypes.Structure):
121 _PyObject._fields_ = [
122 ('ob_refcnt', _Py_ssize_t),
123 ('ob_type', ctypes.POINTER(_PyObject))
127 if object.__basicsize__ != ctypes.sizeof(_PyObject):
128 class _PyObject(ctypes.Structure):
130 _PyObject._fields_ = [
131 ('_ob_next', ctypes.POINTER(_PyObject)),
132 ('_ob_prev', ctypes.POINTER(_PyObject)),
133 ('ob_refcnt', _Py_ssize_t),
134 ('ob_type', ctypes.POINTER(_PyObject))
137 class _Traceback(_PyObject):
139 _Traceback._fields_ = [
140 ('tb_next', ctypes.POINTER(_Traceback)),
141 ('tb_frame', ctypes.POINTER(_PyObject)),
142 ('tb_lasti', ctypes.c_int),
143 ('tb_lineno', ctypes.c_int)
146 def tb_set_next(tb, next):
147 """Set the tb_next attribute of a traceback object."""
148 if not (isinstance(tb, TracebackType) and
149 (next is None or isinstance(next, TracebackType))):
150 raise TypeError('tb_set_next arguments must be traceback objects')
151 obj = _Traceback.from_address(id(tb))
152 if tb.tb_next is not None:
153 old = _Traceback.from_address(id(tb.tb_next))
156 obj.tb_next = ctypes.POINTER(_Traceback)()
158 next = _Traceback.from_address(id(next))
160 obj.tb_next = ctypes.pointer(next)
165 # try to get a tb_set_next implementation
167 from jinja2._speedups import tb_set_next
170 tb_set_next = _init_ugly_crap()