with correct line numbers, locals and contents.
:copyright: (c) 2009 by the Jinja Team.
- :license: BSD.
+ :license: BSD, see LICENSE for more details.
"""
import sys
+import traceback
from jinja2.utils import CodeType, missing, internal_code
+from jinja2.exceptions import TemplateSyntaxError
+
+
+class TracebackFrameProxy(object):
+ """Proxies a traceback frame."""
+
+ def __init__(self, tb):
+ self.tb = tb
+
+ def _set_tb_next(self, next):
+ if tb_set_next is not None:
+ tb_set_next(self.tb, next and next.tb or None)
+ self._tb_next = next
+
+ def _get_tb_next(self):
+ return self._tb_next
+
+ tb_next = property(_get_tb_next, _set_tb_next)
+ del _get_tb_next, _set_tb_next
+
+ @property
+ def is_jinja_frame(self):
+ return '__jinja_template__' in self.tb.tb_frame.f_globals
+
+ def __getattr__(self, name):
+ return getattr(self.tb, name)
+
+
+class ProcessedTraceback(object):
+ """Holds a Jinja preprocessed traceback for priting or reraising."""
+
+ def __init__(self, exc_type, exc_value, frames):
+ assert frames, 'no frames for this traceback?'
+ self.exc_type = exc_type
+ self.exc_value = exc_value
+ self.frames = frames
+
+ def chain_frames(self):
+ """Chains the frames. Requires ctypes or the speedups extension."""
+ prev_tb = None
+ for tb in self.frames:
+ if prev_tb is not None:
+ prev_tb.tb_next = tb
+ prev_tb = tb
+ prev_tb.tb_next = None
+
+ def render_as_text(self, limit=None):
+ """Return a string with the traceback."""
+ lines = traceback.format_exception(self.exc_type, self.exc_value,
+ self.frames[0], limit=limit)
+ return ''.join(lines).rstrip()
+
+ @property
+ def is_template_syntax_error(self):
+ """`True` if this is a template syntax error."""
+ return isinstance(self.exc_value, TemplateSyntaxError)
+
+ @property
+ def exc_info(self):
+ """Exception info tuple with a proxy around the frame objects."""
+ return self.exc_type, self.exc_value, self.frames[0]
+
+ @property
+ def standard_exc_info(self):
+ """Standard python exc_info for re-raising"""
+ return self.exc_type, self.exc_value, self.frames[0].tb
+
+
+def make_traceback(exc_info, source_hint=None):
+ """Creates a processed traceback object from the exc_info."""
+ exc_type, exc_value, tb = exc_info
+ if isinstance(exc_value, TemplateSyntaxError):
+ exc_info = translate_syntax_error(exc_value, source_hint)
+ return translate_exception(exc_info)
def translate_syntax_error(error, source=None):
"""If passed an exc_info it will automatically rewrite the exceptions
all the way down to the correct line numbers and frames.
"""
- result_tb = prev_tb = None
initial_tb = tb = exc_info[2].tb_next
+ frames = []
while tb is not None:
# skip frames decorated with @internalcode. These are internal
# calls we can't avoid and that are useless in template debugging
# output.
- if tb_set_next is not None and tb.tb_frame.f_code in internal_code:
- tb_set_next(prev_tb, tb.tb_next)
+ if tb.tb_frame.f_code in internal_code:
tb = tb.tb_next
continue
+ # save a reference to the next frame if we override the current
+ # one with a faked one.
+ next = tb.tb_next
+
+ # fake template exceptions
template = tb.tb_frame.f_globals.get('__jinja_template__')
if template is not None:
lineno = template.get_corresponding_lineno(tb.tb_lineno)
tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
- lineno, prev_tb)[2]
- if result_tb is None:
- result_tb = tb
- prev_tb = tb
- tb = tb.tb_next
+ lineno)[2]
+
+ frames.append(TracebackFrameProxy(tb))
+ tb = next
+
+ # if we don't have any exceptions in the frames left, we have to
+ # reraise it unchanged.
+ # XXX: can we backup here? when could this happen?
+ if not frames:
+ raise exc_info[0], exc_info[1], exc_info[2]
- return exc_info[:2] + (result_tb or initial_tb,)
+ traceback = ProcessedTraceback(exc_info[0], exc_info[1], frames)
+ if tb_set_next is not None:
+ traceback.chain_frames()
+ return traceback
-def fake_exc_info(exc_info, filename, lineno, tb_back=None):
+def fake_exc_info(exc_info, filename, lineno):
"""Helper for `translate_exception`."""
exc_type, exc_value, tb = exc_info
exc_info = sys.exc_info()
new_tb = exc_info[2].tb_next
- # now we can patch the exc info accordingly
- if tb_set_next is not None:
- if tb_back is not None:
- tb_set_next(tb_back, new_tb)
- if tb is not None:
- tb_set_next(new_tb, tb.tb_next)
-
# return without this frame
return exc_info[:2] + (new_tb,)
# for direct template usage we have up to ten living environments
_spontaneous_environments = LRUCache(10)
+# the function to create jinja traceback objects. This is dynamically
+# imported on the first exception in the exception handler.
+_make_traceback = None
+
def get_spontaneous_environment(*args):
"""Return a new spontaneous environment. A spontaneous environment is an
#: must not be modified
shared = False
+ exception_handler = None
+ exception_formatter = None
+
def __init__(self,
block_start_string=BLOCK_START_STRING,
block_end_string=BLOCK_END_STRING,
try:
return Parser(self, source, name, filename).parse()
except TemplateSyntaxError, e:
- from jinja2.debug import translate_syntax_error
- exc_type, exc_value, tb = translate_syntax_error(e, source)
- raise exc_type, exc_value, tb
+ self.handle_exception(sys.exc_info(), source_hint=source)
def lex(self, source, name=None, filename=None):
"""Lex the given sourcecode and return a generator that yields
try:
return self.lexer.tokeniter(source, name, filename)
except TemplateSyntaxError, e:
- from jinja2.debug import translate_syntax_error
- exc_type, exc_value, tb = translate_syntax_error(e, source)
- raise exc_type, exc_value, tb
+ self.handle_exception(sys.exc_info(), source_hint=source)
def preprocess(self, source, name=None, filename=None):
"""Preprocesses the source with all extensions. This is automatically
template = self.from_string(nodes.Template(body, lineno=1))
return TemplateExpression(template, undefined_to_none)
+ def handle_exception(self, exc_info=None, rendered=False, source_hint=None):
+ """Exception handling helper. This is used internally to either raise
+ rewritten exceptions or return a rendered traceback for the template.
+ """
+ global _make_traceback
+ if exc_info is None:
+ exc_info = sys.exc_info()
+ if _make_traceback is None:
+ from jinja2.debug import make_traceback as _make_traceback
+ traceback = _make_traceback(exc_info, source_hint)
+ if rendered and self.exception_formatter is not None:
+ return self.exception_formatter(traceback)
+ if self.exception_handler is not None:
+ self.exception_handler(traceback)
+ exc_type, exc_value, tb = traceback.standard_exc_info
+ raise exc_type, exc_value, tb
+
def join_path(self, template, parent):
"""Join a template with the parent. By default all the lookups are
relative to the loader root so this method returns the `template`
try:
return concat(self.root_render_func(self.new_context(vars)))
except:
- from jinja2.debug import translate_exception
- exc_type, exc_value, tb = translate_exception(sys.exc_info())
- raise exc_type, exc_value, tb
+ return self.environment.handle_exception(sys.exc_info(), True)
def stream(self, *args, **kwargs):
"""Works exactly like :meth:`generate` but returns a
for event in self.root_render_func(self.new_context(vars)):
yield event
except:
- from jinja2.debug import translate_exception
- exc_type, exc_value, tb = translate_exception(sys.exc_info())
- raise exc_type, exc_value, tb
+ yield self.environment.handle_exception(sys.exc_info(), True)
def new_context(self, vars=None, shared=False, locals=None):
"""Create a new :class:`Context` for this template. The vars