OLD | NEW |
(Empty) | |
| 1 # -*- coding: utf-8 -*- |
| 2 """ |
| 3 jinja2.debug |
| 4 ~~~~~~~~~~~~ |
| 5 |
| 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. |
| 9 |
| 10 :copyright: (c) 2010 by the Jinja Team. |
| 11 :license: BSD, see LICENSE for more details. |
| 12 """ |
| 13 import sys |
| 14 import traceback |
| 15 from types import TracebackType |
| 16 from jinja2.utils import CodeType, missing, internal_code |
| 17 from jinja2.exceptions import TemplateSyntaxError |
| 18 |
| 19 # on pypy we can take advantage of transparent proxies |
| 20 try: |
| 21 from __pypy__ import tproxy |
| 22 except ImportError: |
| 23 tproxy = None |
| 24 |
| 25 |
| 26 # how does the raise helper look like? |
| 27 try: |
| 28 exec "raise TypeError, 'foo'" |
| 29 except SyntaxError: |
| 30 raise_helper = 'raise __jinja_exception__[1]' |
| 31 except TypeError: |
| 32 raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]' |
| 33 |
| 34 |
| 35 class TracebackFrameProxy(object): |
| 36 """Proxies a traceback frame.""" |
| 37 |
| 38 def __init__(self, tb): |
| 39 self.tb = tb |
| 40 self._tb_next = None |
| 41 |
| 42 @property |
| 43 def tb_next(self): |
| 44 return self._tb_next |
| 45 |
| 46 def set_next(self, next): |
| 47 if tb_set_next is not None: |
| 48 try: |
| 49 tb_set_next(self.tb, next and next.tb or None) |
| 50 except Exception: |
| 51 # this function can fail due to all the hackery it does |
| 52 # on various python implementations. We just catch errors |
| 53 # down and ignore them if necessary. |
| 54 pass |
| 55 self._tb_next = next |
| 56 |
| 57 @property |
| 58 def is_jinja_frame(self): |
| 59 return '__jinja_template__' in self.tb.tb_frame.f_globals |
| 60 |
| 61 def __getattr__(self, name): |
| 62 return getattr(self.tb, name) |
| 63 |
| 64 |
| 65 def make_frame_proxy(frame): |
| 66 proxy = TracebackFrameProxy(frame) |
| 67 if tproxy is None: |
| 68 return proxy |
| 69 def operation_handler(operation, *args, **kwargs): |
| 70 if operation in ('__getattribute__', '__getattr__'): |
| 71 return getattr(proxy, args[0]) |
| 72 elif operation == '__setattr__': |
| 73 proxy.__setattr__(*args, **kwargs) |
| 74 else: |
| 75 return getattr(proxy, operation)(*args, **kwargs) |
| 76 return tproxy(TracebackType, operation_handler) |
| 77 |
| 78 |
| 79 class ProcessedTraceback(object): |
| 80 """Holds a Jinja preprocessed traceback for priting or reraising.""" |
| 81 |
| 82 def __init__(self, exc_type, exc_value, frames): |
| 83 assert frames, 'no frames for this traceback?' |
| 84 self.exc_type = exc_type |
| 85 self.exc_value = exc_value |
| 86 self.frames = frames |
| 87 |
| 88 # newly concatenate the frames (which are proxies) |
| 89 prev_tb = None |
| 90 for tb in self.frames: |
| 91 if prev_tb is not None: |
| 92 prev_tb.set_next(tb) |
| 93 prev_tb = tb |
| 94 prev_tb.set_next(None) |
| 95 |
| 96 def render_as_text(self, limit=None): |
| 97 """Return a string with the traceback.""" |
| 98 lines = traceback.format_exception(self.exc_type, self.exc_value, |
| 99 self.frames[0], limit=limit) |
| 100 return ''.join(lines).rstrip() |
| 101 |
| 102 def render_as_html(self, full=False): |
| 103 """Return a unicode string with the traceback as rendered HTML.""" |
| 104 from jinja2.debugrenderer import render_traceback |
| 105 return u'%s\n\n<!--\n%s\n-->' % ( |
| 106 render_traceback(self, full=full), |
| 107 self.render_as_text().decode('utf-8', 'replace') |
| 108 ) |
| 109 |
| 110 @property |
| 111 def is_template_syntax_error(self): |
| 112 """`True` if this is a template syntax error.""" |
| 113 return isinstance(self.exc_value, TemplateSyntaxError) |
| 114 |
| 115 @property |
| 116 def exc_info(self): |
| 117 """Exception info tuple with a proxy around the frame objects.""" |
| 118 return self.exc_type, self.exc_value, self.frames[0] |
| 119 |
| 120 @property |
| 121 def standard_exc_info(self): |
| 122 """Standard python exc_info for re-raising""" |
| 123 tb = self.frames[0] |
| 124 # the frame will be an actual traceback (or transparent proxy) if |
| 125 # we are on pypy or a python implementation with support for tproxy |
| 126 if type(tb) is not TracebackType: |
| 127 tb = tb.tb |
| 128 return self.exc_type, self.exc_value, tb |
| 129 |
| 130 |
| 131 def make_traceback(exc_info, source_hint=None): |
| 132 """Creates a processed traceback object from the exc_info.""" |
| 133 exc_type, exc_value, tb = exc_info |
| 134 if isinstance(exc_value, TemplateSyntaxError): |
| 135 exc_info = translate_syntax_error(exc_value, source_hint) |
| 136 initial_skip = 0 |
| 137 else: |
| 138 initial_skip = 1 |
| 139 return translate_exception(exc_info, initial_skip) |
| 140 |
| 141 |
| 142 def translate_syntax_error(error, source=None): |
| 143 """Rewrites a syntax error to please traceback systems.""" |
| 144 error.source = source |
| 145 error.translated = True |
| 146 exc_info = (error.__class__, error, None) |
| 147 filename = error.filename |
| 148 if filename is None: |
| 149 filename = '<unknown>' |
| 150 return fake_exc_info(exc_info, filename, error.lineno) |
| 151 |
| 152 |
| 153 def translate_exception(exc_info, initial_skip=0): |
| 154 """If passed an exc_info it will automatically rewrite the exceptions |
| 155 all the way down to the correct line numbers and frames. |
| 156 """ |
| 157 tb = exc_info[2] |
| 158 frames = [] |
| 159 |
| 160 # skip some internal frames if wanted |
| 161 for x in xrange(initial_skip): |
| 162 if tb is not None: |
| 163 tb = tb.tb_next |
| 164 initial_tb = tb |
| 165 |
| 166 while tb is not None: |
| 167 # skip frames decorated with @internalcode. These are internal |
| 168 # calls we can't avoid and that are useless in template debugging |
| 169 # output. |
| 170 if tb.tb_frame.f_code in internal_code: |
| 171 tb = tb.tb_next |
| 172 continue |
| 173 |
| 174 # save a reference to the next frame if we override the current |
| 175 # one with a faked one. |
| 176 next = tb.tb_next |
| 177 |
| 178 # fake template exceptions |
| 179 template = tb.tb_frame.f_globals.get('__jinja_template__') |
| 180 if template is not None: |
| 181 lineno = template.get_corresponding_lineno(tb.tb_lineno) |
| 182 tb = fake_exc_info(exc_info[:2] + (tb,), template.filename, |
| 183 lineno)[2] |
| 184 |
| 185 frames.append(make_frame_proxy(tb)) |
| 186 tb = next |
| 187 |
| 188 # if we don't have any exceptions in the frames left, we have to |
| 189 # reraise it unchanged. |
| 190 # XXX: can we backup here? when could this happen? |
| 191 if not frames: |
| 192 raise exc_info[0], exc_info[1], exc_info[2] |
| 193 |
| 194 return ProcessedTraceback(exc_info[0], exc_info[1], frames) |
| 195 |
| 196 |
| 197 def fake_exc_info(exc_info, filename, lineno): |
| 198 """Helper for `translate_exception`.""" |
| 199 exc_type, exc_value, tb = exc_info |
| 200 |
| 201 # figure the real context out |
| 202 if tb is not None: |
| 203 real_locals = tb.tb_frame.f_locals.copy() |
| 204 ctx = real_locals.get('context') |
| 205 if ctx: |
| 206 locals = ctx.get_all() |
| 207 else: |
| 208 locals = {} |
| 209 for name, value in real_locals.iteritems(): |
| 210 if name.startswith('l_') and value is not missing: |
| 211 locals[name[2:]] = value |
| 212 |
| 213 # if there is a local called __jinja_exception__, we get |
| 214 # rid of it to not break the debug functionality. |
| 215 locals.pop('__jinja_exception__', None) |
| 216 else: |
| 217 locals = {} |
| 218 |
| 219 # assamble fake globals we need |
| 220 globals = { |
| 221 '__name__': filename, |
| 222 '__file__': filename, |
| 223 '__jinja_exception__': exc_info[:2], |
| 224 |
| 225 # we don't want to keep the reference to the template around |
| 226 # to not cause circular dependencies, but we mark it as Jinja |
| 227 # frame for the ProcessedTraceback |
| 228 '__jinja_template__': None |
| 229 } |
| 230 |
| 231 # and fake the exception |
| 232 code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec') |
| 233 |
| 234 # if it's possible, change the name of the code. This won't work |
| 235 # on some python environments such as google appengine |
| 236 try: |
| 237 if tb is None: |
| 238 location = 'template' |
| 239 else: |
| 240 function = tb.tb_frame.f_code.co_name |
| 241 if function == 'root': |
| 242 location = 'top-level template code' |
| 243 elif function.startswith('block_'): |
| 244 location = 'block "%s"' % function[6:] |
| 245 else: |
| 246 location = 'template' |
| 247 code = CodeType(0, code.co_nlocals, code.co_stacksize, |
| 248 code.co_flags, code.co_code, code.co_consts, |
| 249 code.co_names, code.co_varnames, filename, |
| 250 location, code.co_firstlineno, |
| 251 code.co_lnotab, (), ()) |
| 252 except: |
| 253 pass |
| 254 |
| 255 # execute the code and catch the new traceback |
| 256 try: |
| 257 exec code in globals, locals |
| 258 except: |
| 259 exc_info = sys.exc_info() |
| 260 new_tb = exc_info[2].tb_next |
| 261 |
| 262 # return without this frame |
| 263 return exc_info[:2] + (new_tb,) |
| 264 |
| 265 |
| 266 def _init_ugly_crap(): |
| 267 """This function implements a few ugly things so that we can patch the |
| 268 traceback objects. The function returned allows resetting `tb_next` on |
| 269 any python traceback object. Do not attempt to use this on non cpython |
| 270 interpreters |
| 271 """ |
| 272 import ctypes |
| 273 from types import TracebackType |
| 274 |
| 275 # figure out side of _Py_ssize_t |
| 276 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'): |
| 277 _Py_ssize_t = ctypes.c_int64 |
| 278 else: |
| 279 _Py_ssize_t = ctypes.c_int |
| 280 |
| 281 # regular python |
| 282 class _PyObject(ctypes.Structure): |
| 283 pass |
| 284 _PyObject._fields_ = [ |
| 285 ('ob_refcnt', _Py_ssize_t), |
| 286 ('ob_type', ctypes.POINTER(_PyObject)) |
| 287 ] |
| 288 |
| 289 # python with trace |
| 290 if hasattr(sys, 'getobjects'): |
| 291 class _PyObject(ctypes.Structure): |
| 292 pass |
| 293 _PyObject._fields_ = [ |
| 294 ('_ob_next', ctypes.POINTER(_PyObject)), |
| 295 ('_ob_prev', ctypes.POINTER(_PyObject)), |
| 296 ('ob_refcnt', _Py_ssize_t), |
| 297 ('ob_type', ctypes.POINTER(_PyObject)) |
| 298 ] |
| 299 |
| 300 class _Traceback(_PyObject): |
| 301 pass |
| 302 _Traceback._fields_ = [ |
| 303 ('tb_next', ctypes.POINTER(_Traceback)), |
| 304 ('tb_frame', ctypes.POINTER(_PyObject)), |
| 305 ('tb_lasti', ctypes.c_int), |
| 306 ('tb_lineno', ctypes.c_int) |
| 307 ] |
| 308 |
| 309 def tb_set_next(tb, next): |
| 310 """Set the tb_next attribute of a traceback object.""" |
| 311 if not (isinstance(tb, TracebackType) and |
| 312 (next is None or isinstance(next, TracebackType))): |
| 313 raise TypeError('tb_set_next arguments must be traceback objects') |
| 314 obj = _Traceback.from_address(id(tb)) |
| 315 if tb.tb_next is not None: |
| 316 old = _Traceback.from_address(id(tb.tb_next)) |
| 317 old.ob_refcnt -= 1 |
| 318 if next is None: |
| 319 obj.tb_next = ctypes.POINTER(_Traceback)() |
| 320 else: |
| 321 next = _Traceback.from_address(id(next)) |
| 322 next.ob_refcnt += 1 |
| 323 obj.tb_next = ctypes.pointer(next) |
| 324 |
| 325 return tb_set_next |
| 326 |
| 327 |
| 328 # try to get a tb_set_next implementation if we don't have transparent |
| 329 # proxies. |
| 330 tb_set_next = None |
| 331 if tproxy is None: |
| 332 try: |
| 333 from jinja2._debugsupport import tb_set_next |
| 334 except ImportError: |
| 335 try: |
| 336 tb_set_next = _init_ugly_crap() |
| 337 except: |
| 338 pass |
| 339 del _init_ugly_crap |
OLD | NEW |