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()