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.
14 from jinja2.utils import CodeType, missing, internal_code
17 def translate_syntax_error(error, source=None):
18 """Rewrites a syntax error to please traceback systems."""
20 error.translated = True
21 exc_info = (type(error), error, None)
22 filename = error.filename
24 filename = '<unknown>'
25 return fake_exc_info(exc_info, filename, error.lineno)
28 def translate_exception(exc_info):
29 """If passed an exc_info it will automatically rewrite the exceptions
30 all the way down to the correct line numbers and frames.
32 result_tb = prev_tb = None
33 initial_tb = tb = exc_info[2].tb_next
36 # skip frames decorated with @internalcode. These are internal
37 # calls we can't avoid and that are useless in template debugging
39 if tb_set_next is not None and tb.tb_frame.f_code in internal_code:
40 tb_set_next(prev_tb, tb.tb_next)
44 template = tb.tb_frame.f_globals.get('__jinja_template__')
45 if template is not None:
46 lineno = template.get_corresponding_lineno(tb.tb_lineno)
47 tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
54 return exc_info[:2] + (result_tb or initial_tb,)
57 def fake_exc_info(exc_info, filename, lineno, tb_back=None):
58 """Helper for `translate_exception`."""
59 exc_type, exc_value, tb = exc_info
61 # figure the real context out
63 real_locals = tb.tb_frame.f_locals.copy()
64 ctx = real_locals.get('context')
66 locals = ctx.get_all()
69 for name, value in real_locals.iteritems():
70 if name.startswith('l_') and value is not missing:
71 locals[name[2:]] = value
73 # if there is a local called __jinja_exception__, we get
74 # rid of it to not break the debug functionality.
75 locals.pop('__jinja_exception__', None)
79 # assamble fake globals we need
83 '__jinja_exception__': exc_info[:2]
86 # and fake the exception
87 code = compile('\n' * (lineno - 1) + 'raise __jinja_exception__[0], ' +
88 '__jinja_exception__[1]', filename, 'exec')
90 # if it's possible, change the name of the code. This won't work
91 # on some python environments such as google appengine
96 function = tb.tb_frame.f_code.co_name
97 if function == 'root':
98 location = 'top-level template code'
99 elif function.startswith('block_'):
100 location = 'block "%s"' % function[6:]
102 location = 'template'
103 code = CodeType(0, code.co_nlocals, code.co_stacksize,
104 code.co_flags, code.co_code, code.co_consts,
105 code.co_names, code.co_varnames, filename,
106 location, code.co_firstlineno,
107 code.co_lnotab, (), ())
111 # execute the code and catch the new traceback
113 exec code in globals, locals
115 exc_info = sys.exc_info()
116 new_tb = exc_info[2].tb_next
118 # now we can patch the exc info accordingly
119 if tb_set_next is not None:
120 if tb_back is not None:
121 tb_set_next(tb_back, new_tb)
123 tb_set_next(new_tb, tb.tb_next)
125 # return without this frame
126 return exc_info[:2] + (new_tb,)
129 def _init_ugly_crap():
130 """This function implements a few ugly things so that we can patch the
131 traceback objects. The function returned allows resetting `tb_next` on
132 any python traceback object.
135 from types import TracebackType
137 # figure out side of _Py_ssize_t
138 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
139 _Py_ssize_t = ctypes.c_int64
141 _Py_ssize_t = ctypes.c_int
144 class _PyObject(ctypes.Structure):
146 _PyObject._fields_ = [
147 ('ob_refcnt', _Py_ssize_t),
148 ('ob_type', ctypes.POINTER(_PyObject))
152 if object.__basicsize__ != ctypes.sizeof(_PyObject):
153 class _PyObject(ctypes.Structure):
155 _PyObject._fields_ = [
156 ('_ob_next', ctypes.POINTER(_PyObject)),
157 ('_ob_prev', ctypes.POINTER(_PyObject)),
158 ('ob_refcnt', _Py_ssize_t),
159 ('ob_type', ctypes.POINTER(_PyObject))
162 class _Traceback(_PyObject):
164 _Traceback._fields_ = [
165 ('tb_next', ctypes.POINTER(_Traceback)),
166 ('tb_frame', ctypes.POINTER(_PyObject)),
167 ('tb_lasti', ctypes.c_int),
168 ('tb_lineno', ctypes.c_int)
171 def tb_set_next(tb, next):
172 """Set the tb_next attribute of a traceback object."""
173 if not (isinstance(tb, TracebackType) and
174 (next is None or isinstance(next, TracebackType))):
175 raise TypeError('tb_set_next arguments must be traceback objects')
176 obj = _Traceback.from_address(id(tb))
177 if tb.tb_next is not None:
178 old = _Traceback.from_address(id(tb.tb_next))
181 obj.tb_next = ctypes.POINTER(_Traceback)()
183 next = _Traceback.from_address(id(next))
185 obj.tb_next = ctypes.pointer(next)
190 # try to get a tb_set_next implementation
192 from jinja2._speedups import tb_set_next
195 tb_set_next = _init_ugly_crap()